前段时间接手了一个在线连麦剧本杀的项目。一开始图省事,直接上了系统最上层的 AVRecorder 进行录音。测试的时候,队友疯狂吐槽:"你这声音怎么忽快忽慢,还带着一股浓浓的电流麦质感,跟外星人通话似的。"我拿真机一测,延迟高得离谱。被逼无奈,只能彻底抛弃高级封装,下沉到 OHAudio 的 AudioCapturer 去抓取最底层的裸流。这过程中的并发调度和流控处理,真是一堂生动的底层原理课。
抛弃高级封装直接抓取底层流
做实时音频传输,任何中间环节的编码压缩和缓存排队,都会带来致命的延迟。我们必须直接拿到最原始、未经过任何处理的 PCM(脉冲编码调制)数据。这就像是你去接水,不要用带滤网的饮水机,直接去接水管里的水,速度最快。
typescript
import audio from '@ohos.multimedia.audio';
async function createLowLatencyCapturer() {
const streamInfo: audio.AudioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 48kHz 采样率
channels: audio.AudioChannel.CHANNEL_1, // 单声道,数据量减半
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 核心:只要裸数据
};
const capturer = await audio.createAudioCapturer({
streamInfo: streamInfo,
capturerInfo: {
source: audio.SourceType.SOURCE_TYPE_MIC,
capturerFlags: 0
}
});
return capturer;
}
执行这段代码后,你就能去监听 readData 回调了。注意,这个回调触发的频率极高,一秒钟会有几十次数据推过来,你在这里的操作稍微重一点,主线程就会立刻冻结。
高频回调的异步处理
由于底层推送数据的频率极高,如果你直接在回调里做降噪、混音或者写文件,应用的 UI 会直接卡死。这就好比你在收银台结账,如果收银员每扫一个商品都要跑去仓库查库存,后面的队伍就会排到门外。
typescript
import taskpool from '@ohos.taskpool';
@Concurrent
function processAudioData(buffer: ArrayBuffer): ArrayBuffer {
// 在子线程里做回声消除和变声处理
return nativeProcess(buffer);
}
capturer.on('readData', (buffer: ArrayBuffer) => {
// 注意:千万别在这里面做复杂运算
const task = new taskpool.Task(processAudioData, buffer);
taskpool.execute(task).then((processed) => {
// 处理完了,再把数据推流出去
sendToNetwork(processed);
});
});
这段代码跑完后,主线程只负责接收和转发,沉重计算全在 TaskPool 里跑,音频流顺畅不卡顿。不过要注意,如果子线程处理得慢,数据会在内存里堆积,你必须建立环形缓冲区或者设定丢帧策略。
硬件级回声消除配置
如果你用外放喇叭,麦克风会再次把喇叭的声音录进去,形成极其刺耳的回声。自己写 AEC 算法太难,必须依赖系统底层的能力。
typescript
const rendererInfo: audio.AudioRendererInfo = {
// 核心:设置为通信流,系统会自动开启 AEC 回声消除
usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION,
rendererFlags: 0
};
通过设置 STREAM_USAGE_VOICE_COMMUNICATION,底层会开启硬件级别的回声消除,通话质量瞬间提升。
常驻后台的音频任务管理
连麦时,用户如果切到微信回个消息,你的声音不能断。必须申请长时任务。
typescript
import backgroundTaskManager from '@ohos.resourceschedule.backgroundTaskManager';
// 必须在 module.json5 声明 audioRecording 模式
const taskId = await backgroundTaskManager.startBackgroundTask(
context,
backgroundTaskManager.BackgroundMode.AUDIO_RECORDING,
agent
);
警示:这玩意申请了必须要在切回前台时关掉,如果忘了调 stopBackgroundTask,手机不仅会持续发烫,甚至会被系统判定为恶意耗电。做低延迟音频,就是抛弃高级封装,使用底层 API 抓取数据,并配合多线程进行并发计算。这套组合拳打下来,连麦体验才能达标。