FFMPEG - ffplay source code analysis

FFmpeg is an open source, free, streaming video and audio programs, cross-platform, which provides a complete recording solution to convert and stream audio and video. The ffplay is ffmpeg ffmpeg-based official of a simple player. Ffplay for the player to learn the process, ffmpeg calls, etc. is a very good example.

 

1. Examples

The description herein using the following example:

./ffplay avm.mp4

2. read_thread()

Thread read_thread responsible demux, its flow as shown below:

  • avformat_alloc_context distribution AVFormatContext. This is the context demux;
  • avformat_open_input () parse the file, the file is determined encapsulation format (i.e. mux type);
  • aformat_find_stream_info () continuously analyzes the stream included in the AVFormatContext, determined according to the type of stream Decoder, and create the AVCodecContext, which is decode context;
  • If the designated playback position, avformat_seek_file () moves the playback position to the specified position;
  • av_find_best_stream () find the best quality specified stream type of stream.
  • stream_component_open () to create a new thread video_thread. Video_thread responsible decode.
  • Finally, read_thread in the loop, the by av_read_frame () Read Packet, and calls packet_queue_put (), placed in queue, for video_thread read.

2.1 avformat_alloc_context()

avformat_alloc_context call avformat_get_context_defaults () to AVFormatContext set default parameters, including AVFormtContext :: io_open () and AVFormatContext :: io_close ().

2.2 avformat_open_input() - input_input() - io_open_default()

avformat_open_input () call av_probe_input_format2 () to determine the package file format, this mention later. Here look io_open_default () How to open the link you want to play. There are two levels of Context: AVIOContext and URLContext. ffio_xxx () is responsible for IO layer, ffurl_xxx () responsible for URL layer.

  • url_find_protocol () first extracts a prefix to play links. Such as http: //.../a.mp4, the prefix is ​​http. There is only a file name avm.mp4, no prefix, it defaults to "file", so avm.mp4 and file: avm.mp4 are equivalent.
  • ffurl_get_protocols get a list of URL protocol has been registered. url_find_protocol () to find in this list based on the prefix. The prefix "file" corresponds ff_file_protocol.
  • ffurl_alloc () call url_alloc_for_protocol () assigned respective URLContext. URLContext really save a file handle fd.
  • ffurl_connect () call file_open () to open the file, call file_seek () to adjust the start position to 0.
  • ffio_fdopen () call avio_alloc_context () allocation IOContext, then initialize it.

2.3 avformat_open_input() - input_input() - av_probe_input_buffer2()

Important function here is av_probe_input_format3 (). It is determined according to the file format of the package file name and file contents. It has two operating modes, depending on the value of the parameter is_opened decision, is_opened indicate whether the file has been opened.

AVInputFormat *av_probe_input_format3 (AVProbeData *pd, int is_opened, int* score_ret); 

如果is_opened为false,则av_probe_input_format3()只根据文件名查找AVInputFormat,否则,av_probe_input_format3()调用AVInputFormat::read_probe(),根据文件内容的头部判断。对于mp4,其read_probe()是mov_probe()。
av_probe_input_format3()的第一个参数pd保存了文件名和文件头部的内容。 avInputFormat可能有多种选择,它们的优先级不同,用score表示。av_probe_input_format3()返回优先级最高的,优先级保存在第3个参数score_ret中。

init_input()第一次调用av_probe_input_format2()。这时文件没打开,根据文件名字找查找封装格式,没找到。av_probe_input_buffer2()先调用avio_read()读文件内容头部分,再第二次调用av_probe_input_format2(),这次mov_probe()返回的AVInputFormat是ff_mov_demuxer。

avio_read()调用IO层的io_read_packet(),和URL层的file_read()读取指定大小的文件内容。

如下是以上过程涉及的类:

2.4 avformat_open_input() - AVInputFormat::read_header()

avformat_open_input()先调用avio_skip()跳过指定字节,这里是0字节。接着调用AVInputFormat::reader_header()分析文件头部。这里调用的是mov_read_header(),它会创建MOV层的上下文MovContext。

2.5 avformat_open_input() - avformat_find_stream_info()

avformat_find_stream_info()调用av_parser_init(),为每个Stream找到AVCodecParserContext,这是用来从连续的stream数据中分割frame的。
avcodec_parameters_to_context()将Codec上下文从内部使用的结构AVCodecParameters,复制到作为对外接口的AVCodecContext中。
find_probe_decoder()根据codec_id查找解码器AVCodec,这里调用avcodec_find_decoder_by_name(),根据名字在已注册的Codec列表中找到ff_h264_decoder。
avcodec_open2()调用AVCodec::init()初始Codec的上下文。这里是h264_decode_init()创建H264Context并初始化。

以上过程涉及到的类如下:

2.6 avformat_open_input() - avformat_seek_file()

avformat_seek_file()调整播放位置。av_rescale()将对外使用的时间单位转换成内部使用的。位置调整之后,之前的frame就不需要了。ff_read_frame_flush()用于清除之前的frame。最后调用mov_read_seek()调整位置。

2.7 avformat_open_input() - av_find_best_stream()

可能包括多个同类型的stream,av_find_best_stream()为每个类型选出最好的一个stream。

2.8 avformat_open_input() - stream_component_open()

stream_component_open()首先创建avcodec_alloc_context3()创建AVCodecContext并初始化,调用avcodec_parameters_to_context()复制内部参数,调用avcodec_find_decoder()找到decoder,调用avcodec_open2()初始化AVCodecContext。

最后是应用层面的数据结构VideoState,用decoder_init()初始化。在decoder_start()中,用packet_queue_start()启动packet queue,用SDL_CreateThread()启动新线程video_thread。

2.9 avformat_open_input() - av_read_frame()

read_thread在一个循环中调用av_read_frame()读packet,调用packet_queue_put()写入packet queue。
av_read_frame调用read_frame_internal(),最终调用mov_read_packet()读取packet。它可能将packet返回,也可能暂时保存在AVFormatContext::AVFormatInternal::packet_buffer中。在后一种情况下,下一次调用av_read_frame()会返回暂存的packet。

涉及到的类如下图:

3.video_thread()

线程video_thread负责decode,它在循环中调用get_video_frame()从packet queue中读packet,decode为frame,然后调用queue_picture()将frame推入frame queue。流程如下图:

3.1 get_video_frame()

decoder_decoder_frame()负责decode frame。packet_queue_get()从packet queue中取出packet。
在avcodec_send_packet()中,实际负责的是decode_receive_frame_internal(),它最终调用h264_decode_frame()。decode后的frame可能返回,也可能暂存在AVCodecInternal::buffer_frame中。在后一种情况下,下一次调用decode_receive_frame_internal()会直接返回暂存的frame。

3.2 queue_picture()

queue_picture()将decode得到的frame写入frame queue。frame_queue_peek_writable()得到可写的位置,frame_queue_push()将写位置推前一步。

涉及的类如下图:

4. main thread / display thread

main thread首先做初始化工作,然后调用event_loop()进入display阶段。

初始化工作包括:

  • av_register_all()注册ffmpeg的各种库,如demux,decode等。
  • parse_options()解析命令行
  • SDL_Init()初始化SDL,SDL_CreateWindow()创建SDL窗口,SDL_CreateRenderer()创建SDL渲染对象。SDL_GetRenderInfo()得到渲染对象的信息。
  • stream_open()调用frame_queue_init()初始化packet queue,调用frame_queue_init()初始化frame queue,调用init_clock()初始化同步时钟。最后调用SDL_CreateThread()创建和启动新的线程read_thread。

4.1 event_loop()

在refresh_loop_wait_event()中,再循环中最多0.01秒调用一次video_refresh()。Video_refresh()读取frame并刷新显示。
frame_queue_nb_remaining()得到frame_queue中的frame数目;
frame_queue_peek_last()和frame_queue_peek()分别得到frame queue中的最后两个frame。Frame_queue_next()将读位置推进一步。
video_display()显示frame。
调用SDL_PumpEvents()和SDL_PeepEvents()得到SDL event。这是SDL的要求,不然SDL会以为用户无动作,所以将SDL窗口变暗。SDL Event返回给refresh_loop_wait_event(),在这里event,如用户输入,会得到处理,如调整播放位置,改变音量等等。

4.2 event_loop() - video_display()

video_display()几乎只与SDL有关。

    • video_open()设置SDL窗口。
    • SDL_SetRenderDrawColor()和SDL_SetRenderClear()清理渲染器的背景。
    • video_image_display()调用frame_queue_peek_last()从frame queue中得到最后一个frame,调用calculate_display_rect()计算显示范围,调用upload_texture()创建纹理并将frame中的YUV分量,渲染到纹理中,调用SDL_RenderCopyEx()将纹理复制到渲染器。
    • SDL_RenderPresent()显示渲染器中的内容。

Guess you like

Origin www.cnblogs.com/schips/p/11525418.html