ffmpeg.c源码分析
如今短视频流行当下,想象一下两个人进行视频通话,从音视频采集、编码、推流、拉流、解码、音视频同步、显示对方画面等过程;而在编码和解码最为重要的部分,使用的技术正是ffmpeg,在一些视频转码也常用到ffmpeg,所以学习ffmpeg.c源码对于掌握ffmpeg非常重要。
ffmpeg结构流程:解析命令行、打开输入文件、打开输出文件、读取输入文件、解码和编码处理、写入输出文件,ffmpeg程序涉及的主要文件:
cmdutils.c:解析命令行相关的工具函数
ffmpeg_opt.c:负责解析命令行输入的参数
ffmpeg.c:多媒体文件转换器的主体
ffmpeg_filter.c:filter相关
(1)命令行解析
首先了解const OptionDef options[]这个数组的结构,它存储着ffmpeg所有命令,以key-value对照关系保存;再个就是OptionParseContext结构体,包括全局命令、输入输出相关参数,最终解析命令行得到的参数存储在OptionsContext。
typedef struct OptionParseContext { OptionGroup global_opts;//全局命令分组 //group[0]存储与输出文件相关参数,grooup[1]存储与输入文件相关参数 OptionGroupList *groups; int nb_groups; /* 临时数组,存储输出、输⼊相关参数*/ OptionGroup cur_group; } OptionParseContext;
下面进行具体代码分析。
(a)函数split_commandline对命令行做分割,里面主要关注finish_group、find_option、opt_default等函数,在split_commandline函数中循环从argc取出参数,判断参数是否有“-”开头,如果没有为输出参数;
//循环取出参数 while (optindex < argc) { //取参数队列 const char *opt = argv[optindex++], *arg; const OptionDef *po; int ret; av_log(NULL, AV_LOG_DEBUG, "Reading option '%s' ...", opt); //跳过参数格式:-- if (opt[0] == '-' && opt[1] == '-' && !opt[2]) { dashdash = optindex; continue; } /* 如果不是以“-”开头,另外一种情况,就是定义输出文件*/ if (opt[0] != '-' || !opt[1] || dashdash+1 == optindex) { finish_group(octx, 0, opt); av_log(NULL, AV_LOG_DEBUG, " matched as %s.\n", groups[0].name); continue; } opt++; ..........................
finish_group把参数写入到octx,做一个入组操作,find_optio查找opt是否在options里面,判断指令是否存在。
/* 参数格式是否为“-i xxx” 如果比配,返回1 */ if ((ret = match_group_separator(groups, nb_groups, opt)) >= 0) { GET_ARG(arg);//获取参数的value finish_group(octx, ret, arg);//将参数入组 av_log(NULL, AV_LOG_DEBUG, " matched as %s with argument '%s'.\n", groups[ret].name, arg); continue; } /* 查找opt 是否在options里面 */ po = find_option(options, opt);//查找的是const OptionDef options[] if (po->name) { if (po->flags & OPT_EXIT) { /* optional argument, e.g. -h */ arg = argv[optindex++]; } else if (po->flags & HAS_ARG) { GET_ARG(arg); } else { arg = "1"; } add_opt(octx, po, opt, arg);//添加options变量 av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with " "argument '%s'.\n", po->name, po->help, arg); continue; } /* 如果解析不到的,就会尝试到这里查找默认参数 */ if (argv[optindex]) { ret = opt_default(NULL, opt, argv[optindex]); if (ret >= 0) { av_log(NULL, AV_LOG_DEBUG, " matched as AVOption '%s' with " "argument '%s'.\n", opt, argv[optindex]); optindex++; continue; } else if (ret != AVERROR_OPTION_NOT_FOUND) { av_log(NULL, AV_LOG_ERROR, "Error parsing option '%s' " "with argument '%s'.\n", opt, argv[optindex]); return ret; } } /* 处理“-noxx” 格式参数,*/ if (opt[0] == 'n' && opt[1] == 'o' && (po = find_option(options, opt + 2)) && po->name && po->flags & OPT_BOOL) { add_opt(octx, po, opt, "0"); av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with " "argument 0.\n", po->name, po->help); continue; } //没有匹配的参数,输出错误 av_log(NULL, AV_LOG_ERROR, "Unrecognized option '%s'.\n", opt); return AVERROR_OPTION_NOT_FOUND; }
如果解析不到就会尝试查找默认参数,找不到就会报错。
(b)解析全局变量
结构OptionGroup用于保存全局参数,函数parse_optgroup对全局参数进行解析。
int parse_optgroup(void *optctx, OptionGroup *g) { int i, ret; av_log(NULL, AV_LOG_DEBUG, "Parsing a group of options: %s %s.\n", g->group_def->name, g->arg); for (i = 0; i < g->nb_opts; i++) { Option *o = &g->opts[i]; if (g->group_def->flags && !(g->group_def->flags & o->opt->flags)) { av_log(NULL, AV_LOG_ERROR, "Option %s (%s) cannot be applied to " "%s %s -- you are trying to apply an input option to an " "output file or vice versa. Move this option before the " "file it belongs to.\n", o->key, o->opt->help, g->group_def->name, g->arg); return AVERROR(EINVAL); } av_log(NULL, AV_LOG_DEBUG, "Applying option %s (%s) with argument %s.\n", o->key, o->opt->help, o->val); ret = write_option(optctx, o->opt, o->key, o->val);//将解析出来的参数,真正写入optctx,进行关联起来 if (ret < 0) return ret; } av_log(NULL, AV_LOG_DEBUG, "Successfully parsed a group of options.\n"); return 0; }
write_option将解析的参数真正写入optctx,把参数值进行类型转换(比如number、时间戳等),如果在OptionDef中查找到的命令行参数有设置了解析的回调函数func_arg,就会在这里进行解析。
static int write_option(void *optctx, const OptionDef *po, const char *opt, const char *arg) { /* new-style options contain an offset into optctx, old-style address of * a global var*/ void *dst = po->flags & (OPT_OFFSET | OPT_SPEC) ? (uint8_t *)optctx + po->u.off : po->u.dst_ptr; int *dstcount; if (po->flags & OPT_SPEC) { SpecifierOpt **so = dst; char *p = strchr(opt, ':'); char *str; dstcount = (int *)(so + 1); *so = grow_array(*so, sizeof(**so), dstcount, *dstcount + 1); str = av_strdup(p ? p + 1 : ""); if (!str) return AVERROR(ENOMEM); (*so)[*dstcount - 1].specifier = str; dst = &(*so)[*dstcount - 1].u; } if (po->flags & OPT_STRING) { char *str; str = av_strdup(arg); av_freep(dst); if (!str) return AVERROR(ENOMEM); *(char **)dst = str; } else if (po->flags & OPT_BOOL || po->flags & OPT_INT) { *(int *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT_MIN, INT_MAX);//解析 int } else if (po->flags & OPT_INT64) { *(int64_t *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT64_MIN, INT64_MAX); } else if (po->flags & OPT_TIME) { *(int64_t *)dst = parse_time_or_die(opt, arg, 1);//时间转换 } else if (po->flags & OPT_FLOAT) { *(float *)dst = parse_number_or_die(opt, arg, OPT_FLOAT, -INFINITY, INFINITY); } else if (po->flags & OPT_DOUBLE) { *(double *)dst = parse_number_or_die(opt, arg, OPT_DOUBLE, -INFINITY, INFINITY); } else if (po->u.func_arg) { int ret = po->u.func_arg(optctx, opt, arg);//调用函数进行解析 if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Failed to set value '%s' for option '%s': %s\n", arg, opt, av_err2str(ret)); return ret; } } if (po->flags & OPT_EXIT) exit_program(0); return 0; }
(2)打开输入文件
函数 static int open_files(OptionGroupList *l, const char *inout,int (*open_file)(OptionsContext*, const char*)) 打开输入文件,传入全局变量参数list、实际处理文件函数指针。
static int open_files(OptionGroupList *l, const char *inout, int (*open_file)(OptionsContext*, const char*)) { int i, ret; for (i = 0; i < l->nb_groups; i++) { OptionGroup *g = &l->groups[i]; OptionsContext o;//保存命令行被解析后的信息,对应一个输入或者输出文件 init_options(&o); o.g = g; ret = parse_optgroup(&o, g);//解析参数,从OptionGroup解析出来 if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error parsing options for %s file " "%s.\n", inout, g->arg); uninit_options(&o); return ret; } av_log(NULL, AV_LOG_DEBUG, "Opening an %s file: %s.\n", inout, g->arg); ret = open_file(&o, g->arg);//一次只打开一路码流(输入/输出) uninit_options(&o); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error opening %s file %s.\n", inout, g->arg); return ret; } av_log(NULL, AV_LOG_DEBUG, "Successfully opened the file.\n"); } return 0; }
传入的函数指针 open_input_file,open_input_file就是ffmpeg开始解码前熟悉的流程:avformat_alloc_context分配上下文、音频/视频解码器id设置、avformat_open_input打开输入文件、choose_decoder查找解码器、avformat_find_stream_info分析码流、设置start_time和time_base等。
(3)打开输出文件
打开输出文件也是调用open_files函数,只是传入的函数指针则变成了open_output_file,作为打开输出文件处理。avformat_alloc_output_context2新建一个输出文件,new_video_stream新建视频流、new_audio_stream新建音频流、new_subtitle_stream字幕流、new_data_stream数据流。
//视频流 /* video: highest resolution */ if (!o->video_disable && av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_VIDEO) != AV_CODEC_ID_NONE) { int area = 0, idx = -1; int qcr = avformat_query_codec(oc->oformat, oc->oformat->video_codec, 0);//测试给定的容器是否可以存储编解码器 for (i = 0; i < nb_input_streams; i++) { int new_area; ist = input_streams[i];//读取流信息(音频/视频),绑定输出流与输入流关系 new_area = ist->st->codecpar->width * ist->st->codecpar->height + 100000000*!!ist->st->codec_info_nb_frames + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); if (ist->user_set_discard == AVDISCARD_ALL) continue; if((qcr!=MKTAG('A', 'P', 'I', 'C')) && (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) new_area = 1; if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && new_area > area) { if((qcr==MKTAG('A', 'P', 'I', 'C')) && !(ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)) continue; area = new_area; idx = i;//获取到输入流的索引 } } if (idx >= 0) new_video_stream(o, oc, idx); } //音频流 /* audio: most channels */ if (!o->audio_disable && av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_AUDIO) != AV_CODEC_ID_NONE) { int best_score = 0, idx = -1; for (i = 0; i < nb_input_streams; i++) { int score; ist = input_streams[i]; score = ist->st->codecpar->channels + 100000000*!!ist->st->codec_info_nb_frames + 5000000*!!(ist->st->disposition & AV_DISPOSITION_DEFAULT); if (ist->user_set_discard == AVDISCARD_ALL) continue; if (ist->st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && score > best_score) { best_score = score; idx = i; } } if (idx >= 0) new_audio_stream(o, oc, idx); } //字幕流 /* subtitles: pick first */ MATCH_PER_TYPE_OPT(codec_names, str, subtitle_codec_name, oc, "s"); if (!o->subtitle_disable && (avcodec_find_encoder(oc->oformat->subtitle_codec) || subtitle_codec_name)) { for (i = 0; i < nb_input_streams; i++) if (input_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) { AVCodecDescriptor const *input_descriptor = avcodec_descriptor_get(input_streams[i]->st->codecpar->codec_id); AVCodecDescriptor const *output_descriptor = NULL; AVCodec const *output_codec = avcodec_find_encoder(oc->oformat->subtitle_codec); int input_props = 0, output_props = 0; if (input_streams[i]->user_set_discard == AVDISCARD_ALL) continue; if (output_codec) output_descriptor = avcodec_descriptor_get(output_codec->id); if (input_descriptor) input_props = input_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); if (output_descriptor) output_props = output_descriptor->props & (AV_CODEC_PROP_TEXT_SUB | AV_CODEC_PROP_BITMAP_SUB); if (subtitle_codec_name || input_props & output_props || // Map dvb teletext which has neither property to any output subtitle encoder input_descriptor && output_descriptor && (!input_descriptor->props || !output_descriptor->props)) { new_subtitle_stream(o, oc, i); break; } } } /* Data only if codec id match */ //数据流 if (!o->data_disable ) { enum AVCodecID codec_id = av_guess_codec(oc->oformat, NULL, filename, NULL, AVMEDIA_TYPE_DATA); for (i = 0; codec_id != AV_CODEC_ID_NONE && i < nb_input_streams; i++) { if (input_streams[i]->user_set_discard == AVDISCARD_ALL) continue; if (input_streams[i]->st->codecpar->codec_type == AVMEDIA_TYPE_DATA && input_streams[i]->st->codecpar->codec_id == codec_id ) new_data_stream(o, oc, i); } }
不管是新建视频流或音频流都会调用new_output_stream来新建一个媒体流,里面通过choose_encoder选择编码器,avcodec_alloc_context3得到编码器上下文,time_base、enc_time_bases、和disposition设置等等。
(4)读取输入文件
调用get_input_packet读取文件得到AVPacket,该函数内部实际调用了av_read_frame()。
static int get_input_packet(InputFile *f, AVPacket *pkt) { if (f->rate_emu) { int i; for (i = 0; i < f->nb_streams; i++) { InputStream *ist = input_streams[f->ist_index + i]; int64_t pts = av_rescale(ist->dts, 1000000, AV_TIME_BASE); int64_t now = av_gettime_relative() - ist->start; if (pts > now) return AVERROR(EAGAIN); } } //如果输入文件大于1,则会有多少个线程去读取 #if HAVE_THREADS if (nb_input_files > 1) return get_input_packet_mt(f, pkt); #endif return av_read_frame(f->ctx, pkt);//读取一个packet }
(5)解码和编码处理
transcode函数处理转码的工作,transcode_init转码初始化(包括初始化输入流和输出流、并将头部信息写入输出文件)、check_keyboard_interaction检测键盘操作、transcode_step进行转码(主要是解码、编码工作)、flush_encoders冲涮编码器输出编码器剩余帧、写入文件尾部。
static int transcode(void) { int ret, i; AVFormatContext *os; OutputStream *ost; InputStream *ist; int64_t timer_start; int64_t total_packets_written = 0; ret = transcode_init();//初始化输入流、输出流,并在输出文件写入head头部信息 if (ret < 0) goto fail; if (stdin_interaction) { av_log(NULL, AV_LOG_INFO, "Press [q] to stop, [?] for help\n"); } timer_start = av_gettime_relative(); #if HAVE_THREADS if ((ret = init_input_threads()) < 0) goto fail; #endif //在循环里面进行转码(解码处理、编码处理(写入输出文件)) while (!received_sigterm) { int64_t cur_time= av_gettime_relative(); /* if 'q' pressed, exits */ if (stdin_interaction) if (check_keyboard_interaction(cur_time) < 0) break; /* check if there's any stream where output is still needed */ if (!need_output()) { av_log(NULL, AV_LOG_VERBOSE, "No more output streams to write to, finishing.\n"); break; } //进行转码 ret = transcode_step(); if (ret < 0 && ret != AVERROR_EOF) { av_log(NULL, AV_LOG_ERROR, "Error while filtering: %s\n", av_err2str(ret)); break; } /* 打印转码信息,输出到屏幕上 */ print_report(0, timer_start, cur_time); } #if HAVE_THREADS free_input_threads(); #endif /* at the end of stream, we must flush the decoder buffers */ for (i = 0; i < nb_input_streams; i++) { ist = input_streams[i]; if (!input_files[ist->file_index]->eof_reached) { process_input_packet(ist, NULL, 0); } } //冲刷编码器,输出编码器中剩余的帧 flush_encoders(); term_exit(); //退出转码循环后,在输出文件写入尾部 /* write the trailer if needed and close file */ for (i = 0; i < nb_output_files; i++) { os = output_files[i]->ctx; if (!output_files[i]->header_written) { av_log(NULL, AV_LOG_ERROR, "Nothing was written into output file %d (%s), because " "at least one of its streams received no packets.\n", i, os->url); continue; } if ((ret = av_write_trailer(os)) < 0) {//输出文件写入尾部 av_log(NULL, AV_LOG_ERROR, "Error writing trailer of %s: %s\n", os->url, av_err2str(ret)); if (exit_on_error) exit_program(1); } } .............. .............. }
在通过init_input_stream、init_output_stream完成输入输出流初始化后,开启循环不断transcode_step进行转码。
static int transcode_step(void) { OutputStream *ost; InputStream *ist = NULL; int ret; ost = choose_output(); if (!ost) { if (got_eagain()) { reset_eagain(); av_usleep(10000); return 0; } av_log(NULL, AV_LOG_VERBOSE, "No more inputs to read from, finishing.\n"); return AVERROR_EOF; } if (ost->filter && !ost->filter->graph->graph) { if (ifilter_has_all_input_formats(ost->filter->graph)) { ret = configure_filtergraph(ost->filter->graph);//设置AVFilterGraph if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error reinitializing filters!\n"); return ret; } } } if (ost->filter && ost->filter->graph->graph) { if (!ost->initialized) { char error[1024] = {0}; ret = init_output_stream(ost, error, sizeof(error)); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error initializing output stream %d:%d -- %s\n", ost->file_index, ost->index, error); exit_program(1); } } if ((ret = transcode_from_filter(ost->filter->graph, &ist)) < 0) return ret; if (!ist) return 0; } else if (ost->filter) { int i; for (i = 0; i < ost->filter->graph->nb_inputs; i++) { InputFilter *ifilter = ost->filter->graph->inputs[i]; if (!ifilter->ist->got_output && !input_files[ifilter->ist->file_index]->eof_reached) { ist = ifilter->ist; break; } } if (!ist) { ost->inputs_done = 1; return 0; } } else { av_assert0(ost->source_index >= 0); ist = input_streams[ost->source_index]; } ret = process_input(ist->file_index);//解码处理 if (ret == AVERROR(EAGAIN)) { if (input_files[ist->file_index]->eagain) ost->unavailable = 1; return 0; } if (ret < 0) return ret == AVERROR_EOF ? 0 : ret; return reap_filters(0);//编码处理 }
整个转码过程是调用process_input函数进行解码,reap_filters进行编码并写入输出文件;
process_input解码处理过程:get_input_packet读取一个AVPacket,process_input_packet进行解码,来看看process_input_packet函数具体实现
static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eof) { int ret = 0, i; int repeating = 0; int eof_reached = 0; AVPacket avpkt; if (!ist->saw_first_ts) { ist->dts = ist->st->avg_frame_rate.num ? - ist->dec_ctx->has_b_frames * AV_TIME_BASE / av_q2d(ist->st->avg_frame_rate) : 0; ist->pts = 0; if (pkt && pkt->pts != AV_NOPTS_VALUE && !ist->decoding_needed) { ist->dts += av_rescale_q(pkt->pts, ist->st->time_base, AV_TIME_BASE_Q); ist->pts = ist->dts; //unused but better to set it to a value thats not totally wrong } ist->saw_first_ts = 1; } if (ist->next_dts == AV_NOPTS_VALUE) ist->next_dts = ist->dts; if (ist->next_pts == AV_NOPTS_VALUE) ist->next_pts = ist->pts; if (!pkt) { /* EOF handling */ av_init_packet(&avpkt); avpkt.data = NULL; avpkt.size = 0; } else { avpkt = *pkt; } if (pkt && pkt->dts != AV_NOPTS_VALUE) { ist->next_dts = ist->dts = av_rescale_q(pkt->dts, ist->st->time_base, AV_TIME_BASE_Q); if (ist->dec_ctx->codec_type != AVMEDIA_TYPE_VIDEO || !ist->decoding_needed) ist->next_pts = ist->pts = ist->dts; } // while we have more to decode or while the decoder did output something on EOF while (ist->decoding_needed) { int64_t duration_dts = 0; int64_t duration_pts = 0; int got_output = 0; int decode_failed = 0; ist->pts = ist->next_pts; ist->dts = ist->next_dts; switch (ist->dec_ctx->codec_type) { case AVMEDIA_TYPE_AUDIO://解码音频(一个AVPacket) ret = decode_audio (ist, repeating ? NULL : &avpkt, &got_output, &decode_failed); break; case AVMEDIA_TYPE_VIDEO://解码视频(一个AVPacket) ret = decode_video (ist, repeating ? NULL : &avpkt, &got_output, &duration_pts, !pkt, &decode_failed); if (!repeating || !pkt || got_output) { if (pkt && pkt->duration) { duration_dts = av_rescale_q(pkt->duration, ist->st->time_base, AV_TIME_BASE_Q); } else if(ist->dec_ctx->framerate.num != 0 && ist->dec_ctx->framerate.den != 0) { int ticks= av_stream_get_parser(ist->st) ? av_stream_get_parser(ist->st)->repeat_pict+1 : ist->dec_ctx->ticks_per_frame; duration_dts = ((int64_t)AV_TIME_BASE * ist->dec_ctx->framerate.den * ticks) / ist->dec_ctx->framerate.num / ist->dec_ctx->ticks_per_frame; } if(ist->dts != AV_NOPTS_VALUE && duration_dts) { ist->next_dts += duration_dts; }else ist->next_dts = AV_NOPTS_VALUE; } if (got_output) { if (duration_pts > 0) { ist->next_pts += av_rescale_q(duration_pts, ist->st->time_base, AV_TIME_BASE_Q); } else { ist->next_pts += duration_dts; } } break; case AVMEDIA_TYPE_SUBTITLE: if (repeating) break; ret = transcode_subtitles(ist, &avpkt, &got_output, &decode_failed); if (!pkt && ret >= 0) ret = AVERROR_EOF; break; default: return -1; } if (ret == AVERROR_EOF) { eof_reached = 1; break; } if (ret < 0) { if (decode_failed) { av_log(NULL, AV_LOG_ERROR, "Error while decoding stream #%d:%d: %s\n", ist->file_index, ist->st->index, av_err2str(ret)); } else { av_log(NULL, AV_LOG_FATAL, "Error while processing the decoded " "data for stream #%d:%d\n", ist->file_index, ist->st->index); } if (!decode_failed || exit_on_error) exit_program(1); break; } if (got_output) ist->got_output = 1; if (!got_output) break; if (!pkt) break; repeating = 1; }
由代码可以知道内部封装了decode_audio、decode_video分别进行音频和视频的解码,这两个函数内部都调用decode函数,具体流程就是我们熟悉的解码流程。
static int decode(AVCodecContext *avctx, AVFrame *frame, int *got_frame, AVPacket *pkt) { int ret; *got_frame = 0; if (pkt) { ret = avcodec_send_packet(avctx, pkt);//发送一个packet到解码器 // In particular, we don't expect AVERROR(EAGAIN), because we read all // decoded frames with avcodec_receive_frame() until done. if (ret < 0 && ret != AVERROR_EOF) return ret; } ret = avcodec_receive_frame(avctx, frame);//从解码器读取一帧frame if (ret < 0 && ret != AVERROR(EAGAIN)) return ret; if (ret >= 0) *got_frame = 1; return 0; }
在decode_video、decode_vaudio函数中,解码得到一帧frame后,send_frame_to_filters(内部是ifilter_send_frame)将解码帧写入输入流的filter中。
static int send_frame_to_filters(InputStream *ist, AVFrame *decoded_frame) { int i, ret; AVFrame *f; av_assert1(ist->nb_filters > 0); /* ensure ret is initialized */ for (i = 0; i < ist->nb_filters; i++) { if (i < ist->nb_filters - 1) { f = ist->filter_frame; ret = av_frame_ref(f, decoded_frame); if (ret < 0) break; } else f = decoded_frame; ret = ifilter_send_frame(ist->filters[i], f); if (ret == AVERROR_EOF) ret = 0; /* ignore */ if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Failed to inject frame into filter network: %s\n", av_err2str(ret)); break; } } return ret; }
reap_filters 编码处理:
av_buffersink_get_frame_flags 获取AVFilterGraph管理的过滤器对象处理后的帧AVFrame ,buffersink是最后一个过滤器
do_video_out,do_audio_out 处理编码工作
static int reap_filters(int flush) { AVFrame *filtered_frame = NULL; int i; /* Reap all buffers present in the buffer sinks */ for (i = 0; i < nb_output_streams; i++) { OutputStream *ost = output_streams[i]; OutputFile *of = output_files[ost->file_index]; AVFilterContext *filter; AVCodecContext *enc = ost->enc_ctx; int ret = 0; //对应的stream使用copy时,不会用到filter过滤器,就会跳过 if (!ost->filter || !ost->filter->graph->graph) continue; filter = ost->filter->filter; if (!ost->initialized) {//只初始化一次 char error[1024] = ""; ret = init_output_stream(ost, error, sizeof(error));//初始化输出流 if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error initializing output stream %d:%d -- %s\n", ost->file_index, ost->index, error); exit_program(1); } } if (!ost->filtered_frame && !(ost->filtered_frame = av_frame_alloc())) { return AVERROR(ENOMEM); } filtered_frame = ost->filtered_frame; while (1) { double float_pts = AV_NOPTS_VALUE;
// 从buffersink中获取一帧 ret = av_buffersink_get_frame_flags(filter, filtered_frame, AV_BUFFERSINK_FLAG_NO_REQUEST); if (ret < 0) { if (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) { av_log(NULL, AV_LOG_WARNING, "Error in av_buffersink_get_frame_flags(): %s\n", av_err2str(ret)); } else if (flush && ret == AVERROR_EOF) { if (av_buffersink_get_type(filter) == AVMEDIA_TYPE_VIDEO) do_video_out(of, ost, NULL, AV_NOPTS_VALUE); } break; } if (ost->finished) { av_frame_unref(filtered_frame); continue; } if (filtered_frame->pts != AV_NOPTS_VALUE) { int64_t start_time = (of->start_time == AV_NOPTS_VALUE) ? 0 : of->start_time; AVRational filter_tb = av_buffersink_get_time_base(filter); AVRational tb = enc->time_base; int extra_bits = av_clip(29 - av_log2(tb.den), 0, 16); tb.den <<= extra_bits; float_pts = av_rescale_q(filtered_frame->pts, filter_tb, tb) - av_rescale_q(start_time, AV_TIME_BASE_Q, tb); float_pts /= 1 << extra_bits; // avoid exact midoints to reduce the chance of rounding differences, this can be removed in case the fps code is changed to work with integers float_pts += FFSIGN(float_pts) * 1.0 / (1<<17); filtered_frame->pts = av_rescale_q(filtered_frame->pts, filter_tb, enc->time_base) - av_rescale_q(start_time, AV_TIME_BASE_Q, enc->time_base); } switch (av_buffersink_get_type(filter)) { case AVMEDIA_TYPE_VIDEO: if (!ost->frame_aspect_ratio.num) enc->sample_aspect_ratio = filtered_frame->sample_aspect_ratio; if (debug_ts) { av_log(NULL, AV_LOG_INFO, "filter -> pts:%s pts_time:%s exact:%f time_base:%d/%d\n", av_ts2str(filtered_frame->pts), av_ts2timestr(filtered_frame->pts, &enc->time_base), float_pts, enc->time_base.num, enc->time_base.den); } //视频编码处理 do_video_out(of, ost, filtered_frame, float_pts); break; case AVMEDIA_TYPE_AUDIO: if (!(enc->codec->capabilities & AV_CODEC_CAP_PARAM_CHANGE) && enc->channels != filtered_frame->channels) { av_log(NULL, AV_LOG_ERROR, "Audio filter graph output is not normalized and encoder does not support parameter changes\n"); break; } //音频编码处理 do_audio_out(of, ost, filtered_frame); break; default: // TODO support subtitle filters av_assert0(0); } av_frame_unref(filtered_frame); } } return 0; }
do_video_out内部主要看这部分代码,正是编码流程过程,output_packet写入输出文件,do_audio_out也是类似一样的过程。
//往编码器发送一帧 ret = avcodec_send_frame(enc, in_picture); if (ret < 0) goto error; // Make sure Closed Captions will not be duplicated av_frame_remove_side_data(in_picture, AV_FRAME_DATA_A53_CC); while (1) { ret = avcodec_receive_packet(enc, &pkt);//从编码器读取一个packet update_benchmark("encode_video %d.%d", ost->file_index, ost->index); if (ret == AVERROR(EAGAIN)) break; if (ret < 0) goto error; if (debug_ts) { av_log(NULL, AV_LOG_INFO, "encoder -> type:video " "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s\n", av_ts2str(pkt.pts), av_ts2timestr(pkt.pts, &enc->time_base), av_ts2str(pkt.dts), av_ts2timestr(pkt.dts, &enc->time_base)); } if (pkt.pts == AV_NOPTS_VALUE && !(enc->codec->capabilities & AV_CODEC_CAP_DELAY)) pkt.pts = ost->sync_opts; av_packet_rescale_ts(&pkt, enc->time_base, ost->mux_timebase); if (debug_ts) { av_log(NULL, AV_LOG_INFO, "encoder -> type:video " "pkt_pts:%s pkt_pts_time:%s pkt_dts:%s pkt_dts_time:%s\n", av_ts2str(pkt.pts), av_ts2timestr(pkt.pts, &ost->mux_timebase), av_ts2str(pkt.dts), av_ts2timestr(pkt.dts, &ost->mux_timebase)); } //写入输出文件 frame_size = pkt.size; output_packet(of, &pkt, ost, 0); /* if two pass, output log */ if (ost->logfile && enc->stats_out) { fprintf(ost->logfile, "%s", enc->stats_out); } }
(6)写入输出文件
在音频/视频编码处理后得到AVPacket后,此时通过 output_packet写入输出文件
static void output_packet(OutputFile *of, AVPacket *pkt, OutputStream *ost, int eof) { int ret = 0; /* apply the output bitstream filters */ if (ost->bsf_ctx) { ret = av_bsf_send_packet(ost->bsf_ctx, eof ? NULL : pkt); if (ret < 0) goto finish; while ((ret = av_bsf_receive_packet(ost->bsf_ctx, pkt)) >= 0) write_packet(of, pkt, ost, 0);//写入文件 if (ret == AVERROR(EAGAIN)) ret = 0; } else if (!eof) write_packet(of, pkt, ost, 0); finish: if (ret < 0 && ret != AVERROR_EOF) { av_log(NULL, AV_LOG_ERROR, "Error applying bitstream filters to an output " "packet for stream #%d:%d.\n", ost->file_index, ost->index); if(exit_on_error) exit_program(1); } }
内部是调用write_packet,最终会执行av_interleaved_write_frame真正写入输出文件。