AOQ Client SDK でカスタムオーディオキャプチャを実装する方法について説明します。外部オーディオストリームの追加、PCM データのプッシュ、ストリームライフサイクルの管理が含まれます。
概要
AOQ Client SDK に組み込まれているオーディオモジュールは基本的な音声要件を満たしますが、シナリオによっては組み込みのキャプチャモジュールだけでは不十分な場合があります。カスタムオーディオキャプチャは、次のような場合に有用です。
- オーディオキャプチャデバイスの競合を回避する。
- カスタムキャプチャシステムまたはオーディオファイルの音声データを SDK に取り込み、送信する。
- AI TTS が生成した音声を SDK 経由でパブリッシュする。
AOQ Client SDK は柔軟なカスタムキャプチャをサポートしており、ユースケースに応じてオーディオデバイスとソースを独自に管理できます。外部オーディオストリームのデータは、音声のパブリッシュ前に内部でキャプチャされた音声とミックスされます。
サンプルコード
近日公開予定です。
前提条件
createEngineを呼び出してエンジンインスタンスが作成されていること。- サーバーへの接続が確立されていること (
onConnectionStatusChangeコールバックがAoqConnectionStatusConnectedを通知済みであること)。
実装
1. オーディオキャプチャの開始または停止
最初にオーディオキャプチャを開始します。外部オーディオストリームのデータは、音声のパブリッシュ前に内部キャプチャデータとミックスされます。内部のマイクキャプチャが不要な場合は、isExternal=true を設定して内部キャプチャデバイスを無効化します。
// オプション 1:内部キャプチャ — 外部オーディオストリームのデータをマイクデータとミックス
AoqClientEngine.AoqAudioCaptureConfig config = new AoqClientEngine.AoqAudioCaptureConfig();
config.isExternal = false; // 内部マイクキャプチャを使用
config.isVoipMode = false;
engine.startAudioCapture(config);
// オプション 2:内部キャプチャなし — 外部オーディオストリームのデータのみをパブリッシュ
AoqClientEngine.AoqAudioCaptureConfig config = new AoqClientEngine.AoqAudioCaptureConfig();
config.isExternal = true; // マイクを開かない。外部オーディオストリームがデータを提供
engine.startAudioCapture(config);
2. 接続後の外部オーディオストリームの追加
onConnectionStatusChange コールバックが AoqConnectionStatusConnected を通知したら、addAudioExternalStream を呼び出して外部オーディオストリームを追加します。データのプッシュとストリーム管理に使用する一意の streamId を割り当てます。
3A 処理 (エコーキャンセレーション、ノイズ抑制、自動ゲイン制御) が必要な場合は、AoqAudioExternalStreamConfig の enable3A パラメーターを設定します。
// onConnectionStatusChange コールバックで接続を確認した後にストリームを追加
@Override
public void onConnectionStatusChange(AoqClientEngine.AoqConnectionStatus status) {
if (status == AoqClientEngine.AoqConnectionStatus.AoqConnectionStatusConnected) {
addExternalAudioStream();
}
}
private void addExternalAudioStream() {
AoqClientEngine.AoqAudioExternalStreamConfig config = new AoqClientEngine.AoqAudioExternalStreamConfig();
config.sampleRate = 48000; // サンプルレート — 実際の音声データと一致させる必要があります
config.channels = 1; // チャンネル数
config.publishVolume = 100; // パブリッシュ音量 [0-100]
config.playoutVolume = 0; // ローカル再生音量 [0-100]。0 = ローカル再生なし
config.maxBufferDuration = 1000; // 最大バッファー長 (ミリ秒)
config.enable3A = true; // 入力 PCM に 3A 処理を適用するかどうか
String streamId = "external_audio_1";
int ret = engine.addAudioExternalStream(streamId, config);
if (ret == 0) {
mExternalStreamId = streamId;
}
}
パラメーター:
パラメーター | タイプ | デフォルト | 説明 |
|---|---|---|---|
trackType | AoqTrackType | AoqTrackTypeAudio | Audio track type |
codecType | AoqEncoderType | AoqEncoderTypeAudioPCM | Audio stream format |
channels | int | 1 | チャンネル数 |
sampleRate | int | 48000 | サンプルレート (Hz) |
playoutVolume | int | 100 | ローカル再生音量 [0–100] |
publishVolume | int | 100 | パブリッシュ音量 [0–100] |
maxBufferDuration | int | 1000 | 最大バッファー長 (ミリ秒) |
enable3A | boolean | false | 入力 PCM に 3A 処理を適用するかどうか |
3. PCMデータのキャプチャまたは取得
ユースケースに合わせて独自のオーディオキャプチャまたはデータソースを実装し、そのデータを SDK に取り込みます。一般的なデータソースは次のとおりです。
- マイクキャプチャ:Android AudioRecord を使用して PCM データをキャプチャします。
- ファイル読み取り:ローカルの PCM または WAV オーディオファイルから PCM データを解析します。
- AI TTS:音声合成エンジンから PCM データを取得します。
- ネットワークストリーム:ネットワークオーディオストリームから PCM データをデコードします。
音声データは PCM 形式である必要があります。サンプルレート、チャンネル数などのパラメーターを使用して、AoqAudioFrameData オブジェクトを構築します。
4. ストリームIDによるSDKへの音声データプッシュ
pushAudioExternalStreamData を呼び出して、キャプチャした PCM データを SDK にプッシュします。
- ハードウェアキャプチャの場合:1 フレームを 10 ms とし、データが利用可能になり次第プッシュします。
- ファイル入力の場合:1 フレームを 40 ms とし、30 ms ごとにプッシュします。
runningフラグを維持します。エンジンが終了するかストリーム ID が削除されたら、プッシュループを終了します。
// メンバー変数:プッシュループを制御するフラグ
private volatile boolean mPushRunning = false;
// 単一のオーディオフレームをプッシュ
private void pushAudioData(byte[] audioData, int bytesRead) {
if (engine == null || mExternalStreamId == null || bytesRead <= 0) {
return;
}
int channels = 1;
int bytesPerSample = 2; // 16-bit PCM
int sampleRate = 48000;
// オーディオフレームデータオブジェクトを構築
AoqClientEngine.AoqAudioFrameData frameData = new AoqClientEngine.AoqAudioFrameData();
frameData.dataPtr = audioData;
frameData.dataSize = bytesRead;
frameData.numOfSamples = bytesRead / (channels * bytesPerSample);
frameData.bytesPerSample = bytesPerSample;
frameData.numOfChannels = channels;
frameData.samplesPerSec = sampleRate;
// データをプッシュし、バッファー満杯エラーを処理
int ret;
final int WAIT_MS = 30;
do {
// running フラグとストリーム ID が引き続き有効かどうかを確認
if (!mPushRunning || mExternalStreamId == null) {
break;
}
ret = engine.pushAudioExternalStreamData(mExternalStreamId, frameData);
if (ret == 110) { // AoqErrorCodeAudioExternalBufferFull
try {
Thread.sleep(WAIT_MS);
} catch (InterruptedException e) {
break;
}
} else {
break;
}
} while (true);
}
重要な注意事項:
- 接続が確立され、外部オーディオストリームが追加された後にのみデータのプッシュを開始してください。
AoqAudioFrameDataのnumOfSamplesは、データの実際の長さに一致するように設定してください。pushAudioExternalStreamDataは、内部バッファーが満杯の場合 (エラーコード 110) に失敗することがあります。短時間待ってから再試行してください。- リアルタイムキャプチャの場合は 10 ms フレームを使用し、データが利用可能になり次第プッシュします。エラーコード 110 を処理してください。
- ファイル入力の場合は 40 ms フレームを使用し、30 ms ごとにプッシュします。エラーコード 110 を処理してください。
- エンジンの終了 (
destroy) またはストリーム ID の削除前に、mPushRunning = falseを設定してプッシュループを停止し、解放されたリソースへのアクセスを回避してください。
5. 外部オーディオストリームの削除
カスタムキャプチャが不要になったら、最初にプッシュループを停止し、その後 removeAudioExternalStream を呼び出して外部オーディオストリームを削除します。
// 先にプッシュを停止
stopPushAudio();
// 次に外部オーディオストリームを削除
engine.removeAudioExternalStream(mExternalStreamId);
mExternalStreamId = null;