本文包含以下内容

1、AVFilter的基本介绍

2、如何利用ffmpeg命令行工具实现各种视频滤镜

3、如何利用libavfilter编程实现在摄像头直播流中加入各类不同滤镜的功能

具有较强的综合性

AVFilter的基本介绍

AVFilter的功能十分强大,可以实现对多媒体数据的各种处理,包括时间线编辑、视音频特效滤镜的添加或信号处理,还可以实现多路媒体流的合并或叠加,其丰富程度令人叹为观止。这里主要以视频滤镜为例进行介绍。使用AVFilter可以为单路视频添加单个或多个滤镜,也可以为多路视频分别添加不同的滤镜并且在最后将多路视频合并为一路视频,AVFilter为实现这些功能定义了以下几个概念:

Filter:代表单个filter
FilterPad:代表一个filter的输入或输出端口,每个filter都可以有多个输入和多个输出,只有输出pad的filter称为source,只有输入pad的filter称为sink
FilterLink:若一个filter的输出pad和另一个filter的输入pad名字相同,即认为两个filter之间建立了link

FilterChain:代表一串相互连接的filters,除了source和sink外,要求每个filter的输入输出pad都有对应的输出和输入pad

FilterGraph:FilterChain的集合

基本和DirectShow类似,也与视频后期调色软件中的节点等概念类似。具体来看,以下面的命令为例

[cpp] view plain copy
  1. [in]split[main][tmp];[tmp]crop=iw:ih/2,vflip[flip];[main][flip]overlay=0:H/2[out]

在该命令中,输入流[in]首先被分[split]为两个流[main]和[tmp],然后[tmp]流经过了剪切[crop]和翻转[vflip]两个滤镜后变为[flip],这时我们将[flip]叠加[overlay]到最开始的[main]上形成最后的输出流[out],最后呈现出的是镜像的效果。下图清晰地表示了以上过程

我们可以认为图中每一个节点就是一个Filter,每一个方括号所代表的就是FilterPad,可以看到split的输出pad中有一个叫tmp的,而crop的输入pad中也有一个tmp,由此在二者之间建立了link,当然input和output代表的就是source和sink,此外,图中有三条FilterChain,第一条由input和split组成,第二条由crop和vflip组成,第三条由overlay和output组成,整张图即是一个拥有三个FilterChain的FilterGraph。

上面的图是人工画出来的,也可以在代码中调用avfilter_graph_dump函数自动将FilterGraph画出来,如下

可以看到,多出来了一个scale滤镜,这是由ffmpeg自动添加的用于格式转换的滤镜。

在FFmpeg命令行工具中使用AVFilter

在命令行中使用AVFilter需要遵循专门的语法,简单来说,就是每个Filter之间以逗号分隔,每个Filter自己的属性之间以冒号分隔,属性和Filter以等号相连,多个Filter组成一个FilterChain,每个FilterChain之间以分号相隔。AVFilter在命令行工具中对应的是-vf或-af或-filter_complex,前两个分别对应于单路输入的视频滤镜和音频滤镜,最后的filter_complex则对应于有多路输入的情况。除了在FFMpeg命令行工具中使用外,在FFplay中同样也可以使用AVFilter。其他一些关于单双引号、转义符号等更详细的语法参考Filter Documentation

下面举几个例子

1、叠加水印

[cpp] view plain copy
  1. ffmpeg -i test.flv -vf movie=test.jpg[wm];[in][wm]overlay=5:5[out] out.flv

将test.jpg作为水印叠加到test.flv的坐标为(5,5)的位置上,效果如下

2、镜像

[cpp] view plain copy
  1. ffmpeg -i test.flv -vf crop=iw/2:ih:0:0,split[left][tmp];[tmp]hflip[right];[left]pad=iw*2[a];[a][right]overlay=w out.flv

输入[in]和输出[out]可以省略不写,pad用于填充画面,效果如下

3、调整曲线

[cpp] view plain copy
  1. ffmpeg -i test.flv -vf curves=vintage out.flv

类似Photoshop里面的曲线调整,这里的vintage是ffmpeg自带的预设,实现复古画风,还可以直接加载其他的Photoshop预设文件并在其基础上加以调整,如下

[cpp] view plain copy
  1. ffmpeg -i test.flv -vf curves=psfile='test.acv':green='0.45/0.53' out.flv

其中的acv预设文件实现的是加强对比度,再次基础上调整绿色的显示效果,以上两个命令的最终效果如下

4、多路输入拼接

[cpp] view plain copy
  1. ffmpeg -i test1.mp4 -i test2.mp4 -i test3.mp4 -i test4.mp4 -filter_complex "[0:v]pad=iw*2:ih*2[a];[a][1:v]overlay=w[b];[b][2:v]overlay=0:h[c];[c][3:v]overlay=w:h" out.mp4

正如前面所说的,当有多个输入时,需要使用filter_complex,效果如下

通过以上几个例子,基本可以明白在命令行中使用AVFilter时需要遵循的语法。

使用libavfilter编程为直播流添加滤镜

要使用libavfilter,首先要注册相关组件

[cpp] view plain copy
  1. avfilter_register_all();

首先需要构造出一个完整可用的FilterGraph,需要用到输入流的解码参数,参见上一篇文章,如下

[cpp] view plain copy
  1. AVFilterContext *buffersink_ctx;//看名字好像AVFilterContext是什么很厉害的东西,但其实只要认为它是AVFilter的一个实例就OK了
  2. AVFilterContext *buffersrc_ctx;
  3. AVFilterGraph *filter_graph;
  4. AVFilter *buffersrc=avfilter_get_by_name("buffer");//Filter的具体定义,只要是libavfilter中已注册的filter,就可以直接通过查询filter名字的方法获得其具体定义,所谓定义即filter的名称、功能描述、输入输出pad、相关回调函数等
  5. AVFilter *buffersink=avfilter_get_by_name("buffersink");
  6. AVFilterInOut *outputs = avfilter_inout_alloc();//AVFilterInOut对应buffer和buffersink这两个首尾端的filter的输入输出
  7. AVFilterInOut *inputs = avfilter_inout_alloc();
  8. filter_graph = avfilter_graph_alloc();
  9. /* buffer video source: the decoded frames from the decoder will be inserted here. */
  10. snprintf(args, sizeof(args),
  11. "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
  12. ifmt_ctx->streams[0]->codec->width, ifmt_ctx->streams[0]->codec->height, ifmt_ctx->streams[0]->codec->pix_fmt,
  13. ifmt_ctx->streams[0]->time_base.num, ifmt_ctx->streams[0]->time_base.den,
  14. ifmt_ctx->streams[0]->codec->sample_aspect_ratio.num, ifmt_ctx->streams[0]->codec->sample_aspect_ratio.den);
  15. ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
  16. args, NULL, filter_graph);//根据指定的Filter,这里就是buffer,构造对应的初始化参数args,二者结合即可创建Filter的示例,并放入filter_graph中
  17. if (ret < 0) {
  18. printf("Cannot create buffer source\n");
  19. return ret;
  20. }
  21. /* buffer video sink: to terminate the filter chain. */
  22. ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
  23. NULL, NULL, filter_graph);
  24. if (ret < 0) {
  25. printf("Cannot create buffer sink\n");
  26. return ret;
  27. }
  28. /* Endpoints for the filter graph. */
  29. outputs->name = av_strdup("in");//对应buffer这个filter的output
  30. outputs->filter_ctx = buffersrc_ctx;
  31. outputs->pad_idx = 0;
  32. outputs->next = NULL;
  33. inputs->name = av_strdup("out");//对应buffersink这个filter的input
  34. inputs->filter_ctx = buffersink_ctx;
  35. inputs->pad_idx = 0;
  36. inputs->next = NULL;
  37. if ((ret = avfilter_graph_parse_ptr(filter_graph, filter_descr,
  38. &inputs, &outputs, NULL)) < 0)//filter_descr是一个filter命令,例如"overlay=iw:ih",该函数可以解析这个命令,然后自动完成FilterGraph中各个Filter之间的联接
  39. return ret;
  40. if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)//检查当前所构造的FilterGraph的完整性与可用性
  41. return ret;
  42. avfilter_inout_free(&inputs);
  43. avfilter_inout_free(&outputs);

上面介绍的是FilterGraph的构造方法之一,即根据filter命令使用avfilter_graph_parse_ptr自动进行构造,当然也可以由我们自己将各个filter一一联接起来,如下,这里假设我们已经有了buffersrc_ctx、 buffersink_ctx和一个filter_ctx

[cpp] view plain copy
  1. // connect inputs and outputs
  2. if (err >= 0) err = avfilter_link(buffersrc_ctx, 0, filter_ctx, 0);
  3. if (err >= 0) err = avfilter_link(filter_ctx, 0, buffersink_ctx, 0);
  4. if (err < 0) {
  5. av_log(NULL, AV_LOG_ERROR, "error connecting filters\n");
  6. return err;
  7. }
  8. err = avfilter_graph_config(filter_graph, NULL);
  9. if (err < 0) {
  10. av_log(NULL, AV_LOG_ERROR, "error configuring the filter graph\n");
  11. return err;
  12. }
  13. return 0;

不过在filter较多的情况下,还是直接使用avfilter_graph_parse_ptr比较方便

在构造好FilterGraph之后,就可以开始使用了,使用流程也很简单,先将一个AVFrame帧推入FIlterGraph中,在将处理后的AVFrame从FilterGraph中拉出来即可,这里以上一篇文章的编解码核心模块的代码为例看一下实现过程。可以看到,是将解码得到的pFrame推入filter_graph,将处理后的数据写入picref中,他也是一个AVFrame。需要注意的是,这里依然要将picref转换为YUV420的帧之后再进行编码,一方面是因为我们这里用的是摄像头数据,是RGB格式的,另一方面,诸如curves这样的filter是在RGB空间进行处理的,最后得到的也是对应像素格式的帧,所以需要进行转换。其他部分基本和原来一样。

[cpp] view plain copy
  1. //start decode and encode
  2. int64_t start_time=av_gettime();
  3. while (av_read_frame(ifmt_ctx, dec_pkt) >= 0){
  4. if (exit_thread)
  5. break;
  6. av_log(NULL, AV_LOG_DEBUG, "Going to reencode the frame\n");
  7. pframe = av_frame_alloc();
  8. if (!pframe) {
  9. ret = AVERROR(ENOMEM);
  10. return -1;
  11. }
  12. //av_packet_rescale_ts(dec_pkt, ifmt_ctx->streams[dec_pkt->stream_index]->time_base,
  13. //  ifmt_ctx->streams[dec_pkt->stream_index]->codec->time_base);
  14. ret = avcodec_decode_video2(ifmt_ctx->streams[dec_pkt->stream_index]->codec, pframe,
  15. &dec_got_frame, dec_pkt);
  16. if (ret < 0) {
  17. av_frame_free(&pframe);
  18. av_log(NULL, AV_LOG_ERROR, "Decoding failed\n");
  19. break;
  20. }
  21. if (dec_got_frame){
  22. #if USEFILTER
  23. pframe->pts = av_frame_get_best_effort_timestamp(pframe);
  24. if (filter_change)
  25. apply_filters(ifmt_ctx);
  26. filter_change = 0;
  27. /* push the decoded frame into the filtergraph */
  28. if (av_buffersrc_add_frame(buffersrc_ctx, pframe) < 0) {
  29. printf("Error while feeding the filtergraph\n");
  30. break;
  31. }
  32. picref = av_frame_alloc();
  33. /* pull filtered pictures from the filtergraph */
  34. while (1) {
  35. ret = av_buffersink_get_frame_flags(buffersink_ctx, picref, 0);
  36. if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
  37. break;
  38. if (ret < 0)
  39. return ret;
  40. if (picref) {
  41. img_convert_ctx = sws_getContext(picref->width, picref->height, (AVPixelFormat)picref->format, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
  42. sws_scale(img_convert_ctx, (const uint8_t* const*)picref->data, picref->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
  43. sws_freeContext(img_convert_ctx);
  44. pFrameYUV->width = picref->width;
  45. pFrameYUV->height = picref->height;
  46. pFrameYUV->format = PIX_FMT_YUV420P;
  47. #else
  48. sws_scale(img_convert_ctx, (const uint8_t* const*)pframe->data, pframe->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
  49. pFrameYUV->width = pframe->width;
  50. pFrameYUV->height = pframe->height;
  51. pFrameYUV->format = PIX_FMT_YUV420P;
  52. #endif
  53. enc_pkt.data = NULL;
  54. enc_pkt.size = 0;
  55. av_init_packet(&enc_pkt);
  56. ret = avcodec_encode_video2(pCodecCtx, &enc_pkt, pFrameYUV, &enc_got_frame);
  57. av_frame_free(&pframe);
  58. if (enc_got_frame == 1){
  59. //printf("Succeed to encode frame: %5d\tsize:%5d\n", framecnt, enc_pkt.size);
  60. framecnt++;
  61. enc_pkt.stream_index = video_st->index;
  62. //Write PTS
  63. AVRational time_base = ofmt_ctx->streams[videoindex]->time_base;//{ 1, 1000 };
  64. AVRational r_framerate1 = ifmt_ctx->streams[videoindex]->r_frame_rate;// { 50, 2 };
  65. AVRational time_base_q = { 1, AV_TIME_BASE };
  66. //Duration between 2 frames (us)
  67. int64_t calc_duration = (double)(AV_TIME_BASE)*(1 / av_q2d(r_framerate1));  //内部时间戳
  68. //Parameters
  69. //enc_pkt.pts = (double)(framecnt*calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base));
  70. enc_pkt.pts = av_rescale_q(framecnt*calc_duration, time_base_q, time_base);
  71. enc_pkt.dts = enc_pkt.pts;
  72. enc_pkt.duration = av_rescale_q(calc_duration, time_base_q, time_base); //(double)(calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base));
  73. enc_pkt.pos = -1;
  74. //Delay
  75. int64_t pts_time = av_rescale_q(enc_pkt.dts, time_base, time_base_q);
  76. int64_t now_time = av_gettime() - start_time;
  77. if (pts_time > now_time)
  78. av_usleep(pts_time - now_time);
  79. ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt);
  80. av_free_packet(&enc_pkt);
  81. }
  82. #if USEFILTER
  83. av_frame_unref(picref);
  84. }
  85. }
  86. #endif
  87. }
  88. else {
  89. av_frame_free(&pframe);
  90. }
  91. av_free_packet(dec_pkt);
  92. }

这里我们还可以实现一个按下不同的数字键就添加不同的滤镜的功能,如下

可以看到,首先写好一些要用的filter命令,然后在多线程的回调函数里监视用户的按键情况,根据不同的按键使用对应的filter命令初始化filter_graph,这里“null”也是一个filter命令,用于将输入视频原样输出

[cpp] view plain copy
  1. #if USEFILTER
  2. int filter_change = 1;
  3. const char *filter_descr="null";
  4. const char *filter_mirror = "crop=iw/2:ih:0:0,split[left][tmp];[tmp]hflip[right]; \
  5. [left]pad=iw*2[a];[a][right]overlay=w";
  6. const char *filter_watermark = "movie=test.jpg[wm];[in][wm]overlay=5:5[out]";
  7. const char *filter_negate = "negate[out]";
  8. const char *filter_edge = "edgedetect[out]";
  9. const char *filter_split4 = "scale=iw/2:ih/2[in_tmp];[in_tmp]split=4[in_1][in_2][in_3][in_4];[in_1]pad=iw*2:ih*2[a];[a][in_2]overlay=w[b];[b][in_3]overlay=0:h[d];[d][in_4]overlay=w:h[out]";
  10. const char *filter_vintage = "curves=vintage";
  11. typedef enum{
  12. FILTER_NULL =48,
  13. FILTER_MIRROR ,
  14. FILTER_WATERMATK,
  15. FILTER_NEGATE,
  16. FILTER_EDGE,
  17. FILTER_SPLIT4,
  18. FILTER_VINTAGE
  19. }FILTERS;
  20. AVFilterContext *buffersink_ctx;
  21. AVFilterContext *buffersrc_ctx;
  22. AVFilterGraph *filter_graph;
  23. AVFilter *buffersrc;
  24. AVFilter *buffersink;
  25. AVFrame* picref;
  26. #endif
  27. DWORD WINAPI MyThreadFunction(LPVOID lpParam)
  28. {
  29. #if USEFILTER
  30. int ch = getchar();
  31. while (ch != '\n')
  32. {
  33. switch (ch){
  34. case FILTER_NULL:
  35. {
  36. printf("\nnow using null filter\nPress other numbers for other filters:");
  37. filter_change = 1;
  38. filter_descr = "null";
  39. getchar();
  40. ch = getchar();
  41. break;
  42. }
  43. case FILTER_MIRROR:
  44. {
  45. printf("\nnow using mirror filter\nPress other numbers for other filters:");
  46. filter_change = 1;
  47. filter_descr = filter_mirror;
  48. getchar();
  49. ch = getchar();
  50. break;
  51. }
  52. case FILTER_WATERMATK:
  53. {
  54. printf("\nnow using watermark filter\nPress other numbers for other filters:");
  55. filter_change = 1;
  56. filter_descr = filter_watermark;
  57. getchar();
  58. ch = getchar();
  59. break;
  60. }
  61. case FILTER_NEGATE:
  62. {
  63. printf("\nnow using negate filter\nPress other numbers for other filters:");
  64. filter_change = 1;
  65. filter_descr = filter_negate;
  66. getchar();
  67. ch = getchar();
  68. break;
  69. }
  70. case FILTER_EDGE:
  71. {
  72. printf("\nnow using edge filter\nPress other numbers for other filters:");
  73. filter_change = 1;
  74. filter_descr = filter_edge;
  75. getchar();
  76. ch = getchar();
  77. break;
  78. }
  79. case FILTER_SPLIT4:
  80. {
  81. printf("\nnow using split4 filter\nPress other numbers for other filters:");
  82. filter_change = 1;
  83. filter_descr = filter_split4;
  84. getchar();
  85. ch = getchar();
  86. break;
  87. }
  88. case FILTER_VINTAGE:
  89. {
  90. printf("\nnow using vintage filter\nPress other numbers for other filters:");
  91. filter_change = 1;
  92. filter_descr = filter_vintage;
  93. getchar();
  94. ch = getchar();
  95. break;
  96. }
  97. default:
  98. {
  99. getchar();
  100. ch = getchar();
  101. break;
  102. }
  103. }
  104. #else
  105. while ((getchar())!='\n')
  106. {
  107. ;
  108. #endif
  109. }
  110. exit_thread = 1;
  111. return 0;
  112. }

ffmpeg为视频添加特效相关推荐

  1. 如何给视频添加特效字幕?分享一个简单好用的办法

    如何给视频添加特效字幕?现如今短视频剪辑已经十分普遍,我们在日常生活中也是经常需要接触到.说到短视频剪辑,可能大家心中多少都有点好奇.其实剪辑短视频并没有你们想象中的那么复杂,我们只需要准备好相应的素 ...

  2. 视频批量剪辑:如何给视频添加特效,比如:色彩变幻效果特效,怎么制作?

    视频太多,如何给视频添加特效,比如:色彩变幻效果特效,要如何制作?可以批量操作吗?可以,今天就让小编来教教大家如何去批量给视频添加色彩变幻效果特效功能 首先第一步,我们要进入视频剪辑高手并在上方三个板 ...

  3. ffmpeg给视频添加时间水印

    ffmpeg给视频添加时间水印 通过 drawtext 滤镜模块给视频添加时间水印 给视频添加时间水印 用来做片源调试,非常方便的查找和定位处理的哪一帧视频片源: 1. 添加本地时间水印 ffmpeg ...

  4. ffmpeg给视频添加文本

    1. 给视频添加文本 给视频添加文本有两种通常做法,一种是使用字幕实现,一种是使用overlay滤镜实现.但下面是通过使用drawtext滤镜,更高级的做法实现. 格式:ffmpeg  -i  inp ...

  5. java操作ffmpeg为视频添加背景音乐

    最近学习仿抖音微信小程序遇到一个坑,视频中使用以下语句为视频添加背景音乐 ffmpeg.exe -i input.mp4 -i 音乐.mp3 -t 7 -y 新视频.mp4 ,然而我怎么尝试都不行,上 ...

  6. 如何给视频添加特效?快速制作特效视频

    如何给视频添加特效?现如今几乎我们人人每天都在与短视频打交道,有些人在日常的生活中也会剪辑一些短视频.其实剪辑短视频并没有你想象中的那么困难.只是需要找到一款合适的软件就可以很快完成.在短视频剪辑中就 ...

  7. Windows下使用ffmpeg为视频添加字幕

    字幕分以下几种形式: 第一种是外挂字幕(软字幕),视频文件和字幕文件分离.当播放某视频文件时,会自动载入相同文件夹下同名的字幕文件,当然也可以用播放器(如:VLC media player)手动载入字 ...

  8. Java操作ffmpeg为视频添加音乐

    最近学习仿抖音微信小程序遇到一个坑,视频中使用以下语句为视频添加背景音乐 ffmpeg.exe -i input.mp4 -i 音乐.mp3 -t 7 -y 新视频.mp4 发现并不管用. 自己将其改 ...

  9. FFmpeg给视频添加图片,文字(vb.net,类库——11)

    给视频添加文字,可以使用添加文字的方法,但是想添加中文字,那只能使用微软雅黑了 那我们想:文字可以被印到图片上,然后图片可以被轻而易举的添加到视频中 借助GDI+完成这一转换 Public Funct ...

最新文章

  1. samba安装部署及简单用法
  2. 调用android系统自带功能
  3. 2 自动递增_有石CAD自动下单,1天工作量1小时完成
  4. 线程的属性 —— 分离的状态(detached state)、栈地址(stack address)、栈大小(stack size)
  5. 小白初涉,先试试水。涉及Python,C语言基础,机器学习等
  6. eclipse 开发环境配置
  7. mysql 事务 select_mysql 多个select需要放入一个事务吗?
  8. ieee期刊_机器人领域主要国际会议与期刊列表
  9. The service command supports only basic LSB actions (start, stop, restart, try-restart, reload, forc
  10. 曲线拟合的线性最小二乘法
  11. cent os7 安装nginx1.16.1
  12. 国际版(英文版)Skype使用国内卡打电话(转)
  13. 【MATLAB】求极限
  14. 诡辩:认知与智商税!外附送签名版国庆福利
  15. python练习题:程序员问卷调查
  16. mahout实现协同过滤推荐算法
  17. Android ImageView: resolveUri failed on bad bitmap uri
  18. x64dbg 调试 EXCEPTION_ACCESS_VIOLATION C0000005
  19. 学习笔记|PSO粒子群算法(1)
  20. 【数据库】实验4:触发器实验

热门文章

  1. 计算机网络——计算机网络常见面试题总结
  2. 【数据库】select、from、where、group by、having、order by、limit的组合用法
  3. SSM框架将数据库数据导出为Excel文件
  4. CSMACA 与 CSMA/CD 区别
  5. 如何恢复vscode的默认配置_史上最全vscode配置使用教程
  6. 所生成项目的处理器架构“MSIL”与引用“ ”的处理器架构“AMD64”不匹配。
  7. rtx2060什么水平_RTX2060值得买吗
  8. AI人工智能外呼机器人
  9. __call__()
  10. (CVPR-2021)RepVGG:极简架构,SOTA性能,让VGG式模型再次伟大