FFmpeg中avfilter模块简介及测试代码(overlay)

news2024/12/27 10:45:02

      FFmpeg中的libavfilter模块(或库)用于filter(过滤器), filter可以有多个输入和多个输出。为了说明可能发生的事情,考虑以下filtergraph(过滤器图):

      该filtergraph将输入流(stream)分成两个流,然后通过crop过滤器和vflip过滤器发送一个流,然后将其与另一个流合并。
      可以使用以下命令来实现: 

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

      结果是将视频的上半部分镜像(mirrored)到输出视频的下半部分:

      (1).同一线性链(same linear chain)中的过滤器用逗号分隔,不同线性链中的过滤器用分号分隔。此示例中,crop、vflip位于一个线性链中,split和overlay分别位于另一线性链中。
      (2).线性链的连接点用方括号内的名称来标记,此示例中,split filter生成两个与标签[main]和[tmp]相关联的输出。
      (3).发送到split的第二个输出(标记为[tmp])的流,通过crop过滤器进行处理,crop掉视频的下半部分,然后垂直翻转(vertically flipped)。
      (4).overlay(覆盖或叠加)过滤器接收split过滤器的第一个未改变的输出(标记为[main]),并在其下半部分overlay由crop,vflip过滤器链生成的输出。
      (5).某些过滤器接受输入参数列表:它们在过滤器名称和等号之后指定,并以冒号彼此分隔
      (6).存在没有音频/视频输入的所谓源过滤器(so-called source filters),以及没有音频/视频输出的接收器(sink)过滤器

      graph2dot:FFmpeg工具目录中包含的graph2dot程序可用于解析filtergraph描述,并用点语言(dot language)发出相应的文本表示。

      filtergraph:
      (1).过滤器图是连接过滤器的有向图。它可以包含循环,并且一对过滤器之间可以有多个链接(multiple links)。每个链接的一侧都有一个输入板(input pad),将其连接到从中获取输入(input)的一个过滤器,另一侧有一个输出板(output pad),将其连接到接受其输出(output)的一个过滤器。
      (2).过滤器图中的每个过滤器都是在应用程序中注册的过滤器类的一个实例(an instance of a filter),它定义了过滤器的功能(features)以及输入和输出板的数量。
      (3).没有输入板的过滤器称为"source",没有输出板的过滤器称为"sink"。

      注:以上内容主要来自于:http://ffmpeg.org/ffmpeg-filters.html

      以下为测试代码:将一幅图像叠加在视频上,类似于为视频添加logo

      1.函数open_input_file:

int open_input_file(const char* filename, AVFormatContext** fmt_ctx, AVCodecContext** dec_ctx, int& video_stream_index)
{
    auto ret = avformat_open_input(fmt_ctx, filename, nullptr, nullptr);
    if (ret < 0) {
        av_log(nullptr, AV_LOG_ERROR, "Cannot open input file\n");
        print_error_string(ret);
        return ret;
    }

    if ((ret = avformat_find_stream_info(*fmt_ctx, nullptr)) < 0) {
        av_log(nullptr, AV_LOG_ERROR, "Cannot find stream information\n");
        print_error_string(ret);
        return ret;
    }

    // select the video stream
    AVCodec* dec = nullptr;
    ret = av_find_best_stream(*fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &dec, 0);
    if (ret < 0) {
        av_log(nullptr, AV_LOG_ERROR, "Cannot find a video stream in the input file\n");
        print_error_string(ret);
        return ret;
    }
    video_stream_index = ret;

    // create decoding context
    *dec_ctx = avcodec_alloc_context3(dec);
    if (!dec_ctx) {
        ret = AVERROR(ENOMEM);
        print_error_string(ret);
        return ret;
    }

    ret = avcodec_parameters_to_context(*dec_ctx, (*fmt_ctx)->streams[video_stream_index]->codecpar);
    if (ret < 0) {
        print_error_string(ret);
        return ret;
    }

    // init the video decoder
    if ((ret = avcodec_open2(*dec_ctx, dec, nullptr)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot open video decoder\n");
        return ret;
    }

    return 0;
}

      2.函数init_filters:

int init_filters(const char* filters_descr, const AVCodecContext* dec_ctx, const AVRational& time_base, AVFilterGraph** filter_graph, AVFilterContext** buffersink_ctx, AVFilterContext** buffersrc_ctx)
{
    const AVFilter* buffersrc = avfilter_get_by_name("buffer");
    const AVFilter* buffersink = avfilter_get_by_name("buffersink");
    AVFilterInOut* outputs = avfilter_inout_alloc();
    AVFilterInOut* inputs = avfilter_inout_alloc();
    *filter_graph = avfilter_graph_alloc();
    if (!outputs || !inputs || !filter_graph || !buffersrc || !buffersink) {
        return AVERROR(ENOMEM);
    }

    // buffer video source: the decoded frames from the decoder will be inserted here.
    char args[512];
    snprintf(args, sizeof(args),
        "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
        dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
        time_base.num, time_base.den,
        dec_ctx->sample_aspect_ratio.num, dec_ctx->sample_aspect_ratio.den);
    auto ret = avfilter_graph_create_filter(buffersrc_ctx, buffersrc, "in",
        args, nullptr, *filter_graph);
    if (ret < 0) {
        av_log(nullptr, AV_LOG_ERROR, "Cannot create buffer source\n");
        print_error_string(ret);
        return ret;
    }

    // buffer video sink: to terminate the filter chain.
    ret = avfilter_graph_create_filter(buffersink_ctx, buffersink, "out",
        nullptr, nullptr, *filter_graph);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n");
        print_error_string(ret);
        return ret;
    }

    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };
    ret = av_opt_set_int_list(*buffersink_ctx, "pix_fmts", pix_fmts,
        AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n");
        print_error_string(ret);
        return ret;
    }

    //Set the endpoints for the filter graph. The filter_graph will be linked to the graph described by filters_descr.

    // The buffer source output must be connected to the input pad of the first filter described by filters_descr; 
    // since the first filter input label is not specified, it is set to "in" by default.
    outputs->name = av_strdup("in");
    outputs->filter_ctx = *buffersrc_ctx;
    outputs->pad_idx = 0;
    outputs->next = nullptr;

    // The buffer sink input must be connected to the output pad of the last filter described by filters_descr; 
    // since the last filter output label is not specified, it is set to "out" by default.
    inputs->name = av_strdup("out");
    inputs->filter_ctx = *buffersink_ctx;
    inputs->pad_idx = 0;
    inputs->next = nullptr;

    if ((ret = avfilter_graph_parse_ptr(*filter_graph, filters_descr,
        &inputs, &outputs, nullptr)) < 0) {
        print_error_string(ret);
        return ret;
    }

    if ((ret = avfilter_graph_config(*filter_graph, nullptr)) < 0) {
        print_error_string(ret);
        return ret;
    }

    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);
    return ret;
}

      3.入口函数test_ffmpeg_libavfilter_movie:接收一个参数,视频文件;为了便于显示,将图像宽、高各缩小一半

int test_ffmpeg_libavfilter_movie(const char* filename)
{
    AVFormatContext* fmt_ctx = nullptr;
    AVCodecContext* dec_ctx = nullptr;
    int video_stream_index = -1;
    auto ret = open_input_file(filename, &fmt_ctx, &dec_ctx, video_stream_index);
    if (ret < 0 || !fmt_ctx || !dec_ctx) {
        fprintf(stderr, "fail to open_input_file: %d\n", ret);
        return -1;
    }

    // ffmpeg.exe -i abc.mp4 -i 1.jpg -filter_complex "overlay=x=10:y=20" out.mp4
    const char* filter_descr = "movie=1.jpg[logo];[in][logo]overlay=10:20[out]";
    AVFilterGraph* filter_graph = nullptr;
    AVFilterContext* buffersink_ctx = nullptr;
    AVFilterContext* buffersrc_ctx = nullptr;
    AVRational time_base = fmt_ctx->streams[video_stream_index]->time_base;
    ret = init_filters(filter_descr, dec_ctx, time_base, &filter_graph, &buffersink_ctx, &buffersrc_ctx);
    if (ret < 0) {
        fprintf(stderr, "fail to init_filters: %d\n", ret);
        return -1;
    }

    AVFrame* frame = av_frame_alloc();
    AVFrame* filt_frame = av_frame_alloc();
    if (!frame || !filt_frame) {
        fprintf(stderr, "fail to av_frame_alloc\n");
        return -1;
    }

    const int width_src = dec_ctx->width, height_src = dec_ctx->height;
    const int width_dst = dec_ctx->width / 2, height_dst = dec_ctx->height / 2;
    SwsContext* sws_ctx = sws_getContext(width_src, height_src, AV_PIX_FMT_YUV420P,
        width_dst, height_dst, AV_PIX_FMT_BGR24, 0, nullptr, nullptr, nullptr);
    if (!sws_ctx) {
        fprintf(stderr, "fail to sws_getContext\n");
        return -1;
    }
    const int stride_src[4] = { width_src, width_src / 2, width_src / 2, 0 };
    const int stride_dst[4] = { width_dst * 3, 0, 0, 0 };

    AVPacket packet;
    bool exit = false;
    cv::Mat mat(height_dst, width_dst, CV_8UC3);
    const char* winname = "show video";
    cv::namedWindow(winname);

    // read all packets
    while (1) {
        if (exit) break;

        if ((ret = av_read_frame(fmt_ctx, &packet)) < 0)
            break;

        if (packet.stream_index == video_stream_index) {
            ret = avcodec_send_packet(dec_ctx, &packet);
            if (ret < 0) {
                av_log(nullptr, AV_LOG_ERROR, "Error while sending a packet to the decoder\n");
                break;
            }

            while (ret >= 0) {
                ret = avcodec_receive_frame(dec_ctx, frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                    break;
                }
                else if (ret < 0) {
                    av_log(nullptr, AV_LOG_ERROR, "Error while receiving a frame from the decoder\n");
                    print_error_string(ret);
                    return -1;
                }

                frame->pts = frame->best_effort_timestamp;

                // push the decoded frame into the filtergraph
                if ((ret = av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) {
                    av_log(nullptr, AV_LOG_ERROR, "Error while feeding the filtergraph\n");
                    print_error_string(ret);
                    break;
                }

                // pull filtered frames from the filtergraph
                while (1) {
                    ret = av_buffersink_get_frame(buffersink_ctx, filt_frame);
                    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                        break;
                    if (ret < 0) {
                        fprintf(stderr, "fail to av_buffersink_get_frame: %d\n", ret);
                        return -1;
                    }

                    sws_scale(sws_ctx, filt_frame->data, stride_src, 0, height_src, &mat.data, stride_dst);
                    cv::imshow(winname, mat);

                    av_frame_unref(filt_frame);

                    int key = cv::waitKey(30);
                    if (key == 27) {
                        exit = true;
                        break;
                    }

                    av_frame_unref(filt_frame);
                }
                av_frame_unref(frame);
            }
        }
        av_packet_unref(&packet);
    }

    avfilter_graph_free(&filter_graph);
    avcodec_free_context(&dec_ctx);
    avformat_close_input(&fmt_ctx);
    av_frame_free(&frame);
    av_frame_free(&filt_frame);
    sws_freeContext(sws_ctx);

    //if (ret < 0 && ret != AVERROR_EOF) {
    //    print_error_string(ret);
    //    return ret;
    //}

    return 0;
}

      执行结果如下图所示:

      GitHub:https://github.com/fengbingchun/OpenCV_Test

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

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

相关文章

Android SDK 上手指南|| 第三章 IDE:Android Studio速览

第三章 IDE&#xff1a;Android Studio速览 Android Studio是Google官方提供的IDE&#xff0c;它是基于IntelliJ IDEA开发而来&#xff0c;用来替代Eclipse。不过目前它还属于早期版本&#xff0c;目前的版本是0.4.2&#xff0c;每个3个月发布一个版本&#xff0c;最近的版本…

非常适合大学附近的校园跑腿和自习室订座小程序

推荐两款非常适合在大学内和大学周边的项目 这两款小程序分别是校园跑腿系统和自习室在线订座系统 1、校园跑腿系统&#xff0c;第一张图所示&#xff0c;支持多校运营、快递代取、校园跑腿、租借服务、代理中心、跑腿中心、人员管理、订单抽成、数据统计、众包接单、消息通…

微信消息没通知iphone can‘t show notifications

小虎最近手机微信消息没通知&#xff0c;本来以为要卸载&#xff0c;但是发现原来是多客户端登录导致消息被其他平台截取&#xff0c;所有没有通知。 解决方法 小虎是在手机和电脑端同时登录的&#xff0c;所有退出电脑端后手机新消息就有提示了。可能是一个bug。

Docker版本号说明:安装不同版本看文档变化|遇错不求人

docker实战(一):centos7 yum安装docker docker实战(二):基础命令篇 docker实战(三):docker网络模式(超详细) docker实战(四):docker架构原理 docker实战(五):docker镜像及仓库配置 docker实战(六):docker 网络及数据卷设置 docker实战(七):docker 性质及版本选择 认知升…

Java:集合框架:Set集合、LinkedSet集合、TreeSet集合、哈希值、HashSet的底层原理

Set集合 创建一个Set集合对象&#xff0c;因为Set是一个接口不能直接new一个对象&#xff0c;所以要用一个实现类来接 HashSet来接 无序性只有一次&#xff0c;只要第一次运行出来后&#xff0c;之后再运行的顺序还是第一次的顺序。 用LinkedSet来接 有序 不重复 无索引 用Tree…

嵌入式入门教学——C51(下)

嵌入式入门教学汇总&#xff1a; 嵌入式入门教学——C51&#xff08;上&#xff09;嵌入式入门教学——C51&#xff08;中&#xff09;嵌入式入门教学——C51&#xff08;下&#xff09; 十三、AT24C02&#xff08;I2C总线&#xff09; 1、存储器 RAM、ROM各有优势&#xff…

pandas(pd)数据的一些操作( np数据转成pd数据、pd数据保存csv文件)

一. np数据转成pd数据 import pandas as pd import numpy as np# 第一种 data {Category: [A, B, C],Value: [10, 20, 15]}df pd.DataFrame(data) print(df)# 第二种 data np.array([[0, 1, 2, 5],[0, 3, 4, 5],[0, 5, 6, 5]]) df pd.DataFrame(data,columns[num1, num2, …

手撕vector容器

一、vector容器的介绍 vector是表示可变大小数组的序列容器。就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素&#xff0c;但是又不像数组&#xff0c;它的大小是可以动态改变的&#xff0c;而且它的大小会被容器自动处理。 总结&#xff1a;vector是一个动态…

OpenAI Function calling

开篇 原文出处 最近 OpenAI 在 6 月 13 号发布了新 feature&#xff0c;主要针对模型进行了优化&#xff0c;提供了 function calling 的功能&#xff0c;该 feature 对于很多集成 OpenAI 的应用来说绝对是一个“神器”。 Prompt 的演进 如果初看 OpenAI 官网对function ca…

计算机网络-物理层(三)编码与调制

计算机网络-物理层&#xff08;三&#xff09;编码与调制 在计算机网络中&#xff0c;计算机需要处理和传输用户的文字、图片、音频和视频&#xff0c;它们可以统称为消息 数据是运输信息的实体&#xff0c;计算机只能处理二进制数据&#xff0c;也就是比特0和比特1。计算机中…

不含数字的webshell绕过

异或操作原理 1.首先我们得了解一下异或操作的原理 在php中&#xff0c;异或操作是两个二进制数相同时&#xff0c;异或(相同)为0&#xff0c;不同为1 举个例子 A的ASCII值是65&#xff0c;对应的二进制值是0100 0001 的ASCII值是96&#xff0c;对应的二进制值是 0110 000…

CSS加载失败的6个原因

有很多刚刚接触 CSS 的新手有时会遇到 CSS 加载失败这个问题&#xff0c;但测试时&#xff0c;网页上没有显示该样式的问题&#xff0c;这就说明 CSS 加载失败了。出现这种状况一般是因为的 CSS 路径书写错&#xff0c;或者是在浏览器中禁止掉了 CSS 的加载&#xff0c;可以重新…

Linux/Ubuntu 的日常更新,如何操作?

我安装的是Ubuntu 20.04.6 LTS的Windows上Linux子系统版本&#xff0c;启动完成后显示&#xff1a; Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.15.90.4-microsoft-standard-WSL2 x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.c…

使用mysql:5.6和owncloud镜像构建个人网盘

一、拉取镜像 使用docker拉取mysql:5.6和owncloud的镜像 [rootexam ~]# docker pull mysql:5.6 [rootexam ~]# docker pull owncloud 运行镜像生成容器实例 [rootexam ~]# docker run -d --name mydb1 --env MYSQL_ROOT_PASSWORD123456 mysql:5.6 a184c65b73ff993cc5cf86f…

保姆级教程:从0到1搭建Stable Diffusion XL完整工作流进行AI绘画 | 【人人都是算法专家】

Rocky Ding 公众号&#xff1a;WeThinkIn 写在前面 【人人都是算法专家】栏目专注于分享Rocky在AI行业中对业务/竞赛/研究/产品维度的思考与感悟。欢迎大家一起交流学习&#x1f4aa; 大家好&#xff0c;我是Rocky。 之前Rocky详细介绍了Stable Diffusion&#xff08;SD&#…

Scratch 之 RPG 引擎制作教程(1) / 地图行走

大家好&#xff0c;又和大家见面了&#xff0c;那么我们这期讲的就是RPG游戏的地图以及人物的行走。 我发现大家并不是很懂RPG游戏引擎&#xff0c;也就是说这种引擎对于技术的要求还是比较高的。为了让更多人直接上手制作RPG游戏&#xff0c;我打算开启这一系列教程。 这个教程…

又一个 Python 图形界面库,简单好用

迷途小书童的Note 读完需要 10分钟 速读仅需 4 分钟 1 环境 python 3.9.16nicegui 1.3.9 2 前言 在现代计算机应用程序开发中&#xff0c;图形用户界面&#xff08;GUI&#xff09;是用户与程序交互的重要组成部分。然而&#xff0c;GUI 开发往往需要大量的代码和复杂的布局&am…

损失函数——感知损失

感知损失&#xff08;Perceptual Loss&#xff09;是一种基于深度学习的图像风格迁移方法中常用的损失函数。与传统的均方误差损失函数&#xff08;Mean Square Error&#xff0c;MSE&#xff09;相比&#xff0c;感知损失更注重图像的感知质量&#xff0c;更符合人眼对图像质量…

webshell实践复现

目录 一.Nginx负载均衡&#xff08;反向代理及负载均衡配置以省略&#xff09; 1.环境搭建 2.复现 二.webshell实践 1&#xff1a;异或操作绕过 2&#xff1a;取反绕过 3&#xff1a;php语法绕过 一.Nginx负载均衡&#xff08;反向代理及负载均衡配置以省略&#xff09; 注…

EndNote(四)【文献引文格式、同步、智能分组、引文报告、文献笔记导出】

参考文献格式&#xff1a;&#xff08;官网引文格式下载文章引文格式更新-word&#xff09; 官网引文格式下载 网址&#xff1a;Downloads | EndNote 点击output styles&#xff1a; 下下来之后&#xff0c;放在这个路径下&#xff1a; 双击看一下&#xff1a; ok. 比如我们要…