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

# 2025/02/08追記
本記事(4)で使用するLINE Notifyが、2025年3月31日にサービスを終了するとのことです。
[LINE Notify提供終了のお知らせ](https://notify-bot.line.me/closing-announce)
LINEの別サービスへの置き換えもできるようなので、他の記事を参考に読み替えていただければと思います。
参考：[LINE Notifyがサービス終了するので、LINE公式アカウントに置き換える](https://qiita.com/jksoft/items/4d57a9282a56c38d0a9c)

# はじめに
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629459/7efdc294-c1ae-45a8-ad83-17730d64ff87.png" width="50%">

住居に必ず備わっているインターホン。
・聞こえづらい部屋がある
・イヤホンをしていると聞こえない
・外出中の来客を把握したい
という課題・要望に対応すべく、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](https://people.csail.mit.edu/hubert/pyaudio/docs/))


# Terminalで接続の確認
参考：[Raspberry Pi オーディオまとめ](https://www.ukeyslabo.com/raspberry-pi/audio/)
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をインストール](https://plaza.rakuten.co.jp/qualis00/diary/202105150000/)
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."とある。)
下記を使って確認。

```python: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を使ってみる](https://qiita.com/yukky-k/items/0d18ec22420e8b35d0ac)

```python: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 オーディオまとめ](https://www.ukeyslabo.com/raspberry-pi/audio/)にあるように、ALSA（Advanced Linux Sound Architecture）のこと）

一方で、

    OSError: [Errno -9981] Input overflowed

が表示される場合は、エラーなので、.wavは保存されない。
これは、
    
    CHUNK = 1024 * 4 # または、*8, *16, ...

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

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

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

その他の記事：
[Raspberry Piでインターホンの音を検知してLINEに通知する (2)PyAudio録音時の警告・エラーに対処する](https://qiita.com/nkgwwwww/items/27e512ab6ef3eb9d69bc)
[Raspberry Piでインターホンの音を検知してLINEに通知する (3)検知基準を検討する](https://qiita.com/nkgwwwww/items/96f664aad4eff5b6cd50)
[Raspberry Piでインターホンの音を検知してLINEに通知する (4)検知してLINEに通知する](https://qiita.com/nkgwwwww/items/ff1966444200890f1847)

# 参考
[PyAudio Documentation](https://people.csail.mit.edu/hubert/pyaudio/docs/)
[Raspberry Pi オーディオまとめ](https://www.ukeyslabo.com/raspberry-pi/audio/)
[raspberry Piにpyaudioをインストール](https://plaza.rakuten.co.jp/qualis00/diary/202105150000/)
https://forums.raspberrypi.com/viewtopic.php?t=71062
[RaspberryPi + Python3でPyaudioとdocomo音声認識APIを使ってみる](https://qiita.com/yukky-k/items/0d18ec22420e8b35d0ac)
[PyAudioの基本メモ1](https://takeshid.hatenadiary.jp/entry/2016/01/10/130729)

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

    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