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

前言

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三个目录下,可根据需要选择不同平台

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342