Open3

Python と WebRTC を使用したソフトウェア・エアコーキャンセルの実装試行錯誤

PINTOPINTO

WebRTCを使用して、マイクから入力された音声に対するエコーキャンセルを実装し、結果をPythonで取得するための全体の流れを、C++でWebRTCのエコーキャンセル機能を実装し、それをPythonから呼び出してエコーキャンセルを適用する形で解説します。

全体の流れ

  1. WebRTCライブラリのビルド: WebRTCのC++ライブラリを取得し、ビルドします。これにより、エコーキャンセル機能を含むライブラリが生成されます。
  2. C++でエコーキャンセルを実装: WebRTCのAudioProcessingクラスを使用して、エコーキャンセル機能を提供する関数をC++で実装します。
  3. C++ライブラリをPythonから利用できるようにする: ctypesを使って、PythonからC++で実装されたエコーキャンセル機能を呼び出します。
  4. Pythonでマイク入力とスピーカー出力を取得し、エコーキャンセルを実行: Pythonのsounddeviceライブラリを使って、マイクとスピーカーの音声をキャプチャし、エコーキャンセルを実行します。
  5. 結果を保存・再生: エコーキャンセルされた音声をファイルに保存し、再生します。

ステップ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から呼び出せるようにします。

C++コード: aec_processing.cpp

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++の共有ライブラリを呼び出します。

Pythonコード: aec_processing.py

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()

まとめ

  1. WebRTCのビルド: WebRTCをビルドし、C++でエコーキャンセル機能を実装します。
  2. C++コードの実装: WebRTCのAudioProcessingクラスを使い、エコーキャンセル処理を行います。
  3. Pythonから呼び出し: Pythonのctypesを使って、C++で作成した共有ライブラリを呼び出し、エコーキャンセルを実行します。
  4. マイクとスピーカー出力のキャプチャ: sounddeviceを使い、マイクとスピーカー出力(モニターデバイス)をキャプチャし、C++のエコーキャンセル関数に渡します。

これにより、PythonとC++を組み合わせて、WebRTCのエコーキャンセル機能を利用することができます。