【以mp4文件格式和H264编码的本地文件为例展开分析】
1、根据前面分析已知晓有一个es_out_t及其es_out_id_t结构体记录输出流信息,在根据其字段定义可知其与decoder层进行交互:
//【vlc/src/input/es_out.c】中
struct es_out_id_t
{
/* ES ID */
int i_id;
es_out_pgrm_t *p_pgrm;
/* */
bool b_scrambled;
/* Channel in the track type */
int i_channel;
es_format_t fmt;
char *psz_language;
char *psz_language_code;
// 此处字段即为解码层结构体
decoder_t *p_dec;
decoder_t *p_dec_record;
/* Fields for Video with CC */
struct
{
vlc_fourcc_t type;
uint64_t i_bitmap; /* channels bitmap */
es_out_id_t *pp_es[64]; /* a max of 64 chans for CEA708 */
} cc;
/* Field for CC track from a master video */
es_out_id_t *p_master;
/* ID for the meta data */
int i_meta_id;
};
// 例如交互流程:
/* FIXME: create a stream_filter sub-module for this */
es_out_Control(p_demux->out, ES_OUT_POST_SUBNODE, p_subitems)
// 分析:
// 最终调用了【out->pf_control( out, i_query, args )】,out为es_out_t结构体,
// 而该结构体out创建是在【vlc/src/input/es_out_timeshift.c】的方法
//【input_EsOutTimeshiftNew】中,而该方法在Init方法中调用。
// 因此通过分析可知继续调用了【es_out_timeshift.c】的【Control】方法,
// 又调用了【es_out_vaControl( p_sys->p_out, i_query, args )】,
// 其中p_sys为es_out_sys_t结构体,其创建在【vlc/src/input/input.c】的【Create】方法中:
// 即【priv->p_es_out_display = input_EsOutNew( p_input, priv->i_rate );】,
// 而input_EsOutNew方法创建了该结构体,通过分析可知调用了【vlc/src/input/es_out.c】的【EsOutControl】该方法,
// 然后在【EsOutControlLocked】方法中得到了处理,通过发送事件event回调得到的处理。
因此必须先分析es out结构体信息创建流程:即如何
首先根据此前分析过的流程从mp4.c中显示分析,在其Open或者DemuxMoov中均有调用如下方法:
static void MP4_TrackSetup( demux_t *p_demux, mp4_track_t *p_track,
MP4_Box_t *p_box_trak,
bool b_create_es, bool b_force_enable )
该方法创建了【es_format_t】结构体信息,内部调用了:【mp4.c中】
static int TrackCreateES( demux_t *p_demux, mp4_track_t *p_track,
unsigned int i_chunk, es_out_id_t **pp_es )
其又调用了:【mp4.c中】
static es_out_id_t * MP4_AddTrackES( es_out_t *out, mp4_track_t *p_track )
{
es_out_id_t *p_es = es_out_Add( out, &p_track->fmt );
/* Force SPU which isn't selected/defaulted */
if( p_track->fmt.i_cat == SPU_ES && p_es && p_track->b_forced_spu )
es_out_Control( out, ES_OUT_SET_ES_DEFAULT, p_es );
return p_es;
}
然后调用了:【vlc/include/vlc_es_out.h】中
VLC_USED
static inline es_out_id_t * es_out_Add( es_out_t *out, const es_format_t *fmt )
{
return out->pf_add( out, fmt );
}
可知调用了 es_out_t 结构体指针方法字段【pf_add】方法,而主要此处的out结构体对象为【p_demux->out】解复用结构体中字段,而此字段根据前面分析过的,该结构体out创建是在【vlc/src/input/es_out_timeshift.c】的方法【input_EsOutTimeshiftNew】中,而该方法在Init方法中调用。然后赋值【p_out->pf_add = Add】Add方法指针,因此通过分析可知继续调用了【es_out_timeshift.c】的【Add】方法。
static es_out_id_t *Add( es_out_t *p_out, const es_format_t *p_fmt )
{
es_out_sys_t *p_sys = p_out->p_sys;
ts_cmd_t cmd;
es_out_id_t *p_es = malloc( sizeof( *p_es ) );
if( !p_es )
return NULL;
vlc_mutex_lock( &p_sys->lock );
TsAutoStop( p_out );
if( CmdInitAdd( &cmd, p_es, p_fmt, p_sys->b_delayed ) )
{
vlc_mutex_unlock( &p_sys->lock );
free( p_es );
return NULL;
}
TAB_APPEND( p_sys->i_es, p_sys->pp_es, p_es );
// 此处进行Add操作或者直接push等
// 注意:[b_delayed]该字段主要用于标记当前的TS线程是否已经启动
if( p_sys->b_delayed )
// true为已启动TS线程【TS线程表示timeshift thread】,
// 处理一些时间流逝功能如暂停后再次请求播放则可以从此前暂停位置开始
// 【存储在文件中,通过读写文件来实现的】
// 而该线程通过信号条件锁进行TS操作指令的唤醒执行【即入栈出栈操作】
// 此处实现比较简单不展开分析
TsPushCmd( p_sys->p_ts, &cmd );
else
// 见1.2小节分析
CmdExecuteAdd( p_sys->p_out, &cmd );
vlc_mutex_unlock( &p_sys->lock );
return p_es;
}
1.2、此处先分析[b_delayed]该字段为false的初始化阶段情况,CmdExecuteAdd实现分析:
// 【vlc/src/input/es_out_timeshift.c】
CmdExecuteAdd( p_sys->p_out, &cmd );
static void CmdExecuteAdd( es_out_t *p_out, ts_cmd_t *p_cmd )
{
p_cmd->u.add.p_es->p_es = es_out_Add( p_out, p_cmd->u.add.p_fmt );
}
// 注意此处[p_sys->p_out]结构体对象和前面的【es_out_Control( out, ES_OUT_SET_ES_DEFAULT, p_es );】中的out结构对象不是同一个,
// 上面已有分析:
// 其中p_sys为es_out_sys_t结构体,其创建在【vlc/src/input/input.c】的【Create】方法中:
// 即【priv->p_es_out_display = input_EsOutNew( p_input, priv->i_rate );】,
// 而input_EsOutNew方法创建了该结构体,通过分析可知调用了【vlc/src/input/es_out.c】的【EsOutAdd】该方法,
// 然后在【EsOutAdd】方法中得到了处理
// 【vlc/src/input/es_out.c】中
/* EsOutAdd:
* Add an es_out
*/
static es_out_id_t *EsOutAdd( es_out_t *out, const es_format_t *fmt )
{
return EsOutAddSlave( out, fmt, NULL );
}
static es_out_id_t *EsOutAddSlave( es_out_t *out, const es_format_t *fmt, es_out_id_t *p_master )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
if( fmt->i_group < 0 )
{
msg_Err( p_input, "invalid group number" );
return NULL;
}
es_out_id_t *es = malloc( sizeof( *es ) );
es_out_pgrm_t *p_pgrm;
int i;
if( !es )
return NULL;
vlc_mutex_lock( &p_sys->lock );
// 此处查找或创建一个当前track对应的【es_out_pgrm_t】结构体信息:
// 重点是创建了Jitter Buffer时钟显示策略
/* Search the program */
p_pgrm = EsOutProgramFind( out, fmt->i_group );
// ...省略部分初始化赋值代码
es->psz_language = LanguageGetName( es->fmt.psz_language ); /* remember so we only need to do it once */
es->psz_language_code = LanguageGetCode( es->fmt.psz_language );
es->p_dec = NULL;
es->p_dec_record = NULL;
es->cc.type = 0;
es->cc.i_bitmap = 0;
// 此处注意:将demux层输入的【es_out_id_t】信息赋值给了当前新建的【es_out_id_t】结构体字段
// 但此次为空
es->p_master = p_master;
TAB_APPEND( p_sys->i_es, p_sys->es, es );
// 此处判断条件为:若当前es_out_pgrm_t结构体信息是旧数据时则执行更新
if( es->p_pgrm == p_sys->p_pgrm )
EsOutESVarUpdate( out, es, false );
// ES out更新媒体元数据到playlist item数据中,主要是key-value类的全局数据创建或更新等
EsOutUpdateInfo( out, es, &es->fmt, NULL );
// ES out选择实现:主要通过【EsSelect】该方法实现--》见1.2.1小节分析
EsOutSelect( out, es, false );
if( es->b_scrambled )
EsOutProgramUpdateScrambled( out, es->p_pgrm );
vlc_mutex_unlock( &p_sys->lock );
return es;
}
1.2.1、EsSelect实现分析:【vlc/src/input/es_out.c】中
static void EsSelect( es_out_t *out, es_out_id_t *es )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
if( EsIsSelected( es ) )
{
msg_Warn( p_input, "ES 0x%x is already selected", es->i_id );
return;
}
// 由前面分析到此处master为空
if( es->p_master )
{
int i_channel;
if( !es->p_master->p_dec )
return;
i_channel = EsOutGetClosedCaptionsChannel( &es->fmt );
if( i_channel == -1 ||
input_DecoderSetCcState( es->p_master->p_dec, es->fmt.i_codec,
i_channel, true ) )
return;
}
else
{
// 进入此处分析,下面这几个if判断处理为:如果禁用了音频或视频或字幕流的话,
// 则直接结束,不为当前track创建decoder层。否则符合条件则创建解码层
const bool b_sout = input_priv(p_input)->p_sout != NULL;
if( es->fmt.i_cat == VIDEO_ES || es->fmt.i_cat == SPU_ES )
{
if( !var_GetBool( p_input, b_sout ? "sout-video" : "video" ) )
{
msg_Dbg( p_input, "video is disabled, not selecting ES 0x%x",
es->i_id );
return;
}
}
else if( es->fmt.i_cat == AUDIO_ES )
{
if( !var_GetBool( p_input, b_sout ? "sout-audio" : "audio" ) )
{
msg_Dbg( p_input, "audio is disabled, not selecting ES 0x%x",
es->i_id );
return;
}
}
if( es->fmt.i_cat == SPU_ES )
{
if( !var_GetBool( p_input, b_sout ? "sout-spu" : "spu" ) )
{
msg_Dbg( p_input, "spu is disabled, not selecting ES 0x%x",
es->i_id );
return;
}
}
// 创建解码层结构体信息,与解码层进行交互,见1.2.1.1小节分析
EsCreateDecoder( out, es );
if( es->p_dec == NULL || es->p_pgrm != p_sys->p_pgrm )
return;
}
// 设置其为选用状态
/* Mark it as selected */
input_SendEventEsSelect( p_input, es->fmt.i_cat, es->i_id );
input_SendEventTeletextSelect( p_input, EsFmtIsTeletext( &es->fmt )