vlc源码分析(2)--input.c mp4文件解复用,mp4模块加载

src/input.c中的  输入线程 run()

分析环境 vlc 3.0.6 ubuntu18.04 (有些奇怪的地方,还没进一步理解其用意,记录下代码跟踪 这个排版相当难受啊。。。)

非常重要的一个线程体,可以说是最主要的活动线程,从文件读数据解复用到输出数据。 

vlc官方文档

https://www.videolan.org/developers/vlc/doc/doxygen/html/index.html

https://www.videolan.org/developers/vlc/doc/doxygen/html/group__input.html

解复用流程:

主要结构体

input_thread_private_t :: input_source_t *master :: demux_t  *p_demux;

上面所列从左到有 ,左边包含右边。

input_thread_private_t,整个 input线程的数据结构,input_souce_t *master, 输入源。demux_t, 输入源里面的解复用器

input_thread_private_t, 在input线程启动后线程体里面的init()调用中创建,同时调用InputSourceNew创建 input_source_t*master输入源。 InputSourceNew创建 master时候,会继续调用InputDemuxNew(),根据具体的输入类型,创建demux解复用器,比如mp4文件,会在这里面根据文件扩展名 加载 请求 mp4模块,mp4模块的相关接口注册到这个解复用器里面。

下面三个函数都是static函数,即input.c里面自己的函数

Init(); ==>

master = InputSourceNew(); ==>

p_demux = InputDemuxNew InputDemuxNew();

demux里面还有两个重要的成员,一个 stream_t *s,有input.c中使用stream_AccessNew()创建。

一个es_out_t    *out; 是input.c 调用 input_EsOutTimeshiftNew()创建的。

demux 解复用的过程,就是从stream_t *s中读数据,然后解复用解析出单一的视频流 或音频流,然后 输出 es_out_t *out.

 

input线程工作流程分析

从 input_Create()函数进入,到input_Start() 开始

先找出主角

struct input_thread_private_t

{………

//Output

bool    b_out_pace_control; /* XXX Move it ot es_sout ? */

sout_instance_t *p_sout;

es_out_t       *p_es_out;

es_out_t       *p_es_out_display;

/* Main source */

input_source_t *master;

……..

}

 

input_Create

( vlc_object_t *p_parent, input_item_t *p_item, const char *psz_header, bool b_preparsing,

                       input_resource_t *p_resource,

                     vlc_renderer_item_t *p_renderer )

{

     1.0 唯一的 input_thread_private_t,

     2.0解析 input_item的附加选项,创建一个类型存储这些配置。(处理关键的输入参数)

     3.0将输入的参数 p_item 直接赋值到了input_thread_private_t 中的 p_item

     4.0 priv->p_es_out_display = input_EsOutNew( p_input, priv->i_rate );

}

线程主体 Run()

在真正启动时候,还会有一轮初始化工作,即在 run() 线程体的开端调用

init()(大概这些操作比较消耗资源,所以只有真正启动才开始处理吧)

{

  1. InitSout( p_input )// 流输出
  2. priv->p_es_out = input_EsOutTimeshiftNew( p_input, priv->p_es_out_display,

                                              priv->i_rate );// 貌似只是一个缓冲过渡的通道

     3.0 master = InputSourceNew( p_input, priv->p_item->psz_uri, NULL, false );

}

上一步,重点,根据 p_item中的uri创建了输入。

完成 master的创建,其内部有demux解复用器,这个 input线程直接在init里面调用

demux_Control来控制解码,获取一些具体视频的信息,这个我觉得是不太合理的地方,应该在master里面去直接操作demux,没理由input越级直接干预了。

上面截图,init获取文件时长,然后设置

input_SendEventLength( p_input, i_length ); input_SendEventPosition( p_input, 0.0, 0 );

init做完这些操作,调用 input_ChangeState(p_input,PLAYING_S)切换到播放状态。

 

进入 MainLoop(p_input, true);

前戏做足,开始干正经事了。顺便提下,input.c中 控制 输入的input的控制函数

不过这些都是 static自己私用的,你要控制找 外层比如 vlm层的控制就行了

这里面还有这样一个操作

start-pause  play-and-pause  一开始先暂停? 放完再暂停?

进入主循环:

 

这个循环里面,主要的工作分两部分,

首先,在 非暂停(正常播放)状态下,调用

MainLoopDemux( p_input, &b_force_update ); 解复用 //Main loop: Fill buffers from access, and demux

{

        demux_Demux(p_demux)

}

完成上面解复用操作,接着处理流播放 控制部分

 

有几个流概念:

https://blog.csdn.net/leopard21/article/details/24818715 几个流名称;

ES Elementary Stream原始流, 有编码器出来的只包含音频或视频的流

PESPaketized Elementary Stream)打包基本码流

PS (Program Stream),节目流,混合音视频的打包流

TS 用于传输 Transport Stream,也是混合流,相对于上面的节目流,这个流有固定的长度包

https://img-blog.csdn.net/20140501003709171?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbGVvcGFyZDIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center

 

补充下demuxer的接口;

demux_Control( p_demux, DEMUX_GET_TIME, &p_priv->i_time ) 即简介调用pf_control来控制 解复用

demux_Demux 也是简介调用到 pf_demux来解复用数据

具体的这两个函数,就是具体的解复用模块需要实现的。

分析解复用相关的代码。

input 线程中用到的解复用器,都是p_priv->master->p_demux

即在 input线程体启动之后 init里面创建的

master = InputSourceNew( p_input, priv->p_item->psz_uri, NULL, false );

这个创建输入源的内部就创建了 解复用器

分析 master :

主角:

struct input_source_t

{

        VLC_COMMON_MEMBERS;

        demux_t  *p_demux; /**< Demux object (most downstream) */

        int i_seekpoint_offset;

        int i_seekpoint_start;

       int i_seekpoint_end;

/* Properties */

    bool b_can_pause;

    bool b_can_pace_control;

    bool b_can_rate_control;

    bool b_can_stream_record;

    bool b_rescale_ts;

}

 

InputSourceNew()

{

  1. 创建 input_source_t
  2. 分析参数 mrl
  3. 根据上面的参数,创建 解复用 in->p_demux = InputDemuxNew( p_input, in, psz_access, psz_demux, psz_path, psz_anchor );
  4. 设置一些其他属性

}

 

关于demux  结构体:strut demux_t

{

        VLC_COMMON_MEMBERS;

        module_t    *p_module; 读取数据的module

       stream_t *s;

      es_out_t    *out;

/* set by demuxer */

    int (*pf_demux)  ( demux_t * );   /* demux one frame only */

    int (*pf_control)( demux_t *, int i_query, va_list args);

}

InputDemuxNew ( input_thread_t *p_input, input_source_t *p_source,

                               const char *psz_access, const char *psz_demux,

                               const char *psz_path, const char *psz_anchor )

{

  1. demux_NewAdvanced(…stream_t *s =NULL….);

 进来先创建demux , 参数 stream_t NULL,这可能只是一个检查,没创建成功,后续就要先创建 stream再一次调用改接口来创建 demux,如果成功,直接完成返回

  1. 第一步没创建成功的情况下,先

       /* create a regular demux with the access stream created */

     p_stream = stream_AccessNew( VLC_OBJECT( p_source ),

     p_input priv->b_preparsing,psz_base_mrl );

    创建这个stream 流的参数,就是 ps_base_mrl  = 文件名

}

 

继续查找这个master 里面的解复用器的 pf_demuxpf_contrl,函数,怎么注册进去

分析demux_NewAdvanced()

{

  1. 先创建 结构体demux_priv_t *priv;

    2.0调用流stream层的函数stream_MimeType()来获取流的类型

         这个stream_MimeType(), stream.c中的“抽象层函数”

        stream层的接口会更调用到具体的 access层实现的控制接口,比如可以看到的不同access

 

  1. 没有从 demux的模块自身的控制中获取到名称,根据psz_file 文件的扩展名称来匹配模块

       psz_module = DemuxNameFromExtension( psz_ext + 1, b_preparsing );

      得到 psz_module = “mp4”

 ( 看来vlc也是个以貌取人的播放器,没有扩展名称的文件,不知道能不能播放,试过window版本,去掉扩展名也能播,误会了)

然后加载 mp4,

p_demux->p_module = vlc_module_load(p_demux, "demux", psz_module,

             !strcmp(psz_module,p_demux->psz_demux), demux_Probe, p_demux);

           psz_access = “file”;

加载了一个 file 模块,作为 demux module

这个module_need加载的时候,已经把p_demux传入了,模块的入口就会设置好

pf_demuxpf_contrl,

}

 

模块加载

详细分析一下上面 module_need( p_demux, "access_demux", p_demux->psz_access, true )

{

  1. 查找相关 能力的所有模块

module_list_cap (&mods, capability) //bank.c文件中的函数,明显这是vlc 最开始初始化的时候已经建立好的一个树存储结构,里面有各个模块的信息

    2 映射得到一组相关能力的模块,然后进一步通过模块名称进行匹配

module_match_name (cand, shortcut)

  3 匹配到模块,加载

module_load (obj, cand, probe, args);

}

 

分析 module_load (obj, cand, probe, args)

{

  1. module_Map(obj, m->plugin)//这应该是一个检查
  2. init (m->pf_activate, ap); //正式加载,这个有点绕

        首先 init 参数 probe传进来的 一个探测”函数指针,

        m, 是参数 cand 传进来的module_t

        ap, 是参数 args

要跟踪这个过程,得返回去找调用者。也就是 上面module_list_cap 映射插件表的时候,从某一个地方获取来得插件列表,要找到这个插件列表的创建的地方。

这个部分就要找:

libvlc_instance() ==>libvlc_new()==>libvlc_internalInit()==>module_LoadPligins()==>

for( module_InitStatic() )==>vlc_pligin_describe()

}

 

对着现在的demo mp4,模块,模块的上面的调用

vlc_module_load(p_demux, "demux", psz_module,

             !strcmp(psz_module, p_demux->psz_demux), demux_Probe, p_demux)

{    ……

      module_load (obj, cand, probe, args);

     probe 为 demux.c文件中 的 static int demux_Probe (void *func, va_list ap)

    {

         int (*probe)(vlc_object_t *) = func;

         demux_t *demux = va_arg(ap, demux_t *);

         return probe(VLC_OBJECT(demux));

    }

  饶了一套弯,最总还是相当于:

  init (m->pf_activate, ap);  ==pf_activatedemux_t);

也就是 调用模块的pf_activate 函数。顾名思义,激活函数

模块的pf_activate 函数,全局找一下能在entry.c中的vlc_plugin_describe 中找到,

也就是上面分析的 libvlc_instance() èlibvlc_new() 。。。加载静态模块的时候已经通过一个循环体,调用各个模块的入口函数 entry(vlc_plugin_desc_cb, plugin), 而具体的模块的 入口函数 都会利用这个调用传入的一个 回调函数指针 desc_cb,这个desc_cb 回调函数,顾名思义,是 模块描述完自身后做的 回调。

(也就是说,libvlc 首先找到各个模块的入口函数,然后调用模块入口函数,具体的模块入口执行完,又主动调用 desc_cb来告诉libvlc我模块已经打开完成,主动权再次还给 libvlc)。 参考 vlc_plugin.h set_callbacks宏,模块文件中 用这个宏来设定 模块的激活函数 activate ,和注销函数 deactive.)

 所以上面加载MP4模块,会跳转到 modules/demux/mp4.c中的 open()

  mp4.c 的open函数,就会设置好 MP4解复用的两个重要函数指针pf_demuxpf_contrl

已经挂钩

………

 }

 

码流最终去了哪里,还需要分析mp4.c 文件中的 demux, 还会进一步处理。

猜你喜欢

转载自blog.csdn.net/u012459903/article/details/88896095