FFMPEG使用代码给视频添加时间戳滤镜

        在上一周,我用QT+FFMPEG显示电脑桌面,总算可以实时显示视频画面。在此基础上,我想为视频添加水印信息。网上的教程大多以命令行为主,少数有利用的代码的方式添加水印,而且都只能对已有文件进行操作,我还是期望能实时操作并显示添加水印的视频。

AVFilter

        相关结构体

        根据网友和官方提供的示例代码,得知FFMPEG提供了AVFilter库可以满足需求,AVFilter库其中包含了音频和视频滤镜,目前我只是用到了简单的视频滤镜。AVFilter中有重要的结构体需要我们了解,对理解代码有一定帮助。

AVFilter库中的重要结构体
结构体说明
AVFilterGrahp管理整个过滤过程
AVFilter内部实现的滤镜类
AVFilterContext滤镜上下文,用于维护滤波相关信息的实例
AVFilterInOut滤镜链的输入输出链接列表,
AVFilterLink滤镜链,链接两个相邻两个AVFilterContext。在实际应用时,可能涉及到多个滤镜协作,一个滤镜的输出可以作为另一个滤镜的输入。
AVFilterPad滤镜的输入和输出称为pad,一个AVFilter可以有多个输入和多个输出端口,AVFilterLink两端分别为前一个滤镜的输入pad和后一个滤镜的输入pad
buffersrc特殊滤镜,作为整个滤波过程的入口,通过调用av_buffersrc_add_frame可以将需要滤波的帧放入滤波过程。因此在创建该滤镜时,需要提供额外的信息指示输入帧的格式,例如time_base,图像的宽高音频采样格式等
buffersink和buffersrc对应,作为滤波过程出口的特殊滤镜。通过调用av_buffersink_get_frame提取滤波后的帧
        调用代码

        在之前的测试代码基础上,我又添加了滤镜相关的变量和函数,其实这部分可以作为全局变量,但是为了风格统一,还是将它们放在的类里面,具体定义和实现如下:

  //滤镜相关
  AVFilterGraph * filter_graph = nullptr;
  AVFilterContext *buffersink_ctx = nullptr;;
  AVFilterContext *buffersrc_ctx = nullptr;;


int CWorkThread::InitFilter(AVCodecContext *codecContext)
{
    char args[512];
    int ret = 0;
    //创建滤镜容器
    filter_graph = avfilter_graph_alloc();

    //获取源滤镜和Sink滤镜
    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();

    //滤镜的描述
    unsigned int myt= 10+time(NULL);
    std::string stime = std::to_string(myt);
    std::string filters_descr = "drawtext=fontfile=arial.ttf:fontcolor=red:fontsize=50:x=0:y=0:text='%{pts\\:localtime\\:"+stime+"\\:%Y-%m-%d\\ \\%H\\\\\\:%M\\\\\\:%S}'";
    enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };

    do {

        if (!outputs || !inputs || !filter_graph)
        {
            ret = AVERROR(ENOMEM);
            break;
        }

        //初始化数据帧的格式
        sprintf_s(args, sizeof(args),
            "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
            codecContext->width, codecContext->height, codecContext->pix_fmt,
           codecContext->time_base.num, codecContext->time_base.den,
            codecContext->sample_aspect_ratio.num, codecContext->sample_aspect_ratio.den);

        //输入数据缓存
        ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
            args, NULL, filter_graph);


        //输出数据缓存
        ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
            NULL, NULL, filter_graph);


        //设置输出的pix_fmt
        ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts,
            AV_PIX_FMT_YUV420P, AV_OPT_SEARCH_CHILDREN);


        //设置滤镜的端点
        outputs->name = av_strdup("in");
        outputs->filter_ctx = buffersrc_ctx;
        outputs->pad_idx = 0;
        outputs->next = NULL;

        inputs->name = av_strdup("out");
        inputs->filter_ctx = buffersink_ctx;
        inputs->pad_idx = 0;
        inputs->next = NULL;

        //初始化滤镜
        if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr.c_str(),
            &inputs, &outputs, NULL)) < 0)
        {
            break;
        }
        //滤镜生效
        if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
        {
            break;
        }
    } while (false);

    //释放对应的输入输出
    avfilter_inout_free(&inputs);
    avfilter_inout_free(&outputs);
    return ret;
}
滤镜初始化过程解析        

        InitFilter函数是整个滤波过程最关键的,传入的AVCodecContext参数只是为了告诉滤镜输入帧的格式,实际可以根据需要对该参数进行修改。InitFileter只需要在过滤前调用一次就好,然后就可以在获取源视频帧的位置调用av_buffersrc_add_frame_flags函数,将需要滤波的帧添加到滤镜中。然后再调用av_buffersink_get_frame获取滤波后的帧。下图是InitFilter对应的流程

InitFilter流程图

 疑问记录

        在设置滤镜端点的时候,outputs->name设置的是“in”,而inputs->name设置的是“out”,第一次看到时觉得有些奇怪,甚至怀疑是写错了。后来看到一个解释:outputs对应的是源buffersrc,作为滤波过程的起点,它必定是输出端。同理inputs对应buffersink,作为滤波的终点,它必定是只有输入端。

运行效果

        下面是运行的截图,可以看到画面左上角有一个当前时间的时间戳

  • 38
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值