Pythonロゴともろみ先輩

今回はpython上でリアルタイム録音・再生ができるpyaudioというライブラリを使った基本的な操作をご紹介します。

まずはライブラリのインストールから初めて,必要機材の確認,ライブラリの利用方法(プログラムコードの紹介)をしていきます。

M1 Macだとうまくインストールが出来なかったので,僕が実際に行ったその対処法についても紹介していきます。

pyaudioのインストール

そもそもpyaudioはPortAudioをバインディングして録音や再生などオーディオ処理を行うものです。

つまりPortAudioがリアルタイムオーディオ入出力を行えるC言語ライブラリなのですが,python上でそれを呼び出している形です。

インストールはpipを使って簡単に実行できます。

$ pip install pyaudio

途中何か聞かれたらyと打ってEnterです。

比較的新しいバージョンのconda環境が入っている方は以下でインストール可能かもしれません。

$ conda install pyaudio

M1 Macでのインストール

最近僕はM1 Macを購入したのですが,pyaudioをいつも通りインストールしようとしたらエラーを吐かれてしまいました。

だいぶ手こずったのですが,ここで議論されているように進めることで解決できました。

具体的に僕がやったこととしてはまずbrewでPortAudioを入れて,

$ brew install PortAudio

その後次のようにpipでpyaudioをインストールしました。

$ python3 -m pip install pyaudio --global-option="build_ext" --global-option="-I/opt/homebrew/include" --global-option="-L/opt/homebrew/lib"

これで僕はpyaudioがインストールされました。

録音・再生に必要な機材について

次にpyaudioを使って録音・再生する際に必要な機材について少しお話しをします。

pyaudioはあくまで録音・再生のライブラリであって,ただのソフトウェアなので実行するためには実際のマイクスピーカが必要です!

あまりクオリティを気にしないのであれば,最近のノートPCには大体内臓マイクと内臓スピーカが搭載されていますのでそれで良いかと思います。

もし内臓マイク・スピーカがない場合でとりあえず実行したい場合は以下のような安価なものを購入すれば実行できます。

選ぶポイントとして,対応している端子と電源供給が別に必要ではないかというところは気をつけた方が良いです。

ここではマイクはミニフォンのもの,スピーカはUSB接続のものをご紹介していますので自分のPCで使用可能か事前にご確認ください。

例えば自作PCでサウンドカードがない場合などはミニフォン端子は対応していなかったりします。

少し良いものになるとキャノン(XLR)端子を採用しているものが多くなりますが,これはPCには直接刺さらないので,別途オーディオインターフェイスという機器が必要になります。

機材の準備が整ったらいよいよ録音・再生をしてみましょう!

pyaudioの使い方

ここでは簡単な使い方をご紹介しますが,すぐ使えるようにコードの全文を書いてから解説という形で進めます。

また,本記事では録音と再生それぞれのコードと録音・再生同時に行うコードをご紹介しますが,先の2つについてはブロッキング処理で,もう1つはコールバック関数を用いたノンブロッキング処理で行います。

簡単に言うとブロッキング処理とは,処理が終わるまで(ここでは録音が終わるまで)他の処理を受け付けないような処理になり,ノンブロッキング処理はある処理の最中に他の処理を実行できるような処理になります。

詳しく知りたい方は公式ページをご確認頂ければと思います。

pyaudioを使った録音

ここではpyaudioライブラリを用いた録音の方法を解説します。

まずはコードの全文です。

import pyaudio
import wave

CHUNK = 2**10
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
record_time = 5
output_path = "./output.wav"

p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK)

print("Recording ...")
frames = []
for i in range(0, int(RATE / CHUNK * record_time)):
    data = stream.read(CHUNK)
    frames.append(data)
print("Done.")

stream.stop_stream()
stream.close()
p.terminate()

wf = wave.open(output_path, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()

それでは解説していきます。まずインポート部

import pyaudio
import wave

ですが,pyaudioの他にwaveというライブラリもインポートします。

これはwavファイルの読み書き等に利用します。

CHUNK = 2**10
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
record_time = 5
output_path = "./output.wav"

ここでは録音する音のパラメータを設定しています。

CHUNKというのはバッファのサイズのことで,このサイズ分は遅延が生じます。

ですので,もしよりリアルタイムに処理したいときには値を小さく設定します。

ただし,バッファ取得の間に処理を完結させる必要があるのであまりに小さく設定すると間に合わない可能性がありますのでご注意ください。

FORMATはいわゆる量子化ビット数を指していますので,大きくするほど解像度が高くなります。通常人間に16bit以上は聴き分けが難しくなると思いますのでこだわりが無ければ16で良いと思います。

CHANNELSは使用するマイクの本数です。ここでは1chを仮定していますがオーディオインターフェイス等を使って多チャンネル録音も可能です。

RATEはサンプリング周波数です。通常44100か48000に設定することが多いと思います(ハイレゾは96000もあります)。

record_timeは録音の秒数で,output_pathは録音した音源を保存する際のファイル名です。

それぞれ目的に応じて変更してお使いください。

p = pyaudio.PyAudio()
stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                frames_per_buffer=CHUNK)

ここではpというインスタンスを生成して,上で定義したパラメータを設定しています。

print("Recording ...")
frames = []
for i in range(0, int(RATE / CHUNK * record_time)):
    data = stream.read(CHUNK)
    frames.append(data)
print("Done.")
stream.stop_stream()
stream.close()
p.terminate()

ここが実際に録音している部分になります。具体的にはframesに各バッファ毎に録音された信号を追加していきます。指定された秒数まで録音したらループを抜けます。

録音が終わったら終了操作をしています。

wf = wave.open(output_path, 'wb')
wf.setnchannels(CHANNELS)
wf.setsampwidth(p.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
wf.close()

最後に録音した信号(framesに格納されています)をwavファイルとして書き出します。

詳しく知りたい方は以下の記事を見て頂くと良いかと思います。

pyaudioを使った再生

次にブロッキング処理による再生です。

録音と同じようにコードの全文を載せた後,解説していきます。録音で述べた内容については省略します。

以下がプログラムコードの全文です。

import pyaudio
import wave

CHUNK_SIZE = 2**10

wf = wave.open('sample.wav', 'rb')
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
                channels=wf.getnchannels(),
                rate=wf.getframerate(),
                output=True)

data = wf.readframes(CHUNK_SIZE)
while len(data) > 0:
    stream.write(data)
    data = wf.readframes(CHUNK_SIZE)

stream.stop_stream()
stream.close()
p.terminate()

それでは解説ですが,再生の場合は元から持っているファイルを利用するのでCHUNK_SIZE以外にパラメータの定義は必要ありません。

以下の再生部分だけ解説します。

data = wf.readframes(CHUNK_SIZE)
while len(data) > 0:
    stream.write(data)
    data = wf.readframes(CHUNK_SIZE)

各バッファで再生はCHUNK_SIZE分だけ行われるので,まずdataという変数に指定したwavファイルの先頭からCHUNK_SIZEだけ信号を取り出す作業を行います。

そしてdataが空でなければ(まだ再生してない信号があれば)stream.write(data)で再生します。これをループすることで指定されたwavファイルが終わるまで再生を続けます。

pyaudioを使って録音しながら再生する

最後にノンブロッキング処理によって録音しながら再生する方法について紹介します。

これを使えば録音して入ってきた信号に対して何かしらの信号処理を施した後リアルタイムで再生するということも可能になります。

ここでは単純に録音した音をそのまま再生するようなプログラムコードを紹介します。

import pyaudio
import time

FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100

p = pyaudio.PyAudio()
def callback(in_data, frame_count, time_info, status):
    return (in_data, pyaudio.paContinue)

stream = p.open(format=FORMAT,
                channels=CHANNELS,
                rate=RATE,
                input=True,
                output=True,
                stream_callback=callback)

stream.start_stream()

while stream.is_active():
    time.sleep(0.1)

stream.stop_stream()
stream.close()
p.terminate()

先程までのコードと違って終了条件を設定していないので,終了したい時には「Ctrl + C」を叩いてください。

それでは解説です。

今までのコードと大きく異なるのはcallback関数を定義して,呼び出しているところです。

それぞれの引数と返り値が何かを説明します。まず引数です。

  • in_data:byte型,収録データ(inputがFalseの場合はNoneが入ってくる)
  • frame_count:int型,収録データのフレーム数
  • time_info:dic型,「input_buffer_adc_time(バッファを入力した時刻)」「current_time(現在時刻)」「output_buffer_dac_time(バッファを出力した時刻)」が格納されている辞書
  • status:int型,現在の状態(インプットがOverflowしている等)が格納される

あまり難しい処理をしないのであれば上2つを理解していれば十分だと思います。

詳細は公式ページを参考にしてみてください。

ただ,time_infoなどの詳細はPortAudioの仕様まで遡る必要がありますので少し面倒かもしれません。

次に返り値ですが,タプルで(out_data, flag)を返します。それぞれ以下のようなものです。

  • out_data:byte型,再生データ(outputがFalseの場合にはNoneにする)
  • flag:int型,録音を続行(pyaudio.paContinue)か終了(pyaudio.paComplete)か中断(pyaudio.paAbort)を返す

out_dataは今回のように録音した信号をそのまま返す場合にはin_dataを渡せば良いですが,そうでない時にはサイズが「frame_count * チャンネル数 * 量子化バイト数」になっている必要があります。

flagについては今回は常にpyaudio.paContinueを入れているのでプログラムコード内で終了することはありませんが,状況に応じて終了するように書き換えることも可能です。

このcallback関数の呼び出しをスタートさせるのが

stream.start_stream()

で,これはノンブロッキング処理なので録音・再生を実行している間にwhile stream.is_active():の下で他の命令を実行することができます。例えば

while stream.is_active():
    print("a")
    time.sleep(0.1)

とすれば録音している間ずっと0.1秒間隔で「a」と出力します。

録音が終わればこのループを抜けて終了プロセスに移ります。

まとめ

お疲れ様でした。

今回はpython上でリアルタイム録音・収録が可能なpyaudioのインストールから機材準備,使い方までを解説しました。

これを応用すればリアルタイムに波形を描画したり,収録信号にEQをかけてすぐに再生したりと色々と応用が可能です。

僕も何かの機会に面白いアプリを作って公開できればと思っています。