举例
要注意的是,PCM的数据在内存中是纯数据的二进制,也就是说我们无法通过解析文件得知他的采样率、采样精度、声道数等信息,只能先得知它的参数再对其进行解析。
我用于解析的PCM文件参数如下
- 采样精度——16bit
- 采样率——16000Hz
- 声道——单声道
- 音频帧率——40
- 每帧的采样点——640
波形如下
此文件为欢迎光临的语音
音频文件的尺寸 = 采样频率 x 采样精度 x 通道数 x 采样时间
于是示例文件的尺寸 = 16000 x 2 x 1 x 1.584 = 50688byte
音量控制
我们先看一个真正的音频样本波形:
如果我们放大5倍波形,也就是振幅乘以5,此时我们听到了更大的声音,此时样本波形如下:
假如我们有2048bytesPCM数据,样本大小两个字节,共有1024个样本,我们要放大两倍声音,代码可以按如下写:
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,不是我们期望的。此时我们就需要裁剪了,确保数值在正确范围内。如下代码对前面说到的放大两倍声音做了裁剪处理:
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函数模拟,效果也不错,至于使用对数如何调整请参考文末链接:
int some_level;
float multiplier = tan (some_level / 100.0 );
上面代码中音量乘数取值为tan (some_level / 100.0 ),最后实现代码如下:
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个阶跃可以调节。
如下图,最后声音音量近似按对数关系增长了:
如果想了解利用对数关系调节音量的具体实现,请参考: