这篇文章对使用滤镜进行视频缩放,裁剪水印等做简单介绍。

一.滤镜

滤镜可以实现多路视频的叠加,水印,缩放,裁剪等功能,ffmpeg提供了丰富的滤镜,可以使用ffmpeg -filters来查看:
Filters:
T.. = Timeline support
.S. = Slice threading
..C = Command support
A = Audio input/output
V = Video input/output
N = Dynamic number and/or type of input/output
| = Source or sink filter
T.. adelay A->A Delay one or more audio channels.
… aecho A->A Add echoing to the audio.
… aeval A->A Filter audio signal according to a specified expression.
T.. afade A->A Fade in/out input audio.
… aformat A->A Convert the input audio to one of the specified formats.
… ainterleave N->A Temporally interleave audio inputs.
… allpass A->A Apply a two-pole all-pass filter.
… amerge N->A Merge two or more audio streams into a single multi-channel stream.
… amix N->A Audio mixing.
… anull A->A Pass the source unchanged to the output.
T.. apad A->A Pad audio with silence.
… aperms A->A Set permissions for the output audio frame.
… aphaser A->A Add a phasing effect to the audio.
… aresample A->A Resample audio data.
… aselect A->N Select audio frames to pass in output.
… asendcmd A->A Send commands to filters.
… asetnsamples A->A Set the number of samples for each output audio frames.
… asetpts A->A Set PTS for the output audio frame.
… asetrate A->A Change the sample rate without altering the data.
… asettb A->A Set timebase for the audio output link.
… ashowinfo A->A Show textual information for each audio frame.
… asplit A->N Pass on the audio input to N audio outputs.
…..
这里只是列出其中一小部分,可见ffmpeg提供了非常丰富的滤镜。

滤镜的几个基本概念

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的集合
经典示例:

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

使用libavfilter为视频添加滤镜

ffmpeg官网给出的filtering_video.c介绍了滤镜的用法,这个例子将一个视频文件解码成原始的一帧数据,然后再将这一帧数据使用滤镜惊醒缩放,缩小到78x24大小后,将像素中的点转换成字符,然后显示在终端中,效果如下:

这个程序结构非常清晰的介绍了滤镜的用法,但这种将视频中的像素转换为字符,然后显示在终端的做法并不能很直观的感受滤镜的作用,因此,我对这个程序做了简单的修改,将滤镜处理后的视频保存在文件中而不是显示在终端。下面为我修改过后,完整的程序,只有一个.c文件:


#define _XOPEN_SOURCE 600 /* for usleep */
#include <unistd.h>#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfiltergraph.h>
#include <libavfilter/avcodec.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavutil/opt.h>/*缩放滤镜,以下命令将视频缩小一般,iw,ih指的是输入的视频宽和高,
*此外也可以直接使用数字知名缩放的大小,比如:scale=200:100
*/
//const char *filter_descr = "scale=iw/2:ih/2";
/*裁剪滤镜,一下命令将视频的左上角的四分之一裁剪下来*/
//const char *filter_descr = "crop=iw/2:ih/2:0:0";
/*添加字符串水印*/
const char *filter_descr = "drawtext=fontfile=FreeSans.ttf:fontcolor=green:fontsize=30:text='Hello'";static AVFormatContext *fmt_ctx;
static AVCodecContext *dec_ctx;
AVFilterContext *buffersink_ctx;
AVFilterContext *buffersrc_ctx;
AVFilterGraph *filter_graph;
static int video_stream_index = -1;
static int64_t last_pts = AV_NOPTS_VALUE;static int open_input_file(const char *filename)
{int ret;AVCodec *dec;if ((ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL)) < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");return ret;}if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");return ret;}/* select the video stream */ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &dec, 0);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot find a video stream in the input file\n");return ret;}video_stream_index = ret;dec_ctx = fmt_ctx->streams[video_stream_index]->codec;av_opt_set_int(dec_ctx, "refcounted_frames", 1, 0);/* init the video decoder */if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot open video decoder\n");return ret;}return 0;
}static int init_filters(const char *filters_descr)
{char args[512];int ret = 0;AVFilter *buffersrc  = avfilter_get_by_name("buffer");AVFilter *buffersink = avfilter_get_by_name("buffersink");AVFilterInOut *outputs = avfilter_inout_alloc();AVFilterInOut *inputs  = avfilter_inout_alloc();AVRational time_base = fmt_ctx->streams[video_stream_index]->time_base;enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE };filter_graph = avfilter_graph_alloc();if (!outputs || !inputs || !filter_graph) {ret = AVERROR(ENOMEM);goto end;}/* buffer video source: the decoded frames from the decoder will be inserted here. */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);ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",args, NULL, filter_graph);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n");goto end;}/* buffer video sink: to terminate the filter chain. */ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",NULL, NULL, filter_graph);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n");goto end;}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");goto end;}/** 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       = NULL;/** 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       = NULL;if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr,&inputs, &outputs, NULL)) < 0)goto end;if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)goto end;end:avfilter_inout_free(&inputs);avfilter_inout_free(&outputs);return ret;
}FILE * file_fd;static void write_frame(const AVFrame *frame)
{if(frame->format==AV_PIX_FMT_YUV420P){printf("format is yuv420p\n");}else{printf("format is not yuv420p\n");}printf("frame widht=%d,frame height=%d\n",\frame->width,frame->height);fwrite(frame->data[0],1,frame->width*frame->height,file_fd);fwrite(frame->data[1],1,frame->width/2*frame->height/2,file_fd);fwrite(frame->data[2],1,frame->width/2*frame->height/2,file_fd);
}int main(int argc, char **argv)
{int ret;AVPacket packet;AVFrame *frame = av_frame_alloc();AVFrame *filt_frame = av_frame_alloc();int got_frame;file_fd = fopen("hello.yuv","wb+");if (!frame || !filt_frame) {perror("Could not allocate frame");exit(1);}if (argc != 2) {fprintf(stderr, "Usage: %s file\n", argv[0]);exit(1);}av_register_all();avfilter_register_all();if ((ret = open_input_file(argv[1])) < 0)goto end;if ((ret = init_filters(filter_descr)) < 0)goto end;/* read all packets */while (1) {if ((ret = av_read_frame(fmt_ctx, &packet)) < 0)break;if (packet.stream_index == video_stream_index) {got_frame = 0;ret = avcodec_decode_video2(dec_ctx, frame, &got_frame, &packet);if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Error decoding video\n");break;}if (got_frame) {frame->pts = av_frame_get_best_effort_timestamp(frame);/* push the decoded frame into the filtergraph */if (av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0) {av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n");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)goto end;write_frame(filt_frame);av_frame_unref(filt_frame);}av_frame_unref(frame);}}av_free_packet(&packet);}
end:avfilter_graph_free(&filter_graph);avcodec_close(dec_ctx);avformat_close_input(&fmt_ctx);av_frame_free(&frame);av_frame_free(&filt_frame);if (ret < 0 && ret != AVERROR_EOF) {fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));exit(1);}fclose(file_fd);exit(0);
}

文件编译参考前面的文章,便宜时会报错:No such filter: ‘drawtext’。这是因为我们没有是能这个滤镜。
解决办法:程序找不到 drawtext这个filter,一般是因为使用默认编译选项是该filter并未被编译进库里面,所以重新再编译ffmpeg,并且在执行”./configure ……”时加上“–enable-libfreetype”。这样就ok了。
完整的配置如下,我们之前已经使能了aac,h.264编解码器。

./configure --enable-libx264 --enable-gpl --enable-decoder=h264 --enable-encoder=libx264 --enable-shared --enable-static --disable-yasm -enable-nonfree  --enable-libfdk-aac --enable-shared --enable-libfreetype --prefix=tmp

配置结束后执行make && make install安装即可。
编译后,执行实例如下:
./out.bin rain.mp4
然后再当前文件下会生成hello.yuv的视频文件,这是原始数据格式的视频,可以使用ffplay来播放:
ffplay -s 1280x640 hello.yuv
改程序会打印出视频的长和宽,这里的1280x640请使用打印出来的长和宽来代替。
三.缩放,裁剪,添加字符串水印
滤镜的用法基本就是如上程序给出的步骤,我们可以通过指定不同的描述字符串来实现不同的滤镜功能。下面的字符串将视频缩放为200x100大小,着我在程序里的注释中已经给出了。
原视频:

缩小为一半:
const char *filter_descr = “scale=iw/2:ih/2”;
缩小后现象不明显,就不贴图了。
如下命令将视频裁剪出中间的1/4。
const char *filter_descr = “crop=iw/2:ih/2:iw/4:ih/4”;
效果如下:

下面字符串给视频添加hello的字符串水印:
const char *filter_descr = “drawtext=fontfile=FreeSans.ttf:fontcolor=green:fontsize=30:text=’Hello’”;
效果如下:

更复杂的滤镜的使用,期待和大家一起学习。

ffmpeg学习十二:滤镜(实现视频缩放,裁剪,水印等)相关推荐

  1. FFmpeg学习之二 (yuv视频渲染)

    FFmpeg学习之二 (yuv视频渲染) yuv简介 1.yuv是什么 2.yuv采集方式 3.yuv存储方式 4.yuv格式 yuv视频渲染 1. iOS YUV视频渲染 1.1 IOS利用open ...

  2. ffmpeg学习(13)音视频转码(2)使用filter

    ffmpeg学习(10)音视频文件muxer(1)封装格式转换 中介绍了媒体文件的封装格式转换,ffmpeg学习(11)音视频文件muxer(2)多输入混流 中介绍了音视频的混流,本文介绍基于ffmp ...

  3. OpenCV与图像处理学习十二——图像形状特征之HOG特征

    OpenCV与图像处理学习十二--图像形状特征之HOG特征 一.图像特征理解 1.1 颜色特征 1.2 纹理特征 1.3 形状特征 1.4 空间关系特征 二.形状特征描述 2.1 HOG特征 2.1. ...

  4. PyTorch框架学习十二——损失函数

    PyTorch框架学习十二--损失函数 一.损失函数的作用 二.18种常见损失函数简述 1.L1Loss(MAE) 2.MSELoss 3.SmoothL1Loss 4.交叉熵CrossEntropy ...

  5. (转)SpringMVC学习(十二)——SpringMVC中的拦截器

    http://blog.csdn.net/yerenyuan_pku/article/details/72567761 SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter, ...

  6. 【FastAPI 学习十二】定时任务篇 (移步博客园或个人网站 无广告,界面清爽整洁)

    声明 目前个人放弃CSDN平台,文章只发布于个人网站和博客园 博客园地址 [FastAPI 学习十二]定时任务篇

  7. ffmpeg学习(11)音视频文件muxer(2)多输入混流

    在 ffmpeg学习(3)编码.解码的流程介绍 和 ffmpeg学习(9)音视频文件demuxer中介绍了媒体文件的解封装.本文记录Ffmpeg封装格式另一种处理与与demuxer相反方式–视音频复用 ...

  8. ffmpeg学习日记604-指令-将视频格式转为H264格式

    ffmpeg学习日记604-指令-将视频格式转为H264格式 在第四篇中,想要解码视频,缺没有弄清楚怎样的一个数据流,现在又明晰了一点,所谓的h264编解码,并不是直接将视频格式,通过h264编解码为 ...

  9. C1认证学习十二(网络拓扑)

    C1认证学习十二(网络拓扑) 任务背景 互联网是一个广义的概念,它泛指是一切通过网路连接在一起的计算机的集合,所以,若果只是局部观察,那就不能再说互联网是一个互联的了,那么,如果说对于一个公司来说,具 ...

最新文章

  1. 算法提高课-搜索-DFS之搜索顺序-AcWing 1117. 单词接龙:dfs
  2. 树莓派小车(远程控制、PWM变速、超声波自动避障)
  3. LeetCode-46. Permutations
  4. 2019.5.18-5.19 ACM-ICPC 全国邀请赛(西安)赛后总结
  5. 联想拯救者电竞手机Pro透明版马上就到:一眼就能看到“芯”
  6. k近邻法的实现(kd树)-相关问题梳理
  7. json 转对象函数_JSON_QUERY()函数从JSON数据提取对象
  8. 计算机如何建筑材料结合所学知识,《技术与设计2》第三、四单元综合测试卷...
  9. 【渝粤教育】国家开放大学2018年春季 0408-21T管理学基础 参考试题
  10. Rust : 求出一个字符串数组中最长的公共连续子序列
  11. Android端公司通讯录开发与实现(一)
  12. 破解点评网字体反爬,深入挖掘系统背后的原理
  13. matlab电磁场,基于matlab的电磁场分析.pdf
  14. 微信小程序 图片缓存
  15. vue2.x 如何更换网页logo
  16. 网站seo优化到底该怎么做?
  17. 阿里妈妈返利比率的商品搜索API接口
  18. 累死你的不是工作是工作方式 好的团队会教你如何工作
  19. Python pip 安装与使用
  20. 7z压缩软件dos命令

热门文章

  1. mysqladmin 管理命令详细应用
  2. 转 大数据量下载解决方案
  3. IPv6 NDP邻居发现协议 1
  4. 一款炫酷牛逼叼炸天的接口测试神器:【postwoman】(postman女票) 【Apifox】
  5. Android的布局管理--表格布局
  6. 销售人员面对陌生客户时的谈话技巧
  7. 一个嵌入式牛人学习经历
  8. HTML学生个人网站作业设计:动漫网站设计——斗破苍穹动漫(6页) HTML+CSS+JavaScript 简单DIV布局个人介绍网页模板代码 DW学生个人网站制作成品下载
  9. ObjectUtils.isEmpty() 和 null 区别
  10. java indexeddb_HTML5之IndexedDB使用详解