自定义音频采集和渲染
声网默认的音频模块可以满足在 App 中使用基本音频功能的需求。声网 SDK 支持使用自定义的音频源和自定义的音频渲染模块为你的 App 添加特殊的音频功能。
技术原理
实时音频传输过程中,声网 SDK 通常会开启默认的音频模块。在以下场景中,你可能会发现默认的音频模块无法满足开发需求,需要自定义音频采集或自定义音频渲染。例如:
- App 中已有自己的音频模块。
- 需要使用前处理库处理采集到的音频。
- 某些音频采集设备被系统独占。为避免与其他业务产生冲突,需要灵活的设备管理策略。
使用自定义音频源管理音频帧的采集、处理和播放时,需要使用声网 SDK 外部方法。
音频数据传输
下图展示在自定义音频采集、音频渲染时,音频数据的传输过程。
自定义音频采集
- 你需要使用 SDK 外部方法自行实现采集模块。
- 调用
pushExternalAudioFrame
,将采集到的音频帧发送给 SDK。
自定义音频渲染
- 你需要使用 SDK 外部方法自行实现渲染模块。
- 调用
pullPlaybackAudioFrame
获取远端用户发送的音频数据。
前提条件
在进行操作之前,请确保你已经在项目中实现了基本的实时音视频功能。详见实现音视频互动。
实现方法
自定义音频采集
本节介绍如何实现自定义音频采集。
参考以下调用时序图,在你的 App 中实现自定义音频采集:
API 调用步骤
参考如下步骤,在你的项目中实现自定义音频采集功能:
-
创建自定义音频轨道
初始化
RtcEngine
后,调用createCustomAudioTrack
创建自定义音频轨道并获得音频轨道 ID。 -
加入频道并发布自定义音频轨道
调用
joinChannel
[2/2] 加入频道, 在ChannelMediaOptions
中,将publishCustomAudioTrackId
参数设置为步骤 1 中获得的音频轨道 ID,并将publishCustomAudioTrack
设置为true
,即可在频道中发布指定的自定义音频轨道。注意如需调用
enableCustomAudioLocalPlayback
在本地播放外部音频源,或调用adjustCustomAudioPlayoutVolume
调节自定义音频采集轨道在本地播放的音量,请将ChannelMediaOptions
中的enableAudioRecordingOrPlayout
设置为true
。JavaChannelMediaOptions option = new ChannelMediaOptions();
option.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
option.autoSubscribeAudio = true;
option.autoSubscribeVideo = true;
// 音频自采集场景下,不发布麦克风采集的音频
option.publishMicrophoneTrack = false;
// 发布自采集音频流
option.publishCustomAudioTrack = true;
// 设置自定义音频轨道 ID
option.publishCustomAudioTrackId = customAudioTrack;
// 加入频道
int res = engine.joinChannel(accessToken, channelId, 0, option); -
实现自采集模块
声网提供了 AudioFileReader.java 示例项目,演示从本地文件读取 PCM 格式的音频数据。在实际项目中,你需要结合业务创建自定义音频采集模块。
-
通过自定义音频轨道推送音频数据到 SDK
调用
pushExternalAudioFrame
将采集到的音频帧通过自定音频轨道推送至 SDK。其中,trackId
要与步骤 2 加入频道时指定的音频轨道 ID 一致,sampleRate
、channels
和bytesPerSample
用于设置外部音频帧的采样率、声道数和每个采样点的字节数。信息为确保音视频同步,声网建议你调用
getCurrentMonotonicTimeInMs
获取当前的 Monotonic Time,将timestamp
(时间戳)设置为系统 Monotonic Time。JavaaudioPushingHelper = new AudioFileReader(requireContext(), (buffer, timestamp) -> {
if (joined && engine != null && customAudioTrack != -1) {
// 推送外部音频帧至 SDK
int ret = engine.pushExternalAudioFrame(buffer, timestamp,
AudioFileReader.SAMPLE_RATE,
AudioFileReader.SAMPLE_NUM_OF_CHANNEL,
Constants.BytesPerSample.TWO_BYTES_PER_SAMPLE,
customAudioTrack);
Log.i(TAG, "pushExternalAudioFrame times:" + (++pushTimes) + ", ret=" + ret);
}
}); -
销毁自定义音频轨道
如果你需要停止发布自定义采集的音频,调用
destroyCustomAudioTrack
销毁自定义音频轨道。Javaengine.destroyCustomAudioTrack(customAudioTrack);
自定义音频渲染
本节介绍如何实现自定义音频渲染。你可以采用如下两种方式实现自定义音频渲染。
参考以下调用时序图,在你的 App 中实现自定义音频渲染:
-
调用
joinChannel
加入频道前,调用setExternalAudioSink
开启和配置自定义音频渲染。JavartcEngine.setExternalAudioSink(
true, // 开启自定义音频渲染
44100, // 采样率 (Hz)。你可以将该值设置为 16000,32000,441000 或者 48000
1 // 自定义音频源的声道数。你可以将该值设置为 1 或 2
); -
加入频道后,调用
pullPlaybackAudioFrame
获取远端用户发送的音频数据。使用你自己的音频渲染器处理音频数据,然后播放已渲染的数据。Javaprivate class FileThread implements Runnable {
@Override
public void run() {
while (mPull) {
int lengthInByte = 48000 / 1000 * 2 * 1 * 10;
ByteBuffer frame = ByteBuffer.allocateDirect(lengthInByte);
int ret = engine.pullPlaybackAudioFrame(frame, lengthInByte);
byte[] data = new byte[frame.remaining()];
frame.get(data, 0, data.length);
// 写入本地文件或由播放器进行渲染
FileIOUtils.writeFileFromBytesByChannel("/sdcard/agora/pull_48k.pcm", data, true, true);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
使用原始音频数据回调
开始前,请确保你的项目中已实现原始音频数据的采集和处理。详见原始音频数据。
参考如下步骤,在你的项目中调用原始音频数据 API 实现自定义音频渲染:
- 从
onRecordAudioFrame
,onPlaybackAudioFrame
,onMixedAudioFrame
或onPlaybackAudioFrameBeforeMixing
获取待播放的音频数据。 - 自行渲染并播放音频数据。
参考信息
示例项目
声网提供了开源的音频自采集和音频自渲染的示例项目供你参考,你可以前往下载或查看其中的源代码。