FFmpeg Filter过滤器实战

引文 - FFmpeg Filter的介绍

Filter,一般被译为"过滤器"或者"滤镜",本篇文章统一以"过滤器"著称。

那么过滤器的作用是什么呢?FFmpeg中的过滤器系统是在解码之后、编码之前对媒体流进行处理的关键组件。

下图是一个FFmpeg音视频处理的流程图,可以看到在解码之后可以将数据经由过滤器处理,处理过后的内容再进行编码合成操作,这样就可以实现对一个视频进行诸如视频分辨率转换、音频混音、模糊、锐化等一系列的操作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

所以,过滤器的功能十分强大,FFmpeg中内置了几百种过滤器。可以使用 ffmpeg -filters 命令查看所有的过滤器,也可以用 ffmpeg -h filter=xxx 命令查看指定的过滤器,或者访问:FFmpeg Filters Documentation 查看官方文档。

FFmpeg过滤器实战

Filter结构分析

简单来说,FFmpeg Filter的使用可以概括为三个步骤,分别是FilterGraph绘制、数据输入、输出数据。可以理解为,"FilterGraph绘制"就是绘制一张图纸并按照图纸设置好一台机器,"数据输入"表示将原料放入这台机器,"输出数据"就是原料经过机器处理最终将目标产物输出出来。

一个完整的FilterGraph结构如下图所示,可以看到,FilterGraph本质上就是一个流程图,图中的每一个组件就是一个过滤器,例如"buffer"、"crop"、"overlay"等,而每两个过滤器之间的关系就是一个link,其描述了数据在过滤器之间的走向。

关于这个FilterGraph不用把它想的太复杂,将其看作就是一个流程图即可。

实战流程分析

首先来认识结构体:

AVFilterGraph是FilterGraph的结构体,用于描绘整个Filter流程图
AVFilterAVFilterContext用于表示一个Filter

AVFilterLink是Filter链的结构体,不过一般不常用,相反avfilter_link函数比较常见

具体流程如下:

  1. 首先要创建一张图:avfilter_graph_alloc
  2. 接着查找要使用的Filter:avfilter_get_by_name(获取与给定名称匹配的过滤器定义)
  3. 然后在图中创建并初始化这个Filter:avfilter_graph_alloc_filter - 在FilterGraph中创建新的过滤器实例。接着用avfilter_init_str或``avfilter_init_dict`初始化对应的Filter,两个函数作用一样,只是传入的参数不一样。
  4. 连接这些过滤器:avfilter_link,link操作就相当于是将a连向b,具体含义是将前者的输出作为后者的输入,对应的pad参数中pad为buffer_ctx->output_pads数组中的下标(从0开始)
  5. avfilter_graph_config检查有效性,并配置图表中的所有链接和格式。
  6. av_buffersrc_add_frame将frame作为输入源传入到过滤器中
  7. av_buffersink_get_frame获取经过滤器处理后的frame

注意事项

  1. avfilter_init_str和avfilter_graph_create_filter是使用字符串初始化buffer参数的,所以对其有严格的格式要求,必须是 "key=value" 形式的 ":" 分隔的选项列表,例如:"video_size=1280x720:time_base=1/25"
  2. FFmpeg Filter是有安全检查的,如果一个filter的输入或输出部分没有任何连接就会报错(这是FFmpeg内部自己做的)
  3. overlay过滤器的参数,"y=0:W/2"这种写法表示的实际含义就相当于"y=W/2",此时的x默认为0。"y=0:x=W/2"这种写法才表示的是从y=0,x=W/2的位置开始绘制。而且,y=W/2y=w/2 的区别在于它们分别引用了不同的宽度值,大写的W、H等表示的是输出视频的宽度,小写的则是覆盖视频的宽度。
  4. 特殊的filter,buffer和buffersink。buffer通常作为输入缓冲区,buffersink通常作为输出缓冲区。数据先经过buffer流向一系列的过滤器中,最终从buffersink中流出数据。
  5. 经过filter流程处理后的一帧图像,其大小并不会因为覆盖了多组图像而变大,像素设置的是多少就是多少。
  6. buffer options的相关定义:buffersrc.c - line 322(版本FFmpeg-7.0)

demo样例

demo用到的filter

  • buffer:通常是作为过滤器的输入源来使用,必须有。
  • buffersink:通常是作为过滤器的目标输出源来使用。
  • split:接收一个输入流,并将其分成指定数量的输出流,每个输出流都是输入流的一个完全拷贝。
  • crop:视频/图像的裁剪。
  • vflip:垂直翻转输入视频。除此之外,hflip filter用于水平翻转输入视频。
  • overlay:视频叠加。

demo样例流程图

c 复制代码
/**********************************************************
*                   filter 流程分析                        *
***********************************************************
*           [main] pad-0                                  *
* buffer ------> split ------> overlay ---> buffersink    *
*   | pad-1                      |                        *
*   | [tmp]                [flip]|                        *
*   +-----> crop --> vflip --->--+                        *
***********************************************************/
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
}


int main(int argc, char* argv)
{
    const char* inFileName = "yuv420p_1280x720.yuv";
    const char* outFileName = "out.yuv";
    ifstream inFile(inFileName, ios_base::in | ios_base::binary);
    ofstream outFile(outFileName, ios_base::out | ios_base::binary);
    int in_width = 1280;
    int in_height = 720;

    /* Step 1
     *  创建并初始化用到的filter:
     * graph、buffer、sinkbuffer、
     * split、crop、vflip、overlay
     *****************************/
    // filter fraph
    AVFilterGraph* filter_graph = avfilter_graph_alloc();
    // buffer filter
    const AVFilter* buffer = avfilter_get_by_name("buffer");
    AVFilterContext* buffer_ctx =
            avfilter_graph_alloc_filter(filter_graph, buffer, "buffer_src");
    string args = "video_size=" + to_string(in_width) + "x" + to_string(in_height)
            + ":pix_fmt=" + to_string(AV_PIX_FMT_YUV420P) + ":time_base=1/25"
            + ":pixel_aspect=1/1";
    avfilter_init_str(buffer_ctx, args.c_str());
    // sinkbuffer filter
    const AVFilter* buffersink = avfilter_get_by_name("buffersink");
    AVFilterContext* buffersink_ctx =
            avfilter_graph_alloc_filter(filter_graph, buffersink, "buffer_dst");
    avfilter_init_str(buffersink_ctx, nullptr);
    // split filter
    const AVFilter *splitFilter = avfilter_get_by_name("split");
    AVFilterContext *splitFilter_ctx =
            avfilter_graph_alloc_filter(filter_graph, splitFilter, "splitFilter");
    avfilter_init_str(splitFilter_ctx, nullptr);
    // crop filter
    const AVFilter *cropFilter = avfilter_get_by_name("crop");
    AVFilterContext *cropFilter_ctx =
            avfilter_graph_alloc_filter(filter_graph, cropFilter, "cropFilter");
    avfilter_init_str(cropFilter_ctx, "out_w=iw:out_h=ih/2:x=0:y=0");
    // vflip filter
    const AVFilter *vflipFilter = avfilter_get_by_name("vflip");
    AVFilterContext *vflipFilter_ctx =
            avfilter_graph_alloc_filter(filter_graph, vflipFilter, "vflipFilter");
    avfilter_init_str(vflipFilter_ctx, nullptr);
    // overlay filter
    const AVFilter *overlayFilter = avfilter_get_by_name("overlay");
    AVFilterContext *overlayFilter_ctx =
            avfilter_graph_alloc_filter(filter_graph, overlayFilter, "overlayFilter");
    avfilter_init_str(overlayFilter_ctx, "y=0:H/2");

    /* Step2: link filter and check
    ********************************/
    // buffer_src link to splitFilter
    avfilter_link(buffer_ctx, 0, splitFilter_ctx, 0);
    // split filter's first pad to overlay filter's main pad
    avfilter_link(splitFilter_ctx, 0, overlayFilter_ctx, 0);
    // split filter's second pad to crop filter
    avfilter_link(splitFilter_ctx, 1, cropFilter_ctx, 0);
    // crop filter to vflip filter
    avfilter_link(cropFilter_ctx, 0, vflipFilter_ctx, 0);
    // vflip filter to overlay filter's second pad
    avfilter_link(vflipFilter_ctx, 0, overlayFilter_ctx, 1);
    // overlay filter to sink filter
    avfilter_link(overlayFilter_ctx, 0, buffersink_ctx, 0);
    // check filter graph
    avfilter_graph_config(filter_graph, nullptr);

    /* Step3: 写入到输出文件
    ***********************/
    int buf_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                                            in_width, in_height, 1);
    AVFrame *frame_in = av_frame_alloc();
    uint8_t *in_buf = new uint8_t[buf_size];
    AVFrame *frame_out = av_frame_alloc();
    frame_in->width = in_width;
    frame_in->height = in_height;
    frame_in->format = AV_PIX_FMT_YUV420P;
    uint32_t frame_counts = 0;
    // tips:yuv420p格式,一个y对应一个uv,所以一个像素点平均占3/2个像素大小
    while (inFile.read((char*)in_buf, in_width * in_height * 3/2))
    {
        // 写入frame数据
        av_image_fill_arrays(frame_in->data, frame_in->linesize, in_buf,
                             AV_PIX_FMT_YUV420P, in_width, in_height, 1);

        // 将frame的内容作为输入添加到第一个参数所指向的AVFilterContext
        av_buffersrc_add_frame(buffer_ctx, frame_in);
        // 从第一个参数所指向的AVFilterContext中读取内容到frame
        av_buffersink_get_frame(buffersink_ctx, frame_out);

        // 将处理好的内容写入到文件
        // 暂定格式为 YUV420P
        for (int i = 0; i < frame_out->height; i++) // Y分量
            outFile.write((char*)frame_out->data[0] +
                    frame_out->linesize[0] * i, frame_out->width);
        for (int i = 0; i < frame_out->height / 2; i++) // U分量
            outFile.write((char*)frame_out->data[1] +
                    frame_out->linesize[1] * i, frame_out->width / 2);
        for (int i = 0; i < frame_out->height / 2; i++) // V分量
            outFile.write((char*)frame_out->data[2] +
                    frame_out->linesize[2] * i, frame_out->width / 2);
        // 每隔25帧打印一次
        if(++frame_counts % 25 == 0)
            cout << "Process " << frame_counts << "frames!" << endl;
        // 及时释放,防止内存泄漏
        av_frame_unref(frame_out);
    }

    // clear and exit
    inFile.close();
    outFile.close();
    av_frame_free(&frame_in);
    av_frame_free(&frame_out);
    avfilter_free(buffer_ctx);
    avfilter_free(buffersink_ctx);
    avfilter_free(splitFilter_ctx);
    avfilter_free(cropFilter_ctx);
    avfilter_free(vflipFilter_ctx);
    avfilter_free(overlayFilter_ctx);
    avfilter_graph_free(&filter_graph);


    cout << "filter work over!" << endl;
    return 0;
}

参考资料

  1. Filter | ffmpeg-examples (andy-zhangtao.github.io)
  2. FFmpeg filter简介 - 博客园
  3. ffmpeg入门篇-过滤器的基本使用 - 白狼栈 - 博客园 (cnblogs.com)

/andy-zhangtao.github.io/ffmpeg-examples/filter.html)

  1. FFmpeg filter简介 - 博客园
  2. ffmpeg入门篇-过滤器的基本使用 - 白狼栈 - 博客园 (cnblogs.com)
相关推荐
darkdragonking5 小时前
FLV视频封装格式详解
音视频
元争栈道7 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
元争栈道8 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
因我你好久不见10 小时前
springboot java ffmpeg 视频压缩、提取视频帧图片、获取视频分辨率
java·spring boot·ffmpeg
MediaTea12 小时前
Pr:音频仪表
音视频
桃园码工12 小时前
13_HTML5 Audio(音频) --[HTML5 API 学习之旅]
音视频·html5·audio
cuijiecheng201818 小时前
音视频入门基础:MPEG2-TS专题(21)——FFmpeg源码中,获取TS流的视频信息的实现
ffmpeg·音视频
γ..18 小时前
基于MATLAB的图像增强
开发语言·深度学习·神经网络·学习·机器学习·matlab·音视频
cuijiecheng201819 小时前
音视频入门基础:AAC专题(13)——FFmpeg源码中,获取ADTS格式的AAC裸流音频信息的实现
ffmpeg·音视频·aac
悟纤1 天前
Suno Api V4模型无水印开发「高清音频WAV下载」 —— 「Suno Api系列」第6篇
音视频·suno·suno v4·suno ai