FFmpeg 的 showwavespic 滤镜如何得到频谱图
音频数据通常由波形图像表示。
FFmpeg 通过使用 showwavespic 可以得到音频数据的频谱图
ffmpeg -i input -filter_complex "showwavespic=s=640x120" -frames:v 1 output.png
运行上面一条命令之后,即可得到一张如下的图片:
那么 FFmpeg 是如何将音频数据转换为波形图的呢?
首先通过命令我们知道使用了名为showwavespic
的滤镜,根据名字大概猜想此滤镜就是生成频谱图的关键所在。
所以,我么直接定位到 showwavespic
的定义处:
// showwavespic 滤镜的输入
static const AVFilterPad showwavespic_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_AUDIO,
.config_props = showwavespic_config_input,
.filter_frame = showwavespic_filter_frame,
},
{
NULL }
};
// showwavespic 滤镜的输出
static const AVFilterPad showwavespic_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.config_props = config_output, // 配置下一个滤镜的相关参数(例如输出frame 的宽、高)
.request_frame = request_frame,
},
{
NULL }
};
AVFilter ff_avf_showwavespic = {
.name = "showwavespic", // 输入的音频转换为频谱图输出
.description = NULL_IF_CONFIG_SMALL("Convert input audio to a video output single picture."),
.init = init, // 初始化方法
.uninit = uninit,
.query_formats = query_formats, // 滤镜支持的格式
.priv_size = sizeof(ShowWavesContext),
.inputs = showwavespic_inputs,
.outputs = showwavespic_outputs,
.priv_class = &showwavespic_class,
};
通过参考其他资源,理清楚滤镜的工作流程。花费几天的时间阅读 FFmpeg 的源码,生成波形图的原理 – 解码音频文件得到音频裸数据 —> 通过 showwavespic 滤镜处理PCM数据得到波形图
showwavespic 滤镜是如何处理 PCM 数据得到波形图的呢?
PCM 数据
首先我们要了解什么是 PCM 音频数据:
PCM(Pulse Code Modulation)称为脉冲编码调制,PCM 音频数据是未经压缩的音频采样数据裸流,它是由模拟信号经过采样、量化、编码转换成的标准的数字音频数据。
存储格式
如果是单声道的音频文件,采样数据按时间的先后顺序依次存入(有时也会采用LRLRLR方式存储,只是另一个声道的数据为0),如果是双声道的话就按照LRLRLR的方式存储。
单声道
+------+------+------+------+------+------+------+------+------+
| 500 | 300 | -100 | -20 | -300 | 900 | -200 | -50 | 250 |
+------+------+------+------+------+------+------+------+------+
每个采样的整数的大小最小为 -32768,最大为 32768。根据采样数据的位置和值画一个图的话,就会得到像播放器上那样的波浪形图。
立体声的采样是每一个 frame 是一个 16bit 的采样点。左右声道的数据交叉存放。
那么采样数据的绝对值按照生成图片的高的比例即可得出振幅。频率通过生成图片的宽计算得到。
- 音频文件解码得到 PCM(音频裸数据), 统计音频的采样总数
- 以 采样总数 / 输出图片的宽度 为波形图统计频率
- 采样数据的绝对值 * 生成图片的高度 / 32768 计算得出振幅大小
-
滤镜处理流程
-
流程详情
-
init
– showwavespic 滤镜的初始化static av_cold int init(AVFilterContext *ctx) { // showwaves 滤镜的私有数据 ShowWavesContext *showwaves = ctx->priv; if (!strcmp(ctx->filter->name, "showwavespic")) { // 如果是 showwavespic 滤镜 showwaves->single_pic = 1; // 使用 cline 的绘图 mode showwaves->mode = MODE_CENTERED_LINE; } return 0
-