zoukankan      html  css  js  c++  java
  • VLC源码分析(一)

    VLC源码分析

    目录

    1 VLC源码结构

    vlc核心的是libvlc,它提供界面,应用处理功能,所有的libvlc的源代码都放在src目录及其子目录

    1.1 ./config/

    从命令行和配置文件中加载配置

    1.2 ./control/

    提供动作控制功能,如播放等操作

    1.3  ./extras/

    大多是平台的特殊代码

    1.4  ./modules/

    模块管理

    1.5  ./network/

    提供网络接口(socket管理,网络接口)

    1.6  ./osd/

    显示屏幕上的操作

    1.7  ./test/

    libvlc测试模块

    1.8  ./text/

    字符集

    1.9  ./interface/

    提供代码中可以调用的接口,如按键后的硬件作出反应

    1.10  ./playlist/

    管理播放功能

    1.11  ./input/

    建立并读取一个输入流,并且分离其中的音频和视频,然后把分离好的音频和视频流发给解码器

    1.12  ./audio_output/

    初始化音频混合器,即设置正确的同步频率,并对从解码器传来的音频流重新取样

     

    1.13  ./video_output/

    初始化视频播放器,把从解码器得到视频画面转化格式从yuv到rgb,然后播放

    1.14  ./stream_output/ 

    输出音频流和视频流到网络

    1.15  ./misc/

    libvlc使用的其他部分功能,如线程系统,消息队列等.

    2 configure详解

     

    概述

    VLC 属于Video LAN开源项目组织中的一款全开源的流媒体服务器和多媒体播放器。作为流媒体服务器,VLC跨平台,支持多操作系统和计算机体系结构;作 为多媒体播放器,VLC可以播放多种格式的媒体文件。主要包括有:WMV、ASF、MPG、MP、AVI、H.264等多种常见媒体格式。

    VLC 采用全模块化结构,在系统内部,通过动态的载入所需的模块,放入一个module_bank的结构体中统一管理,连VLC的Main模块也是通过插件的方 式动态载入的(通过module_InitBank函数在初始化建立module_bank时)。对于不支持动态载入插件的系统环境中,VLC也可以采用 builtin的方式,在VLC启动的时候静态载入所需要的插件,并放入module_bank统一管理。

    VLC 的模块分成很多类别主要有:access、access_filter、access_output、audio_filter、 audio_mixer、audio_output、codec、control、demux、gui、misc、mux、packetizer、 stream_output、video_filter、video_output、interface、input、playlist等(其中黑体为核 心模块)。VLC无论是作为流媒体服务器还是多媒体播放器,它的实质思路就是一个“播放器”,之所以这么形象描述,是因为(The core gives a framework to do the media processing, from input (files, network streams) to output (audio or video, on ascreen or a network), going through various muxers, demuxers, decoders and filters. Even the interfaces are plugins for LibVLC. It is up to the developer to choose which module will be loaded. 摘 于官网说明) 它实质处理的是ES、PES、PS、TS等流间的转换、传输与显示。对于流媒体服务器,如果从文件作为输入 即:PS->DEMUX->ES->MUX->TS;对于多媒体播放器如果采用UDP方式传输 即:TS->DEMUX->ES。

    1. 插件管理框架

    在VLC中每种类型的模块中都有一个抽象层/结构体,在抽象层或结构体中定义了若干操作的函数指针,通过这些函数指针就能实现模块的动态载入,赋值相关的函数指针的函数地址,最后通过调用函数指针能调用实际模块的操作。

    对 于VLC所有的模块中,有且仅有一个导出函数:vlc_entry__(MODULE_NAME)。(其中MODULE_NAME为宏定义,对于main 模块,在\include\modules_inner.h中定义为main)动态载入模块的过程是:使用module_Need函数,在 module_bank中根据各个插件的capability等相关属性,寻找第一个能满足要求并激活的模块。所谓激活是指,调用插件的初始化函数成功。 对于各个插件的初始化函数和析构函数均在vlc_entry__(MODULE_NAME)函数中指定了相关函数地址。因此载入各个插件(动态库)的过 程,就成为了解析动态库文件,并找到其中vlc_entry__函数的地址,然后运行。这样各个模块的激活函数就会赋值各个操作的函数地址,以待后面函数 动态调用。

    具体函数调用过程如下:

    l Main模块的载入过程:

    int main( int i_argc, char *ppsz_argv[] ) (src\vlc.c)->i_ret = VLC_Init( 0, i_argc, ppsz_argv )-> module_InitBank( p_vlc ) (src\libvlc.c void __module_InitBank( vlc_object_t *p_this ))-> module_LoadMain( p_this ) (src\misc \modules.c)->AllocateBuiltinModule( p_this, vlc_entry__main )->pf_entry( p_module ) (激活了main模块,以上为main模块的载入过程,对于main模块调用的实际函数为导出函数vlc_entry__main,其它模块导出的均为 vlc_entry__0_8_6)

    l Module_Need函数实现载入任意模块的过程:

    module_t * __module_Need( vlc_object_t *p_this, const char *psz_capability,

                              const char *psz_name, vlc_bool_t b_strict ) (src\misc\modules.c)-> vlc_list_find(将所有已经载入的模块查询出来)->然后循环,根据 capability查找第一个最合适的module->AllocatePlugin(动态载入所需要的插件,该函数会在动态库所在目录,遍历所 有动态库文件,)->p_module->pf_activate(调用激活函数)

    l VLC_Init函数流程:

    module_InitBank->module_LoadBuiltins(载入静态插件)->module_LoadPlugins(载入动态插件->VLC_AddIntf(添加interface插件,VLC会静态载入hotkeys模块)

    在VLC中根据处理任务不同,会静态载入不同的模块,main、memcpy、hotkeys等;动态载入的模块根据处理任务不同,差异很大。

    2. VLC流媒体服务器体系结构

    以下主要讨论VLC作为流媒体服务器时的体系结构。针对一个节目单文件,调试其运行过程,并最后给出总结。

    该实例的播放节目单为如下:

    New br broadcast enabled

    Setup br input /mnt/hgfs/movie/caiyan.mpg

    Setup br output #standard{mux=ts,access=udp,url=234.0.1.4,sap,name=ch1}

    在例子中,通过VLC提供API:libvlc_new,libvlc_vlm_new,libvlc_vlm_play_media,libvlc_vlm_load_file等(有些API是自己添加的)可以完成对广播节目br的播放。

    下面让我们仔细看看通过这几个接口,VLC内部到底是怎么工作完成了流媒体发布的。

    1. 首先程序调用libvlc_new(\src\control\core.c)接口,实现创建一个VLC运行实例libvlc_instance_t,该实例在程序运行过程中唯一。

    2. 在libvlc_new接口中,调用了VLC_Init函数实现具体的初始化工作。

    3. VLC_Init(\src \libvlc.c)函数中,首先通过system_Init函数完成传入参数对系统的相关初始化,接着通过module_InitBank(\src \misc\modules.c)函数初始化module_bank结构体,并创建了main模块,然后通过module_LoadBuiltins载入 静态模块,通过module_LoadPlugins(\src\misc\modules.c)函数载入动态模块,通过 module_Need(\src\misc\modules.c)函数载入并激活memcpy模块,通过playlist_Create(\src \playlist\playlist.c)函数,创建了一个playlist播放管理的线程,其线程处理函数为RunThread(\src \playlist\playlist.c),通过VLC_AddIntf(\src\libvlc.c)函数添加并激活hotkeys模块,最后根据系 统设置定义了宏HAVE_X11_XLIB_H,因此还需要添加screensaver模块。

    4. 总结:此时加载的模块有main,hotkeys,screensaver,memcpy;多创建了一个线程,用于管理playlist,该线程无限循环,直到p_playlist->b_die状态为止。

    5. 其次程序中调用libvlc_vlm_new接口,创建VLM对象(该接口为自己添加的)。

    6. 该接口调用的是vlm_New(\src\misc\vlm.c)函数,实现VLM对象的创建,函数返回值是指向vlm_t的指针。

    7. Vlm_new 函数中,创建了一个vlm管理线程,线程处理函数为Manage(\src\misc\vlm.c)。该函数循环处理当前各种媒体(vod、 broadcast、schedule)的播放实例,控制其每个播放细节(如:从一个input切换到下一个input;schedule周期循环调度 等)。与playlist线程不同的是,Manage主要针对播放实例的操作,而RunThread主要针对播放列表的管理,也就是说VLC管理是分级 的,播放列表级和播放列表中媒体播放实例级。

    8. 其次程序调用libvlc_vlm_load_file接口,载入播放节目单(该接口也为自己添加,播放节目单如上所述)。

    9. 该接口调用的是vlm_Load(\src\misc\vlm.c)函数,在该函数中,依次调用如下函数:stream_UrlNew、stream_Seek、stream_Read、Load,以下详细介绍各个函数作用。

    a) 首 先是stream_UrlNew(\src\input\stream.c)函数。先调MRLSplit(\src\input\input.c)函数完 成对access、demux和path的解析。具体对于本例解析的结果为:access= " ",demux=" ",path=" aa"。然后调 用access2_New(\src\input\access.c)函数创建一个access_t结构体并初始化。具体运行时载入模块的相关参数 是:capability="access2",name="access_file",psz_filename=access/libaccess_file_plugin.so。 最后调用stream_AccessNew(\src\input\stream.c)函数,创建stream_t结构体对象,并初始化对象中所有函数指 针;

    b) 再调用stream_Seek(\include\vlc_stream.h)内联函数,设置起始位置;

    c) 再调用stream_Size(\include\vlc_stream.h)获得大小;

    d) 再调用stream_Read(\include\vlc_stream.h),读取到缓冲区;

    e) 最 后调用Load(\src\misc\vlm.c),完成实际的载入节目单。对于节目单文件,是一行行解析,并调用 ExecuteCommand(\src\misc\vlm.c)完成解析的。Load函数的调用仅仅是设置了相关参数,如:设置input字符串值,设 置output字符串值,设置mux的值及与播放相关的enabled、loop等参数。Load工作仅仅是为了下一步发布流做准备的。

    10. 程序中调用libvlc_vlm_play_media接口,将节目流发布出去。(自己添加接口)

    11. 在 libvlc_vlm_play_media接口中,实质是创建了命令“control br play”再调用 vlm_ExecuteCommand(\src\misc\vlm.c),完成对命令的执行,根据命令类型,由 vlm_MediaControl(\src\misc\vlm.c)函数处理。

    12. 在 vlm_MediaControl函数中,会调用vlc_input_item_Init(\include\vlc_input.h)函数完成播放实例 的初始化,并调用input_CreateThread2(\src\input\input.c)函数完成播放线程的创建。该线程的处理函数为 Run(\src\input\input.c)。

    13. Run线程是整个VLC作为流媒体服务器的核心。其主要分为如下几个步骤:Init、MainLoop和End。其中MainLoop是一个无限循环,是完成流媒体的整个发布过程。

    a) 首先调用Init(\src\input\input.c)函数,初始化相关统计参数;

    b) 其次再调用input_EsOutNew(\src\input\es_out.c)函数,初始化es_out_t结构体对象和es_out_sys_t结构体对象,并设置相关函数指针;

    c) 再调用InputSourceInit(\src\input\input.c)函数,初始化input_thread_t对象中的input_source_t对象,主要有access_t、stream_t、demux_t三个结构体对象;

    d) 总结此时各个模块实际载入的情况:

    1) (access_t)type="access",name="access_filter",capability="access2",psz_filename="access/libaccess_file_plugin.so";

    2) (stream_t)type="stream",pf_read="AStreamReadStream",pf_seek="AStreamPeekStream",pf_control="AStreamControl",pf_destory="AStreamDestory";

    3) (demux_t)type="demux",capability="demux2",shortcuts="ps";

    4) (sout_instance_t)type="stream out",psz_capability="sout stream",shortcut="stream_out_standard",psz_filename="/stream_out /libstream_out_standard_plugin.so";

    5) (es_out_t)pf_add="ESOutAdd",pf_send="ESOutSend",pf_del="ESOutDel",pf_control="ESOutControl";

    e) 再调用MainLoop(\src\input\input.c)函数,完成读取、解复用、解码、复用和传输;

    f) MainLoop函数为无限循环,直到input_thread_t对象存在b_die、b_error、b_eof时为止。在该函数中,存在如下行代码:

    i_ret=p_input->input.p_demux->pf_demux(p_input->input.p_demux);

    它就是流媒体服务器运行的起点,所有的后续操作都会在该函数中继续衍生。

    g) Pf_demux调用的是(\modules\demux\ps.c)中的Demux函数,在该函数中主要完成如下操作:

    1) 先调用ps_pkt_resynch(\modules\demux\ps.c)函数,完成PS流中数据包重新同步(这里应该涉及到多媒体相关知识,需要补补);

    2) 再调用ps_pkt_read(\modules\demux\ps.c)函数,最终调用stream_Block函数,这个函数内部会根据实际情况,调用stream_t模块中的pf_read或pf_block函数,函数结果会返回一个读取的buffer;

    3) 根据数据包的i_code的值,做不同的处理,对于音视频数据流,调用es_out_Send(\include\vlc_es_out.h)函数处理;

    4) es_out_Send一个抽象层函数,其通过函数指针,实际调用的是EsOutSend(\src\input\es_out.c)函数;

    5) EsOutSend函数最终会调用input_DecoderDecode(\src\input\decoder.c)函数;

    6) input_DecoderDecode函数会调用DecoderDecode(\src\input\decoder.c)函数完成解码;

    7) DecoderDecode函数会调用pf_packetize(\modules\packetizer\mpegvideo.c)函数实现PES的打包;

    8) DecoderDecode函数会调用sout_InputSendBuffer(\src\stream_output\stream_output.c)函数,实现发送;

    9) sout_InputSendBuffer函数中的pf_send指针,指向的是(\modules\stream_out\standard.c)Send函数;

    10) Send 函数调用的是流化输出(stream_output)的抽象层(\src\stream_output\stream_output.c)中的 sout_MuxSendBuffer函数,首先将要发送的数据放入fifo队列中,然后调用pf_mux函数指针,完成多路复用;

    11) Pf_mux函数指针指向的是(\modules\mux\mpeg\ts.c)的Mux函数,完成多路复用后,最终调用(\modules\mux\mpeg\ts.c)TSSchedule函数,准备调度发送了;

    12) TSSchedule函数中调用了TSDate(\modules\mux\mpeg\ts.c)函数;

    13) TSDate函数中调用了流化输出(stream_output)的抽象层(\src\stream_output\stream_output.c)中的sout_AccessOutWrite函数,最终调用pf_write函数完成数据输出;

    14) pf_write函数指向的是(\modules\access_output\udp.c)中的Write函数,完成数据UDP发送,这样数据就转换称TS流输出了;

    15) 总结:pf_demux函数为流媒体所有操作的起点,通过该处衍生了很多其他模块的处理,从上面的分析可以看出,系统实质就是PS、ES、PES和TS几种流间的转换,针对应用场合(主要指做服务器或客户端)的不同,转换的方式不同

  • 相关阅读:
    欧拉公式
    isap的一些想法
    错误合集
    Hello World
    PAT (Advanced Level) Practice 1068 Find More Coins
    PAT (Advanced Level) 1087 All Roads Lead to Rome
    PAT (Advanced Level) 1075 PAT Judge
    PAT (Advanced Level) 1067 Sort with Swap(0, i)
    PAT (Advanced Level) 1017 Queueing at Bank
    PAT (Advanced Level) 1025 PAT Ranking
  • 原文地址:https://www.cnblogs.com/xiaOt119/p/2555783.html
Copyright © 2011-2022 走看看