鸿蒙音视频进阶 01:音频低延迟采集与实时变声实战
开篇:连麦变声器,卡得像个鬼畜
现在社交、直播类 App,没个“变声器”、“实时混音”功能都不好意思拉用户。用户对着麦克风喊一句,变成萝莉音或者大叔音传出去,互动效果直接拉满。
但很多开发者做出来的变声功能,延迟高达几百毫秒。用户说完话,过了一秒钟才听到自己的声音,不仅自己听着难受,连麦的队友也感觉像是在跨服聊天。
音频开发里,延迟是永恒的敌人。这篇就教你用 HarmonyOS 的 OHAudio 接口,打通低延迟采集到实时变声的链路。
一、为什么用 OHAudio 而不是 AVRecorder?
我们在之前讲过 AVRecorder 录制音频。那玩意儿适合录个文件、语音消息。它底层经过了大量的封装,采集、编码、写文件,整个链路很长,延迟起步就是几百毫秒。
做实时变声,需要拿到最底层的 PCM(脉冲编码调制)裸数据。必须用 @ohos.multimedia.audio 里的 OHAudio(或者 AudioCapturer)。
typescript
import audio from '@ohos.multimedia.audio';
async function createAudioCapturer() {
const audioStreamInfo: audio.AudioStreamInfo = {
samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 48kHz 高采样率
channels: audio.AudioChannel.CHANNEL_1, // 单声道,降低处理量
sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 16位短整型
encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 只要裸数据
};
const audioCapturerInfo: audio.AudioCapturerInfo = {
source: audio.SourceType.SOURCE_TYPE_MIC,
capturerFlags: 0 // 0 表示普通模式,1 表示低延迟模式(如果支持)
};
const capturer = await audio.createAudioCapturer({
streamInfo: audioStreamInfo,
capturerInfo: audioCapturerInfo
});
return capturer;
}
关键点:
-
ENCODING_TYPE_RAW:告诉系统,不要给我编码,我要最原始的 PCM 字节。 -
SOURCE_TYPE_MIC:麦克风输入。
二、实时处理:PCM 到变声算法
拿到了 PCM 数据(一个 ArrayBuffer),你该怎么变声?
变声的本质是改变音频的采样率或音高(Pitch)。
-
萝莉音:提高音高,加快语速。
-
大叔音:降低音高,放慢语速。
这里涉及到复杂的数字信号处理(DSP)。自己写一套变声算法极其困难(需要做快速傅里叶变换 FFT,然后修改频域数据,再逆变换)。
实战方案: 引入 C/C++ 编写的高效音频处理库(如 SoundTouch、Rubber Band),通过 NAPI 在鸿蒙中调用。
这里写个伪代码展示流程:
typescript
import taskpool from '@ohos.taskpool';
// 开启采集
capturer.on('readData', (buffer: ArrayBuffer) => {
// 绝对不能在主线程处理音频数据!
const task = new taskpool.Task(processAudioData, buffer);
taskpool.execute(task).then((processedBuffer: ArrayBuffer) => {
// 把处理后的数据扔给播放器或者网络发送
playProcessedData(processedBuffer);
});
});
@Concurrent
function processAudioData(buffer: ArrayBuffer): ArrayBuffer {
// 这里调用底层 C++ 库进行变声
// 1. 改变音高 (Pitch Shift)
// 2. 改变节奏 (Tempo)
return nativeChangeVoice(buffer, 1.5); // 1.5 代表提高音高
}
大坑:内存碎片与延迟累积。
音频数据是连续不断的流。如果每次回调的数据你处理得很慢(比如超过了 20ms),数据就会在内存里堆积,延迟越来越大。最后声音就变成慢动作了。
所以处理逻辑必须轻量,且必须放到子线程,保证按需消费。
三、播放与回声消除
变完声,得播放出来。
如果你用扬声器外放,麦克风会再次把扬声器的声音录进去,形成极其刺耳的啸叫(回声)。
解法:使用 AEC(回声消除)。
HarmonyOS 的音频模块内置了 AEC 能力。在创建 AudioRenderer(播放器)时,需要设置正确的流类型:
typescript
const rendererInfo: audio.AudioRendererInfo = {
usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION, // 通信流,系统会自动开启 AEC
rendererFlags: 0
};
注意看 STREAM_USAGE_VOICE_COMMUNICATION,这个类型非常重要。如果你设成了 STREAM_USAGE_MUSIC,系统就只当你在放歌,绝对不会给你做回声消除。
四、避坑指南
坑一:采样率不匹配。
麦克风采集的是 48kHz,播放器如果设置成 44.1kHz,声音会变得怪怪的(类似快放或慢放)。采集、处理、播放,这三个环节的采样率必须完全一致。
坑二:缓冲区太小导致卡顿。
采集的 buffer 别设得太小(比如 1ms 一次),会导致频繁触发回调,CPU 直接跑满。一般设 10ms 或 20ms 比较合适。
五、总结
实时音频处理,难在“低延迟”和“性能”。核心记住三点:
-
不用 AVRecorder,用
AudioCapturer抓取裸流。 -
变声算法放子线程(TaskPool),防卡顿。
-
播放用通信流(VOICE_COMMUNICATION),触发系统自带的回声消除。
小任务: 试着用 AudioCapturer 录一段 5 秒的 PCM 数据,把它直接保存为 .pcm 文件,然后用 Audacity 软件导入,加上正确的头文件(48000Hz,单声道,16位),听听是不是你的原声。
更多推荐




所有评论(0)