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()(大概这些操作比较消耗资源,所以只有真正启动才开始处理吧)
{
- InitSout( p_input )// 流输出
- 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.****.net/leopard21/article/details/24818715 几个流名称;
ES Elementary Stream原始流, 有编码器出来的只包含音频或视频的流
PES(Paketized Elementary Stream)打包基本码流
PS (Program Stream流),节目流,混合音视频的打包流
TS 用于传输 Transport Stream,也是混合流,相对于上面的节目流,这个流有固定的长度包
补充下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()
{
- 创建 input_source_t
- 分析参数 mrl
- 根据上面的参数,创建 解复用 in->p_demux = InputDemuxNew( p_input, in, psz_access, psz_demux, psz_path, psz_anchor );
- 设置一些其他属性
}
关于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 )
{
- demux_NewAdvanced(…stream_t *s =NULL….);
进来先创建demux , 参数 stream_t 传NULL,这可能只是一个检查,没创建成功,后续就要先创建 stream再一次调用改接口来创建 demux,如果成功,直接完成返回
- 第一步没创建成功的情况下,先
/* 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_demux和pf_contrl,函数,怎么注册进去
分析demux_NewAdvanced()
{
- 先创建 结构体demux_priv_t *priv;
2.0调用流stream层的函数stream_MimeType()来获取流的类型
这个stream_MimeType(),是 stream.c中的“抽象层函数”
stream层的接口会更调用到具体的 access层实现的控制接口,比如可以看到的不同access
- 没有从 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_demux和pf_contrl,
}
模块加载
详细分析一下上面 module_need( p_demux, "access_demux", p_demux->psz_access, true )
{
- 查找相关 能力的所有模块
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)
{
- module_Map(obj, m->plugin)//这应该是一个检查
- 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_activate(demux_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_demux和pf_contrl
已经挂钩
………
}
码流最终去了哪里,还需要分析mp4.c 文件中的 demux, 还会进一步处理。