【以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 ) ? es->i_id : -1 );
}
1.2.1.1、EsCreateDecoder实现分析:【vlc/src/input/es_out.c】中
(解码层结构体信息decoder_t的创建)
static void EsCreateDecoder( es_out_t *out, es_out_id_t *p_es )
{
es_out_sys_t *p_sys = out->p_sys;
input_thread_t *p_input = p_sys->p_input;
// 该方法实现:主要从输入源处理线程处创建另一个新的解码器解码线程,见1.2.1.1.1小节分析
// 注:此处最后一个参数【input_priv(p_input)->p_sout】为【sout_instance_t】结构体信息(可能为空当设置了renderer时),
// 该信息定义为流输出实例即Stream Output,初始化流程:InitSout方法【vlc/src/input/input.c】-->
// input_resource_RequestSout方法【vlc/src/input/resource.c】-->sout_NewInstance方法【vlc/src/stream_out.c】,
// 并且其创建了内部字段流输出模块modules链【sout_stream_t】结构体信息。
p_es->p_dec = input_DecoderNew( p_input, &p_es->fmt, p_es->p_pgrm->p_clock, input_priv(p_input)->p_sout );
if( p_es->p_dec )
{
// 若输出流正在缓冲中则通过wait条件锁【vlc_cond_signal( &p_owner->wait_request );】信号通知其解码器等待
if( p_sys->b_buffering )
input_DecoderStartWait( p_es->p_dec );
if( !p_es->p_master && p_sys->p_sout_record )
{
p_es->p_dec_record = input_DecoderNew( p_input, &p_es->fmt, p_es->p_pgrm->p_clock, p_sys->p_sout_record );
if( p_es->p_dec_record && p_sys->b_buffering )
input_DecoderStartWait( p_es->p_dec_record );
}
}
// 更新上述解码器的delay延迟展示时间字段值
EsOutDecoderChangeDelay( out, p_es );
}
1.2.1.1.1、input_DecoderNew实现分析:
// 【vlc/src/input/decoder.c】中
/**
* Spawns a new decoder thread from the input thread
*
* \param p_input the input thread
* \param p_es the es descriptor
* \return the spawned decoder object
*/
decoder_t *input_DecoderNew( input_thread_t *p_input,
es_format_t *fmt, input_clock_t *p_clock,
sout_instance_t *p_sout )
{
return decoder_New( VLC_OBJECT(p_input), p_input, fmt, p_clock,
input_priv(p_input)->p_resource, p_sout );
}
/* TODO: pass p_sout through p_resource? -- Courmisch */
static decoder_t *decoder_New( vlc_object_t *p_parent, input_thread_t *p_input,
const es_format_t *fmt, input_clock_t *p_clock,
input_resource_t *p_resource,
sout_instance_t *p_sout )
{
decoder_t *p_dec = NULL;
// 根据前面分析p_out为空时代表设置了渲染器,需要解码渲染
const char *psz_type = p_sout ? N_("packetizer") : N_("decoder");
int i_priority;
// 查找加载合适的音频或视频或字幕流解码器模块【调用前面分析过的module_need方法】
// 并初始化解码器结构体信息对象:
// 重要点-->初始化了解码器FIFO先入先出块block队列,初始化锁/条件信息,
// 解码器回调、解码器输出流格式、demuxer输入流格式。
// 另外:若输入处理还没准备好是否需要打包发送流则默认加载一个打包数据器。
// 并赋值方法指针用于可初始化vout/aout/spu-vout的方法
/* Create the decoder configuration structure */
p_dec = CreateDecoder( p_parent, p_input, fmt, p_resource, p_sout );
if( p_dec == NULL )
{
msg_Err( p_parent, "could not create %s", psz_type );
vlc_dialog_display_error( p_parent, _("Streaming / Transcoding failed"),
_("VLC could not open the %s module."), vlc_gettext( psz_type ) );
return NULL;
}
if( !p_dec->p_module )
{
DecoderUnsupportedCodec( p_dec, fmt, !p_sout );
DeleteDecoder( p_dec );
return NULL;
}
p_dec->p_owner->p_clock = p_clock;
assert( p_dec->fmt_out.i_cat != UNKNOWN_ES );
// 设置decoder解码器解码工作线程优先级
if( p_dec->fmt_out.i_cat == AUDIO_ES )
i_priority = VLC_THREAD_PRIORITY_AUDIO;
else
i_priority = VLC_THREAD_PRIORITY_VIDEO;
// 创建线程,并执行【DecoderThread】该方法,分析见后续第2小节
/* Spawn the decoder thread */
if( vlc_clone( &p_dec->p_owner->thread, DecoderThread, p_dec, i_priority ) )
{
msg_Err( p_dec, "cannot spawn decoder thread" );
DeleteDecoder( p_dec );
return NULL;
}
return p_dec;
}
1.3、TsThread工作线程启动实现:【vlc/src/input/es_out_timeshift.c】
static int TsStart( es_out_t *p_out )
{
es_out_sys_t *p_sys = p_out->p_sys;
ts_thread_t *p_ts;
assert( !p_sys->b_delayed );
p_sys->p_ts = p_ts = calloc(1, sizeof(*p_ts));
if( !p_ts )
return VLC_EGENERIC;
// 由前面第1小节提到过,TS线程主要的任务就是实现文件缓存,缓存执行es_out相关命令
// 初始化临时文件大小路径等
p_ts->i_tmp_size_max = p_sys->i_tmp_size_max;
p_ts->psz_tmp_path = p_sys->psz_tmp_path;
p_ts->p_input = p_sys->p_input;
p_ts->p_out = p_sys->p_out;
vlc_mutex_init( &p_ts->lock );
vlc_cond_init( &p_ts->wait );
p_ts->b_paused = p_sys->b_input_paused && !p_sys->b_input_paused_source;
p_ts->i_pause_date = p_ts->b_paused ? mdate() : -1;
p_ts->i_rate_source = p_sys->i_input_rate_source;
p_ts->i_rate = p_sys->i_input_rate;
p_ts->i_rate_date = -1;
p_ts->i_rate_delay = 0;
p_ts->i_buffering_delay = 0;
p_ts->i_cmd_delay = 0;
p_ts->p_storage_r = NULL;
p_ts->p_storage_w = NULL;
// 此处设置【b_delayed】为true即开启了TS工作线程,线程执行方法【TsRun】见1.3.1小节分析
p_sys->b_delayed = true;
if( vlc_clone( &p_ts->thread, TsRun, p_ts, VLC_THREAD_PRIORITY_INPUT ) )
{
msg_Err( p_sys->p_input, "cannot create timeshift thread" );
TsDestroy( p_ts );
p_sys->b_delayed = false;
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
1.3.1、TsRun实现分析:【线程循环体实现方式】【vlc/src/input/es_out_timeshift.c】
static void *TsRun( void *p_data )
{
ts_thread_t *p_ts = p_data;
mtime_t i_buffering_date = -1;
for( ;; )
{
ts_cmd_t cmd;
mtime_t i_deadline;
bool b_buffering;
// 以下实现主要是取出此前第1小节的【TsPushCmd】存入的TS工作命令
// 通过读取命令个数来判断是否已使用完当前命令
/* Pop a command to execute */
vlc_mutex_lock( &p_ts->lock );
mutex_cleanup_push( &p_ts->lock );
for( ;; )
{
const int canc = vlc_savecancel();
b_buffering = es_out_GetBuffering( p_ts->p_out );
// out输出未暂停或正在缓冲数据中,并且TsPopCmdLocked若push的命令为空则必须执行下面的wait
if( ( !p_ts->b_paused || b_buffering ) && !TsPopCmdLocked( p_ts, &cmd, false ) )
{
vlc_restorecancel( canc );
break;
}
vlc_restorecancel( canc );
// 没有工作指令执行了则wait等待,由前面第1小节分析可知,
// TsPushCmd方法入栈push命令后会唤醒此处开始执行该命令任务
vlc_cond_wait( &p_ts->wait, &p_ts->lock );
}
if( b_buffering && i_buffering_date < 0 )
{
// 更新此处执行命令时的缓冲时间为命令入栈时的时间
i_buffering_date = cmd.i_date;
}
else if( i_buffering_date > 0 )
{
p_ts->i_buffering_delay += i_buffering_date - cmd.i_date; /* It is < 0 */
if( b_buffering )
i_buffering_date = cmd.i_date;
else
i_buffering_date = -1;
}
if( p_ts->i_rate_date < 0 )
p_ts->i_rate_date = cmd.i_date;
p_ts->i_rate_delay = 0;
if( p_ts->i_rate_source != p_ts->i_rate )
{
const mtime_t i_duration = cmd.i_date - p_ts->i_rate_date;
p_ts->i_rate_delay = i_duration * p_ts->i_rate / p_ts->i_rate_source - i_duration;
}
if( p_ts->i_cmd_delay + p_ts->i_rate_delay + p_ts->i_buffering_delay < 0 && p_ts->i_rate != p_ts->i_rate_source )
{
const int canc = vlc_savecancel();
/* Auto reset to rate 1.0 */
msg_Warn( p_ts->p_input, "es out timeshift: auto reset rate to %d", p_ts->i_rate_source );
p_ts->i_cmd_delay = 0;
p_ts->i_buffering_delay = 0;
p_ts->i_rate_delay = 0;
p_ts->i_rate_date = -1;
p_ts->i_rate = p_ts->i_rate_source;
// 重新设置码率,若成功则通过[input_ControlPush]将该控制命令事件压入控制命令栈
// 去唤醒【条件锁】此前的播放主线程循环体进行继续执行任务
if( !es_out_SetRate( p_ts->p_out, p_ts->i_rate_source, p_ts->i_rate ) )
{
vlc_value_t val = {
.i_int = p_ts->i_rate };
/* Warn back input
* FIXME it is perfectly safe BUT it is ugly as it may hide a
* rate change requested by user */
input_ControlPush( p_ts->p_input, INPUT_CONTROL_SET_RATE, &val );
}
vlc_restorecancel( canc );
}
// 当前命令的执行时间点
i_deadline = cmd.i_date + p_ts->i_cmd_delay + p_ts->i_rate_delay + p_ts->i_buffering_delay;
vlc_cleanup_pop();
vlc_mutex_unlock( &p_ts->lock );
/* Regulate the speed of command processing to the same one than
* reading */
vlc_cleanup_push( cmd_cleanup_routine, &cmd );
// 检查当前命令的执行时间点是否已到,若没到则进行线程睡眠到指定执行时间点系统唤醒
mwait( i_deadline );
vlc_cleanup_pop();
// 执行时间已到,开始执行当前命令
/* Execute the command */
const int canc = vlc_savecancel();
switch( cmd.i_type )
{
case C_ADD:
// 如此前第1小节点处【TsPushCmd】入栈的Add命令,该执行又回到了前面第1小节中已分析的流程了
CmdExecuteAdd( p_ts->p_out, &cmd );
CmdCleanAdd( &cmd );
break;
case C_SEND:
CmdExecuteSend( p_ts->p_out, &cmd );
CmdCleanSend( &cmd );
break;
case C_CONTROL:
CmdExecuteControl( p_ts->p_out, &cmd );
CmdCleanControl( &cmd );
break;
case C_DEL:
CmdExecuteDel( p_ts->p_out, &cmd );
break;
default:
vlc_assert_unreachable();
break;
}
vlc_restorecancel( canc );
}
return NULL;
}
2、DecoderThread解码器解码主工作线程【线程循环体】实现分析:【vlc/src/input/decoder.c】
/**
* The decoding main loop
*
* \param p_dec the decoder
*/
static void *DecoderThread( void *p_data )
{
decoder_t *p_dec = (decoder_t *)p_data;
decoder_owner_sys_t *p_owner = p_dec->p_owner;
bool paused = false;
// 下面构建了解码层的主循环体代码
/* The decoder's main loop */
// 待解码数据块通道FIFO先进先出block块队列加锁【此处FIFO队列实现在fifo.c中,单链表】
vlc_fifo_Lock( p_owner->p_fifo );
// 这是当前线程被取消时的监听处理,与【vlc_cleanup_pop】必须成对出现,底层使用宏定义块实现
vlc_fifo_CleanupPush( p_owner->p_fifo );
for( ;; )
{
// true表示需要decoder层立即执行刷新清空【丢弃】所有的缓冲buffer数据【p_fifo】(未完成的buffer数据)
// 该标识被设置为true场景:1、seeking或者deselecting a stream,或者deleting结束当前decoder线程
if( p_owner->flushing )
{
/* Flush before/regardless of pause. We do not want to resume just
* for the sake of flushing (glitches could otherwise happen). */
int canc = vlc_savecancel();
vlc_fifo_Unlock( p_owner->p_fifo );
// 刷新清空当前decoder层buffer数据和视频输出端vout数据,见2.1小节分析
/* Flush the decoder (and the output) */
DecoderProcessFlush( p_dec );
vlc_fifo_Lock( p_owner->p_fifo );
vlc_restorecancel( canc );
/* Reset flushing after DecoderProcess in case input_DecoderFlush
* is called again. This will avoid a second useless flush (but
* harmless). */
p_owner->flushing = false;
continue;
}
// 当前保存的暂停状态不一致时进入,更新输出端的播放或暂停状态
if( paused != p_owner->paused )
{
/* Update playing/paused status of the output */
int canc = vlc_savecancel();
// 请求暂停时间点
mtime_t date = p_owner->pause_date;
paused = p_owner->paused;
vlc_fifo_Unlock( p_owner->p_fifo );
// vout_ChangePause该方法内部通过请求等待条件锁【wait_acknowledge】
// 来确定vout端已执行完毕后再进行唤醒当前线程继续执行
// 注:只有音视频输出流才需关注暂停状态,若sout媒体输出流或字幕流则不需要处理
/* NOTE: Only the audio and video outputs care about pause. */
msg_Dbg( p_dec, "toggling %s", paused ? "resume" : "pause" );
if( p_owner->p_vout != NULL )
// 该方法内部实现为此前分析过的【vout_control_Push】和【vout_control_WaitEmpty】实现流程
// 即将当前暂停/播放状态指令加入到vout指令控制层并根据条件wait当前decoder线程,
// 然后等待其接受执行指令后唤醒当前decoder线程继续执行
// 后续流程分析见vout视频输出端分析
vout_ChangePause( p_owner->p_vout, paused, date );
if( p_owner->p_aout != NULL )
// 更新【aout_owner_t->sync.end】同步最后一次显示的时间值,
// 并(可能)调用【aout->pause】方法传递状态指令事件给vout端
// 后续流程分析见aout音频输出端分析 TODO
aout_DecChangePause( p_owner->p_aout, paused, date );
vlc_restorecancel( canc );
vlc_fifo_Lock( p_owner->p_fifo );
continue;
}
// 当前若是暂停状态且当前解码帧数据为空时进入,wait等待恢复播放
if( p_owner->paused && p_owner->frames_countdown == 0 )
{
/* Wait for resumption from pause */
p_owner->b_idle = true;
// 注:通过相关代码的分析,该wait等待事件其实是在控制demuxer层线程的解复用速度
// 而不是唤醒decoder解码层的wait等待事件,这点要注意。
// 并且该等待事件是在[vlc/src/input/es_out.c]的[EsOutControlLocked]方法中的
// 【ES_OUT_SET_PCR和ES_OUT_SET_GROUP_PCR】事件进行执行wait的,而该事件是demuxer层进行调用的。
// 通过其英文注释可大概知晓用意为:控制demuxer层的解复用速度来达到控制seek/倍速播放等功能
vlc_cond_signal( &p_owner->wait_acknowledge );
// FIFO优先block块队列进入等待状态[wait for data]
vlc_fifo_Wait( p_owner->p_fifo );
p_owner->b_idle = false;
continue;
}
vlc_cond_signal( &p_owner->wait_fifo );
// 若此线程要求停止了,则在此处加快强制取消当前线程
vlc_testcancel(); /* forced expedited cancellation in case of stop */
// 读取FIFO块队列单链表中的第一个待解码数据,使其出队。空代表无需要解码的数据
// 见2.2小节分析
block_t *p_block = vlc_fifo_DequeueUnlocked( p_owner->p_fifo );
if( p_block == NULL )
{
// [b_draining]为true时即当前流EOS结束状态【通过其他代码分析该状态值代表当前流EOS或当前es_out进行delete了】,
// [b_draining]为true时表示没有更多的待解码block数据了,
// 解复用层会在EOS时唤醒此处进行最后的FIFO队列数据最后解码,并输出后清空fifo数据buffer
// 如若条件成立则wait等待解复用的待解码block块数据(或者等待消费请求)
// 由前面可知最开始线程启动时还没有解复用出来的待解码block数据,
// 因此进入该if条件中进行等待解复用端传入待解码block数据
if( likely(!p_owner->b_draining) )
{
/* Wait for a block to decode (or a request to drain) */
p_owner->b_idle = true;
// 注:通过相关代码的分析,该wait等待事件其实是在控制demuxer层的解复用速度
vlc_cond_signal( &p_owner->wait_acknowledge );
// FIFO优先block块队列进入等待状态【即等待解复用端传入待解码block数据】
// [wait for a data demuxed by demuxer layer]
vlc_fifo_Wait( p_owner->p_fifo );
p_owner->b_idle = false;
continue;
}
// 此处FIFO队列数据已经清空了并且有一个待消费请求,
/* We have emptied the FIFO and there is a pending request to
* drain. Pass p_block = NULL to decoder just once. */
}
vlc_fifo_Unlock( p_owner->p_fifo );
// 记录当前线程是否取消状态
int canc = vlc_savecancel();
// 执行解码出block块数据,见2.3小节分析
DecoderProcess( p_dec, p_block );
// 根据注释:当前decoder解码器已耗尽[刷新清空或丢弃buffer数据]即当前解码器没有数据可解码状态【相当于销毁/退出解码器工作】
// 并且此时所有已解码的buffer数据进入到了output,因此通知消费该output中的已解码数据
if( p_block == NULL )
{
/* Draining: the decoder is drained and all decoded buffers are
* queued to the output at this point. Now drain the output. */
if( p_owner->p_aout != NULL )
aout_DecFlush( p_owner->p_aout, true );
}
vlc_restorecancel( canc );
/* TODO? Wait for draining instead of polling. */
vlc_mutex_lock( &p_owner->lock );
// [b_draining]为true时即当前流EOS结束状态【通过其他代码分析该状态值代表当前流EOS或当前es_out进行delete了】,
if( p_owner->b_draining && (p_block == NULL) )
{
p_owner->b_draining = false;
p_owner->drained = true;
}
vlc_fifo_Lock( p_owner->p_fifo );
// 注:通过相关代码的分析,该wait等待事件其实是在控制demuxer层的解复用速度
vlc_cond_signal( &p_owner->wait_acknowledge );
vlc_mutex_unlock( &p_owner->lock );
}
vlc_cleanup_pop();
vlc_assert_unreachable();
}
2.1、DecoderProcessFlush实现分析:【vlc/src/input/decoder.c】
// 刷新清空当前decoder层buffer数据和视频输出端vout数据
static void DecoderProcessFlush( decoder_t *p_dec )
{
decoder_owner_sys_t *p_owner = p_dec->p_owner;
decoder_t *p_packetizer = p_owner->p_packetizer;
if( p_owner->error )
return;
// 重打包数据的解码器执行decoder的[pf_flush]刷新buffer数据方法
if( p_packetizer != NULL && p_packetizer->pf_flush != NULL )
p_packetizer->pf_flush( p_packetizer );
// 解码器执行[pf_flush]刷新buffer数据方法
if ( p_dec->pf_flush != NULL )
p_dec->pf_flush( p_dec );
// 字幕流子解码器执行[pf_flush]刷新buffer数据方法【字幕流可能需要多个不同解码器进行解码】
/* flush CC sub decoders */
if( p_owner->cc.b_supported )
{
for( int i=0; i<MAX_CC_DECODERS; i++ )
{
decoder_t *p_subdec = p_owner->cc.pp_decoder[i];
if( p_subdec && p_subdec->pf_flush )
p_subdec->pf_flush( p_subdec );
}
}
// 以上都是执行decoder解码器模块的[pf_flush]刷新buffer数据方法,
// 该分析见decoder层实现分析章节进行分析 TODO
#ifdef ENABLE_SOUT
// 若启用了流媒体输出方式
if ( p_owner->p_sout_input != NULL )
{
// 刷新清空sout流媒体输出端的buffer数据,
// 调用了【sout_stream_t】的[pf_flush]刷新buffer数据方法。
// 见2.1.1小节分析
sout_InputFlush( p_owner->p_sout_input );
}
#endif
if( p_dec->fmt_out.i_cat == AUDIO_ES )
{
if( p_owner->p_aout )
// 执行刷新清空aout音频输出端的buffer数据
// 见2.1.2小节分析
aout_DecFlush( p_owner->p_aout, false );
}
else if( p_dec->fmt_out.i_cat == VIDEO_ES )
{
if( p_owner->p_vout )
// 执行刷新清空vout视频输出端的buffer数据
// 见2.1.3小节分析
vout_Flush( p_owner->p_vout, VLC_TS_INVALID+1 );
}
else if( p_dec->fmt_out.i_cat == SPU_ES )
{
// 执行刷新清空spu-vout视频字幕输出端的buffer数据
if( p_owner->p_spu_vout )
{
// 从resource对象中获取其持有的vout线程描述处理对象,并增加其引用计数
vout_thread_t *p_vout = input_resource_HoldVout( p_owner->p_resource );
if( p_vout && p_owner->p_spu_vout == p_vout )
// 见2.1.4小节分析
vout_FlushSubpictureChannel( p_vout, p_owner->i_spu_channel );
if( p_vout )
vlc_object_release( p_vout );
}
}
vlc_mutex_lock( &p_owner->lock );
p_owner->i_preroll_end = INT64_MIN;
vlc_mutex_unlock( &p_owner->lock );
}
2.1.1、sout_InputFlush实现分析:
// 【vlc/src/stream_out/stream_out.c】
void sout_InputFlush( sout_packetizer_input_t *p_input )
{
sout_instance_t *p_sout = p_input->p_sout;
vlc_mutex_lock( &p_sout->lock );
sout_StreamFlush( p_sout->p_stream, p_input->id );
vlc_mutex_unlock( &p_sout->lock );
}
// 【vlc/include/vlc_sout.h】
static inline void sout_StreamFlush( sout_stream_t *s,
sout_stream_id_sys_t *id )
{
if (s->pf_flush)
// 调用了【sout_stream_t】的[pf_flush]刷新buffer数据方法。
// 见sout输出端章节分析 TODO
s->pf_flush( s, id );
}
2.1.2、aout_DecFlush实现分析:
void aout_DecFlush (audio_output_t *aout, bool wait)
{
aout_owner_t *owner = aout_owner (aout);
aout_OutputLock (aout);
owner->sync.end = VLC_TS_INVALID;
if (owner->mixer_format.i_format)
{
// 若有音频格式信息
if (wait)
{
// 若需要wait等待aout端播放当前已解码的数据
// 通过音频滤波器链滤波【获取】音频缓冲区数据,并【刷新清空】filters端数据
// 见本小节后续分析
block_t *block = aout_FiltersDrain (owner->filters);
if (block)
// 执行audio数据输出播放流程
// 见本小节后续分析
aout_OutputPlay (aout, block);
}
else
// 若不需要等待aout音频输出端播放已解码数据,
// 则直接进行aout的filters端数据的刷新清空【即丢弃已解码音频数据】
// 见本小节后续分析
aout_FiltersFlush (owner->filters);
// 内部调用了【aout->flush (aout, wait);】刷新清空aout视频输出端的buffer数据。
// 执行该方法用于快速用于音频seek或stop操作等
// 【flush】方法实现分析见aout音频输出端章节分析 TODO
aout_OutputFlush (aout, wait);
}
aout_OutputUnlock (aout);
}
// 【vlc/src/audio_output/filters.c】
block_t *aout_FiltersDrain (aout_filters_t *filters)
{
// 遍历所有的音频filters滤波器数据,获取所有filters携带的音频总数据并返回,
// 然后清空filters携带的数据
/* Drain the filters pipeline */
block_t *block = aout_FiltersPipelineDrain (filters->tab, filters->count);
if (filters->resampler != NULL)
{
// 若用户有设置重采样配置,则根据重采样配置执行处理得到目标数据
block_t *chain = NULL;
filters->resampler->fmt_in.audio.i_rate += filters->resampling;
if (block)
{
// 最终调用了【filter->pf_audio_filter (filter, block)】
// 通过过滤器过滤选择一个音频数据buffer
/* Resample the drained block from the filters pipeline */
block = aout_FiltersPipelinePlay (&filters->resampler, 1, block);
if (block)
block_ChainAppend (&chain, block);
}
// 获取重采样过滤器音频数据,并追加到已存在音频数据中
/* Drain the resampler filter */
block = aout_FiltersPipelineDrain (&filters->resampler, 1);
if (block)
block_ChainAppend (&chain, block);
filters->resampler->fmt_in.audio.i_rate -= filters->resampling;
return chain ? block_ChainGather (chain) : NULL;
}
else
return block;
}
// 【vlc/src/audio_output/output.c】
/**
* Plays a decoded audio buffer.
* \note This can only be called after a successful aout_OutputNew().
* \warning The caller must hold the audio output lock.
*/
void aout_OutputPlay (audio_output_t *aout, block_t *block)
{
aout_OutputAssertLocked (aout);
#ifndef NDEBUG
// 此处执行条件为:若没有定义【不需要debug模式】即debug模式时进行执行check代码
aout_owner_t *owner = aout_owner (aout);
assert (owner->mixer_format.i_frame_length > 0);
assert (block->i_buffer == 0 || block->i_buffer / block->i_nb_samples ==
owner->mixer_format.i_bytes_per_frame /
owner->mixer_format.i_frame_length);
#endif
// 执行aout音频输出端播放请求方法
// 见aout音频输出端章节分析 TODO
aout->play (aout, block);
}
// 【vlc/src/audio_output/filters.c】
void aout_FiltersFlush (aout_filters_t *filters)
{
aout_FiltersPipelineFlush (filters->tab, filters->count);
if (filters->resampler != NULL)
aout_FiltersPipelineFlush (&filters->resampler, 1);
}
/**
* Flush the chain of filters.
*/
static void aout_FiltersPipelineFlush(filter_t *const *filters,
unsigned count)
{
for (unsigned i = 0; i < count; i++)
filter_Flush (filters[i]);
}
// 【vlc/include/vlc_filter.h】
/**
* Flush a filter
*
* This function will flush the state of a filter (audio or video).
*/
static inline void filter_Flush( filter_t *p_filter )
{
if( p_filter->pf_flush != NULL )
// 最终调用了filter_t过滤器【图像滤镜等】的【pf_flush】刷新清空方法
// 见aout或vout音频输出端章节分析 TODO
p_filter->pf_flush( p_filter );
}
2.1.3、vout_Flush实现分析:
// [vlc/src/video_output/vieo_output.c]
// 功能:刷新清空当前vout视频输出端的图像队列旧数据
void vout_Flush(vout_thread_t *vout, mtime_t date)
{
// 此处两方法实现为:先将当前控制指令传入vout控制命令列表中,
// 然后进行等待vout端处理完毕该指令后唤醒此处的wait事件
vout_control_PushTime(&vout->p->control, VOUT_CONTROL_FLUSH, date);
vout_control_WaitEmpty(&vout->p->control);
}
// 【vlc/src/video_output/control.c】
void vout_control_PushTime(vout_control_t *ctrl, int type, mtime_t time)
{
vout_control_cmd_t cmd;
vout_control_cmd_Init(&cmd, type);
cmd.u.time = time;
vout_control_Push(ctrl, &cmd);
}
// 【vlc/src/video_output/control.c】
void vout_control_Push(vout_control_t *ctrl, vout_control_cmd_t *cmd)
{
vlc_mutex_lock(&ctrl->lock);
if (!ctrl->is_dead) {
// 若vout视频输出线程控制端没关闭,则将当前控制指令传入控制端,
// 并【若wait则】唤醒vout端继续执行
// 见vout视频输出端章节分析 TODO
ARRAY_APPEND(ctrl->cmd, *cmd);
vlc_cond_signal(&ctrl->wait_request);
} else {
vout_control_cmd_Clean(cmd);
}
vlc_mutex_unlock(&ctrl->lock);
}
// 【vlc/src/video_output/control.c】
void vout_control_WaitEmpty(vout_control_t *ctrl)
{
vlc_mutex_lock(&ctrl->lock);
while ((ctrl->cmd.i_size > 0 || ctrl->is_processing) && !ctrl->is_dead)
// 等待vout端处理完毕该指令后唤醒此处的wait事件
// 见vout视频输出端章节分析 TODO
vlc_cond_wait(&ctrl->wait_acknowledge, &ctrl->lock);
vlc_mutex_unlock(&ctrl->lock);
}
2.1.4、vout_FlushSubpictureChannel实现分析:
//【vlc/src/video_output/video_output.c】
void vout_FlushSubpictureChannel( vout_thread_t *vout, int channel )
{
vout_control_PushInteger(&vout->p->control, VOUT_CONTROL_FLUSH_SUBPICTURE,
channel);
}
//【vlc/src/video_output/control.c】
void vout_control_PushInteger(vout_control_t *ctrl, int type, int integer)
{
vout_control_cmd_t cmd;
vout_control_cmd_Init(&cmd, type);
cmd.u.integer = integer;
// 该方法2.1.3中已分析过即:
// 若vout视频输出线程控制端没关闭,则将当前控制指令传入控制端,
// 并【若wait则】唤醒vout端继续执行
// 见vout视频输出端章节分析 TODO
vout_control_Push(ctrl, &cmd);
}
2.2、vlc_fifo_DequeueUnlocked实现分析:【vlc/src/input/decoder.c】
block_t *vlc_fifo_DequeueUnlocked(block_fifo_t *fifo)
{
// 此处检查是否有加锁后才访问此方法的
vlc_assert_locked(&fifo->lock);
// 取出fifo块队列中的第一个待解码块数据
block_t *block = fifo->p_first;
if (block == NULL)
return NULL; /* Nothing to do */
// 单链表操作,将第二个块数据作为第一个数
fifo->p_first = block->p_next;
if (block->p_next == NULL)
fifo->pp_last = &fifo->p_first;
block->p_next = NULL;
assert(fifo->i_depth > 0);
// 队列链表长度减一
fifo->i_depth--;
assert(fifo->i_size >= block->i_buffer);
// 队列中剩余数据大小:队列中总数据大小减去出栈的快大小
fifo->i_size -= block->i_buffer;
return block;
}
2.3、DecoderProcess实现分析:请求解码一个block块 【vlc/src/input/decoder.c】
/**
* Decode a block
*
* \param p_dec the decoder object
* \param p_block the block to decode
*/
static void DecoderProcess( decoder_t *p_dec, block_t *p_block )
{
decoder_owner_sys_t *p_owner = p_dec->p_owner;
if( p_owner->error )
goto error;
// 根据注释:该【reload】原子字段值表示是否重新加载decoder解码模块
/* Here, the atomic doesn't prevent to miss a reload request.
* DecoderProcess() can still be called after the decoder module or the
* audio output requested a reload. This will only result in a drop of an
* input block or an output buffer. */
enum reload reload;
if( ( reload = atomic_exchange( &p_owner->reload, RELOAD_NO_REQUEST ) ) )
{
msg_Warn( p_dec, "Reloading the decoder module%s",
reload == RELOAD_DECODER_AOUT ? " and the audio output" : "" );
// 此处卸载此前加载的decoder模块,并根据当前输入解码参数重新加载对应的decoder模块
// 由后续分析可知,该处理原因是当前输入流媒体格式发生了变化,因此需要重加载对应的decoder模块
if( ReloadDecoder( p_dec, false, &p_dec->fmt_in, reload ) != VLC_SUCCESS )
goto error;
}
// 是否需要分包数据【vout等流媒体传输形式】,true为需要分包数据
bool packetize = p_owner->p_packetizer != NULL;
if( p_block )
{
if( p_block->i_buffer <= 0 )
goto error;
vlc_mutex_lock( &p_owner->lock );
// 当前待解码block块数据不为空时则根据当前block块的flag标志位来更新preroll该值
DecoderUpdatePreroll( &p_owner->i_preroll_end, p_block );
vlc_mutex_unlock( &p_owner->lock );
if( unlikely( p_block->i_flags & BLOCK_FLAG_CORE_PRIVATE_RELOADED ) )
{
/* This block has already been packetized */
packetize = false;
}
}
#ifdef ENABLE_SOUT
// 开启了流媒体传输方式,则将数据流进行sout解码流程
if( p_owner->p_sout != NULL )
{
// 见2.3.1小节分析
DecoderProcessSout( p_dec, p_block );
return;
}
#endif
// 若未开启流媒体输出方式或p_owner->p_sout为空,进入此处
if( packetize )
{
// 该条件的意义是:需要当前特殊的分包解码器重新分包待解码的数据,
// 用来给当前特殊的分包解码器进行分包化特殊数据格式解码
block_t *p_packetized_block;
block_t **pp_block = p_block ? &p_block : NULL;
decoder_t *p_packetizer = p_owner->p_packetizer;
// 通过分包解码器模块将当前待解码块数据进行特殊分组成另外的分包块数据
while( (p_packetized_block =
p_packetizer->pf_packetize( p_packetizer, pp_block ) ) )
{
if( !es_format_IsSimilar( &p_dec->fmt_in, &p_packetizer->fmt_out ) )
{
// 此处因为输入流格式改变会重启解码层模块
msg_Dbg( p_dec, "restarting module due to input format change");
// 此注释意思为:传入NULL是decoder层要求解码层模块结束解码
// 并且返回所有可使用的输出帧frames或块block数据。
// 该方法后续分析
/* Drain the decoder module */
DecoderDecode( p_dec, NULL );
// 此处卸载此前加载的decoder模块,并根据当前输入解码参数重新加载对应的decoder模块
if( ReloadDecoder( p_dec, false, &p_packetizer->fmt_out,
RELOAD_DECODER ) != VLC_SUCCESS )
{
block_ChainRelease( p_packetized_block );
return;
}
}
// 这部分从注解来看应该是解码器解码来获取字幕数据
if( p_packetizer->pf_get_cc )
PacketizerGetCc( p_dec, p_packetizer );
while( p_packetized_block )
{
// 已分包的有效数据块block单链表队列,则进入该循环
// 取出第一个已分包的block块数据进行解码
block_t *p_next = p_packetized_block->p_next;
p_packetized_block->p_next = NULL;
// 进行解码请求
// 见2.3.2小节分析
DecoderDecode( p_dec, p_packetized_block );
if( p_owner->error )
{
// 解码失败
block_ChainRelease( p_next );
return;
}
// 解码成功则继续下一个数据块解码
p_packetized_block = p_next;
}
}
// 当前待解码块数据pp_block已分包并解码完成或分包失败则清空输出当前已编码数据
/* Drain the decoder after the packetizer is drained */
if( !pp_block )
DecoderDecode( p_dec, NULL );
}
else
// 直接进行解码请求,不特殊的分包解码器重新分包待解码的数据
// 见2.3.2小节分析
DecoderDecode( p_dec, p_block );
return;
error:
if( p_block )
block_Release( p_block );
}
2.3.1、DecoderProcessSout实现分析:【vlc/src/input/decoder.c】
/* This function process a block for sout
*/
static void DecoderProcessSout( decoder_t *p_dec, block_t *p_block )
{
decoder_owner_sys_t *p_owner = p_dec->p_owner;
block_t *p_sout_block;
block_t **pp_block = p_block ? &p_block : NULL;
// 【pf_packetize】该方法主要功能:将输入待解码块block进行分包输出返回多个数据包格式的块信息,
// 主要用于流媒体输出【如流媒体输出需要在网络上等传输更小的快单位数据】
// 注意:此处不是要解码,而是要将待解码的数据进行分包进行流媒体传输
// 【pf_packetize】为分包模块的处理,见另一章节分析
while( ( p_sout_block =
p_dec->pf_packetize( p_dec, pp_block ) ) )
{
if( p_owner->p_sout_input == NULL )
{
vlc_mutex_lock( &p_owner->lock );
// 更新当前流输出output输出端的媒体流输出格式信息
DecoderUpdateFormatLocked( p_dec );
p_owner->fmt.i_group = p_dec->fmt_in.i_group;
p_owner->fmt.i_id = p_dec->fmt_in.i_id;
if( p_dec->fmt_in.psz_language )
{
free( p_owner->fmt.psz_language );
p_owner->fmt.psz_language =
strdup( p_dec->fmt_in.psz_language );
}
vlc_mutex_unlock( &p_owner->lock );
// 支持媒体流输出方式,则创建媒体流输出分包结构体信息
p_owner->p_sout_input =
sout_InputNew( p_owner->p_sout, &p_owner->fmt );
if( p_owner->p_sout_input == NULL )
{
msg_Err( p_dec, "cannot create packetizer output (%4.4s)",
(char *)&p_owner->fmt.i_codec );
p_owner->error = true;
if(p_block)
block_Release(p_block);
// 失败则释放block块单链表所有数据
block_ChainRelease(p_sout_block);
break;
}
}
while( p_sout_block )
{
// 已分包数据流block块数据链表不为空时,得到分包的可能更小的block块数据链表
block_t *p_next = p_sout_block->p_next;
p_sout_block->p_next = NULL;
// 然后进行当前单独第一个已分包块数据进行媒体流传输播放流程
// 见2.3.1.1小节分析
if( DecoderPlaySout( p_dec, p_sout_block ) == VLC_EGENERIC )
{
msg_Err( p_dec, "cannot continue streaming due to errors with codec %4.4s",
(char *)&p_owner->fmt.i_codec );
p_owner->error = true;
/* Cleanup */
if( p_block )
block_Release( p_block );
block_ChainRelease( p_next );
return;
}
// 循环再次执行链表中下一个block数据,
// 直到为空结束本次循环进行外层大循环可能继续分包数据
p_sout_block = p_next;
}
}
}
2.3.1.1、DecoderPlaySout输出流播放实现分析:
// 【vlc/src/input/decoder.c】
static int DecoderPlaySout( decoder_t *p_dec, block_t *p_sout_block )
{
decoder_owner_sys_t *p_owner = p_dec->p_owner;
assert( p_owner->p_clock );
assert( !p_sout_block->p_next );
vlc_mutex_lock( &p_owner->lock );
// 通过相关代码分析:【b_waiting】为true时同时设置了b_has_data为false,
// 这两设置标识了需要当前所有sout输出解码或打包的block数据到输出播放端流程被中断
// 即让decoder层线程进行wait,即暂停解码层解码操作也暂停了解码层输出给播放输出端的执行。
if( p_owner->b_waiting )
{
p_owner->b_has_data = true;
// 通过条件锁唤醒其demuxer工作线程继续demux数据。
// 注:通过相关代码的分析,该wait等待事件其实是在控制demuxer层的解复用速度
vlc_cond_signal( &p_owner->wait_acknowledge );
}
// 使用请求条件锁等待demuxer层处理完毕后进行唤醒此处,当然也可能不需要等待,等待是有条件判断成立才进入的
// vlc_cond_wait( &p_dec->p_owner->wait_request, &p_owner->lock );
DecoderWaitUnblock( p_dec );
// 根据时钟clock控制TS时间策略,更正更新当前block的DTS、PTS、时长等信息
DecoderFixTs( p_dec, &p_sout_block->i_dts, &p_sout_block->i_pts,
&p_sout_block->i_length, NULL, INT64_MAX );
vlc_mutex_unlock( &p_owner->lock );
// 检查流输出有效性,发送当前解码块block数据给对应媒体流输出端模块进行传输
/* FIXME --VLC_TS_INVALID inspect stream_output*/
return sout_InputSendBuffer( p_owner->p_sout_input, p_sout_block );
}
//【vlc/src/stream_output/stream_output.c】
int sout_InputSendBuffer( sout_packetizer_input_t *p_input,
block_t *p_buffer )
{
sout_instance_t *p_sout = p_input->p_sout;
int i_ret;
vlc_mutex_lock( &p_sout->lock );
// 由此可知调用了流输出端的流输出模块来处理的即具体的流输出模块来输出分包后的block数据
// 【pf_send】为流媒体输出模块的处理,见另一章节分析
i_ret = p_sout->p_stream->pf_send( p_sout->p_stream,
p_input->id, p_buffer );
vlc_mutex_unlock( &p_sout->lock );
return i_ret;
}
2.3.2、DecoderDecode解码请求实现分析:【vlc/src/input/decoder.c】
// 备注:当p_block参数传入NULL时,decoder层要求解码层模块结束解码
// 并且返回所有可使用的输出帧frames或块block数据。
static void DecoderDecode( decoder_t *p_dec, block_t *p_block )
{
decoder_owner_sys_t *p_owner = p_dec->p_owner;
// 由此可知调用了解码器模块的解码方法,
// 该方法是在解码模块加载时模块初始化入口方法中进行赋值的
// 【pf_decode】为解码器模块的处理,见另一章节分析 TODO
int ret = p_dec->pf_decode( p_dec, p_block );
switch( ret )
{
case VLCDEC_SUCCESS:
// 该分析见decoder层解码器章节分析 TODO
p_owner->pf_update_stat( p_owner, 1, 0 );
break;
case VLCDEC_ECRITICAL:
p_owner->error = true;
break;
case VLCDEC_RELOAD:
// 修改原子性标记值为需要reload重加载解码器模块标记
RequestReload( p_dec );
if( unlikely( p_block == NULL ) )
break;
if( !( p_block->i_flags & BLOCK_FLAG_CORE_PRIVATE_RELOADED ) )
{
p_block->i_flags |= BLOCK_FLAG_CORE_PRIVATE_RELOADED;
// 此处内部实现会根据上述标记值来重加载解码器模块
DecoderProcess( p_dec, p_block );
}
else /* We prefer loosing this block than an infinite recursion */
block_Release( p_block );
break;
default:
vlc_assert_unreachable();
}
}
3、接着前面【sout_InputSendBuffer】中的【p_sout->p_stream->pf_send】该调用的实现分析:
// 该分析见sout媒体流输出端章节分析 TODO
4、p_dec->pf_decode( p_dec, p_block )实现分析:
由此前分析decoder初始化过程可知:decoder加载了具体的解码模块,因此该方法是在解码模块加载时模块初始化入口方法中进行赋值的,因此此处已分析h264
// 该分析见decoder层解码器模块第六章节分析 TODO