濾波也不老是單一的輸入,也存在對多個輸入流進行濾波的需求,最多見的就是對視頻添加可視水印,水印的組成一般爲原視頻以及做爲水印的圖片或者小動畫,在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的,能防止幀被遺漏。