命令行语法
ffmpeg中定义了很多filter,例如视频缩放(scale)、叠加(overlay)、截取(trim)、裁剪(crop)、旋转(rotate)等,ffmpeg支持的filter可以用下面的命令查看:
ffmpeg -filters
ffmpeg中某个filter的使用方法可用下面的命令查看:
#filter_name:filter名,例如sclae、overlay
ffmpeg -h filter=$filter_name
ffmpeg filter语法包含3个层次:filter->filterchain->filtergraph
filter语法
用一个字符串描述filter的组成,形式如下
[in_link_1]...[in_link_N]filter_name=parameters[out_link_1]...[out_link_N]
[in_link_N]、[out_link_N]:标识输入和输出的标签名,可以自定义,需要用"[]"括起来。1个filter可能有多个输入和输出,没有输入的filter称为source filter,没有输出的filter称为sink filter。对输入和输出打标签是可选的,打标签是为了方便连接其他filter。
parameters:初始化filter的参数,有以下几种形式:
#1. 使用":"字符分割的1个"key=value"列表,例如下面的命令将视频宽高缩小1倍
ffmpeg -i input.mp4 -vf scale=w=iw/2:h=ih/2 output.mp4
#2. 使用":"字符分割的value列表,在这种情况下key按照声明的顺序来接收value的值,例如scale filter的前两个选项分别是w和h,下面的命令将iw/2赋值给w,ih/2赋值给h
ffmpeg -i input.mp4 -vf scale=iw/2:ih/2 output.mp4
#3. 使用":"字符分割混合key和key=value列表,key必须在key=value之前,遵循第2种形式的约束规则,例如:
ffmpeg -i input.mp4 -vf scale=iw/2:h=ih/2 output.mp4
#4. 如果选项的值本身就是1个列表,则这种列表通常使用"|"分割,例如:
ffmpeg -i input.mp4 -vf "format=pix_fmts=yuv420p|yuv444p|yuv410p" output.mp4
filterchain语法
用1个字符串描述filterchain的组成,形式如下,filter之间以","隔开,前1个filter的输出连接后1个filter的输入。
filter1,filter2,...filterN-1,filterN
在下面的例子中,[tmp]crop=iw:ih/2:0:0,vfilp[flip]就是1个filterchain
#1. split filter将输入视频分离成两路完全相同的流main和tmp
#2. tmp流输入到crop filter中,crop filter将其下半部分裁剪掉,再输入到vflip filter中做垂直旋转,输出flip
#3. 将flip叠加在main的[0,H/2]位置处,最终输出的视频上半部分是原始的,下半部分是上半部分的倒影
ffmpeg -i input.mp4 -vf "split[main][tmp];[tmp]crop=iw:ih/2:0:0,vflip[flip];[main][flip]overlay=0:H/2" iutput.mp4
filtergraph语法
用1个字符串描述filtergraph的组成,形式如下,filterchain之间以";"隔开
filterchain1;filterchain2...filterchainN-1;filterchainN
例如split[main][tmp];[tmp]crop=iw:ih/2:0:0,vfilp[flip]就是1个filtergraph
关于","和";"的使用,可以这样理解:对同一路流做处理的filter用","隔开,对不同路流做处理的filter用";"隔开,例如[tmp]crop=iw:ih/2:0:0,vfilp[flip],crop和vflip都是对tmp做处理,所以用","隔开。
ffmpeg filter命令行有简单filter和复杂filter,简单filter只有1个输入流,使用-vf或-filter:v命令,例如:
ffmpeg -i input.mp4 -filter:v scale=iw/2:ih/2 output.mp4
ffmpeg -i input.mp4 -vf "split[main][tmp];[tmp]crop=iw:ih/2:0:0,vflip[flip];[main][flip]overlay=0:H/2" output.mp4
复杂filter有多个输入流,使用-filtercomplex或-lavfi命令,例如:
#[1:v]这个参数中1表示操作对象的编号,在本例中0是input.mp4,1是image1.png,2是image2.png,3是output.mp4。v表示操作的是对象里的视频信息。
#[1:v]scale=100:100[img1]表示调整image1.png的尺寸为100x100,输出img1
#[2:v]scale=200:200[img2]表示调整image2.png的尺寸为200x200,输出img2
#[0:v][img1]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2[bkg]表示将img1叠加到input.mp4的视频上,输出bkg。叠加位置为[(main_w-overlay_w)/2:(main_h-overlay_h)/2],其中main_w、main_h表示主视频input.mp4的宽和高,overlay_w、overlay_h表示水印img1的宽和高。
#[bkg][img2]overlay=0:0表示将img2叠加到bkg上,叠加位置为[0,0]。
#这个命令相当于给input.mp4添加了两个水印image1和image2
ffmpeg -i input.mp4 -i image1.png -i image2.png -filter_complex[1:v]scale=100:100[img1];[2:v]scale=200:200[img2];[0:v][img1]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2[bkg];[bkg][img2]overlay=0:0 -y output.mp4
#下面的命令实现了ffplay同时播放两个视频,可用于主观对比两个视频的质量
#1. color=s=2160*1920:c=black[base]:新建1块大小为2160x1920的黑色幕布,输出base
#2. [base][org1]overlay=0:0[out1]:将org1(input1.mp4)从位置[0,0]处叠加到base上,输出out1。
#3. [out1][org2]overlay=1080:0:将org2(input2.mp4)从位置[1080,0]处叠加到out1上
ffplay -f lavfi -i "color=s=2160*1920:c=black[base];movie=input1.mp4[org1];movie=input2.mp4[org2];[base][org1]overlay=0:0[out1];[out1][org2]overlay=1080:0"
API使用
ffmpeg filter graph有以下3种创建方式:
1. 用avfilter_graph_create_filter函数一个一个创建滤镜(AVFilterContext),然后用avfilter_link函数把各个滤镜的输入输出连接起来。这种方式比较灵活,但是非常繁琐。例如创建1个scale滤镜:
static int init_filter_v1(int width, int height) {
AVFilterGraph *filter_graph;
AVFilterContext *buffer_ctx;
AVFilterContext *scale_ctx;
AVFilterContext *buffersink_ctx;
filter_graph = avfilter_graph_alloc();
if (!filter_graph) {
printf("alloc filter graph failed");
return -1;
}
int ret = 0;
AVFilter *buffer = avfilter_get_by_name("buffer");
AVFilter *scale = avfilter_get_by_name("scale");
AVFilter *buffersink = avfilter_get_by_name("buffersink");
enum AVPixelFormat pix_fmts[] = {AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE};
AVBPrint args;
av_bprint_init(&args, 0, AV_BPRINT_SIZE_AUTOMATIC);
av_bprintf(&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,
ifmt_ctx->streams[video_index]->time_base.num, ifmt_ctx->streams[video_index]->time_base.den,
ifmt_ctx->streams[video_index]->sample_aspect_ratio.num, ifmt_ctx->streams[video_index]->sample_aspect_ratio.den);
printf("buffer args=%s\n", args.str);
ret = avfilter_graph_create_filter(&buffer_ctx, buffer, "in", args.str, NULL, filter_graph);
if (ret < 0) {
printf("create buffer ctx failed\n");
return ret;
}
av_bprint_init(&args, 0, AV_BPRINT_SIZE_AUTOMATIC);
av_bprintf(&args, "w=%d:h=%d", width, height);
printf("scale args=%s\n", args.str);
ret = avfilter_graph_create_filter(&scale_ctx, scale, "scale", args.str, NULL, filter_graph);
if (ret < 0) {
printf("create scale ctx failed\n");
return ret;
}
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out", NULL, NULL, filter_graph);
if(ret < 0) {
printf("create buffersink ctx failed\n");
return ret;
}
ret = av_opt_set_int_list(buffersink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
if (ret < 0) {
printf("Cannot set output pixel format\n");
return ret;
}
//连接buffer滤镜和scale滤镜
if ((ret = avfilter_link(buffer_ctx, 0, scale_ctx, 0)) < 0) {
printf("link buffer ctx to scale ctx failed\n");
return ret;
}
//连接scale滤镜和buffersink滤镜
if ((ret = avfilter_link(scale_ctx, 0, buffersink_ctx, 0)) < 0) {
printf("link scale ctx to buffersink ctx failed\n");
return ret;
}
//正式打开滤镜
if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0) {
printf("config filter graph failed\n");
return ret;
}
printf("create filter graph success\n");
return 0;
}