至简播放器ffplay工作原理

下载,编译及运行

参考博文 http://blog.csdn.net/ericbar/article/details/79382783 即可完成ffplay的基本测试。
如果要进行GDB调试,需要先编译一个带GDB信息的执行文件,编译前修改配置选项,

./configure --prefix=ffout --disable-stripping --disable-optimizations

保留GDB信息,以GDB方式运行ffplay,注意播放片源等参数是在运行命令 r 之后,

gdb ffplay
r 1080p.avi

main函数

C语言的代码入口在main()函数里,下面是main函数内部主要函数介绍:
init_dynload
动态加载的初始化,这是Windows平台的dll库相关处理;
av_log_set_flags
设置打印的标记,AV_LOG_SKIP_REPEATED表示对于重复打印的语句,不重复输出;
avdevice_register_all
注册音视频相关的设备,主要是和操作系统底层相关的图像,声音设备注册;
avfilter_register_all
注册音视频处理相关的资源,filter在FFmpeg里主要是做各种变换的概念;
av_register_all
注册复用/解复用相关的资源;
avformat_network_init
初始化网络资源;
init_opts
初始化选项,通过av_dict_set设置字典,字典就是key-value对键值的集合,这在JAVA等高级语言里是很常见的类,比如map,由于FFmpeg是C语言实现的,所以设立了一个字典的集合;
signal(SIGINT , sigterm_handler);
signal(SIGTERM, sigterm_handler);
注册了2个信号,分别对应程序终止(interrupt)信号和程序结束(terminate)信号,用于异常处理;
show_banner
显示FFmpeg和ffplay程序相关的库版本等信息,类似如下:

ffplay version 3.4.2 Copyright (c) 2003-2018 the FFmpeg developers
  built with gcc 5.4.0 (Ubuntu 5.4.0-6ubuntu1~16.04.4) 20160609
  configuration: --prefix=ffout --disable-stripping
  libavutil      55. 78.100 / 55. 78.100
  libavcodec     57.107.100 / 57.107.100
  libavformat    57. 83.100 / 57. 83.100
  libavdevice    57. 10.100 / 57. 10.100
  libavfilter     6.107.100 /  6.107.100
  libswscale      4.  8.100 /  4.  8.100
  libswresample   2.  9.100 /  2.  9.100

parse_options
解析ffplay程序启动的所有命令行参数,并存储到对应的变量(options有默认值),如获取要播放的媒体文件名,通过注册的回调函数opt_input_file,将最终的媒体文件名存储在变量input_filename中;
display_disable
video_disable
表示是否关闭视频显示;
audio_disable
表示是否关闭音频的播放
以上两个参数决定了SDL初始化时的flag标记;
SDL_Init
SDL初始化,完成显示和音频的初始化;
av_lockmgr_register
注册解码和解复用两部分的互斥量,这是一个平台适配接口,满足互斥量的实现;
av_init_packet
初始化flush_pkt结构体,flush_pkt作为连续包队列中的标记,用于判断是否连续的包;
SDL_CreateWindow
SDL_CreateRenderer
创建视频显示的图层,SDL_GetRendererInfo获取相关画布信息;
stream_open
打开媒体文件播放,所有核心的播放流程代码都由此函数实现;
event_loop
ffplay播放程序的按键和事件处理,包括快进快退,暂停,退出等;

stream_open函数

VideoState
在函数入口,将全局信息存储在此结构体中,类似于上下文context;
frame_queue_init
初始化了3个帧队列,分别保存解码后的视频,音频以及字幕;
packet_queue_init
初始化了3个包队列,分别存储解码前的视频包,音频包以及字幕包;
init_clock
完成视频,音频以及外部时钟的初始化;
startup_volume
起播时的音量值,需要映射到SDL的音量范围;
av_sync_type
为音视频同步的类型,一般以音频时钟为同步基准;
SDL_CreateThread
SDL创建线程的函数,核心工作在read_thread线程中完成;

read_thread函数

avformat_alloc_context
分配一个avformat的上下文,context中的interrupt_callback是注册到底层的回调函数,主要用于中断退出,比如退出网络阻塞。退出的条件由decode_interrupt_cb函数实现。
avformat_open_input
打开播放文件,得到context相关信息;
avformat_find_stream_info
分析媒体流的相关信息;
start_time变量
起播时跳转到的时间戳位置;
realtime变量
是否是实时播放的节目,比如rtsp,udp,rtp节目;
av_dump_format
显示媒体格式相关的内容,主要由dump_metadata和dump_stream_format函数完成,类似如下格式打印;

Input #0, avi, from '../1080p.avi':0KB vq=    0KB sq=    0B f=0/0   
  Metadata:
    encoder         : Lavf52.64.2
  Duration: 00:00:40.20, start: 0.000000, bitrate: 8204 kb/s
    Stream #0:0: Video: h264 (Main) (H264 / 0x34363248), yuv420p(progressive), 1920x1080 [SAR 1:1 DAR 16:9], 7918 kb/s, 23.98 fps, 23.98 tbr, 23.98 tbn, 47.96 tbc
Stream #0:1: Audio: mp3 (U[0][0][0] / 0x0055), 48000 Hz, stereo, s16p, 320 kb/s

av_find_best_stream
查找适合的流进行播放;
av_guess_sample_aspect_ratio
获取待解码视频的宽高比,从而得到视频窗口的初始大小;
stream_component_open
打开视频,音频和字幕流;

if (is->paused != is->last_paused) {
            is->last_paused = is->paused;
            if (is->paused)
                is->read_pause_return = av_read_pause(ic);
            else
                av_read_play(ic);
        }

暂停状态如果发生变化,执行暂停或者恢复播放;
if (is->seek_req)
如有seek请求,则执行seek,avformat_seek_file定位到目标位置,并清除解码前的数据队列,packet_queue_flush清除队列,packet_queue_put存储一个flush标记包flush_pkt;
stream_has_enough_packets
读取队列中是否还有足够的数据包,如果已经足够,本轮循环延迟10毫秒;
loop变量
表示循环播放的次数,播放到头时,重新通过stream_seek函数定位到start_time的位置;
av_read_frame
读取一个数据包(解码前),这是数据读取的核心函数,根据包的类型(视频,音频,字幕),通过packet_queue_put函数将读取到的包,存储到队列里;

fail:
    if (ic && !is->ic)
        avformat_close_input(&ic);

    if (ret != 0) {
        SDL_Event event;

        event.type = FF_QUIT_EVENT;
        event.user.data1 = is;
        SDL_PushEvent(&event);
    }
SDL_DestroyMutex(wait_mutex);

失败时,关闭文件播放上下文,退出SDL事件管理,销毁互斥量。

小结

上述过程,很好的解释了怎样打开一个媒体文件,然后怎样进行数据包的读取,那么解码以及显示的工作在哪里完成呢?其实是在stream_component_open函数中完成的,此函数根据不同的流类型,最后通过decoder_start各自启动一个线程负责视频,音频及字幕的解码。这部分我们放到后面讨论。

主要函数流图

下面是ffplay的主要函数调用关系图
ffplay主要函数关系图
从上图可以看出,整个程序有如下4个线程:
read_thread
读数据包线程,主要是通过函数av_read_frame从媒体文件中读取包;
audio_thread
video_thread
subtitle_thread
上述3个线程分别负责音频,视频,字幕的解码;
event_loop
其实我们也可以理解为一个线程循环,这是主线程的while循环函数,除了顾名思义做事件相关的处理外,其实它还通过refresh_loop_wait_event函数中的video_refresh函数,完成视频显示和刷新。视频显示的时候,需要参考PTS时钟信息进行同步。
而音频的播放,由于音频数据的读取是由 SDL音频管理器自动进行的,sdl_audio_callback函数由SDL主动回调并请求数据,我们只要在此回调函数里完成音频数据的解码,并注入SDL音频buffer即可。

总结

通过前面的介绍,大概了解了ffplay播放的主线,介绍了播放器打开文件,读取数据包,解码和显示的主要函数,但是由于每一块功能的实现原理都很复杂,并有很多基本理论需要了解,所以我们将在接下来的文章中,逐一展开分析,最终达到完整的掌握FFmpeg基本原理和实现机制的目的。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值