はじめに
以前の記事で音声データの読み取り方法やFFT解析の方法についての説明を行いました。
今回はマイクからの入力信号をリアルタイムでプロットする方法についてやっていきたいと思います。
音声の入出力
使用するライブラリ
Pythonで音声信号の収音・再生・録音を行うためのライブラリはいろいろありますが、リアルタイム処理等においてpython-sounddeviceが使いやすいとのことなのでこれを使ってみたいと思います。
サンプルコードを参考に作ってみます。
こちらのブログも大変参考になりました。
デバイスの選択
まずは、入出力のデバイスの選択を行います。
下記コードで自分のPC上の入出力デバイス一覧を取得できます。
import sounddevice as sd device_list = sd.query_devices() print(device_list)
0 Microsoft Sound Mapper - Input, MME (2 in, 0 out) > 1 マイク (Realtek High Definition Au, MME (2 in, 0 out) 2 マイク (JBL Pebbles), MME (2 in, 0 out) 3 FaceRig Virtual Microphone (Fac, MME (2 in, 0 out) 4 マイク (DroidCam Virtual Audio), MME (1 in, 0 out) 5 Microsoft Sound Mapper - Output, MME (0 in, 2 out) < 6 スピーカー (JBL Pebbles), MME (0 in, 2 out) 7 スピーカー (FaceRig Virtual Audio Dr, MME (0 in, 2 out) 8 Acer ET241Y (NVIDIA High Defini, MME (0 in, 2 out) 9 マイク (Realtek HD Audio Mic input), Windows WDM-KS (2 in, 0 out) 10 ライン入力 (Realtek HD Audio Line input), Windows WDM-KS (2 in, 0 out) 11 ステレオ ミキサー (Realtek HD Audio Stereo input), Windows WDM-KS (2 in, 0 out) 12 Speakers (Realtek HD Audio output), Windows WDM-KS (0 in, 8 out) 13 MIDI (DroidCam Audio), Windows WDM-KS (1 in, 0 out) 14 Output (DroidCam Audio), Windows WDM-KS (0 in, 1 out) 15 Input (Larmkanal), Windows WDM-KS (2 in, 0 out) 16 Output (Larmkanal), Windows WDM-KS (0 in, 2 out) 17 Output (NVIDIA High Definition Audio), Windows WDM-KS (0 in, 2 out) 18 スピーカー (JBL Pebbles), Windows WDM-KS (0 in, 2 out) 19 マイク (JBL Pebbles), Windows WDM-KS (2 in, 0 out)
デフォルトの入出力デバイスが現状1番のマイクと、6番のスピーカとなっています
これの変更は下記コードでできます。
(今回は変更しませんが)
sd.default.device = [1, 6] # Input, Outputデバイス指定
リアルタイム収音
さっそくリアルタイム収音をやってみましょう。
python-sounddeviceには入出力信号をリアルタイムでnumpy配列として処理できるStreamというクラスが実装されておりますので、これを使えば簡単にリアルタイム処理が書けます。
収音にはInputStreamというクラスを使用します。
下記は10秒間マイクからの入力信号をNumpy配列にしてprintするという処理を行うコードです。
callback関数を作成しておき、その中でリアルタイムに処理したい処理を書きます。
import sounddevice as sd import numpy as np sd.default.device = [1, 6] # Input, Outputデバイス指定 duration = 10 # 10秒間収音する def callback(indata, frames, time, status): # indata.shape=(n_samples, n_channels) # print root mean square in the current frame print(indata) with sd.InputStream( channels=1, dtype='float32', callback=callback ): sd.sleep(int(duration * 1000))
こんな感じの出力になります。
[[ 0.0000000e+00] [ 9.1552734e-05] [ 6.1035156e-05] ... [-9.1552734e-05] [-2.7465820e-04] [-1.2207031e-04]] [[ 0.00012207] [ 0.00018311] [ 0.00045776] ... [-0.00033569] [-0.0005188 ] [-0.00048828]] ... [[-0.00045776] [-0.00021362] [-0.00018311] ... [ 0.00097656] [ 0.00088501] [ 0.00073242]] [[3.9672852e-04] [6.1035156e-05] [6.1035156e-04] ... [2.7465820e-04] [3.0517578e-04] [3.0517578e-04]]
indataの要素数を見てみます。
import sounddevice as sd import numpy as np sd.default.device = [1, 6] # Input, Outputデバイス指定 duration = 10 # 10秒間収音する def callback(indata, frames, time, status): # indata.shape=(n_samples, n_channels) # print root mean square in the current frame print(indata.shape) with sd.InputStream( channels=1, dtype='float32', callback=callback ): sd.sleep(int(duration * 1000))
(1136, 1) (1136, 1) (1136, 1) ... (1136, 1) (1136, 1)
(1136, 1)の配列となっています。
1136は1chunk分の信号データで、サンプリング周波数が44100Hzなので0.026sec分の信号データが毎回indataに渡されていることがわかります。
1次元なのは、channels=1としたためであり、マイクがモノラル入力なのでこうなってます。
あとはこのデータをグラフ化するだけです。
リアルタイム収音データのプロット
グラフには1秒間分のデータ数をプロットすることとします。
とりあえず1秒分のデータを0で初期化してプロットしてみます。
import sounddevice as sd import numpy as np import matplotlib.pyplot as plt downsample = 10 length = int(1000 * 44100 / (1000 * downsample)) plotdata = np.zeros((length)) fig, ax = plt.subplots() line, = ax.plot(plotdata) ax.set_ylim([-1.0, 1.0]) ax.yaxis.grid(True) fig.tight_layout() plt.show()
結果はこちら。
このplotdataをcallback関数で更新していくことでリアルタイムプロットができます。
リアルタイム収音では0.026秒分のデータが毎回取得できるのでplotdataの前半0.026秒分は削除して、後ろに取得した0.026秒分のデータを追加する処理です。
作成したcallback関数は以下です。
def callback(indata, frames, time, status): # indata.shape=(n_samples, n_channels) global plotdata data = indata[::downsample, 0] shift = len(data) plotdata = np.roll(plotdata, -shift, axis=0) plotdata[-shift:] = data
np.rollでリアルタイム収音したデータ数分、plotdataを左にシフトさせたあと、右側のデータを収音したデータで置き換える作業をしています。
全コード
あとはアニメーションにするだけです。
matplotlibを用いたアニメーションの方法は、下記記事を参照ください。
import sounddevice as sd import numpy as np from matplotlib.animation import FuncAnimation import matplotlib.pyplot as plt device_list = sd.query_devices() print(device_list) sd.default.device = [1, 6] # Input, Outputデバイス指定 def callback(indata, frames, time, status): # indata.shape=(n_samples, n_channels) global plotdata data = indata[::downsample, 0] shift = len(data) plotdata = np.roll(plotdata, -shift, axis=0) plotdata[-shift:] = data def update_plot(frame): """This is called by matplotlib for each plot update. """ global plotdata line.set_ydata(plotdata) return line, downsample = 10 length = int(1000 * 44100 / (1000 * downsample)) plotdata = np.zeros((length)) fig, ax = plt.subplots() line, = ax.plot(plotdata) ax.set_ylim([-1.0, 1.0]) ax.set_xlim([0, length]) ax.yaxis.grid(True) fig.tight_layout() stream = sd.InputStream( channels=1, dtype='float32', callback=callback ) ani = FuncAnimation(fig, update_plot, interval=30, blit=True) with stream: plt.show()
結果はこちら。
マイクで適当にしゃべったりした際の波形です。
体感ですが、そこまでカクツキは気にならなかったです。
ラグもほぼなくプロットできてそうです。
おわりに
自分で作成したプログラムで自分の声がリアルタイムでプロットできるのは面白いですね。
sounddeviceではボイスチェンジャーみたいなのも作成できるようなのでぜひ試してみたいですね。