硬核音频系列(一)—— 声音信息的表示

基础概念扫盲,PCM 编码方式

Posted by Shao Guoji on August 5, 2019

关于「硬核音频系列」

千万别被标题所迷惑,这个系列讲的可不是什么高深莫测的理论,或者奇淫巧技骚操作,而是个人在接触数字音频知识以来的切身体会,以及少量代码实践后的学习笔记。在参与公司项目的过程中,先后了解了 PCM 编码、压缩编解码、淡入淡出等知识,因此本系列文章也将按照这个顺序组织,内容偏向入门小白级别,同时结合嵌入式系统的特点,分为三篇记录总结我的音频学习之路:

  1. 硬核音频系列(一)—— 声音信息的表示:基础概念扫盲,PCM 编码方式
  2. 硬核音频系列(二)—— 音频文件编解码格式:手写 adpcm 解码器
  3. 硬核音频系列(三)—— 线性淡入淡出:算法思路、实现与优化方法描述

本篇是系列一,主要梳理数字音频涉及的相关概念,构建基本的理解框架,为后续进一步学习做铺垫,毕竟把事情的来龙去脉弄清楚这才是最硬核的。

声音信息数字化

用代码控制声音这件事情远没有想象中有趣,系统和框架层面做的事情主要是保证音频数据正确处理和输出,音频在形式上与其他数据无异,程序猿更多承担的是加工和搬运工角色,反倒不关心数据内容。但无论如何,对于「声光热力电」此类的模拟量,要想通过写代码解决相关问题,第一步便要把信息数字化,用具体数值来表示(没办法谁让计算机只认冷冰冰二进制)。

PCM(脉冲编码调制)与切蛋糕

将模拟量用数字编码表示,PCM 是一种常见方式。这里需要区分两个概念 —— PCM 过程和 PCM 格式编码:PCM 先是作为是一种「模拟信号数字化手段」,然后才有对应的「音频 PCM 编码」,除此之外,PCM 还能应用在数字电信系统,同时也是数字视频的标准。

模数转换说白了是化连续为离散,在理解 PCM 过程中,只要抓住「对时间划分」和「对数量划分」这两个维度就足够了,一般用 y 轴表示时间,x 轴表示数量(值大小),然后你就可以想象自己在平面坐标系上切蛋糕,竖着切上几刀,横着再切几刀,将平面划分一个个小方块。

再回到音频信号本身,声音本质是振动产生的波动,就拿最简单的正弦波形举例,如何将这样一根优美的曲线,变成一串冷冰冰的数字呢?答案便是 —— 切它!

  1. 把正弦波曲线放在直角坐标系「蛋糕」上
  2. 距相等间隔竖着切蛋糕多次(采样)
  3. 距相等间隔横着切蛋糕多次
  4. 所有竖着切到曲线的点往上(或往下)移动至横切刀痕处(横竖交点处)(量化)
  5. 依次读出每个点的纵坐标,得到的一串数值便是最终的数字化结果(编码)

通过下面这个动图,你能更好理解我刚刚到底在干嘛:

图1 PCM 转换过程动画

「切蛋糕」其实是一个采集样本(takes samples)、并用样本近似表示原本信息的过程,每切一刀称为一次采样,把连续的波形用切出来的离散采样点表示,然后点连成线替代原始波形,这便是 PCM。然而,如何用有限个点表示原本复杂的波形变化,又是个值得考虑的问题。

几个重要概念(采样率、位深)

图片中为了演示效果更直观,只取了少数的几个点(8 个),转换出来的波形也是惨不忍睹的大锯齿,损失了大量的细节,和原来的信号相去甚远,完全不像啊。要解决这个问题也不难:多切几刀,加大采样密度即可:

图2 PCM 加大采样密度

可以看到在增加了采样点数量后(50 个点左右),虽然波形依然存在锯齿,但至少整体形状有了,也和原始信号更为接近。不妨假设一下,如果继续增加采样点个数,转换出来的结果还原度便会继续提高(音质更好),但总不能一直加吧(采样越多,数据越大,会消耗存储空间),到底要采样几次才比较合适?

采样率(sample rate)

在谈论这个问题之前,必须要有一个计量单位来表示采样的密度或频率,借用表示频率的单位赫兹(Hz),类比得到表示采样快慢的指标 —— 采样率,即每秒对音频采样的次数。

留意两个频率:采样的频率和声音的频率,一个是表示手段的属性,另一个是信息内容属性,两者含义不同但又有所关联(具体联系下面会提到)。

假设上面讨论的波形持续时间为 1 秒,那么第一次那个粗糙的锯齿大方波,在一秒钟内把蛋糕切成了 8 份,是采样率 8Hz 下输出的结果,第二次精细一点的波形采样率为 50Hz。显然,声音波形变换越快、越复杂,正确还原所需采样率也越大,人耳听觉范围最高频率是 20kHz,根据采样定理,对人类来说至少要以 40kHz 的频率进行采样(每秒四万次),才有可能正确还原声音。

因此我们听到的歌曲一般采样率为 44.1kHz,这也是早期 CD 唱片的采样率。另外,还有一种 DSD 格式的音频文件采样率高达 2.82MHz,一首 5 分钟的钢琴曲几百兆大。

越高的采样率在还原时的音质越好,存储数据量也越大,必须结合不同的使用场景,对音质与存储空间/传输带宽做出取舍,常用采样率表格如下:

采样率 用途
8,000 Hz 电话所用采样率,对于人的说话已经足够
22,050 Hz 无线电广播所用采样率
32,000 Hz miniDV 数字视频 camcorder、DAT(LP mode)所用采样率
44,100 Hz 音频 CD,也常用于 MPEG-1 音频(VCD, SVCD, MP3)所用采样率
48,000 Hz miniDV、数字电视、DVD、DAT、电影和专业音频所用的数字声音所用采样率
96,000 或者 192,000 Hz DVD-Audio、一些 LPCM DVD 音轨、Blu-ray Disc(蓝光光盘)音轨、和HD-DVD(高清晰度DVD)音轨所用所用采样率
2.8224 MHz SACD、索尼和飞利浦联合开发的称为 Direct Stream Digital 的 1 位 sigma-delta modulation 过程所用采样率

位深(bit depth)

前面提到 PCM 过程包括「对时间划分」和「对数量划分」这两个维度,如果说采样率是对时间划分的(竖切蛋糕),那采样位深就是对数量划分(横切蛋糕)。在数字音频中,无论每次采样得到的物理量是多少,最终都要往有限个数值上面去靠。在最小值到最大值的范围内,用间隔相等的整数表示数值大小,表示这些值所需要的二进制位数便是位深。

一图胜千言,下面是采样深度为 4bit 的 PCM 数据量化示意图(共 16 种值大小,同样精度感人):

图3 4bit量化示意图

不难看出,对任何波形进行 4bit 的 PCM 量化,得到的值输出只有 0-15 这 16 种结果,每个采样值都能用 4 个二进制位表示,共有 32 个采样(采样个数多少由采样率决定),可以用一串数字序列表示图片中这一段正弦波:

7, 9, 11, 12, 13, 14, 14, 15, 15, 15, 14, 14, 13, 12, 10, 9, 7, 6, 5, 3, 2, 1, 0, 0, 0, 0, 0, 1, 1, 2, 3, 5, 6, 7

如果用一个字节表示两个采样,通过 C 语言数组表示同样的波形采样:

char sin_wave[16] = {0x79, 0xBC, 0xDE, 0xEF, 0xFF, 0xEE, 0xDC, 0xA9. 0x76, 0x53, 0x21, 0x00, 0x00, 0x11, 0x23, 0x56};

声道数(channel)

前面的描述都是针对一个声音的情况,但实际使用场景中,许多音频文件都不止包含一个声音,系统能在同一时刻录制/播放的声音个数称为声道数。常常听到的单声道(mono)指的是只有一个声音,而立体声(stereo)能包含两个或以上的声音(像耳机的左右边、家庭环绕影院)。多个独立声道的声音通过不同扬声器同时播放,利用听觉「双耳效应」表现出声音的方向和深度。

要想表示多声道音频,需同时记录多个声源信息,n 声道音频每次采样得到 n 个采样点,由于每一个声道都要占用同大小的存储空间,因此好音质也意味着数据量将成倍增长。下面是上一节示例波形采样数据的双声道版本,一个字节代表同一时刻两个声道上的两个采样值(不同声道采样值紧挨着放置):

char stereo_sin_wave[32] = {0x77, 0x99, 0xBB, 0xCC, 0xDD, 0xEE, 0xEE, 0xFF. 0xFF, 0xFF, 0xEE, 0xEE, 0xDD, 0xCC, 0xAA, 0x99, 0x77, 0x66, 0x55, 0x33, 0x22, 0x11, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x22, 0x33, 0x55, 0x66};

相信机智的你一定发现了其中的端倪:这显然是个假的立体声,只不过是把一个声道拷贝了一遍,虽说同时能出两个声音,但这两个声音一模一样,听觉上并不立体,或许只是音量大点罢了。这个例子的目的,是通过在不同声道上放置相同的声音,让你直观了解多声道数据的表示形式:ABCDE(mono) -> AABBCCDDEE(stereo)

PCM 格式数据

上述的所有数值序列,是直接对声音波形采样量化得到,代表最原始的音频数据,能够存储在计算机中并直接播放声音,称为 PCM 格式数据。在确定了采样率、位深、和声道数后,就能完整描述一段 PCM 格式数据所表示的声音了,在录制和播放声音时,将这些参数设置好,即可实现声音与数据的转换。

PCM 格式数据占存储空间的大小,与音频时长、采样率、位深和声道数都有关,采样率越大、声道数越多,单位时间采样数量越多,位深越大,单个采样值所占空间越大,最终音频整体数据量也就越大。在音频开发中经常遇到数据大小、采样数与音频时长的相互转换,公式如下:

数据大小(Byte)= 采样率 × 音频时长(s)× 声道数 × (位深 ÷ 8)
采样数 = 采样率 × 音频时长(s)× 声道数
音频时长(s)= 采样数 ÷ 采样率 ÷ 声道数

虽然 PCM 数据能够经过采样得到并直接输出播放,计算处理过程简便、精度高音质好,但是一般不会直接存储 PCM 裸数据。主要有两个原因,一是 PCM 格式数据体积过大,可以计算,一首 44.1k 采样率、16 bit 位深、时长 5 分钟的单声道歌曲,就需要 5 × 60 × 44100 × 2Byte = 26,460,000KB = 25MB 存储空间,在如今人手一两 T 硬盘的时代看起来还好,但若是放在在早期的数字随声听设备里,恐怕歌都下载不了几首。

第二个原因是裸数据不便于管理。在计算机里,所有数据都是以二进制存放,形式上完全一样不做区分,先不说如何在一堆二进制里找到正确的音频数据,就算是找到了一段采样值,直接丢给解码器也不能播放,因为不知道对应的采样率、采样位深等参数,就无法正确地把每一个采样值拆分出来,因此还是不能得到原始波形。

针对这两个问题,引出了两个新概念 —— 编码格式和容器格式,具体细节将在硬核音频系列第二篇中详细介绍。

参考资料