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。
前提条件
開始する前に、以下の手順を完了していることを確認してください。
Alibaba Real-Time Communication (ARTC) アプリケーションを作成し、ApsaraVideo Live コンソールから App ID および App Key を取得済みであること。手順については、「アプリケーションの作成」をご参照ください。
ARTC SDK をプロジェクトに統合し、基本的なリアルタイム音声・映像通話機能を実装済みであること。手順については、「ARTC SDK のダウンロードと統合」および「音声・映像通話の実装」をご参照ください。
実装手順
1. 内部キャプチャの有効化または無効化
カスタム音声キャプチャを使用するには、まず SDK の内部キャプチャモジュールを無効化する必要があります。この操作は、エンジンを作成するために getInstance を呼び出す際に extras パラメーターを渡す方法で実行することを推奨します。以下のパラメーターを使用します:
user_specified_use_external_audio_record:SDK の内部キャプチャを無効化し、カスタム音声キャプチャを有効化します。
"TRUE":カスタム音声キャプチャを使用(内部キャプチャを無効化)。"FALSE":カスタム音声キャプチャを使用しない(内部キャプチャを有効化)。
`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 シリアル化に失敗しました")
return
}
let engine = AliRtcEngine.sharedInstance(self, extras:extras)Mac
NSString * extras = @"{\"user_specified_use_external_audio_record\":\"TRUE\"}";
mAliRtcEngine = [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 を取得します。音響エコーキャンセレーション(AEC)、自動ゲイン制御(AGC)、ノイズ抑制(ANS)を含む 3A 音声処理を適用する場合は、AliRtcExternalAudioStreamConfig オブジェクト内の `enable3A` パラメーターを設定します。
このメソッドを呼び出すタイミング:
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 へのデータプッシュに必要です。
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];Mac
/* アプリケーションの要件に応じてパラメーターを設定します。 */
AliRtcExternalAudioStreamConfig *config = [AliRtcExternalAudioStreamConfig new];
config.channels = pcmChannels;
/** サンプルレート。デフォルト:48000。サポートされる値:8000、12000、16000、24000、32000、44100、48000、64000、88200、96000、176400、192000。 */
config.sampleRate = pcmSampleRate;
config.playoutVolume = 0;
config.publishVolume = 100;
int ret = [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. 音声データを SDK にプッシュ
音声ストリームの公開が成功した後(`onAudioPublishStateChanged` コールバックの状態が `AliRtcStatsPublished` に変更された後)、pushExternalAudioStreamRawData メソッドを呼び出し、ステップ 2 で取得した音声ストリーム ID を mExternalAudioStreamId パラメーターに設定し、収集した音声データを SDK に渡します。収集した音声データは、AliRtcAudioFrame オブジェクトに変換する必要があります。関連する構成は以下のとおりです。
`data`:音声データ。
`numSamples`:提供されたデータにおける各チャンネルあたりのサンプルポイント数。
`bytesPerSample`:サンプルポイントあたりのバイト数(ビット深度 ÷ 8)。たとえば、16 ビット音声の場合、この値は 2 です。
`numChannels`:音声チャンネル数。
`samplesPerSec`:サンプルレート(Hz 単位)。たとえば、16000 または 48000 など。
音声ストリームが公開された後(`
onAudioPublishStateChanged` コールバックが `AliRtcStatsPublished` 状態を報告した後)のみ、データのプッシュを開始してください。numSamplesパラメーターをAliRtcAudioFrameオブジェクトに、実際にキャプチャされたデータの長さに設定してください。AudioRecord.readのようなメソッドは、バッファーサイズよりも少ないデータを返す場合があるため、実際のデータ長を判断するためにメソッドの戻り値を使用する必要があります。内部バッファーが満杯の場合、
pushExternalAudioStreamRawDataの呼び出しは失敗することがあります。アプリケーションでは、このエラーを処理し、再試行メカニズムを実装する必要があります。データ送信の頻度として、10 ms ごとに
pushExternalAudioStreamRawDataを呼び出すことを推奨します。
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) {
// バッファーが満杯のシナリオを処理します。短時間待機して再試行します。
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 エラー、戻り値: \(rc)".printLog()
}
break
}
}Mac
while ( true ) {
if (![pcmInputThread isExecuting]) {
push_error = YES;
break;
}
AliRtcAudioFrame *sample = [AliRtcAudioFrame new];
sample.dataPtr = pcmData;
sample.samplesPerSec = pcmSampleRate;
sample.bytesPerSample = sizeof(int16_t);
sample.numOfChannels = pcmChannels;
sample.numOfSamples = numOfSamples;
int rc = [self.engine pushExternalAudioStream:_externalPublishStreamId rawData:sample];
count = count + 1;
/* エラーが `AliRtcErrAudioBufferFull` の場合、一時的に待機してプッシュを継続します。 */
if ( rc == AliRtcErrAudioBufferFull && [pcmInputThread isCancelled ] == NO ) {
[NSThread sleepForTimeInterval:0.04] ;
}else {
if ( rc < 0 ) {
push_error = true ;
}
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);
// バッファーが満杯である場合などのエラーを処理します。
if ( ret == AliEngineErrorAudioBufferFull ) {
Sleep(40);
continue ;
}
// メディアエンジンを解放します。
mAliRtcMediaEngine->Release();5. 外部音声ストリームの削除
カスタムソースからの音声の公開を停止するには、removeExternalAudioStream を呼び出します。
Android
mAliRtcEngine.removeExternalAudioStream(mExternalAudioStreamId);iOS
[self.engine removeExternalAudioStream:_externalPublishStreamId];Mac
[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\"}}")Mac
// 内部キャプチャを動的に無効化。
[self setParameter:@"{\"audio\":{\"enable_system_audio_device_record\":\"FALSE\"}}"];
// 内部キャプチャを動的に有効化。
[self setParameter:@"{\"audio\":{\"enable_system_audio_device_record\":\"TRUE\"}}"];Windows
/* 内部キャプチャを動的に無効化。 */
mAliRtcEngine->SetParameter("{\"audio\":{\"enable_system_audio_device_record\":\"FALSE\"}}");
/* 内部キャプチャを動的に有効化。 */
mAliRtcEngine->SetParameter("{\"audio\":{\"enable_system_audio_device_record\":\"TRUE\"}}");よくある質問
pushExternalAudioStreamRawDataの呼び出し頻度として推奨されるのは何ですか?物理音声デバイスのクロックと同期することを推奨します。デバイスが新しいデータパケットを提供するたびにこのメソッドを呼び出してください。
物理デバイスのクロックが利用できない場合は、10 ms ~ 50 ms の間隔でデータを送信することを推奨します。
カスタム音声キャプチャで、SDK の内部 3A 音声処理(AEC、AGC、ANS)を使用できますか?
はい。ステップ 2 で説明したとおり、外部音声ストリームを追加する際に `enable3A` パラメーターを設定することで、SDK の内部 3A 音声処理を有効化または無効化できます。