花满楼原创


小白:音量设置?按几下音量按键就好啦!

花满楼:这种办法是全局的音量控制方式,现在是直接改音频数据,来做到音量的变化控制。

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

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

重点讲解的部分是pcm文件的运算,解码与编码部分可以参考之前的文章来写代码完成,这里直接用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不就是未压缩的格式吗,为什么还有s16le这样的格式?

花满楼:pcm是没有未压缩,但还会细化出多种格式,你可以看下avcodec.h里面的定义,比如AV_CODEC_ID_PCM_F32BE、AV_CODEC_ID_PCM_F32LE等等,也可以看下之前讲的“音频之岁月留声”。

改变音量,这里很暴力地,直接把每个样本乘以一个系数。当这个系数超过1之后,削顶失真的问题就会出现。

这里给出一个演示效果。

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

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

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

小白:你这些都是图片,我要的声音呢?

花满楼:好吧,我看下有没有上传音频的功能。

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

小白:你说这是一个简单直接的办法?难道还有不直接的办法能避免失真?

花满楼:还有其它办法来放大音量,比如动态范围控制(DRC)等,这些在以后再作介绍。

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

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