开篇:连麦变声器,卡得像个鬼畜

现在社交、直播类 App,没个“变声器”、“实时混音”功能都不好意思拉用户。用户对着麦克风喊一句,变成萝莉音或者大叔音传出去,互动效果直接拉满。

但很多开发者做出来的变声功能,延迟高达几百毫秒。用户说完话,过了一秒钟才听到自己的声音,不仅自己听着难受,连麦的队友也感觉像是在跨服聊天。

音频开发里,延迟是永恒的敌人。这篇就教你用 HarmonyOS 的 OHAudio 接口,打通低延迟采集到实时变声的链路。

https://placeholder-01.jpg/

一、为什么用 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;
}

关键点:

  1. ENCODING_TYPE_RAW:告诉系统,不要给我编码,我要最原始的 PCM 字节。

  2. 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),数据就会在内存里堆积,延迟越来越大。最后声音就变成慢动作了。
所以处理逻辑必须轻量,且必须放到子线程,保证按需消费。

https://placeholder-02.png/

三、播放与回声消除

变完声,得播放出来。

如果你用扬声器外放,麦克风会再次把扬声器的声音录进去,形成极其刺耳的啸叫(回声)。

解法:使用 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 比较合适。

https://placeholder-03.png/

五、总结

实时音频处理,难在“低延迟”和“性能”。核心记住三点:

  1. 不用 AVRecorder,用 AudioCapturer 抓取裸流。

  2. 变声算法放子线程(TaskPool),防卡顿。

  3. 播放用通信流(VOICE_COMMUNICATION),触发系统自带的回声消除。

小任务: 试着用 AudioCapturer 录一段 5 秒的 PCM 数据,把它直接保存为 .pcm 文件,然后用 Audacity 软件导入,加上正确的头文件(48000Hz,单声道,16位),听听是不是你的原声。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐