Alibaba Real-Time Communication (ARTC) SDK は、柔軟なカスタム音声キャプチャ機能を提供します。
機能紹介
ARTC SDK の内部音声モジュールは、ほとんどのアプリケーションの基本的な音声要件を満たすことができますが、特定のシナリオでは十分ではない場合があります。このような場合、カスタム音声キャプチャを実装する必要があります。一般的なユースケースは次のとおりです:
デフォルトの音声キャプチャデバイスが他のプロセスに占有されている問題を解決する場合。
独自のキャプチャシステムや音声ファイルなどのカスタムソースから音声データをキャプチャし、SDK に渡して送信する必要がある場合。
ARTC SDK は柔軟なカスタムキャプチャをサポートしており、必要に応じてオーディオデバイスとソースを管理できます。
サンプルコード
Android: Android/ARTCExample/AdvancedUsage/src/main/java/com/aliyun/artc/api/advancedusage/CustomAudioCaptureAndRender/CustomAudioCaptureActivity.java
iOS: iOS/ARTCExample/AdvancedUsage/CustomAudioCapture/CustomAudioCaptureVC.swift
前提条件
次の要件を満たしていることを確認してください:
ARTC アプリケーションを作成し、ApsaraVideo Live コンソールから AppID と AppKey を取得します。
実装
1. 内部キャプチャの有効化/無効化
カスタム音声キャプチャ機能を使用するには、通常、SDK の内部音声キャプチャモジュールを無効にする必要があります。これを行うには、getInstance を呼び出してエンジンを作成する際に `extras` パラメーターを渡します。関連するパラメーターは次のとおりです:
user_specified_use_external_audio_record:外部音声キャプチャを使用するかどうかを指定します (これにより SDK の内部キャプチャが無効になります)。
TRUE:外部音声キャプチャを使用します (SDK の内部キャプチャを無効化)。FALSE:外部音声キャプチャを使用しません (SDK の内部キャプチャを有効化)。
`extras` パラメーターは JSON 文字列です。
Android
String extras = "{\"user_specified_use_external_audio_record\":\"TRUE\"}";
mAliRtcEngine = AliRtcEngine.getInstance(this, extras);iOS
// エンジンを作成して初期化します。
var customAudioCaptureConfig: [String: String] = [:]
// 外部キャプチャを使用します。
customAudioCaptureConfig["user_specified_use_external_audio_record"] = "TRUE"
// JSON にシリアル化します。
guard let jsonData = try? JSONSerialization.data(withJSONObject: customAudioCaptureConfig, options: []),
let extras = String(data: jsonData, encoding: .utf8) else {
print("JSON serialization failed")
return
}
let engine = AliRtcEngine.sharedInstance(self, extras:extras)Windows
/* Windows は作成時に音声キャプチャの有効化/無効化をサポートします。 */
/* 内部キャプチャモジュールを無効化します。 */
char* extra = "{\"user_specified_enable_use_virtual_audio_device\":\"TRUE\", \"user_specified_use_external_audio_record\":\"TRUE\"}";
mAliRtcEngine = AliRtcEngine.Create(extra);
/* 内部キャプチャモジュールを有効化します。 */
char* extra = "{\"user_specified_enable_use_virtual_audio_device\":\"FALSE\", \"user_specified_use_external_audio_record\":\"FALSE\"}";
mAliRtcEngine = AliRtcEngine.Create(extra);2. 外部音声ストリームの追加
addExternalAudioStream メソッドを呼び出して外部音声ストリームを追加し、そのストリーム ID を取得します。SDK の 3A 音声処理機能 (AEC、AGC、ANS) が必要な場合は、AliRtcExternalAudioStreamConfig オブジェクトの enable3A パラメーターを設定します。
この API を呼び出すタイミング:
3A 処理が必要な場合は、音声ストリームが公開され、カスタムキャプチャモジュールが最初の音声フレームを取得した後に
addExternalAudioStreamを呼び出します。具体的には、onAudioPublishStateChangedコールバックがnewStateとしてAliRtcStatsPublished (3)を返した後に呼び出します。3A 処理が不要な場合 (ローカルファイル、ネットワークストリーム、または TTS 生成データからの音声ストリーミングなど)、エンジンの作成直後にこのメソッドを呼び出すことができます。その後、音声ストリームが公開された後に音声データのプッシュを開始できます。
Android
AliRtcEngine.AliRtcExternalAudioStreamConfig config = new AliRtcEngine.AliRtcExternalAudioStreamConfig();
config.sampleRate = SAMPLE_RATE; // サンプルレート
config.channels = CHANNEL; // チャンネル数
// 公開ボリューム
config.publishVolume = 100;
// ローカル再生ボリューム
config.playoutVolume = isLocalPlayout ? 100 : 0;
config.enable3A = true;
int result = mAliRtcEngine.addExternalAudioStream(config);
if (result <= 0) {
return;
}
// 戻り値はストリーム ID です。後で SDK にデータをプッシュするためにこの ID が必要になります。
mExternalAudioStreamId = result;iOS
/* ビジネスニーズに応じてパラメーターを設定します。 */
AliRtcExternalAudioStreamConfig *config = [AliRtcExternalAudioStreamConfig new];
// これは外部 PCM 音声ストリームのチャンネル数と同じでなければなりません。モノラルの場合は 1、ステレオの場合は 2 に設定します。
config.channels = _pcmChannels;
// これは外部 PCM 音声ストリームのサンプルレートと同じでなければなりません。
config.sampleRate = _pcmSampleRate;
config.playoutVolume = 0;
config.publishVolume = 100;
_externalPlayoutStreamId = [self.engine addExternalAudioStream:config];Windows
/* メディアエンジンを取得します。 */
IAliEngineMediaEngine* mAliRtcMediaEngine = nullptr;
mAliRtcEngine->QueryInterface(AliEngineInterfaceMediaEngine, (void **)&mAliRtcMediaEngine);
/* ビジネスニーズに応じてパラメーターを設定します。 */
AliEngineExternalAudioStreamConfig config;
config.playoutVolume = currentAudioPlayoutVolume;
config.publishVolume = currentAudioPublishVolume;
config.channels = 1;
config.sampleRate = 48000;
config.publishStream = 0;
audioStreamID = mAliRtcMediaEngine->AddExternalAudioStream(config);
mAliRtcMediaEngine->Release();3. カスタム音声キャプチャモジュールの実装
ビジネスシナリオに基づいて音声データをキャプチャおよび処理するための独自のロジックを実装し、そのデータを SDK に渡して送信する必要があります。
Alibaba Cloud は、ローカルファイルまたはマイクから PCM データを読み取る方法を示すサンプルコードを提供しています。実装の詳細については、「カスタムキャプチャサンプル」をご参照ください。
4. ストリーム ID に基づく SDK への音声データのプッシュ
音声ストリームが公開された後 (onAudioPublishStateChanged コールバックがステータスとして AliRtcStatsPublished を報告したとき)、pushExternalAudioStreamRawData メソッドを呼び出します。mExternalAudioStreamId パラメーターをステップ 2 で取得したストリーム ID に設定し、キャプチャした音声データを SDK に渡します。キャプチャした音声データは、次のプロパティを持つ AliRtcAudioFrame オブジェクトに変換する必要があります:
data:音声データ。numSamples:提供されたデータ内のチャンネルごとのサンプルポイント数。bytesPerSample:サンプルポイントあたりのバイト数。これはビット深度に関連します。16 ビット PCM の場合、この値は 2 です。numChannels:音声チャンネル数。samplesPerSec:サンプルレート (16000 や 48000 など)。
音声ストリームが公開された後 (
onAudioPublishStateChangedコールバックがステータスとしてAliRtcStatsPublishedを報告した後) にのみ、データのプッシュを開始する必要があります。AliRtcAudioFrameのnumSamplesプロパティは、キャプチャされたデータの実際の長さに応じて設定してください。例えば、一部のデバイスでは、AudioRecord.readを介して取得される音声データの長さがバッファサイズよりも小さい場合があります。戻り値を使用して実際のデータ長を決定する必要があります。pushExternalAudioStreamRawDataを呼び出す際、内部バッファがいっぱいの場合、操作が失敗することがあります。この例外を処理し、データの再送信を試行する必要があります。10 ms ごとにデータを送信することを推奨します。
Android
// キャプチャされた音声データが audioData にあり、データサイズが bytesRead バイトで、10 ms 分のデータであると仮定します。
if (mAliRtcEngine != null && bytesRead > 0) {
// AliRtcAudioFrame オブジェクトを構築します。bitsPerSample はビット深度で、通常は 16 です。
AliRtcEngine.AliRtcAudioFrame sample = new AliRtcEngine.AliRtcAudioFrame();
sample.data = audioData;
sample.numSamples = bytesRead / (channels * (bitsPerSample / 8)); // 実際に読み取られたバイト数に基づいてサンプル数を計算します。
sample.numChannels = channels;
sample.samplesPerSec = sampleRate;
sample.bytesPerSample = bitsPerSample / 8;
int ret = 0;
// バッファがいっぱいでプッシュが失敗した場合にリトライします。
int retryCount = 0;
final int MAX_RETRY_COUNT = 20;
final int BUFFER_WAIT_MS = 10;
do {
// 取得したデータを SDK にプッシュします。
ret = mAliRtcEngine.pushExternalAudioStreamRawData(mExternalAudioStreamId, sample);
if(ret == ErrorCodeEnum.ERR_SDK_AUDIO_INPUT_BUFFER_FULL) {
// バッファフルケースを処理します。一定期間待機してリトライします。最大リトライ期間は数百 ms です。
retryCount++;
if(mExternalAudioStreamId <= 0 || retryCount >= MAX_RETRY_COUNT) {
// ストリームが停止したか、最大リトライ回数に達しました。ループを終了します。
break;
}
try {
// 一定期間一時停止します。
Thread.sleep(BUFFER_WAIT_MS);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
} else {
// プッシュが成功したか、別のエラーが発生しました。直接ループを終了します。
break;
}
} while (retryCount < MAX_RETRY_COUNT);
}iOS
// キャプチャした音声データから AliRtcAudioFrame オブジェクトを構築します。
let sample = AliRtcAudioFrame()
sample.dataPtr = UnsafeMutableRawPointer(mutating: pcmData)
sample.samplesPerSec = pcmSampleRate
sample.bytesPerSample = Int32(MemoryLayout<Int16>.size)
sample.numOfChannels = pcmChannels
sample.numOfSamples = numOfSamples
var retryCount = 0
while retryCount < 20 {
if !(pcmInputThread?.isExecuting ?? false) {
break
}
// 音声データを SDK にプッシュします。
let rc = rtcEngine?.pushExternalAudioStream(externalPublishStreamId, rawData: sample) ?? 0
// バッファフルを処理します。
// 0x01070101 SDK_AUDIO_INPUT_BUFFER_FULL: バッファがいっぱいです。再送信が必要です。
if rc == 0x01070101 && !(pcmInputThread?.isCancelled ?? true) {
Thread.sleep(forTimeInterval: 0.03) // 30ms
retryCount += 1;
} else {
if rc < 0 {
"pushExternalAudioStream error, ret: \(rc)".printLog()
}
break
}
}Windows
Windows でカスタムキャプチャを実装する前に、QueryInterface メソッドを呼び出してメディアエンジンオブジェクトを取得する必要があります。
/* メディアエンジンを取得します。 */
IAliEngineMediaEngine* mAliRtcMediaEngine = nullptr;
mAliRtcEngine->QueryInterface(AliEngineInterfaceMediaEngine, (void **)&mAliRtcMediaEngine);
// データから音声フレームを構築します。
AliEngineAudioRawData rawData;
rawData.dataPtr = frameInfo.audio_data[0];
rawData.numOfSamples = (int) (frameInfo.audio_data[0].length / (2 * frameInfo.audio_channels));
rawData.bytesPerSample = 2;
rawData.numOfChannels = frameInfo.audio_channels;
rawData.samplesPerSec = frameInfo.audio_sample_rate;
// データを SDK にプッシュします。
int ret = mAliRtcMediaEngine->PushExternalAudioStreamRawData(audioStreamID, rawData);
// バッファフルやその他のエラーを処理します。
// メディアエンジンを解放します。
mAliRtcMediaEngine->Release();5. 外部音声ストリームの削除
カスタム音声ソースからの音声公開を停止するには、removeExternalAudioStream メソッドを呼び出して外部音声ストリームを削除します。
Android
mAliRtcEngine.removeExternalAudioStream(mExternalAudioStreamId);iOS
[self.engine removeExternalAudioStream:_externalPublishStreamId];Windows
/* メディアエンジンを取得します。 */
IAliEngineMediaEngine* mAliRtcMediaEngine = nullptr;
mAliRtcEngine->QueryInterface(AliEngineInterfaceMediaEngine, (void **)&mAliRtcMediaEngine);
mAliRtcMediaEngine->RemoveExternalAudioStream(audioStreamID);
mAliRtcMediaEngine->Release();6. (オプション) 内部キャプチャの動的な有効化/無効化
ビジネスシナリオで、通話中に SDK の内部キャプチャを動的に有効化または無効化する必要がある場合は、setParameter メソッドを呼び出します。
Android
/* 内部キャプチャモジュールを動的に無効化します。 */
String parameter = "{\"audio\":{\"enable_system_audio_device_record\":\"FALSE\"}}";
mAliRtcEngine.setParameter(parameter);
/* 内部キャプチャモジュールを動的に有効化します。 */
String parameter = "{\"audio\":{\"enable_system_audio_device_record\":\"TRUE\"}}";
mAliRtcEngine.setParameter(parameter);iOS
// 内部キャプチャモジュールを動的に無効化します。
engine.setParameter("{\"audio\":{\"enable_system_audio_device_record\":\"FALSE\"}}")
// 内部キャプチャモジュールを動的に有効化します。
engine.setParameter("{\"audio\":{\"enable_system_audio_device_record\":\"TRUE\"}}")Windows
/* Windows は作成時に音声キャプチャの有効化/無効化をサポートします。 */
/* 内部キャプチャモジュールを動的に無効化します。 */
char* extra = "{\"user_specified_enable_use_virtual_audio_device\":\"TRUE\", \"user_specified_use_external_audio_record\":\"TRUE\"}";
mAliRtcEngine = AliRtcEngine.Create(extra);
/* 内部キャプチャモジュールを有効化します。 */
char* extra = "{\"user_specified_enable_use_virtual_audio_device\":\"FALSE\", \"user_specified_use_external_audio_record\":\"FALSE\"}";
mAliRtcEngine = AliRtcEngine.Create(extra);よくある質問
pushExternalAudioStreamを呼び出す推奨頻度はどのくらいですか?物理オーディオデバイスがデータをキャプチャするたびに、そのデバイスのクロックに基づいて呼び出します。
タイミングを駆動する物理デバイスがない場合は、10 ms から 50 ms ごとにデータを送信することを推奨します。
カスタム音声キャプチャで SDK 内部の 3A 音声処理 (AEC、AGC、ANS) を使用できますか?
はい。ステップ 2 で説明したように、外部音声ストリームを追加する際に
enable3Aパラメーターを設定することで、SDK 内部の 3A 処理を有効または無効にできます。