FFmpeg Filter过滤器实战

news2024/11/25 4:19:54

引文 - 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样例流程图
在这里插入图片描述

/**********************************************************
*                   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)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2035990.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java面试八股之什么是MQTT协议

什么是MQTT协议 MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输&#xff09;是一种轻量级的“发布/订阅”&#xff08;Publish/Subscribe&#xff09;模式的消息传输协议&#xff0c;特别适合于远程和低带宽网络环境&#xff0c;如物联网&…

【向量数据库】向量数据库的构建和检索

1、使用 sentence-transformers 将文本编码为向量 安装 sentence-transformers&#xff1a; pip install -U sentence-transformers在 huggingface 下载 all-MiniLM-L6-v2 模型权重&#xff08;1_Pooling 是文件夹&#xff0c;里面包含一个 config.json 文件&#xff09;&…

Qt内存泄漏与程序异常崩溃

内存泄漏 什么是内存泄漏&#xff1f; 内存泄漏&#xff08;Memory Leak&#xff09;指的是程序在动态分配内存后未能正确释放已分配的内存&#xff0c;导致这些内存块无法被再次使用或回收。内存泄漏的发生主要是在使用堆内存&#xff08;通过new或malloc分配的内存&#xf…

ZBrush入门使用介绍——8、模型网格显示隐藏和遮罩操作

大家好&#xff0c;我是阿赵。   有时候我们需要把需要雕刻的范围限制在某个局部&#xff0c;之前也介绍过一些方法&#xff0c;比如使用遮罩。这里再详细说一下具体的操作。这次我拿这个圆柱为例子&#xff0c;先生成多边形网格&#xff0c;再CtrlD几次增加一点细分级别。 …

【css】使用!important提升选择器的优先级

背景 昨天我的个人博客备案通过了嘛&#xff0c;然后我就想着完善页面底部的备案信息&#xff0c;参考Argon主题博客美化的 网站底部信息 但是我想要把icp备案和公安联网备案的信息分开&#xff0c;即 subject-value-value 的结构&#xff0c; 因为 value 的选择器里面写的是…

ThinkPHP教程

thinkPHP笔记 01. phpEnv配置安装 主讲老师 - 李炎恢 1. 学习基础 ThinkPHP8.x: 前端基础:HTML5/CSS(必须)、JavaScript(可选、但推荐有);后端基础:PHP基础,版本不限,但不能太老,至少PHP5.4以上语法,TP8是兼容PHP8.x的;数据库基础:MySQL数据库,掌握了常规的SQL…

uni-app总结

1. <u-form-item label"报废人" ><u--input v-model"model.remark" border"bottom" placeholder"请输入"></u--input> </u-form-item> border"bottom" 报废日期 为了

【海贼王航海日志:前端技术探索】一篇文章带你走进JavaScript(一)

目录 1 -> 初识JavaScript 1.1 -> JavaScript是什么 1.2 -> 发展历史 1.3 -> JavaScript和HTML和CSS之间的关系 1.4 -> JavaScript运行过程 1.5 -> JavaScript的组成 2 -> 前置知识 2.1 -> JavaScript的书写形式 2.2 -> 注释 2.3 -> 输…

Stable Diffusion-inpaint(mask补全)是怎么做的?

AIGC专栏4——Stable Diffusion原理解析-inpaint修复图片为例_diffusion inpaint-CSDN博客 如果我们必须训练一个inpaint模型才能对当前的模型进行inpaint&#xff0c;那就太麻烦了&#xff0c;有没有什么方法可以不需要训练就能inpaint呢&#xff1f; Stable Diffusion就是一…

Unity新输入系统 之 InputAction(输入配置文件最基本的单位)

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正​ 首先你应该了解新输入系统的构成结构&#xff1a;Unity新输入系统结构概览-CSDN博客 Input System - Unity 手册 1.In…

创客匠人媛姐:做得一切都是为了拿到结果!

大家好&#xff0c;我是媛姐。近期我做了《百场IP发售销讲实战宣讲-发售教练点评》的直播活动。邀请了艺得世界人才创造社白钰玮老师为大家现场演练一场销讲&#xff0c;展示发售销讲私教班的培训成果。结果证明&#xff0c;白老师完成得非常出色。 以下&#xff0c;我将分享一…

WPF APP生命周期和全局异常捕获

应用启动事件与启动参数 属性查找 选择想要控件的事件&#xff0c;可以在控件上鼠标右击选择属性&#xff0c;在右上角点击闪电符号即可看到这个控件的所有事件&#xff1a; APP.Run()启动方法&#xff1a; 打开项目中这个文件&#xff1a; ".....\XH.EventLesson\obj…

Chapter 9 Operational Amplifiers

Chapter 9 Operational Amplifiers operational amplifier (op-amp) 运算放大器无疑是模拟电路中最基础最重要的block之一. 这一章我们首先review telescopic and folded-cascode 拓扑, 然后学习two-stage and gain-boosting 结构, 和共模反馈问题, 最后引入slew rate, 分析su…

基于Python、Django开发Web计算器

1、创建项目 创建Django项目参照https://blog.csdn.net/qq_42148307/article/details/140798249&#xff0c;其中项目名为compute&#xff0c;并在该项目下创建一个名为app的应用&#xff0c;并且进行基本的配置。 2、导入Bootstrap前端框架 Bootstrap的使用参照https://blo…

【项目分享】使用python的ttkbootstrap模块构建一个炫酷的计时器

目录 前言 项目背景 项目展示(图片) 项目实现 1. 安装与设置 2. 创建主窗口 3. 初始化计时器功能 4. 实现计时功能 5. 实现隐藏边框与置顶功能 6. 运行应用 完整代码 结论 🌟 嗨,我是命运之光! 🌍 2024,每日百字,记录时光,感谢有你一路同行。 🚀 携…

TCP详解(二)滑动窗口/流量控制

本文解释了TCP为何能保证数据传输的可靠性&#xff0c;以及如何保证整个网络的顺畅。 1 网络分层模型 这是一切的本质。网络被设计成分层的&#xff0c;所以网络的操作就可以称作一个“栈”&#xff0c;这就是网络协议栈的名称的由来。在具体的操作上&#xff0c;数据包最终形…

tcpdump入门——抓取三次握手数据包

1. 使用docker启动一个tcp应用 参考&#xff1a;https://blog.csdn.net/LONG_Yi_1994/article/details/141175526 2. 获取容器id docker ps |grep gochat 3. 获取容器的 PID 首先&#xff0c;你需要获得容器的进程 ID&#xff08;PID&#xff09;。可以使用 docker inspect…

kafka下载|安装

1、下载kafka https://kafka.apache.org/downloads 2、安装kafka 解压下载的kafka安装包即可 tar -xvf kafka_2.13-3.7.0.tgz -C /usr/local/3、查看kafka目录 bin目录&#xff1a;存放了脚本 config目录&#xff1a;主要存放了配置文件

Pytest-BDD流程性接口测试和自定义测试报告

引言 上篇文章《Pytest-BDD实现接口自动化测试&#xff0c;并附全部代码》我们介绍了怎么使用Pytest-BDD实现接口自动化测试&#xff0c;本篇文章主要介绍怎么去做流程性接口测试和自定义测试报告相关内容。 流程性接口测试 流程性接口测试&#xff0c;指的是一个业务流需要…

【九芯电子】智能声控台灯语音模块,低成本语音识别芯片

在当今数字化时代&#xff0c;智能家居已经逐渐成为现代生活中的一部分。从温度调节到安全监控&#xff0c;我们对家居设备的控制已经更加便捷。然而&#xff0c;随着生活节奏的加快&#xff0c;用户对于更便捷的家庭控制方式的需求也在不断增加。针对这一关键的问题&#xff0…