由前第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;