天天看點

FFmpeg濾鏡代碼級分析

前一篇文章《為FFmpeg添加自定義濾鏡》詳細講述了FFmpeg的濾鏡添加步驟,并給出了代碼執行個體。

本文将以FFmpeg自帶的deinterlace濾鏡”yadif –

yet another deinterlace filter”為例分析

FFmpeg濾鏡的代碼級實作機制。

總的來說,FFmpeg的濾鏡機制和MicroSoft

Directshow機制基本相同——不知道誰學了誰的,還是程

序員所見略同:

建構機制上:FFmpeg自己實作了統一的對象模型,DShow是COM的實作;

架構實作上:都使用filter

graph來管理濾鏡;

連接配接方式上:FFmpeg使用Pad結構體,DShow使用Pin,兩者都是配置設定器的機制;

下面是以層級來分析代碼:

FFmpeg轉碼層的僞碼如下所示,紅色粗體是和filter相關的函數:

點選(此處)折疊或打開

int transcode(AVFormatContext **output_files,

              int nb_output_files,

              AVInputFile *input_files,

              int nb_input_files,

              AVStreamMap *stream_maps, 

              int nb_stream_maps)

{

  …

  解碼參數計算;

  #if CONFIG_AVFILTER

  if (configure_video_filters(ist,

ost)) {

    fprintf(stderr, "Error opening

filters!\n");

    ret = AVERROR(EINVAL);

    goto fail;

  }

  #endif

  編碼參數計算;

  output_packet(ist, ist_index,

ost_table,

                nb_ostreams,

&pkt, &errorflag);

  ……

  return ret;

}

函數configure_video_filters是用來初始化filter graph

和filter本身的初始化;

函數output_packet是FFmpeg整個解碼,濾鏡處理,編碼調用的實作。

1.

先來看configure_video_filters的實作:

int configure_video_filters(AVInputStream *ist, 

                            AVOutputStream *ost)

   //

類似于directshow一樣初始化filter graph等;

  ost->graph = avfilter_graph_alloc();

    // 初始化filter

graph和filter

  avfilter_graph_parse(ost->graph,

ost->avfilter, 

                       &inputs,

&outputs, NULL)

  //

檢查filter支援的媒體類型

  avfilter_graph_config(ost->graph,

NULL);

  。。。

  return 0;

filter

graph和filter的初始化:

int avfilter_graph_parse(AVFilterGraph *graph, 

                         const char *filters,

                         AVFilterInOut **open_inputs, 

                         AVFilterInOut **open_outputs,

                         void *log_ctx)

    do {

        parse_inputs(&filters, &curr_inputs, open_outputs, log_ctx);

        parse_filter(&filter,

&filters, graph, index, log_ctx);

        if (filter->input_count == 1 && !curr_inputs && !index) {

            /* First

input can be omitted if it is "[in]" */

            const char *tmp = "[in]";

            if ((ret = parse_inputs(&tmp, &curr_inputs, open_outputs, 

                                    log_ctx)) < 0)

                goto

fail;

        }

        link_filter_inouts(filter, &curr_inputs, open_inputs, log_ctx);

        parse_outputs(&filters, &curr_inputs, open_inputs, 

                      open_outputs, log_ctx);

        filters += strspn(filters, WHITESPACES);

        chr = *filters++;

        index++;

    } while (chr == ‘,‘ || chr == ‘;‘);

    if (open_inputs && *open_inputs && 

        !strcmp((*open_inputs)->name, ""out"") && curr_inputs) {

        /* Last output can be

omitted if it is "[out]" */

        const char *tmp = "[out]";

        if ((ret = parse_outputs(&tmp, &curr_inputs, open_inputs, 

                                              open_outputs, log_ctx)) < 0)

            goto

    }

    return 0;

static int parse_filter(AVFilterContext **filt_ctx, 

    const char **buf, AVFilterGraph *graph,"

    int index, void *log_ctx)

    char *opts = NULL;

    char *name = av_get_token(buf, "=,;[\n");

    int ret;

    if (**buf == ‘=‘) {

        (*buf)++;

        opts = av_get_token(buf, "[],;\n");

    }

    ret = create_filter(filt_ctx, graph, index, name,

opts, log_ctx);

    av_free(name);

    av_free(opts);

    return ret;

到了真正的Filter

graph初始化,這部分和DShow很相似:

int create_filter(AVFilterContext **filt_ctx, 

                  AVFilterGraph *ctx, int index,

                  const char *filt_name, const char *args, 

                  void *log_ctx)

    AVFilter *filt;

    char inst_name[30];

    char tmp_args[256];

    int ret;

    //

注冊目前filter到靜态數組中

    filt = avfilter_get_by_name(filt_name);

配置設定并初始化輸入輸出Pad

    ret = avfilter_open(filt_ctx, filt, inst_name);

将目前filter添加到filter graph中

    ret = avfilter_graph_add_filter(ctx, *filt_ctx);

通過函數指針,調用目前filter的初始化函數

    ret = avfilter_init_filter(*filt_ctx,

args, NULL);

int avfilter_init_filter(AVFilterContext *filter, 

                         const char *args, 

                         void *opaque)

    int ret=0;

    if (filter->filter->init)

        ret = filter->filter->init(filter, args, opaque);

    return ret;

static av_cold int init(AVFilterContext *ctx, 

                                  const char *args, 

                                  void *opaque)

    YADIFContext *yadif = ctx->priv;

    av_unused int cpu_flags = av_get_cpu_flags();

    yadif->mode = 0;

    yadif->parity = -1;

    yadif->csp = NULL;

濾鏡函數指針初始化指派

    yadif->filter_line = filter_line_c;

2.

接着來看兩個filter的Pad連接配接時的媒體類型的協商機制

int avfilter_graph_config(AVFilterGraph *graphctx, void *log_ctx)

     //

檢查filter的權限

    if ((ret = ff_avfilter_graph_check_validity(graphctx, log_ctx)))

        return ret;

    //

檢查Pad支援的媒體類型

    if ((ret = ff_avfilter_graph_config_formats(graphctx,

log_ctx)))

 // 

    if ((ret = ff_avfilter_graph_config_links(graphctx, log_ctx)))

檢查Pad支援的媒體類型 :

int ff_avfilter_graph_config_formats(AVFilterGraph *graph, 

                                                          AVClass *log_ctx)

    /* find

supported formats from sub-filters, and merge along links */

    if ((ret = query_formats(graph,

log_ctx)) < 0)

    /* Once

everything is merged, it‘s possible that we‘ll still have

     *

multiple valid media format choices. We pick the first one. */

    pick_formats(graph);

static int query_formats(AVFilterGraph *graph, 

                         AVClass *log_ctx)

    int i, j, ret;

    int scaler_count = 0;

    /* ask all

the sub-filters for their supported media formats */

    for (i = 0; i < graph->filter_count; i++) {

        if (graph->filters[i]->filter->query_formats)

            graph->filters[i]->filter->query_formats(graph->filters[i]);

        else

            avfilter_default_query_formats(graph->filters[i]);

    /* go through and merge as many

format lists as possible */

       … 

/* 檢查連接配接用Pad支援的媒體類型 */

static int query_formats(AVFilterContext *ctx)

    static const enum PixelFormat

pix_fmts[] = {

        PIX_FMT_YUV420P,

        PIX_FMT_YUV422P,

        PIX_FMT_YUV444P,

        PIX_FMT_YUV410P,

        PIX_FMT_YUV411P,

        PIX_FMT_GRAY8,

        PIX_FMT_YUVJ420P,

        PIX_FMT_YUVJ422P,

        PIX_FMT_YUVJ444P,

        AV_NE( PIX_FMT_GRAY16BE, PIX_FMT_GRAY16LE ),

        PIX_FMT_YUV440P,

        PIX_FMT_YUVJ440P,

        AV_NE( PIX_FMT_YUV420P16BE, PIX_FMT_YUV420P16LE ),

        AV_NE( PIX_FMT_YUV422P16BE, PIX_FMT_YUV422P16LE ),

        AV_NE( PIX_FMT_YUV444P16BE, PIX_FMT_YUV444P16LE ),

        PIX_FMT_NONE

    };

    avfilter_set_common_pixel_formats(ctx, 

                                      avfilter_make_format_list(pix_fmts));

3. 最後來看濾鏡的連接配接和對每幀圖像處理時的調用

int output_packet(AVInputStream *ist, int ist_index,

                  AVOutputStream **ost_table,

                  int nb_ostreams,

                  const AVPacket *pkt, 

                  int *errorflag)

  /* decode the packet if needed */

  if (ist->decoding_needed) {

    解碼;

#if CONFIG_AVFILTER

    if(ist->st->codec->codec_type == AVMEDIA_TYPE_VIDEO)

    if (start_time == 0 || ist->pts >= start_time) {

      for(i=0;i<nb_ostreams;i++) {

        ost = ost_table[i];

        if (ost->input_video_filter && ost->source_index == ist_index) {

          if (!picture.sample_aspect_ratio.num)

          picture.sample_aspect_ratio = ist->st->sample_aspect_ratio;

          picture.pts = ist->pts;

          av_vsrc_buffer_add_frame(ost->input_video_filter,

&picture, AV_VSRC_BUF_FLAG_OVERWRITE);

      }

#endif

    frame_available = ist->st->codec->codec_type

                      != AVMEDIA_TYPE_VIDEO ||

                      !ost->output_video_filter ||

                      avfilter_poll_frame(ost->output_video_filter->inputs[0]);

    while (frame_available) {

      if (ist->st->codec->codec_type == AVMEDIA_TYPE_VIDEO &&

          ost->output_video_filter) {

        AVRational

ist_pts_tb = ost->output_video_filter->inputs[0]->time_base;

        if (av_vsink_buffer_get_video_buffer_ref(ost->output_video_filter,

            &ost->picref, 0) < 0)

          goto

cont;

        if (ost->picref) {

          AVRational

tempVar = {1, AV_TIME_BASE};

          avfilter_fill_frame_from_video_buffer_ref(&picture, ost->picref);

          //ist->pts = av_rescale_q(ost->picref->pts, ist_pts_tb, AV_TIME_BASE_Q);

          ist->pts = av_rescale_q(ost->picref->pts, ist_pts_tb, tempVar);//modify

by chenrui

      編碼;

      return 0;

      fail_decode:

      return -1;

   }

先來看濾鏡的連接配接:

int av_vsrc_buffer_add_frame(AVFilterContext *buffer_src,

         const AVFrame *frame, int flags)

    // 從幀清單中得到目前幀

    AVFilterBufferRef *picref =

avfilter_get_video_buffer_ref_from_frame(frame, 

       AV_PERM_WRITE);

    if (!picref)

        return AVERROR(ENOMEM);

    ret =

av_vsrc_buffer_add_video_buffer_ref(buffer_src, 

              picref, flags);

    picref->buf->data[0] = NULL;

    avfilter_unref_buffer(picref);

int av_vsrc_buffer_add_video_buffer_ref(AVFilterContext

*buffer_filter,

AVFilterBufferRef *picref, int flags)"

    智能插入相應的filter; 

    c->picref =

avfilter_get_video_buffer(outlink, AV_PERM_WRITE,

          picref->video->w,

picref->video->h);

    av_image_copy(c->picref->data,

c->picref->linesize,

picref->data, picref->linesize,

picref->format, picref->video->w, picref->video->h);

    avfilter_copy_buffer_ref_props(c->picref, picref);

    return 0;

AVFilterBufferRef *avfilter_get_video_buffer(AVFilterLink *link,

                  int perms, int

w, int h)

    AVFilterBufferRef *ret = NULL;

    if (link->dstpad->get_video_buffer)

        ret =

link->dstpad->get_video_buffer(link, perms, w, h);

    if (!ret)

avfilter_default_get_video_buffer(link, perms, w, h);

    if (ret)

        ret->type = AVMEDIA_TYPE_VIDEO;

AVFilterBufferRef *get_video_buffer(AVFilterLink *link, int

perms, 

                int w, int h)

    AVFilterBufferRef *picref;

    int width = FFALIGN(w, 32);

    int height= FFALIGN(h+2, 32);

    int i;

    picref = avfilter_default_get_video_buffer(link, perms,

width, height);

    picref->video->w = w;

    picref->video->h = h;

    for (i = 0; i < 3; i++)

        picref->data[i] +=

picref->linesize[i];

    return picref;

最後來看圖像處理函數的實作:

int avfilter_poll_frame(AVFilterLink *link)

    int i, min = INT_MAX;

    if (link->srcpad->poll_frame)

        return link->srcpad->poll_frame(link);

    for (i = 0; i < link->src->input_count; i++) {

        int val;

        if (!link->src->inputs[i])

            return -1;

        val = avfilter_poll_frame(link->src->inputs[i]);

        min = FFMIN(min, val);

    return min;

int poll_frame(AVFilterLink *link)

    YADIFContext *yadif = link->src->priv;

    int ret, val;

    if (yadif->frame_pending)

        return 1;

    val = avfilter_poll_frame(link->src->inputs[0]);

    if (val==1 && !yadif->next) { 

      //FIXME change

API to not requre this red tape

      if ((ret = avfilter_request_frame(link->src->inputs[0])) < 0)

      val = avfilter_poll_frame(link->src->inputs[0]);

    assert(yadif->next || !val);

    return

val * ((yadif->mode&1)+1);

int avfilter_request_frame(AVFilterLink *link)

    if (link->srcpad->request_frame)

        return link->srcpad->request_frame(link);

    else if (link->src->inputs[0])

        return

avfilter_request_frame(link->src->inputs[0]);

    else return -1;

static int request_frame(AVFilterLink *link)

    BufferSourceContext *c = link->src->priv;

    if (!c->picref) {

        av_log(link->src, AV_LOG_WARNING,

               "request_frame()

called with no available frame!\n");

AVERROR(EINVAL);

圖像處理函數的真正實作

    avfilter_start_frame(link, avfilter_ref_buffer(c->picref, ~0));

    avfilter_draw_slice(link, 0, link->h, 1);

    avfilter_end_frame(link);

    avfilter_unref_buffer(c->picref);

    c->picref = NULL;

完結。