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 ); } } }