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 customVideoRenderConfig: [String: String] = [:]
// 自定义视频渲染开关
customVideoRenderConfig["user_specified_use_external_audio_record"] = "TRUE"
// 序列化为Json
guard let jsonData = try? JSONSerialization.data(withJSONObject: customVideoRenderConfig, 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。
需要在音频推流成功(回调
onAudioPublishStateChanged状态为AliRtcStatsPublished)后再开始送入数据。需要按照数据的实际长度设置
AliRtcAudioFrame的numSamples。部分设备上例如通过AudioRecord.read获取的音频数据长度可能小于传入的 buffer,需要根据返回值确定真实的音频数据长度。调用
pushExternalAudioStreamRawData传入数据时,可能出现内部缓冲区满而导致失败的情形,需要进行处理并重传。通常每隔 10ms 调用
pushExternalAudioStreamRawData送入一次数据。
Android
// 假设您采集到的音频数据位于audioData中,数据大小为bytesRead 字节,为10ms数据
if (mAliRtcEngine != null && bytesRead > 0) {
// 构造AliRtcAudioFrame对象
AliRtcEngine.AliRtcAudioFrame sample = new AliRtcEngine.AliRtcAudioFrame();
sample.data = audioData;
sample.numSamples = bytesRead / (channels * (bitsPerSample / 8)); // 根据实际读取的字节数计算样本数
sample.numChannels = channels;
sample.sampleRate = 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();常见问题
调用自定义音频采集
pushExternalAudioStream的频率怎么设置?推荐按照物理音频设备的时钟驱动,在物理设备采集到数据的时候调用;
如果没有具体的物理设备来驱动,建议 10~50ms 传入一次。
使用自定义音频采集是否可以使用 SDK 内部的音频 3A(回声消除 AEC、自动增益控制 AGC、噪声抑制 ANS)?
可以,第 2 步添加外部音频流时可以设置 enable3A 参数决定是否开启 SDK 内部的音频 3A。