实时音频传输过程中,声网 SDK 通常会启动默认的音频模块进行采集和渲染。在以下场景中,你可能会发现默认的音频模块无法满足开发需求:
声网 SDK 支持使用自定义的音频源或渲染器,实现相关场景。本文介绍如何实现自定义音频采集和渲染。
声网在 GitHub 提供了一个开源的 API-Examples 示例项目,其中包含实现自定义音频采集和渲染功能的示例。
下表列出示例项目中主要的代码文件:
文件 | 描述 |
---|---|
CustomAudioSource | 自定义音频采集功能的示例,相关的主要方法如下:
|
CustomAudioRender | 自定义音频渲染功能的示例,相关的主要方法如下:
|
ExternalAudio | 包含如下文件:
|
参考如下步骤,在你的项目中实现自定义音频采集功能:
enableExternalAudioSourceWithSampleRate
方法指定使用自定义音频采集。pushExternalAudioFrameRawData
或 pushExternalAudioFrameSampleBuffer
将数据推送给 SDK 进行播放及后续操作。API 的调用时序如下图所示:
首先调用 SDK 的 enableExternalAudioSourceWithSampleRate
方法,告知 SDK 使用自采集的音频数据。
/**
* 开启外部音频数据采集。
* @param sampleRate 音频数据的采样率
* @param channel 音频数据的采样声道数
*/
agoraKit.enableExternalAudioSource(withSampleRate: sampleRate, channelsPerFrame: channel)
开启自定义采集时,你需要自行定义音频采集的方法,并指定采集音频的采样率和声道数。在示例项目中,我们定义了一个 setUpAudioSessionWithSampleRate 方法,并通过调用 iOS 的原生方法实现采集。
/** 开启音频采集。
* @param sampleRate 音频采样率
* @param channels 采样声道数
* @param audioCRMode 音频采样模式。SDK 根据该参数的设置选择使用声网的 API 还是系统的 API 获取采集到的音频数据
* @param ioType iOS 设备的音频播放通道设置
*/
[self.audioController setUpAudioSessionWithSampleRate:sampleRate channelCount:channels audioCRMode:audioCRMode IOType:ioType];
调用 SDK 的 joinChannelByToken
方法加入频道,并在成功加入频道后,告知系统开始采集音频数据。在示例项目中,我们定义了一个 startWork
方法来开启自定义的音频采集。
// 调用 SDK 的 joinChannelByToken 方法加入频道
let result = agoraKit.joinChannel(byToken: KeyCenter.Token, channelId: channelName, info: nil, uid: 0) {[unowned self] (channel, uid, elapsed) -> Void in
self.isJoined = true
LogUtils.log(message: "Join \(channel) with uid \(uid) elapsed \(elapsed)ms", level: .info)
// 调用 startWork 方法开启音频采集
self.exAudio.startWork()
try? AVAudioSession.sharedInstance().setPreferredSampleRate(Double(sampleRate))
}
if result != 0 {
// 常见的报错原因是填入的参数无效
self.showAlert(title: "Error", message: "joinChannel call failed: \(result), please check your params")
}
当系统采集到音频数据后,再调用 SDK 的 pushExternalAudioFrameRawData
方法,将采集到的数据推送给 SDK。SDK 接收到数据后,会自动进行播放。
首先你需要一个方法来接收采集到的音频数据。在示例项目中,我们定义了一个 AudioController
类,其中就包括一个 didCaptureData
回调,在接收到采集的音频数据时触发。
// 已接收到自定义采集的音频数据。你可以在该回调中获取采集的数据及数据字节大小
- (void)audioController:(AudioController *)controller didCaptureData:(unsigned char *)data bytesLength:(int)bytesLength {
// 当前采集模式为使用自定义采集 + SDK 渲染
if (self.audioCRMode != AudioCRModeExterCaptureSDKRender) {
// 调用 SDK 的 pushExternalAudioFrameRawData 方法推送采集到的数据给 SDK
[self.agoraKit pushExternalAudioFrameRawData:data samples:bytesLength / 2 timestamp:0];
}
}
用户离开频道时,停止采集音频数据。
// 用户离开频道时,停止采集音频数据
if isJoined {
exAudio.stopWork()
// 调用 SDK 的 leaveChannel 方法离开频道
agoraKit.leaveChannel { (stats) -> Void in
LogUtils.log(message: "left channel, duration: \(stats.duration)", level: .info)
}
}
enableExternalAudioSourceWithSampleRate
disableExternalAudioSource
pushExternalAudioFrameRawData
pushExternalAudioFrameSampleBuffer
声网 SDK 提供如下两种自定义音频渲染的方式,你可以选择其一实现:
方式一
registerAudioFrameObserver
方法注册音频帧观测器,并在该方法中实现一个 IAudioFrameObserver
类。成功注册后,你可以通过 onPlaybackAudioFrame
回调获取远端流的原始音频数据。setParameters("{\"che.audio.external_render\": true}")
关闭 SDK 的音频渲染。setParameters("{\"che.audio.keep.audiosession\": true}")
关闭 SDK 对 Audio Session 的控制。onPlaybackAudioFrame
回调中自行渲染音频数据。getNativeHandle
获取 C++ 的 IRtcEngine 对象。API 调用时序如下图所示:
方式二
enableExternalAudioSink
开启并设置外部音频渲染。pullPlaybackAudioFrameRawData
或 pullPlaybackAudioFrameSampleBufferByLengthInByte
方法拉取远端发送的音频数据。API 调用时序如下图所示:
定义一个 setupExternalAudioWithAgoraKit
方法:首先设置自定义音频的采样率、声道数、采集和渲染模式及播放通道,然后调用 SDK 的 registerAudioFrameObserver
方法注册音频帧观测器。
// 定义一个 setupExternalAudioWithAgoraKit 方法来设置自定义音频。
- (void)setupExternalAudioWithAgoraKit:(AgoraRtcEngineKit *)agoraKit sampleRate:(uint)sampleRate channels:(uint)channels audioCRMode:(AudioCRMode)audioCRMode IOType:(IOUnitType)ioType {
// 实现一个 AudioController 类。在合适的时候触发回调
self.audioController = [AudioController audioController];
self.audioController.delegate = self;
// 调用 C++ 的方法,注册音频帧观测器
agora::rtc::IRtcEngine* rtc_engine = (agora::rtc::IRtcEngine*)agoraKit.getNativeHandle;
agora::util::AutoPtr<agora::media::IMediaEngine> mediaEngine;
mediaEngine.queryInterface(rtc_engine, agora::AGORA_IID_MEDIA_ENGINE);
if (mediaEngine) {
s_audioFrameObserver = new ExternalAudioFrameObserver();
s_audioFrameObserver -> sampleRate = sampleRate;
s_audioFrameObserver -> sampleRate_play = channels;
mediaEngine->registerAudioFrameObserver(s_audioFrameObserver);
}
}
调用 setupExternalAudioWithAudioKit
对自定义音频进行设置,使用 SDK 采集自渲染模式。
exAudio.setupExternalAudio(withAgoraKit: agoraKit, sampleRate: UInt32(sampleRate), channels: UInt32(channel), audioCRMode: .sdkCaptureExterRender, ioType: .remoteIO)
为避免 SDK 影响自定义音频渲染,你需要关闭 SDK 的音频渲染以及 SDK 对 Audio Session 的控制。
// 关闭 SDK 的音频渲染
agoraKit.setParameters("{\"che.audio.external_render\": true}")
// 关闭 SDK 对 Audio Session 的控制
agoraKit.setParameters("{\"che.audio.keep.audiosession\": true}")
成功注册音频帧观测器后,SDK 会通过 onPlaybackAudioFrame 回调提供远端用户的原始音频数据。在该回调中渲染原始音频数据。
virtual bool onPlaybackAudioFrame(AudioFrame& audioFrame) override
{
// 实现渲染原始音频数据
}
调用 SDK 的 joinChannelByToken
方法加入频道,并在成功加入频道后,告知系统开始渲染音频数据。
// 调用 SDK 的 joinChannelByToken 方法加入频道
let result = agoraKit.joinChannel(byToken: KeyCenter.Token, channelId: channelName, info: nil, uid: 0) {[unowned self] (channel, uid, elapsed) -> Void in
self.isJoined = true
LogUtils.log(message: "Join \(channel) with uid \(uid) elapsed \(elapsed)ms", level: .info)
// 调用 startWork 方法开启音频渲染
self.exAudio.startWork()
}
if result != 0 {
// 常见的报错原因是填入的参数无效
self.showAlert(title: "Error", message: "joinChannel call failed: \(result), please check your params")
}
在 ExternalAudio.mm 文件中定义了 stopWork
和 cancelRegister
方法。
- (void)stopWork {
[self.audioController stopWork];
[self cancelRegister];
}
- (void)cancelRegister {
agora::rtc::IRtcEngine* rtc_engine = (agora::rtc::IRtcEngine*)self.agoraKit.getNativeHandle;
agora::util::AutoPtr<agora::media::IMediaEngine> mediaEngine;
mediaEngine.queryInterface(rtc_engine, agora::AGORA_IID_MEDIA_ENGINE);
// 取消注册音频帧观测器
mediaEngine->registerAudioFrameObserver(NULL);
}
用户离开频道时,停止渲染音频数据。
// 用户离开频道时,停止渲染音频数据
if isJoined {
exAudio.stopWork()
// 调用 SDK 的 leaveChannel 方法离开频道
agoraKit.leaveChannel { (stats) -> Void in
LogUtils.log(message: "left channel, duration: \(stats.duration)", level: .info)
}
}
enableExternalAudioSink
disableExternalAudioSink
pullPlaybackAudioFrameRawData
pullPlaybackAudioFrameSampleBufferByLengthInByte
getNativeHandle
registerAudioFrameObserver
onPlaybackAudioFrame
自定义音频采集和渲染场景中,需要开发者具有采集或渲染音频数据的能力:
如果使用 onPlaybackAudioFrame
回调来获取原始音频数据进行渲染,必须要调用 setParameters
方法关闭 SDK 的音频渲染。