一、播放列表线程功能
二、列表对象
vlc与c++一样,有着封装的结构,一个指针指向的对象,有公共数据和私有数据,公共对象如下:
struct playlist_t
{
VLC_COMMON_MEMBERS
playlist_item_array_t items; //播放项数组
//当前播放项
playlist_item_array_t current; /**< Items currently being played */
int i_current_index; /**< Index in current array */
//预先准备的播放项
playlist_item_t root;
playlist_item_t *p_playing;
playlist_item_t *p_media_library;
};
私有数据如下:
typedef struct playlist_private_t
{
playlist_t public_data;//公共数据
struct intf_thread_t *interface; //界面线程
void *input_tree; //播放列表树
void *id_tree; /**播放列表id*/
vlc_sd_internal_t **pp_sds;
int i_sds; /**服务发现模块序号 */
input_thread_t * p_input; /**input线程*/
input_resource_t * p_input_resource; /**< 输入资源 */
vlc_renderer_item_t *p_renderer;//渲染项
struct {
/* 播放列表项状态,界面线程可以触发它*/
playlist_item_t * p_item; /**< Currently playing/active item */
playlist_item_t * p_node; /**< Current node to play from */
} status;
struct {
/* 请求,使用这个给playlist线程发送一个请求 */
playlist_item_t * p_node; /**< requested node to play from */
playlist_item_t * p_item; /**< requested item to play in the node */
int i_skip; /**< Number of items to skip */
bool b_request;/**< Set to true by the requester
The playlist sets it back to false
when processing the request */
bool input_dead; /**< Set when input has finished. */
} request;
vlc_thread_t thread; /**< engine thread */
vlc_mutex_t lock; /**< dah big playlist global lock */
vlc_cond_t signal; /**< 唤醒playlist线程 */
bool killed; /**< playlist is shutting down */
bool cork_effective; /**< Corked while actively playing */
int i_last_playlist_id; /**< Last id to an item */
bool b_reset_currently_playing; /** Reset current item array */
bool b_tree; /**< Display as a tree */
bool b_preparse; /**< Preparse items */
} playlist_private_t;
输入项结构input_item_t
struct input_item_t
{
char *psz_name; /**< text describing this item */
char *psz_uri; /**< mrl of this item */
int i_options; /**< Number of input options */
char **ppsz_options; /**< Array of input options */
uint8_t *optflagv; /**< Some flags of input options */
unsigned optflagc;
input_item_opaque_t *opaques; /**< List of opaque pointer values */
mtime_t i_duration; /**< Duration in microseconds */
int i_categories; /**< Number of info categories */
info_category_t **pp_categories; /**< Pointer to the first info category */
int i_es; /**< Number of es format descriptions */
es_format_t **es; /**< Es formats */
input_stats_t *p_stats; /**< Statistics */
vlc_meta_t *p_meta;
int i_epg; /**< Number of EPG entries */
vlc_epg_t **pp_epg; /**< EPG entries */
int64_t i_epg_time; /** EPG timedate as epoch time */
const vlc_epg_t *p_epg_table; /** running/selected program cur/next EPG table */
int i_slaves; /**< Number of slaves */
input_item_slave_t **pp_slaves; /**< Slave entries that will be loaded by
the input_thread */
vlc_event_manager_t event_manager;//事件管理结构
vlc_mutex_t lock; /**< Lock for the item */
uint8_t i_type; /**< Type (file, disc, ... see input_item_type_e) */
bool b_net; /**< Net: always true for TYPE_STREAM, it
depends for others types */
bool b_error_when_reading;/**< Error When Reading */
int i_preparse_depth; /**< How many level of sub items can be preparsed:
-1: recursive, 0: none, >0: n levels */
bool b_preparse_interact; /**< Force interaction with the user when
preparsing.*/
};
播放项playlist_item_t结构如下:
struct playlist_item_t
{
input_item_t *p_input; /**指向输入项 */
playlist_item_t **pp_children; /**< 播放项的子项*/
playlist_item_t *p_parent; /**< 播放项的父项 */
int i_children; /**< 子项个数 */
unsigned i_nb_played; /**< 播放次数 */
int i_id; /**< 播放id */
uint8_t i_flags; /**< Flags \see playlist_item_flags_e */
};
二、列表对象创建
列表对象playlist_t在程序启动的时候创建,并在创建的时候启动播放列表线程
playlist_t *playlist_Create( vlc_object_t *p_parent )
{
playlist_t *p_playlist;
playlist_private_t *p;
//列表对象父对象是libvlc_int_t,也就是vlc的实例对象
p = vlc_custom_create( p_parent, sizeof( *p ), "playlist" );
if( !p )
return NULL;
//公共对外的对象数据
p_playlist = &p->public_data;
p->input_tree = NULL;
p->id_tree = NULL;
TAB_INIT( pl_priv(p_playlist)->i_sds, pl_priv(p_playlist)->pp_sds );
// 初始化列表对象的元属性,比如"loop",播放列表是否重复播放属性
VariablesInit( p_playlist );
vlc_mutex_init( &p->lock );
vlc_cond_init( &p->signal );
p->killed = false;
//初始化列表对象特有的数据
pl_priv(p_playlist)->i_last_playlist_id = 0;
pl_priv(p_playlist)->p_input = NULL;
ARRAY_INIT( p_playlist->items );
ARRAY_INIT( p_playlist->current );
p_playlist->i_current_index = 0;
pl_priv(p_playlist)->b_reset_currently_playing = true;
pl_priv(p_playlist)->b_tree = var_InheritBool( p_parent, "playlist-tree" );
pl_priv(p_playlist)->b_preparse = var_InheritBool( p_parent, "auto-preparse" );
p_playlist->root.p_input = NULL;
p_playlist->root.pp_children = NULL;
p_playlist->root.i_children = 0;
p_playlist->root.i_nb_played = 0;
p_playlist->root.i_id = 0;
p_playlist->root.i_flags = 0;
/* Create the root, playing items and meida library nodes */
playlist_item_t *playing, *ml;
PL_LOCK;
//创建播放节点
playing = playlist_NodeCreate( p_playlist, _( "Playlist" ),
&p_playlist->root, PLAYLIST_END,
PLAYLIST_RO_FLAG|PLAYLIST_NO_INHERIT_FLAG );
if( var_InheritBool( p_parent, "media-library") )
ml = playlist_NodeCreate( p_playlist, _( "Media Library" ),
&p_playlist->root, PLAYLIST_END,
PLAYLIST_RO_FLAG|PLAYLIST_NO_INHERIT_FLAG );
else
ml = NULL;
PL_UNLOCK;
if( unlikely(playing == NULL) )
abort();
p_playlist->p_playing = playing;
p_playlist->p_media_library = ml;
/* Initial status */
pl_priv(p_playlist)->status.p_item = NULL;
pl_priv(p_playlist)->status.p_node = p_playlist->p_playing;
pl_priv(p_playlist)->request.b_request = false;
p->request.input_dead = false;
if (ml != NULL)
playlist_MLLoad( p_playlist );
//创建输入资源
p->p_input_resource = input_resource_New( VLC_OBJECT( p_playlist ) );
if( unlikely(p->p_input_resource == NULL) )
abort();
/* Audio output (needed for volume and device controls). */
audio_output_t *aout = input_resource_GetAout( p->p_input_resource );
if( aout != NULL )
input_resource_PutAout( p->p_input_resource, aout );
/* Initialize the shared HTTP cookie jar */
vlc_value_t cookies;
cookies.p_address = vlc_http_cookies_new();
if ( likely(cookies.p_address) )
{
var_Create( p_playlist, "http-cookies", VLC_VAR_ADDRESS );
var_SetChecked( p_playlist, "http-cookies", VLC_VAR_ADDRESS, cookies );
}
//激活列表线程
playlist_Activate (p_playlist);
/* Add service discovery modules */
char *mods = var_InheritString( p_playlist, "services-discovery" );
if( mods != NULL )
{
char *s = mods, *m;
while( (m = strsep( &s, " :," )) != NULL )
playlist_ServicesDiscoveryAdd( p_playlist, m );
free( mods );
}
return p_playlist;
}
三、列表线程启动
void playlist_Activate( playlist_t *p_playlist )
{
playlist_private_t *p_sys = pl_priv(p_playlist);
if( vlc_clone( &p_sys->thread, Thread, p_playlist,
VLC_THREAD_PRIORITY_LOW ) )
{
msg_Err( p_playlist, "cannot spawn playlist thread" );
abort();
}
}
列表线程函数Thread,如下:
static void *Thread ( void *data )
{
playlist_t *p_playlist = data;
playlist_private_t *p_sys = pl_priv(p_playlist);
bool played = false;
PL_LOCK;
//程序没停止就一直循环
while( !p_sys->killed )
{
/* Playlist in stopped state */
assert(p_sys->p_input == NULL);
if( !p_sys->request.b_request )
{
//没有input播放的时候,这个时候会在这睡眠,等待播放项激活
vlc_cond_wait( &p_sys->signal, &p_sys->lock );
continue;
}
/* Playlist in running state */
while( !p_sys->killed && Next( p_playlist ) )
{
//如果有input在播放的时候,会在LoopInput里面循环
LoopInput( p_playlist );
played = true;
}
/* Playlist stopping */
msg_Dbg( p_playlist, "nothing to play" );
if( played && var_InheritBool( p_playlist, "play-and-exit" ) )
{
msg_Info( p_playlist, "end of playlist, exiting" );
libvlc_Quit( p_playlist->obj.libvlc );
}
/* Destroy any video display now (XXX: ugly hack) */
if( input_resource_HasVout( p_sys->p_input_resource ) )
{
PL_UNLOCK; /* Mind: NO LOCKS while manipulating input resources! */
input_resource_TerminateVout( p_sys->p_input_resource );
PL_LOCK;
}
}
PL_UNLOCK;
input_resource_Terminate( p_sys->p_input_resource );
return NULL;
}
有输入流的时候,列表线程在LoopInput里面循环,如下:
static void LoopInput( playlist_t *p_playlist )
{
playlist_private_t *p_sys = pl_priv(p_playlist);
input_thread_t *p_input = p_sys->p_input;
assert( p_input != NULL );
//输入未停止,就一直循环
while( !p_sys->request.input_dead )
{
if( p_sys->request.b_request || p_sys->killed )
{
PL_DEBUG( "incoming request - stopping current input" );
input_Stop( p_input );//列表结构中检测有停止请求,停止输入线程
}
vlc_cond_wait( &p_sys->signal, &p_sys->lock );
}
/* This input is dead. Remove it ! */
PL_DEBUG( "dead input" );
p_sys->p_input = NULL;
p_sys->request.input_dead = false;
PL_UNLOCK;
var_SetAddress( p_playlist, "input-current", NULL );
/* WARNING: Input resource manipulation and callback deletion are
* incompatible with the playlist lock. */
if( !var_InheritBool( p_input, "sout-keep" ) )
input_resource_TerminateSout( p_sys->p_input_resource );
var_DelCallback( p_input, "intf-event", InputEvent, p_playlist );
input_Close( p_input );
PL_LOCK;
}
四、输入线程
列表线程管理输入的切换,列表项的管理,而输入线程则是代表单个输入的处理,vlc后台支持同时起多个输入,只是界面模块暂时不支持而已。输入对象与列表对象的创建流程类似,创建后初始化变量和注册变量回调。当播放流时,会创建一个输入线程,input线程函数如下:
static void *Run( void *data )
{
input_thread_private_t *priv = data;
input_thread_t *p_input = &priv->input;
vlc_interrupt_set(&priv->interrupt);
//线程资源初始化
if( !Init( p_input ) )
{
if( priv->b_can_pace_control && priv->b_out_pace_control )
{
/* We don't want a high input priority here or we'll
* end-up sucking up all the CPU time */
vlc_set_priority( priv->thread, VLC_THREAD_PRIORITY_LOW );
}
//进入循环
MainLoop( p_input, true ); /* FIXME it can be wrong (like with VLM) */
/* Clean up */
End( p_input );
}
input_SendEventDead( p_input );
return NULL;
}
MainLoop会进入循环,不断的从demux模块中获取数据,数据经抽象层次es_out传递给decode进行解码。
static void MainLoop( input_thread_t *p_input, bool b_interactive )
{
mtime_t i_intf_update = 0;
mtime_t i_last_seek_mdate = 0;
if( b_interactive && var_InheritBool( p_input, "start-paused" ) )
ControlPause( p_input, mdate() );
bool b_pause_after_eof = b_interactive &&
var_InheritBool( p_input, "play-and-pause" );
bool b_paused_at_eof = false;
demux_t *p_demux = input_priv(p_input)->master->p_demux;
const bool b_can_demux = p_demux->pf_demux != NULL;
//没有停止不停循环
while( !input_Stopped( p_input ) && input_priv(p_input)->i_state != ERROR_S )
{
mtime_t i_wakeup = -1;
bool b_paused = input_priv(p_input)->i_state == PAUSE_S;
/* FIXME if input_priv(p_input)->i_state == PAUSE_S the access/access_demux
* is paused -> this may cause problem with some of them
* The same problem can be seen when seeking while paused */
//es_out_GetBuffering:判断是否正在缓冲数据中(后续文章再进行深入分析数据缓冲)
if( b_paused )
b_paused = !es_out_GetBuffering( input_priv(p_input)->p_es_out )
|| input_priv(p_input)->master->b_eof;
if( !b_paused )
{
if( !input_priv(p_input)->master->b_eof )
{
//是否进行强制刷新
bool b_force_update = false;
//解复用数据
MainLoopDemux( p_input, &b_force_update );
//i_wakeup : 获取下一个唤醒时间
if( b_can_demux )
i_wakeup = es_out_GetWakeup( input_priv(p_input)->p_es_out );
if( b_force_update )
i_intf_update = 0;
b_paused_at_eof = false;
}
else if( !es_out_GetEmpty( input_priv(p_input)->p_es_out ) )
{
//等待解码线程解码完毕
msg_Dbg( p_input, "waiting decoder fifos to empty" );
i_wakeup = mdate() + INPUT_IDLE_SLEEP;
}
/* Pause after eof only if the input is pausable.
* This way we won't trigger timeshifting for nothing */
else if( b_pause_after_eof && input_priv(p_input)->b_can_pause )
{
//暂停播放
if( b_paused_at_eof )
break;
vlc_value_t val = { .i_int = PAUSE_S };
msg_Dbg( p_input, "pausing at EOF (pause after each)");
Control( p_input, INPUT_CONTROL_SET_STATE, val );
b_paused = true;
b_paused_at_eof = true;
}
else
{
//重复播放
if( MainLoopTryRepeat( p_input ) )
break;
}
/* Update interface and statistics */
mtime_t now = mdate();
if( now >= i_intf_update )
{
// 刷新播放数据统计值
MainLoopStatistics( p_input );
//每隔250ms刷新一次,或者b_force_update 为true时强制刷新
i_intf_update = now + INT64_C(250000);
}
}
/* Handle control */
for( ;; )
{
mtime_t i_deadline = i_wakeup;
/* Postpone seeking until ES buffering is complete or at most
* 125 ms. */
bool b_postpone = es_out_GetBuffering( input_priv(p_input)->p_es_out )
&& !input_priv(p_input)->master->b_eof;
//b_postpone 为true,正在缓冲数据中
if( b_postpone )
{
mtime_t now = mdate();
//如果正在缓冲,则每个20ms,检测是否缓冲完整
if( now < i_last_seek_mdate + INT64_C(125000)
&& (i_deadline < 0 || i_deadline > now + INT64_C(20000)) )
i_deadline = now + INT64_C(20000);
else
b_postpone = false;//超过125ms将b_postpone 置成false
}
int i_type;
vlc_value_t val;
//睡眠等待,直到有请求,或者到达唤醒时间
if( ControlPop( p_input, &i_type, &val, i_deadline, b_postpone ) )
{
if( b_postpone )//有缓冲,继续等待直到没有缓冲或者超过125ms将b_postpone 置成false
continue;//
break; //唤醒时间到了,开始继续上面大循环的解复用数据
}
#ifndef NDEBUG
msg_Dbg( p_input, "control type=%d", i_type );
#endif
//根据请求类型,处理请求,这里下篇文章详解,主要处理input的控制逻辑
if( Control( p_input, i_type, val ) )
{
//开始播放和seek后都会进缓冲buffering,i_last_seek_mdate 记录seek时间
if( ControlIsSeekRequest( i_type ) )
i_last_seek_mdate = mdate();
i_intf_update = 0;
}
/* Update the wakeup time */
if( i_wakeup != 0 )
i_wakeup = es_out_GetWakeup( input_priv(p_input)->p_es_out );
}
}
}