ARTC SDK提供了靈活的自訂音頻採集功能。
功能介紹
ARTC SDK 內部音頻模組可滿足您在應用中對基本音頻功能的需求,但是在特定情境中,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。
前提條件
在設定視頻配置之前,請確保達成以下條件:
功能實現
1.開啟或關閉內部採集
當您需要使用 SDK 的自訂音頻採集功能時,通常需要關閉 SDK 內部音頻採集,推薦在調用getInstance建立引擎時傳入 extras 參數來關閉 SDK 內部採集,相關參數如下:
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 序列化失敗")
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。如果需要音頻 3A(回聲消除 AEC、自動增益控制 AGC、雜訊抑制 ANS),請配置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;
}
// 傳回值為streamid,後續向SDK內push資料需要用到
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 進行傳輸。
阿里雲提供了範例程式碼,示範從本地 PCM 檔案或者麥克風讀取 PCM 格式的資料,相關實現請參考自採集樣本。
4.通過外部音頻流 ID 推送音頻資料到 SDK
在音頻推流成功後(onAudioPublishStateChanged 回調狀態變為AliRtcStatsPublished),調用pushExternalAudioStreamRawData介面,將mExternalAudioStreamId參數設定為步驟 2 擷取的音頻流 ID,將採集到的音頻資料傳入 SDK。採集到的音頻資料需要轉化為 AliRtcAudioFrame 對象,相關配置:
data:音頻資料。
numSamples:採樣點數,表示傳入的資料總共有多少個採樣點(單個聲道)。
bytesPerSample:每個採樣點有多少位元組,該節點與採樣點的位元位深相關聯,通常採用 16bit 位深 PCM,對應的位元組數就是 2 位元組。
numChannels:聲道數。
samplesPerSec:採樣率,例如 16000、48000 等。
需要在音頻推流成功(回調
onAudioPublishStateChanged狀態為AliRtcStatsPublished)後再開始送入資料。需要按照資料的實際長度設定
AliRtcAudioFrame的numSamples。部分裝置上例如通過AudioRecord.read擷取的音頻資料長度可能小於傳入的 buffer,需要根據傳回值確定真實的音頻資料長度。調用
pushExternalAudioStreamRawData傳入資料時,可能出現內部緩衝區滿而導致失敗的情形,需要進行處理並重傳。通常每隔 10ms 調用
pushExternalAudioStreamRawData送入一次資料。
Android
// 假設您採集到的音頻資料位元於audioData中,資料大小為bytesRead 位元組,為10ms資料
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;
// 當緩衝區滿導致push失敗的時候需要進行重試
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 內部採集
如果您的業務情境中需要在通話中動態開啟/關閉 SDK 的內部採集,請調用setParameter介面動態開啟/關閉 SDK 內部採集。
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~50ms 傳入一次。
使用自訂音頻採集是否可以使用 SDK 內部的音頻 3A(回聲消除 AEC、自動增益控制 AGC、雜訊抑制 ANS)?
可以,第 2 步添加外部音頻流時可以設定 enable3A 參數決定是否開啟 SDK 內部的音頻 3A。