[ffmpeg] 多輸入濾波同步方式(framesync)

濾波也不老是單一的輸入,也存在對多個輸入流進行濾波的需求,最多見的就是對視頻添加可視水印,水印的組成一般爲原視頻以及做爲水印的圖片或者小動畫,在ffmpeg中可使用overlay濾波器進行水印添加。html

對於多視頻流輸入的濾波器,ffmpeg提供了一個名爲framesync的處理方案。framesync爲濾波器分擔了不一樣線路的輸入的幀同步任務,併爲濾波器提供同步事後的幀,使得濾波器專一於濾波處理。緩存

 

Extend Mode

因爲各個視頻流可能長短不一,可能起始或者結束時間也不一樣,爲了應對由此產生的各類需求,framesync爲每一個輸入流的起始以及結束都提供了3種可選的擴展方式函數

Mode before(流開始前) after(流結束後)
EXT_STOP 在這個流開始前的這段時間不能夠進行濾波處理。若是有多個流都指定了before=EXT_STOP,那麼以時間線最後的流爲準。 在這個流結束後濾波處理必須中止。若是有多個流都指定了after=EXT_STOP,那麼以時間線最前的流爲準。
EXT_NULL 其他的流能夠在缺乏了該流的狀況下執行濾波處理。 其他的流能夠在缺乏了該流的狀況下執行濾波處理。
EXT_INFINITY 在這個流開始前的這段時間,提供這一個流的第一幀給濾波器進行處理。 在這個流結束後的這段時間,提供這一個流的最後一幀給濾波器進行處理。

 

 

Sync

在framesync所提供的同步服務中,濾波器能夠爲輸入流設置同步等級,同步等級最高的輸入流會被看成同步基準。動畫

image

如上圖所示,不一樣的輸入流可能有不一樣的幀率,所以有必要對輸入的流進行同步。上面的例子中,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

 

 

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有以下要求:htm

  1. 在濾波器的參數結構體(Context)內包含FFFramesync結構體。
  2. 在進行濾波處理時,調用ff_framesync_get_frame來得到framesync同步後的幀。
  3. 在config_output時或以前調用ff_framesync_init來進行framesync初始化。
  4. 在config_output時設置各個輸入的time base,extend mode,sync level,並調用ff_framesync_configure進行配置。
  5. 在config_output時或以前設置fs->opaque=context(參數結構體),用於後續濾波處理。
  6. 在config_output時或以前設置用於回調的濾波處理函數fs->on_event=process_frame。
  7. 在activate時調用ff_framesync_activate。在該函數內部若是frame ready,就會執行回調函數。

 

 

framesync的同步實現

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內是一個循環,退出該循環須要知足任意以下一個條件:圖片

  • fs->frame_ready==1。表明接下來能夠執行濾波處理。
  • fs->eof==1。表明結束整個濾波處理。
  • ret = consume_from_fifos(fs) <= 0。返回值小於0表明出錯;返回值等於0表明目前沒法都從全部的輸入流中獲得幀。

 

從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表明目前已經從全部的輸入流中得到了幀。

  1. 若是已經從某個輸入得到了幀,則不須要再次去獲取。
  2. 若是某個輸入流還未得到幀,則會調用ff_inlink_comsume_frame嘗試從輸入link中獲取幀。
  3. 若是獲得了幀,就會調用framesync_inject_frame把從輸入流中得到的幀存放在fs->in[i].frame_next中,並用fs->in[i].have_next表示第i個輸入流已經得到了幀。
  4. 若是沒有得到幀,則調用ff_inlink_acknowledge_status檢查是否出錯或者EOF,是則代表該輸入流結束,不是則代表前面的濾波器實例沒法爲咱們提供幀。
  5. 因爲沒法得到咱們所須要的幀,所以要調用ff_inlink_request_frame向前面的濾波器實例發出請求。
  6. 只有當從全部的輸入流都獲得幀後,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看成接下來會用於進行濾波處理的幀緩存。

  1. 從所緩存的幀(frame_next)中提取pts最小的一幀。
  2. 存放到用於提供給濾波器的緩存中(frame = frame_next)。
  3. 把這一幀所在輸入流幀緩存設置爲空(frame_next = NULL)。
  4. 若是這一幀所在的輸入流是同步級別最高的流,代表此時在frame中該同步級別最高的流所輸入的幀的pts最大,符合咱們前面的同步描述,所以設置frame_ready = 1,代表接下來能夠進行濾波處理。
  5. 若是這一幀所在的輸入流不是同步級別最高的流,則須要繼續執行下一循環(執行consume_from_fifos)。

以咱們前面所展現的圖片爲例

image

每次都把frame_next中pts最小的一幀放入frame時,同時也代表在frame中新所放入的一幀永遠是pts最大的一幀。當被放入到frame中的幀是屬於最高同步等級的輸入流的時候,能夠執行濾波處理。若是咱們把這一幀的pts定義爲同步pts,此時其他的輸入流中的幀的pts儘管比同步pts小,不過也是各自輸入流中最大的,這與咱們前面所說的同步處理是一致的。

framesync的實現總結來講就是循環執行:

  1. 從輸入流中提取幀填補空缺的frame_next。
  2. 當全部輸入流的frame_next都被寫入幀後(即全部輸入流的have_next都爲1)consume_from_fifos纔會返回1,而後進行各個流之間的pts比較。
  3. 接下來把pts最小的幀從frame_next存入frame,如此一來該frame_next又會出現空缺。

這種實現方式能保證全部的幀都是以pts從小到大由frame_next移入frame的,能防止幀被遺漏。

相關文章
相關標籤/搜索