概要
ステレオミキサー等の仕掛けを使わずにPythonでマイクとシステム音声(スピーカー)を同時録音する必要が生じた。ソースはgithubに置いてあるので正直それを読めば全部わかる。
ayataka0nk/meeting-recorder (github.com)
soundcard モジュールの罠
bastibe/SoundCard: A Pure-Python Real-Time Audio Library (github.com)
pyaudioよりシンプルな記述でスピーカーを録音できるこのモジュールを使う。
罠1 Windowsで録音できない
次の記事を参考にした。記事の最後にもあるように、Windowsでは不具合があった。これがなんと2023/11/19現在、まだリリースに含まれていない。(v0.4.2)
【8行】Pythonでシステム音を録音する方法 #Python – Qiita
Cannot record sound with loopback if silence at start · Issue #166 · bastibe/SoundCard (github.com)
なのでWindowsでも動かしたければ、SoundCardのmainブランチをpullして、それを参照してpipでインストールする。例えば次の記事を参考に。
[解決!Python]pipでローカル環境にあるパッケージをインストールするには:解決!Python – @IT (itmedia.co.jp)
罠2 同じ名称のマイクとスピーカーがあるとスピーカー音声を録音できない
次の画像を見ると、出力はLineOut
、入力にLineIn
とある。これ、もともとは両方とも名称がLine
だった。
そのせいで、スピーカーをマイク扱いで取得したい次のコードにおいて、同名のマイクが取得されてしまう現象が生じていた。
def record_speaker():
mic = sc.get_microphone(id=str(sc.default_speaker().name), include_loopback=True)
yield from record(mic)
構造上被りうる値をID代わりに使う奴があるか。なぜそれを区別する他の仕組みがないんだ。ふざけるな!ふざけるな!バカヤロー!!
オーディオデバイスとして私はYAMAHA AG03MK2
を使っており、これが発生した。地球は広く、AG03MK2を使ってpythonのsoundcardモジュールでloopback録音を試みて発狂する人が俺以外にも一人ぐらいはいる気がするので、デバイス名も記しておく。強く生きろ。
解決策はほぼ前述の通り、普通にWindowsの設定から名称を変えればよい。
同時録音のための並列処理
マイクとスピーカーを直接pythonで録音したい。しかしSoundCardはmain threadでしか動かない。なのでmultiprocessingを使ってこうじゃ。
def record_mix_audio():
speakerQueue = Queue()
micQueue = Queue()
speakerProcess = Process(target=record_speaker_multi, args=(speakerQueue,))
micProcess = Process(target=record_microphone_multi, args=(micQueue,))
speakerProcess.start()
micProcess.start()
try:
while True:
newSpeakerAudio = speakerQueue.get()
newMicAudio = micQueue.get()
yield mix_audio(newSpeakerAudio, newMicAudio)
except KeyboardInterrupt:
speakerProcess.terminate()
micProcess.terminate()
raise
finally:
speakerProcess.terminate()
micProcess.terminate()
record_speaker_multi
関数の詳細が必要なら、大した量じゃないのでソース読んでもろて。
マイク音声とシステム音声をミックス
次のようにnumpyのNDArrayを雑に足したら普通にパフォーマンス上なんの問題もなく綺麗に足せてワロタ。numpyすごいっすね。もし個別の音量調整が必要なら足す前に倍率かけたり、ノイズ調整したかったら適当に前後にnumpyで作ったローパスフィルタ作ったりすればよさげ。俺は今回不要だったからやらなかったけど。
def mix_audio(input1: NDArray[float32], input2: NDArray[float32]):
return input1 + input2
コメント