濾波也不總是單一的輸入,也存在對多個輸入流進行濾波的需求,最常見的就是對視頻添加可視水印,水印的組成通常為原視頻以及作為水印的圖片或者小動畫,在ffmpeg中可以使用overlay濾波器進行水印添加。
對於多視頻流輸入的濾波器,ffmpeg提供了一個名為framesync的處理方案。framesync為濾波器分擔了不同線路的輸入的幀同步任務,並為濾波器提供同步過后的幀,使得濾波器專注於濾波處理。
Extend Mode
由於各個視頻流可能長短不一,可能起始或者結束時間也不同,為了應對由此產生的各種需求,framesync為每個輸入流的起始以及結束都提供了3種可選的擴展方式
| Mode | before(流開始前) | after(流結束后) |
| EXT_STOP | 在這個流開始前的這段時間不可以進行濾波處理。如果有多個流都指定了before=EXT_STOP,那么以時間線最后的流為准。 | 在這個流結束后濾波處理必須停止。如果有多個流都指定了after=EXT_STOP,那么以時間線最前的流為准。 |
| EXT_NULL | 其余的流可以在缺少了該流的情況下執行濾波處理。 | 其余的流可以在缺少了該流的情況下執行濾波處理。 |
| EXT_INFINITY | 在這個流開始前的這段時間,提供這一個流的第一幀給濾波器進行處理。 | 在這個流結束后的這段時間,提供這一個流的最后一幀給濾波器進行處理。 |
Sync
在framesync所提供的同步服務中,濾波器可以為輸入流設置同步等級,同步等級最高的輸入流會被當作同步基准。
如上圖所示,不同的輸入流可能有不同的幀率,因此有必要對輸入的流進行同步。上面的例子中,input stream 1的同步級別最高,因此以該流為同步基准,即每次得到input stream 1的幀時,可以進行濾波處理。濾波處理所提供的幀為各個流最近所獲得的幀,在上面的例子中,當input stream 1獲得序號為2的幀時,input stream 2剛剛所獲得的幀序號為3,input stream 3剛剛所獲得的幀序號為1,因此濾波時framesync所提供的幀分別為stream 1的2、stream 2的3、stream 3的1。
Example
濾波器調用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有如下要求:
- 在濾波器的參數結構體(Context)內包含FFFramesync結構體。
- 在進行濾波處理時,調用ff_framesync_get_frame來獲得framesync同步后的幀。
- 在config_output時或之前調用ff_framesync_init來進行framesync初始化。
- 在config_output時設置各個輸入的time base,extend mode,sync level,並調用ff_framesync_configure進行配置。
- 在config_output時或之前設置fs->opaque=context(參數結構體),用於后續濾波處理。
- 在config_output時或之前設置用於回調的濾波處理函數fs->on_event=process_frame。
- 在activate時調用ff_framesync_activate。在該函數內部如果frame ready,就會執行回調函數。
framesync的同步實現
framesync的同步實現主要集中在ff_framesync_activate所調用的framesync_advance函數當中。
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內是一個循環,退出該循環需要滿足任意如下一個條件:
- fs->frame_ready==1。代表接下來可以執行濾波處理。
- fs->eof==1。代表結束整個濾波處理。
- ret = consume_from_fifos(fs) <= 0。返回值小於0代表出錯;返回值等於0代表目前無法都從所有的輸入流中得到幀。
從consume_from_fifos開始分析,我們將會對framesync的同步機制有詳細的了解。
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代表目前已經從所有的輸入流中獲得了幀。
- 如果已經從某個輸入獲得了幀,則不需要再次去獲取。
- 如果某個輸入流還未獲得幀,則會調用ff_inlink_comsume_frame嘗試從輸入link中獲取幀。
- 如果得到了幀,就會調用framesync_inject_frame把從輸入流中獲得的幀存放在fs->in[i].frame_next中,並用fs->in[i].have_next表示第i個輸入流已經獲得了幀。
- 如果沒有獲得幀,則調用ff_inlink_acknowledge_status檢查是否出錯或者EOF,是則表明該輸入流結束,不是則表明前面的濾波器實例無法為我們提供幀。
- 由於無法獲得我們所需要的幀,因此要調用ff_inlink_request_frame向前面的濾波器實例發出請求。
- 只有當從所有的輸入流都得到幀后,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_next)。
- 把這一幀所在輸入流幀緩存設置為空(frame_next = NULL)。
- 如果這一幀所在的輸入流是同步級別最高的流,表明此時在frame中該同步級別最高的流所輸入的幀的pts最大,符合我們前面的同步描述,因此設置frame_ready = 1,表明接下來可以進行濾波處理。
- 如果這一幀所在的輸入流不是同步級別最高的流,則需要繼續執行下一循環(執行consume_from_fifos)。
以我們前面所展示的圖片為例
每次都把frame_next中pts最小的一幀放入frame時,同時也表明在frame中新所放入的一幀永遠是pts最大的一幀。當被放入到frame中的幀是屬於最高同步等級的輸入流的時候,可以執行濾波處理。如果我們把這一幀的pts定義為同步pts,此時其余的輸入流中的幀的pts盡管比同步pts小,不過也是各自輸入流中最大的,這與我們前面所說的同步處理是一致的。
framesync的實現總結來說就是循環執行:
- 從輸入流中提取幀填補空缺的frame_next。
- 當所有輸入流的frame_next都被寫入幀后(即所有輸入流的have_next都為1)consume_from_fifos才會返回1,然后進行各個流之間的pts比較。
- 接下來把pts最小的幀從frame_next存入frame,如此一來該frame_next又會出現空缺。
這種實現方式能保證所有的幀都是以pts從小到大由frame_next移入frame的,能防止幀被遺漏。


