Open3
Python と WebRTC を使用したソフトウェア・エアコーキャンセルの実装試行錯誤
- ChatGPT との壁打ち
WebRTCを使用して、マイクから入力された音声に対するエコーキャンセルを実装し、結果をPythonで取得するための全体の流れを、C++でWebRTCのエコーキャンセル機能を実装し、それをPythonから呼び出してエコーキャンセルを適用する形で解説します。
全体の流れ
- WebRTCライブラリのビルド: WebRTCのC++ライブラリを取得し、ビルドします。これにより、エコーキャンセル機能を含むライブラリが生成されます。
-
C++でエコーキャンセルを実装: WebRTCの
AudioProcessing
クラスを使用して、エコーキャンセル機能を提供する関数をC++で実装します。 -
C++ライブラリをPythonから利用できるようにする:
ctypes
を使って、PythonからC++で実装されたエコーキャンセル機能を呼び出します。 -
Pythonでマイク入力とスピーカー出力を取得し、エコーキャンセルを実行: Pythonの
sounddevice
ライブラリを使って、マイクとスピーカーの音声をキャプチャし、エコーキャンセルを実行します。 - 結果を保存・再生: エコーキャンセルされた音声をファイルに保存し、再生します。
ステップ1: WebRTCライブラリのビルド
WebRTCはC++で実装された音声処理ライブラリです。まず、WebRTCのソースコードをクローンし、必要なビルドツールを使ってビルドします。
手順1: depot_toolsのインストール
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH="$PATH:/path/to/depot_tools"
手順2: WebRTCリポジトリのクローン
mkdir webrtc
cd webrtc
fetch --nohooks webrtc
cd src
gclient sync
手順3: WebRTCのビルド
# ビルド設定の生成
gn gen out/Default --args='is_debug=false target_os="linux"'
# WebRTCをビルド
ninja -C out/Default
ビルドが成功すると、out/Default
ディレクトリに必要なオブジェクトファイルとライブラリが生成されます。
ステップ2: C++でエコーキャンセルを実装
次に、WebRTCのAudioProcessing
クラスを使って、エコーキャンセル機能を提供するC++コードを作成します。以下のコードでは、apply_echo_cancellation
という関数をエクスポートして、Pythonから呼び出せるようにします。
aec_processing.cpp
C++コード: webrtc/src/aec_processing.cpp
#include <iostream>
#include <memory>
#include "modules/audio_processing/include/audio_processing.h"
#include "modules/audio_processing/include/audio_processing_statistics.h"
#include "rtc_base/ref_counted_object.h"
void apply_echo_cancellation(const short int* near_end, const short int* far_end, short int* out, int sample_rate, int num_samples) {
// AudioProcessing インスタンスを作成
rtc::scoped_refptr<webrtc::AudioProcessing> apm = webrtc::AudioProcessingBuilder().Create();
// エコーキャンセレーションの設定を有効化
webrtc::AudioProcessing::Config config;
config.echo_canceller.enabled = true;
apm->ApplyConfig(config);
// 入力と出力のサンプル
int16_t far_frame[num_samples];
int16_t input_frame[num_samples];
int16_t output_frame[num_samples];
// データをコピー
memcpy(far_frame, far_end, num_samples * sizeof(int16_t));
memcpy(input_frame, near_end, num_samples * sizeof(int16_t));
// ProcessReverseStream と ProcessStream を実行
apm->ProcessReverseStream(far_frame, webrtc::StreamConfig(sample_rate, 1), webrtc::StreamConfig(sample_rate, 1), far_frame);
apm->ProcessStream(input_frame, webrtc::StreamConfig(sample_rate, 1), webrtc::StreamConfig(sample_rate, 1), output_frame);
// 処理後のデータをコピー
memcpy(out, output_frame, num_samples * sizeof(int16_t));
}
int main() {
std::cout << "This is a shared library for WebRTC echo cancellation." << std::endl;
return 0;
}
ステップ3: C++ライブラリのビルドとPythonからの利用
C++コードをコンパイルし、Pythonから呼び出せる共有ライブラリ(.so
ファイル)を生成します。
コンパイルコマンド
webrtc/src
フォルダがカレントフォルダのとき。
g++ -shared \
-o libaec_processing.so \
-fPIC aec_processing.cpp \
-I. \
-Ithird_party/abseil-cpp \
-Imodules/audio_processing/include \
-Imodules/audio_processing \
-Lout/Default/obj \
-lwebrtc \
-lstdc++ \
-pthread \
-fuse-cxa-atexit
clang++ -shared \
-o libaec_processing.so \
-fPIC aec_processing.cpp \
-I. \
-Ithird_party/abseil-cpp \
-Imodules/audio_processing/include \
-Imodules/audio_processing \
-Lout/Default/obj \
-lwebrtc \
-pthread \
-stdlib=libstdc++
これにより、libaec_processing.so
という共有ライブラリが生成されます。
PythonからC++のエコーキャンセル関数を呼び出す
Pythonのctypes
ライブラリを使って、このC++の共有ライブラリを呼び出します。
aec_processing.py
Pythonコード: import ctypes
import numpy as np
# C++の共有ライブラリを読み込む
lib = ctypes.CDLL('./libaec_processing.so')
# C++の関数プロトタイプを指定
lib.apply_echo_cancellation.argtypes = (
ctypes.POINTER(ctypes.c_short), # input_data
ctypes.POINTER(ctypes.c_short), # far_end_data
ctypes.POINTER(ctypes.c_short), # output_data
ctypes.c_int, # sample_rate
ctypes.c_int # num_samples
)
# 仮の音声データを作成
sample_rate = 16000
num_samples = sample_rate * 1 # 1秒間のデータ
# 入力データ、リファレンスデータ、出力データを作成
input_data = np.array([i % 32767 for i in range(num_samples)], dtype=np.int16)
far_end_data = np.array([i % 32767 for i in range(num_samples)], dtype=np.int16)
output_data = np.zeros(num_samples, dtype=np.int16)
# C++関数を呼び出す
lib.apply_echo_cancellation(
input_data.ctypes.data_as(ctypes.POINTER(ctypes.c_short)),
far_end_data.ctypes.data_as(ctypes.POINTER(ctypes.c_short)),
output_data.ctypes.data_as(ctypes.POINTER(ctypes.c_short)),
sample_rate,
num_samples
)
# 結果を確認
print("エコーキャンセル後のデータ:", output_data[:10])
ステップ4: Pythonでマイク入力とスピーカー出力を取得し、エコーキャンセルを実行
PulseAudioのモニターデバイスを使って、システムのスピーカーから出力される音声(リファレンスデータ)をキャプチャし、マイクの入力音声からエコーをキャンセルする実装を行います。
Pythonでのキャプチャとエコーキャンセル
import sounddevice as sd
import numpy as np
SAMPLE_RATE = 16000
DURATION = 5 # 録音時間(秒)
OUTPUT_MONITOR_DEVICE = 'alsa_output.pci-0000_00_1b.0.analog-stereo.monitor'
INPUT_DEVICE = None # デフォルトのマイクデバイス
# 録音する関数
def record_audio(duration, sample_rate, input_device, output_monitor_device):
mic_data = np.zeros((int(duration * sample_rate),), dtype=np.float32)
ref_data = np.zeros((int(duration * sample_rate),), dtype=np.float32)
def mic_callback(indata, frames, time, status):
mic_data[:frames] = indata[:, 0]
def ref_callback(indata, frames, time, status):
ref_data[:frames] = indata[:, 0]
with sd.InputStream(samplerate=sample_rate, channels=1, device=input_device, callback=mic_callback):
with sd.InputStream(samplerate=sample_rate, channels=1, device=output_monitor_device, callback=ref_callback):
sd.sleep(int(duration * 1000))
return mic_data, ref_data
# メイン処理
mic_data, ref_data = record_audio(DURATION, SAMPLE_RATE, INPUT_DEVICE, OUTPUT_MONITOR_DEVICE)
# C++でエコーキャンセル処理を呼び出す
output_data = np.zeros_like(mic_data, dtype=np.int16)
lib.apply_echo_cancellation(
mic_data.ctypes.data_as(ctypes.POINTER(ctypes.c_short
)),
ref_data.ctypes.data_as(ctypes.POINTER(ctypes.c_short)),
output_data.ctypes.data_as(ctypes.POINTER(ctypes.c_short)),
SAMPLE_RATE,
len(mic_data)
)
# 結果を再生
sd.play(output_data, SAMPLE_RATE)
sd.wait()
まとめ
- WebRTCのビルド: WebRTCをビルドし、C++でエコーキャンセル機能を実装します。
-
C++コードの実装: WebRTCの
AudioProcessing
クラスを使い、エコーキャンセル処理を行います。 -
Pythonから呼び出し: Pythonの
ctypes
を使って、C++で作成した共有ライブラリを呼び出し、エコーキャンセルを実行します。 -
マイクとスピーカー出力のキャプチャ:
sounddevice
を使い、マイクとスピーカー出力(モニターデバイス)をキャプチャし、C++のエコーキャンセル関数に渡します。
これにより、PythonとC++を組み合わせて、WebRTCのエコーキャンセル機能を利用することができます。
うまくいかない