【十五】【vlc-android】vlc-sout流媒体输出端源码实现分析【Part 1】

由前第5章节分析第【1.2.1.1】小节(EsCreateDecoder实现分析:【vlc/src/input/es_out.c】中)分析可知sout媒体流输出端对象初始化大致流程概述:

// sout对象为【sout_instance_t】结构体信息(可能为空当设置了renderer时),
// 该信息定义为流输出实例即Stream Output,初始化流程:InitSout方法【vlc/src/input/input.c】-->
// input_resource_RequestSout方法【vlc/src/input/resource.c】-->sout_NewInstance方法【vlc/src/stream_out.c】,
// 并且其创建了内部字段流输出模块modules链【sout_stream_t】结构体信息。

并且若sout不为空时在【vlc/src/input/decoder.c】的【LoadDecoder】方法中会加载【module_need( p_dec, “packetizer”, “$packetizer”, false );】模块。
备注:【“packetizer”】组件名的分组分包器模块组件加载流程,见第十五章节【Part 2】部分分析

1、第三章分析[2.3.1.12.3.1.1]小节分析中播放器初始化阶段【vlc/src/input/input.c】的【Init】方法中默认开启sout流媒体输出模块的加载:

// [vlc/src/input/input.c]
static int Init( input_thread_t * p_input )
{
    
    
    // ...省略部分代码
#ifdef ENABLE_SOUT
    // 若启动了渲染器流输出链模块进行音视频输出(RTP、UDP、标准输出等),则初始化sout模块【stream_out】,
    // 如果已存在有效的sout则重复使用,否则创建一个新的链流输出模块。
    // 即根据多种输出链方式进行初始化对应的多个输出流模块即输出流模块链
    // 注:该实现内部有对输出链流方式如URI的解析处理等,此处不展开分析,内部的【"sout"】变量的值如输出流URI
    if( InitSout( p_input ) )
        goto error;
#endif
    // ...省略部分代码
}

// [vlc/src/input/input.c]
static int InitSout( input_thread_t * p_input )
{
    
    
    input_thread_private_t *priv = input_priv(p_input);

    if( priv->b_preparsing )
        return VLC_SUCCESS;

    // 获取一个非空并可用的sout流媒体输出对象描述,创建后将其关联p_input输入端信息对象
    // 该值是播放器默认写入或用户选择的
    /* Find a usable sout and attach it to p_input */
    char *psz = var_GetNonEmptyString( p_input, "sout" );
    if( priv->p_renderer )
    {
    
    // 同时需要渲染时
        /* Keep sout if it comes from a renderer and if the user didn't touch
         * the sout config */
        bool keep_sout = psz == NULL;
        free(psz);

        const char *psz_renderer_sout = vlc_renderer_item_sout( priv->p_renderer );
        // 将psz_renderer_sout格式化后数据写入psz中
        if( asprintf( &psz, "#%s", psz_renderer_sout ) < 0 )
            return VLC_ENOMEM;
        if( keep_sout )
            var_SetBool( p_input, "sout-keep", true );
    }
    if( psz && strncasecmp( priv->p_item->psz_uri, "vlc:", 4 ) )
    {
    
    // 有sout对象描述且URI字符串以vlc:开头,则进入
        // 见下面的分析
        priv->p_sout  = input_resource_RequestSout( priv->p_resource, NULL, psz );
        if( priv->p_sout == NULL )
        {
    
    
            input_ChangeState( p_input, ERROR_S );
            msg_Err( p_input, "cannot start stream output instance, " \
                              "aborting" );
            free( psz );
            return VLC_EGENERIC;
        }
        if( libvlc_stats( p_input ) )
        {
    
    // 初始化流媒体输出时相关统计值:发送包、字节数、码率,可用于分析问题
            INIT_COUNTER( sout_sent_packets, COUNTER );
            INIT_COUNTER( sout_sent_bytes, COUNTER );
            INIT_COUNTER( sout_send_bitrate, DERIVATIVE );
        }
    }
    else
    {
    
    
        // 根据该方法的分析,当后面两个参数为空时则表示destroy释放【缓存的】sout
        input_resource_RequestSout( priv->p_resource, NULL, NULL );
    }
    free( psz );

    return VLC_SUCCESS;
}

// [vlc/src/input/resource.c]
sout_instance_t *input_resource_RequestSout( input_resource_t *p_resource, sout_instance_t *p_sout, const char *psz_sout )
{
    
    
    vlc_mutex_lock( &p_resource->lock );
    sout_instance_t *p_ret = RequestSout( p_resource, p_sout, psz_sout );
    vlc_mutex_unlock( &p_resource->lock );

    return p_ret;
}

// [vlc/src/input/resource.c]
static sout_instance_t *RequestSout( input_resource_t *p_resource,
                                     sout_instance_t *p_sout, const char *psz_sout )
{
    
    
#ifdef ENABLE_SOUT
    if( !p_sout && !psz_sout )
    {
    
    // 若都为空则处理为释放当前sout对象
        if( p_resource->p_sout )
        {
    
    
            msg_Dbg( p_resource->p_sout, "destroying useless sout" );
            DestroySout( p_resource );
        }
        return NULL;
    }

    assert( !p_sout || ( !p_resource->p_sout && !psz_sout ) );

    // 检查已缓存的sout对象描述符类型是否为请求目标,若不是则释放
    /* Check the validity of the sout */
    if( p_resource->p_sout &&
        strcmp( p_resource->p_sout->psz_sout, psz_sout ) )
    {
    
    
        msg_Dbg( p_resource->p_parent, "destroying unusable sout" );
        DestroySout( p_resource );
    }

    if( psz_sout )
    {
    
    
        if( p_resource->p_sout )
        {
    
    // 此处表示,请求目标URL和当前缓存sout对象URL是相同的,因此重用即可
            /* Reuse it */
            msg_Dbg( p_resource->p_parent, "reusing sout" );
            msg_Dbg( p_resource->p_parent, "you probably want to use gather stream_out" );
        }
        else
        {
    
    // 不相同则新创建一个sout对象,并缓存起来
        // 见下面的分析
            /* Create a new one */
            p_resource->p_sout = sout_NewInstance( p_resource->p_parent, psz_sout );
        }

        p_sout = p_resource->p_sout;
        p_resource->p_sout = NULL;

        return p_sout;
    }
    else
    {
    
    
        // p_sout不为空而p_resource->p_sout为空时
        p_resource->p_sout = p_sout;
        return NULL;
    }
#else
    VLC_UNUSED (p_resource); VLC_UNUSED (p_sout); VLC_UNUSED (psz_sout);
    return NULL;
#endif
}

// 【vlc/src/stream_output/stream_output.c】
/*****************************************************************************
 * sout_NewInstance: creates a new stream output instance
 *****************************************************************************/
sout_instance_t *sout_NewInstance( vlc_object_t *p_parent, const char *psz_dest )
{
    
    
    sout_instance_t *p_sout;
    char *psz_chain;

    assert( psz_dest != NULL );

    if( psz_dest[0] == '#' )
    {
    
    
        // 去掉第一个“#”字符
        psz_chain = strdup( &psz_dest[1] );
    }
    else
    {
    
    
        // 将输入流地址转换为流输出链接地址
        // 【"sout-display"是一个配置值,若为true则表示需要开启duplicate模式(复制模式)
        // 即本地播放和流媒体输出同时进行】
        // 见下面的分析
        psz_chain = sout_stream_url_to_chain(
            var_InheritBool(p_parent, "sout-display"), psz_dest );
    }
    if(!psz_chain)
        return NULL;

    // 创建"stream output"流输出对象
    /* *** Allocate descriptor *** */
    p_sout = vlc_custom_create( p_parent, sizeof( *p_sout ), "stream output" );
    if( p_sout == NULL )
    {
    
    
        free( psz_chain );
        return NULL;
    }

    // 打印流媒体输出链
    msg_Dbg( p_sout, "using sout chain=`%s'", psz_chain );

    /* *** init descriptor *** */
    p_sout->psz_sout    = strdup( psz_dest );
    p_sout->i_out_pace_nocontrol = 0;

    vlc_mutex_init( &p_sout->lock );
    p_sout->p_stream = NULL;

    var_Create( p_sout, "sout-mux-caching", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );

    // 创建流输出【对象】链
    // 见下面的分析
    p_sout->p_stream = sout_StreamChainNew( p_sout, psz_chain, NULL, NULL );
    if( p_sout->p_stream )
    {
    
    
        free( psz_chain );
        return p_sout;
    }
    
    // sout创建错误

    msg_Err( p_sout, "stream chain failed for `%s'", psz_chain );
    free( psz_chain );

    FREENULL( p_sout->psz_sout );

    vlc_mutex_destroy( &p_sout->lock );
    vlc_object_release( p_sout );
    return NULL;
}

// 【vlc/src/stream_output/stream_output.c】
static char *sout_stream_url_to_chain( bool b_sout_display,
                                       const char *psz_url )
{
    
    
    mrl_t       mrl;
    char        *psz_chain;

    // 解析access、name、way属性参数
    mrl_Parse( &mrl, psz_url );

    // 检查url是否走的是#rtp -否则我们将使用#standard
    /* Check if the URLs goes to #rtp - otherwise we'll use #standard */
    // rtp应用层协议可能使用的传输层协议集合,rtp和rtsp都是应用层协议
    static const char rtplist[] = "dccp\0sctp\0tcp\0udplite\0";
    for (const char *a = rtplist; *a; a += strlen (a) + 1)
        if (strcmp (a, mrl.psz_access) == 0)
        // 相等,走的为rtp输出流方式,则流转到rtp处理
            goto rtp;

    if (strcmp (mrl.psz_access, "rtp") == 0)
    {
    
    // rtp传输流程
        // 端口号
        char *port;
        // vlc历史原因,此处rtp访问方式表示使用UDP上传输的RTP,则转换为UDP
        /* For historical reasons, rtp:// means RTP over UDP */
        strcpy (mrl.psz_access, "udp");
rtp:  // 若直接goto到此处,则代表去掉上面的udp转换处理,不会采用UDP传输数据
        if (mrl.psz_name[0] == '[')
        {
    
    
            // 查找字符串"]:"的首次出现【其实是返回后面所有的字符串】
            port = strstr (mrl.psz_name, "]:");
            if (port != NULL)
            // 若不为空则代表有端口值
                port++;
        }
        else
            // 查找字符“:”
            port = strchr (mrl.psz_name, ':');
        if (port != NULL)
            // 去掉“:”字符
            *port++ = '\0'; /* erase ':' */

        // 格式化rtp访问链地址描述,并保存在[psz_chain]
        if (asprintf (&psz_chain,
                      "rtp{mux=\"%s\",proto=\"%s\",dst=\"%s%s%s\"}",
                      mrl.psz_way, mrl.psz_access, mrl.psz_name,
                      port ? "\",port=\"" : "", port ? port : "") == -1)
            psz_chain = NULL;
    }
    else
    {
    
    // 若不是RTP访问方式,则转换为标准sout输出链地址描述
        /* Convert the URL to a basic standard sout chain */
        if (asprintf (&psz_chain,
                      "standard{mux=\"%s\",access=\"%s\",dst=\"%s\"}",
                      mrl.psz_way, mrl.psz_access, mrl.psz_name) == -1)
            psz_chain = NULL;
    }

    // duplicate模式开启即同时流媒体输出和本地播放
    /* Duplicate and wrap if sout-display is on */
    if (psz_chain && b_sout_display)
    {
    
    
        // 因此此处将URL链描述符格式化为两个dst内容,并标记duplicate模式
        char *tmp;
        if (asprintf (&tmp, "duplicate{dst=display,dst=%s}", psz_chain) == -1)
            tmp = NULL;
        free (psz_chain);
        psz_chain = tmp;
    }

    mrl_Clean( &mrl );
    return psz_chain;
}

// 【vlc/src/stream_output/stream_output.c】
// 根据注释:创建一个完整的"stream_out"模块链描述
/* Creates a complete "stream_out" modules chain
 *
 *  chain format: module1{option=*:option=*}[:module2{option=*:...}]
 *
 *  The modules are created starting from the last one and linked together
 *  A pointer to the last module created is stored if pp_last isn't NULL, to
 *  make sure sout_StreamChainDelete doesn't delete modules created in another
 *  place.
 *
 *  Returns a pointer to the first module.
 */
sout_stream_t *sout_StreamChainNew(sout_instance_t *p_sout, const char *psz_chain,
                                sout_stream_t *p_next, sout_stream_t **pp_last)
{
    
    
    if(!psz_chain || !*psz_chain)
    {
    
    
        if(pp_last) *pp_last = NULL;
        return p_next;
    }

    char *psz_parser = strdup(psz_chain);
    if(!psz_parser)
        return NULL;

    vlc_array_t cfg, name;
    vlc_array_init(&cfg);
    vlc_array_init(&name);

    // 循环解析vlc串流链描述【例如::sout=#transcode{vcodec=h264,vb=800,scale=自动,
    // acodec=mp4a,ab=128,channels=2,samplerate=8000,scodec=none}:display :sout-all :sout-keep】
    /* parse chain */
    while(psz_parser)
    {
    
    
        config_chain_t *p_cfg;
        char *psz_name;
        
        // 解析串流链K-V值
        char *psz_rest_chain = config_ChainCreate( &psz_name, &p_cfg, psz_parser );
        free( psz_parser );
        psz_parser = psz_rest_chain;

        // 数组中保存解析出的配置值
        vlc_array_append_or_abort(&cfg, p_cfg);
        vlc_array_append_or_abort(&name, psz_name);
    }

    size_t i = vlc_array_count(&name);
    vlc_array_t module;
    vlc_array_init(&module);
    while(i--)
    {
    
    
        // 创建每个串流模块,传入串流模块名字和配置信息等
        // 见下面的分析
        p_next = sout_StreamNew( p_sout, vlc_array_item_at_index(&name, i),
            vlc_array_item_at_index(&cfg, i), p_next);

        if(!p_next)
            goto error;

        // 将最后一个串流模块赋值
        if(i == vlc_array_count(&name) - 1 && pp_last)
            *pp_last = p_next;   /* last module created in the chain */

        // 将每次创建的流模块保存在数组中,该数组只用于在error时便于删除的
        vlc_array_append_or_abort(&module, p_next);
    }

    vlc_array_clear(&name);
    vlc_array_clear(&cfg);
    vlc_array_clear(&module);

    return p_next;

error:
// ... 省略部分代码
    return NULL;
}

// 【vlc/src/stream_output/stream_output.c】
/* Create a "stream_out" module, which may forward its ES to p_next module */
/*
 * XXX name and p_cfg are used (-> do NOT free them)
 */
static sout_stream_t *sout_StreamNew( sout_instance_t *p_sout, char *psz_name,
                               config_chain_t *p_cfg, sout_stream_t *p_next)
{
    
    
    sout_stream_t *p_stream;

    assert(psz_name);

    p_stream = vlc_custom_create( p_sout, sizeof( *p_stream ), "stream out" );
    if( !p_stream )
        return NULL;

    // 初始化保存信息
    p_stream->p_sout   = p_sout;
    p_stream->psz_name = psz_name;
    p_stream->p_cfg    = p_cfg;
    p_stream->p_next   = p_next;
    p_stream->pf_flush = NULL;
    p_stream->pf_control = NULL;
    p_stream->pace_nocontrol = false;
    p_stream->p_sys = NULL;

    msg_Dbg( p_sout, "stream=`%s'", p_stream->psz_name );

    // 加载对应的串流功能组件【"sout stream"】
    // 见第2小节分析
    p_stream->p_module =
        module_need( p_stream, "sout stream", p_stream->psz_name, true );

    if( !p_stream->p_module )
    {
    
    
        /* those must be freed by the caller if creation failed */
        p_stream->psz_name = NULL;
        p_stream->p_cfg = NULL;

        sout_StreamDelete( p_stream );
        return NULL;
    }

    p_sout->i_out_pace_nocontrol += p_stream->pace_nocontrol;
    return p_stream;
}

自此sout对象初始化创建过程已分析完毕。其调用处理流程请查看第5章节中sout相关分析流程【如第2.3.1小节分析】,并且通过上面的分析可知,sout输出模块组件是可能有多个存在即不同传输方式

2、sout流媒体输出组件模块的加载:
‘通过全局搜索串流功能组件【“sout stream”】如下,

// "Automatically add/delete input streams"
autodel.c (vlc\modules\stream_out) line 48 :     set_capability( "sout stream", 50 )

// 流媒体桥接方案? TODO
bridge.c (vlc\modules\stream_out) line 96 :     set_capability( "sout stream", 50 )
bridge.c (vlc\modules\stream_out) line 109 :     set_capability( "sout stream", 50 )

// google的投屏技术? TODO
cast.cpp (vlc\modules\stream_out\chromecast) line 241 :     set_capability("sout stream", 0)
cast.cpp (vlc\modules\stream_out\chromecast) line 266 :         set_capability("sout stream", 0)

// 音频指纹处理模块,类似可开发歌曲识别功能
chromaprint.c (vlc\modules\stream_out) line 62 :     set_capability( "sout stream", 0 )

// 周期性循环输出流
cycle.c (vlc\modules\stream_out) line 328 :     set_capability("sout stream", 0)

// 为每个基本码流指定一个延迟时间处理
delay.c (vlc\modules\stream_out) line 56 :     set_capability( "sout stream", 50 )

// description stream output module (gathers ES info)
description.c (vlc\modules\stream_out) line 55 :     set_capability( "sout stream", 50 )

// 播放输出流【传入解码器进行解码后播放】
display.c (vlc\modules\stream_out) line 55 :     set_capability( "sout stream", 50 )

// 假实现即空实现
dummy.c (vlc\modules\stream_out) line 51 :     set_capability( "sout stream", 50 )

// vlc中比较重要的功能实现:
// duplicate【复制】输出流推流模式即服务端推流的同时进行播放
duplicate.c (vlc\modules\stream_out) line 45 :     set_capability( "sout stream", 50 )

// 基本流推流组件【"Elementary stream output"】 实现单一码流的推流方式
// 例如,annexb就是h264裸码流Elementary Stream的格式
// ES流通常是指编码器的音频或视频输出, 且只能包含一种类型的数据: 如音频数据或视频数据或字幕等.
// PES用于定义如何在TS和PS流中以包的形式携带ES流.
es.c (vlc\modules\stream_out) line 81 :     set_capability( "sout stream", 50 )

// 收集流输出模块组件? 后续看情况分析 TODO
gather.c (vlc\modules\stream_out) line 46 :     set_capability( "sout stream", 50 )

// 马赛克效果处理流
mosaic_bridge.c (vlc\modules\stream_out) line 141 :     set_capability( "sout stream", 0 )

// 录制输出流模块
record.c (vlc\modules\stream_out) line 58 :     set_capability( "sout stream", 0 )

// RTP【RTSP】传输方式
rtp.c (vlc\modules\stream_out) line 189 :     set_capability( "sout stream", 0 )

setid.c (vlc\modules\stream_out) line 64 :     set_capability( "sout stream", 50 )
setid.c (vlc\modules\stream_out) line 77 :     set_capability( "sout stream", 50 )

// 流输出到内存缓冲区中处理 【"Stream output to memory buffer"】
smem.c (vlc\modules\stream_out) line 98 :     set_capability( "sout stream", 0 )

// 标准流输出模块 【"Standard stream output"】,支持【"std", "file", "http", "udp", "srt"】
standard.c (vlc\modules\stream_out) line 94 :     set_capability( "sout stream", 50 )

// 将流相关统计状态值输出到文件中而非通过标准输出模块【打印】
// 【"Writes stats to file instead of stdout"】
stats.c (vlc\modules\stream_out) line 53 :     set_capability( "sout stream", 0 )

// 转码输出流,该模块在流输出需要进行转码时加载的
transcode.c (vlc\modules\stream_out\transcode) line 148 :     set_capability( "sout stream", 50 )

如上,主要分析的模块组件有:

// vlc中比较重要的功能实现:
// duplicate【复制】输出流推流模式即服务端推流的同时进行播放
duplicate.c (vlc\modules\stream_out) line 45 :     set_capability( "sout stream", 50 )

// 基本流推流组件【"Elementary stream output"】 实现单一码流的推流方式
// 例如,annexb就是h264裸码流Elementary Stream的格式
// ES流通常是指编码器的音频或视频输出, 且只能包含一种类型的数据: 如音频数据或视频数据或字幕等.
// PES用于定义如何在TS和PS流中以包的形式携带ES流.
es.c (vlc\modules\stream_out) line 81 :     set_capability( "sout stream", 50 )

// RTP【RTSP】传输方式
rtp.c (vlc\modules\stream_out) line 189 :     set_capability( "sout stream", 0 )

// 转码输出流,该模块在流输出需要进行转码时加载的
transcode.c (vlc\modules\stream_out\transcode) line 148 :     set_capability( "sout stream", 50 )

本章分析RTP推流方式【vlc中live555没有用来推流,只用来拉流,推流vlc自己实现的】,其它实现组件后续接着分析

// RTP【RTSP】传输方式
rtp.c (vlc\modules\stream_out) line 189 :     set_capability( "sout stream", 0 )

RTP推流模块组件声明:【vlc/modules/stream_out/rtp.c】

#define SOUT_CFG_PREFIX "sout-rtp-"
#define MAX_EMPTY_BLOCKS 200

vlc_module_begin ()
    set_shortname( N_("RTP"))
    set_description( N_("RTP stream output") )
    set_capability( "sout stream", 0 )
    // 【vod: video on demand 视频点播技术】
    add_shortcut( "rtp", "vod" )
    set_category( CAT_SOUT )
    set_subcategory( SUBCAT_SOUT_STREAM )

    add_string( SOUT_CFG_PREFIX "dst", "", DEST_TEXT,
                DEST_LONGTEXT, true )
    add_string( SOUT_CFG_PREFIX "sdp", "", SDP_TEXT,
                SDP_LONGTEXT, true )
    add_string( SOUT_CFG_PREFIX "mux", "", MUX_TEXT,
                MUX_LONGTEXT, true )
    add_bool( SOUT_CFG_PREFIX "sap", false, SAP_TEXT, SAP_LONGTEXT,
              true )

    add_string( SOUT_CFG_PREFIX "name", "", NAME_TEXT,
                NAME_LONGTEXT, true )
    add_string( SOUT_CFG_PREFIX "cat", "", CAT_TEXT, CAT_LONGTEXT, true )
    add_string( SOUT_CFG_PREFIX "description", "", DESC_TEXT,
                DESC_LONGTEXT, true )
    add_string( SOUT_CFG_PREFIX "url", "", URL_TEXT,
                URL_LONGTEXT, true )
    add_string( SOUT_CFG_PREFIX "email", "", EMAIL_TEXT,
                EMAIL_LONGTEXT, true )
    add_obsolete_string( SOUT_CFG_PREFIX "phone" ) /* since 3.0.0 */

    add_string( SOUT_CFG_PREFIX "proto", "udp", PROTO_TEXT,
                PROTO_LONGTEXT, false )
        change_string_list( ppsz_protos, ppsz_protocols )
    add_integer( SOUT_CFG_PREFIX "port", 5004, PORT_TEXT,
                 PORT_LONGTEXT, true )
    add_integer( SOUT_CFG_PREFIX "port-audio", 0, PORT_AUDIO_TEXT,
                 PORT_AUDIO_LONGTEXT, true )
    add_integer( SOUT_CFG_PREFIX "port-video", 0, PORT_VIDEO_TEXT,
                 PORT_VIDEO_LONGTEXT, true )

    add_integer( SOUT_CFG_PREFIX "ttl", -1, TTL_TEXT,
                 TTL_LONGTEXT, true )
    add_bool( SOUT_CFG_PREFIX "rtcp-mux", false,
              RTCP_MUX_TEXT, RTCP_MUX_LONGTEXT, false )
    add_integer( SOUT_CFG_PREFIX "caching", DEFAULT_PTS_DELAY / 1000,
                 CACHING_TEXT, CACHING_LONGTEXT, true )

// RTP (Real-time Transport Protocol) and SRTP (Secure RTP) 安全RTP
#ifdef HAVE_SRTP
    add_string( SOUT_CFG_PREFIX "key", "",
                SRTP_KEY_TEXT, SRTP_KEY_LONGTEXT, false )
    add_string( SOUT_CFG_PREFIX "salt", "",
                SRTP_SALT_TEXT, SRTP_SALT_LONGTEXT, false )
#endif

    add_bool( SOUT_CFG_PREFIX "mp4a-latm", false, RFC3016_TEXT,
                 RFC3016_LONGTEXT, false )

    // 见下面2.1小节分析
    set_callbacks( Open, Close )

    // 加载RTSP子模块【服务端】
    add_submodule ()
    set_shortname( N_("RTSP VoD" ) )
    set_description( N_("RTSP VoD server") )
    set_category( CAT_SOUT )
    set_subcategory( SUBCAT_SOUT_VOD )
    set_capability( "vod server", 10 )
    // 见第十五章节【Part 5】rtsp VoD server服务器端实现分析
    set_callbacks( OpenVoD, CloseVoD )
    add_shortcut( "rtsp" )
    // 默认超时60s 【通过后续分析可知单位为秒】
    add_integer( "rtsp-timeout", 60, RTSP_TIMEOUT_TEXT,
                 RTSP_TIMEOUT_LONGTEXT, true )
    // 用户和密码
    add_string( "sout-rtsp-user", "",
                RTSP_USER_TEXT, RTSP_USER_LONGTEXT, true )
    add_password( "sout-rtsp-pwd", "",
                  RTSP_PASS_TEXT, RTSP_PASS_LONGTEXT, true )

vlc_module_end ()

2.1、RTP模块组件初始化入口方法Open实现:

// 【vlc/modules/stream_output/rtp.c】
static int Open( vlc_object_t *p_this )
{
    
    
    sout_stream_t       *p_stream = (sout_stream_t*)p_this;
    sout_stream_sys_t   *p_sys = NULL;
    char                *psz;
    bool          b_rtsp = false;

    // 解析sout流对象配置信息,并创建对应的变量保存
    config_ChainParse( p_stream, SOUT_CFG_PREFIX,
                       ppsz_sout_options, p_stream->p_cfg );

    p_sys = malloc( sizeof( sout_stream_sys_t ) );
    if( p_sys == NULL )
        return VLC_ENOMEM;

    // 获取上面的流媒体配置信息
    
    p_sys->psz_destination = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "dst" );

    p_sys->i_port       = var_GetInteger( p_stream, SOUT_CFG_PREFIX "port" );
    p_sys->i_port_audio = var_GetInteger( p_stream, SOUT_CFG_PREFIX "port-audio" );
    p_sys->i_port_video = var_GetInteger( p_stream, SOUT_CFG_PREFIX "port-video" );
    p_sys->rtcp_mux     = var_GetBool( p_stream, SOUT_CFG_PREFIX "rtcp-mux" );

    if( p_sys->i_port_audio && p_sys->i_port_video == p_sys->i_port_audio )
    {
    
    // 错误原因:音频和视频的RTP流端口必须不一样
        msg_Err( p_stream, "audio and video RTP port must be distinct" );
        free( p_sys->psz_destination );
        free( p_sys );
        return VLC_EGENERIC;
    }

    for( config_chain_t *p_cfg = p_stream->p_cfg; p_cfg != NULL; p_cfg = p_cfg->p_next )
    {
    
    
        // 检查配置中是否有启动RTSP传输协议方式
        if( !strcmp( p_cfg->psz_name, "sdp" )
         && ( p_cfg->psz_value != NULL )
         && !strncasecmp( p_cfg->psz_value, "rtsp:", 5 ) )
        {
    
    
            b_rtsp = true;
            break;
        }
    }
    if( !b_rtsp )
    {
    
    
        // 若不是RTSP传输方式,则获取sdp描述【session descriptor protocol】
        psz = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "sdp" );
        if( psz != NULL )
        {
    
    
            if( !strncasecmp( psz, "rtsp:", 5 ) )
            // 此处sdp描述也是用rtsp
                b_rtsp = true;
            free( psz );
        }
    }

    // 下面接着是传输层协议的选择 【vlc中rtp推流默认使用UDP网络传输方式】
    /* Transport protocol */
    p_sys->proto = IPPROTO_UDP;
    // 获取配置协议
    psz = var_GetNonEmptyString (p_stream, SOUT_CFG_PREFIX"proto");

    if ((psz == NULL) || !strcasecmp (psz, "udp"))
        // 注意:vlc中rtp推流默认使用UDP网络传输方式
        (void)0; /* default */
    else
    if (!strcasecmp (psz, "dccp"))
    {
    
    // 数据包拥塞控制协议(DCCP)是一个提供双向单播拥塞控制连接的不可靠数据包传输协议,
    // 它适合传输相当大的数据量的应用,并且能在时间线和可靠性上权衡。
    // DCCP使用一个缓存来取代TCP的探测帧,这样减少了网络开销
        p_sys->proto = IPPROTO_DCCP;
        p_sys->rtcp_mux = true; /* Force RTP/RTCP mux */
    }
#if 0
    else
    if (!strcasecmp (psz, "sctp")) // 默认不开启
    {
    
    // 传输层协议,SCTP兼有TCP及UDP两者的特点,但有区别
        p_sys->proto = IPPROTO_TCP;
        p_sys->rtcp_mux = true; /* Force RTP/RTCP mux */
    }
#endif
#if 0
    else
    if (!strcasecmp (psz, "tcp")) // 默认不开启
    {
    
    
        p_sys->proto = IPPROTO_TCP;
        p_sys->rtcp_mux = true; /* Force RTP/RTCP mux */
    }
#endif
    else
    if (!strcasecmp (psz, "udplite") || !strcasecmp (psz, "udp-lite"))
    // 轻量级用户数据包协议(UDP-Lite) 
        p_sys->proto = IPPROTO_UDPLITE;
    else
        msg_Warn (p_this, "unknown or unsupported transport protocol \"%s\"",
                  psz);
    free (psz);
    var_Create (p_this, "dccp-service", VLC_VAR_STRING);
    
    // 由上的网络传输协议处理分析,可知vlc默认不支持TCP传输协议

    p_sys->p_vod_media = NULL;
    p_sys->psz_vod_session = NULL;

    if (! strcmp(p_stream->psz_name, "vod"))
    {
    
    // 若name等于“vod”的处理
        /* The VLM stops all instances before deleting a media, so this
         * reference will remain valid during the lifetime of the rtp
         * stream output. */
        p_sys->p_vod_media = var_InheritAddress(p_stream, "vod-media");

        if (p_sys->p_vod_media != NULL)
        {
    
    
            p_sys->psz_vod_session = var_InheritString(p_stream, "vod-session");
            if (p_sys->psz_vod_session == NULL)
            {
    
    
                msg_Err(p_stream, "missing VoD session");
                free(p_sys);
                return VLC_EGENERIC;
            }

            const char *mux = vod_get_mux(p_sys->p_vod_media);
            var_SetString(p_stream, SOUT_CFG_PREFIX "mux", mux);
        }
    }

    if( p_sys->psz_destination == NULL && !b_rtsp
        && p_sys->p_vod_media == NULL )
    {
    
    
        msg_Err( p_stream, "missing destination and not in RTSP mode" );
        free( p_sys );
        return VLC_EGENERIC;
    }

    // 这是设置的Time to Live 生存时间值?
    int i_ttl = var_GetInteger( p_stream, SOUT_CFG_PREFIX "ttl" );
    if( i_ttl != -1 )
    {
    
    
        var_Create( p_stream, "ttl", VLC_VAR_INTEGER );
        var_SetInteger( p_stream, "ttl", i_ttl );
    }

    p_sys->b_latm = var_GetBool( p_stream, SOUT_CFG_PREFIX "mp4a-latm" );

    // NPT时间(Normal Play Time) 正常播放时间。
    /* NPT=0 time will be determined when we packetize the first packet
     * (of any ES). But we want to be able to report rtptime in RTSP
     * without waiting (and already did in the VoD case). So until then,
     * we use an arbitrary reference PTS for timestamp computations, and
     * then actual PTS will catch up using offsets. */
    p_sys->i_npt_zero = VLC_TS_INVALID;
    // 初始化一个任意的开始PTS时间戳值,默认使用【p_vod_media】指针值,长度48位bit
    p_sys->i_pts_zero = rtp_init_ts(p_sys->p_vod_media,
                                    p_sys->psz_vod_session);
    p_sys->i_es = 0;
    p_sys->es   = NULL;
    p_sys->rtsp = NULL;
    p_sys->psz_sdp = NULL;

    p_sys->b_export_sap = false;
    p_sys->p_session = NULL;
    p_sys->psz_sdp_file = NULL;

    p_sys->p_httpd_host = NULL;
    p_sys->p_httpd_file = NULL;

    p_stream->p_sys     = p_sys;

    vlc_mutex_init( &p_sys->lock_sdp );
    vlc_mutex_init( &p_sys->lock_ts );
    vlc_mutex_init( &p_sys->lock_es );

    // 获取配置的流数据复用类型【音视频流封装格式】:只支持PS或TS
    // 若为空则表示不需要复用器功能
    psz = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "mux" );
    if( psz != NULL )
    {
    
    // 需要加载流复用器模块功能
        /* Check muxer type */
        if( strncasecmp( psz, "ps", 2 )
         && strncasecmp( psz, "mpeg1", 5 )
         && strncasecmp( psz, "ts", 2 ) )
        {
    
    
            msg_Err( p_stream, "unsupported muxer type for RTP (only TS/PS)" );
            free( psz );
            vlc_mutex_destroy( &p_sys->lock_sdp );
            vlc_mutex_destroy( &p_sys->lock_ts );
            vlc_mutex_destroy( &p_sys->lock_es );
            free( p_sys->psz_vod_session );
            free( p_sys->psz_destination );
            free( p_sys );
            return VLC_EGENERIC;
        }

        // 输出访问对象
        p_sys->p_grab = GrabberCreate( p_stream );
        // 创建初始化流媒体复用器模块【文件格式】
        // 见2.2小节分析
        p_sys->p_mux = sout_MuxNew( p_stream->p_sout, psz, p_sys->p_grab );
        free( psz );

        if( p_sys->p_mux == NULL )
        {
    
    
            msg_Err( p_stream, "cannot create muxer" );
            sout_AccessOutDelete( p_sys->p_grab );
            vlc_mutex_destroy( &p_sys->lock_sdp );
            vlc_mutex_destroy( &p_sys->lock_ts );
            vlc_mutex_destroy( &p_sys->lock_es );
            free( p_sys->psz_vod_session );
            free( p_sys->psz_destination );
            free( p_sys );
            return VLC_EGENERIC;
        }

        p_sys->packet = NULL;

        // 添加输出流对象对应功能方法指针
        // 三功能分析见十五章节【Part 4】部分分析 TODO
        p_stream->pf_add  = MuxAdd;
        p_stream->pf_del  = MuxDel;
        p_stream->pf_send = MuxSend;
    }
    else
    {
    
    // 不需要加载流复用器模块功能
        p_sys->p_mux    = NULL;
        p_sys->p_grab   = NULL;

        // 添加输出流对象对应功能方法指针
        // 三功能分析见十五章节【Part 3】部分分析 TODO
        p_stream->pf_add    = Add;
        p_stream->pf_del    = Del;
        p_stream->pf_send   = Send;
    }
    // 默认不控制输出流速度
    p_stream->pace_nocontrol = true;

    // SAP(Session Announcement Protocol,会话通告协议)、SDP(Session Description Protocol,会话描述协议)
    if( var_GetBool( p_stream, SOUT_CFG_PREFIX"sap" ) )
        // 使用SAP处理SDP协议信息
        // 见2.3小节分析
        SDPHandleUrl( p_stream, "sap://" );

    // SDP会话描述协议内容
    psz = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "sdp" );
    if( psz != NULL )
    {
    
    
        config_chain_t *p_cfg;

        // 处理SDP信息
        // 见2.3小节分析
        SDPHandleUrl( p_stream, psz );

        // 根据网络传输协议配置来处理SDP
        for( p_cfg = p_stream->p_cfg; p_cfg != NULL; p_cfg = p_cfg->p_next )
        {
    
    
            if( !strcmp( p_cfg->psz_name, "sdp" ) )
            {
    
    
                if( p_cfg->psz_value == NULL || *p_cfg->psz_value == '\0' )
                    continue;

                /* needed both :sout-rtp-sdp= and rtp{sdp=} can be used */
                if( !strcmp( p_cfg->psz_value, psz ) )
                    continue;

                // 见2.3小节分析
                SDPHandleUrl( p_stream, p_cfg->psz_value );
            }
        }
        free( psz );
    }

    if( p_sys->p_mux != NULL )
    {
    
    // 媒体流复用器不为空时
        // 见第十五章节【Part 3】章节分析
        sout_stream_id_sys_t *id = Add( p_stream, NULL );
        if( id == NULL )
        {
    
    
            Close( p_this );
            return VLC_EGENERIC;
        }
    }

    return VLC_SUCCESS;
}

2.2、sout_MuxNew实现分析:

// 【vlc/src/stream_output/stream_output.c】
sout_mux_t * sout_MuxNew( sout_instance_t *p_sout, const char *psz_mux,
                          sout_access_out_t *p_access )
{
    
    
    sout_mux_t *p_mux;
    char       *psz_next;

    p_mux = vlc_custom_create( p_sout, sizeof( *p_mux ), "mux" );
    if( p_mux == NULL )
        return NULL;

    p_mux->p_sout = p_sout;
    // 解析音视频流包括字幕流的复用封装格式【串流链K-V值】
    psz_next = config_ChainCreate( &p_mux->psz_mux, &p_mux->p_cfg, psz_mux );
    free( psz_next );

    p_mux->p_access     = p_access;
    p_mux->pf_control   = NULL;
    p_mux->pf_addstream = NULL;
    p_mux->pf_delstream = NULL;
    p_mux->pf_mux       = NULL;
    p_mux->i_nb_inputs  = 0;
    p_mux->pp_inputs    = NULL;

    p_mux->p_sys        = NULL;
    p_mux->p_module     = NULL;

    p_mux->b_add_stream_any_time = false;
    // 默认需要等待添加流,而不是任意时间的添加
    p_mux->b_waiting_stream = true;
    p_mux->i_add_stream_start = -1;

    // 复用器【"sout mux"】模块组件加载
    // 见第十六章节流媒体复用分析
    p_mux->p_module =
        module_need( p_mux, "sout mux", p_mux->psz_mux, true );

    if( p_mux->p_module == NULL )
    {
    
    
        FREENULL( p_mux->psz_mux );

        vlc_object_release( p_mux );
        return NULL;
    }

    // 分析复用器能力
    /* *** probe mux capacity *** */
    if( p_mux->pf_control )
    {
    
    
        int b_answer = false;

        // 请求【p_mux->pf_control】复用器该方法
        if( sout_MuxControl( p_mux, MUX_CAN_ADD_STREAM_WHILE_MUXING,
                             &b_answer ) )
        {
    
    
            b_answer = false;
        }

        if( b_answer )
        {
    
    // 为true表示,当前流媒体复用器支持任意时间添加新的流数据进行文件格式复用操作,不需要等待
            msg_Dbg( p_sout, "muxer support adding stream at any time" );
            p_mux->b_add_stream_any_time = true;
            p_mux->b_waiting_stream = false;

            // 若需要控制输出流速度则最好在开始复用操作之前进行wait操作,以便获得更好的输出流或文件
            /* If we control the output pace then it's better to wait before
             * starting muxing (generates better streams/files). */
            if( !p_sout->i_out_pace_nocontrol )
            {
    
    // 不控制输出流速度时,默认在开始流复用操作之前进行wait操作
                b_answer = true;
            }
            else if( sout_MuxControl( p_mux, MUX_GET_ADD_STREAM_WAIT,
                                      &b_answer ) )
            {
    
    // 复用器请求失败则默认false
                b_answer = false;
            }

            if( b_answer )
            {
    
    // 流复用器【推荐】等待所有的ES(Elementary Stream)流数据后才开始复用操作
                msg_Dbg( p_sout, "muxer prefers to wait for all ES before "
                         "starting to mux" );
                p_mux->b_waiting_stream = true;
            }
        }
    }

    return p_mux;
}

2.3、SDPHandleUrl实现分析:【选择应用层协议来发送SDP信息】

// 【vlc/modules/stream_output/rtp.c】
static void SDPHandleUrl( sout_stream_t *p_stream, const char *psz_url )
{
    
    
    sout_stream_sys_t *p_sys = p_stream->p_sys;
    vlc_url_t url;

    // 解析URL
    vlc_UrlParse( &url, psz_url );
    if( url.psz_protocol && !strcasecmp( url.psz_protocol, "http" ) )
    {
    
    // http网络协议时
        if( p_sys->p_httpd_file )
        {
    
    // 只允许初始化一次
            msg_Err( p_stream, "you can use sdp=http:// only once" );
            goto out;
        }

        // 初始化http功能【流媒体HTTP网络传输】
        // 该方式目前暂不考虑分析 TODO
        if( HttpSetup( p_stream, &url ) )
        {
    
    // 失败,无法使用HTTP网络传输方式进行SDP会话描述协议请求
            msg_Err( p_stream, "cannot export SDP as HTTP" );
        }
    }
    else if( url.psz_protocol && !strcasecmp( url.psz_protocol, "rtsp" ) )
    {
    
    // RTSP流媒体传输协议时
        if( p_sys->rtsp != NULL )
        {
    
    // 只能初始化一次哦
            msg_Err( p_stream, "you can use sdp=rtsp:// only once" );
            goto out;
        }

        if( url.psz_host != NULL && *url.psz_host )
        {
    
    // 注译:RTSP主机地址在多主机地址配置中可能会被忽略,请自行承担使用风险。
            msg_Warn( p_stream, "\"%s\" RTSP host might be ignored in "
                      "multiple-host configurations, use at your own risks.",
                      url.psz_host );
            // 打印信息          
            msg_Info( p_stream, "Consider passing --rtsp-host=IP on the "
                                "command line instead." );

            // 创建rtsp主机地址变量并保存值
            var_Create( p_stream, "rtsp-host", VLC_VAR_STRING );
            var_SetString( p_stream, "rtsp-host", url.psz_host );
        }
        if( url.i_port != 0 )
        {
    
    // 端口号不为空时,创建对应变量并保存值
            /* msg_Info( p_stream, "Consider passing --rtsp-port=%u on "
                      "the command line instead.", url.i_port ); */

            var_Create( p_stream, "rtsp-port", VLC_VAR_INTEGER );
            var_SetInteger( p_stream, "rtsp-port", url.i_port );
        }

        // rtsp模块功能初始化
        // 见第十七章节RTSP模块功能分析 TODO
        p_sys->rtsp = RtspSetup( VLC_OBJECT(p_stream), NULL, url.psz_path );
        if( p_sys->rtsp == NULL )
            // 失败,无法使用RTSP网络传输协议进行SDP会话描述协议请求
            msg_Err( p_stream, "cannot export SDP as RTSP" );
    }
    else if( ( url.psz_protocol && !strcasecmp( url.psz_protocol, "sap" ) ) ||
             ( url.psz_host && !strcasecmp( url.psz_host, "sap" ) ) )
    {
    
    // 使用会话通告协议传输SDP会话描述协议内容
        p_sys->b_export_sap = true;
        // 该方式目前不考虑分析 TODO
        SapSetup( p_stream );
    }
    else if( url.psz_protocol && !strcasecmp( url.psz_protocol, "file" ) )
    {
    
    // 串流方式:file文件网络传输方式,即将流写入目标文件中
    // 该方式目前不考虑分析 TODO
        if( p_sys->psz_sdp_file != NULL )
        {
    
    
            msg_Err( p_stream, "you can use sdp=file:// only once" );
            goto out;
        }
        p_sys->psz_sdp_file = vlc_uri2path( psz_url );
        if( p_sys->psz_sdp_file == NULL )
            goto out;
        FileSetup( p_stream );
    }
    else
    {
    
    // 不支持的未知网络传输协议错误
        msg_Warn( p_stream, "unknown protocol for SDP (%s)",
                  url.psz_protocol );
    }

out:
    vlc_UrlClean( &url );
}

本章节到此结束,其他内容请往后章节查看

猜你喜欢

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