Raspberry Piでインターホンの音を検知してLINEに通知する (1)インターホンの音を録音する


住居に必ず備わっているインターホン。
・聞こえづらい部屋がある
・イヤホンをしていると聞こえない
・外出中の来客を把握したい
という課題・要望に対応すべく、Raspberry Piを使ってLINEに通知するシステムを作りました。
LINEであれば、スマホがブーブブッと振動して気づきやすいですよね。

少し調べたところ、インターホンのLEDや画面の点灯で検出するものが多かったですが、
自分のアパートのインターホンだと難しそうだったので、音で検知するようにしました。
FFTを使って、インターホンに対応する周波数(音の高さ)を検出します。
いろんなサイトから組み合わせる形になったので、まとめます。

今回は、Raspberry Piを使ってインターホンの音を録音するところまで。
このデータを使って、音の検知基準を作成します。
※準備では.wavファイルに保存していますが、運用時には保存せずに処理します。

装置:Raspberry Pi 4
マイク:共立プロダクツ MI-305 [USBマイク]
https://www.yodobashi.com/product/100000001004508009/
※Raspberry Piにはジャックマイクは使用できないため、USBマイクが必須。ジャックはOutputのみ。
プログラム言語:Python3 (PyAudioモジュールを使用。 PyAudio Documentation)

Terminalで接続の確認

参考:Raspberry Pi オーディオまとめ
Raspberry PiのTerminalを開き、デバイスをチェック。

pi@raspberrypi:~ $ cat /proc/asound/devices
  0: [ 0]   : control
 16: [ 0- 0]: digital audio playback
 32: [ 1]   : control
 33:        : timer
 48: [ 1- 0]: digital audio playback
 64: [ 2]   : control
 88: [ 2- 0]: digital audio capture

88の"~ capture"がUSBマイクの抜き差しに応じて現れたり消えたりしたら認識されているはず。
64の"control"も同様にマイクに反応して出現するみたい。
Terminalのままマイクテストを行いたい場合は、上記リンクの「マイクチェック」を参照。

PyAudioのインストール

参考:raspberry Piにpyaudioをインストール
PyAudioを使って録音していきます。pipによるインストールが必要。
上記リンクを参考に、下記のようにsudoをすればOK。

sudo apt-get install python-dev portaudio19-dev
sudo pip3 install pyaudio

PyAudioで使用するデバイス番号を確認

参考:https://forums.raspberrypi.com/viewtopic.php?t=71062
上記Terminalで表示されるデバイス番号と、PyAudioで使用するデバイス番号は関係無い。
(参考リンクにも、"Device indexes in pyaudio are pretty arbitrary."とある。)
下記を使って確認。

CheckDevID.py
# https://forums.raspberrypi.com/viewtopic.php?t=71062
import pyaudio
p = pyaudio.PyAudio()
for i in range(p.get_device_count()):
  dev = p.get_device_info_by_index(i)
  print((i,dev['name'],dev['maxInputChannels']))
# (0, 'bcm2835 HDMI 1: - (hw:0,0)', 0)
# (1, 'bcm2835 Headphones: - (hw:1,0)', 0)
# (2, 'USB PnP Sound Device: Audio (hw:2,0)', 1)
# (3, 'sysdefault', 0)
# (4, 'lavrate', 0)
# (5, 'samplerate', 0)
# (6, 'speexrate', 0)
# (7, 'pulse', 32)
# (8, 'upmix', 0)
# (9, 'vdownmix', 0)
# (10, 'dmix', 0)
# (11, 'default', 32)

自分の場合、USBマイクは2番。'maxInputChannels'が1なのはモノラルであることを示している。

PyAudioでインターホンの音を録音する

参考:RaspberryPi + Python3でPyaudioとdocomo音声認識APIを使ってみる

record.py
import pyaudio
import wave

input_device_index = 2 # 先ほど確認したデバイス番号

CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1 # モノラル入力 # 先ほど確認したmaxInputChannelsが上限
RATE = 44100
RECORD_SECONDS = 5
WAVE_OUTPUT_FILENAME = "output.wav"

p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,
                channels=CHANNELS,
                input_device_index = input_device_index,
                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!")

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()

これで"output.wav"に音が録音されているはず。
ジャックにイヤホンを接続して聞いてみる。
問題無さそうなら、プログラム開始と同時にインターホンを鳴らして録音する。
(スマホなどにも録音しておくとチェックの時に便利。)

なお、ここで怒涛の警告メッセージが出た(長いので最後に載せる)。
エラーではなく警告メッセージなので、これが現れても、
"Recording..."~"Done!"の部分は表示され、.wavは正常に保存されるはず。
しかしこの量の警告メッセージは煩わしいので、ALSAに関わる部分を表示させなくする方法を見つけたので次回まとめる。
(ALSAとは、Raspberry Pi オーディオまとめにあるように、ALSA(Advanced Linux Sound Architecture)のこと)

一方で、

OSError: [Errno -9981] Input overflowed

が表示される場合は、エラーなので、.wavは保存されない。
これは、

CHUNK = 1024 * 4 # または、*8, *16, ...

と、CHUNKを大きくすることで回避できる。
CHUNKとは、音源から1回読み込むときのデータサイズのこと(PyAudioの基本メモ1)。
CHUNKはdata = stream.read(CHUNK)の部分で使っているが、おそらく、読み込み量・頻度が小さく、読み込まれないまま消えていったデータがあるときに上記エラーが出ると思われる。したがって、CHUNKを大きくすればOK。

しかし、運用するためにずっと回していると、CHUNKを大きくしても突発的に上記エラーで止まることがある。
overflowを無理矢理回避する方法も次回にまとめる。
今回の録音程度では、CHUNKを大きくしていれば問題無いはず。

まとめ

Raspberry Pi + PyAudioを使ってインターホンの音を録音することができました。
次回は、この音を解析する・・・前に、ALSAの警告メッセージとoverflowエラーの対処についてまとめます。(ここが調べるのに一番苦労したところ・・・)

その他の記事:
Raspberry Piでインターホンの音を検知してLINEに通知する (2)PyAudio録音時の警告・エラーに対処する
Raspberry Piでインターホンの音を検知してLINEに通知する (3)検知基準を検討する
Raspberry Piでインターホンの音を検知してLINEに通知する (4)検知してLINEに通知する

参考

PyAudio Documentation
Raspberry Pi オーディオまとめ
raspberry Piにpyaudioをインストール
https://forums.raspberrypi.com/viewtopic.php?t=71062
RaspberryPi + Python3でPyaudioとdocomo音声認識APIを使ってみる
PyAudioの基本メモ1

文中で述べた警告メッセージ

ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.front.0:CARD=0'
ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM front
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.surround51.0:CARD=0'
...(中略)...
ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM surround71
ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.iec958.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2'
ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM iec958
ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.iec958.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2'
ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM spdif
ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.iec958.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2'
ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM spdif
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.hdmi
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.modem
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline
ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'defaults.bluealsa.device'
ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5036:(snd_config_expand) Args evaluate error: No such file or directory
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM bluealsa
ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'defaults.bluealsa.device'
ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5036:(snd_config_expand) Args evaluate error: No such file or directory
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM bluealsa
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
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
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
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
ALSA lib pcm_oss.c:377:(_snd_pcm_oss_open) Unknown field port
ALSA lib pcm_oss.c:377:(_snd_pcm_oss_open) Unknown field port
ALSA lib pcm_a52.c:823:(_snd_pcm_a52_open) a52 is only for playback
ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.iec958.0:CARD=0,AES0=6,AES1=130,AES2=0,AES3=2'
ALSA lib conf.c:4568:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5047:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2565:(snd_pcm_open_noupdate) Unknown PCM iec958:{AES0 0x6 AES1 0x82 AES2 0x0 AES3 0x2  CARD 0}
ALSA lib pcm_usb_stream.c:486:(_snd_pcm_usb_stream_open) Invalid type for card
ALSA lib pcm_usb_stream.c:486:(_snd_pcm_usb_stream_open) Invalid type for card
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
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock
JackShmReadWritePtr::~JackShmReadWritePtr - Init not done for -1, skipping unlock