`
chriszeng87
  • 浏览: 738592 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

ffplay.c函数结构简单分析

阅读更多

最近重温了一下FFplay的源代码。FFplay是FFmpeg项目提供的播放器示例。尽管FFplay只是一个简单的播放器示例,它的源代码的量也是不少的。之前看代码,主要是集中于某一个“点”进行研究,而没有从总体结构上进行分析。本文就打算弥补之前学习的不足,从总体结构上分析一下FFplay的源代码,画图理一下它的结构。其中还有诸多不足,以后有机会慢慢完善。
说明一下自己画的结构图的规则:图中仅画出了比较重要的函数之间的调用关系。粉红色的函数是FFmpeg编解码类库(libavcodec,libavformat等)的API。紫色的函数是SDL的API。其他不算很重要的函数就不再列出了。
在看ffplay.c的代码之前,最好先看一下简单的代码了解FFmpeg播放一个视频的核心代码:

100行代码实现最简单的基于FFMPEG+SDL的视频播放器

最简单的基于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结构体中。定义如下:

[cpp] view plaincopy
 
  1. typedef struct OptionDef {  
  2.     const char *name;  
  3.     int flags;  
  4. #define HAS_ARG    0x0001  
  5. #define OPT_BOOL   0x0002  
  6. #define OPT_EXPERT 0x0004  
  7. #define OPT_STRING 0x0008  
  8. #define OPT_VIDEO  0x0010  
  9. #define OPT_AUDIO  0x0020  
  10. #define OPT_INT    0x0080  
  11. #define OPT_FLOAT  0x0100  
  12. #define OPT_SUBTITLE 0x0200  
  13. #define OPT_INT64  0x0400  
  14. #define OPT_EXIT   0x0800  
  15. #define OPT_DATA   0x1000  
  16. #define OPT_PERFILE  0x2000     /* the option is per-file (currently ffmpeg-only).  
  17.          implied by OPT_OFFSET or OPT_SPEC */  
  18. #define OPT_OFFSET 0x4000       /* option is specified as an offset in a passed optctx */  
  19. #define OPT_SPEC   0x8000       /* option is to be stored in an array of SpecifierOpt.  
  20.          Implies OPT_OFFSET. Next element after the offset is  
  21.          an int containing element count in the array. */  
  22. #define OPT_TIME  0x10000  
  23. #define OPT_DOUBLE 0x20000  
  24.      union {  
  25.         void *dst_ptr;  
  26.         int (*func_arg)(void *, const char *, const char *);  
  27.         size_t off;  
  28.     } u;  
  29.     const char *help;  
  30.     const char *argname;  
  31. } 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内容如下:

[cpp] view plaincopy
 
  1. "L"          , OPT_EXIT, {(void*)show_license},      "show license" },  
  2.    { "h"          , OPT_EXIT, {(void*) show_help},         "show help""topic" },  
  3.    { "?"          , OPT_EXIT, {(void*)show_help},         "show help""topic" },  
  4.    { "help"       , OPT_EXIT, {(void*)show_help},         "show help""topic" },  
  5.    { "-help"      , OPT_EXIT, {(void*)show_help},         "show help""topic" },  
  6.    { "version"    , OPT_EXIT, {(void*)show_version},      "show version" },  
  7.    { "formats"    , OPT_EXIT, {(void*)show_formats  },    "show available formats" },  
  8.    { "codecs"     , OPT_EXIT, {(void*)show_codecs   },    "show available codecs" },  
  9.    { "decoders"   , OPT_EXIT, {(void*)show_decoders },    "show available decoders" },  
  10.    { "encoders"   , OPT_EXIT, {(void*)show_encoders },    "show available encoders" },  
  11.    { "bsfs"       , OPT_EXIT, {(void*)show_bsfs     },    "show available bit stream filters" },  
  12.    { "protocols"  , OPT_EXIT, {(void*)show_protocols},    "show available protocols" },  
  13.    { "filters"    , OPT_EXIT, {(void*)show_filters  },    "show available filters" },  
  14.    { "pix_fmts"   , OPT_EXIT, {(void*)show_pix_fmts },    "show available pixel formats" },  
  15.    { "layouts"    , OPT_EXIT, {(void*)show_layouts  },    "show standard channel layouts" },  
  16.    { "sample_fmts", OPT_EXIT, {(void*)show_sample_fmts }, "show available audio sample formats" },  
  17.    { "loglevel"   , HAS_ARG,  {(void*)opt_loglevel},      "set libav* logging level""loglevel" },  
  18.    { "v",           HAS_ARG,  {(void*)opt_loglevel},      "set libav* logging level""loglevel" },  
  19.    { "debug"      , HAS_ARG,  {(void*)opt_codec_debug},   "set debug flags""flags" },  
  20.    { "fdebug"     , HAS_ARG,  {(void*)opt_codec_debug},   "set debug flags""flags" },  
  21.    { "report"     , 0,        {(void*)opt_report}, "generate a report" },  
  22.    { "max_alloc"  , HAS_ARG,  {(void*) opt_max_alloc},     "set maximum size of a single allocated block""bytes" },  
  23.    { "cpuflags"   , HAS_ARG | OPT_EXPERT, {(void*) opt_cpuflags}, "force specific cpu flags""flags" },  


options数组的定义位于ffplay.c中,如下所示:

[cpp] view plaincopy
 
  1. static const OptionDef options[] = {  
  2. #include "cmdutils_common_opts.h"//包含进来  
  3.     { "x", HAS_ARG, { (void*) opt_width }, "force displayed width""width" },  
  4.     { "y", HAS_ARG, { (void*) opt_height }, "force displayed height""height" },  
  5.     { "s", HAS_ARG | OPT_VIDEO, { (void*) opt_frame_size }, "set frame size (WxH or abbreviation)""size" },  
  6.     { "fs", OPT_BOOL, { &is_full_screen }, "force full screen" },  
  7.     { "an", OPT_BOOL, { &audio_disable }, "disable audio" },  
  8.     { "vn", OPT_BOOL, { &video_disable }, "disable video" },  
  9.     { "ast", OPT_INT | HAS_ARG | OPT_EXPERT, { &wanted_stream[AVMEDIA_TYPE_AUDIO] }, "select desired audio stream""stream_number" },  
  10.     { "vst", OPT_INT | HAS_ARG | OPT_EXPERT, { &wanted_stream[AVMEDIA_TYPE_VIDEO] }, "select desired video stream""stream_number" },  
  11.     { "sst", OPT_INT | HAS_ARG | OPT_EXPERT, { &wanted_stream[AVMEDIA_TYPE_SUBTITLE] }, "select desired subtitle stream""stream_number" },  
  12.     { "ss", HAS_ARG, { (void*) opt_seek }, "seek to a given position in seconds""pos" },  
  13.     { "t", HAS_ARG, { (void*) opt_duration }, "play  \"duration\" seconds of audio/video""duration" },  
  14.     //选项众多,不再一一列出…  
  15. };  



选项众多,简单举几个例子:
强行设置设置屏幕的宽度选项(“-x”选项):

[cpp] view plaincopy
 
  1. "x", HAS_ARG, { (void*) opt_width }, "force displayed width""width" }  

从代码中可以看出,“-x”选项包含选项值(HAS_ARG),选项处理函数是opt_width()。选项说明是"force displayed width"。opt_width()的内容如下:

[cpp] view plaincopy
 
  1. static int opt_width(void *optctx, const char *opt, const char *arg)  
  2. {  
  3.     screen_width = parse_number_or_die(opt, arg, OPT_INT64, 1, INT_MAX);  
  4.     return 0;  
  5. }  


可以看出其作用是解析输入的字符串为整数并赋值给全局变量screen_width。


全屏(“-fs”选项)

[cpp] view plaincopy
 
  1. "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:该循环确实是无止尽的,其形式为如下

[cpp] view plaincopy
 
  1. SDL_Event event;  
  2. for (;;) {  
  3.     SDL_WaitEvent(&event);  
  4.         switch (event.type) {  
  5.         case SDLK_ESCAPE:  
  6.         case SDLK_q:  
  7.                 do_exit(cur_stream);  
  8.                 break;  
  9.         case SDLK_f:  
  10.         …  
  11.         …  
  12.         }  
  13. }  



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源码

    "ffplay精简版VC6.0平台c源码"是一个基于FFmpeg的简单播放器项目,特别针对Visual C++ 6.0这个古老的开发环境进行了优化和简化。 ffplay 是 FFmpeg 项目中的一个小型命令行播放器,它用C语言编写,展示了如何直接...

    ffmpeg/ffplay 源码解析 最完整的全套学习资源 源码 可直接编译

    ffplay则是基于这些库构建的一个简单媒体播放器,它展示了如何使用FFmpeg进行实时播放。 在提供的资源中,我们有ffplay目录,这是ffplay播放器的源代码,你可以在这里看到FFmpeg如何与SDL(Simple DirectMedia ...

    ffplay源码解析图

    雷神总结的关于ffplay.c的函数调用结构流程图,相当清晰

    ffplay源码整理可单独编译.zip

    ffplay是一个简单的播放器,基于SDL库实现,它可以帮助我们理解多媒体播放的基本原理。本压缩包"ffplay源码整理可单独编译.zip"提供了一个经过整理的ffplay源代码,能够独立于整个FFmpeg项目进行编译,这对于学习和...

    ffmpeg/ffplay源码分析

    FFplay是FFmpeg项目中的一个简单易用的多媒体播放器,其源代码相对简洁,是了解FFmpeg框架的一个良好切入点。杨书良所著的《FFmpeg/FFplay源码剖析》一书,对FFmpeg及FFplay的源码进行了深入分析,是学习和研究这一...

    FFMpeg_FFPlay 源码剖析(杨书良).pdf

    5. ffplay作为FFmpeg提供的一个简单的视频播放器,是研究和学习FFmpeg的好例子。文档中特别对ffplay的源码进行了剖析,包括其播放流程和主要改动点。ffplay通过解复用、解码以及使用SDL库显示视频和播放音频,展示了...

    ffplay播放器

    ffplay的PDF说明文档通常会包含关于如何编译和运行ffplay的指导,以及源代码的结构和关键函数的解释。这对于初学者来说,是理解FFmpeg和SDL工作原理的好起点。你可以通过阅读这份文档,了解到ffplay的初始化流程、...

    FFMPEG的完整教程pdf

    FFplay是FFmpeg项目中的一个简易播放器示例,用于展示如何使用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 ...

    ffmpeg基础开发资料自总结

    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 ...

    FFMpeg之FFMplay源码分析

    第五章是直接对ffplay的剖析,它是基于前面提到的库构建的,这里详细分析了ffplay.c文件,这是ffplay播放器的主要实现文件。 文章还提到了一些可能由于OCR扫描导致的错误或遗漏,如“感恩的心,感谢生命中的每一个...

    ffmpeg源码剖析.pdf

    ffplay 是一个基于 ffmpeg 库构建的简易多媒体播放器,能够播放多种格式的音频和视频文件。其设计简洁高效,是学习多媒体处理技术的良好实例。 #### 1.2 播放器一般原理 多媒体播放器通常包含以下几个核心部分: -...

    ffmpeg-win32-4.2.2.zip

    3. ffprobe:用于获取多媒体文件的元数据信息,帮助分析文件结构。 4. libavcodec:音频和视频编码库,提供了丰富的编码算法。 5. libavformat:多媒体容器格式的解析和生成库。 6. libavfilter:滤镜库,允许对视频...

    ffmpeg精简版本以及相关的说明书

    FFmpeg是一款强大的开源多媒体处理工具,它包含了多个子项目,如ffmpeg用于命令行音视频转换,ffplay是一个轻量级的媒体播放器,而ffprobe则用于分析多媒体容器和流信息。本文将深入探讨杨书良对ffplay的精简版本...

    ffmpeg vc6下直接编译源码

    `ffplay.c`是FFmpeg自带的简单媒体播放器源代码,它基于SDL库实现。用户可以通过编译这个源文件来创建一个基本的媒体播放程序,可以播放本地和网络流媒体。 `cmdutils.c`包含了一系列与命令行接口相关的函数,这些...

    ffmpeg_dev_packet.zip

    这些头文件(通常扩展名为 .h)包含了函数声明和数据结构定义,让开发者能在他们的项目中使用 FFmpeg API 进行编程。而库文件(LIB/DLL)则包含了预编译的 FFmpeg 函数实现,供应用程序链接使用,以便调用 FFmpeg 的...

Global site tag (gtag.js) - Google Analytics