前一篇文章《為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;
完結。