FFmpeg简单使用:过滤器 ---- 视频过滤2

news2025/1/14 18:11:21

1. 简介

FFmpeg filter提供了很多⾳视频特效处理的功能,⽐如视频缩放、截取、翻转、叠加等。
其中定义了很多的filter,例如以下常⽤的⼀些filter。
scale:视频/图像的缩放
overlay:视频/图像的叠加
crop:视频/图像的裁剪
trim:截取视频的⽚段
rotate:以任意⻆度旋转视频
⽀持的filter的列表可以通过以下命令获得。

ffmpeg -filters

也可以查看⽂档[2],具体某个版本的⽀持情况以命令⾏获取到的结果为准。
以下是filter的⼀个简单的应⽤示例,对视频的宽和⾼减半。

ffmpeg -i input -vf scale=iw/2:ih/2 output

2. filter使用方法

FFmpeg中filter包含三个层次,filter->filterchain->filtergraph。

2.1 filter语法

⽤⼀个字符串描述filter的组成,形式如下

[in_link_1]…[in_link_N]filter_name=parameters[out_link_1]…[out_link_M]

参数说明:
1. [in_link_N]、[out_link_N]:⽤来标识输⼊和输出的标签。in_link_N是标签名,标签名可以任意命名,需使⽤⽅括号括起来。在filter_name的前⾯的标签⽤于标识输⼊,在filter_name后⾯的⽤于标识输出。⼀个filter可以有多个输⼊和多个输出,没有输⼊的filter称为source filter,没有输出的filter称为sink filter。对输⼊或输出打标签是可选的,打上标签是为了连接其他filter时使⽤。
2. filter_name:filter的名称。
3. “=parameters”:包含初始化filter的参数,是可选的。
“=parameters”有以下⼏种形式
1. 使⽤':'字符分隔的⼀个“键=值”对列表。如下所示。

ffmpeg -i input -vf scale=w=iw/2:h=ih/2 output
ffmpeg -i input -vf scale=h=ih/2:w=iw/2 output

2. 使⽤':'字符分割的“值”的列表。在这种情况下,键按照声明的顺序被假定为选项名。例如,scale filter 的前两个选项分别是w和h,当参数列表为“iw/2:ih/2”时,iw/2的值赋给w,ih/2的值赋给h。如下所示。

ffmpeg -i input -vf scale=iw/2:ih/2 output

3. 使⽤':' 字符分隔混合“值”和“键=值”对的列表。“值”必须位于“键=值”对之前,并遵循与前⼀点相同的约束顺序。之后的“键=值”对的顺序不受约束。如下所示。

ffmpeg -i input -vf scale=iw/2:h=ih/2 output

2.2 filterchain的语法

⽤⼀个字符串描述filterchain的组成,形式如下

"filter1, filter2, ... filterN-1, filterN"

说明:
1. 由⼀个或多个filter的连接⽽成,filter之间以逗号“,”分隔。
2. 每个filter都连接到序列中的前⼀个filter,即前⼀个filter的输出是后⼀个filter的输⼊。
⽐如示例

ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT

示例说明:
1. crop、vflip在同⼀个filterchain中

2.3 filtergraph的语法

⽤⼀个字符串描述filtergraph的组成,形式如下
"filterchain1;filterchain2;...filterchainN-1;fiterchainN"
说明:
1. 由⼀个或多个filter的组合⽽成,filterchain之间⽤分号";"分隔。
2. filtergraph是连接filter的有向图。它可以包含循环,⼀对filter之间可以有多个连接。
3. 当在filtergraph中找到两个相同名称的标签时,将创建相应输⼊和输出之间的连接。
4. 如果输出没有被打标签,则默认将其连接到filterchain中下⼀个filter的第⼀个未打标签的输⼊。例如
以下filterchain中。

nullsrc, split[L1], [L2]overlay, nullsink

说明:split filter有两个输出,overlay filter有两个输⼊。split的第⼀个输出标记为“L1”,overlay的第⼀个输⼊pad标记为“L2”。split的第⼆个输出将连接到overlay的第⼆个输⼊。
5. 在⼀个filter描述中,如果没有指定第⼀个filter的输⼊标签,则假定为“In”。如果没有指定最后⼀个filter的输出标签,则假定为“out”。
6. 在⼀个完整的filterchain中,所有没有打标签的filter输⼊和输出必须是连接的。如果所有filterchain的所有filter输⼊和输出pad都是连接的,则认为filtergraph是有效的[2]。
⽐如示例

ffmpeg -i INPUT -vf "split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2" OUTPUT

其中有三个filterchain, 分别是:
1. "split [main][tmp]"。它只有⼀个filter,即 split,它有⼀个默认的输⼊,即INPUT解码后的frame。有两个输出, 以 [main], [tmp] 标识。
2. "[tmp] crop=iw:ih/2:0:0, vflip [flip]"。它由两个filter组成,crop和vflip,crop的输⼊ 为[tmp],vflip的输出标识为[flip]。
3. "[main][flip] overlay=0:H/2"。它由⼀个filter组成,即overlay。有两个输⼊,[main]和[flip]。有⼀个默认的输出。

3. 基本结构

我们把⼀整个滤波的流程称为滤波过程。下⾯是⼀个滤波过程的结构

 图中简要指示出了滤波所⽤到的各个结构体,各个结构体有如下作⽤:

AVFilterGraph⽤于统合这整个滤波过程的结构体。
AVFilter滤波器,滤波器的实现是通过AVFilter以及位于其下的结构体/函数来维护的。
AVFilterContext⼀个滤波器实例,即使是同⼀个滤波器,但是在进⾏实际的滤波时,也会由于输⼊的参数不同⽽有不同的滤波效果,AVFilterContext就是在实际进⾏滤波时⽤于维护滤波相关信息的实体。
AVFilterLink 滤波器链,作⽤主要是⽤于连接相邻的两个AVFilterContext。为了实现⼀个滤波过程,可能会需要多个滤波器协同完成,即⼀个滤波器的输出可能会是另⼀个滤波器的输⼊,AVFilterLink的作⽤是串联两个相邻的滤波器实例,形成两个滤波器之间的通道。
AVFilterPad 滤波器的输⼊输出端⼝,⼀个滤波器可以有多个输⼊以及多个输出端⼝,相邻滤波器之间是通过AVFilterLink来串联的,⽽位于AVFilterLink两端的分别就是前⼀个滤波器的输出端⼝以及后⼀个滤波器的输⼊端⼝。
buffersrc ⼀个特殊的滤波器,这个滤波器的作⽤就是充当整个滤波过程的⼊⼝,通过调⽤该滤波器提供的函数(如av_buffersrc_add_frame)可以把需要滤波的帧传输进⼊滤波过程。在创建该滤波器实例的时候需要提供⼀些关于所输⼊的帧的格式的必要参数(如:time_base、图像的宽⾼、图像像素格式等)。
buffersink ⼀个特殊的滤波器,这个滤波器的作⽤就是充当整个滤波过程的出⼝,通过调⽤该滤波器提供的函数(如av_buffersink_get_frame)可以提取出被滤波过程滤波完成后的帧。

demo

#include <stdio.h>

#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>

AVFilterContext *mainsrc_ctx = NULL;
AVFilterContext *resultsink_ctx = NULL;
AVFilterGraph *filter_graph = NULL;

int init_filters(const int width, const int height, const int format)
{
    int ret = 0;
    AVFilterInOut *inputs = NULL;
    AVFilterInOut *outputs = NULL;
    char filter_args[1024] = { 0 };

    filter_graph = avfilter_graph_alloc();
    if (!filter_graph) {
        printf("Error: allocate filter graph failed\n");
        return -1;
    }

//    snprintf(filter_args, sizeof(filter_args),
//             "buffer=video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d[v0];" // Parsed_buffer_0
//             "[v0]split[main][tmp];"        // Parsed_split_1
//             "[tmp]crop=iw:ih/2:0:0,vflip[flip];"   // Parsed_crop_2 Parsed_vflip_3
//             "[main][flip]overlay=0:H/2[result];" // Parsed_overlay_4
//             "[result]buffersink", // Parsed_buffersink_5
//             width, height, format, 1, 25, 1, 1);

    snprintf(filter_args, sizeof(filter_args),
             "buffer=video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d[v0];" // Parsed_buffer_0
             "[v0]split[main][tmp];"        // Parsed_split_1
             "[tmp]crop=iw:ih/2:0:0,vflip[flip];"  // Parsed_crop_2 Parsed_vflip_3
             "[main]buffersink;" // Parsed_buffersink_4
             "[flip]buffersink", // Parsed_buffersink_5
             width, height, format, 1, 25, 1, 1);

    ret = avfilter_graph_parse2(filter_graph, filter_args, &inputs, &outputs);
    if (ret < 0) {
        printf("Cannot parse graph\n");
        return ret;
    }

    ret = avfilter_graph_config(filter_graph, NULL);    // 提交过滤器
    if (ret < 0) {
        printf("Cannot configure graph\n");
        return ret;
    }

    // Get AVFilterContext from AVFilterGraph parsing from string
    mainsrc_ctx = avfilter_graph_get_filter(filter_graph, "Parsed_buffer_0");
    if(!mainsrc_ctx) {
        printf("avfilter_graph_get_filter Parsed_buffer_0 failed\n");
        return -1;
    }
    resultsink_ctx = avfilter_graph_get_filter(filter_graph, "Parsed_buffersink_4");
    if(!resultsink_ctx) {
        printf("avfilter_graph_get_filter Parsed_buffersink_5 failed\n");
        return -1;
    }
    printf("sink_width:%d, sink_height:%d\n", av_buffersink_get_w(resultsink_ctx),
           av_buffersink_get_h(resultsink_ctx));


    return 0;
}
// ffmpeg -i 9.5.flv -vf "split[main][tmp];[tmp]crop=iw:ih/2:0:0,vflip [flip];[main][flip]overlay=0:H/2" -b:v 500k -vcodec libx264 9.5_out.flv
int main(int argc, char** argv)
{
    int ret = 0;
    int in_width = 768;
    int in_height = 320;

    avfilter_register_all();
    if(init_filters(in_width, in_height, AV_PIX_FMT_YUV420P) < 0) {
        printf("init_filters failed\n");
        return -1;
    }
    // input yuv
    FILE* inFile = NULL;
    const char* inFileName = "768x320.yuv";
    fopen_s(&inFile, inFileName, "rb+");
    if (!inFile) {
        printf("Fail to open file\n");
        return -1;
    }

    // output yuv
    FILE* outFile = NULL;
    const char* outFileName = "out_crop_vfilter_4.yuv";
    fopen_s(&outFile, outFileName, "wb");
    if (!outFile) {
        printf("Fail to create file for output\n");
        return -1;
    }

    char *graph_str = avfilter_graph_dump(filter_graph, NULL);
    FILE* graphFile = NULL;
    fopen_s(&graphFile, "graphFile.txt", "w");  // 打印filtergraph的具体情况
    fprintf(graphFile, "%s", graph_str);
    av_free(graph_str);

    AVFrame *frame_in = av_frame_alloc();
    unsigned char *frame_buffer_in = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, in_width, in_height, 1));
    av_image_fill_arrays(frame_in->data, frame_in->linesize, frame_buffer_in,
        AV_PIX_FMT_YUV420P, in_width, in_height, 1);

    AVFrame *frame_out = av_frame_alloc();
    unsigned char *frame_buffer_out = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, in_width, in_height, 1));
    av_image_fill_arrays(frame_out->data, frame_out->linesize, frame_buffer_out,
        AV_PIX_FMT_YUV420P, in_width, in_height, 1);

    frame_in->width = in_width;
    frame_in->height = in_height;
    frame_in->format = AV_PIX_FMT_YUV420P;
    uint32_t frame_count = 0;
    while (1) {
        // 读取yuv数据
        if (fread(frame_buffer_in, 1, in_width*in_height * 3 / 2, inFile) != in_width*in_height * 3 / 2) {
            break;
        }
        //input Y,U,V
        frame_in->data[0] = frame_buffer_in;
        frame_in->data[1] = frame_buffer_in + in_width*in_height;
        frame_in->data[2] = frame_buffer_in + in_width*in_height * 5 / 4;

        if (av_buffersrc_add_frame(mainsrc_ctx, frame_in) < 0) {
            printf("Error while add frame.\n");
            break;
        }
        // filter内部自己处理
        /* pull filtered pictures from the filtergraph */
        ret = av_buffersink_get_frame(resultsink_ctx, frame_out);
        if (ret < 0)
            break;

        //output Y,U,V
        if (frame_out->format == AV_PIX_FMT_YUV420P) {
            for (int i = 0; i < frame_out->height; i++) {
                fwrite(frame_out->data[0] + frame_out->linesize[0] * i, 1, frame_out->width, outFile);
            }
            for (int i = 0; i < frame_out->height / 2; i++) {
                fwrite(frame_out->data[1] + frame_out->linesize[1] * i, 1, frame_out->width / 2, outFile);
            }
            for (int i = 0; i < frame_out->height / 2; i++) {
                fwrite(frame_out->data[2] + frame_out->linesize[2] * i, 1, frame_out->width / 2, outFile);
            }
        }
        ++frame_count;
        if(frame_count % 25 == 0)
            printf("Process %d frame!\n",frame_count);
        av_frame_unref(frame_out);
    }

    fclose(inFile);
    fclose(outFile);

    av_frame_free(&frame_in);
    av_frame_free(&frame_out);
    avfilter_graph_free(&filter_graph); // 内部去释放AVFilterContext产生的内存
    printf("finish\n");

    return 0;
}

 

 

 

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

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

相关文章

新建unity项目

在此处点击新项目按钮&#xff0c;建立新的项目。 选择对应的项目模板和项目名称&#xff0c;位置。 项目新建会花费几分钟到十几分钟的时间。 新建完项目打开后就可以进入到unity引擎中。 新建项目会默认创建一个场景&#xff0c;场景保存在Assets--> Scenes中&#xff0c;…

服务器,只有“鞋盒”大小

上期,我们谈到了边缘端的远中近分类法,并介绍了戴尔科技集团最新发布的“远边缘”服务器PowerEdge XR4000。今天我们来继续说说这款服务器的奥秘。      服务器,只有“鞋盒”大小      PowerEdge XR4000代表了戴尔继续致力于边缘创新的承诺,它的大小只与鞋盒差不多,是P…

Java 集合有哪些内容?

今天我们就来简单的了解下java中的集合&#xff0c;有了解过得朋友都知道&#xff0c;也都用过&#xff0c;比如说做常用的List&#xff0c;还有Set、Map&#xff0c;而且像List和Set都是用于存储单列数据的集合&#xff0c;他们的父接口都是Conllection&#xff0c;List的特点…

hyperf中的缓存之CacheEvict

在开发中难免会遇到要批量删除一些缓存&#xff0c;hyperf的注解中提供了关于缓存的几个注解如下 Cacheable 生成缓存的&#xff0c;根据你的返回值生成缓存 参数($prefix, $value, $ttl, $listener, $offset, $group, $collect) CachePut 更新缓存 参数($prefix, $value, …

MS 训练笔记【1】:nnUNet

文章目录前言1. 安装2. 训练与测试2.1. 数据处理2.1.1. 整理数据路径2.1.2. 设置 nnFormer 读取文件的路径2.1.3. 数据集预处理2.2. 训练2.2.1. 训练代码2.2. 预测总结前言 nnUNet是德国癌症研究中心的工程师编写的框架&#xff0c;迄今为止依旧在维护和更新。本文主要记载 nn…

FBX模型

概览 fbx文件&#xff0c;一般是导出给unity使用的模型文件。 如下图所示&#xff0c;建立一个models目录&#xff0c;然后右击&#xff0c;选择 imoprt new asserts 即可导入这些文件。 展示如下&#xff0c;Mesh定义了形状。 铅笔也是同理&#xff0c;只不过铅笔有自己的贴图…

尚医通-前端列表(十一)

目录&#xff1a; &#xff08;1&#xff09;搭建前端环境 &#xff08;2&#xff09;目录结构和登录改造 &#xff08;3&#xff09;医院设置前端-列表 &#xff08;1&#xff09;搭建前端环境 vue-element-admin 简介 vue-element-admin是基于element-ui 的一套后台管理系…

计算机系统基础实验 - 数的机器级表示

实验1 数的机器级表示 实验序号&#xff1a;1 实验名称&#xff1a;数的机器级表示 适用专业&#xff1a;软件工程 学时数&#xff1a;2学时 一、实验目的 1.熟练掌握无符号整数的机器级表示 2.熟练掌握有符号整数的机器级表示 3.熟练掌握浮点数的机器级表示 二、实验要求 …

WORDPRESS WOOCOMMERCE购物网站安装AB跳转支付插件接口的教程

本文介绍在Wordpress Woocommerce购物系统上安装支付插件&#xff0c;并且对接《品牌出海AB跳转收单系统》的方法&#xff0c;以下是图文教程 注意本教程是采用Wordoress中文版本后台截取的图例&#xff0c;英文版本后台文字略有不同&#xff1b; 1、首先打开支付插件压缩包&…

反编译python 生成的exe源码

反编译python 生成的exe源码记录反编译exe工具使用 工具准备 – pyinstxtractor.py – uncompyle6 – sublime Text&#xff08;或者其他的二进制编辑工具&#xff09; 一、解包&#xff08;exe&#xff09; ①&#xff1a;先把下载的pyinstxtractor.py文件和需要进行反编译的…

π120M60代替ADuM2210SRIZ 双通道数字隔离器 电路简单速度快

π120M60代替ADuM2210SRIZ 双通道数字隔离器 电路简单速度快。具有出色的性能特征和可靠性&#xff0c;整体性能优于光耦和基于其他原理的数字隔离器产品。传输通道间彼此独立&#xff0c;可实现多种传输方向的配置&#xff0c;可实现 5.0kV rms 隔离耐压等级和 DC 到 10Mbps 信…

下载和安装MySQL官方提供的示例数据库(Employees)

一、前言 在此之前笔者写过一篇博客《你说精通MySQL其实很菜jī&#xff08;1&#xff09;&#xff1a;你不一定会的基本技巧或知识点&#xff08;值得一看&#xff09;》&#xff0c;本文内容是从那篇博客截取出来的。我们要学习MySQL相关的技术点&#xff0c;使用官方提供的测…

构造HTTP请求

构造HTTP请求一、浏览器自己构造二、通过 form 表单构造三、通过 ajax 构造一、浏览器自己构造 1&#xff09;地址栏里写URL&#xff0c;构造出GET请求 2&#xff09;点击a标签等&#xff0c;也会构造GET请求 3&#xff09;img、link、script也会构造GET请求 二、通过 form 表…

Java中的常用队列

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;Java中的常用队列 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: 林在闪闪…

【Django】第二课 基于Django图书借阅管理网站平台

概念 本文在上一篇文章之上完成登录&#xff0c;图书显示&#xff0c;关键字搜索以及分页功能 登录功能实现 当用户在首页进行输入学生用户信息后&#xff0c;点击登录按钮发送请求给服务器&#xff0c;地址请求为: /toLogin/ urls.py path(toLogin/,views.toLogin), 将接…

ArcGIS基础实验操作100例--实验3旋转矢量要素

实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 基础编辑篇--实验3 旋转矢量要素 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&#xff09;加载【旋转】工具 &#xff08;2&#xff09;旋转矢量要素 一…

灵龟八法推算

很久之前就对这个算法感兴趣了&#xff0c;但是一直没搞定&#xff0c;网上公说公的&#xff0c;婆说婆的&#xff0c;搞得你头晕眼睛花&#xff0c;最后东拼西凑研究了好久才研究出来,在这里给大家分享。 第一步&#xff0c;掏出你的老黄历&#xff0c;如果你没有老黄历问题不…

BUUCTF Misc 弱口令 [RoarCTF2019]黄金6年 小易的U盘 [WUSTCTF2020]alison_likes

目录 弱口令 [RoarCTF2019]黄金6年 小易的U盘 [WUSTCTF2020]alison_likes 弱口令 下载文件 一个压缩包&#xff0c;需要密码&#xff0c;爆破了一会没出结果&#xff0c;百度了一下竟然有提示 蓝色内容复制到sublime查看&#xff08;记事本也不能看&#xff09; 摩斯密码&…

vue-cli2配置webpack生产环境.env.production、测试环境.env.development(配置不同环境的打包访问地址)

vue-cli区分办法 vue配置生产环境.env.production、测试环境.env.development vue配置webpack生产环境、测试环境 在使用webpack创建完vue2项目的时候&#xff0c;为了解决生产打包、测试打包对应的全局变量不一致的问题。 首先看一下package.json的改动&#xff1a; "…

【Git】一文带你入门Git分布式版本控制系统(创建版本库、 版本回退)

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端 &#x1f4c3;个人状态&#xff1a; 在校大学生一枚&#xff0c;已拿多个前端 offer&#xff08;秋招&#xff09; &#x1f680;未…