音频特征(4):改变音量
音频特征(4):改变音量
想简单地改变手机播放歌曲时声音的大小吗? 按几下手机的音量键就解决问题了。但这种办法是全局的音量控制方式,而且是通过更改扬声器的属性来实现的,并不会修改到原音频文件的数据。
小程在这里要介绍的办法,是直接修改音频文件的数据,让它的能量发生变化,也就是声音大小发生改变。我觉得之前已经说过这个办法了,参考“多媒体开发(5):声音可以调大一点吗?”。
本文介绍直接更改pcm值,以达到能量控制。
大体的思路是这样的,先解码音频文件,得到pcm文件,再修改pcm文件的数据,最后把pcm文件编码成aac。
本文的重点是修改pcm文件的数据,对于解码与编码,小程这里直接用ffmpeg的命令行来完成(小程使用的是macos环境,而且之前已经安装了FFmpeg)。
先给出完整的代码,再在后面做一些解释:
#include <stdio.h>
#include <stdlib.h>
const char* FFMPEGEXE = "ffmpeg";
const int BUF_LEN = 1024;
const int SAMPLE_RATE = 44100;
const int CHANNELS = 2;
const int BITRATE = 128;
void decode(const char* srcfile, const char* outfile) {
char buf[BUF_LEN] = {0};
sprintf(buf, "%s -i %s -f s16le -ar %d -ac %d -y %s", FFMPEGEXE, srcfile, SAMPLE_RATE, CHANNELS, outfile);
system(buf);
}
void encode(const char* srcfile, const char* outfile) {
char buf[BUF_LEN] = {0};
sprintf(buf, "%s -ar %d -ac %d -f s16le -i %s -ar %d -ac %d -b:a %dK -y %s", FFMPEGEXE, SAMPLE_RATE, CHANNELS, srcfile, SAMPLE_RATE, CHANNELS, BITRATE, outfile);
system(buf);
}
void change_volume(const char* pcmfile, double volume_factor, const char* outfile) {
const int sample_count =1024;
short samples[sample_count];
FILE* src = fopen(pcmfile, "rb");
FILE* out = fopen(outfile, "wb");
if (src && out) {
int cnt = 0;
while (cnt = fread(samples, sizeof(short), sample_count, src)) {
for (int i = 0; i < cnt; i ++) {
samples[i] = (short)(samples[i] * volume_factor);
}
fwrite(samples, sizeof(short), cnt, out);
}
fclose(src);
fclose(out);
}
}
int main(int argc, char *argv[])
{
const char* srcfile = "test.mp3";
const char* pcmfile = "test.pcm";
const char* pcmvolfile = "test_vol.pcm";
const char* outfile = "test.aac";
decode(srcfile, pcmfile);
change_volume(pcmfile, 1.1, pcmvolfile);
encode(pcmvolfile, outfile);
return 0;
}
解码,decode函数,解码为pcm中的s16le的格式,也就是一个short为一个样本。
参数ar与ac分别指定采样率与声道数。
对于pcm格式,可以再细分出许多格式,详细请查阅avcodec.h里面的定义,比如AV_CODEC_ID_PCM_F32BE、AV_CODEC_ID_PCM_F32LE等,你也可以参考之前介绍的“媒体格式的概念”这篇文章。
改变音量,这里很暴力地,直接把每个样本乘以一个系数。当然你也可以转成dB后,再乘以系数,再转回pcm数据,pcm值跟dB有关系如下:
dB=20∗log10(pcm)
pcm=pow(10,(dB/20.0))
当这个系数超过1之后,削顶失真的问题就会出现。
这里给出一个演示效果。
小于1倍音量时,声音很小,没有失真的问题(1倍音量时跟原文件一样):
1.5倍音量时,声音变大,感觉还可以;3倍音量时,开始失真明显了:
10倍音量的部分波形图是这样的,削顶失真很明显:
可以看出,让声音变小是没有问题的,但如果想放大声音并且避免明显失真的话,就要考虑一个合适的系数,比如不要超过3之类。
但是,在实际改变音量的实现上,直接乘以一个系数,并不是一个很好的办法,至少在乘以系数后应该加上限幅的处理,以避免出现截顶失真。而常用的其它改变音量的做法,比如动态范围控制(DRC)、自动增益控制(AGC)等,这些在以后再作介绍。
编码,把pcm编码成aac,可以使用FFmpeg自带的编码器(注意,FFmpeg3.x才开始支持),也可以使用faac或fdk-aac等,这里使用的是fdk-aac,先要保证FFmpeg启用了fdk-aac:
在使用命令时,需要指定输入的pcm的格式如ar/ac/f等参数。
至此,改变音量的简单实现介绍完毕了。
总结一下,本文介绍了改变音量的一种简单的实现,也就是通过改变pcm的数值来实现,基于这个思路,你可以设计出更好的处理办法,或者沿用已有的声音增益的算法,以避免放大声音后可能引入失真等问题。