最近重温了一下FFplay的源代码。FFplay是FFmpeg项目提供的播放器示例。尽管FFplay只是一个简单的播放器示例,它的源代码的量也是不少的。之前看代码,主要是集中于某一个“点”进行研究,而没有从总体结构上进行分析。本文就打算弥补之前学习的不足,从总体结构上分析一下FFplay的源代码,画图理一下它的结构。其中还有诸多不足,以后有机会慢慢完善。
说明一下自己画的结构图的规则:图中仅画出了比较重要的函数之间的调用关系。粉红色的函数是FFmpeg编解码类库(libavcodec,libavformat等)的API。紫色的函数是SDL的API。其他不算很重要的函数就不再列出了。
在看ffplay.c的代码之前,最好先看一下简单的代码了解FFmpeg播放一个视频的核心代码:
100行代码实现最简单的基于FFMPEG+SDL的视频播放器
总体结构图
FFplay的总体函数调用结构图如下图所示。
上图所示本是一张高清大图。但是页面显示不下。因此上传了一份:
http://my.csdn.net/leixiaohua1020/album/detail/1788077
上面地址的那张图保存下来的话就是一张清晰的图片了。
下文对主要函数分别解析。
main()
main()是FFplay的主函数。
调用了如下函数
av_register_all():注册所有编码器和解码器。
show_banner():打印输出FFmpeg版本信息(编译时间,编译选项,类库信息等)。
parse_options():解析输入的命令。
SDL_Init():SDL初始化。
stream_open ():打开输入媒体。
event_loop():处理各种消息,不停地循环下去。
下图红框中的内容即为show_banner()的输出结果。
parse_options()
parse_options()解析全部输入选项。即将输入命令“ffplay -f h264 test.264”中的“-f”这样的命令解析出来。其函数调用结构如下图所示。需要注意的是,FFplay(ffplay.c)的parse_options()和FFmpeg(ffmpeg.c)中的parse_options()实际上是一样的。因此本部分的内容和《ffmpeg.c函数结构简单分析(画图)》中的parse_options()有很多重复的地方。
parse_options()调用了如下函数:
parse_option():解析一个输入选项。具体的解析步骤不再赘述。parse_options()会循环调用parse_option()直到所有选项解析完毕。FFmpeg的每一个选项信息存储在一个OptionDef结构体中。定义如下:
- typedef struct OptionDef {
- const char *name;
- int flags;
- #define HAS_ARG 0x0001
- #define OPT_BOOL 0x0002
- #define OPT_EXPERT 0x0004
- #define OPT_STRING 0x0008
- #define OPT_VIDEO 0x0010
- #define OPT_AUDIO 0x0020
- #define OPT_INT 0x0080
- #define OPT_FLOAT 0x0100
- #define OPT_SUBTITLE 0x0200
- #define OPT_INT64 0x0400
- #define OPT_EXIT 0x0800
- #define OPT_DATA 0x1000
- #define OPT_PERFILE 0x2000 /* the option is per-file (currently ffmpeg-only).
- implied by OPT_OFFSET or OPT_SPEC */
- #define OPT_OFFSET 0x4000 /* option is specified as an offset in a passed optctx */
- #define OPT_SPEC 0x8000 /* option is to be stored in an array of SpecifierOpt.
- Implies OPT_OFFSET. Next element after the offset is
- an int containing element count in the array. */
- #define OPT_TIME 0x10000
- #define OPT_DOUBLE 0x20000
- union {
- void *dst_ptr;
- int (*func_arg)(void *, const char *, const char *);
- size_t off;
- } u;
- const char *help;
- const char *argname;
- } OptionDef;
其中的重要字段:
name:用于存储选项的名称。例如“i”,“f”,“codec”等等。
flags:存储选项值的类型。例如:HAS_ARG(包含选项值),OPT_STRING(选项值为字符串类型),OPT_TIME(选项值为时间类型。
u:存储该选项的处理函数。
help:选项的说明信息。
FFmpeg使用一个名称为options,类型为OptionDef的数组存储所有的选项。有一部分通用选项存储在cmdutils_common_opts.h中。这些选项对于FFmpeg,FFplay以及FFprobe都试用。
cmdutils_common_opts.h内容如下:
- { "L" , OPT_EXIT, {(void*)show_license}, "show license" },
- { "h" , OPT_EXIT, {(void*) show_help}, "show help", "topic" },
- { "?" , OPT_EXIT, {(void*)show_help}, "show help", "topic" },
- { "help" , OPT_EXIT, {(void*)show_help}, "show help", "topic" },
- { "-help" , OPT_EXIT, {(void*)show_help}, "show help", "topic" },
- { "version" , OPT_EXIT, {(void*)show_version}, "show version" },
- { "formats" , OPT_EXIT, {(void*)show_formats }, "show available formats" },
- { "codecs" , OPT_EXIT, {(void*)show_codecs }, "show available codecs" },
- { "decoders" , OPT_EXIT, {(void*)show_decoders }, "show available decoders" },
- { "encoders" , OPT_EXIT, {(void*)show_encoders }, "show available encoders" },
- { "bsfs" , OPT_EXIT, {(void*)show_bsfs }, "show available bit stream filters" },
- { "protocols" , OPT_EXIT, {(void*)show_protocols}, "show available protocols" },
- { "filters" , OPT_EXIT, {(void*)show_filters }, "show available filters" },
- { "pix_fmts" , OPT_EXIT, {(void*)show_pix_fmts }, "show available pixel formats" },
- { "layouts" , OPT_EXIT, {(void*)show_layouts }, "show standard channel layouts" },
- { "sample_fmts", OPT_EXIT, {(void*)show_sample_fmts }, "show available audio sample formats" },
- { "loglevel" , HAS_ARG, {(void*)opt_loglevel}, "set libav* logging level", "loglevel" },
- { "v", HAS_ARG, {(void*)opt_loglevel}, "set libav* logging level", "loglevel" },
- { "debug" , HAS_ARG, {(void*)opt_codec_debug}, "set debug flags", "flags" },
- { "fdebug" , HAS_ARG, {(void*)opt_codec_debug}, "set debug flags", "flags" },
- { "report" , 0, {(void*)opt_report}, "generate a report" },
- { "max_alloc" , HAS_ARG, {(void*) opt_max_alloc}, "set maximum size of a single allocated block", "bytes" },
- { "cpuflags" , HAS_ARG | OPT_EXPERT, {(void*) opt_cpuflags}, "force specific cpu flags", "flags" },
options数组的定义位于ffplay.c中,如下所示:
- static const OptionDef options[] = {
- #include "cmdutils_common_opts.h"//包含进来
- { "x", HAS_ARG, { (void*) opt_width }, "force displayed width", "width" },
- { "y", HAS_ARG, { (void*) opt_height }, "force displayed height", "height" },
- { "s", HAS_ARG | OPT_VIDEO, { (void*) opt_frame_size }, "set frame size (WxH or abbreviation)", "size" },
- { "fs", OPT_BOOL, { &is_full_screen }, "force full screen" },
- { "an", OPT_BOOL, { &audio_disable }, "disable audio" },
- { "vn", OPT_BOOL, { &video_disable }, "disable video" },
- { "ast", OPT_INT | HAS_ARG | OPT_EXPERT, { &wanted_stream[AVMEDIA_TYPE_AUDIO] }, "select desired audio stream", "stream_number" },
- { "vst", OPT_INT | HAS_ARG | OPT_EXPERT, { &wanted_stream[AVMEDIA_TYPE_VIDEO] }, "select desired video stream", "stream_number" },
- { "sst", OPT_INT | HAS_ARG | OPT_EXPERT, { &wanted_stream[AVMEDIA_TYPE_SUBTITLE] }, "select desired subtitle stream", "stream_number" },
- { "ss", HAS_ARG, { (void*) opt_seek }, "seek to a given position in seconds", "pos" },
- { "t", HAS_ARG, { (void*) opt_duration }, "play \"duration\" seconds of audio/video", "duration" },
- //选项众多,不再一一列出…
- };
选项众多,简单举几个例子:
强行设置设置屏幕的宽度选项(“-x”选项):
- { "x", HAS_ARG, { (void*) opt_width }, "force displayed width", "width" }
从代码中可以看出,“-x”选项包含选项值(HAS_ARG),选项处理函数是opt_width()。选项说明是"force displayed width"。opt_width()的内容如下:
- static int opt_width(void *optctx, const char *opt, const char *arg)
- {
- screen_width = parse_number_or_die(opt, arg, OPT_INT64, 1, INT_MAX);
- return 0;
- }
可以看出其作用是解析输入的字符串为整数并赋值给全局变量screen_width。
全屏(“-fs”选项)
- { "fs", OPT_BOOL, { &is_full_screen }, "force full screen" }
从代码中可以看出,“-fs”选项包含布尔型选项值(OPT_BOOL),并绑定了全局变量is_full_screen。选项说明是"force full screen"。
SDL_Init()
SDL_Init()用于初始化SDL。FFplay中视频的显示和声音的播放都用到了SDL。
stream_open()
stream_open()的作用是打开输入的媒体。这个函数还是比较复杂的,包含了FFplay中各种线程的创建。它的函数调用结构如下图所示。
stream_open()调用了如下函数:
packet_queue_init():初始化各个PacketQueue(视频/音频/字幕)
read_thread():读取媒体信息线程。
read_thread()
read_thread()调用了如下函数:
avformat_open_input():打开媒体。
avformat_find_stream_info():获得媒体信息。
av_dump_format():输出媒体信息到控制台。
stream_component_open():分别打开视频/音频/字幕解码线程。
refresh_thread():视频刷新线程。
av_read_frame():获取一帧压缩编码数据(即一个AVPacket)。
packet_queue_put():根据压缩编码数据类型的不同(视频/音频/字幕),放到不同的PacketQueue中。
refresh_thread()
refresh_thread()调用了如下函数:
SDL_PushEvent(FF_REFRESH_EVENT):发送FF_REFRESH_EVENT的SDL_Event
av_usleep():每两次发送之间,间隔一段时间。
stream_component_open()
stream_component_open()用于打开视频/音频/字幕解码的线程。其函数调用关系如下图所示。
stream_component_open()调用了如下函数:
avcodec_find_decoder():获得解码器。
avcodec_open2():打开解码器。
audio_open():打开音频解码。
SDL_PauseAudio(0):SDL中播放音频的函数。
video_thread():创建视频解码线程。
subtitle_thread():创建字幕解码线程。
packet_queue_start():初始化PacketQueue。
audio_open()调用了如下函数
SDL_OpenAudio():SDL中打开音频设备的函数。注意它是根据SDL_AudioSpec参数打开音频设备。SDL_AudioSpec中的callback字段指定了音频播放的回调函数sdl_audio_callback()。当音频设备需要更多数据的时候,会调用该回调函数。因此该函数是会被反复调用的。
下面来看一下SDL_AudioSpec中指定的回调函数sdl_audio_callback()。
sdl_audio_callback()调用了如下函数
audio_decode_frame():解码音频数据。
update_sample_display():当不显示视频图像,而是显示音频波形的时候,调用此函数。
audio_decode_frame()调用了如下函数
packet_queue_get():获取音频压缩编码数据(一个AVPacket)。
avcodec_decode_audio4():解码音频压缩编码数据(得到一个AVFrame)。
swr_init():初始化libswresample中的SwrContext。libswresample用于音频采样采样数据(PCM)的转换。
swr_convert():转换音频采样率到适合系统播放的格式。
swr_free():释放SwrContext。
video_thread()调用了如下函数
avcodec_alloc_frame():初始化一个AVFrame。
get_video_frame():获取一个存储解码后数据的AVFrame。
queue_picture():
get_video_frame()调用了如下函数
packet_queue_get():获取视频压缩编码数据(一个AVPacket)。
avcodec_decode_video2():解码视频压缩编码数据(得到一个AVFrame)。
queue_picture()调用了如下函数
SDL_LockYUVOverlay():锁定一个SDL_Overlay。
sws_getCachedContext():初始化libswscale中的SwsContext。Libswscale用于图像的Raw格式数据(YUV,RGB)之间的转换。注意sws_getCachedContext()和sws_getContext()功能是一致的。
sws_scale():转换图像数据到适合系统播放的格式。
SDL_UnlockYUVOverlay():解锁一个SDL_Overlay。
subtitle_thread()调用了如下函数
packet_queue_get():获取字幕压缩编码数据(一个AVPacket)。
avcodec_decode_subtitle2():解码字幕压缩编码数据。
event_loop()
FFplay再打开媒体之后,便会进入event_loop()函数,永远不停的循环下去。该函数用于接收并处理各种各样的消息。有点像Windows的消息循环机制。
PS:该循环确实是无止尽的,其形式为如下
- SDL_Event event;
- for (;;) {
- SDL_WaitEvent(&event);
- switch (event.type) {
- case SDLK_ESCAPE:
- case SDLK_q:
- do_exit(cur_stream);
- break;
- case SDLK_f:
- …
- …
- }
- }
event_loop()函数调用关系如下所示。
根据event_loop()中SDL_WaitEvent()接收到的SDL_Event类型的不同,会调用不同的函数进行处理(从编程的角度来说就是一个switch()语法)。图中仅仅列举了几个例子:
SDLK_ESCAPE(按下“ESC”键):do_exit()。退出程序。
SDLK_f(按下“f”键):toggle_full_screen()。切换全屏显示。
SDLK_SPACE(按下“空格”键):toggle_pause()。切换“暂停”。
SDLK_DOWN(按下鼠标键):stream_seek()。跳转到指定的时间点播放。
SDL_VIDEORESIZE(窗口大小发生变化):SDL_SetVideoMode()。重新设置宽高。
FF_REFRESH_EVENT(视频刷新事件(自定义事件)):video_refresh()。刷新视频。
下面分析一下do_exit()函数。该函数用于退出程序。函数的调用关系如下图所示。
do_exit()函数调用了以下函数
stream_close():关闭打开的媒体。
SDL_Quit():关闭SDL。
stream_close()函数调用了以下函数
packet_queue_destroy():释放PacketQueue。
SDL_FreeYUVOverlay():释放SDL_Overlay。
sws_freeContext():释放SwsContext。
下面重点分析video_refresh()函数。该函数用于将图像显示到显示器上。函数的调用关系如下图所示。
video_refresh()函数调用了以下函数
video_display():显示像素数据到屏幕上。
show_status:这算不上是一个函数,但是是一个独立的功能模块,因此列了出来。该部分打印输出播放的状态至屏幕上。如下图所示。
video_display()函数调用了以下函数
video_open():初始化的时候调用,打开播放窗口。
video_audio_display():显示音频波形图(或者频谱图)的时候调用。里面包含了不少画图操作。
video_image_display():显示视频画面的时候调用。
video_open()函数调用了以下函数
SDL_SetVideoMode():设置SDL_Surface(即SDL最基础的黑色的框)的大小等信息。
SDL_WM_SetCaption():设置SDL_Surface对应窗口的标题文字。
video_audio_display()函数调用了以下函数
SDL_MapRGB():获得指定(R,G,B)以及SDL_PixelFormat的颜色数值。例如获得黑色的值,作为背景。(R,G,B)为(0x00,0x00,0x00)。
fill_rectangle():将指定颜色显示到屏幕上。
SDL_UpdateRect():更新屏幕。
video_image_display()函数调用了以下函数
calculate_display_rect():计算显示画面的位置。当拉伸了SDL的窗口的时候,可以让其中的视频保持纵横比。
SDL_DisplayYUVOverlay():显示画面至屏幕。
转自:http://blog.csdn.net/leixiaohua1020/article/details/39762143
相关推荐
"ffplay精简版VC6.0平台c源码"是一个基于FFmpeg的简单播放器项目,特别针对Visual C++ 6.0这个古老的开发环境进行了优化和简化。 ffplay 是 FFmpeg 项目中的一个小型命令行播放器,它用C语言编写,展示了如何直接...
ffplay则是基于这些库构建的一个简单媒体播放器,它展示了如何使用FFmpeg进行实时播放。 在提供的资源中,我们有ffplay目录,这是ffplay播放器的源代码,你可以在这里看到FFmpeg如何与SDL(Simple DirectMedia ...
雷神总结的关于ffplay.c的函数调用结构流程图,相当清晰
ffplay是一个简单的播放器,基于SDL库实现,它可以帮助我们理解多媒体播放的基本原理。本压缩包"ffplay源码整理可单独编译.zip"提供了一个经过整理的ffplay源代码,能够独立于整个FFmpeg项目进行编译,这对于学习和...
FFplay是FFmpeg项目中的一个简单易用的多媒体播放器,其源代码相对简洁,是了解FFmpeg框架的一个良好切入点。杨书良所著的《FFmpeg/FFplay源码剖析》一书,对FFmpeg及FFplay的源码进行了深入分析,是学习和研究这一...
5. ffplay作为FFmpeg提供的一个简单的视频播放器,是研究和学习FFmpeg的好例子。文档中特别对ffplay的源码进行了剖析,包括其播放流程和主要改动点。ffplay通过解复用、解码以及使用SDL库显示视频和播放音频,展示了...
ffplay的PDF说明文档通常会包含关于如何编译和运行ffplay的指导,以及源代码的结构和关键函数的解释。这对于初学者来说,是理解FFmpeg和SDL工作原理的好起点。你可以通过阅读这份文档,了解到ffplay的初始化流程、...
FFplay是FFmpeg项目中的一个简易播放器示例,用于展示如何使用FFmpeg库来播放媒体文件。本教程将深入剖析FFmpeg及其相关组件的源码,以帮助开发者更好地理解和使用这个强大的多媒体处理工具。 ### 第一部分:FFMPEG...
11.5 文件结构分析 438 11.5.1 File Type Box(ftyp) 438 11.5.2 Movie Box(moov) 438 第十二章 flv 文件格式分析 457 12.1 概述 457 12.2 文件总体结构 457 12.3 文件结构分析 458 12.3.1 flv文件头的结构 458 ...
11.5 文件结构分析 418 11.5.1 File Type Box(ftyp) 418 11.5.2 Movie Box(moov) 418 第十二章 flv 文件格式分析 437 12.1 概述 437 12.2 文件总体结构 437 12.3 文件结构分析 438 12.3.1 flv 文件头的结构 438 ...
第五章是直接对ffplay的剖析,它是基于前面提到的库构建的,这里详细分析了ffplay.c文件,这是ffplay播放器的主要实现文件。 文章还提到了一些可能由于OCR扫描导致的错误或遗漏,如“感恩的心,感谢生命中的每一个...
ffplay 是一个基于 ffmpeg 库构建的简易多媒体播放器,能够播放多种格式的音频和视频文件。其设计简洁高效,是学习多媒体处理技术的良好实例。 #### 1.2 播放器一般原理 多媒体播放器通常包含以下几个核心部分: -...
3. ffprobe:用于获取多媒体文件的元数据信息,帮助分析文件结构。 4. libavcodec:音频和视频编码库,提供了丰富的编码算法。 5. libavformat:多媒体容器格式的解析和生成库。 6. libavfilter:滤镜库,允许对视频...
FFmpeg是一款强大的开源多媒体处理工具,它包含了多个子项目,如ffmpeg用于命令行音视频转换,ffplay是一个轻量级的媒体播放器,而ffprobe则用于分析多媒体容器和流信息。本文将深入探讨杨书良对ffplay的精简版本...
`ffplay.c`是FFmpeg自带的简单媒体播放器源代码,它基于SDL库实现。用户可以通过编译这个源文件来创建一个基本的媒体播放程序,可以播放本地和网络流媒体。 `cmdutils.c`包含了一系列与命令行接口相关的函数,这些...
这些头文件(通常扩展名为 .h)包含了函数声明和数据结构定义,让开发者能在他们的项目中使用 FFmpeg API 进行编程。而库文件(LIB/DLL)则包含了预编译的 FFmpeg 函数实现,供应用程序链接使用,以便调用 FFmpeg 的...