04-播放列表线程和输入线程

一、播放列表线程功能

二、列表对象

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 );
        }
    }
}
上一篇:[ScyllaHide] 04 ScyllaHide配置报错原因定位


下一篇:Vue_04(插槽)