码迷,mamicode.com
首页 > 其他好文 > 详细

FFMPEG - ffplay源代码分析

时间:2019-09-16 09:23:41      阅读:90      评论:0      收藏:0      [点我收藏+]

标签:重要   scale   ogr   ext3   video   清理   container   基于   keyword   

FFmpeg是一个开源,免费,跨平台的视频和音频流方案,它提供了一套完整的录制、转换以及流化音视频的解决方案。而ffplay是有ffmpeg官方提供的一个基于ffmpeg的简单播放器。学习ffplay对于播放器流程、ffmpeg的调用等等是一个非常好的例子。

 

1.例子

这里的说明使用如下的例子:

./ffplay avm.mp4

2. read_thread()

线程read_thread负责demux,它的流程如下图:

技术图片
  • avformat_alloc_context分配AVFormatContext。这是demux的上下文;
  • avformat_open_input()解析文件,确定文件的封装格式(即mux类型);
  • aformat_find_stream_info()继续解析AVFormatContext中包含的stream,根据stream类型确定其decoder,并创建AVCodecContext,这是decode的上下文;
  • 如果指定了播放位置,avformat_seek_file()将播放位置移动到指定位置;
  • av_find_best_stream()查找指定stream类型的最佳质量的stream。
  • stream_component_open()创建新的线程video_thread。Video_thread负责decode。
  • 最后,read_thread在循环中,调用av_read_frame()读packet,并调用packet_queue_put(),放入queue中,供video_thread读取。

2.1 avformat_alloc_context()

avformat_alloc_context调用avformat_get_context_defaults()给AVFormatContext设置缺省参数,其中包括AVFormtContext::io_open()和AVFormatContext::io_close()。

技术图片

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

avformat_open_input()调用av_probe_input_format2()确定文件的封装格式,这点后面再提。这里先看io_open_default()如何打开要播放的链接。有两个层次的Context:AVIOContext和URLContext。ffio_xxx()负责IO层,ffurl_xxx()负责URL层。

技术图片
  • url_find_protocol()首先提取播放链接的前缀。如http://.../a.mp4,前缀就是http。这里只有文件名avm.mp4,没有前缀,则默认为“file”,所以avm.mp4与file:avm.mp4是等同的。
  • ffurl_get_protocols得到已注册的URL protocol列表。url_find_protocol()根据前缀在这个列表中查找。前缀”file”对应的是ff_file_protocol。
  • ffurl_alloc()调用url_alloc_for_protocol()分配相应的URLContext。URLContext保存真正的文件句柄fd。
  • ffurl_connect()调用file_open()打开文件,调用file_seek()将开始位置调整到0。
  • ffio_fdopen()调用avio_alloc_context()分配IOContext,然后初始化它。

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

技术图片

这里的重要函数是av_probe_input_format3()。它根据文件名及文件内容确定文件封装格式。它有两种工作模式,根据参数is_opened值决定,is_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()显示渲染器中的内容。

FFMPEG - ffplay源代码分析

标签:重要   scale   ogr   ext3   video   清理   container   基于   keyword   

原文地址:https://www.cnblogs.com/schips/p/11525418.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!