8、音频数据格式:PCM编码、采样率、位深、声道布局

聊到音频回声消除,绕不开一个最基础的问题——音频数据到底长什么样?

我刚开始做音频开发时,拿到一段音频数据,打开十六进制一看,全是字节。当时我就懵了:这堆数字怎么就能变成声音?后来才明白,这些数字背后有一套严格的编码规则。今天咱们就把这套规则掰开揉碎了讲清楚。

8.1 PCM编码:最朴素的音频数字化方式

PCM,全称是脉冲编码调制。说白了,就是把连续的声波信号,每隔一小段时间“咔嚓”拍一张快照,然后把快照的数值记录下来。

这个过程分三步:

  • 采样:在时间轴上等间隔取点
  • 量化:把采样点的幅度值映射到离散的数值上
  • 编码:把量化后的数值用二进制表示

举个例子。你对着麦克风说“你好”,声波是连续变化的。PCM编码后,就变成了一串数字:0x23, 0x45, 0x67, 0x89...。播放时,把这些数字按顺序喂给DAC(数模转换器),就能还原出声音。

核心要点:PCM是未压缩的原始音频数据。回声消除算法直接操作的就是PCM数据。没有编码、没有压缩、没有花里胡哨的包装。

我在项目中遇到过一个问题:某款手机的回声消除效果时好时坏。排查了半天,发现是音频数据在传输过程中被做了重采样,PCM格式变了,但算法没感知到。从那以后,我养成了一个习惯——在任何音频处理链路的入口,先确认PCM格式参数。

8.2 采样率:决定你能听到多高的频率

采样率,就是每秒钟采多少个点。单位是Hz(赫兹)。

为什么采样率这么重要?奈奎斯特采样定理告诉我们:要无失真地还原一个信号,采样率必须至少是信号最高频率的两倍。

人耳能听到的频率范围大约是20Hz~20kHz。所以理论上,40kHz以上的采样率就够用了。但实际工程中,我们常用这几个值:

采样率 常见场景 最高可还原频率
8kHz 电话语音、对讲机 4kHz
16kHz VoIP通话、语音识别 8kHz
48kHz 高清音频、音乐录制 24kHz

做回声消除时,我个人最常用的是16kHz。为什么?

  • 8kHz虽然数据量小,但高频信息丢失太多,回声路径的细节不够
  • 48kHz数据量太大,处理延迟高,对实时通话不友好
  • 16kHz刚好平衡了质量和性能

我的经验:如果你在做手机端的回声消除,建议先确认一下音频链路的采样率。很多Android设备的麦克风默认是48kHz,但通话链路内部会做重采样到16kHz。如果你直接拿48kHz的数据做处理,可能会和下游不匹配。

8.3 位深:决定声音的动态范围

位深,也叫采样精度。它表示每个采样点用多少位二进制数来表示。

常见的位深有两种:

  • 16bit:每个采样点用16位表示,取值范围是-32768~32767
  • 32bit float:每个采样点用32位浮点数表示,取值范围非常大

16bit是音频行业的“老黄牛”。CD音质就是16bit/44.1kHz。做回声消除时,16bit也完全够用。

32bit float呢?它最大的好处是——不怕溢出。做算法处理时,经常要做加减乘除。如果用16bit整数,一不小心就溢出了,声音直接爆掉。用32bit float,你随便算,它都能兜住。

注意:32bit float虽然精度高,但数据量是16bit的两倍。在内存受限的嵌入式设备上,要慎重使用。我曾经在一个低端Android平板上做回声消除,用了32bit float,结果内存直接爆了。后来改成16bit,问题解决。

嗯,这里还要提一个坑:Android的AudioRecord默认输出是16bit的PCM数据。但有些第三方库或者硬件抽象层会输出32bit float。你拿到数据后,一定要先检查位深,再做处理。

8.4 声道布局:单声道还是立体声?

声道布局,就是音频数据有几个通道。

做回声消除,99%的情况用单声道。为什么?

  • 回声消除只需要一路参考信号和一路麦克风信号
  • 立体声会增加算法复杂度,而且效果提升有限
  • 大部分手机麦克风就是单声道

但有一种情况要注意:有些Android设备会输出双声道的麦克风数据(左声道是主麦克风,右声道是降噪麦克风)。这时候你要搞清楚,哪一路才是你要处理的信号。

声道布局在数据中的排列方式是这样的:

  • 单声道:L L L L L L ...
  • 立体声:L R L R L R L R ...
  • 5.1声道:L R C LFE Ls Rs ...

做回声消除时,我建议只取左声道(或者你指定的主声道)的数据。把多声道合并成单声道,可以减少计算量,而且不会丢失关键信息。

8.5 知识体系总览

下面这张图,把PCM编码的四个核心要素串起来了。你可以把它当作一个快速参考。

PCM音频数据格式核心要素 PCM编码 采样率 位深 声道布局 编码格式 8kHz / 16kHz / 48kHz 16bit / 32bit float 单声道 / 立体声 PCM / WAV / RAW 回声消除算法通常使用 16kHz / 16bit / 单声道 PCM 数据

8.6 实战:如何确认音频格式

在Android上,你可以通过AudioRecord的配置来确认音频格式。下面是一段示例代码:

// 配置音频参数
int sampleRate = 16000;      // 16kHz
int channelConfig = AudioFormat.CHANNEL_IN_MONO;  // 单声道
int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 16bit

// 创建AudioRecord
int bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
AudioRecord recorder = new AudioRecord(
    MediaRecorder.AudioSource.MIC,
    sampleRate,
    channelConfig,
    audioFormat,
    bufferSize
);

// 读取数据
short[] audioBuffer = new short[bufferSize / 2];
recorder.read(audioBuffer, 0, audioBuffer.length);

这段代码创建了一个16kHz、单声道、16bit的音频输入流。读取到的audioBuffer就是PCM数据,每个元素是一个16位的短整型。

小技巧:如果你不确定当前设备的音频格式,可以打印一下AudioRecord.getMinBufferSize()的返回值。如果返回的是负数,说明参数不合法。我经常用这个方法来快速验证配置是否正确。

8.7 总结

PCM编码是音频处理的地基。采样率决定了频率范围,位深决定了动态范围,声道布局决定了通道数量。做回声消除时,记住这个黄金组合:16kHz、16bit、单声道

你想想看,如果连音频格式都没搞对,后面再牛的算法也是白搭。所以,每次拿到音频数据,先问自己三个问题:采样率是多少?位深是多少?声道布局是什么?

搞清楚这些,咱们才能继续往下走。