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源代码分析的更多相关文章

  1. FFplay源代码分析:整体流程图(仅供参考)

  2. 视频播放器控制原理:ffmpeg之ffplay播放器源代码分析

    版权声明:本文由张坤原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/535574001486630869 来源:腾云阁 ht ...

  3. 零基础读懂视频播放器控制原理——ffplay播放器源代码分析

    版权声明:本文由张坤原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/535574001486630869 来源:腾云阁 ht ...

  4. XBMC源代码分析 6:视频播放器(dvdplayer)-文件头(以ffmpeg为例)

    XBMC分析系列文章: XBMC源代码分析 1:整体结构以及编译方法 XBMC源代码分析 2:Addons(皮肤Skin) XBMC源代码分析 3:核心部分(core)-综述 XBMC源代码分析 4: ...

  5. XBMC源代码分析 4:视频播放器(dvdplayer)-解码器(以ffmpeg为例)

    XBMC分析系列文章: XBMC源代码分析 1:整体结构以及编译方法 XBMC源代码分析 2:Addons(皮肤Skin) XBMC源代码分析 3:核心部分(core)-综述 本文我们分析XBMC中视 ...

  6. 转:SDL2源代码分析

    1:初始化(SDL_Init()) SDL简介 有关SDL的简介在<最简单的视音频播放示例7:SDL2播放RGB/YUV>以及<最简单的视音频播放示例9:SDL2播放PCM>中 ...

  7. 转:ffdshow 源代码分析

    ffdshow神奇的功能:视频播放时显示运动矢量和QP FFDShow可以称得上是全能的解码.编码器.最初FFDShow只是mpeg视频解码器,不过现在他能做到的远不止于此.它能够解码的视频格式已经远 ...

  8. ffmpeg/ffplay vc6 源码剖析

    ffmpeg/ffplay是当今多媒体领域的王者,很多很多的人想研究学习ffmpeg/ffplay,但苦于ffmpeg/ffplay庞大的代码量,令人望而生畏.为帮助更多的人研习ffmpeg/ffpl ...

  9. SDL2源代码分析8:视频显示总结

    ===================================================== SDL源代码分析系列文章列表: SDL2源代码分析1:初始化(SDL_Init()) SDL ...

随机推荐

  1. MySQL - \g 和 \G用法与区别

    [1]DOS环境下 ① \g 可同时(单独)使用\g; 其作用等效于分号—’:’ : ② \G 可同时(单独)使用\G;; /G 的作用是将查到的结构旋转90度变成纵向:

  2. 肠道微生物研究进展 | Microbiology | Human Gut Microbiome | human gut microbiota

    之前我有过一篇16s基本概念和数据分析的文章.16S 基础知识.分析工具和分析流程详解 可以分成两部分,生物层面和技术层面. 生物层面: 1. 肠道微生物里面包含了哪些微生物?显然包含了所有层面的微生 ...

  3. 小程序运行报错navigateTo:fail page "pages/warn/warn" is not found

    在index.js中配置触发时页面转发 wx.navigateTo({ url: '../warn/warn', }) 实际上触发时报错页面找不到 原因是页面路径没有在app.json里面没有定义过, ...

  4. linux中proz下载软件安装部署

    W系统里有迅雷这个下载工具,L系统里也一样有prozilla下面说一下CENTOS 5 系统里安装prozilla的过程1.首先在下面的链接下载最新稳定版本的prozilla 记得下tar包版本的[u ...

  5. 我的一个PLSQL【我】 循环嵌套、游标使用、变量定义、查询插入表、批量提交事务、字符串截取、动态sql拼接执行

    代码块: --CREATE OR REPLACE PROCEDURE PRO_REVENUE_STATISTICS --IS DECLARE --计数器 ins_counter PLS_INTEGER ...

  6. declaration of 'int ret' shadows a parameter

    定义的变量名称重复, 例如: int look_up_max(int m, int n) {   int m; //... return m; }

  7. ES6深入浅出-4 迭代器与生成器-5.科班 V.S. 培训

    为什么要学用不到的东西 科班是把你未来一二十年用的东西都给你入个门 做前端 三年后一定要再学一门语言. 买一本图解算法 培训讲究的是技能,只能满足3到5年,而不是术,学术学的是你未来10年甚至20年用 ...

  8. 【JS】jquery展示JSON插件JSONView

    JSONView介绍 jQuery插件,用于显示漂亮的JSON. 官网地址:https://plugins.jquery.com/jsonview/ git地址:https://github.com/ ...

  9. PHP IE9 AJAX success 返回 undefined 问题解决

    jquery的AJAX返回结果为undefined,并且有“由于出现错误c00ce56e”的错误提示.这个问题是由于IE9不能解析其他编码而产生的.解决这个问题之需要按照W3C规范,声明一下编码为ut ...

  10. Pythonrandom模块(获取随机数)常用方法和使用例子

    Python random模块(获取随机数)常用方法和使用例子 这篇文章主要介绍了Python random模块(获取随机数)常用方法和使用例子,需要的朋友可以参考下 random.random ra ...