美文网首页ffmpeg
视频压缩和翻转ffmpeg滤镜(二十二)

视频压缩和翻转ffmpeg滤镜(二十二)

作者: 仙人掌__ | 来源:发表于2020-07-07 16:20 被阅读0次

    前言

    ffmpeg内置了很多滤镜库,都封装在AVFilter模块中,通过这个滤镜模块可以用来更加方便的处理音视频。比如视频分辨率压缩滤镜scale(用来对视频的分辨率进行缩放),视频翻转滤镜transpose(对视频进行上下左右的翻转);音频格式转换滤镜aformat(它实际上最终是调用avresample滤镜实现的),volume(用来调整音量大小)等等。

    关于ffmpeg的滤镜AVFilter源码及编译
    1、默认情况下libavfilter模块会编译如下文件:
    OBJS = allfilters.o
    audio.o
    avfilter.o
    avfiltergraph.o
    buffersink.o
    buffersrc.o
    drawutils.o
    fifo.o
    formats.o
    framepool.o
    framequeue.o
    graphdump.o
    graphparser.o
    transform.o
    video.o
    以上来自libavfilter/Makefile,所以就算编译ffmpeg时加入--disable-filters 也会将这些文件编译进去生成libavfilter库;
    如果要将整个libavfilter模块禁用掉,在根目录Makefile将如下语句注释掉即可:
    FFLIBS-$(CONFIG_AVFILTER) += avfilter

    2、如果要使用音频格式转换滤镜时需要添加如下选项:
    aformat滤镜用于音频格式(如采样率,采样格式,声道类型)的转换(相当于实现了SwrContext的功能),它内部最终调用的aresample滤镜,而aresample滤镜内部又是用libswresample模块
    的SwrContext实现的
    --enable-filter=aformat;--enable-filter=aresample
    3、如果要使用音频声音变化滤镜时需要添加如下选项:
    --enable-filter=volume
    4、分别对应的滤镜名称为
    ff_af_aformat;ff_af_aresample;ff_af_volume
    5、ffmpeg所有的滤镜在libavfilter/filter_list.c文件下

    本文目标

    通过scale滤镜(实现视频压缩,像素格式转换)和tranpose滤镜(实现视频上下左右翻转)来了解视频滤镜的使用流程。其实不管是音频还是视频滤镜使用流程都是一样的。

    ffmpeg的视频滤镜是通过滤镜管道来进行管理的,滤镜管道可以将各个滤镜连接到一起,形成一个处理流水线,流程如下:
    srcfilter-->scalefilter->transpose....->otherfilter->sinkfilter

    1、一个视频滤镜管道必须要有一个输入滤镜(buffer,用于接收要处理的数据),一个输出滤镜(buffersink,用于提供处理好的数据)
    2、每一个滤镜(AVFilter)都有一个滤镜上下文AVFilterContext(也称为滤镜实例)与之对应,滤镜的参数通过这个上下文来设置
    3、输入滤镜的输出端口连接着scale滤镜的输入端口,scale滤镜的输出端口连接着transpose的输入端口,transpose的输出端口连接着输出滤镜的输入端口,
    这样就形成了一个滤镜处理链

    视频滤镜使用流程

    和音频滤镜一样,视频滤镜其实使用流程也有两种方式,音频滤镜的使用连接
    中已经总结通过方式二在创建滤镜链时更加方便和简洁,故这里视频的使用也只是采用方式二

    image.png

    文字描述具体如下:
    1、创建视频输入滤镜;该滤镜作为滤镜管道的第一个滤镜,用于接收要处理的原始视频数据AVFrame
    2、创建滤镜输出滤镜,该滤镜作为滤镜管道的嘴鸥一个滤镜,用于输出滤镜处理过的视频数据AVFrame
    3、创建滤镜输入节点,输出节点并对应上输入输出滤镜。这两个节点和通过滤镜描述符创建的滤镜链最终连接成整个完整的滤镜处理链
    4、定义滤镜描述符字符串,然后通过滤镜描述符以及前面定义的inputs,ouputs节点创建一个完整的滤镜处理链
    滤镜描述符的格式:滤镜名1=滤镜参数名1=滤镜参数值1:滤镜参数名2=滤镜参数值2:.....,滤镜名2=滤镜参数名2=滤镜参数值1:滤镜参数名2=滤镜参数值2:.....,
    5、初始化滤镜管道
    6、向滤镜输入要处理的数据
    7、从滤镜获取处理好的数据

    • 具体看实现代码的解释说明

    实现代码

    bool VideoScale::init_vidoo_filter_graph()
    {
        graph = avfilter_graph_alloc();
        if (!graph) {
            LOGD("avfilter_graph_alloc() fail");
            internalrelease();
            return false;
        }
        
        // 1、创建视频输入滤镜;该滤镜作为滤镜管道的第一个滤镜,用于接收要处理的原始视频数据AVFrame
        const AVFilter *src_filter = avfilter_get_by_name("buffer");
        /** 滤镜参数的格式 key1=value1:key2=value2.....
         *  具体的参数参考源码中滤镜AVFilter对应的字段priv_class的options字段,比如视频输入滤镜的定义为AVFilter ff_vsrc_buffer,它的字段为如下:
         *  static const AVOption buffer_options[] = {
             { "width",         NULL,                     OFFSET(w),                AV_OPT_TYPE_INT,      { .i64 = 0 }, 0, INT_MAX, V },
             { "video_size",    NULL,                     OFFSET(w),                AV_OPT_TYPE_IMAGE_SIZE,                .flags = V },
             { "height",        NULL,                     OFFSET(h),                AV_OPT_TYPE_INT,      { .i64 = 0 }, 0, INT_MAX, V },
             { "pix_fmt",       NULL,                     OFFSET(pix_fmt),          AV_OPT_TYPE_PIXEL_FMT, { .i64 = AV_PIX_FMT_NONE }, .min = AV_PIX_FMT_NONE, .max = INT_MAX, .flags = V },
             { "sar",           "sample aspect ratio",    OFFSET(pixel_aspect),     AV_OPT_TYPE_RATIONAL, { .dbl = 0 }, 0, DBL_MAX, V },
             { "pixel_aspect",  "sample aspect ratio",    OFFSET(pixel_aspect),     AV_OPT_TYPE_RATIONAL, { .dbl = 0 }, 0, DBL_MAX, V },
             { "time_base",     NULL,                     OFFSET(time_base),        AV_OPT_TYPE_RATIONAL, { .dbl = 0 }, 0, DBL_MAX, V },
             { "frame_rate",    NULL,                     OFFSET(frame_rate),       AV_OPT_TYPE_RATIONAL, { .dbl = 0 }, 0, DBL_MAX, V },
             { "sws_param",     NULL,                     OFFSET(sws_param),        AV_OPT_TYPE_STRING,                    .flags = V },
             { NULL },
            };
         *  那么只需要设置width,height,pix_fmt,time_base必要字段即可,这些字段的值要和实际的值对应上,切不可乱设置,否则滤镜处理会出错
         */
        char src_args[200] = {0};
        AVStream *video_in_stream = in_fmt->streams[video_in_index];
        snprintf(src_args, sizeof(src_args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d",de_ctx->width,de_ctx->height,de_ctx->pix_fmt
                 ,video_in_stream->time_base.num,video_in_stream->time_base.den);
        // 创建输入滤镜上下文;该上下文用来对滤镜进行参数设置,初始化,连接其它滤镜等等
        int ret = avfilter_graph_create_filter(&src_filter_ctx, src_filter, "in", src_args, NULL, graph);
        if (ret < 0) {
            LOGD("avfilter_graph_create_filter() fail");
            internalrelease();
            return false;
        }
        
        /** 遇到问题:avfilter_graph_parse_ptr()调用时提示"Media type mismatch between the 'Parsed_transpose_1' filter output pad 0 (video) and the 'ou' filter input pad 0 (audio)
        Cannot create the link transpose:0 -> abuffersink:0"
         *  分析原因:视频输出滤镜的名字叫buffersink,错写成了音频输出滤镜的名字abuffersink
         *  解决方案:
         */
        // 2、创建滤镜输出滤镜,该滤镜作为滤镜管道的嘴鸥一个滤镜,用于输出滤镜处理过的视频数据AVFrame
        const AVFilter *sink_filter = avfilter_get_by_name("buffersink");
        if (!sink_filter) {
            LOGD("abuffersink get fail()");
            internalrelease();
            return false;
        }
        // 输出滤镜是不需要参数的
        ret = avfilter_graph_create_filter(&sink_filter_ctx, sink_filter, "ou", NULL, NULL, graph);
        if (ret < 0) {
            LOGD("sink filter ctx create fail()");
            internalrelease();
            return false;
        }
        
        // 3、创建滤镜输入节点,输出节点并对应上输入输出滤镜。这两个节点和通过滤镜描述符创建的滤镜链最终连接成整个完整的滤镜处理链
        AVFilterInOut *inputs = avfilter_inout_alloc();
        AVFilterInOut *ouputs = avfilter_inout_alloc();
        // inputs节点连接着通过滤镜描述符创建的滤镜链的最后一个滤镜的输出端口,所以它的name设置为"out"
        inputs->name = av_strdup("out");
        inputs->filter_ctx = sink_filter_ctx;
        inputs->pad_idx = 0;
        inputs->next = NULL;
        
        // ouputs节点连接着通过滤镜描述符创建的滤镜链的第一个滤镜的输入端口,所以它的name设置为"in"
        ouputs->name = av_strdup("in");
        ouputs->filter_ctx = src_filter_ctx;
        ouputs->pad_idx = 0;
        ouputs->next = NULL;
        
        /** 4、定义滤镜描述符字符串,然后通过滤镜描述符以及前面定义的inputs,ouputs节点创建一个完整的滤镜处理链
         *  滤镜描述符的格式:
         *  滤镜名1=滤镜参数名1=滤镜参数值1:滤镜参数名2=滤镜参数值2:.....,滤镜名2=滤镜参数名2=滤镜参数值1:滤镜参数名2=滤镜参数值2:.....,
         */
        // 这里用到了视频压缩滤镜和视频翻转滤镜;主要scale滤镜和transpose滤镜
        /** 遇到问题:avfilter_graph_parse_ptr()调用时提示"No such filter: 'scale' "
         *  分析原因:编译ffmpeg时没有将scale滤镜编译进去
         *  解决方案:通过选项 --enable-filter=scale和--enable-filter=transpose开启。
         */
        /** 遇到问题:编码时提示"Input picture width (840) is greater than stride (448)"
         *  分析原因:由于下面用到了cclock翻转滤镜,意思是视频翻转180度,那么就意味着目标视频的宽高和原视频是互换了,所以对于scale滤镜在进行压缩时就要保持宽高比和原视频反过来
         *  解决方案:scale=代表宽高压缩的比例,如果没有transpose=cclock,应该为snprintf(filter_desc, sizeof(filter_desc), "scale=%d:%d",dst_width,dst_height);由于有了transpose=cclock,
         *  则要将宽高参数调换
         */
        char filter_desc[200] = {0};
        snprintf(filter_desc, sizeof(filter_desc), "scale=%d:%d,transpose=cclock",dst_height,dst_width);
        ret = avfilter_graph_parse_ptr(graph, filter_desc, &inputs, &ouputs, NULL);
        if (ret < 0) {
            LOGD("avfilter_graph_parse_ptr fail");
            internalrelease();
            return false;
        }
        
        // 5、初始化滤镜管道
        if (avfilter_graph_config(graph, NULL) < 0) {
            LOGD("avfilter_graph_config fail");
            internalrelease();
            return false;
        }
        
        return true;
    }
    

    上面贴出了滤镜初始化前的代码,具体查看项目源码

    遇到问题

    • 问题1
      遇到问题:avfilter_graph_parse_ptr()调用时提示"Media type mismatch between the 'Parsed_transpose_1' filter output pad 0 (video) and the 'ou' filter input pad 0 (audio)
      分析原因:视频输出滤镜的名字叫buffersink,错写成了音频输出滤镜的名字abuffersink
      解决方案:更改正确名字
    • 问题2
      遇到问题:avfilter_graph_parse_ptr()调用时提示"No such filter: 'scale' "
      分析原因:编译ffmpeg时没有将scale滤镜编译进去
      解决方案:通过选项 --enable-filter=scale和--enable-filter=transpose开启。
    • 问题3
      遇到问题:编码时提示"Input picture width (840) is greater than stride (448)"
      分析原因:由于下面用到了cclock翻转滤镜,意思是视频翻转180度,那么就意味着目标视频的宽高和原视频是互换了,所以对于scale滤镜在进行压缩时就要保持宽高比和原视频反过来
      解决方案:scale=代表宽高压缩的比例,如果没有transpose=cclock,应该为snprintf(filter_desc, sizeof(filter_desc), "scale=%d:%d",dst_width,dst_height);由于有了transpose=cclock,则要将宽高参数调换

    项目地址

    https://github.com/nldzsz/ffmpeg-demo

    位于cppsrc目录下文件VideoScale.hpp/VideoScale.cpp

    项目下示例可运行于iOS/android/mac平台,工程分别位于demo-ios/demo-android/demo-mac三个目录下,可根据需要选择不同平台

    相关文章

      网友评论

        本文标题:视频压缩和翻转ffmpeg滤镜(二十二)

        本文链接:https://www.haomeiwen.com/subject/nnitfktx.html