2024/10/17 14:17:33
原始音频数据
本文介绍如何对采集到的原始音频数据进行前处理和后处理。
技术原理
音频传输过程中,我们可以对采集到的音频数据进行前处理和后处理,获取想要的播放效果。
对于有自行处理音频数据需求的场景,声网提供原始数据功能。你可以在将数据发送给编码器前进行前处理,对捕捉到的音频信号进行修改;也可以在将数据发送给解码器后进行后处理,对接收到的音频信号进行修改。
下图展示使用原始音频数据的基础流程:
前提条件
在进行操作之前,请确保你已经在项目中实现了基本的实时音视频功能。详见实现音视频互动。
实现方法
参考如下步骤,在你的项目中实现原始音频数据功能:
- 加入频道前,创建一个
IAudioFrameObserver
实例,并调用registerAudioFrameObserver
方法注册音频观测器。 - 调用
setRecordingAudioFrameParameters
、setPlaybackAudioFrameParameters
和setMixedAudioFrameParameters
为前缀的方法配置音频帧格式。 - 实现
onRecordAudioFrame
、onPlaybackAudioFrame
、onPlaybackAudioFrameBeforeMixing
和onMixedAudioFrame
回调。以上回调会对音频帧进行采集和处理。如果以上回调的返回值是false
,则说明对音频帧的处理无效。
C++
BOOL CAgoraOriginalAudioDlg::RegisterAudioFrameObserver(BOOL bEnable, IAudioFrameObserver *audioFrameObserver)
{
agora::util::AutoPtr<agora::media::IMediaEngine> mediaEngine;
// 查询 AGORA_IID_MEDIA_ENGINE 接口
mediaEngine.queryInterface(m_rtcEngine, agora::rtc::AGORA_IID_MEDIA_ENGINE);
int nRet = 0;
if (mediaEngine.get() == NULL)
return FALSE;
if (bEnable)
// 注册音频观测器并传入 IAudioFrameObserver 对象
nRet = mediaEngine->registerAudioFrameObserver(audioFrameObserver);
else
// 取消注册音频观测器
nRet = mediaEngine->registerAudioFrameObserver(NULL);
return nRet == 0 ? TRUE : FALSE;
}
// 实现 onRecordAudioFrame 回调
bool COriginalAudioProcFrameObserver::onRecordAudioFrame(const char* channelId, AudioFrame& audioFrame)
{
SIZE_T nSize = audioFrame.channels * audioFrame.samplesPerChannel * 2;
unsigned int readByte = 0;
int timestamp = GetTickCount();
short *pBuffer = (short *)audioFrame.buffer;
for (SIZE_T i = 0; i < nSize / 2; i++)
{
if (pBuffer[i] * 2 > 32767) {
pBuffer[i] = 32767;
}
else if (pBuffer[i] * 2 < -32768) {
pBuffer[i] = -32768;
}
else {
pBuffer[i] *= 2;
}
}
#ifdef _DEBUG
CString strInfo;
strInfo.Format(_T("audio Frame buffer size:%d, timestamp:%d \n"), nSize, timestamp);
OutputDebugString(strInfo);
audioFrame.renderTimeMs = timestamp;
#endif
return true;
}
// 实现 onPlaybackAudioFrame 回调
bool COriginalAudioProcFrameObserver::onPlaybackAudioFrame(const char* channelId, AudioFrame& audioFrame)
{
return true;
}
// 实现 onMixedAudioFrame 回调
bool COriginalAudioProcFrameObserver::onMixedAudioFrame(const char* channelId, AudioFrame& audioFrame)
{
return true;
}
// 实现 onPlaybackAudioFrameBeforeMixing 回调
bool COriginalAudioProcFrameObserver::onPlaybackAudioFrameBeforeMixing(const char* channelId, rtc::uid_t uid, AudioFrame& audioFrame)
{
return true;
}
// 调用 set 为前缀的方法,配置每个回调采集到的音频帧
m_rtcEngine->setRecordingAudioFrameParameters(44100, 2, RAW_AUDIO_FRAME_OP_MODE_READ_WRITE, 1024);
m_rtcEngine->setPlaybackAudioFrameParameters(44100, 2, RAW_AUDIO_FRAME_OP_MODE_READ_WRITE, 1024);
m_rtcEngine->setPlaybackAudioFrameBeforeMixingParameters(44100, 2);
m_rtcEngine->setMixedAudioFrameParameters(44100, 2, 1024);
参考信息
注意事项
声网实时互动 SDK 提供的音频裸数据采用同步回调机制。当你需要对原始音频数据进行保存或回写等操作时,请考虑以下建议:
-
为确保音频流的连续性,请不要阻塞 SDK 线程,建议不要直接在回调函数中处理数据。你可以先对接收的音频数据进行深拷贝,然后将复制的数据转移到另一个线程进行处理。
-
如果你决定在回调函数内直接同步处理音频数据,请严格控制处理时长。例如,若回调函数每 10 毫秒触发一次,那么在回调内的处理时间必须小于 10 毫秒,防止造成音频流的延迟或中断。
示例项目
声网提供了开源的原始音频数据示例项目供你参考,你可以前往下载或查看其中的源代码。