ラズパイとUSBマイクを用いたデシベル計算、騒音測定器の作成【Python + pyaudio】

今回はRaspberry PiとUSBマイクを組み合わせて騒音測定器の作成方法を紹介します。

この記事では「Raspberry Pi 3B」とサンワサプライ社のUSBマイクロホン「MM-MCU01BK」を使用します。「Raspberry Pi 3」はLinuxOS相当のOSを動作可能で、USBポートも標準搭載されているため、ハードウェアの増設やドライバーのインストールといった特別な環境整備を行うことなく、USBマイクの入力音声をPythonスクリプトなどから操作することが可能です。

USBマイクの入力音声から騒音レベルを測定することで、下記のような課題解決に応用できると考えています。

  • 騒音対策:騒音レベルをディスプレイ表示し、ユーザーに注意喚起
  • スマートホーム:テレビやスピーカーの音量を一定以下に保つ
  • 空間表現:音楽のパーカッション成分と照明、LEDライトを連動させ、耳だけでなく目でも音楽を楽しめる空間を作成

 

実際にテレビやスピーカーの音量を一定以下に保つために、スマートリモコン化した事例を以下の記事で紹介しています。

関連記事

こんにちは、ほっさまです。 皆さんは、騒音問題について考えることはあるでしょうか。 私はマンションタイプの集合住宅に居住していて、最近はテレワークにより一日のほとんどを家の中で過ごしているため、自分自身や近隣住民が出す生活音に以[…]

eye_catch
広告

 

動作確認済み環境


開発マシン: Raspberry Pi 3 MODEL B
OS: Raspbian Buster with desktop(Release date: 2019-09-26)
プログラミング言語: python3(version=3.7.3)
「Raspberry Pi」は以下の記事をもとに、OSのインストールと環境設定されていることを前提としています。
関連記事

この記事では、Raspberry Piの購入後に実施する以下の2点について、書いています。 1. OS(Raspbian)インストール 2. 初期セットアップ この記事は以下のような方向けのものになります。 Raspberr[…]

raspberry-pi-initial-set-up
関連記事

この記事では、Raspberry Pi 3 MODEL Bを対象に、ロボット製作に適した開発環境のセットアップ手順を紹介します。 このセットアップ手順を実行することで、以下の記事で紹介したようなロボット用のプログラミングが可能になりま[…]

make-jupyter-notebook
※「2. プログラミング言語、およびユーティリティのインストール」と「3-2. OpenCV、Numpy関連パッケージ」のみで、本記事に必要な「Python 3」と「Numpy」がインストールされます。その他の設定は必須ではない認識ですが、省略した上で本記事で紹介するスクリプトが正常に動作するかは未検証です。

 

1. 必要モジュール、パッケージのインストール


USBマイクから音声を取得するためにPythonの「PyAudio」パッケージをインストールします。

1-1. PortAudio modules

「PyAudio」は「PortAudio」というオーディオの再生、録音機能を持つライブラリのラッパーです。「PyAudio」を使用する場合は、「PortAudio」関連のモジュールを事前にインストールしておく必要があります。

「PortAudio」関連のモジュールのインストールは、以下のコマンドで行います。

sudo apt-get install libportaudio2 libportaudiocpp0 portaudio19-dev

 

1-2. PyAudio

「PyAudio」は以下のpipコマンドでインストールできます。

pip3 install pyaudio

 

2. USBマイクの接続、識別番号の確認


2-1. USBマイクの接続

「Raspberry Pi 3B」のUSBポートにUSBマイクを接続します。

connect_usb_mic_to_pi

 

2-2. USBマイクの接続確認

「Raspberry Pi 3B」のターミナルで、「lsusb」コマンドを実行し、USBマイクが認識されていることを確認します。

$ lsusb
Bus 001 Device 004: ID 0d8c:8100 C-Media Electronics, Inc.
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. SMC9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

上記の例では「Device 002」が接続したUSBマイクに該当します。デバイス名からの判断が難しい場合には、USBマイクを接続していない状態の出力結果と比較することで、デバイス名の特定が可能です。

 

2-3. USBマイクの識別番号の確認

「PyAudio」でUSBマイクの入力音声を取得するためには、USBマイクの識別番号を指定する必要があります。認識されている全ての音声入出力デバイスの識別番号は以下のPythonスクリプトを実行することで確認できます。

import pyaudio
p = pyaudio.PyAudio()
for index in range(0, p.get_device_count()):
device_info = p.get_device_info_by_index(index)
print("DEVICE_INDEX:{}, DEVICE_NAME:{}".format(device_info["index"], device_info["name"]))
p.terminate()

(実行結果)

DEVICE_INDEX:0, DEVICE_NAME:bcm2835 HDMI 1: - (hw:0,0)
DEVICE_INDEX:1, DEVICE_NAME:bcm2835 Headphones: - (hw:1,0)
DEVICE_INDEX:2, DEVICE_NAME:USB PnP Sound Device: Audio (hw:2,0)
DEVICE_INDEX:3, DEVICE_NAME:sysdefault
DEVICE_INDEX:4, DEVICE_NAME:output
DEVICE_INDEX:5, DEVICE_NAME:dmix
DEVICE_INDEX:6, DEVICE_NAME:default

上記の例では、USBマイクの識別番号は「2」になります。
「lsusb」と同様に、デバイス名からの判断が難しい場合には、USBマイクを接続していない状態の出力結果と比較することで、デバイス名の特定が可能です。
広告

3. 騒音レベルの測定


3-1. Pythonスクリプト

import pyaudio
import numpy as np
import sys
import time
# check args
if (len(sys.argv) < 2) or (not sys.argv[1].isdecimal()):
print("Please specify input_device_index in integer")
sys.exit(-1)
p = pyaudio.PyAudio()
# set prams
INPUT_DEVICE_INDEX = int(sys.argv[1])
CHUNK = 2 ** 10 # 1024
FORMAT = pyaudio.paInt16
CHANNELS = int(p.get_device_info_by_index(INPUT_DEVICE_INDEX)["maxInputChannels"])
SAMPLING_RATE = int(p.get_device_info_by_index(INPUT_DEVICE_INDEX)["defaultSampleRate"])
RECORD_SECONDS = 1
# amp to db
def to_db(x, base=1):
y=20*np.log10(x/base)
return y
# main loop
def main():
while True:
start = time.time()
stream = p.open(format = FORMAT,
channels = CHANNELS,
rate = SAMPLING_RATE,
input = True,
frames_per_buffer = CHUNK,
input_device_index = INPUT_DEVICE_INDEX
)
# get specified range of data. size of data equals (CHUNK * (SAMPLING_RATE / CHUNK) * RECORD_SECONDS)
data = np.empty(0)
for i in range(0, int(SAMPLING_RATE / CHUNK * RECORD_SECONDS)):
elm = stream.read(CHUNK, exception_on_overflow = False)
elm = np.frombuffer(elm, dtype="int16")/float((np.power(2,16)/2)-1)
data = np.hstack([data, elm])
# calc RMS
rms = np.sqrt(np.mean([elm * elm for elm in data]))
# RMS to db
db = to_db(rms, 20e-6)
stream.close()
elapsed_time = time.time() - start
print("elapsed_time:{:.3f}[sec], DB:{:.3f}[db]".format(elapsed_time, db))
try:
main()
except KeyboardInterrupt:
pass
finally:
p.terminate()

 

(パラメータ解説)

・14行目:コマンドライン引数に、前章で確認した識別番号を指定します。

・15行目:USBマイクから一度に読み取る入力音声のフレーム数は「CHUNK」で指定します。パラメータは後で調整するため、この段階では一般的な「1024」(=2の10乗)を指定します。

・16行目:「FORMAT」に「pyaudio.paInt16」を指定することで、入力音声の量子化ビット数を「16」とします。これにより、入力音声データを[-32,768, 32,767]の値として扱えるようにします。

・17-18行目:入力音声のチャンネル数「CHANNELS」、サンプリングレート「SAMPLING_RATE」は、「get_device_info_by_index」メソッドで取得したデフォルト値を使用します。

・19行目:騒音レベルの計算に使用する音声データのサイズは「RECORD_SECONDS」で指定します。計算に使用するフレーム数は、「SAMPLING_RATE*RECORD_SECONDS」になります。

 

(ロジック解説)

・31-42行目:音声入力部分(open、stream.readメソッド)は「PyAudio」の「Documentation & Examples」を参考に作成していますので、説明は割愛します。

・43行目(分子):入力音声データはバイナリ形式で取得されます。このままでは騒音レベルの計算に使用できないため、「np.frombuffer」メソッドで整数型のNumpy配列に変換します。変換後の配列サイズは「CHUNK」で指定した数と一致します。

・43行目(分母):入力音声データを「int16」の最大値「32,767」で除算することで最大値を「1」とします。これにより、入力音声データが最大値「1」の時の基準音圧レベル「1Pa」に対する騒音レベルが「0dB」となり、USBマイクの入力感度「0dB=1V/Pa」と対応させます。

・46行目:「RECORD_SECONDS」で指定した秒数分のデータから、実効値「RMS」を計算します。「RMS」は二乗平均の平方根を扱います。

・48行目:「RMS」から騒音レベルを計算します。基準音圧レベルは、人間の最小可聴音圧の「20μPa」とします。

 

(出力結果)※10秒間の出力結果
elapsed_time:1.812[sec], DB:61.476[db]
elapsed_time:1.816[sec], DB:63.758[db]
elapsed_time:1.812[sec], DB:57.581[db]
elapsed_time:1.812[sec], DB:56.743[db]
elapsed_time:1.820[sec], DB:58.754[db]
elapsed_time:1.816[sec], DB:53.417[db]
・1ループ当たりの処理時間は平均1.81秒で、パラメータで指定した「RECORD_SECONDS」より80%以上の遅れが発生しています。これは、指定した「CHUNK」が大きすぎるために、入力音声データの確保に時間がかかっているためと考えらえれます。
(参考:https://dsp.stackexchange.com/questions/13728

・騒音レベルはスマートフォンアプリの「騒音測定器」と比較して、最小値「+2dB」、平均値「-3dB」の誤差があります。これは、1ループ当たりの経過時間が「RECORD_SECONDS」で指定した時間より遅れているために、データの取りこぼしがあったためと考えらえれます。

スマートフォンアプリで測定した騒音レベルの最大値は、スマートフォン操作時に跳ね上がってしまう傾向があったため、比較対象から除外しています。

 

(「騒音測定器」での計測結果)
noise_level_by_smart_phone1

 

3-2. パラメータ調整

「CHUNK」の値を小さくすることで、1ループ当たりの処理時間を「RECORD_SECONDS」で指定した値に近づけます。

下表の結果から、「CHUNK」が「512」以下では処理時間にほとんど誤差がないため、1ループ当たりの処理時間は「入力音声データの長さ+騒音レベルの計算にかかる時間」で収束しているものと考えらえれます。

騒音レベルの比較から、本記事のデバイスを使用する限りでは、「CHUNK」に「128」を設定するのが妥当と考えられます。

(パラメータごとの処理時間、騒音レベル)

CHUNK 処理時間[sec] (平均)
騒音レベル/Pythonスクリプト 騒音レベル/スマートフォンアプリ
    最小 平均 最小 平均
128 1.11 50.6 53.1 51 54
256 1.12 46.9 49.0 45 48
512 1.13 52.6 54.0 48 53
1024 1.81 53.4 58.6 52 61

 

所感


USBマイクの入力音声から騒音レベルを測定した結果は、スマートフォンアプリで測定した騒音レベルと比較して、平均値が1dB未満に収まっているため、実用に足るものと考えられます。

 

記事内で紹介したデバイス


広告