本文主要以转码为例子,流程主要为解析命令,转码,转码又可以细分为:打开编解码器,开始转码,关闭编解码器。
FFmpeg的总体函数调用结构图如下图所示:
命令解析
ffmpeg_parse_options
命令解析主要在ret = ffmpeg_parse_options(argc, argv);
find_option
po = find_option(options, opt); //从全局const OptionDef options[]中查找opt,opt是字符串,对应OptionDef的第一列。
void opt_output_file(void *optctx, const char *filename)
{
//略…
err = avformat_alloc_output_context2(&oc, NULL, o->format, filename);
if (!oc) {
print_error(filename, err);
exit(1);
}
//略…
new_video_stream();
…
new_audio_stream();
…
new_subtitle_stream ();
//略…
}
avformat_find_stream_info
avformat_find_stream_info()函数可以读取一部分视音频数据并且获得一些相关的信息。函数正常执行后返回值大于等于0。该函数上下文部分是调节起播延迟效果最明显的地方。函数分析流的时间主要由传入的AVFormatContext的probesize 和 max_analyze_duration两个属性决定,probesize是探测读入大小,默认值为32K;max_analyze_duration默认值为5S。在网络状况比较好的情况下可分别设置4K和1S,也可根据具体情况在起播画面效果、起播延迟、分析成功率等因素间取舍。这两个参数的设置在avformat_open_input()函数对结构体AVFormatContext处理完成后进行设置。
在ffmpeg_parse_options中,会调用libavfornat/utils.c里的avformat_find_stream_info,其中会会调用try_decode_frame(avcodec_open2,avcodec_decode_xxx,),avcodec_close;重要步骤为:
1.查找解码器:find_decoder()
2.打开解码器:avcodec_open2()
3.读取完整的一帧压缩编码的数据:read_frame_internal()
4.解码一些压缩编码数据:try_decode_frame()
choose_output()
用于选择一个OutputStream。比如有一个audio,一个video,那要根据pts策略,比如谁的pts比较小,就挑哪个OutputStream先干活。
transcode_frome_filter()函数用于选个一个InputStream,用于下一步的process_input()。
process_input()函数主要是解码,并把解码的buffer送往filter处理,送入滤镜。
reap_filters()函数主要是,从filter的FIFO拿出buffer,并编码。
do_video_out(AVFormatContext*s... //准备编码YUV数据。
static OutputStream *choose_output(void)
{
int i;
int64_t opts_min = INT64_MAX;
OutputStream *ost_min = NULL;
for (i = 0; i < nb_output_streams; i++) {
OutputStream *ost = output_streams[i];
int64_t opts = ost->st->cur_dts == AV_NOPTS_VALUE ? INT64_MIN :
av_rescale_q(ost->st->cur_dts, ost->st->time_base,
AV_TIME_BASE_Q);
if (ost->st->cur_dts == AV_NOPTS_VALUE)
av_log(NULL, AV_LOG_DEBUG, "cur_dts is invalid (this is harmless if it occurs once at the start per stream)\n");
if (!ost->finished && opts < opts_min) {
opts_min = opts;
ost_min = ost->unavailable ? NULL : ost;
}
}
//比如;输出是双码率,nb_output_streams就是4(一个视频,一个音频),从4个输出里找最小的,即为:opts_min。
return ost_min;
}
update_stream_timings
start_time1 = av_rescale_q(st->start_time, st->time_base,AV_TIME_BASE_Q);
start_time = FFMIN(start_time, start_time1);
ic->start_time = start_time; //90000基转成1000000基,修改了开始时间。如133200变成1480000。
try_decode_frame
从try_decode_frame()的定义可以看出,该函数首先判断视音频流的解码器是否已经打开,如果没有打开的话,先打开相应的解码器。接下来根据视音频流类型的不同,调用不同的解码函数进行解码:视频流调用avcodec_decode_video2(),音频流调用avcodec_decode_audio4(),字幕流调用avcodec_decode_subtitle2()。解码的循环会一直持续下去直到满足了while()的所有条件。while()语句的条件中有一个has_codec_parameters()函数,用于判断AVStream中的成员变量是否都已经设置完毕。该函数在avformat_find_stream_info()中的多个地方被使用过。
转码
转码的内容封装在transcode函数里,会重新打开解码器,编码器。
# 打开编解码器
如上图,打开编码器,到最后还是调用具体的编码器(X264编码器),X264初始化的函数为int X264_init(AVCodecContext *avctx),可以看出ffmpeg通过AVCodecContext结构体,将编码参数传递给x264的x264_param_t。
process_input
主要是解码,并把解码的buffer送往filter处理。
process_input_packet
该函数对帧数据进行解码并通过所有适用的过滤器进行处理。时间戳校准和字幕处理的工作也在这个函数中进行。最后,在函数返回之前,已解码的帧被复制到每个相关的输出流。
复制解码后的帧
static int process_input_packet(InputStream *ist, const AVPacket *pkt, int no_eof)
{......
for (i = 0; pkt && i < nb_output_streams; i++) {
... // check constraintsdo_streamcopy(ist, ost, pkt);
} ......
}
transcode_step()
transcode_step()调用了如下函数:process_input():完成解码工作。
transcode_step()函数调用reap_filters()函数(第1424行)来循环遍历每个输出流。reap_filters()函数的for循环负责收集缓冲区中待处理的帧,并将这些帧进行编码,然后封装到一个输出文件中。
reap_filters()
reap_filters()主要完成了编码的工作。其函数调用结构如下图所示。reap_filters()调用了如下函数
av_buffersink_get_buffer_ref():从AVFilterContext中取出一帧解码后的数据(结构为AVFilterBufferRef,可以转换为AVFrame)。
avfilter_copy_buf_props():AVFilterBufferRef转换为AVFrame。
do_audio_out():编码音频。
do_video_out():编码视频。
avfilter_unref_buffer():释放资源。
do_video_out()调用了如下函数
avcodec_encode_video2():编码一帧视频。
write_frame():写入编码后的视频压缩数据。
write_frame()调用了如下函数:
av_bitstream_filter_filter():使用AVBitStreamFilter的时候,会调用此函数进行处理。
av_interleaved_write_frame():写入压缩编码数据。
do_audio_out()调用的函数与do_video_out()基本上一样。唯一的不同在于视频编码函数avcodec_encode_video2()变成了音频编码函数avcodec_encode_audio2()。
copy怎么工作?
在main()->ffmpeg-parse_options()->open_files()->open_output_file()->new_video->stream()->new_output_stream()->choose_encoder()有:
if (!strcmp(codec_name, "copy"))
ost->stream_copy = 1;
另一个地方会 ost->encoding_needed = !ost->stream_copy,表明是否需要解码。
在transcode_init中
if (ost->stream_copy) {
所有的参数都用解码后得到的参数。
}else{
使用转码参数。
调用init_simple_filtergraph();有这句ost->filter = fg->outputs[0];这样ost->filter就有了值,在reap_filters就会进行编码(会判断ost->filter)。
}
多线程问题
#要想使用多线程,需要编译的时候加上--enable-pthread。
#有些编解码不支持多线程,配置了线程数也不会起作用。
参考:http://blog.csdn.net/leixiaohua1020/article/details/39760711