本文档详细说明如何基于AICallKit SDK,通过底层AliRtcEngine实现音频裸数据(如PCM)及本地音频文件(如WAV/MP3)的低延迟、无回声播放。
功能介绍
在通话过程中,若需播放音效或背景音,AICallKit SDK 提供了底层的 AliRtcEngine 引擎对象。您可获取该对象并调用其 API 播放音频,借助 RTC 引擎内置的音频处理和回声消除(AEC)能力,有效防止播放的声音被麦克风重新采集,从而避免回声问题。
前提条件
已经集成音视频通话智能体,并实现了基础的音视频通话功能,请参考音视频通话智能体集成。
功能实现
AICallKit SDK并未直接提供播放音频接口,它依赖于AliVCSDK_ARTC所提供的外推音频裸数据API,或播放伴奏API。您可以基于AICallKit获取到AliRtcEngine引擎对象,并在需要音频播放时,调用AliRtcEngine的接口实现音频播放,整体交互流程如下:
缓存AliRtcEngine引擎对象,必要时提前启动播放器
可以在AICallKit SDK的onAliRtcEngineCreated回调中获取ARTC SDK引擎对象AliRtcEngine,如果需要在接通过程中播放音频,则需要提前启动播放器。示例代码如下:
Android
@Override
public void onAliRtcEngineCreated(AliRtcEngine engine) {
if(engine != null) {
//获取AliRtcEngine对象engine
mRtcEngine = engine;
// 如果需要在接通过程中播放音频,则需要提前启动播放器
mRtcEngine.startAudioPlayer();
}
}iOS
public func onAICallRTCEngineCreated() {
guard let engine = self.engine.getRTCInstance() as? AliRtcEngine else {
return
}
// 保存AliRtcEngine对象engine
self.rtcEngine = engine
// 如果需要在接通过程中播放音频,则需要提前启动播放器
self.rtcEngine?.startAudioPlayer()
}
场景一: 播放PCM数据
该步骤适用于App通过长链接从服务端接收到音频裸数据后的播放场景。
向AliRtcEngine对象添加外部输入流。
Android
AliRtcEngine.AliRtcExternalAudioStreamConfig config = new AliRtcEngine.AliRtcExternalAudioStreamConfig(); config.channels = mPcmChannels; //PCM声道数 config.sampleRate = mPcmSampleRate;//PCM采样率 config.playoutVolume = 100; config.publishVolume = 0; //只播放,不推流 mStreamId = mAliRtcEngine.addExternalAudioStream(config); if mStreamId <= 0 { debugPrint("addExternalAudioStream failed: \(mStreamId)") return }iOS
guard let rtc = self.rtcEngine else { return } let config = AliRtcExternalAudioStreamConfig() config.sampleRate = self.pcmSampleRate //PCM采样率 config.channels = self.pcmChannels //PCM声道数 config.playoutVolume = 100 config.publishVolume = 0 //只播放,不推流 self.streamId = rtc.addExternalAudioStream(config) if self.streamId <= 0 { debugPrint("addExternalAudioStream failed: \(self.streamId)") return }向SDK中送入音频PCM数据。
Android
// 单次送给SDK的播放时长,建议为20~50ms int duration = 30; // 从缓存里获取接下来要播放的pcm数据(pcm播放时长不超过duration) byte[] dataToSend = ... // 检查dataToSend:如果未停止播放且数据太少可以sleep一下并返回等待更多缓存数据 // 计算dataToSend的采样点数 int numOfSamples = dataToSend的字节长度 / (2 * frameInfo.audio_channels); AliRtcEngine.AliRtcAudioFrame rawData = new AliRtcEngine.AliRtcAudioFrame(); rawData.data = dataToSend; rawData.numSamples = numOfSamples; rawData.bytesPerSample = 2; rawData.numChannels = mPcmChannels; rawData.samplesPerSec = mPcmSampleRate; int ret = mAliRtcEngine.pushExternalAudioStreamRawData(mStreamId, rawData); if(ret == 0x01070101) { // rtc buffer已经满,dataToSend需要在下次再送给rtc } else if(ret < 0) { // rtc播放失败,不建议继续送给rtc了,检查参数和推流状态, return; } else { // 成功送给rtc,当前的dataToSend需要从缓存里移除,下次不能再送进去 } sleep(duration - 10);iOS
guard let rtc = self.rtcEngine, self.streamId > 0 else { return } // 单次送给SDK的播放时长,建议为20~50ms let duration = 30 // 从缓存里获取接下来要播放的pcm数据(pcm播放时长不超过duration) let dataToSend = ... // 检查dataToSend:如果未停止播放且数据太少可以sleep一下并返回等待更多缓存数据 // 计算dataToSend的采样点数 let numOfSamples = dataToSend的字节长度 / (2 * frameInfo.audio_channels) let pushFrame = AliRtcAudioFrame() pushFrame.dataPtr = dataToSend pushFrame.numOfSamples = numOfSamples // 采样点数 pushFrame.bytesPerSample = 2 pushFrame.samplesPerSec = self.pcmSampleRate pushFrame.numOfChannels = self.pcmChannels let result = rtc.pushExternalAudioStream(self.pushStreamId, rawData: pushFrame) if result == 0x01070101 { // rtc buffer已经满,dataToSend需要在下次再送给rtc } else if result < 0 { // rtc播放失败,不建议继续送给rtc了,检查参数和推流状态, return } else { // 成功送给rtc,当前的dataToSend需要从缓存里移除,下次不能再送进去 } Thread.sleep(forTimeInterval: Double(duration - 10) / 1000.0)移除外部音频流。
Android
mRtcEngine.removeExternalAudioStream(mStreamId); mRtcEngine = null; mStreamId = -1;iOS
self.rtcEngine?.removeExternalAudioStream(self.streamId) self.rtcEngine = nil self.streamId = -1
场景二: 播放本地音频文件
该步骤适用于通过本地音频文件播放的场景。
启动播放。
Android
// 音频文件路径 private String mFilepath = "/assets/music.wav"; // 播放配置 AliRtcEngine.AliRtcAudioAccompanyConfig config = new AliRtcEngine.AliRtcAudioAccompanyConfig(); config.loopCycles = 1; // 循环次数 config.publishVolume = 0; // 只播放,不推流 config.playoutVolume = 100; config.startPosMs = 0; // 起始播放位置 // 开始播放 mRtcEngine.startAudioAccompany(mFilepath, config);iOS
// 音频文件路径 let filePath = Bundle.main.path(forResource: "music", ofType: "wav") // 播放配置 let config = AliRtcAudioAccompanyConfig() config.loopCycles = 1 // 循环次数, -1表示无限循环 config.publishVolume = 0 // 只播放,不推流 config.playoutVolume = 100 config.startPosMs = 0 // 开始播放 let result = rtcEngine.startAudioAccompany(withFile: filePath, config: config)说明在开启播放后,可以通过其他API进行播放控制,包括:
pauseAudioAccompany:暂停播放。
resumeAudioAccompany: 恢复播放。
stopAudioAccompany:停止播放。
监听播放完成回调。
Android
@Override public void onAudioAccompanyStateChanged(ARTCAICallEngine.ARTCAICallAudioAccompanyStateCode playState, ARTCAICallEngine.ARTCAICallAudioAccompanyErrorCode errorCode) { if (playState == ARTCAICallEngine.ARTCAICallAudioAccompanyStateCode.ARTCAICallAudioAccompanyEnded) { // 处理播放结束 } }iOS
func onAudioAccompanyStateChanged(state: ARTCAICallAudioAccompanyState, errorCode: ARTCAICallAudioAccompanyErrorCode) { if state == .ARTCAICallAudioAccompanyEnded { // 处理播放结束 } }
该文章对您有帮助吗?