改变音量

想简单地改变手机播放歌曲时声音的大小吗? 按几下手机的音量键就解决问题了。

但这种办法是全局的音量控制方式,而且是通过更改扬声器的属性来实现的,并不会修改到原音频文件的数据。

小程在这里要介绍的办法,是直接修改音频文件的数据,让它的能量发生变化,也就是声音大小发生改变。

本文介绍直接更改pcm值,以达到能量控制。

大体的思路是这样的,先解码音频文件,得到pcm文件,再修改pcm文件的数据,最后把pcm文件编码成aac。

本文的重点是修改pcm文件的数据,对于解码与编码,小程这里直接用ffmpeg的命令行来完成(小程使用的是macos环境,而且之前已经安装了FFmpeg)。

读者如果想以写代码的方式,来代替调用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等,读者也可以参考之前介绍的“媒体格式的概念”这篇文章。

扫描二维码关注公众号,回复: 78677 查看本文章

改变音量,这里很暴力地,直接把每个样本乘以一个系数。

当这个系数超过1之后,削顶失真的问题就会出现。

这里给出一个演示效果。

小于1倍音量时,声音很小,没有失真的问题(1倍音量时跟原文件一样):
音量对比1

1.5倍音量时,声音变大,感觉还可以;3倍音量时,开始失真明显了:
音量对比2

10倍音量的部分波形图是这样的,削顶失真很明显:
10倍音量时的部分能量图

可以看出,让声音变小是没有问题的,但如果想放大声音并且避免明显失真的话,就要考虑一个合适的系数,比如不要超过3之类。

但是,在实际改变音量的实现上,直接乘以一个系数,并不是一个很好的办法,至少在乘以系数后应该加上限幅的处理,以避免出现截顶失真。而更常用的改变音量的做法,比如动态范围控制(DRC)、自动增益控制(AGC)等,这些在以后再作介绍。

编码,把pcm编码成aac,可以使用FFmpeg自带的编码器(注意,FFmpeg3.x才支持),也可以使用faac或fdk-aac等,这里使用的是fdk-aac,先要保证FFmpeg启用了fdk-aac:
打开fdk-aac

在使用命令时,需要指定输入的pcm的格式如ar/ac/f等参数。

至此,改变音量的简单实现介绍完毕了。


总结一下,本文介绍了改变音量的一种简单的实现,也就是通过改变pcm的数值来实现,基于这个思路,读者可以设计出更好的处理办法,或者沿用已有的声音增益的算法,以避免放大声音后可能引入失真等问题。

猜你喜欢

转载自blog.51cto.com/13136504/2108479