Alby's blog

世上没有巧合,只有巧合的假象。

0%

FFmpeg filters 分析:af_volume

一、概述

本文分析 FFmpeg af_volume 的实现。

二、af_volume 的作用及基本原理

af_volume 的作用是调整音频的音量。基本原理是 PCM 数据乘于某个系数,就达到了调整音量的目的。

三、在调用 ffmpeg 程序时使用 af_volume

减半音量:

1
ffmpeg -i input.mp3 -af "volume=0.5" output.mp3

ffmpeg 的表达式解析和计算支持将 “volume=0.5” 写作 “volume=1/2”(不会当成整数相除为0的情况) 或 “volume=-6.0206dB”(负数)。

音量增大50%:

1
ffmpeg -i input.mp3 -af "volume=1.5" output.mp3

“volume=2” 可以写作 “volume=3.5218dB”(正数)。

备注: -6.0206 是个什么数字?pow(10, -6.0206 / 20) 约等于 0.5。同样的对于 3.5218, pow(10, 3.5218 / 20) 约等于 1.5

四、源码分析

af_volume 源码位于 ffmpg/libavfilter/af_volume.c 中。

分析 filter 一般从 static int filter_frame(AVFilterLink *inlink, AVFrame *in) 函数入手。

如果音量参数是以 dB 为单位,在运算时会转换为比例。

五、C# 简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/// <summary>
/// 按分贝增加音量。支持 S16LE 格式。
/// </summary>
/// <param name="raw"></param>
/// <param name="offset"></param>
/// <param name="length"></param>
/// <param name="dB">正数增加,负数减少</param>
public static void IncreaseDecibel(byte[] raw, int offset, int length, double dB)
{
if (dB == 0) return;

var scaleFactor = Math.Pow(10, dB / 20);
SetVolume(raw, offset, length, scaleFactor);
}

/// <summary>
/// 按比例设置音量。支持 S16LE 格式。
/// </summary>
/// <param name="raw"></param>
/// <param name="offset"></param>
/// <param name="length"></param>
/// <param name="factor">大于1增加音量,小于1减小音量</param>
public static void SetVolume(byte[] raw, int offset, int length, double factor)
{
if (factor == 1) return;

var numberOfSamples = length / sizeof(short);
for (var i = offset; i < numberOfSamples; i++)
{
var temp = (int)(BitConverter.ToInt16(raw, i * sizeof(short)) * factor);
temp = temp > short.MaxValue ? short.MaxValue : temp;
temp = temp < short.MinValue ? short.MinValue : temp;
var bytes = BitConverter.GetBytes((short)temp);
raw[i * sizeof(short)] = bytes[0];
raw[i * sizeof(short) + 1] = bytes[1];
}
}

参考资料

FFmpeg filters 官网文档: volume
Calculator dB