滤波也不老是单一的输入,也存在对多个输入流进行滤波的需求,最多见的就是对视频添加可视水印,水印的组成一般为原视频以及做为水印的图片或者小动画,在ffmpeg中可使用overlay滤波器进行水印添加。html
对于多视频流输入的滤波器,ffmpeg提供了一个名为framesync的处理方案。framesync为滤波器分担了不一样线路的输入的帧同步任务,并为滤波器提供同步事后的帧,使得滤波器专一于滤波处理。缓存
因为各个视频流可能长短不一,可能起始或者结束时间也不一样,为了应对由此产生的各类需求,framesync为每一个输入流的起始以及结束都提供了3种可选的扩展方式函数
Mode | before(流开始前) | after(流结束后) |
EXT_STOP | 在这个流开始前的这段时间不能够进行滤波处理。若是有多个流都指定了before=EXT_STOP,那么以时间线最后的流为准。 | 在这个流结束后滤波处理必须中止。若是有多个流都指定了after=EXT_STOP,那么以时间线最前的流为准。 |
EXT_NULL | 其他的流能够在缺乏了该流的状况下执行滤波处理。 | 其他的流能够在缺乏了该流的状况下执行滤波处理。 |
EXT_INFINITY | 在这个流开始前的这段时间,提供这一个流的第一帧给滤波器进行处理。 | 在这个流结束后的这段时间,提供这一个流的最后一帧给滤波器进行处理。 |
在framesync所提供的同步服务中,滤波器能够为输入流设置同步等级,同步等级最高的输入流会被看成同步基准。动画
如上图所示,不一样的输入流可能有不一样的帧率,所以有必要对输入的流进行同步。上面的例子中,input stream 1的同步级别最高,所以以该流为同步基准,即每次获得input stream 1的帧时,能够进行滤波处理。滤波处理所提供的帧为各个流最近所得到的帧,在上面的例子中,当input stream 1得到序号为2的帧时,input stream 2刚刚所得到的帧序号为3,input stream 3刚刚所得到的帧序号为1,所以滤波时framesync所提供的帧分别为stream 1的二、stream 2的三、stream 3的1。3d
滤波器调用framesync须要执行以下代码:视频
typedef struct Context { FFFrameSync fs; //Context involves FFFrameSync } Context; static int process_frame(FFFrameSync *fs) { Context *s = fs->opaque; AVFrame *in1, *in2, *in3; int ret; //get frame before filtering if ((ret = ff_framesync_get_frame(&s->fs, 0, &in1, 0)) < 0 || (ret = ff_framesync_get_frame(&s->fs, 1, &in2, 0)) < 0 || (ret = ff_framesync_get_frame(&s->fs, 2, &in3, 0)) < 0) //filtering } //Before filtering, we can only get timebase in function config_output.
//See avfilter_config_links static int config_output(AVFilterLink *outlink) { FFFrameSyncIn *in; ret = ff_framesync_init(&s->fs, ctx, 3); //init framesync if (ret < 0) return ret; //set inputs parameter: timebase, sync level, before mode, after mode in = s->fs.in; in[0].time_base = srclink1->time_base; in[1].time_base = srclink2->time_base; in[2].time_base = srclink3->time_base; in[0].sync = 2; in[0].before = EXT_STOP; in[0].after = EXT_STOP; in[1].sync = 1; in[1].before = EXT_NULL; in[1].after = EXT_INFINITY; in[2].sync = 1; in[2].before = EXT_NULL; in[2].after = EXT_INFINITY; //save Context to fs.opaque which will be used on filtering s->fs.opaque = s; //filtering function s->fs.on_event = process_frame; return ff_framesync_configure(&s->fs); //framesync configure } static int activate(AVFilterContext *ctx) { RemapContext *s = ctx->priv; return ff_framesync_activate(&s->fs); //call filtering function if frame ready } static av_cold void uninit(AVFilterContext *ctx) { RemapContext *s = ctx->priv; ff_framesync_uninit(&s->fs); } static const AVFilterPad remap_inputs[] = { { .name = "source 1", .type = AVMEDIA_TYPE_VIDEO, .config_props = config_input, }, { .name = "source 2", .type = AVMEDIA_TYPE_VIDEO, }, { .name = "source 3", .type = AVMEDIA_TYPE_VIDEO, }, { NULL } }; static const AVFilterPad remap_outputs[] = { { .name = "default", .type = AVMEDIA_TYPE_VIDEO, .config_props = config_output, }, { NULL } };
能够发现使用framesync有以下要求:htm
framesync的同步实现主要集中在ff_framesync_activate所调用的framesync_advance函数当中。blog
static int framesync_advance(FFFrameSync *fs) { while (!(fs->frame_ready || fs->eof)) { ret = consume_from_fifos(fs); if (ret <= 0) return ret; } return 0; }
framesync_advance内是一个循环,退出该循环须要知足任意以下一个条件:图片
从consume_from_fifos开始分析,咱们将会对framesync的同步机制有详细的了解。ip
static int consume_from_fifos(FFFrameSync *fs) { AVFilterContext *ctx = fs->parent; AVFrame *frame = NULL; int64_t pts; unsigned i, nb_active, nb_miss; int ret, status; nb_active = nb_miss = 0; for (i = 0; i < fs->nb_in; i++) { if (fs->in[i].have_next || fs->in[i].state == STATE_EOF) continue; nb_active++; ret = ff_inlink_consume_frame(ctx->inputs[i], &frame); if (ret < 0) return ret; if (ret) { av_assert0(frame); framesync_inject_frame(fs, i, frame); } else { ret = ff_inlink_acknowledge_status(ctx->inputs[i], &status, &pts); if (ret > 0) { framesync_inject_status(fs, i, status, pts); } else if (!ret) { nb_miss++; } } } if (nb_miss) { if (nb_miss == nb_active && !ff_outlink_frame_wanted(ctx->outputs[0])) return FFERROR_NOT_READY; for (i = 0; i < fs->nb_in; i++) if (!fs->in[i].have_next && fs->in[i].state != STATE_EOF) ff_inlink_request_frame(ctx->inputs[i]); return 0; } return 1; }
在consume_from_fifos返回1表明目前已经从全部的输入流中得到了帧。
consume_from_fifos返回1的时候,全部输入流的帧缓存fs->in[i].frame_next都存储了一帧,该帧缓存标志fs->in[i].have_next的值都为1。而后进行下列同步处理:
static int framesync_advance(FFFrameSync *fs) { unsigned i; int64_t pts; int ret; while (!(fs->frame_ready || fs->eof)) { ret = consume_from_fifos(fs); if (ret <= 0) return ret; pts = INT64_MAX; for (i = 0; i < fs->nb_in; i++) //get the least pts frame if (fs->in[i].have_next && fs->in[i].pts_next < pts) pts = fs->in[i].pts_next; if (pts == INT64_MAX) { framesync_eof(fs); break; } for (i = 0; i < fs->nb_in; i++) { if (fs->in[i].pts_next == pts || (fs->in[i].before == EXT_INFINITY && fs->in[i].state == STATE_BOF)) { av_frame_free(&fs->in[i].frame); fs->in[i].frame = fs->in[i].frame_next; //move from frame_next to frame fs->in[i].pts = fs->in[i].pts_next; fs->in[i].frame_next = NULL; fs->in[i].pts_next = AV_NOPTS_VALUE; fs->in[i].have_next = 0; fs->in[i].state = fs->in[i].frame ? STATE_RUN : STATE_EOF; if (fs->in[i].sync == fs->sync_level && fs->in[i].frame)//the highest level frame fs->frame_ready = 1; if (fs->in[i].state == STATE_EOF && fs->in[i].after == EXT_STOP) framesync_eof(fs); } } if (fs->frame_ready) for (i = 0; i < fs->nb_in; i++) if ((fs->in[i].state == STATE_BOF && fs->in[i].before == EXT_STOP)) fs->frame_ready = 0; fs->pts = pts; } return 0; }
这里咱们把frame_next看成从上一滤波器实例中获取的帧缓存,frame看成接下来会用于进行滤波处理的帧缓存。
以咱们前面所展现的图片为例
每次都把frame_next中pts最小的一帧放入frame时,同时也代表在frame中新所放入的一帧永远是pts最大的一帧。当被放入到frame中的帧是属于最高同步等级的输入流的时候,能够执行滤波处理。若是咱们把这一帧的pts定义为同步pts,此时其他的输入流中的帧的pts尽管比同步pts小,不过也是各自输入流中最大的,这与咱们前面所说的同步处理是一致的。
framesync的实现总结来讲就是循环执行:
这种实现方式能保证全部的帧都是以pts从小到大由frame_next移入frame的,能防止帧被遗漏。