实时音频传输过程中,声网 SDK 通常会启动默认的音频模块进行采集和渲染。在以下场景中,你可能会发现默认的音频模块无法满足开发需求:
基于此,声网 RTC Native SDK 支持使用自定义的音频源或渲染器,实现相关场景。本文介绍如何实现自定义音频采集和渲染。
我们在 GitHub 上提供一个开源的示例项目,你可以前往下载,或查看其中的源代码。
开始自定义采集和渲染前,请确保你已在项目中实现基本的通话或者直播功能,详见一对一通话或互动直播。
参考如下步骤,在你的项目中实现自定义音频采集功能:
setExternalAudioSource
指定自定义音频源。你需要自行管理音频采集模块。joinChannel
加入频道。pushAudioFrame
将采集的音频帧发送给 SDK。参考下图时序在你的项目中实现自定义音频采集。
下图展示了自采集情况下的音频数据流转过程:
pushAudioFrame
将采集的音频帧发送给 SDK。setExternalAudioSource
指定自定义音频源。// 指定或取消自定义音频源
BOOL CAgoraCaptureAduioDlg::EnableExtendAudioCapture(BOOL bEnable)
{
int nRet = 0;
if ( bEnable )
// 指定自定义音频源
nRet = m_rtcEngine->setExternalAudioSource(true, m_capAudioInfo.sampleRate, m_capAudioInfo.channels);
else
// 取消自定义音频源
nRet = m_rtcEngine->setExternalAudioSource(false, m_capAudioInfo.sampleRate, m_capAudioInfo.channels);
return nRet == 0 ? TRUE : FALSE;
}
IAudioFrameObserver
中 AudioFrame
结构体实例的参数,以及 onRecordAudioFrame
回调的录制声音格式。启动自采集模块采集音频数据。// 启动自采集模块采集音频数据
void CAgoraCaptureAduioDlg::EnableCaputre(BOOL bEnable) {
WAVEFORMATEX waveFormat;
SIZE_T nBufferSize = 0;
if (bEnable == (BOOL)m_extenalCaptureAudio)
return;
if (bEnable)
{
// 选择采集设备
m_agAudioCaptureDevice.SelectMediaCap(m_cmbAudioType.GetCurSel());
// 获取音频采集设备
m_agAudioCaptureDevice.GetCurrentAudioCap(&waveFormat);
nBufferSize = waveFormat.nAvgBytesPerSec / AUDIO_CALLBACK_TIMES;
// 设置音频采集 buffer
m_agAudioCaptureDevice.SetCaptureBuffer(nBufferSize, 16, waveFormat.nBlockAlign);
// 根据自采集音频设置 IAudioFrameObserver 中 AudioFrame 结构体实例的参数
m_audioFrame.avsync_type = 0;
m_audioFrame.bytesPerSample = 2;
m_audioFrame.type = IAudioFrameObserver::FRAME_TYPE_PCM16;
m_audioFrame.channels = waveFormat.nChannels;
m_audioFrame.samplesPerSec = waveFormat.nSamplesPerSec;
m_audioFrame.samples = m_audioFrame.samplesPerSec / 100;
// 设置 onRecordAudioFrame 回调的录制声音格式。
// mode 设置为 RAW_AUDIO_FRAME_OP_MODE_READ_WRITE 才可以读写和操作数据。
m_rtcEngine->setRecordingAudioFrameParameters(waveFormat.nSamplesPerSec, waveFormat.nChannels, RAW_AUDIO_FRAME_OP_MODE_READ_WRITE, waveFormat.nSamplesPerSec * waveFormat.nChannels / 100);
// 创建音频采集过滤器
if (!m_agAudioCaptureDevice.CreateCaptureFilter())
return;
// 开始音频自采集
m_agAudioCaptureDevice.Start();
}
else {
// 停止音频自采集
m_agAudioCaptureDevice.Stop();
}
m_extenalCaptureAudio = !m_extenalCaptureAudio;
}
pushAudioFrame
将自采集的音频数据发送给 SDK。// 将自采集的音频数据发送给 SDK
void CAgoraCaptureAduioDlg::PushAudioFrameThread(CAgoraCaptureAduioDlg * self)
{
// 创建使用 IMediaEngine 类为 template 的 AutoPtr 实例
agora::util::AutoPtr<agora::media::IMediaEngine> mediaEngine;
// AutoPtr 实例调用 queryInterface 方法,通过 IID 获取 IMediaEngine 实例的指针。
// AutoPtr 实例会通过箭头操作符访问 IMediaEngine 实例的指针并通过 IMediaEngine 实例调用 pushAudioFrame
mediaEngine.queryInterface(self->m_rtcEngine, agora::AGORA_IID_MEDIA_ENGINE);
int fps = self->m_audioFrame.samplesPerSec / self->m_audioFrame.samples;
while (self->m_extenalCaptureAudio)
{
// 获取每个音频帧样本的大小
SIZE_T nSize = self->m_audioFrame.samples * self->m_audioFrame.channels * self->m_audioFrame.bytesPerSample;
unsigned int readByte = 0;
int timestamp = 0;
// 如果未读到自采集音频 buffer,则休眠 1 毫秒并跳过下面的部分,继续循环
if (!CircleBuffer::GetInstance()->readBuffer(self->m_audioFrame.buffer, nSize, &readByte, timestamp))
{
Sleep(1);
continue;
}
CString strInfo;
strInfo.Format(_T("audio Frame buffer size:%d, readByte:%d, timestamp:%d \n"), nSize, readByte, timestamp);
OutputDebugString(strInfo);
// 设置音频帧的渲染时间,时长等于每个音频帧的时长
self->m_audioFrame.renderTimeMs = 1000 / fps;
// 将自采集的音频数据发送给 SDK
mediaEngine->pushAudioFrame(&self->m_audioFrame);
// 休眠,时长等于每个音频帧的时长
Sleep(1000 / fps);
}
}
API 参考
参考如下步骤,在你的项目中实现自定义音频渲染功能:
setExternalAudioSink
设置音频渲染。pullAudioFrame
方法拉取远端音频数据。IAudioFrameObserver
中 AudioFrame
结构体实例的 buffer
参数中获得。参考下图时序在你的项目中实现自定义音频渲染。
下图展示了自渲染情况下的音频数据流转过程:
pullAudioFrame
从 SDK 拉取远端音频数据。AudioFrame
的 buffer
参数中。setExternalAudioSink
设置自定义音频渲染。// 设置自定义音频渲染
int nRet = 0;
nRet = m_rtcEngine->setExternalAudioSink(true, m_renderAudioInfo.sampleRate, m_renderAudioInfo.channels);
pullAudioFrame
方法拉取远端音频数据,拉取的音频数据在 AudioFrame 结构体实例的 buffer 参数中获得。调用音频自渲染模块渲染拉取的远端音频数据。// 拉取远端音频数据
void CAgoraCaptureAduioDlg::PullAudioFrameThread(CAgoraCaptureAduioDlg * self)
{
int nRet = 0;
// 创建使用 IMediaEngine 类为 template 的 AutoPtr 实例
agora::util::AutoPtr<agora::media::IMediaEngine> mediaEngine;
// AutoPtr 实例调用 queryInterface 方法,通过 IID 获取 IMediaEngine 实例的指针。
// AutoPtr 实例会通过箭头操作符访问 IMediaEngine 实例的指针并通过 IMediaEngine 实例调用 pushAudioFrame
mediaEngine.queryInterface(self->m_rtcEngine, agora::AGORA_IID_MEDIA_ENGINE);
// 设置 IAudioFrameObserver 中 AudioFrame 结构体实例的参数
IAudioFrameObserver::AudioFrame audioFrame;
audioFrame.avsync_type = 0;
audioFrame.bytesPerSample = 2;
audioFrame.type = agora::media::IAudioFrameObserver::FRAME_TYPE_PCM16;
audioFrame.channels = self->m_renderAudioInfo.channels;
audioFrame.samples = self->m_renderAudioInfo.sampleRate / 100 * self->m_renderAudioInfo.channels;
audioFrame.samplesPerSec = self->m_renderAudioInfo.sampleRate;
// BYTE 为自定义数据类型:unsigned char,详见示例项目
audioFrame.buffer = new BYTE[audioFrame.samples * audioFrame.bytesPerSample];
while (self->m_extenalRenderAudio )
{
// 拉取远端音频数据
nRet = mediaEngine->pullAudioFrame(&audioFrame);
if (nRet != 0)
{
Sleep(10);
continue;
}
SIZE_T nSize = audioFrame.samples * audioFrame.bytesPerSample;
// 调用音频自渲染模块渲染拉取的远端音频数据
self->m_audioRender.Render((BYTE*)audioFrame.buffer, nSize);
}
delete audioFrame.buffer;
}
API 参考
startPreview
前调用 setExternalAudioSink
。setRecordingAudioFrameParameters
里的 mode
设置为 RAW_AUDIO_FRAME_OP_MODE_READ_WRITE
才可以读写和操作数据。