JCHub

  • Home
  • Category
    • A/V
    • WebRTC
    • Beauty of Programming
    • Linux
    • Windows
    • Moments of Life
    • Campus Life
  • Reference
    • API Reference
    • Utilities
    • AV Test
    • Doc
  • Message Board
  • About
JCHub
Code as My Sword, Lost in Obsession
  1. Main page
  2. A/V
  3. Main content

PCM音量控制

2016年6月2日 12424hotness 5likes 8comments

一、声音的相关概念

声音是介质振动在听觉系统中产生的反应。声音总可以被分解为不同频率不同强度正弦波的叠加(傅里叶变换)。

声音有两个基本的物理属性:频率与振幅。声音的振幅就是音量,频率的高低就是指音调,频率用赫兹(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音量控制(高级篇)

This article is licensed with Creative Commons Attribution-NonCommercial-No Derivatives 4.0 International License
Tag: 音视频
Last updated:2019年11月21日

Jeff

管理员——代码为剑,如痴如醉

Tip the author Like
< Last article
Next article >

Comments

  • Eban

      你好,请问关于增益系数,也就是这里的multiplier具体该怎么确定呢?我使用

    20 * log10(Xn)的方式把一段PCM数据的响度值给计算出来了,但是还是不太清楚这个

    增益系数该如何来确定?

    2017年5月10日
    Reply
    • Jianchihu

      @Eban 这是个很大的学问,又可以写一篇长文了。等我周末花点时间整理下以前这方面的一些资料,到时放到博客上。以前我只是简单的用tan函数模拟,效果还可以,博客上也只是轻描淡写,至于用分贝公式y = 20 * log10(x)如何控制音量调节范围,周末我写篇文章,你到时再看下。

      2017年5月11日
      Reply
      • Jianchihu

        @Jianchihu

        请参考:http://blog.jianchihu.net/pcm-vol-control-advance.html  这篇最新文章。

        2017年5月19日
        Reply
    • tony

      @Eban 我最近也遇到这问题。还有网上找到的示例PCM的一串数据平方和 在通过log10计算分贝。我无法理解啊。能不详细的讲讲??

      2017年5月19日
      Reply
      • Jianchihu

        @tony

        你再看下http://blog.jianchihu.net/pcm-vol-control-advance.html 这篇文章。基本上音量调整就这样子了,不知道所谓的平方和是干什么的。

        2017年5月19日
        Reply
  • yang

    那32bit的浮点型PCM如何转换为32位整型呢

    2019年10月17日
    Reply
    • Jeff

      @yang 这个没研究过。。。

      2019年10月20日
      Reply
    • xu

      @yang 乘以2的31次方(针对有符号),转为整型, 再分别取高低16位按你要的大小端组合就可以了

      2020年5月6日
      Reply
  • razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
    Cancel

    This site uses Akismet to reduce spam. Learn how your comment data is processed.

    文章目录
    • 一、声音的相关概念
    • 二、PCM
    • 三、PCM音量控制
      • 数据溢出
      • 对数描述
    Related Posts
    • 告别CapCut收费!开源视频剪辑工具OpenCut 来了
    • 音视频开发入门:视频基础
    • 大话WebRTC
    • WebRTC音视频传输基础:NAT穿透
    • Intel平台硬件加速视频编解码开发
    Categories

    COPYRIGHT © 2026 jianchihu.net. ALL RIGHTS RESERVED.

    Theme Kratos Made By Seaton Jiang