hpb21的专栏

0x00 前置信息

VLC是一个非常庞大的工程,我从它的架构及流程入手进行分析,涉及到一些很细的概念先搁置一边,日后详细分析。

0x01 源码结构(Android Java相关的暂未分析)# build-android-arm-linux-androideabi/:第三方库。# modules/:模块代码。# modules/demux:解复用模块代码。# modules/codec:解码模块相关代码。# modules/access: 访问模块相关代码。# 其他:未详细分析。# src/: VLC架构核心代码。# src/config/:从命令行和配置文件加载配置,提供功能模块的读取和写入配置。# src/control/:提供动作控制功能,如播放/暂停,音量管理,全屏,日志等。# src/extras/:平台特殊性相关代码。# src/modules/:模块管理。# src/network/:提供网络接口。# src/posix/:多线程相关。# src/osd/:显示屏幕上的操作。# src/interface/ :提供代码中可以调用的接口中,如按键后硬件作出反应。# src/playlist/:管理播放功能,如停止,播放,下一首,随机播放等。# src/text/:字符集。# src/input/:输入流相关代码。# src/video_output/ : 初始化视频播放器,把从解码器获得的数据处理后播放。# src/audio_output/ : 初始化音频混合器,把从解码器获得的数据处理后播放。# src/stream_output/: 输出音频流和视频流到网络。# src/test/:libvlc测试模块。# src/misc/:libvlc使用的其他部分功能,如线程系统,消息队列,CPU的检测,对象查找系统,或平台的特定代码。# 其他:未详细分析。0x02 基础概念

对于一个视频的播放,播放器的执行步骤大致如下:

VLC在包含以上概念的基础上,又抽象出几个其他概念,先列出VLC中抽象出来的重要概念:

下图显示了这些抽象的概念的关系,其中蓝色表示VLC抽象的概念。

0x04 架构综述

VLC的整体框架是设计成一套module的管理机制,将功能分类并抽象成modules。

VLC main: player的main。初始化libVLC 并加载用户界面。libVLCcore:libvlc的核心,抽象出了一个libvlc_instance_t 对象,提供modules的装载/卸载机制。modules: modules提供具体的功能,比如上面的access,demux,decode就是以一个模块的形式存在。External libraries:外部开源库。

模块的加载方式:首先模块先将自身注册到VLC中,代码片段如:

vlc_module_begin()…vlc_module_end()

然后在需要加载模块的时候,调用module_need接口,去找到合适的模块。找到合适的模块后,会执行注册中设置的回调方法,诸如Open*名字的方法。同样自己可以实现模块,只需要按照VLC模块的标准即可。VLC中很多模块就是通过外部的开源库实现的。

vlc中模块大致分类:

0x05 流程分析

首先,给出流程图,参照该图,再继续下面的流程分析,绿色线表示打开VLC后的执行操作;黑色线表示用户输入一个视频后的执行操作;蓝色线从红色圈开始,表示开始播放输入流后的数据流向。

(1) main函数(vlc/bin/vlc.c)参数信号处理相关,不详分析。调用libvlc_new()初始化一个libvlc_instance_t实例。(libvlc_instance_t is opaque. It represents a libvlc instance)2.1 调用libvlc_InternalCreate创建一个libvlc_int_t。(This structure is a LibVLC instance, for use by libvlc core and plugins.)2.2 调用libvlc_InternalInit初始化libvlc_int_t实例。2.3 初始化libvlc_instance_t其他成员。调用libvlc_set_exit_handler设置VLC退出时的回调函数。调用libvlc_add_intf添加模块。4.1 获取playlist,如果为空,则调用playlist_Create创建一个playlist结构,并调用playlist_Activate创建新的playlist线程Thread(src/playlist/thread.c)。4.2 调用intf_Create创建一个默认的interface。4.2.1 调用vlc_custom_create创建一个vlc object(intf_thread_t)。4.2.2 注册一个添加interface的回调方法。4.2.3 调用module_need加载一个interface模块。调用libvlc_playlist_play,如果播放列表不为空,并且被设置为自动播放,则播放播放列表内容。信号处理相关,不详分析。(2) 创建一个输入初始化成功后,程序运行在playlist的线程Thread(src/playlist/thread.c)中,循环接收界面输入的请求。当输入一个新的文件或者流地址,在PlaylistVAControl获得信号,并发送该信号。Thread接收到播放请求后,在LoopRequest中调用PlayItem方法。3.1 调用input_Create创建一个input结构,并初始化各种成员,其中包括调用input_EsOutNew创建p_es_out_display(es_out)。3.2 调用input_Start创建一个input线程Run(src/input/input.c)。(3) 初始化输入

调用Run(src/input/input.c)中的Init方法,开始初始化。

调用input_EsOutTimeshiftNew新建一个50M的Timeshift(暂停缓存),包括创建并初始化p_es_out(es_out),与后续步骤9相关。设置input的状态为OPENING_S。调用InputSourceInit。3.1 调用input_SplitMRL分解输入uri。3.2 以stream形参为NULL调用demux_New加载"access_demux"模块。3.3 如果没有合适的"access_demux"模块,则调用access_New创建一个实际的access。3.3.1 调用vlc_custom_create创建access_t结构体。3.3.2 调用module_need加载合适的access模块。3.3.3 调用access模块的Open*方法,以avio模块为例。3.3.3.1 调用vlc_init_avformat初始化VLC即avformat环境。3.3.3.2 调用avio_open2打开该uri。3.3.3.3 设置access的IO方法指针。3.4 调用stream_AccessNew创建一个stream。3.4.1 根据模式(stream/block)设置steam层的IO方法指针。stream层的IO方法实际指向access层对应的IO方法指针。3.4.2 为stream层的缓冲申请并初始化内存。3.4.3 调用AStreamPrebufferStream执行一次读操作。3.5 调用stream_FilterChainNew,Add stream filters(源码描述)。3.6 调用demux_New创建一个demux。3.6.1 调用vlc_custom_create创建demux_t结构体。3.6.2 调用module_need加载合适的demux模块。3.6.3 调用demux模块的Open*方法,以avformat/demux模块为例。3.6.3.1 调用stream_Peek从stream层获取数据,用于分析输入的文件格式。3.6.3.2 调用av_probe_input_format分析输入的文件格式。3.6.3.3 设置demux_sys_t结构体部分变量的值。3.6.3.4 调用avformat_alloc_context分配AVFormatContext结构体。3.6.3.5 调用avio_alloc_context设置AVFormatContext结构体的AVIOContext类型成员pb,并设置read和seek方法指针。3.6.3.6 调用avformat_open_input打开一个输入,这里的input与VLC中的input不是一个概念,关于avformat_open_input的分析详见我的另一篇文章《avformat_open_input详细分析》链接地址。3.6.3.7 调用avformat_find_stream_info分析流信息,该方法通过读取数据初始化流以及流解码信息。3.6.3.8 根据分析的流信息,设置fmt变量,并调用es_out_Add。3.6.3.9 实际调用EsOutAdd(src/input/es_out.c),添加一个es_out,有几个流就做几次es_out_Add操作,比如该输入中有一个视频流和一个音频流,则作两次es_out_Add操作。3.6.3.10 nb_chapters相关未详细分析。3.7 设置record相关。3.8 调用demux_Control设置demux pts delay。3.9 调用demux_Control设置fps。调用InitPrograms,设置es_out和decoder相关。8.1 调用UpdatePtsDelay计算正确的pts_delay值。8.2 sout相关可选,暂不分析。8.3 调用es_out_SetMode,设置es_out的mode为ES_OUT_MODE_AUTO。8.4 以DEMUX_SET_GROUP指令调用demux_Control,DEMUX_SET_GROUP/SET_ES only a hint for demuxer (mainly DVB) to allow not reading everything。续8.3,实际调用EsOutControlLocked进入case ES_OUT_SET_MODE分支。9.1 设置es_out_sys_t 的b_active和i_mode。9.2 调用EsOutSelect方法,根据指定模块选择一个es_out。9.3 在EsOutSelect方法中进入ES_OUT_MODE_AUTO分支,进一步调用EsSelect方法,再进一步调用EsCreateDecoder方法创建decoder。9.3.1 调用input_DecoderNew创建一个新的decoder。9.3.2 如果需要缓存,调用input_DecoderStartWait发送信号,开始线程等待。9.3.3 调用EsOutDecoderChangeDelay设置decode delay。续9.3.1进入decoder_New方法。10.1 调用CreateDecoder创建decoder配置结构体。10.1.1 调用vlc_custom_create创建一个vlc object(decoder_t)。10.1.2 新建decode fifo。10.1.3 调用module_need加载适配的解码模块。10.1.3.1 调用decode模块的OpenDecoder方法,以codec/avcodec模块为例。10.1.3.2 调用GetFfmpegCodec方法 determine codec type(源码描述)。10.1.3.3 调用vlc_init_avcodec方法初始化解码环境。10.1.3.4 调用avcodec_find_decoder设置AVCodec。10.1.3.5 调用avcodec_alloc_context3分配一个AVCodecContext。10.1.3.6 调用Init*Dec系列初始化解码环境。10.1.4 初始化decoder_t结构体其他成员。10.2 调用vlc_clone创建解码线程DecoderThread。续10.1.3.5,以InitVideoDec为例。12.1 为decoder_sys_t结构分配内存。12.2 设置相关回调方法。12.3 设置解码线程类型。12.4 调用ffmpeg_InitCodec初始化extradata相关数据。12.5 调用OpenVideoCodec方法,设置解码的长宽及采用率,进一步调用avcodec_open2打开codec。(4) 播放输入我就想是一只草原中被牧童遗忘的羊,

hpb21的专栏

相关文章:

你感兴趣的文章:

标签云: