FFmpeg Filter过滤器实战

server/2024/12/23 1:22:00/

引文 - 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 fraphAVFilterGraph* filter_graph = avfilter_graph_alloc();// buffer filterconst 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 filterconst 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 filterconst 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 filterconst 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 filterconst 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 filterconst 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 splitFilteravfilter_link(buffer_ctx, 0, splitFilter_ctx, 0);// split filter's first pad to overlay filter's main padavfilter_link(splitFilter_ctx, 0, overlayFilter_ctx, 0);// split filter's second pad to crop filteravfilter_link(splitFilter_ctx, 1, cropFilter_ctx, 0);// crop filter to vflip filteravfilter_link(cropFilter_ctx, 0, vflipFilter_ctx, 0);// vflip filter to overlay filter's second padavfilter_link(vflipFilter_ctx, 0, overlayFilter_ctx, 1);// overlay filter to sink filteravfilter_link(overlayFilter_ctx, 0, buffersink_ctx, 0);// check filter graphavfilter_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的内容作为输入添加到第一个参数所指向的AVFilterContextav_buffersrc_add_frame(buffer_ctx, frame_in);// 从第一个参数所指向的AVFilterContext中读取内容到frameav_buffersink_get_frame(buffersink_ctx, frame_out);// 将处理好的内容写入到文件// 暂定格式为 YUV420Pfor (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 exitinFile.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.ppmy.cn/server/101267.html

相关文章

在Vite+TypeScript项目中导入组件时会报找不到模块的声明文件

在使用Vite创建Vue3项目时&#xff0c;遇到找不到模块“/components/***.vue”的错误。解决方法包括在src下的vite-env.d.ts文件中添加特定代码&#xff0c;或者如果文件不存在&#xff0c;则在根目录创建env.d.ts或者vite-env.d.ts文件并追加相应代码&#xff0c;从而完美解决…

Ubuntu中设置环境变量 PATH 的命令,不生效的问题“PATH=~/bin:$PATH”

1. 知识点 PATH~/bin:$PATH PATH&#xff1a;这是一个环境变量&#xff0c;用于指定操作系统在哪些目录中查找可执行文件。 ~&#xff1a;这是一个特殊的符号&#xff0c;代表当前用户的主目录。 /bin&#xff1a;这通常是存放标准实用程序&#xff08;如 ls, cp 等&#xff…

win10 / win11 永久暂停自动更新方法

step1&#xff1a;键盘win键&#xff08;就是Windows图标&#xff0c;在键盘左下角找&#xff09;R打开运行窗口输入regedit打开注册表。 step2&#xff1a;按照以下顺序打开相应文件夹 HKEY_LOCAL_MACHINE SOFTWARE Microsoft WindowsUpdate UX Settings 然后在对话框右…

Linux SystemV(共享内存(*)、消息队列、信号量)

个人主页&#xff1a;仍有未知等待探索-CSDN博客 专题分栏&#xff1a; Linux 目录 ​编辑 一、共享内存 1、原理 理解&#xff1a; 2、操作具体理解 1.概括 2.创建共享内存 共享内存的生命周期&#xff1f; key是什么&#xff1f; 进程怎么知道&#xff0c;共享内存是…

android kotlin集成WorkManager实现定时获取数据

在Android中使用Kotlin集成WorkManager来实现定时获取数据是一个很常见的需求。WorkManager可以帮助你在设备处于闲置或应用被关闭时执行后台任务&#xff0c;特别适用于需要在特定时间间隔内重复执行的任务。以下是实现步骤&#xff1a; 1. 添加依赖项 首先&#xff0c;在你…

HTML 列表和容器元素——WEB开发系列10

HTML 提供了多种方式来组织和展示内容&#xff0c;其中包括无序列表、有序列表、分区元素 ​​<div>​​ 和内联元素 ​​<span>​​、以及如何使用 ​​<div>​​​ 进行布局和表格布局。 一、HTML 列表 1. 无序列表 (​​<ul>​​) 无序列表用于展…

使用Clion开发STM32——移植FreeRTOS

FreeRTOS版本&#xff1a;202212.01 STM32型号&#xff1a;STM32H743VIT6 使用工具&#xff1a;Clion 其实在STM32cubeMX中就可以直接配置FreeRTOS&#xff0c;rtthread等操作系统&#xff0c;而且使用接口封装过的&#xff0c;很方便。 但我还是想手动一直一遍&#xff0c;肯…

Redis中List数据类型常用命令

目录 1. 基本操作 &#xff08;1&#xff09;在列表的头部插入一个元素 &#xff08;2&#xff09;在列表的尾部插入一个元素 &#xff08;3&#xff09;获取列表的长度 &#xff08;4&#xff09;获取列表中的元素 2. 读取和修改 &#xff08;1&#xff09;获取列表的范围&…