Python用ライブラリ「PyAudio」を使用して、マイクから録音・スピーカーから再生・取得した音声のリアルタイム分析まで行ってみました。
準備ライブラリをインストールする際、依存ライブラリが存在することに注意する必要があります。
単に「pip install pyaudio
」だけでは、依存ライブラリ不足でインストールが止まってしまうことがあるからです。
そのため、Mac OSの場合「brew install portaudio
」、Linuxの場合「sudo apt-get install portaudio19-dev python-all-dev
」が必要になります。詳しくは他の方の記事を参照してください。
まず、PyAudio
型のインスタンスを作成する必要があります。PyAudio
型には__enter__
や__exit__
が実装されていませんので、破棄する際は明示的にterminate()
メソッドを叩く必要があります。
import pyaudio
p = pyaudio.PyAudio()
# (この間に、PyAudioによる処理を行う)
p.terminate()
次に、PyAudio
型のインスタンスから実行できる処理について示します。
利用できるデバイスについて調べる
録音用や再生用に使用できるオーディオデバイスの一覧を取得できます。
ただし、APIとして用意されているのは「デバイスの数を取得する」メソッドと「指定したインデックスのデバイスの情報を取得する」メソッドであり、「デバイスの一覧を取得する」メソッドは無いので注意が必要です。
for index in range(0, p.get_device_count()):
print(p. get_device_info_by_index(index))
また、PyAudioはPortAudioというクロスプラットフォームなオーディオライブラリのラッパーです。PortAudioでは
- オーディオデバイスの種類を「Host API(大分類)」
- Host APIが持つオーディオインタフェースを「Device(小分類)」
と呼んで管理しており、PyAudioでもそれを参照して操作できます。
for host_index in range(0, p.get_host_api_count()):
print(p. get_host_api_info_by_index(host_index))
for device_index in range(0, p. get_host_api_info_by_index(host_index)['deviceCount']):
print(p.get_device_info_by_host_api_device_index(host_index, device_index))
より詳しく理解するため、コンピューターのオーディオデバイスが以下の表の状態だった場合で説明します。
host_api index | device_index 1 | device_index 2 | name |
---|---|---|---|
0 | 0 | 0 | A |
0 | 1 | 1 | B |
0 | 2 | 2 | C |
1 | 0 | 3 | D |
1 | 1 | 4 | E |
2 | 0 | 5 | F |
# Host APIの総数を返す(この値は3)
host_api_count = p.get_host_api_count()
# 指定したHost APIの情報を返す
host_api_info1 = p.get_host_api_info_by_index(0)
host_api_info2 = p.get_host_api_info_by_index(1)
# Host APIの情報から、それに連なったDeviceの数を返す
device_count1 = host_api_info1['deviceCount'] # host_api_index=0なので、この値は3
device_count2 = host_api_info2['deviceCount'] # host_api_index=1なので、この値は2
# 指定したHost APIの、指定したDeviceの情報を返す
# 第二引数は、上記の表で言えば「device_index 1」に相当
device_info1 = p.get_device_info_by_host_api_device_index(0, 0) # 値はA
device_info2 = p.get_device_info_by_host_api_device_index(0, 1) # 値はB
device_info3 = p.get_device_info_by_host_api_device_index(1, 0) # 値はD
# デバイスの総数を返す(この値は6)
device_count = p.get_device_count()
# 指定したデバイスの情報を返す
# 引数は、上記の表で言えば「device_index 2」に相当
device_info4 = p.get_device_info_by_index(0) # 値はA
device_info5 = p.get_device_info_by_index(1) # 値はB
device_info6 = p.get_device_info_by_index(3) # 値はD
ここで、get_host_api_info_by_index()
およびget_device_info_by_index()
から取得できる、Host APIの情報・Deviceの情報はそれぞれ次の通りです。
# Host APIの情報。PaHostApiInfo構造体に準拠している。詳しくはこちら↓
# http://portaudio.com/docs/v19-doxydocs/structPaHostApiInfo.html
{
'defaultInputDevice': 0, # デフォルトの入力デバイスの「device_index 2」
'defaultOutputDevice': 1, # デフォルトの出力デバイスの「device_index 2」
'deviceCount': 2, # Host APIが持つオーディオデバイスの数
'index': 0, # Host APIのインデックス
'name': 'Core Audio', # Host APIの名称
'structVersion': 1, # ?
'type': 5 # Host APIの種類。例えば「5」は「paCoreAudio(Mac OSのCoreAudio)」を指す
}
# Deviceの情報。PaDeviceInfo構造体に準拠している。詳しくはこちら↓
# http://portaudio.com/docs/v19-doxydocs/structPaDeviceInfo.html
{
# 非インタラクティブな用途(wavファイルの再生など)におけるデフォルトの入力レイテンシ
'defaultHighInputLatency': 0.01310657596371882,
# 非インタラクティブな用途(wavファイルの再生など)におけるデフォルトの出力レイテンシ
'defaultHighOutputLatency': 0.1,
# インタラクティブな用途におけるデフォルトの入力レイテンシ
'defaultLowInputLatency': 0.0029478458049886623,
# インタラクティブな用途におけるデフォルトの出力レイテンシ
'defaultLowOutputLatency': 0.01,
# デフォルトのサンプリングレート
'defaultSampleRate': 44100.0,
'hostApi': 0, # Deviceが属しているHost APIのインデックス
'index': 0, # 上記の表で言えば「device_index 2」に相当
'maxInputChannels': 2, # 最大の入力チャンネル数。0なら入力を受け付けない
'maxOutputChannels': 0, # 最大の出力チャンネル数。0なら出力を受け付けない
'name': 'Built-in Microphone', # Deviceの名称
'structVersion': 2 # ?
}
なお、get_host_api_info_by_type()
を使用すれば、Host APIの種類を指定してHost APIの情報を取得できます。
ですが、Host APIの種類を表す数字(PortAudioではenum PaHostApiTypeId
)から、「pyaudio.paCoreAudio
」などの種類を逆引きするAPIはありませんので、必要なら自分で作りましょう。
ちなみに、デフォルトで使用するHost API、デフォルトで使用する入力・出力Deviceの情報は、それぞれget_default_host_api_info()
、get_default_input_device_info(),
、get_default_output_device_info()
から取得できます。
録音/再生用途で使用する
PyAudioの場合、録音/再生を行うため、デバイスからStream
型のインスタンスを取得し、それに対して操作を行うことで処理を行います。Stream
型のインスタンスは、生成時に「入力用に使うか」「出力用に使うか」「入力と出力の両方に使うか」を指定できます。この際、「入力専用のStreamを作成し、処理後に出力専用のStreamに渡す」といったことも可能です。
また、Stream
型のインスタンスを生成する際、入力用・出力用にどのデバイスを使用するかを設定できます(設定しないとデフォルトのデバイスが使用される)。
# Streamを開く(ブロッキング処理の場合)
stream = p.open(format=pyaudio.paInt16,
channels=2,
rate=44100,
input=True,
output=True,
input_device_index=0,
output_device_index=1,
frames_per_buffer=4096)
# Streamを開く(非ブロッキング処理の場合)
stream = p.open(format=pyaudio.paInt16,
channels=2,
rate=44100,
input=True,
output=True,
input_device_index=0,
output_device_index=1,
frames_per_buffer=4096,
stream_callback=func)
例:WAVファイルを読み込んで再生する
典型的なブロッキング処理です。出力用のStreamを開いておき、読み込んだWAVEファイルを順次書き込みます。
このコードはPyAudioの公式ドキュメントにあるものを参考にしています。
import pyaudio
import wave
# チャンクサイズ(粒度)
CHUNK_SIZE = 1024
# WAVファイルを開く
wf = wave.open('test.wav', 'rb')
# PyAudioインスタンスを作成する
p = pyaudio.PyAudio()
# Streamを開く。フォーマット・チャンネル・サンプリングレートをWAVファイルと
# 合わせているが、合わせなくても再生は行える。
# ちなみにフォーマットとはビット深度のことであり、
# 8bitなら「p.get_format_from_width(1)」、
# 16bitなら「p.get_format_from_width((2)」とバイト数で設定することに注意
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True)
# データをチャンクサイズだけ読み込む
data = wf.readframes(CHUNK_SIZE)
# Streamに読み取ったデータを書き込む=再生する
while len(data) > 0:
# Streamに書き込む
stream.write(data)
# 再度チャンクサイズだけ読み込む。これを繰り返す
data = wf.readframes(CHUNK_SIZE)
# Streamを止めて、closeする。closeしなければ、start_stream()で再開できる
stream.stop_stream()
stream.close()
# PyAudioインスタンスを破棄する
p.terminate()
例:録音した音声の音量(RMS)を計算して随時表示する
典型的な非ブロッキング処理です。入力用のStreamを開くのですが、その際にコールバック関数を設定するのがポイントです。
コールバック関数には「(in_data, frame_count, time_info, status)」という4種類の引数を設定し、「(out_data, flag)」というタプルを戻り値として返します。それぞれの引数の意味は次の通り。
- in_data……bytes型。その時録音された音声データがバイナリ形式て返ってくる。WAVフォーマットは「リトルエンディアン」「ステレオだとLRLRの順番」「ビット深度が8bitなら1バイト分、16bitなら2バイト分の塊」なので、structを駆使してバイナリを解析しよう
- frame_count……int型。その時録音された音声データの要素数。in_dataのバイト数と必ずしも一致するわけではないので、例えばステレオ・16bitならframe_count×4=len(in_data)となる
- time_info……dict型。「入力バッファから入力した時刻」「出力バッファに出力した時刻」「現在時刻」が秒単位(恐らくOS起動時からの経過時間)で書き込まれている。他のオーディオデバイスとの時刻合わせなどに使える
- status……int型。公式ドキュメントにあるように、現在の状態が書き込まれている
- out_data……bytes型。in_data型と書式は同じ。
p.open
した際にoutput=True
だったなら当然書き込むが、そうでない場合はNone
でも渡せばいい - flag……int型。現在の録音状況について、続行する・中断する・終了するから選べる。後々
stream.stop_stream()
する予定がある場合、単にpyaudio_ex.paContinue
だけ返していてもいい
import pyaudio
import struct
def callback(in_data, frame_count, time_info, status):
# bytes型を配列に変換する
# (とりあえず8bit・モノクロだとした例を書く。
# データは1バイトづつであり、0〜255までで中央値が128であることに注意)
in_data2 = struct.unpack(f'<{len(in_data)}B', in_data)
in_data3 = tuple((x - 128) / 128.0 for x in in_data2)
# 読み取った配列(各要素は-1以上1以下の実数)について、RMSを計算する
rms = math.sqrt(sum([x * x for x in in_data3]) / len(in_data3))
# RMSからデシベルを計算して表示する
db = 20 * math.log10(rms) if rms > 0.0 else -math.inf
print(f"RMS:{format(db, '3.1f')}[dB]")
return None, pyaudio_ex.paContinue
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paInt8,
channels=1,
rate=8000,
input=True,
stream_callback=callback)
- 閲覧数 30569
コメントを追加