FFMPEG 3.4.2 - ffmpeg源代码分析 (三)

1. Frame在Filters中的流动

如下图,这里涉及的是Frame从buffer filter到buffer_sink filter的流动。图中省略了一些filter,但不影响分析。

FFMPEG 3.4.2 - ffmpeg源代码分析 (三)_第1张图片
  • 一个pad link连接一个源filter和一个目的filter。Pad link中有一个frame queue。
  • FFMPEG的基本想法是pad link的源filter把frame推入pad link,再通知目的filter去处理。用ff_filter_frame()做这件事。
  • Filter的处理方式有两种:
    • 一种是单输入的,如split, crop。一个输入准备好就可以开始处理。单输入Filter应该实现filter_frame()函数,在filter_frame()中调用ff_filter_frame()将frame推到下一个filter。
    • 另一种是多输入的,如overlay。一个输入准备好还不能决定输出,要先缓存,等到足够的输入才处理。多输入filter应该实现activate()函数,前几次activate()时缓存frame,最后一次才调用ff_filter_frame()。
  • 为避免函数调用嵌套过深,没有采用迭代的方式,而是在循环中实现。具体做法是:filter有个ready值,缺省值为0;用ff_filter_frame()推入frame后,增大目的filter的ready值,比如改为300;目的filter处理后,将ready改回0;每一次循环时找出ready值最大的filter,调用ff_filter_frame()或activate()推动下一轮处理。
    • 前面的图显示的是buffer filter将frame推入pad link后的情况。split.ready=300意味着下一步循环将是它处理。这里有frame的queue被标记为更深的绿色。
    • 下面的图显示的是split处理后的情况。(实际上还有一个中间状态,后面会再提到)split.ready恢复到0。overlay.ready和crop.ready变成300。Frame就是这样一步步往前推进的。
    • 可以ready设成不同值来控制处理的先后顺序。
FFMPEG 3.4.2 - ffmpeg源代码分析 (三)_第2张图片
  • Frame在FilterGraph中的流动是从av_buffer_add_frame_flags()开始的。这时的Frame是已经解码的frame。
    • 注意buffer filter自己有一个frame queue。av_fifo_generic_write()将frame写入buffer filter的queue。
    • 在request_frame()中,av_fifo_generic_read()从buffer filter的queue中读出frame,调用ff_filter_frame()。如前所说,它将frame推入pad link,标记下一个filter的ready值为300。这里是split filter。
FFMPEG 3.4.2 - ffmpeg源代码分析 (三)_第3张图片
  • 下一步调用push_frame()。Push_frame()实现了前面说的,在循环向前推进Frame。
    • 如下图,ff_filter_graph_run_once()(用黄色标出)被循环调用。
    • 首先调用ff_filter_activate()。对于多输入filter,调用它的activate();对于单输入filter(没有activate()),调用ff_filter_activate_default()。
    • 如前所说,在activate()中,多输入filter或者缓存,或者进行处理,并最终调用ff_filter_frame(),为下一轮循环做好准备。
    • 在ff_filter_activate_default()中调用ff_filter_frame_to_filter()。首先调用ff_inlink_consume_frame()从pad link取出frame,然后调用ff_filter_frame_framed()。对于单输入filter,调用它的filter_filter();对没有activate()也没有filter_frame()的filter,调用default_filter_frame()。(还不知道哪个filter是这样的。)
    • 如前所说,单输入filter处理后调用ff_filter_frame。
FFMPEG 3.4.2 - ffmpeg源代码分析 (三)_第4张图片
  • 注意ff_filter_frame_to_filter()。如果ff_filter_frame_framed()确实处理了frame,它会调用ff_filter_set_ready()。与ff_filter_frame()设置目的filter不同,它设置的是源filter。这样做的目的,是再次推动当前filter,看看是不是有新的frame到达。这就是前面提到的中间状态:split已经将Frame推入pad link,但它自己的ready值还是300。

2. 从InputStream得到Frame

  • Ffmpeg的转换过程是在transcode()中完成的。
    • 它首先调用transcode_init(),完成一些初始化工作,如调用decoder的初始化函数。
    • 然后在一个循环里运行transcode_step()。它涵盖了真正的转换过程。
FFMPEG 3.4.2 - ffmpeg源代码分析 (三)_第5张图片
  • FFMPEG的转换过程是在transcode()中完成的。
    • choose_output()选择一个frame时间最早的OutputStream。从output找到对应的inputStream开始处理。
  • process_input()用于从InputStream读取packet并处理。
    • get_input_packet()读packet。然后调用process_input_packet()处理它。
    • decode_video()先调用decode()解码得到AVFrame。avcodec_send_packet()和av_codec_receive_frame()的使用与ffplay一样。
    • decode_video()再调用send_frame_to_filters()将AVFrame送入FiltreGraph。
    • 调用av_buffer_add_frame_flags()将frame推入buffer filter,开始frame在Filters中的流动。
    • 注意处理第一个frame时还要做些初始化工作。 Ifilter_parameters_from_frame()用frame的参数初始化filter;更重要的是像前面提到的,如果FilterGraph还没有初始化,要调用configure_filtergraph()进行初始化。
FFMPEG 3.4.2 - ffmpeg源代码分析 (三)_第6张图片

3. 将Frame写入OutputStream

  • Transcode_step()调用reap_filters()读出AVFrame写入OutputStream。
FFMPEG 3.4.2 - ffmpeg源代码分析 (三)_第7张图片
  • Av_buffersink_get_frame_flags()从buffer_sink filter读出AVFrame。
  • do_video_out()写AVFrame()。
    • av_codec_send_frame()和av_codec_receive_packet()调用encoder进行编码,得到packet。
    • out_packet(), write_packet() (ffmpeg.c)和av_interleaved_write_frame()写入Packet。
    • write_packet() (mux.c)中,如果文件头还没有写,则调用write_header_internal()写文件头,这里会调用ff_mp4_muxer的mov_write_header()函数。
      iv.调用ff_mp4_muxer()的mov_write_packet()函数写packet。

相关链接

FFMPEG 3.4.2 - ffmpeg源代码分析 (一)
FFMPEG 3.4.2 - ffmpeg源代码分析 (二)
FFMPEG 3.4.2 - ffmpeg源代码分析 (三)
FFMPEG 3.4.2 - ffmpeg源代码分析 (四)- x264
FFMPEG 3.4.2 - ffplay源代码分析 (一)
FFMPEG 3.4.2 - ffplay源代码分析 (二)
FFMPEG 3.4.2 - ffplay源代码分析 (三)

你可能感兴趣的:(FFMPEG 3.4.2 - ffmpeg源代码分析 (三))