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. vmWare安装centos7之后使用yum安装提示there are on enabled repos(修改yum源)

    可以使用这个命令修改yum源 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.rep ...

  2. STM32F429的LTDC和DMA2D ***

    在阅读了STM32F429的手册之后,对LTDC与DMA2D有了一点认识. STM32F429与之前的系列强大之处就在于增加了LTDC个功能,从手册上看STM32F429的LTDC可以用于驱动1024 ...

  3. Nexus入门【转】

    介绍 DevOps平台采用的介质服务器类型为NEXUS,NEXUS是一个强大的maven仓库管理器,它极大的简化了本地内部仓库的维护和外部仓库的访问. 一.配置Maven [root@meteor ~ ...

  4. sass - &的作用

    6.8. & in SassScript 就像在选择器中使用时一样,&在SassScript中引用当前父选择器.它是一个逗号分隔的列表和空格分隔的列表.例如: .foo.bar .ba ...

  5. Kubernetes addons 之 coredns部署

    Kubernetes addons 之 coredns部署 2019.06.04 18:04:35字数 1045阅读 121 DNS 是 Kubernetes 的核心功能之一,通过 kube-dns ...

  6. 小D课堂 - 零基础入门SpringBoot2.X到实战_第7节 SpringBoot常用Starter介绍和整合模板引擎Freemaker、thymeleaf_28..SpringBoot Starter讲解

    笔记 1.SpringBoot Starter讲解     简介:介绍什么是SpringBoot Starter和主要作用 1.官网地址:https://docs.spring.io/spring-b ...

  7. iptables 配置场景3

    iptables -I INPUT -i lo -j ACCEPT    #允许本地回环地址访问: iptables -I INPUT -m state --state ESTABLISHED,REL ...

  8. 【转】暴力破解无线WiFi密码

    # coding:utf-8 import pywifi from pywifi import const import time from asyncio.tasks import sleep cl ...

  9. functools.partial偏函数的使用

    https://docs.python.org/3.6/library/functools.html 从名字可以看出,该函数的作用就是部分使用某个函数,即冻结住某个函数的某些参数,让它们保证为某个值, ...

  10. VPB测试 使用Osgdem运行例子

    1.Osgdem运行例子所需数据下载地址: http://www.cc.gatech.edu/projects/large_models/ps.html Download Elevation Map: ...