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真正写入输出文件。

浙公网安备 33010602011771号