Raspberry Piでインターホンの音を検知してLINEに通知する (2)PyAudio録音時の警告・エラーに対処する

# 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%">

「インターホンの音を検知してLINE通知することで、どこにいてもイヤホンをしていても来客に気づきたい」と考えて作成したものをまとめています。
前回「[(1)インターホンの音を録音する](https://qiita.com/nkgwwwww/items/cd0e7433cfc4b2ea0a59)」では、Raspberry PiとPyAudioモジュールを使ってインターホンの音を録音しました。
しかし、その際に警告やエラーが出ることを説明しました。
今回は、これらへの対処方法をまとめます。特にエラー対処は実際に運用する際に重要です。

なかなか解決方法が見つからなかったので需要があるかなあと思う反面、
調べきれていないとも感じている箇所です（実際、どれも根本的な解決ではない）。

装置：Raspberry Pi 4
マイク：[共立プロダクツ MI-305 [USBマイク]](https://www.yodobashi.com/product/100000001004508009/)
プログラム言語：Python3 (PyAudioモジュールを使用。 [PyAudio Documentation](https://people.csail.mit.edu/hubert/pyaudio/docs/))

下記のプログラムを念頭に話を進めます。（今回は.wavに保存するところは省略）

```python:overflow_and_ALSAerror.py
import pyaudio
import wave
input_device_index = 2 # 前回確認したデバイス番号
CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1 # モノラル入力 # 前回確認したmaxInputChannelsが上限
RATE = 44100
RECORD_SECONDS = 5

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

# OSError: [Errno -9981] Input overflowed
上記を実行した際、

    OSError: [Errno -9981] Input overflowed

が表示されることがあり、その場合は

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

のようにCHUNKを増やせば防げることは前回の通り。
しかしCHUNKを大きくしても、何度も繰り返すとたまに出現し、録音が止まる。
今回のように24時間動作していてほしい場合には致命的。

そこで、[PyAudioのdocumentation](https://people.csail.mit.edu/hubert/pyaudio/docs/)を参考に、下記のように書き換える。

```python:wo_overflow.py
import pyaudio
import wave
input_device_index = 2 # 前回確認したデバイス番号
CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1 # モノラル入力 # 前回確認したmaxInputChannelsが上限
RATE = 44100
RECORD_SECONDS = 5

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, exception_on_overflow=False)
    # ↑↑↑
    frames.append(data)
print("Done!")
stream.stop_stream()
stream.close()
p.terminate()
```

stream.readの引数にexception_on_overflow=Falseを加えることで、overflow時のOS Errorを出さないようにする（デフォルトではTrue）。
これでOSError: [Errno -9981] Input overflowedは出ないはず。
ただ、overflowしていることには変わりがないので、その瞬間は切り取られたような音声データになっている（と思う）。
まずはCHUNKを大きくして、ある程度は安定に録音できるようにしてからこの対処をすべきか。

# ALSA関連の警告

冒頭のコードを実行した際、

    ALSA lib confmisc.c:1281:(snd_func_refer) Unable to find definition 'cards.bcm2835_hdmi.pcm.front.0:CARD=0'

で始まる警告がたたみかけてくることは前回の通り（最後にほぼ全文を載せておく）。
録音に問題はないのだが、１画面に収まらない量の警告なのでどうにかしたい。

調べたら、下記のページが役に立ちそう。
「[PyAudio working, but spits out error messages each time](https://stackoverflow.com/questions/7088672/pyaudio-working-but-spits-out-error-messages-each-time)」
質問に対していくつか解決策が寄せられているが、
「All of the above is true and a good solution. I just came here to suggest a nicer way of re-using the error handler code:」
で始まる回答を使用してみた。configファイル系をいじらなくて良さそうなのと、nicer wayということなので。

```python:error_hider.py
from ctypes import *
from contextlib import contextmanager
import pyaudio

ERROR_HANDLER_FUNC = CFUNCTYPE(None, c_char_p, c_int, c_char_p, c_int, c_char_p)

def py_error_handler(filename, line, function, err, fmt):
    pass

c_error_handler = ERROR_HANDLER_FUNC(py_error_handler)

@contextmanager
def noalsaerr():
    asound = cdll.LoadLibrary('libasound.so')
    asound.snd_lib_error_set_handler(c_error_handler)
    yield
    asound.snd_lib_error_set_handler(None)
```

```python:wo_ALSAerror.py
import pyaudio
import wave

from error_hider import noalsaerr # ↑のコードをimport

input_device_index = 2 # 前回確認したデバイス番号
CHUNK = 1024
FORMAT = pyaudio.paInt16
CHANNELS = 1 # モノラル入力 # 前回確認したmaxInputChannelsが上限
RATE = 44100
RECORD_SECONDS = 5

# ↓↓↓ここを変更
with noalsaerr():
    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, exception_on_overflow=False)
    frames.append(data)
print("Done!")
stream.stop_stream()
stream.close()
p.terminate()
```
上記を実行すると、ALSA関連の警告は消える。
"with noalsaerr():"を加えるだけなので非常に簡便。
しかし、下記のjack server関連の警告は引き続き現れる・・・。
これ以上はわからなかったのと、対処前と比べると少ない量なので、ここで妥協することにした。

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

# まとめ
今回は、Raspberry PiとPyAudioを使って録音する際に現れる、警告・エラーへの対処方法をまとめました。
特にOverflowへの対処は運用を始めてから重要さに気づきました。
どれも対症療法であるだけでなく、警告が消えきっていないところもあるので心苦しいですが、
改善が見られたので良かったです。
次回は、前回録音したデータを解析して、インターホンの検知基準を作成します。

その他の記事：
[Raspberry Piでインターホンの音を検知してLINEに通知する (1)インターホンの音を録音する](https://qiita.com/nkgwwwww/items/cd0e7433cfc4b2ea0a59)
[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/)
[PyAudio working, but spits out error messages each time](https://stackoverflow.com/questions/7088672/pyaudio-working-but-spits-out-error-messages-each-time)

# ALSA関連の警告のほぼ全文
    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