一、声音的相关概念
声音是介质振动在听觉系统中产生的反应。声音总可以被分解为不同频率不同强度正弦波的叠加(傅里叶变换)。
声音有两个基本的物理属性:频率与振幅。声音的振幅就是音量,频率的高低就是指音调,频率用赫兹(Hz)作单位。人耳只能听到20Hz到20khz范围的声音。
模拟音频(Analogous Audio),用连续的电流或电压表示的音频信号,在时间和振幅上是连续。在过去记录声音记录的都是模拟音频,比如机械录音(以留声机、机械唱片为代表)、光学录音(以电影胶片为代表)、磁性录音(以磁带录音为代表)等模拟录音方式。
数字音频(Digital Audio),通过采样和量化技术获得的离散性(数字化)音频数据。计算机内部处理的是二进制数据,处理的都是数字音频,所以需要将模拟音频通过采样、量化转换成有限个数字表示的离散序列(即实现音频数字化)。
采样频率(Sampling Rate),单位时间内采集的样本数,是采样周期的倒数,指两个采样之间的时间间隔。采样频率必须至少是信号中最大频率分量频率的两倍,否则就不能从信号采样中恢复原始信号,这其实就是著名的香农采样定理。CD音质采样率为 44.1 kHz,其他常用采样率:22.05KHz,11.025KHz,一般网络和移动通信的音频采样率:8KHz。
量化深度,表示一个样本的二进制的位数,即样本的比特数。量化是将经过采样得到的离散数据转换成二进制数的过程,量化深度表示每个采样点用多少比特表示,在计算机中音频的量化深度一般为4、8、16、32位(bit)等。例如:量化深度为8bit时,每个采样点可以表示256个不同的量化值,而量化深度为16bit时,每个采样点可以表示65536个不同的量化值。量化深度的大小影响到声音的质量,显然,位数越多,量化后的波形越接近原始波形,声音的质量越高,而需要的存储空间也越多;位数越少,声音的质量越低,需要的存储空间越少。CD音质采用的是16 bits,移动通信 8bits。
声道数,记录声音时,如果每次生成一个声波数据,称为单声道;每次生成两个声波数据,称为双声道。使用双声道记录声音,能够在一定程度上再现声音的方位,反映人耳的听觉特性。
数字音频存储大小。采样频率、量化深度数越高,声音质量也越高,保存这段声音所用的空间也就越大。立体声(双声道)存储大小是单声道文件的两倍。即:文件大小(B)=采样频率(Hz)×录音时间(S)×(量化深度/8)×声道数(单声道为1,立体声为2)
如:录制1分钟采样频率为44.1KHz,量化深度为16位,立体声的声音(CD音质),文件大小为:
44.1×1000×60×(16/8)×2=10584000B≈10.09M
二、PCM
音频编码,指将模拟音频转换成数字音频并以某种格式存储的技术或过程。
PCM(Pulse Code Modulation)编码,即通过脉冲编码调制方法生成数字音频数据的技术或格式,是一种无损编码格式,是音频模拟信号数字化的一种方法,需要经过采样、量化和编码过程,以实现音频模拟信号数字化。
首先从6个方面描述PCM:
1)采样率;
2)符号:表示样本数据是否是有符号位,比如用一字节表示的样本数据,有符号的话表示范围为-128~127,无符号就是0~255,;
3)字节序:字节序分为大端与小端;
4)样本大小:决定了每个样本由多少位组成,即前面说到的量化深度,一般16位是最常见的;
5)声道数:分为单声道与双声道。
6)整形或浮点型:大多数格式的PCM样本数据使用整形表示,然而在一些对精度要求高的应用方面,使用浮点类型表示PCM样本数据。
打开ffmpeg,敲:ffmpeg -formats
命令,获取ffmpeg支持的音视频格式,在这当中我们可以找到支持的PCM格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
DE f32be PCM 32-bit floating-point big-endian DE f32le PCM 32-bit floating-point little-endian DE f64be PCM 64-bit floating-point big-endian DE f64le PCM 64-bit floating-point little-endian DE mulaw PCM mu-law DE s16be PCM signed 16-bit big-endian DE s16le PCM signed 16-bit little-endian DE s24be PCM signed 24-bit big-endian DE s24le PCM signed 24-bit little-endian DE s32be PCM signed 32-bit big-endian DE s32le PCM signed 32-bit little-endian DE s8 PCM signed 8-bit DE u16be PCM unsigned 16-bit big-endian DE u16le PCM unsigned 16-bit little-endian DE u24be PCM unsigned 24-bit big-endian DE u24le PCM unsigned 24-bit little-endian DE u32be PCM unsigned 32-bit big-endian DE u32le PCM unsigned 32-bit little-endian DE u8 PCM unsigned 8-bit |
比如DE s16be,就表示一个样本用16bits有符号的整形数据表示,字节序为大端。
假设我们有一个PCM signed 16-bit little-endian,双声道的PCM文件。如下是文件中前9个样本:
1 2 3 |
+------+------+------+------+------+------+------+------+------+ | 500 | 300 | -100 | -20 | -300 | 900 | -200 | -50 | 250 | +------+------+------+------+------+------+------+------+------+ |
每个样本2字节,总共18字节,每个样本取值范围:-32768 ~ 32767。
三、PCM音量控制
通过前面描述我们对PCM有了个了解,知道了在PCM流中数据如何存储。下面我们先看一个真正的音频样本波形:
如果我们放大5倍波形,也就是振幅乘以5,此时我们听到了更大的声音,此时样本波形如下:
假如我们有2048bytesPCM数据,样本大小两个字节,共有1024个样本,我们要放大两倍声音,代码可以按如下写:
1 2 3 4 |
int16_t pcm[1024] = read in some pcm data; for (ctr = 0; ctr < 1024; ctr++) { pcm[ctr] *= 2; } |
这是不是很简单,但是接下来我们还需要考虑两个方面的问题。
数据溢出
因为每个样本取值范围是有限制的,调节音量时不可能随便增大,比如一个signed 16 bits的样本,值为5000,我们放大10倍,由于有符号位16bits数据取值范围为-32768~32767,5000乘以10得到的50000超过了32767,数据溢出了,最后值可能变为-15536,不是我们期望的。此时我们就需要裁剪了,确保数值在正确范围内。如下代码对前面说到的放大两倍声音做了裁剪处理:
1 2 3 4 5 6 7 8 9 10 11 12 |
int16_t pcm[1024] = read in some pcm data; int32_t pcmval; for (ctr = 0; ctr < 1024; ctr++) { pcmval = pcm[ctr] * 2; if (pcmval < 32767 && pcmval > -32768) { pcm[ctr] = pcmval } else if (pcmval > 32767) { pcm[ctr] = 32767; } else if (pcmval < -32768) { pcm[ctr] = -32768; } } |
对数描述
平时表示声音强度我们都是用分贝(db)作单位的,声学领域中,分贝的定义是声源功率与基准声功率比值的对数乘以10的数值。根据人耳的心理声学模型,人耳对声音感知程度是对数关系,而不是线性关系。人类的听觉反应是基于声音的相对变化而非绝对的变化。对数标度正好能模仿人类耳朵对声音的反应。所以用分贝作单位描述声音强度更符合人类对声音强度的感知。前面我们直接将声音乘以某个值,也就是线性调节,调节音量时会感觉到刚开始音量变化很快,后面调的话好像都没啥变化,使用对数关系调节音量的话声音听起来就会均匀增大。
如下图,横轴表示音量调节滑块,纵坐标表示人耳感知到的音量,图中取了两块横轴变化相同的区域,音量滑块滑动变化一样,
但是人耳感觉到的音量变化是不一样的,在左侧也就是较安静的地方,感觉到音量变化大,在右侧声音较大区域人耳感觉到的音量变化较小。
下面我们讲下音量值乘数取值,这里我只简单的用tan函数模拟,效果也不错,至于使用对数如何调整请参考文末链接:
1 2 |
int some_level; float multiplier = tan (some_level / 100.0 ); |
上面代码中音量乘数取值为tan (some_level / 100.0 )
,最后实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int16_t pcm[1024] = read in some pcm data; int32_t pcmval; uint8_t level = certain value; float multiplier = tan(level/100.0); for (ctr = 0; ctr < 1024; ctr++) { pcmval = pcm[ctr] * multiplier; if (pcmval < 32767 && pcmval > -32768) { pcm[ctr] = pcmval } else if (pcmval > 32767) { pcm[ctr] = 32767; } else if (pcmval < -32768) { pcm[ctr] = -32768; } } |
其中level取值需要具体测试实现,一般使用时level取值为某个范围的几个数,比如取10个数,这样音量就有10个阶跃可以调节。
如下图,最后声音音量近似按对数关系增长了:
如果想了解利用对数关系调节音量的具体实现,请参考:
PCM音量控制(高级篇)
文章评论
你好,请问关于增益系数,也就是这里的multiplier具体该怎么确定呢?我使用
20 * log10(Xn)的方式把一段PCM数据的响度值给计算出来了,但是还是不太清楚这个
增益系数该如何来确定?
@Eban 这是个很大的学问,又可以写一篇长文了。等我周末花点时间整理下以前这方面的一些资料,到时放到博客上。以前我只是简单的用tan函数模拟,效果还可以,博客上也只是轻描淡写,至于用分贝公式y = 20 * log10(x)如何控制音量调节范围,周末我写篇文章,你到时再看下。
@Jianchihu
请参考:http://blog.jianchihu.net/pcm-vol-control-advance.html 这篇最新文章。
@Eban 我最近也遇到这问题。还有网上找到的示例PCM的一串数据平方和 在通过log10计算分贝。我无法理解啊。能不详细的讲讲??
@tony
你再看下http://blog.jianchihu.net/pcm-vol-control-advance.html 这篇文章。基本上音量调整就这样子了,不知道所谓的平方和是干什么的。
那32bit的浮点型PCM如何转换为32位整型呢
@yang 这个没研究过。。。
@yang 乘以2的31次方(针对有符号),转为整型, 再分别取高低16位按你要的大小端组合就可以了