音视频传输过程中,我们可以对采集到的音视频数据进行前处理和后处理,获取想要的播放效果。
对于有自行处理音视频数据需求的场景,声网提供原始数据功能,你可以在将数据发送给编码器前进行前处理,对捕捉到的音频信号或视频帧进行修改;也可以在将数据发送给解码器后进行后处理,对接收到的音频信号或视频帧进行修改。
声网在 GitHub 上提供以下实现了原始音频数据功能的开源示例项目:
你可以下载体验并参考源代码。
请确保已在你的项目中实现基本的实时音视频功能。
下图展示了原始音频数据的流转:
你可以通过 onRecordAudioFrame
、onPlaybackAudioFrame
、onPlaybackAudioFrameBeforeMixing
或 onMixedAudioFrame
回调:
从 v3.4.5 开始,你可以调用 iOS/macOS 原生接口获取原始音频数据并进行处理。
setAudioDataFrame
方法设置音频帧 delegate
。该方法在加入频道前后均可调用,但是只能捕捉到成功注册音频帧 delegate
后的音频数据。delegate
后,SDK 会触发 getRecordAudioParams
、getPlaybackAudioParams
或 getMixedAudioParams
回调,你可以在这些回调的返回值中设置想要获取的音频数据格式。getObservedAudioFramePosition
和 isMultipleChannelFrameWanted
回调,你可以在这些回调的返回值中设置想要观测的音频位置和是否获取多个频道的音频数据。getObservedAudioFramePosition
和 isMultipleChannelFrameWanted
的返回值触发 onRecordAudioFrame
、onPlaybackAudioFrame
、onPlaybackAudioFrameBeforeMixing
/onPlaybackAudioFrameBeforeMixingEx
或 onMixedAudioFrame
回调发送采集到的原始音频数据。onRecordAudioFrame
、onPlaybackAudioFrame
、onPlaybackAudioFrameBeforeMixing
/onPlaybackAudioFrameBeforeMixingEx
或 onMixedAudioFrame
回调发送给 SDK。下图展示使用原始音频数据的 API 调用时序:
// swift
class RawAudioDataMain: BaseViewController {
var localVideo = Bundle.loadVideoView(type: .local, audioOnly: true)
var remoteVideo = Bundle.loadVideoView(type: .remote, audioOnly: true)
@IBOutlet weak var container: AGEVideoContainer!
// 定义 agoraKit 变量
var agoraKit: AgoraRtcEngineKit!
...
// 初始化 agoraKit,并注册相关的回调事件
agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
// 调用 setAudioDataFrame 方法设置音频帧 delegate。你需要在该方法中实现一个 AgoraAudioDataFrameProtocol 协议。
agoraKit.setAudioDataFrame(self)
...
// 在当前类中实现一个 AgoraAudioDataFrameProtocol 协议的 extension
extension RawAudioDataMain: AgoraAudioDataFrameProtocol{
// 实现 getObservedAudioFramePosition 回调,在该回调的返回值中设置音频观测位置
func getObservedAudioFramePosition() -> AgoraAudioFramePosition {
return .record
}
// 实现 onRecordAudioFrame 回调
func onRecordAudioFrame(_ frame: AgoraAudioFrame) -> Bool {
return true
}
// 实现 getRecordAudioParams 回调,在该回调的返回值中设置 SDK 采集的音频数据格式
func getRecordAudioParams() -> AgoraAudioParam {
let param = AgoraAudioParam()
param.channel = 1
param.mode = .readOnly
param.sampleRate = 44100
param.samplesPerCall = 1024
return param
}
}
setAudioDataFrame
onRecordAudioFrame
onPlaybackAudioFrame
onMixedAudioFrame
onPlaybackAudioFrameBeforeMixing
onPlaybackAudioFrameBeforeMixingEx
getObservedAudioFramePosition
getRecordAudioParams
getMixedAudioParams
getPlaybackAudioParams
在使用原始数据功能前,请确保你已在项目中完成基本的实时音视频功能。
参考如下步骤,在你的项目中实现原始音频数据功能:
registerAudioFrameObserver
方法注册语音观测器,并在该方法中实现一个 IAudioFrameObserver
类。onRecordAudioFrame
、onPlaybackAudioFrame
、onPlaybackAudioFrameBeforeMixing
或 onMixedAudioFrame
回调发送采集到的原始音频数据。onRecordAudioFrame
、onPlaybackAudioFrame
、onPlaybackAudioFrameBeforeMixing
或 onMixedAudioFrame
回调发送给 SDK。.mm
文件中实现。.mm
文件的开头需要引入 C++ 头文件。
getNativeHandle
获取 C++ 的 IRtcEngine 对象。下图展示使用原始音频数据的 API 调用时序:
你可以对照 API 时序图,参考下面的示例代码片段,在项目中实现音频原始数据功能:
1. 初始化 AgoraRtcEngineKit
调用 sharedEngineWithConfig 初始化 AgoraRtcEngineKit。
// Swift
// 初始化 AgoraRtcEngineKit
let config = AgoraRtcEngineConfig()
agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
2. 注册语音数据观测器
// Swift
let audioType:ObserverAudioType = ObserverAudioType(rawValue: ObserverAudioType.recordAudio.rawValue | ObserverAudioType.playbackAudioFrameBeforeMixing.rawValue | ObserverAudioType.mixedAudio.rawValue | ObserverAudioType.playbackAudio.rawValue);
agoraMediaDataPlugin?.registerAudioRawDataObserver(audioType)
agoraMediaDataPlugin?.audioDelegate = self
由于声网仅提供 C++ 语言的音频原始数据相关接口,我们需要通过在 iOS 或 macOS 上调用 C++ 接口,实现注册音频数据观测器。
// 引入 C++ 头文件
#import <AgoraRtcKit/IAgoraMediaEngine.h>
#import <AgoraRtcKit/IAgoraRtcEngine.h>
- (void)registerAudioRawDataObserver:(ObserverAudioType)observerType {
// 获取 Native SDK 的 C++ 句柄
agora::rtc::IRtcEngine* rtc_engine = (agora::rtc::IRtcEngine*)self.agoraKit.getNativeHandle;
// 创建 IMediaEngine 实例
agora::util::AutoPtr<agora::media::IMediaEngine> mediaEngine;
// 必须使用 IMediaEngine 实例调用 queryInterface 设置 agora::AGORA_IID_MEDIA_ENGINE 接口,否则无法使用 mediaEngine 执行 registerAudioFrameObserver
mediaEngine.queryInterface(rtc_engine, agora::AGORA_IID_MEDIA_ENGINE);
NSInteger oldValue = self.observerAudioType;
self.observerAudioType |= observerType;
if (mediaEngine && oldValue == 0)
{
// 注册音频帧观测器
mediaEngine->registerAudioFrameObserver(&s_audioFrameObserver);
s_audioFrameObserver.mediaDataPlugin = self;
}
}
3. 设置音频数据采集参数
如果想要设置采集到的音频数据的采样率、修改模式、声道数、采样间隔等,可以在加入频道前,调用如下方法分别设置对应回调内返回的原始音频数据格式。
// Swift
// 设置 onRecordAudioFrame 回调返回的音频数据格式
agoraKit.setRecordingAudioFrameParametersWithSampleRate(44100, channel: 1, mode: .readWrite, samplesPerCall: 4410)
// 设置 onMixedAudioFrame 回调返回的音频数据格式
agoraKit.setMixedAudioFrameParametersWithSampleRate(44100, samplesPerCall: 4410)
// 设置 onPlaybackAudioFrame 回调返回的音频数据格式
agoraKit.setPlaybackAudioFrameParametersWithSampleRate(44100, channel: 1, mode: .readWrite, samplesPerCall: 4410)
4. 加入频道
调用 joinChannelByToken
加入频道。
// Swift
agoraKit.joinChannel(byToken: KeyCenter.Token, channelId: channelName, info: nil, uid: 0) {[unowned self] (channel, uid, elapsed)}
5. 获取采集到的音频数据
加入频道后,你可以通过 IAudioFrameObserver
类中的回调接收采集到的音频数据。完成音频数据处理后,你还可以通过这些回调将处理过的数据再发送回给 SDK。
// Swift
// 获取本地用户的原始音频数据,处理后再发送回 SDK
func mediaDataPlugin(_mediaDataPlugin: AgoraMediaDataPlugin, didRecord audioRawData: AgoraAudioRawDate) -> AgoraAudioRawData {
return audioRawData
}
// 获取所有远端用户的原始音频数据,处理后再发送回 SDK
func mediaDataPlugin(_mediaDataPlugin: AgoraMediaDataPlugin, willPlaybackAudioRawData audioRawData: AgoraRawData) -> AgoraAudioRawData {
return audioRawData
}
// 获取特定远端用户的原始音频数据,处理后再发送回 SDK
func mediaDataPlugin(_mediaDataPlugin: AgoraMediaDataPlugin, willPlaybackBeforeMixing audioRawData: AgoraAudioRawData, ofUid uid: uint) -> AgoraAudioRawData {
return audioRawData
}
// 获取本地用户和所有远端用户的原始音频数据,处理后再发送回 SDK
func mediaDataPlugin(_mediaDataPlugin: AgoraMediaDataPlugin, didMixedAudioRawData audioRawData: AgoraAudioRawData) -> AgoraAudioRawData {
return audioRawData
}
在 .mm
文件中调用 C++ 的 API 实现获取原始音频数据的回调。
// Swift
class AgoraMediaDataPluginAudioFrameObserver : public agora::media::IAudioFrameObserver
{
public:
AgoraMediaDataPlugin *mediaDataPlugin;
// 定义原始音频数据的格式
AgoraAudioRawData* getAudioRawDataWithAudioFrame(AudioFrame& audioFrame)
{
AgoraAudioRawData *data = [[AgoraAudioRawData alloc] init];
data.samples = audioFrame.samples;
data.bytesPerSample = audioFrame.bytesPerSample;
data.channels = audioFrame.channels;
data.samplesPerSec = audioFrame.samplesPerSec;
data.renderTimeMs = audioFrame.renderTimeMs;
data.buffer = (char *)audioFrame.buffer;
data.bufferSize = audioFrame.samples * audioFrame.bytesPerSample;
return data;
}
// 定义处理原始音频数据的格式
void modifiedAudioFrameWithNewAudioRawData(AudioFrame& audioFrame, AgoraAudioRawData *audioRawData)
{
audioFrame.samples = audioRawData.samples;
audioFrame.bytesPerSample = audioRawData.bytesPerSample;
audioFrame.channels = audioRawData.channels;
audioFrame.samplesPerSec = audioRawData.samplesPerSec;
audioFrame.renderTimeMs = audioRawData.renderTimeMs;
}
// 通过 onRecordAudioFrame 回调获取本地用户的原始音频数据
virtual bool onRecordAudioFrame(AudioFrame& audioFrame) override
{
if (!mediaDataPlugin && ((mediaDataPlugin.observerAudioType >> 0) == 0)) return true;
@autoreleasepool {
if ([mediaDataPlugin.audioDelegate respondsToSelector:@selector(mediaDataPlugin:didRecordAudioRawData:)]) {
AgoraAudioRawData *data = getAudioRawDataWithAudioFrame(audioFrame);
AgoraAudioRawData *newData = [mediaDataPlugin.audioDelegate mediaDataPlugin:mediaDataPlugin didRecordAudioRawData:data];
modifiedAudioFrameWithNewAudioRawData(audioFrame, newData);
}
}
// 返回值设为 true,表示将音频数据发送回 SDK
return true;
}
// 通过 onPlaybackAudioFrame 回调获取所有远端用户的原始音频数据
virtual bool onPlaybackAudioFrame(AudioFrame& audioFrame) override
{
if (!mediaDataPlugin && ((mediaDataPlugin.observerAudioType >> 1) == 0)) return true;
@autoreleasepool {
if ([mediaDataPlugin.audioDelegate respondsToSelector:@selector(mediaDataPlugin:willPlaybackAudioRawData:)]) {
AgoraAudioRawData *data = getAudioRawDataWithAudioFrame(audioFrame);
AgoraAudioRawData *newData = [mediaDataPlugin.audioDelegate mediaDataPlugin:mediaDataPlugin willPlaybackAudioRawData:data];
modifiedAudioFrameWithNewAudioRawData(audioFrame, newData);
}
}
// 返回值设为 true,表示将音频数据发送回 SDK
return true;
}
// 通过 onPlaybackAudioFrameBeforeMixing 回调获取特定远端用户的原始音频数据
virtual bool onPlaybackAudioFrameBeforeMixing(unsigned int uid, AudioFrame& audioFrame) override
{
if (!mediaDataPlugin && ((mediaDataPlugin.observerAudioType >> 2) == 0)) return true;
@autoreleasepool {
if ([mediaDataPlugin.audioDelegate respondsToSelector:@selector(mediaDataPlugin:willPlaybackBeforeMixingAudioRawData:ofUid:)]) {
AgoraAudioRawData *data = getAudioRawDataWithAudioFrame(audioFrame);
AgoraAudioRawData *newData = [mediaDataPlugin.audioDelegate mediaDataPlugin:mediaDataPlugin willPlaybackBeforeMixingAudioRawData:data ofUid:uid];
modifiedAudioFrameWithNewAudioRawData(audioFrame, newData);
}
}
// 返回值设为 true,表示将音频数据发送回 SDK
return true;
}
// 通过 onMixedAudioFrame 回调获取本地和远端所有远端用户的原始音频数据
virtual bool onMixedAudioFrame(AudioFrame& audioFrame) override
{
if (!mediaDataPlugin && ((mediaDataPlugin.observerAudioType >> 3) == 0)) return true;
@autoreleasepool {
if ([mediaDataPlugin.audioDelegate respondsToSelector:@selector(mediaDataPlugin:didMixedAudioRawData:)]) {
AgoraAudioRawData *data = getAudioRawDataWithAudioFrame(audioFrame);
AgoraAudioRawData *newData = [mediaDataPlugin.audioDelegate mediaDataPlugin:mediaDataPlugin didMixedAudioRawData:data];
modifiedAudioFrameWithNewAudioRawData(audioFrame, newData);
}
}
// 返回值设为 true,表示将音频数据发送回 SDK
return true;
}
};
6. 取消注册音频帧观测器
调用 registerAudioFrameObserver(NULL)
方法取消注册音频帧观测器。
- (void)deregisterAudioRawDataObserver:(ObserverAudioType)observerType {
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);
self.observerAudioType ^= observerType;
if (mediaEngine && self.observerAudioType == 0)
{
mediaEngine->registerAudioFrameObserver(NULL);
s_audioFrameObserver.mediaDataPlugin = nil;
}
}
registerAudioFrameObserver
setRecordingAudioFrameParameters
setPlaybackAudioFrameParameters
setMixedAudioFrameParameters
onRecordAudioFrame
onPlaybackAudioFrame
onPlaybackAudioFrameBeforeMixing
onMixedAudioFrame
.mm
文件中实现。完整代码可以参考 AgoraMediaDataPlugin.mmgetNativeHandler
获取 C++ 的 IRtcEngine 对象。