【四】【vlc-android】播放控制交互与demux解复用层、媒体数据流拉取层的具体数据传递和控制流程源码分析

1、VLC中有很多demux/mux/encoder/decoder模块,因此需要先了解这些模块的加载原理,模块的加载原理基本一致,因此举例分析MP4解复用模块如何加载完成的,主要流程如下:

// vlc中MP4解复用模块的实现代码位于【vlc/modules/demux/mp4/mp4.c】中
// 模块声明加载代码片段:
vlc_module_begin ()
    set_category( CAT_INPUT )
    set_subcategory( SUBCAT_INPUT_DEMUX )
    set_description( N_("MP4 stream demuxer") )
    set_shortname( N_("MP4") )
    set_capability( "demux", 240 )
    set_callbacks( Open, Close )

    add_category_hint("Hacks", NULL, true)
    add_bool( CFG_PREFIX"m4a-audioonly", false, MP4_M4A_TEXT, MP4_M4A_LONGTEXT, true )
vlc_module_end ()

// 开始和结束两个方法是vlc的两个宏定义,且所有模块的声明加载信息均在这两个宏定义之间
// 其中【set_callbacks】参数为所有模块加载时必须声明的方法回调即打开和关闭该模块的功能
static int  Open ( vlc_object_t * );
static void Close( vlc_object_t * );

// 注:可以添加当前模块的子模块,如h26x的实现:h264+h265
vlc_module_begin ()
    set_shortname( "H264")
    set_category( CAT_INPUT )
    set_subcategory( SUBCAT_INPUT_DEMUX )
    set_description( N_("H264 video demuxer" ) )
    set_capability( "demux", 6 )
    set_section( N_("H264 video demuxer" ), NULL )
    add_float( "h264-fps", 0.0, FPS_TEXT, FPS_LONGTEXT, true )
    set_callbacks( OpenH264, Close )
    add_shortcut( "h264" )

    add_submodule()
        set_shortname( "HEVC")
        set_category( CAT_INPUT )
        set_subcategory( SUBCAT_INPUT_DEMUX )
        set_description( N_("HEVC/H.265 video demuxer" ) )
        set_capability( "demux", 6 )
        set_section( N_("HEVC/H.265 video demuxer" ), NULL )
        add_float( "hevc-fps", 0.0, FPS_TEXT, FPS_LONGTEXT, true )
        set_callbacks( OpenHEVC, Close )
        add_shortcut( "hevc", "h265" )

vlc_module_end ()

// set_callback宏定义如下:
#define set_callbacks( activate, deactivate ) \
    if (vlc_module_set(VLC_MODULE_CB_OPEN, #activate, (void *)(activate)) \
     || vlc_module_set(VLC_MODULE_CB_CLOSE, #deactivate, \
                       (void *)(deactivate))) \
        goto error;

// 因此通过分析可知初始化模块信息后对应的两个字段如下结构体中两方法指针:
  void *pf_activate
  void *pf_deactivate
/**
 * Internal module descriptor
 */
struct module_t
{
    
    
    vlc_plugin_t *plugin; /**< Plug-in/library containing the module */
    module_t   *next;

    /** Shortcuts to the module */
    unsigned    i_shortcuts;
    const char **pp_shortcuts;

    /*
     * Variables set by the module to identify itself
     */
    const char *psz_shortname;                              /**< Module name */
    const char *psz_longname;                   /**< Module descriptive name */
    const char *psz_help;        /**< Long help string for "special" modules */

    const char *psz_capability;                              /**< Capability */
    int      i_score;                          /**< Score for the capability */

    /* Callbacks */
    const char *activate_name;
    const char *deactivate_name;
    void *pf_activate;
    void *pf_deactivate;
};

// 如pf_activate方法主要由【vlc/src/modules/modules.c】这里的两个方法中调用:
static int module_load (vlc_object_t *obj, module_t *m,
                        vlc_activate_t init, va_list args)
// 或下面该方法中的调用
#undef module_start
int module_start (vlc_object_t *obj, const module_t *m)
{
    
    
   int (*activate) (vlc_object_t *) = m->pf_activate;

   return (activate != NULL) ? activate (obj) : VLC_SUCCESS;
}
// 或是该方法进行初始化
static int generic_start(void *func, va_list ap)
{
    
    
    vlc_object_t *obj = va_arg(ap, vlc_object_t *);
    int (*activate)(vlc_object_t *) = func;

    return activate(obj);
}

// 通过代码调用链可知,这两方法会在此前讲过的vlc初始化过程等均有调用加载,后续会具体分析一下

2、接下来,交互途径主要是如下功能方法:

// 位于【vlc/include/vlc_demux.h】
static inline int demux_Control( demux_t *p_demux, int i_query, ... )
{
    
    
    va_list args;
    int     i_result;

    va_start( args, i_query );
    i_result = demux_vaControl( p_demux, i_query, args );
    va_end( args );
    return i_result;
}

// 位于【vlc/src/input/demux.c】
int demux_vaControl( demux_t *demux, int query, va_list args )
{
    
    
    // 通过query查询动作枚举来控制解复用输出功能及与解复用端沟通获取数据等
    if( demux->s != NULL )
        switch( query )
        {
    
    
            /* Legacy fallback for missing getters in synchronous demuxers */
            case DEMUX_CAN_PAUSE:
            case DEMUX_CAN_CONTROL_PACE:
            case DEMUX_GET_PTS_DELAY:
            {
    
    
                int ret;
                va_list ap;

                va_copy( ap, args );
                ret = demux->pf_control( demux, query, args );
                if( ret != VLC_SUCCESS )
                    ret = vlc_stream_vaControl( demux->s, query, ap );
                va_end( ap );
                return ret;
            }

            /* Some demuxers need to control pause directly (e.g. adaptive),
             * but many legacy demuxers do not understand pause at all.
             * If DEMUX_CAN_PAUSE is not implemented, bypass the demuxer and
             * byte stream. If DEMUX_CAN_PAUSE is implemented and pause is
             * supported, pause the demuxer normally. Else, something went very
             * wrong.
             *
             * Note that this requires asynchronous/threaded demuxers to
             * always return VLC_SUCCESS for DEMUX_CAN_PAUSE, so that they are
             * never bypassed. Otherwise, we would reenter demux->s callbacks
             * and break thread safety. At the time of writing, asynchronous or
             * threaded *non-access* demuxers do not exist and are not fully
             * supported by the input thread, so this is theoretical. */
            case DEMUX_SET_PAUSE_STATE:
            {
    
    
                bool can_pause;

                if( demux_ControlInternal( demux, DEMUX_CAN_PAUSE,
                                           &can_pause ) )
                    return vlc_stream_vaControl( demux->s, query, args );

                /* The caller shall not pause if pause is unsupported. */
                assert( can_pause );
                break;
            }
        }

    return demux->pf_control( demux, query, args );
}

2.1 举例说明:如java层若想获取媒体播放总时长,则该时长信息初始化在此前分析的Init方法中:

demux_Control( master->p_demux, DEMUX_GET_LENGTH, &i_length )

然后会执行demux模块的该方法去获取:

demux->pf_control( demux, query, args )

有上面第1部分分析可知:该方法调用到对应模块的该方法实现,如mp4模块中:

// 位于【vlc/modules/demux/mp4/mp4.c】的如下方法中赋值初始化的:
static int Open( vlc_object_t * p_this ){
    
    
    // ...省略其他代码
    p_demux->pf_control = Control;    
}
// Control方法的实现:
static int Control( demux_t *p_demux, int i_query, va_list args )
{
    
    // ...省略其他代码
       demux_sys_t *p_sys = p_demux->p_sys;
      int64_t i64, *pi64;
      const uint64_t i_duration = __MAX(p_sys->i_duration, p_sys->i_cumulated_duration);
      switch( i_query ){
    
    
        case DEMUX_GET_LENGTH:
            pi64 = va_arg( args, int64_t * );
            if( p_sys->i_timescale > 0 )
            {
    
    // MP4中的视频时间缩放单位大于0,则计算其真正的视频时长
                *pi64 = MP4_rescale( i_duration,
                                     p_sys->i_timescale, CLOCK_FREQ );
            }
            else *pi64 = 0;
            return VLC_SUCCESS;         
     }
}

static int64_t MP4_rescale( int64_t i_value, uint32_t i_timescale, uint32_t i_newscale )
{
    
    
    if( i_timescale == i_newscale )
        return i_value;

    if( i_value <= INT64_MAX / i_newscale )
        return i_value * i_newscale / i_timescale;

    /* overflow */
    int64_t q = i_value / i_timescale;
    int64_t r = i_value % i_timescale;
    return q * i_newscale + r * i_newscale / i_timescale;
}

由此其他操作命令同理也通过类似的这种方式进行的,参数传递的是可变参数。

3、媒体数据流模块加载媒体数据流结构体信息和解复用模块创建数据交互通道流程分析:

// 首先确定媒体数据流结构体定义如下:位于【vlc/include/vlc_stream.h】
/**
 * \defgroup stream Stream
 * \ingroup input
 * Buffered input byte streams
 * @{
 * \file
 * Byte streams and byte stream filter modules interface
 */

/**
 * stream_t definition
 */

struct stream_t
{
    
    
    VLC_COMMON_MEMBERS

    /* Module properties for stream filter */
    module_t    *p_module;

    char        *psz_name;
    char        *psz_url; /**< Full URL or MRL (can be NULL) */
    const char  *psz_location; /**< Location (URL with the scheme stripped) */
    char        *psz_filepath; /**< Local file path (if applicable) */
    bool         b_preparsing; /**< True if this access is used to preparse */

    /* Stream source for stream filter */
    stream_t *p_source;
    ssize_t     (*pf_read)(stream_t *, void *buf, size_t len);
    block_t    *(*pf_block)(stream_t *, bool *eof);
    int         (*pf_control)(stream_t *, int i_query, va_list);
    /**
     * Private data pointer
     */
    void *p_sys;
    // ...省略部分代码
};

// 媒体流结构信息创建方法为:位于【vlc/src/input/access.c】
// 【注:该方法是在播放初始化流程Init方法调用链中调用的,
// 并将该返回值赋值给了demux_t结构中的s字段(stream_t *s)】
stream_t *stream_AccessNew(vlc_object_t *parent, input_thread_t *input,
                           bool preparsing, const char *url)
{
    
    
    // 该方法实现为根据URL创建一个媒体原始流结构信息
    
    // 注意:该方法中有两个stream流结构体,
    // 但是第一个s结构代表播放器通用的流结构信息。
    // 第二个access结构才是代表我们真正数据流访问模块的功能提供者,
    // 如rtsp/http/file/tcp等方式进行的数据传递读取数据流,
    // 而如下实现中第二个access流结构被赋值到第一个结构体的【p_sys】指针中,
    // 从整个调用链实现分析可知:这个做法其实类似面向对象中的装饰者或代理类的实现方案,
    // 可以实现模块化方案的加载。
    // 具体如何代理功能的,下面功能中只举例分析该功能【s->pf_read = AStreamReadStream;】
    
    // 代理实现者即初始化一个默认结构信息,用来代理下面的access
    stream_t *s = vlc_stream_CommonNew(parent, AStreamDestroy);
    if (unlikely(s == NULL))
        return NULL;

    // 真正模块功能实现提供者access,见后面3.1部分分析
    stream_t *access = access_New(VLC_OBJECT(s), input, preparsing, url);
    if (access == NULL)
    {
    
    
        stream_CommonDelete(s);
        return NULL;
    }

    s->p_input = input;
    s->psz_url = strdup(access->psz_url);

    const char *cachename;

    if (access->pf_block != NULL)
    {
    
    
        // 代理调用真正对应模块实现者access的对应方法功能
        s->pf_block = AStreamReadBlock;
        cachename = "prefetch,cache_block";
    }
    else
    if (access->pf_read != NULL)
    {
    
    
        // 代理调用真正对应模块实现者access的对应方法功能
        // 具体如何代理功能实现,见3.2小节分析
        s->pf_read = AStreamReadStream;
        cachename = "prefetch,cache_read";
    }
    else
    {
    
    
        cachename = NULL;
    }

    if (access->pf_readdir != NULL)
        s->pf_readdir = AStreamReadDir;
    else
        s->pf_readdir = AStreamNoReadDir;

    // 这两方法的实现其实是代理调用真正对应模块实现者access的对应方法功能
    s->pf_seek    = AStreamSeek;
    s->pf_control = AStreamControl;
    // 第二个access流结构被赋值到第一个结构体的【p_sys】指针中
    s->p_sys      = access;

    if (cachename != NULL)
        s = stream_FilterChainNew(s, cachename);
    return stream_FilterAutoNew(s);
}

3.1、access_New(VLC_OBJECT(s), input, preparsing, url); 实现分析:
位于【vlc/src/input/access.c】

static stream_t *access_New(vlc_object_t *parent, input_thread_t *input,
                            bool preparsing, const char *mrl)
{
    
    
    char *redirv[MAX_REDIR];
    unsigned redirc = 0;

    // 创建一个默认结构
    stream_t *access = vlc_stream_CommonNew(parent, vlc_access_Destroy);
    if (unlikely(access == NULL))
        return NULL;

    // 进行初始化
    access->p_input = input;
    access->psz_name = NULL;
    access->psz_url = strdup(mrl);
    access->psz_filepath = NULL;
    access->b_preparsing = preparsing;

    if (unlikely(access->psz_url == NULL))
        goto error;

    // while循环体主要实现为根据媒体输入源URL【URI】,
    // 选择加载对应能够加载该媒体数据源的获取数据模块【file/http/rtsp/tcp等】
    while (redirc < MAX_REDIR)
    {
    
    
        char *url = access->psz_url;
        msg_Dbg(access, "creating access: %s", url);

        // 获取【"://"】之后的字符串即可能真正的URL
        const char *p = strstr(url, "://");
        if (p == NULL)
            goto error;

        access->psz_name = strndup(url, p - url);
        if (unlikely(access->psz_name == NULL))
            goto error;

        access->psz_location = p + 3;
        // 根据URL尝试解析其文件路径
        access->psz_filepath = get_path(access->psz_location);
        if (access->psz_filepath != NULL)
            msg_Dbg(access, " (path: %s)", access->psz_filepath);

        // 加载能够处理该类型输入流的获取数据流模块,见3.1.1部分分析
        access->p_module = module_need(access, "access", access->psz_name,
                                       true);
        if (access->p_module != NULL) /* success */
        {
    
    
            while (redirc > 0)
                free(redirv[--redirc]);

            // 注意:此处判断非常重要,而该值的赋值是在【module_need】功能执行时赋值的
            // 【即对应模块在模块初始化入口方法中赋值的】
            assert(access->pf_control != NULL);
            return access;
        }
        // ... 省略其他代码
    }

    msg_Err(access, "too many redirections");
error:
    // ... 省略其他代码
    return NULL;
}

3.1.1、module_need实现:位于【vlc/src/modules/modules.c】

#undef module_need
module_t *module_need(vlc_object_t *obj, const char *cap, const char *name,
                      bool strict)
{
    
    
    return vlc_module_load(obj, cap, name, strict, generic_start, obj);
}

// 注意此处的该方法非常重要,它就是前面第1小节分析过的加载模块流程中的初始化该模块的入口方法
static int generic_start(void *func, va_list ap)
{
    
    
    vlc_object_t *obj = va_arg(ap, vlc_object_t *);
    int (*activate)(vlc_object_t *) = func;

    return activate(obj);
}

module_t *vlc_module_load(vlc_object_t *obj, const char *capability,
                          const char *name, bool strict,
                          vlc_activate_t probe, ...)
{
    
    // ... 省略其他代码
    // 注意此处变量【probe】就是指的上面的该方法【generic_start】
    // 实现原理:根据不同模块初始化模块加载时的功能类型声明,
    // 查找到所有符合功能要求的模块来匹配当前需要的功能【capability】
    int ret = module_load (obj, cand, probe, args);
}

static int module_load (vlc_object_t *obj, module_t *m,
                        vlc_activate_t init, va_list args)
{
    
    
    int ret = VLC_SUCCESS;

    if (module_Map(obj, m->plugin))
        return VLC_EGENERIC;

    if (m->pf_activate != NULL)
    {
    
    // 模块的初始化功能方法指针存在时进入
        va_list ap;

        va_copy (ap, args);
        // 注意此处:init为前面的该方法【generic_start】指针,
        // 因此此处也就调用了该模块的模块初始化入口方法,
        // 如果初始化成功即可代表该模块可以处理当前媒体流的数据
        ret = init (m->pf_activate, ap);
        va_end (ap);
    }

    if (ret != VLC_SUCCESS)
        vlc_objres_clear(obj);

    return ret;
}

3.2、s->pf_read = AStreamReadStream; 的代理功能实现:

/* Read access */
static ssize_t AStreamReadStream(stream_t *s, void *buf, size_t len)
{
    
    
    // 根据上述的功能分析,可知此处的流结构[s->p_sys]即为真正的流处理模块功能实现者
    stream_t *access = s->p_sys;
    input_thread_t *input = s->p_input;

    if (vlc_stream_Eof(access))
        return 0;
    if (vlc_killed())
        return -1;

    // 代码追踪下去可知,该方法最终调用了:
    //  [s->pf_read(s, buf, len);],该方法即access的实现方法,由此实现了代理功能
    ssize_t val = vlc_stream_ReadPartial(access, buf, len);

    if (val > 0 && input != NULL)
    {
    
    
        uint64_t total;

        vlc_mutex_lock(&input_priv(input)->counters.counters_lock);
        stats_Update(input_priv(input)->counters.p_read_bytes, val, &total);
        stats_Update(input_priv(input)->counters.p_input_bitrate, total, NULL);
        stats_Update(input_priv(input)->counters.p_read_packets, 1, NULL);
        vlc_mutex_unlock(&input_priv(input)->counters.counters_lock);
    }

    return val;
}

4、基于上面的分析已大致知晓VLC中各个demux/mux/encoder/decoder功能模块的加载原理。如下分析一个模块的功能处理流程:【以mp4文件格式和H264编码的本地文件为例展开分析】。
首先分析模块初始化入口方法功能:位于【vlc/modules/demux/mp4/mp4.c】

// 检查文件并初始化MP4格式结构信息
static int Open( vlc_object_t * p_this )
{
    
    
    demux_t  *p_demux = (demux_t *)p_this;
    demux_sys_t     *p_sys;

    const uint8_t   *p_peek;

    // MP4的Box结构体
    MP4_Box_t       *p_ftyp;
    const MP4_Box_t *p_mvhd = NULL;
    const MP4_Box_t *p_mvex = NULL;

    bool      b_enabled_es;

    // 实现功能:简单检查输入源是否为MP4封装格式的文件,读取11个字节数据
    /* A little test to see if it could be a mp4 */
    if( vlc_stream_Peek( p_demux->s, &p_peek, 11 ) < 11 ) return VLC_EGENERIC;

    switch( VLC_FOURCC( p_peek[4], p_peek[5], p_peek[6], p_peek[7] ) )
    {
    
     // 此处判断第5、6、7、8位置的字节是否为以下对应有效的字节
        case ATOM_moov:
        case ATOM_foov:
        case ATOM_moof:
        case ATOM_mdat:
        case ATOM_udta:
        case ATOM_free:
        case ATOM_skip:
        case ATOM_wide:
        case ATOM_uuid:
        case VLC_FOURCC( 'p', 'n', 'o', 't' ):
            break;
            // 一般mp4文件这四个字节对应该值【ftyp】,并且要求f4v不支持
        case ATOM_ftyp:
            /* We don't yet support f4v, but avformat does. */
            if( p_peek[8] == 'f' && p_peek[9] == '4' && p_peek[10] == 'v' )
                return VLC_EGENERIC;
            break;
         default:
            return VLC_EGENERIC;
    }

    /* create our structure that will contains all data */
    p_sys = calloc( 1, sizeof( demux_sys_t ) );
    if ( !p_sys )
        return VLC_EGENERIC;

    // 检查输入源媒体流拉取端是否支持进行seek解析操作
    // 该方法位于【vlc_stream.h】中,并且最终调用了stream_t的方法即【s->pf_control(s, cmd, args);】
    // 而该方法此前已分析过,通过代理调用真正媒体流拉取实现者,
    // 分析本地mp4文件则此处为[file.c]文件拉取数据模块的该方法实现
    // 即FileControl( stream_t *p_access, int i_query, va_list args ),
    // 最终可知是否可以seek是根据文件的读取状态决定的
    /* I need to seek */
    vlc_stream_Control( p_demux->s, STREAM_CAN_SEEK, &p_sys->b_seekable );
    // 如果本地文件允许seek操作,则再次检查是否可以快速seek操作,同理可看到file模块的处理实现
    if( p_sys->b_seekable )
        vlc_stream_Control( p_demux->s, STREAM_CAN_FASTSEEK, &p_sys->b_fastseekable );

    // 设置支持的功能:解复用和交互控制,功能以方法指针链接
    /*Set exported functions */
    p_demux->pf_demux = Demux;
    p_demux->pf_control = Control;

    p_sys->context.i_lastseqnumber = UINT32_MAX;

    // 注意此处赋值关注点:前后两个结构体其实不是同一个结构体,
    // 但是此处巧妙的使用了结构体指针来进行赋值转换操作,因此是可行的。
    // 当然需注意再次使用时需要知道此前赋值的是啥才能有效转换回去
    p_demux->p_sys = p_sys;

    // 实现:解析文件加载MP4格式所有的Box结构体信息【链表结构实现】
    //【除了媒体流原始信息外-->即不包括编码的媒体流信息】
    // 分析见4.1小节
    if( LoadInitFrag( p_demux ) != VLC_SUCCESS )
        goto error;

    // Dump打印输出初始化的所有Box结构体信息
    MP4_BoxDumpStructure( p_demux->s, p_sys->p_root );

    // 获取到媒体文件类型【ftyp】结构体信息
    if( ( p_ftyp = MP4_BoxGet( p_sys->p_root, "/ftyp" ) ) )
    {
    
    
        switch( BOXDATA(p_ftyp)->i_major_brand )
        {
    
    
            case MAJOR_isom:
                msg_Dbg( p_demux,
                         "ISO Media (isom) version %d.",
                         BOXDATA(p_ftyp)->i_minor_version );
                break;
            case MAJOR_3gp4:
            case MAJOR_3gp5:
            case MAJOR_3gp6:
            case MAJOR_3gp7:
                msg_Dbg( p_demux, "3GPP Media Release: %4.4s",
                         (char *)&BOXDATA(p_ftyp)->i_major_brand );
                break;
            case MAJOR_qt__:
                msg_Dbg( p_demux, "Apple QuickTime media" );
                break;
            case MAJOR_isml:
                msg_Dbg( p_demux, "PIFF (= isml = fMP4) media" );
                break;
            case MAJOR_dash:
                msg_Dbg( p_demux, "DASH Stream" );
                break;
            case MAJOR_M4A:
                msg_Dbg( p_demux, "iTunes audio" );
                if( var_InheritBool( p_demux, CFG_PREFIX"m4a-audioonly" ) )
                    p_sys->hacks.es_cat_filters = AUDIO_ES;
                break;
            default:
                msg_Dbg( p_demux,
                         "unrecognized major media specification (%4.4s).",
                          (char*)&BOXDATA(p_ftyp)->i_major_brand );
                break;
        }
        /* also lookup in compatibility list */
        for(uint32_t i=0; i<BOXDATA(p_ftyp)->i_compatible_brands_count; i++)
        {
    
    
            if (BOXDATA(p_ftyp)->i_compatible_brands[i] == MAJOR_dash)
            {
    
    
                msg_Dbg( p_demux, "DASH Stream" );
            }
            else if (BOXDATA(p_ftyp)->i_compatible_brands[i] == VLC_FOURCC('s', 'm', 'o', 'o') )
            {
    
    
                msg_Dbg( p_demux, "Handling VLC Smooth Stream" );
            }
        }
    }
    else
    {
    
    
        msg_Dbg( p_demux, "file type box missing (assuming ISO Media)" );
    }

    // 检查【moov】结构体是否存在
    /* the file need to have one moov box */
    p_sys->p_moov = MP4_BoxGet( p_sys->p_root, "/moov" );
    if( unlikely(!p_sys->p_moov) )
    {
    
    
        p_sys->p_moov = MP4_BoxGet( p_sys->p_root, "/foov" );
        if( !p_sys->p_moov )
        {
    
    
            msg_Err( p_demux, "MP4 plugin discarded (no moov,foov,moof box)" );
            goto error;
        }
        /* we have a free box as a moov, rename it */
        p_sys->p_moov->i_type = ATOM_moov;
    }

    // 获取并初始化【mvhd】结构体信息
    p_mvhd = MP4_BoxGet( p_sys->p_moov, "mvhd" );
    if( p_mvhd && BOXDATA(p_mvhd) && BOXDATA(p_mvhd)->i_timescale )
    {
    
    
        p_sys->i_timescale = BOXDATA(p_mvhd)->i_timescale;
        p_sys->i_moov_duration = p_sys->i_duration = BOXDATA(p_mvhd)->i_duration;
        p_sys->i_cumulated_duration = BOXDATA(p_mvhd)->i_duration;
    }
    else
    {
    
    
        msg_Warn( p_demux, "No valid mvhd found" );
        goto error;
    }

    MP4_Box_t *p_rmra = MP4_BoxGet( p_sys->p_root, "/moov/rmra" );
    if( p_rmra != NULL && p_demux->p_input != NULL )
    {
    
    
        int        i_count = MP4_BoxCount( p_rmra, "rmda" );
        int        i;

        msg_Dbg( p_demux, "detected playlist mov file (%d ref)", i_count );

        input_thread_t *p_input = p_demux->p_input;
        input_item_t *p_current = input_GetItem( p_input );

        input_item_node_t *p_subitems = input_item_node_Create( p_current );

        for( i = 0; i < i_count; i++ )
        {
    
    
            MP4_Box_t *p_rdrf = MP4_BoxGet( p_rmra, "rmda[%d]/rdrf", i );
            char      *psz_ref;
            uint32_t  i_ref_type;

            if( !p_rdrf || !BOXDATA(p_rdrf) || !( psz_ref = strdup( BOXDATA(p_rdrf)->psz_ref ) ) )
            {
    
    
                continue;
            }
            i_ref_type = BOXDATA(p_rdrf)->i_ref_type;

            msg_Dbg( p_demux, "new ref=`%s' type=%4.4s",
                     psz_ref, (char*)&i_ref_type );

            if( i_ref_type == VLC_FOURCC( 'u', 'r', 'l', ' ' ) )
            {
    
    
                if( strstr( psz_ref, "qt5gateQT" ) )
                {
    
    
                    msg_Dbg( p_demux, "ignoring pseudo ref =`%s'", psz_ref );
                    free( psz_ref );
                    continue;
                }
                if( !strncmp( psz_ref, "http://", 7 ) ||
                    !strncmp( psz_ref, "rtsp://", 7 ) )
                {
    
    
                    ;
                }
                else
                {
    
    
                    char *psz_absolute;
                    char *psz_path = strdup( p_demux->psz_location );
                    char *end = strrchr( psz_path, '/' );
                    if( end ) end[1] = '\0';
                    else *psz_path = '\0';

                    if( asprintf( &psz_absolute, "%s://%s%s",
                                  p_demux->psz_access, psz_path, psz_ref ) < 0 )
                    {
    
    
                        free( psz_ref );
                        free( psz_path );
                        input_item_node_Delete( p_subitems );
                        return VLC_ENOMEM;
                    }

                    free( psz_ref );
                    psz_ref = psz_absolute;
                    free( psz_path );
                }
                msg_Dbg( p_demux, "adding ref = `%s'", psz_ref );
                input_item_t *p_item = input_item_New( psz_ref, NULL );
                input_item_CopyOptions( p_item, p_current );
                // 每个item数据可以有多个子item数据如媒体流渲染过滤器创建新的item等
                input_item_node_AppendItem( p_subitems, p_item );
                input_item_Release( p_item );
            }
            else
            {
    
    
                msg_Err( p_demux, "unknown ref type=%4.4s FIXME (send a bug report)",
                         (char*)&BOXDATA(p_rdrf)->i_ref_type );
            }
            free( psz_ref );
        }

        // 最终调用了【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回调得到的处理。
        /* FIXME: create a stream_filter sub-module for this */
        if (es_out_Control(p_demux->out, ES_OUT_POST_SUBNODE, p_subitems))
            input_item_node_Delete(p_subitems);
    }

    if( !(p_mvhd = MP4_BoxGet( p_sys->p_root, "/moov/mvhd" ) ) )
    {
    
     // 没有找到【mvhd】结构体信息则进入
        if( !p_rmra )
        {
    
    
            msg_Err( p_demux, "cannot find /moov/mvhd" );
            goto error;
        }
        else
        {
    
    
            // 注:【DemuxRef】该方法为空实现
            msg_Warn( p_demux, "cannot find /moov/mvhd (pure ref file)" );
            p_demux->pf_demux = DemuxRef;
            return VLC_SUCCESS;
        }
    }
    else
    {
    
    
        p_sys->i_timescale = BOXDATA(p_mvhd)->i_timescale;
        if( p_sys->i_timescale == 0 )
        {
    
    
            msg_Err( p_this, "bad timescale" );
            goto error;
        }
    }

    // 获取音视频等【track】Box结构体信息个数
    const unsigned i_tracks = MP4_BoxCount( p_sys->p_root, "/moov/trak" );
    if( i_tracks < 1 )
    {
    
    
        msg_Err( p_demux, "cannot find any /moov/trak" );
        goto error;
    }
    msg_Dbg( p_demux, "found %u track%c", i_tracks, i_tracks ? 's':' ' );

    if( CreateTracks( p_demux, i_tracks ) != VLC_SUCCESS )
        goto error;

    /* Search the first chap reference (like quicktime) and
     * check that at least 1 stream is enabled */
    p_sys->p_tref_chap = NULL;
    b_enabled_es = false;
    for( unsigned i = 0; i < p_sys->i_tracks; i++ )
    {
    
    
        MP4_Box_t *p_trak = MP4_BoxGet( p_sys->p_root, "/moov/trak[%d]", i );


        MP4_Box_t *p_tkhd = MP4_BoxGet( p_trak, "tkhd" );
        if( p_tkhd && BOXDATA(p_tkhd) && (BOXDATA(p_tkhd)->i_flags&MP4_TRACK_ENABLED) )
            b_enabled_es = true;

        MP4_Box_t *p_chap = MP4_BoxGet( p_trak, "tref/chap", i );
        if( p_chap && p_chap->data.p_tref_generic &&
            p_chap->data.p_tref_generic->i_entry_count > 0 && !p_sys->p_tref_chap )
            p_sys->p_tref_chap = p_chap;
    }

    // 初始化并保存一些媒体元数据
    /* Set and store metadata */
    if( (p_sys->p_meta = vlc_meta_New()) )
        MP4_LoadMeta( p_sys, p_sys->p_meta );

    // 执行每个track并获取有用信息
    /* now process each track and extract all useful information */
    for( unsigned i = 0; i < p_sys->i_tracks; i++ )
    {
    
    
        MP4_Box_t *p_trak = MP4_BoxGet( p_sys->p_root, "/moov/trak[%u]", i );
        // 解析track信息并创建运行时所需数据如展示的宽高、语言类型、音视频track或字幕track信息解析、编解码类型、
        // 创建并初始化数据块chunk索引列表和样本simple索引列表、为每一个track创建一个ES结构体信息、fps信息、
        MP4_TrackSetup( p_demux, &p_sys->track[i], p_trak, true, !b_enabled_es );

        if( p_sys->track[i].b_ok && !p_sys->track[i].b_chapters_source )
        {
    
    
            const char *psz_cat;
            switch( p_sys->track[i].fmt.i_cat )
            {
    
    
                case( VIDEO_ES ):
                    psz_cat = "video";
                    break;
                case( AUDIO_ES ):
                    psz_cat = "audio";
                    break;
                case( SPU_ES ):
                    psz_cat = "subtitle";
                    break;

                default:
                    psz_cat = "unknown";
                    break;
            }

            msg_Dbg( p_demux, "adding track[Id 0x%x] %s (%s) language %s",
                     p_sys->track[i].i_track_ID, psz_cat,
                     p_sys->track[i].b_enable ? "enable":"disable",
                     p_sys->track[i].fmt.psz_language ?
                     p_sys->track[i].fmt.psz_language : "undef" );
        }
        else if( p_sys->track[i].b_ok && p_sys->track[i].b_chapters_source )
        {
    
    
            msg_Dbg( p_demux, "using track[Id 0x%x] for chapter language %s",
                     p_sys->track[i].i_track_ID,
                     p_sys->track[i].fmt.psz_language ?
                     p_sys->track[i].fmt.psz_language : "undef" );
        }
        else
        {
    
    
            msg_Dbg( p_demux, "ignoring track[Id 0x%x]",
                     p_sys->track[i].i_track_ID );
        }
    }

    p_mvex = MP4_BoxGet( p_sys->p_moov, "mvex" );
    if( p_mvex != NULL )
    {
    
    
        const MP4_Box_t *p_mehd = MP4_BoxGet( p_mvex, "mehd");
        if ( p_mehd && BOXDATA(p_mehd) )
        {
    
    
            if( BOXDATA(p_mehd)->i_fragment_duration > p_sys->i_duration )
            {
    
    
                p_sys->b_fragmented = true;
                p_sys->i_duration = BOXDATA(p_mehd)->i_fragment_duration;
            }
        }

        const MP4_Box_t *p_sidx = MP4_BoxGet( p_sys->p_root, "sidx");
        if( p_sidx )
            p_sys->b_fragmented = true;

        if ( p_sys->b_seekable )
        {
    
    
            if( !p_sys->b_fragmented /* as unknown */ )
            {
    
    
                /* Probe remaining to check if there's really fragments
                   or if that file is just ready to append fragments */
                ProbeFragments( p_demux, (p_sys->i_duration == 0), &p_sys->b_fragmented );
            }

            if( vlc_stream_Seek( p_demux->s, p_sys->p_moov->i_pos ) != VLC_SUCCESS )
                goto error;
        }
        else /* Handle as fragmented by default as we can't see moof */
        {
    
    
            p_sys->context.p_fragment_atom = p_sys->p_moov;
            p_sys->context.i_current_box_type = ATOM_moov;
            p_sys->b_fragmented = true;
        }
    }

    if( p_sys->b_fragmented )
    {
    
    // 此处改变了该值
        p_demux->pf_demux = DemuxFrag;
        msg_Dbg( p_demux, "Set Fragmented demux mode" );
    }

    if( !p_sys->b_seekable && p_demux->pf_demux == Demux )
    {
    
    
        msg_Warn( p_demux, "MP4 plugin discarded (not seekable)" );
        goto error;
    }

    if( p_sys->i_tracks > 1 && !p_sys->b_fastseekable )
    {
    
    
        uint64_t i_max_continuity;
        bool b_flat;
        MP4_GetInterleaving( p_demux, &i_max_continuity, &b_flat );
        if( b_flat )
            msg_Warn( p_demux, "that media doesn't look interleaved, will need to seek");
        else if( i_max_continuity > DEMUX_TRACK_MAX_PRELOAD )
            msg_Warn( p_demux, "that media doesn't look properly interleaved, will need to seek");
    }

    // 加载字幕【章节】数据
    /* */
    LoadChapter( p_demux );

    // ASF文件格式功能【ASF是windows开发使用的媒体格式】
    p_sys->asfpacketsys.p_demux = p_demux;
    p_sys->asfpacketsys.pi_preroll = &p_sys->i_preroll;
    p_sys->asfpacketsys.pi_preroll_start = &p_sys->i_preroll_start;
    p_sys->asfpacketsys.pf_doskip = NULL;
    p_sys->asfpacketsys.pf_send = MP4ASF_Send;
    p_sys->asfpacketsys.pf_gettrackinfo = MP4ASF_GetTrackInfo;
    p_sys->asfpacketsys.pf_updatetime = NULL;
    p_sys->asfpacketsys.pf_setaspectratio = NULL;

    return VLC_SUCCESS;

error:
    if( vlc_stream_Tell( p_demux->s ) > 0 )
    {
    
    
        if( vlc_stream_Seek( p_demux->s, 0 ) != VLC_SUCCESS )
            msg_Warn( p_demux, "Can't reset stream position from probing" );
    }

    Close( p_this );

    return VLC_EGENERIC;
}

4.1、LoadInitFrag分析:位于【vlc/modules/demux/mp4/mp4.c】

static int LoadInitFrag( demux_t *p_demux )
{
    
    
    demux_sys_t *p_sys = p_demux->p_sys;

    /* Load all boxes ( except raw data ) */
    if( ( p_sys->p_root = MP4_BoxGetRoot( p_demux->s ) ) == NULL )
    {
    
    
        goto LoadInitFragError;
    }

    return VLC_SUCCESS;

LoadInitFragError:
    msg_Warn( p_demux, "MP4 plugin discarded (not a valid initialization chunk)" );
    return VLC_EGENERIC;
}

MP4_Box_t *MP4_BoxGetRoot( stream_t *p_stream )
{
    
    
    int i_result;

    // 初始化了一个“虚假”root节点的Box数据结构体,作为开始节点
    MP4_Box_t *p_vroot = MP4_BoxNew( ATOM_root );
    if( p_vroot == NULL )
        return NULL;

    p_vroot->i_shortsize = 1;
    uint64_t i_size;
    // 调用了【vlc_stream_Control( s, STREAM_GET_SIZE, size )】,
    // 则如上面类似分析可知最终调用了【file.c】的FileControl方法,
    // 通过获取当前文件得到文件总大小
    if( vlc_stream_GetSize( p_stream, &i_size ) == 0 )
        p_vroot->i_size = i_size;

    // 获取第一个有效结构体信息【moov】,并且如果读取成功则移动当前已读BOX容器位置position,
    // 然后对文件读取端进行seek操作
    /* First get the moov */
    {
    
    
        const uint32_t stoplist[] = {
    
     ATOM_moov, ATOM_mdat, 0 };
        i_result = MP4_ReadBoxContainerChildren( p_stream, p_vroot, stoplist );
    }

    /* mdat appeared first */
    if( i_result && !MP4_BoxGet( p_vroot, "moov" ) )
    {
    
     // 没有找到moov结构体信息
        bool b_seekable;
        if( vlc_stream_Control( p_stream, STREAM_CAN_SEEK, &b_seekable ) != VLC_SUCCESS || !b_seekable )
        {
    
    
            msg_Err( p_stream, "no moov before mdat and the stream is not seekable" );
            goto error;
        }

        // 重新加载moov结构体信息
        /* continue loading up to moov */
        const uint32_t stoplist[] = {
    
     ATOM_moov, 0 };
        i_result = MP4_ReadBoxContainerChildren( p_stream, p_vroot, stoplist );
    }

    if( !i_result )
        goto error;

    /* If there is a mvex box, it means fragmented MP4, and we're done */
    if( MP4_BoxCount( p_vroot, "moov/mvex" ) > 0 )
    {
    
     // 当前已读取完毕则进入
        /* Read a bit more atoms as we might have an index between moov and moof */
        const uint32_t stoplist[] = {
    
     ATOM_sidx, 0 };
        const uint32_t excludelist[] = {
    
     ATOM_moof, ATOM_mdat, 0 };
        MP4_ReadBoxContainerChildrenIndexed( p_stream, p_vroot, stoplist, excludelist, false );
        return p_vroot;
    }

    // 当前读取偏移位置小于文件大小,则继续读取剩余Box结构数据信息【循环读取】
    if( vlc_stream_Tell( p_stream ) + 8 < (uint64_t) stream_Size( p_stream ) )
    {
    
    
        /* Get the rest of the file */
        i_result = MP4_ReadBoxContainerChildren( p_stream, p_vroot, NULL );

        if( !i_result )
            goto error;
    }

    MP4_Box_t *p_moov;
    MP4_Box_t *p_cmov;

    // 检查是否有一个cmov,如果是则用未压缩的替换压缩的moov
    /* check if there is a cmov, if so replace
      compressed moov by  uncompressed one */
    if( ( ( p_moov = MP4_BoxGet( p_vroot, "moov" ) ) &&
          ( p_cmov = MP4_BoxGet( p_vroot, "moov/cmov" ) ) ) ||
        ( ( p_moov = MP4_BoxGet( p_vroot, "foov" ) ) &&
          ( p_cmov = MP4_BoxGet( p_vroot, "foov/cmov" ) ) ) )
    {
    
    
        /* rename the compressed moov as a box to skip */
        p_moov->i_type = ATOM_skip;

        /* get uncompressed p_moov */
        p_moov = p_cmov->data.p_cmov->p_moov;
        p_cmov->data.p_cmov->p_moov = NULL;

        /* make p_root father of this new moov */
        p_moov->p_father = p_vroot;

        /* insert this new moov box as first child of p_root */
        p_moov->p_next = p_vroot->p_first;
        p_vroot->p_first = p_moov;
    }

    return p_vroot;

error:
    MP4_BoxFree( p_vroot );
    MP4_Seek( p_stream, 0 );
    return NULL;
}

后续流程分析请见后续章节分析

猜你喜欢

转载自blog.csdn.net/u012430727/article/details/110590018