VLC 播放的音视频数据处理流水线搭建

news2024/12/23 15:34:10

VLC 播放的音视频数据处理流水线搭建

    • 音视频流播放处理循环
    • 音频输出处理流水线

VLC 用 input_thread_t 对象直接或间接管理音视频播放有关的各种资源,包括 AccessDemuxDecodeOutputFilter 等,这个类型定义 (位于 vlc-3.0.16/include/vlc_input.h) 如下:

struct input_thread_t
{
    VLC_COMMON_MEMBERS
};

input_thread_t 是个抽象类型,VLC 中这个类型的具体实现为 input_thread_private_t,后者定义 (位于 vlc-3.0.16/src/input/input_internal.h) 如下:

#define INPUT_CONTROL_FIFO_SIZE    100

/* input_source_t: gathers all information per input source */
typedef struct
{
    VLC_COMMON_MEMBERS

    demux_t  *p_demux; /**< Demux object (most downstream) */

    /* Title infos for that input */
    bool         b_title_demux; /* Titles/Seekpoints provided by demux */
    int          i_title;
    input_title_t **title;

    int i_title_offset;
    int i_seekpoint_offset;

    int i_title_start;
    int i_title_end;
    int i_seekpoint_start;
    int i_seekpoint_end;

    /* Properties */
    bool b_can_pause;
    bool b_can_pace_control;
    bool b_can_rate_control;
    bool b_can_stream_record;
    bool b_rescale_ts;
    double f_fps;

    /* */
    int64_t i_pts_delay;

    bool       b_eof;   /* eof of demuxer */

} input_source_t;

typedef struct
{
    int         i_type;
    vlc_value_t val;
} input_control_t;

/** Private input fields */
typedef struct input_thread_private_t
{
    struct input_thread_t input;

    /* Global properties */
    bool        b_preparsing;
    bool        b_can_pause;
    bool        b_can_rate_control;
    bool        b_can_pace_control;

    /* Current state */
    int         i_state;
    bool        is_running;
    bool        is_stopped;
    bool        b_recording;
    int         i_rate;

    /* Playtime configuration and state */
    int64_t     i_start;    /* :start-time,0 by default */
    int64_t     i_stop;     /* :stop-time, 0 if none */
    int64_t     i_time;     /* Current time */
    bool        b_fast_seek;/* :input-fast-seek */

    /* Output */
    bool            b_out_pace_control; /* XXX Move it ot es_sout ? */
    sout_instance_t *p_sout;            /* Idem ? */
    es_out_t        *p_es_out;
    es_out_t        *p_es_out_display;
    vlc_viewpoint_t viewpoint;
    bool            viewpoint_changed;
    vlc_renderer_item_t *p_renderer;

    /* Title infos FIXME multi-input (not easy) ? */
    int          i_title;
    const input_title_t **title;

    int i_title_offset;
    int i_seekpoint_offset;

    /* User bookmarks FIXME won't be easy with multiples input */
    seekpoint_t bookmark;
    int         i_bookmark;
    seekpoint_t **pp_bookmark;

    /* Input attachment */
    int i_attachment;
    input_attachment_t **attachment;
    const demux_t **attachment_demux;

    /* Main input properties */

    /* Input item */
    input_item_t   *p_item;

    /* Main source */
    input_source_t *master;
    /* Slave sources (subs, and others) */
    int            i_slave;
    input_source_t **slave;

    /* Resources */
    input_resource_t *p_resource;
    input_resource_t *p_resource_private;

    /* Stats counters */
    struct {
        counter_t *p_read_packets;
        counter_t *p_read_bytes;
        counter_t *p_input_bitrate;
        counter_t *p_demux_read;
        counter_t *p_demux_bitrate;
        counter_t *p_demux_corrupted;
        counter_t *p_demux_discontinuity;
        counter_t *p_decoded_audio;
        counter_t *p_decoded_video;
        counter_t *p_decoded_sub;
        counter_t *p_sout_sent_packets;
        counter_t *p_sout_sent_bytes;
        counter_t *p_sout_send_bitrate;
        counter_t *p_played_abuffers;
        counter_t *p_lost_abuffers;
        counter_t *p_displayed_pictures;
        counter_t *p_lost_pictures;
        vlc_mutex_t counters_lock;
    } counters;

    /* Buffer of pending actions */
    vlc_mutex_t lock_control;
    vlc_cond_t  wait_control;
    int i_control;
    input_control_t control[INPUT_CONTROL_FIFO_SIZE];

    vlc_thread_t thread;
    vlc_interrupt_t interrupt;
} input_thread_private_t;

播放 VLC 播放列表中的一个音视频流的时候,音视频流的播放通过 PlayItem() 函数起动,这个函数被调用的调用栈如下:

Thread 3 "vlc" hit Breakpoint 14, PlayItem (p_item=<optimized out>, p_playlist=0xaaaaaab37980) at playlist/thread.c:227
227	        if( input_Start( p_input_thread ) )
(gdb) bt
#0  PlayItem (p_item=<optimized out>, p_playlist=0xaaaaaab37980) at playlist/thread.c:227
#1  Next (p_playlist=0xaaaaaab37980) at playlist/thread.c:474
#2  Thread (data=0xaaaaaab37980) at playlist/thread.c:499
#3  0x0000fffff7e4d5c8 in start_thread (arg=0x0) at ./nptl/pthread_create.c:442

PlayItem() 函数定义 (位于 vlc-3.0.16/src/playlist/thread.c) 如下:

static bool PlayItem( playlist_t *p_playlist, playlist_item_t *p_item )
{
    playlist_private_t *p_sys = pl_priv(p_playlist);
    input_item_t *p_input = p_item->p_input;
    vlc_renderer_item_t *p_renderer;

    PL_ASSERT_LOCKED;

    msg_Dbg( p_playlist, "creating new input thread" );

    p_item->i_nb_played++;
    set_current_status_item( p_playlist, p_item );
    p_renderer = p_sys->p_renderer;
    /* Retain the renderer now to avoid it to be released by
     * playlist_SetRenderer when we exit the locked scope. If the last reference
     * was to be released, we would use a dangling pointer */
    if( p_renderer )
        vlc_renderer_item_hold( p_renderer );
    assert( p_sys->p_input == NULL );
    PL_UNLOCK;

    libvlc_MetadataCancel( p_playlist->obj.libvlc, p_item );

    input_thread_t *p_input_thread = input_Create( p_playlist, p_input, NULL,
                                                   p_sys->p_input_resource,
                                                   p_renderer );
    if( p_renderer )
        vlc_renderer_item_release( p_renderer );
    if( likely(p_input_thread != NULL) )
    {
        var_AddCallback( p_input_thread, "intf-event",
                         InputEvent, p_playlist );

        if( input_Start( p_input_thread ) )
        {
            var_DelCallback( p_input_thread, "intf-event",
                             InputEvent, p_playlist );
            vlc_object_release( p_input_thread );
            p_input_thread = NULL;
        }
    }

    /* TODO store art policy in playlist private data */
    char *psz_arturl = input_item_GetArtURL( p_input );
    /* p_input->p_meta should not be null after a successful CreateThread */
    bool b_has_art = !EMPTY_STR( psz_arturl );

    if( !b_has_art || strncmp( psz_arturl, "attachment://", 13 ) )
    {
        PL_DEBUG( "requesting art for new input thread" );
        libvlc_ArtRequest( p_playlist->obj.libvlc, p_input, META_REQUEST_OPTION_NONE );
    }
    free( psz_arturl );

    PL_LOCK;
    p_sys->p_input = p_input_thread;
    PL_UNLOCK;

    var_SetAddress( p_playlist, "input-current", p_input_thread );

    PL_LOCK;
    return p_input_thread != NULL;
}

PlayItem() 函数接收 playlist_item_t 类型的 p_item 参数,这个类型定义 (位于 vlc-3.0.16/include/vlc_playlist.h) 如下:

/** playlist item / node */
struct playlist_item_t
{
    input_item_t           *p_input;    /**< Linked input item */

    playlist_item_t      **pp_children; /**< Children nodes/items */
    playlist_item_t       *p_parent;    /**< Item parent */
    int                    i_children;  /**< Number of children, -1 if not a node */
    unsigned               i_nb_played; /**< Times played */

    int                    i_id;        /**< Playlist item specific id */
    uint8_t                i_flags;     /**< Flags \see playlist_item_flags_e */
};

playlist_item_t 类型包含一个类型为 input_item_t 的字段 p_input,其中包含要播放的音视频流的各种信息,如 URI,名称,基础流的个数及格式等,input_item_t 类型的详细定义 (位于 vlc-3.0.16/include/vlc_input_item.h) 如下:

struct info_t
{
    char *psz_name;            /**< Name of this info */
    char *psz_value;           /**< Value of the info */
};

struct info_category_t
{
    char   *psz_name;      /**< Name of this category */
    int    i_infos;        /**< Number of infos in the category */
    struct info_t **pp_infos;     /**< Pointer to an array of infos */
};

/**
 * Describes an input and is used to spawn input_thread_t objects.
 */
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.*/
};

这里的几个类型的结构关系大概如下图所示:
VLC Objects
PlayItem() 函数主要是调用 input_Create() 函数创建 input_thread_t/input_thread_private_t 对象,并调用 input_Start() 函数启动对音视频流的处理。

input_Create()input_Start() 函数函数定义 (位于 vlc-3.0.16/src/input/input.c) 如下:

input_thread_t *input_Create( vlc_object_t *p_parent,
                              input_item_t *p_item,
                              const char *psz_log, input_resource_t *p_resource,
                              vlc_renderer_item_t *p_renderer )
{
    return Create( p_parent, p_item, psz_log, false, p_resource, p_renderer );
}
 . . . . . .
/**
 * Start a input_thread_t created by input_Create.
 *
 * You must not start an already running input_thread_t.
 *
 * \param the input thread to start
 */
int input_Start( input_thread_t *p_input )
{
    input_thread_private_t *priv = input_priv(p_input);
    void *(*func)(void *) = Run;

    if( priv->b_preparsing )
        func = Preparse;

    assert( !priv->is_running );
    /* Create thread and wait for its readiness. */
    priv->is_running = !vlc_clone( &priv->thread, func, priv,
                                   VLC_THREAD_PRIORITY_INPUT );
    if( !priv->is_running )
    {
        input_ChangeState( p_input, ERROR_S );
        msg_Err( p_input, "cannot create input thread" );
        return VLC_EGENERIC;
    }
    return VLC_SUCCESS;
}
 . . . . . .
 static input_thread_t *Create( vlc_object_t *p_parent, input_item_t *p_item,
                               const char *psz_header, bool b_preparsing,
                               input_resource_t *p_resource,
                               vlc_renderer_item_t *p_renderer )
{
    /* Allocate descriptor */
    input_thread_private_t *priv;

    priv = vlc_custom_create( p_parent, sizeof( *priv ), "input" );
    if( unlikely(priv == NULL) )
        return NULL;

    input_thread_t *p_input = &priv->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;
}

除了 b_preparsing 参数传 false 外,input_Create() 函数将接收的各个参数传给 Create(),通过后者创建 input_thread_private_t 对象。input_Start() 函数启动一个新的线程处理音视频流,默认情况下,新起的线程跑 Run() 函数,但如果设置了 b_preparsing,新起的线程跑 Preparse() 函数。

播放列表中各个音频流的信息,也通过 input_thread_t 获取。当我们往 VLC 的播放列表拖入一个文件时,UI 事件向播放列表添加一个输入项,输入项最终被放入播放列表预解析器的待解析队列中,这个调用过程如下面的调用栈所示:

#0  background_worker_Push (worker=0xaaaaaaae2e40, entity=entity@entry=0xffffc885fec0, id=id@entry=0xffffc87f5ac0, timeout=timeout@entry=-1) at misc/background_worker.c:211
#1  0x0000fffff7ced04c in playlist_preparser_Push (preparser=0xaaaaaaadbe00, item=item@entry=0xffffc885fec0, i_options=META_REQUEST_OPTION_NONE, timeout=-1, id=0xffffc87f5ac0)
    at playlist/preparser.c:178
#2  0x0000fffff7cca748 in vlc_MetadataRequest
    (libvlc=0xaaaaaaab58e0, item=item@entry=0xffffc885fec0, i_options=i_options@entry=META_REQUEST_OPTION_NONE, timeout=timeout@entry=-1, id=id@entry=0xffffc87f5ac0) at libvlc.c:504
#3  0x0000fffff7ceee5c in playlist_Preparse (p_item=0xffffc87f5ac0, p_playlist=0xaaaaaab37980) at playlist/item.c:750
#4  playlist_NodeAddInput (p_playlist=p_playlist@entry=0xaaaaaab37980, p_input=p_input@entry=0xffffc885fec0, p_parent=<optimized out>, i_pos=i_pos@entry=-1) at playlist/item.c:541
#5  0x0000fffff7ceef20 in playlist_AddInput (p_playlist=p_playlist@entry=0xaaaaaab37980, p_input=p_input@entry=0xffffc885fec0, play_now=play_now@entry=false, b_playlist=b_playlist@entry=true)
    at playlist/item.c:504
#6  0x0000fffff7ceefe4 in playlist_AddExt
    (p_playlist=0xaaaaaab37980, psz_uri=<optimized out>, psz_name=<optimized out>, play_now=false, i_options=0, ppsz_options=0x0, i_option_flags=2, b_playlist=true) at playlist/item.c:483
#7  0x0000ffffe51d4440 in Open::openMRLwithOptions(intf_thread_t*, QString const&, QStringList*, bool, bool, char const*)
    (p_intf=0xaaaaaad8e8d0, mrl=..., options=<optimized out>, b_start=false, b_playlist=true, title=0x0) at /usr/include/aarch64-linux-gnu/qt5/QtCore/qarraydata.h:61
#8  0x0000ffffe51ba768 in MainInterface::dropEventPlay(QDropEvent*, bool, bool) (this=0xffffc81475f0, event=0xffffe319d020, b_play=<optimized out>, b_playlist=true)
    at gui/qt/main_interface.cpp:1580
#9  0x0000ffffe4b96378 in QWidget::event(QEvent*) () at /usr/lib/aarch64-linux-gnu/libQt5Widgets.so.5
#10 0x0000ffffe4b52ac0 in QApplicationPrivate::notify_helper(QObject*, QEvent*) () at /usr/lib/aarch64-linux-gnu/libQt5Widgets.so.5
#11 0x0000ffffe4b5a2e0 in QApplication::notify(QObject*, QEvent*) () at /usr/lib/aarch64-linux-gnu/libQt5Widgets.so.5
#12 0x0000ffffe40acb90 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () at /usr/lib/aarch64-linux-gnu/libQt5Core.so.5

其中 input_item_t 对象在 playlist_AddExt() 函数中创建,它所属的 playlist_item_t 对象则在 playlist_AddInput() 函数中通过 playlist_NodeAddInput() 创建。VLC 起一个后台线程来预解析音视频流,VLC 调用 PreparserOpenInput() 函数预解析音视频流,这个函数被调用的调用栈如下:

Thread 32 "vlc" hit Breakpoint 7, PreparserOpenInput (preparser_=0xaaaaaaadbe00, item_=0xffffc8730830, out=0xffffb021e790) at playlist/preparser.c:55
55	    playlist_preparser_t* preparser = preparser_;
(gdb) bt
#0  PreparserOpenInput (preparser_=0xaaaaaaadbe00, item_=0xffffc8730830, out=0xffffb021e790) at playlist/preparser.c:55
#1  0x0000fffff7d43c1c in Thread (data=0xaaaaaaae2e40) at misc/background_worker.c:115
#2  0x0000fffff7e4d5c8 in start_thread (arg=0x0) at ./nptl/pthread_create.c:442

PreparserOpenInput() 函数定义 (位于 vlc-3.0.16/src/playlist/preparser.c) 如下:

static int PreparserOpenInput( void* preparser_, void* item_, void** out )
{
    playlist_preparser_t* preparser = preparser_;

    input_thread_t* input = input_CreatePreparser( preparser->owner, item_ );
    if( !input )
    {
        input_item_SignalPreparseEnded( item_, ITEM_PREPARSE_FAILED );
        return VLC_EGENERIC;
    }

    var_AddCallback( input, "intf-event", InputEvent, preparser->worker );
    if( input_Start( input ) )
    {
        var_DelCallback( input, "intf-event", InputEvent, preparser->worker );
        input_Close( input );
        input_item_SignalPreparseEnded( item_, ITEM_PREPARSE_FAILED );
        return VLC_EGENERIC;
    }

    *out = input;
    return VLC_SUCCESS;
}

与前面看到的 PlayItem() 函数有些类似,但它调用 input_CreatePreparser() 函数创建 input_thread_private_t 对象,同时由于配置了 b_preparsinginput_Start() 函数起的线程将运行 Preparse() 函数,且创建的 input_thread_private_t 对象被配置了 OBJECT_FLAGS_QUIET | OBJECT_FLAGS_NOINTERACT,这意味着无法通过 VLC 的日志机制为这个对象输出日志。input_CreatePreparser()Preparse() 函数定义 (位于 vlc-3.0.16/src/input/input.c) 如下:

static void *Preparse( 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 the demux is a playlist, call Mainloop that will call
         * demux_Demux in order to fetch sub items */
        bool b_is_playlist = false;

        if ( input_item_ShouldPreparseSubItems( priv->p_item )
          && demux_Control( priv->master->p_demux, DEMUX_IS_PLAYLIST,
                            &b_is_playlist ) )
            b_is_playlist = false;
        if( b_is_playlist )
            MainLoop( p_input, false );
        End( p_input );
    }

    input_SendEventDead( p_input );
    return NULL;
}
 . . . . . .
input_thread_t *input_CreatePreparser( vlc_object_t *parent,
                                       input_item_t *item )
{
    return Create( parent, item, NULL, true, NULL, NULL );
}

input_CreatePreparser() 函数也是 Create() 函数的包装,只是它的 b_preparsing 参数传的是 truePreparse() 函数与 Run() 函数类似,只是它不运行音视频流的 Demux 循环。预解析处理有超时限制,VLC 并不总是会等预解析完成。预解析成功完成时,可以在 VLC 的播放列表中看到通过 demuxer 获得的音视频流的时长等信息,没能成功完成时,则只能看到文件名作为标题。

音视频流播放处理循环

VLC 中,input_Start() 函数启动一个新的线程跑 Run() 函数来播放音视频流,这个函数调用 Init() 函数初始化音视频流播放处理过程,初始化前面通过 input_Create() 函数创建的 input_thread_private_t 对象,随后调用 MainLoop() 函数跑一个处理循环,最后调用 End() 函数结束处理。

音视频流播放处理过程初始化,需要完成许多事情,如为要播放的音视频流创建 access 以便于获取数据,创建 demuxer,创建 decoder 等。具体来看 Init() 函数的定义 (位于 vlc-3.0.16/src/input/input.c) 如下:

static int Init( input_thread_t * p_input )
{
    input_thread_private_t *priv = input_priv(p_input);
    input_source_t *master;

    if( var_Type( p_input->obj.parent, "meta-file" ) )
    {
        msg_Dbg( p_input, "Input is a meta file: disabling unneeded options" );
        var_SetString( p_input, "sout", "" );
        var_SetBool( p_input, "sout-all", false );
        var_SetString( p_input, "input-slave", "" );
        var_SetInteger( p_input, "input-repeat", 0 );
        var_SetString( p_input, "sub-file", "" );
        var_SetBool( p_input, "sub-autodetect-file", false );
    }

    InitStatistics( p_input );
#ifdef ENABLE_SOUT
    if( InitSout( p_input ) )
        goto error;
#endif

    /* Create es out */
    priv->p_es_out = input_EsOutTimeshiftNew( p_input, priv->p_es_out_display,
                                              priv->i_rate );
    if( priv->p_es_out == NULL )
        goto error;

    /* */
    input_ChangeState( p_input, OPENING_S );
    input_SendEventCache( p_input, 0.0 );

    /* */
    master = InputSourceNew( p_input, priv->p_item->psz_uri, NULL, false );
    if( master == NULL )
        goto error;
    priv->master = master;

    InitTitle( p_input );

    /* Load master infos */
    /* Init length */
    mtime_t i_length;
    if( demux_Control( master->p_demux, DEMUX_GET_LENGTH, &i_length ) )
        i_length = 0;
    if( i_length <= 0 )
        i_length = input_item_GetDuration( priv->p_item );
    input_SendEventLength( p_input, i_length );

    input_SendEventPosition( p_input, 0.0, 0 );

    if( !priv->b_preparsing )
    {
        StartTitle( p_input );
        SetSubtitlesOptions( p_input );
        LoadSlaves( p_input );
        InitPrograms( p_input );

        double f_rate = var_InheritFloat( p_input, "rate" );
        if( f_rate != 0.0 && f_rate != 1.0 )
        {
            vlc_value_t val = { .i_int = INPUT_RATE_DEFAULT / f_rate };
            input_ControlPush( p_input, INPUT_CONTROL_SET_RATE, &val );
        }
    }

    if( !priv->b_preparsing && priv->p_sout )
    {
        priv->b_out_pace_control = priv->p_sout->i_out_pace_nocontrol > 0;

        msg_Dbg( p_input, "starting in %ssync mode",
                 priv->b_out_pace_control ? "a" : "" );
    }

    vlc_meta_t *p_meta = vlc_meta_New();
    if( p_meta != NULL )
    {
        /* Get meta data from users */
        InputMetaUser( p_input, p_meta );

        /* Get meta data from master input */
        InputSourceMeta( p_input, master, p_meta );

        /* And from slave */
        for( int i = 0; i < priv->i_slave; i++ )
            InputSourceMeta( p_input, priv->slave[i], p_meta );

        es_out_ControlSetMeta( priv->p_es_out, p_meta );
        vlc_meta_Delete( p_meta );
    }

    msg_Dbg( p_input, "`%s' successfully opened",
             input_priv(p_input)->p_item->psz_uri );

    /* initialization is complete */
    input_ChangeState( p_input, PLAYING_S );

    return VLC_SUCCESS;

error:
    input_ChangeState( p_input, ERROR_S );

    if( input_priv(p_input)->p_es_out )
        es_out_Delete( input_priv(p_input)->p_es_out );
    es_out_SetMode( input_priv(p_input)->p_es_out_display, ES_OUT_MODE_END );
    if( input_priv(p_input)->p_resource )
    {
        if( input_priv(p_input)->p_sout )
            input_resource_RequestSout( input_priv(p_input)->p_resource,
                                         input_priv(p_input)->p_sout, NULL );
        input_resource_SetInput( input_priv(p_input)->p_resource, NULL );
        if( input_priv(p_input)->p_resource_private )
            input_resource_Terminate( input_priv(p_input)->p_resource_private );
    }

    if( !priv->b_preparsing && libvlc_stats( p_input ) )
    {
#define EXIT_COUNTER( c ) do { if( input_priv(p_input)->counters.p_##c ) \
                                   stats_CounterClean( input_priv(p_input)->counters.p_##c );\
                               input_priv(p_input)->counters.p_##c = NULL; } while(0)
        EXIT_COUNTER( read_bytes );
        EXIT_COUNTER( read_packets );
        EXIT_COUNTER( demux_read );
        EXIT_COUNTER( input_bitrate );
        EXIT_COUNTER( demux_bitrate );
        EXIT_COUNTER( demux_corrupted );
        EXIT_COUNTER( demux_discontinuity );
        EXIT_COUNTER( played_abuffers );
        EXIT_COUNTER( lost_abuffers );
        EXIT_COUNTER( displayed_pictures );
        EXIT_COUNTER( lost_pictures );
        EXIT_COUNTER( decoded_audio );
        EXIT_COUNTER( decoded_video );
        EXIT_COUNTER( decoded_sub );

        if( input_priv(p_input)->p_sout )
        {
            EXIT_COUNTER( sout_sent_packets );
            EXIT_COUNTER( sout_sent_bytes );
            EXIT_COUNTER( sout_send_bitrate );
        }
#undef EXIT_COUNTER
    }

    /* Mark them deleted */
    input_priv(p_input)->p_es_out = NULL;
    input_priv(p_input)->p_sout = NULL;

    return VLC_EGENERIC;
}

Init() 函数做的主要的事情如下:

  1. 初始化统计计数器。
  2. 创建 es outesElementary streams,即基本流。后面更详细地对 es 作解释。
  3. 调用 InputSourceNew() 函数创建 input source。VLC 的 input source 指解码之前,对音视频流数据做处理的组件,这包括 access 组件,access 组件后面执行缓存策略的 stream_filter 组件,再后面自动的 stream_filter 组件,再后面更上层指定的其它 stream_filter 组件,再后面的 demux 组件。或者对于类型的流,可以将 demux 和数据获取合并的情况,为 access_demux 组件。demux 组件或 access_demux 组件后面的是若干个更上层指定的 demux_filter 组件。所有这些由 input_source_t 对象描述,它的 demux_t 类型的字段

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2252394.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android 12系统源码_RRO机制(一)Runtime Resource Overlay机制实践

前言 Android的RRO&#xff08;Runtime Resource Overlay&#xff09;机制允许开发者在运行时替换或重写系统资源&#xff0c;例如布局、图标、字符串等。这个机制的目标是为了支持设备定制和主题化&#xff0c;特别是在不修改系统源代码的情况下。RRO通过在系统的资源上叠加一…

Tomcat新手成长之路:安装部署优化全解析(下)

接上篇《Tomcat新手成长之路&#xff1a;安装部署优化全解析&#xff08;上&#xff09;》: link 文章目录 7.应用部署7.1.上下文7.2.启动时进行部署7.3.动态应用部署 8.Tomcat 类加载机制8.1.简介8.2.类加载器定义8.3.XML解析器和 Java 9.JMS监控9.1.简介9.2.启用 JMX 远程监…

动态代理如何加强安全性

在当今这个信息爆炸、网络无孔不入的时代&#xff0c;我们的每一次点击、每一次浏览都可能留下痕迹&#xff0c;成为潜在的安全隐患。如何在享受网络便利的同时&#xff0c;有效保护自己的隐私和信息安全&#xff0c;成为了每位网络使用者必须面对的重要课题。动态代理服务器&a…

python---面向对象-python中的实践(2)

如何定义一个类&#xff1f; class 类名:pass怎样通过类&#xff0c;创建出一个对象&#xff1f; 根据类创建对象one Money() 执行流程1. 类的定义2. 根据类&#xff0c;创建出一个对象3. 将对象的唯一标识返回class Money:passprint(Money.__name__) xxx Money print(xxx.…

数据结构-散列函数的构造方法

一.数字关键词 关键词存储应该尽可能的离散 直接定址法:利用线性函数,例如上面的例子,h(key)key-1990,key1990&#xff0c;这个就被存放在0的位置 数字分析法:关键字可能有很到位组成,每一位变化可能都不一样&#xff0c;有的位是不变的,就是说不同的对象这一位都是一样的,有的…

单点登录解决方案 CAS(Central Authentication Service)详解

目录 CAS 的工作原理 票据&#xff08;Ticket&#xff09;详解 CAS 的优势 CAS 的应用场景 小结 参考资料 Central Authentication Service&#xff08;中央认证服务&#xff0c;简称 CAS&#xff09;是一个开源的企业级单点登录&#xff08;Single Sign-On, SSO&#xf…

输入json 达到预览效果

下载 npm i vue-json-pretty2.4.0 <template><div class"newBranchesDialog"><t-base-dialogv-if"addDialogShow"title"Json数据配置"closeDialog"closeDialog":dialogVisible"addDialogShow":center"…

U盘文件夹变打不开的文件:深度解析、恢复策略与预防之道

一、U盘文件夹变打不开的文件现象解析 在日常使用U盘的过程中&#xff0c;我们时常会遇到这样的困扰&#xff1a;原本存储有序、可以轻松访问的文件夹&#xff0c;突然之间变成了无法打开的文件。这些文件通常以未知图标或乱码形式显示&#xff0c;双击或右键尝试打开时&#…

2025年软考-网络工程师新旧教程及考纲变化对比!

2025网工教材改版基本确认&#xff01;网络工程师一直是软考中级的热门科目。最近&#xff0c;官方发布了官方第六版的网工教材&#xff0c;本次出版在原有第五版的基础上做了大量的删减&#xff0c;并新增了部分新内容。明年的软考考纲大概率会根据这次的新版教材进行修改&…

视觉处理基础1

目录 一、CNN 1. 概述 1.1 与传统网络的区别 1.2 全连接的局限性 1.3 卷积思想 1.4 卷积的概念 1.4.1 概念 1.4.2 局部连接 1.4.3 权重共享 2. 卷积层 2.1 卷积核 2.2 卷积计算 2.3 边缘填充 2.4 步长Stride 2.5 多通道卷积计算 2.7 特征图大小计算方法 2…

大疆T100大载重吊运植保无人机技术详解

大疆T100作为一款大载重吊运植保无人机&#xff0c;融合了全新的AI和AR功能&#xff0c;旨在进一步提升安全性并满足喷洒、播撒、吊运等多种作业场景的需求。以下是对其技术的详细解析&#xff1a; 一、总体性能 最大起飞重量&#xff1a;149.9公斤 喷洒容量&#xff1a;75升…

Kylin Server V10 下 RocketMQ 主备自动切换模式部署

一、NameServer简介 NameServer 是一个注册中心,提供服务注册和服务发现的功能。NameServer 可以集群部署,集群中每个节点都是对等的关系,节点之间互不通信。 服务注册 Broker 启动的时候会向所有的 NameServer 节点进行注册,注意这里是向集群中所有的 NameServer 节点注册…

ESP32+VSCODE开发过程无法使用Debug调试问题解决

1.点击Debug按钮报错 Error: libusb_open() failed with LIBUSB_ERROR_ACCESS Error: esp_usb_jtag: could not find or open device! 2.解决办法 1.找见espidf的安装目录下的如下文件&#xff08;如下自己的安装目录&#xff09; home\fjq\esp-idf\espidfv5.3.1\tools\tool…

【CSS】一篇掌握CSS

不是因为有了希望才去坚持,而是坚持了才有了希望 目录 一.导入方式 1.行内样式 2.内部样式 3.外部样式(常用) 二.选择器 1.基本选择器(常用) 1.1标签选择器 1.2类选择器 1.3id选择器 2.层次选择器 2.1后代选择器 2.2子选择器 2.3相邻兄弟选择器 2.4通用兄弟选择器…

MySQL底层概述—6.索引原理

大纲 1.索引原理 2.二叉查找树 3.平衡二叉树(AVL树) 4.红黑树 5.B-Tree 6.BTree 7.Hash索引 8.聚簇索引与非聚簇索引 1.索引原理 索引会在数据文件中(ibd文件)&#xff0c;通过数据页(Page)进行存储。索引可以加快检索速度&#xff0c;但也会降低增删改速度&#xff0…

C语言学习笔记:循环结构

循环结构 什么是循环 代码的重复执行&#xff0c;就叫做循环。 循环的分类 无限循环&#xff1a;程序设计中尽量避免无限循环&#xff0c;其实就是死循环。程序中的无限循环必须是可控的。有限循环&#xff1a;循环限定循环次数或者循环的条件。 循环的构成&#xff1a; …

stable diffusion实践操作-大模型介绍:SD的发展历史,SD1.5和SDXL之间的差别

大家有没有这样的困惑&#xff1a;在找模型时&#xff0c;老是会出现一些奇怪的标签&#xff0c;像 sd1.5、sdxl 之类的模型后缀&#xff0c;真让人摸不着头脑&#xff0c;一会儿 1.0&#xff0c;一会儿 1.5&#xff0c;一会儿 XL&#xff0c;完全搞不清楚状况。今天就来给大家…

AI高中数学教学视频生成技术:利用通义千问、MathGPT、视频多模态大模型,语音大模型,将4个模型融合 ,生成高中数学教学视频,并给出实施方案。

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下AI高中数学教学视频生成技术&#xff1a;利用通义千问、MathGPT、视频多模态大模型&#xff0c;语音大模型&#xff0c;将4个模型融合 &#xff0c;生成高中数学教学视频&#xff0c;并给出实施方案。本文利用专家模…

PyCharm中Python项目打包并运行到服务器的简明指南

目录 一、准备工作 二、创建并设置Python项目 创建新项目 配置项目依赖 安装PyInstaller 三、打包项目 打包为可执行文件 另一种打包方式(使用setup.py) 四、配置服务器环境 五、上传可执行文件到服务器 六、在服务器上运行项目 配置SSH解释器 配置部署 上传代…

git clone超大仓库时报错:fatal: early EOF

环境版本&#xff1a; 系统&#xff1a;Ubuntu git版本&#xff1a;version 2.43.0 在执行git clone命令时报错&#xff0c;信息如下&#xff1a; 系统&#xff1a;Win10 git版本&#xff1a;version 2.47.0 解决办法1&#xff1a; 1、关闭压缩&#xff1a; git conf…