読者です 読者をやめる 読者になる 読者になる

RaspberryPiでマイク録音してみる【ヒミツのクマちゃん その2】

コンピュータ 開発 電子工作 RaspberryPi python API

RaspberryPiでマイク録音してみる

前回のエントリでヒミツのクマちゃんが占いをしてくれるようになったので、今度は会話したくなってきました。そこでその第一歩としてRaspberryPiにマイクを接続しようと思います。

qiita.com

こちらの記事を参考にしています。

以下のマイクが実績があるとのことだったので購入することにしました。

値段的にはピン形状のマイク(アナログ)でもいいかなと思うのですが、ノイズが結構入るかなと思ったのでUSB接続にしました。

マイクを接続してみる。

マイクをUSBに接続してみてlsusbで認識状況を確認します。

【接続前】

$ lsusb
Bus 001 Device 006: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 001 Device 005: ID 2019:1201 PLANEX
Bus 001 Device 004: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp. LAN9500 Ethernet 10/100 Adapter / SMSC9512/9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

【接続後】

$ lsusb
Bus 001 Device 007: ID 0d8c:0134 C-Media Electronics, Inc.
Bus 001 Device 006: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 001 Device 005: ID 2019:1201 PLANEX
Bus 001 Device 004: ID 05e3:0608 Genesys Logic, Inc. USB-2.0 4-Port HUB
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp. LAN9500 Ethernet 10/100 Adapter / SMSC9512/9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

比較すると一番上の行に以下のエントリが追加されていることがわかります。

Bus 001 Device 007: ID 0d8c:0134 C-Media Electronics, Inc.

以前使用していたUSBスピーカーもC-Media製だったような気がするのでC-Media製の音声デバイスって多いみたい。

何もしなくてもデバイスの認識はされたような感じですね。

通常であれば

/etc/modprobe.d/alsa-base.confを編集して、使用するオーディオモジュールの優先順位の変更して、RaspberryPiのリブート

の処理を行うことになるのですが、実は行わなくても録音は可能です。

alsaを使用して録音してみる。

基本的なテストはCLIalsaのコマンド群を使用することで確認ができます。

録音に使用できるデバイス一覧の確認

$ arecord -l
**** ハードウェアデバイス CAPTURE のリスト ****
カード 1: Device [USB PnP Audio Device], デバイス 0: USB Audio [USB Audio]
  サブデバイス: 1/1
  サブデバイス #0: subdevice #0

複数ある場合にはいくつかのデバイスが列挙できますが、今回は1つしか存在していないのでこのような表示になります。

ここで覚えて置かなければならなのは カード番号が1、デバイス番号が0であることです。

先程オーディオモジュールの優先順位を変更を飛ばしたといいましたが、デフォルト値ではなく、デバイス指定を行うので不要でしたということになります。

録音を行う

$ arecord -D plughw:1,0 test.wav
録音中 WAVE 'test.wav' : Unsigned 8 bit, レート 8000 Hz, モノラル
^Cシグナル 割り込み で中断...

wavファイルはデフォルトの設定で録音します(データは8bit、サンプリングレートは8kHzのモノラル)。 コマンドスイッチで-D plughw:1,0としている部分がデバイスを明示的にしている部分となります。明示的に指定しないとデフォルト値(デバイスの優先順位が高いものを)探しに行くので注意が必要です。

先程のarecord -lコマンドの結果で、「カード番号が1、デバイス番号が0」と検出されていたので、この値を-Dで渡しています。

再生する

再生に関しては以下となります。

$ aplay test.wav
再生中 WAVE 'test.wav' : Unsigned 8 bit, レート 8000 Hz, モノラル

録音のパラメータも正しく認識されているので問題ありません。

録音ボリューム調整

録音すると音量が小さいことがあります。その場合には以下で調整することになります。

$ amixer sset Mic 46  -c 1
Simple mixer control 'Mic',0
  Capabilities: cvolume cvolume-joined cswitch cswitch-joined
  Capture channels: Mono
  Limits: Capture 0 - 62
  Mono: Capture 46 [74%] [14.62dB] [on]

ここで-cで渡している番号カード番号となるので、arecord -lコマンドの結果で、「カード番号が1、デバイス番号が0」と検出されていたので、カード番号の1を渡し、-c 1となります。

最小は0で最大は62まで指定できるようです。

録音ボリュームの値を確認するだけであれば以下で確認できます。

$ amixer sget Mic -c 1
Simple mixer control 'Mic',0
  Capabilities: cvolume cvolume-joined cswitch cswitch-joined
  Capture channels: Mono
  Limits: Capture 0 - 62
  Mono: Capture 46 [74%] [14.62dB] [on]

これでデバイスの基本的な機能の確認ができました。

pythonから録音してみる

これからは毎度のことですが、いつも通りpythonから使用できるかを確認することになります。ネットを調べるとpyaudioというモジュールを使うと良さそうです。

people.csail.mit.edu

pipでインストールしてみる

ではpipコマンドを使用してインストールしてみます。

$ pip install pyaudio
Downloading/unpacking pyaudio
  Downloading PyAudio-0.2.9.tar.gz (289kB): 289kB downloaded
  Running setup.py (path:/tmp/pip-build-uNj4Vs/pyaudio/setup.py) egg_info for package pyaudio

    warning: no files found matching '*.c' under directory 'test'
Installing collected packages: pyaudio
  Running setup.py install for pyaudio
    building '_portaudio' extension
    arm-linux-gnueabihf-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fno-strict-aliasing -D_FORTIFY_SOURCE=2 -g -fstack-protector-strong -Wformat -Werror=format-security -fPIC -I/usr/include/python2.7 -c src/_portaudiomodule.c -o build/temp.linux-armv6l-2.7/src/_portaudiomodule.o
    src/_portaudiomodule.c:28:20: fatal error: Python.h: そのようなファイルやデ ィレクトリはありません
     #include "Python.h"
                        ^
    compilation terminated.
    error: command 'arm-linux-gnueabihf-gcc' failed with exit status 1
    Complete output from command /usr/bin/python -c "import setuptools, tokenize;__file__='/tmp/pip-build-uNj4Vs/pyaudio/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-RuGTpw-record/install-record.txt --single-version-externally-managed --compile:
    running install

running build

running build_py

creating build

creating build/lib.linux-armv6l-2.7

copying src/pyaudio.py -> build/lib.linux-armv6l-2.7

running build_ext

building '_portaudio' extension

creating build/temp.linux-armv6l-2.7

creating build/temp.linux-armv6l-2.7/src

arm-linux-gnueabihf-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fno-strict-aliasing -D_FORTIFY_SOURCE=2 -g -fstack-protector-strong -Wformat -Werror=format-security -fPIC -I/usr/include/python2.7 -c src/_portaudiomodule.c -o build/temp.linux-armv6l-2.7/src/_portaudiomodule.o

src/_portaudiomodule.c:28:20: fatal error: Python.h: そのようなファイルやディレ クトリはありません

 #include "Python.h"

                    ^

compilation terminated.

error: command 'arm-linux-gnueabihf-gcc' failed with exit status 1

----------------------------------------
Cleaning up...
Command /usr/bin/python -c "import setuptools, tokenize;__file__='/tmp/pip-build-uNj4Vs/pyaudio/setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\r\n', '\n'), __file__, 'exec'))" install --record /tmp/pip-RuGTpw-record/install-record.txt --single-version-externally-managed --compile failed with error code 1 in /tmp/pip-build-uNj4Vs/pyaudio
Traceback (most recent call last):
  File "/usr/bin/pip", line 9, in <module>
    load_entry_point('pip==1.5.6', 'console_scripts', 'pip')()
  File "/usr/lib/python2.7/dist-packages/pip/__init__.py", line 248, in main
    return command.main(cmd_args)
  File "/usr/lib/python2.7/dist-packages/pip/basecommand.py", line 161, in main
    text = '\n'.join(complete_log)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 57: ordinal not in range(128)

エラー?

先程のサイトをみると

pip will download the PyAudio source and build it for your system. Be sure to install the portaudio library development package (portaudio19-dev) and the python development package (python-all-dev) beforehand. For better isolation from system packages, consider installing PyAudio in a virtualenv.

という記述が。インストール時にビルドするので事前にportaudio19-devpython-all-devのパッケージは事前に入れてねということみたいです。PyAudioはportaudioの機能を使用するモジュールなので確かに必要ですね。

今回はお手軽さを目指したいのでapt-getでインストールします。

apt-getでインストールしてみる

$ sudo apt-get install python-pyaudio python3-pyaudio

パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
以下のパッケージが自動でインストールされましたが、もう必要とされていません:
  gyp libasn1-8-heimdal libc-ares-dev libc-ares2 libgssapi3-heimdal
  libhcrypto4-heimdal libheimbase1-heimdal libheimntlm0-heimdal
  libhx509-5-heimdal libjs-node-uuid libjs-underscore libkrb5-26-heimdal
  libroken18-heimdal libssl-dev libssl-doc libv8-3.14-dev libv8-3.14.5
  libwind0-heimdal rlwrap
これを削除するには 'apt-get autoremove' を利用してください。
提案パッケージ:
  python-pyaudio-doc
以下のパッケージが新たにインストールされます:
  python-pyaudio python3-pyaudio
アップグレード: 0 個、新規インストール: 2 個、削除: 0 個、保留: 181 個。
46.8 kB のアーカイブを取得する必要があります。
この操作後に追加で 194 kB のディスク容量が消費されます。
取得:1 http://mirrordirector.raspbian.org/raspbian/ jessie/main python-pyaudio armhf 0.2.8-1 [23.2 kB]
取得:2 http://mirrordirector.raspbian.org/raspbian/ jessie/main python3-pyaudio armhf 0.2.8-1 [23.6 kB]
46.8 kB を 1秒 で取得しました (30.8 kB/s)
以前に未選択のパッケージ python-pyaudio を選択しています。
(データベースを読み込んでいます ... 現在 129542 個のファイルとディレクトリがインストールされています。)
.../python-pyaudio_0.2.8-1_armhf.deb を展開する準備をしています ...
python-pyaudio (0.2.8-1) を展開しています...
以前に未選択のパッケージ python3-pyaudio を選択しています。
.../python3-pyaudio_0.2.8-1_armhf.deb を展開する準備をしています ...
python3-pyaudio (0.2.8-1) を展開しています...
python-pyaudio (0.2.8-1) を設定しています ...
python3-pyaudio (0.2.8-1) を設定しています ...

python3はまだ使用する気はないですが、一応入れます。

PyAudioで録音を行う

公式サイトのサンプルを実験してみます。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""PyAudio example: Record a few seconds of audio and save to a WAVE file."""

import pyaudio
import wave

CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"

p = pyaudio.PyAudio()

stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK)

print("* recording")

frames = []

for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
    data = stream.read(CHUNK)
    frames.append(data)

print("* done recording")

stream.stop_stream()
stream.close()
p.terminate()

wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()

これを実行させると…

$ python pyaudio_sample.py
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
Expression 'parameters->channelCount <= maxChans' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 1514
Expression 'ValidateParameters( inputParameters, hostApi, StreamDirection_In )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 2818
Traceback (most recent call last):
  File "pyaudio_sample.py", line 19, in <module>
    frames_per_buffer=CHUNK)
  File "/usr/lib/python2.7/dist-packages/pyaudio.py", line 747, in open
    stream = Stream(self, *args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/pyaudio.py", line 442, in __init__
    self._stream = pa.open(**arguments)
IOError: [Errno Invalid number of channels] -9998

おや?エラーになってしまいました。エラーメッセージをみるとbuffer関連でエラーが出ています。

  File "pyaudio_sample.py", line 19, in <module>
    frames_per_buffer=CHUNK)

いろいろ実験を行った結果録音のパラメータがハードウエアに対応していなかったりするとこのようなエラーがでるようです。そこでパラメータを以下のように変更してみます。

【冒頭の設定部分】

CHUNK = 1024 * 2
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"

バッファのサイズを倍、チャンネル数をモノラル、レートを16kHzへ変更してみました。

再度実行を行うと

$ python pyaudio_sample.py
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started
* recording
* done recording

いろいろalsaやportaudioからアラートは出ていますが、無事に録音できました。

PyAudioで再生を行う

PyAudioは再生ももちろんできるのですが… 公式サイトの再生のサンプルを使ってみます。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""PyAudio Example: Play a WAVE file."""

import pyaudio
import wave
import sys

CHUNK = 1024

if len(sys.argv) < 2:
    print("Plays a wave file.\n\nUsage: %s filename.wav" % sys.argv[0])
    sys.exit(-1)

wf = wave.open(sys.argv[1], 'rb')

p = pyaudio.PyAudio()

stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                channels=wf.getnchannels(),
                rate=wf.getframerate(),
                output=True)

data = wf.readframes(CHUNK)

while data != '':
    stream.write(data)
    data = wf.readframes(CHUNK)

stream.stop_stream()
stream.close()

p.terminate()

これを実行すると…

$ python pyaudio_sample_play.py output.wav
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
Cannot connect to server socket err = No such file or directory
Cannot connect to server request channel
jack server is not running or cannot be started

こちらもいろいろアラートが表示されますが、再生はできます…しかし、なんかノイズのっています。aplayで再生するとノイズが乗っていないので再生時にノイズがのっているようですが、原因はわかりませんでした。(処理能力などの問題でしょうか。)

単に再生するだけならpygameでも再生はできるのでそっちを使ってもいいかもしれません。(ループなど再生機能が多機能なので)

今回はpythonから録音ができたので、次は会話ができるように拡張をしていきたいと思います。