此章节分析承接上一章分析:
【十五】【vlc-android】vlc-sout流媒体输出端源码实现分析【Part 3】【01】
1.1.2.1、net_ListenSingle实现分析:【本地IP地址和端口号】
// [vlc/src/network/udp.c]
static int net_ListenSingle (vlc_object_t *obj, const char *host, int port,
int protocol)
{
// 网络地址信息
struct addrinfo hints = {
// sock类型即数据报套接字类型
.ai_socktype = SOCK_DGRAM,
// 协议类型【如UDP】
.ai_protocol = protocol,
// 输入标识
.ai_flags = AI_PASSIVE | AI_NUMERICSERV | AI_IDN,
}, *res;
if (host && !*host)
// 指针不为空但值为空则将指针置为空指针
host = NULL;
msg_Dbg (obj, "net: opening %s datagram port %d",
host ? host : "any", port);
// 内部主要使用getaddrinfo函数解析网址和IP地址【127.0.0.1】等【获取可用地址列表】
// 内部会有vlc的特殊处理:接收空串host主机地址请求等
int val = vlc_getaddrinfo (host, port, &hints, &res);
if (val)
{
msg_Err (obj, "Cannot resolve %s port %d : %s", host, port,
gai_strerror (val));
return -1;
}
val = -1;
// socket打开处理【可能需要从可用网络信息中选择可用的】
for (const struct addrinfo *ptr = res; ptr != NULL; ptr = ptr->ai_next)
{
// 打开socket并返回套接字描述符
// 见下面的分析
int fd = net_Socket (obj, ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (fd == -1)
{
msg_Dbg (obj, "socket error: %s", vlc_strerror_c(net_errno));
continue;
}
#ifdef IPV6_V6ONLY
/* Try dual-mode IPv6 if available. */
if (ptr->ai_family == AF_INET6)
setsockopt (fd, SOL_IPV6, IPV6_V6ONLY, &(int){ 0 }, sizeof (int));
#endif
// 初始化数据包套接字
// 见下面的分析
fd = net_SetupDgramSocket( obj, fd, ptr );
if( fd == -1 )
continue;
// 检查socket地址是否为多播地址,若为多播地址则执行加入多播组,
// 若加入失败则重新下一个地址
if (net_SockAddrIsMulticast (ptr->ai_addr, ptr->ai_addrlen)
&& net_Subscribe (obj, fd, ptr->ai_addr, ptr->ai_addrlen))
{
net_Close (fd);
continue;
}
val = fd;
break;
}
freeaddrinfo (res);
return val;
}
// [vlc/src/network/io.c]
int net_Socket (vlc_object_t *p_this, int family, int socktype,
int protocol)
{
// 内部调用socket函数创建socket套接字连接
int fd = vlc_socket (family, socktype, protocol, true);
if (fd == -1)
{
if (net_errno != EAFNOSUPPORT)
msg_Err (p_this, "cannot create socket: %s",
vlc_strerror_c(net_errno));
return -1;
}
// 设置套接字的选项:若等待时间,缓冲区大小等
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof (int));
// 以下为其他连接方式的处理,暂不分析
#ifdef IPV6_V6ONLY
/*
* Accepts only IPv6 connections on IPv6 sockets.
* If possible, we should open two sockets, but it is not always possible.
*/
if (family == AF_INET6)
setsockopt (fd, IPPROTO_IPV6, IPV6_V6ONLY, &(int){ 1 }, sizeof (int));
#endif
#if defined (_WIN32)
# ifndef IPV6_PROTECTION_LEVEL
# warning Please update your C library headers.
# define IPV6_PROTECTION_LEVEL 23
# define PROTECTION_LEVEL_UNRESTRICTED 10
# endif
if (family == AF_INET6)
setsockopt (fd, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL,
&(int){ PROTECTION_LEVEL_UNRESTRICTED }, sizeof (int));
#endif
#ifdef DCCP_SOCKOPT_SERVICE
if (socktype == SOL_DCCP)
{
char *dccps = var_InheritString (p_this, "dccp-service");
if (dccps != NULL)
{
setsockopt (fd, SOL_DCCP, DCCP_SOCKOPT_SERVICE, dccps,
(strlen (dccps) + 3) & ~3);
free (dccps);
}
}
#endif
return fd;
}
// [vlc/src/network/udp.c]
static int net_SetupDgramSocket (vlc_object_t *p_obj, int fd,
const struct addrinfo *ptr)
{
#if defined (SO_REUSEPORT) && !defined (__linux__)
// 设置套接字的选项
setsockopt (fd, SOL_SOCKET, SO_REUSEPORT, &(int){ 1 }, sizeof (int));
#endif
#if defined (_WIN32)
// ... 省略中间Windows平台处理
#endif
// 调用bind将IP地址信息【地址和端口号、缓冲区的长度】和socket绑定
if (bind (fd, ptr->ai_addr, ptr->ai_addrlen))
{
msg_Err( p_obj, "socket bind error: %s", vlc_strerror_c(net_errno) );
net_Close (fd);
return -1;
}
return fd;
}
1.2、rtp_get_fmt实现分析:【vlc/modules/stream_out/ftpfmt.c】
// 注解意思是:可以考虑更简单的方式实现下面大串switch匹配方式代码实现
/* TODO: make this into something more clever than a big switch? */
int rtp_get_fmt( vlc_object_t *obj, const es_format_t *p_fmt, const char *mux,
rtp_format_t *rtp_fmt )
{
assert( p_fmt != NULL || mux != NULL );
// 动态的负载类型。 放入每个ES流到它自己的RTP会话轨道数据中,因此没有冲突风险
/* Dynamic payload type. Payload types are scoped to the RTP
* session, and we put each ES in its own session, so no risk of
* conflict. */
rtp_fmt->payload_type = 96;
// SDP数据格式负载的数据类型:音频流、视频流、字幕流、数据流。 默认处理为视频流
rtp_fmt->cat = mux != NULL ? VIDEO_ES : p_fmt->i_cat;
if( rtp_fmt->cat == AUDIO_ES )
{
// 采样率和声音通道格式
rtp_fmt->clock_rate = p_fmt->audio.i_rate;
rtp_fmt->channels = p_fmt->audio.i_channels;
}
else
// 不是音频流时【最常见情况就是视频】,则默认设置时钟速率为90KHz
rtp_fmt->clock_rate = 90000; /* most common case for video */
// 媒体流码率计算,若p_fmt对象为空则为0,【单位为kbps】
/* Stream bitrate in kbps */
rtp_fmt->bitrate = p_fmt != NULL ? p_fmt->i_bitrate/1000 : 0;
rtp_fmt->fmtp = NULL;
// RTP流复用格式【封装格式】,若指定了封装格式则直接返回
if( mux != NULL )
{// 不为空,若为TS则使用MEPG-TS流传输封装格式,否则默认为MEPG-PS封装格式
if( strncmp( mux, "ts", 2 ) == 0 )
{
rtp_fmt->payload_type = 33;
rtp_fmt->ptname = "MP2T";
}
else
rtp_fmt->ptname = "MP2P";
return VLC_SUCCESS;
}
// 未指定封装格式则根据当前编解码类型
switch( p_fmt->i_codec )
{
case VLC_CODEC_MULAW:
if( p_fmt->audio.i_channels == 1 && p_fmt->audio.i_rate == 8000 )
rtp_fmt->payload_type = 0;
rtp_fmt->ptname = "PCMU";
rtp_fmt->pf_packetize = rtp_packetize_pcm;
break;
// ... 省略部分代码
case VLC_CODEC_H263:
rtp_fmt->ptname = "H263-1998";
// 分组分包功能方法指针赋值,在第五章中分析调用的
rtp_fmt->pf_packetize = rtp_packetize_h263;
break;
case VLC_CODEC_H264:
rtp_fmt->ptname = "H264";
// 分组分包功能方法指针赋值,在第五章中分析调用的
// 该实现分析见后续章节 TODO
rtp_fmt->pf_packetize = rtp_packetize_h264;
rtp_fmt->fmtp = NULL;
// 【i_extra】解复用后es格式数据中的额外数据指针字节长度
if( p_fmt->i_extra > 0 )
{
// SPS 和 PPS
char *p_64_sps = NULL;
char *p_64_pps = NULL;
char hexa[6+1];
// H264数据迭代器
hxxx_iterator_ctx_t it;
hxxx_iterator_init( &it, p_fmt->p_extra, p_fmt->i_extra, 0 );
// NALU格式分为2类,VCL和non-VCL,总共有19种不同的NALU格式
// 通过下面的分析可知该指针指向AnnexB格式数据流开始位置【已去掉起始码】
const uint8_t *p_nal;
// 通过下面的分析可知该值表示【p_nal】指针负载的数据大小
size_t i_nal;
// 循环获取【Annex B流格式】数据即解析不同NALU数据
// Annex B格式通常用于实时的流格式,比如说传输流,通过无线传输的广播、DVD等。
// 在这些格式中通常会周期性的重复SPS和PPS包,经常是在每一个关键帧之前,
// 因此据此建立解码器可以一个随机访问的点,这样就可以加入一个正在进行的流,及播放一个已经在传输的流。
// 见1.2.1小节分析
while( hxxx_annexb_iterate_next( &it, &p_nal, &i_nal ) )
{// 根据分析可知while条件处理功能为:获取到AnnexB数据流
// 并去掉任何的AnnexB数据流格式中的起始码【三字节和四字节的起始码】
if( i_nal < 2 )
{// NAL数据大小小于2
msg_Dbg( obj, "No-info found in nal ");
continue;
}
// 根据H264码流架构可知,此处第一个字节数据即为NAL Header
// 因此此处和【0001 1111】第4到8位进行与运算来获取这几位的值,即为NALU类型
const int i_nal_type = p_nal[0]&0x1f;
msg_Dbg( obj, "we found a startcode for NAL with TYPE:%d", i_nal_type );
if( i_nal_type == 7 && i_nal >= 4 )
{// SPS类型数据处理,并且至少数据大小为4个字节
free( p_64_sps );
// 使用Base64算法来编码二进制数据得到SPS数据,便于网络传输
// 【Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一
// ,Base64编码可用于在HTTP环境下传递较长的标识信息】
p_64_sps = vlc_b64_encode_binary( p_nal, i_nal );
// 读取NAL数据中的第2到4个字节数据【根据下面分析可知表示profile-level-id】
// 格式化转换二进制数据为十六进制数据表示
sprintf_hexa( hexa, &p_nal[1], 3 );
}
else if( i_nal_type == 8 )
{// PPS类型数据处理
free( p_64_pps );
// 同上SPS处理
p_64_pps = vlc_b64_encode_binary( p_nal, i_nal );
}
}
// 参数集PS和档次等级ID值格式化并保存
/* */
if( p_64_sps && p_64_pps &&
( asprintf( &rtp_fmt->fmtp,
"packetization-mode=1;profile-level-id=%s;"
"sprop-parameter-sets=%s,%s;", hexa, p_64_sps,
p_64_pps ) == -1 ) )
rtp_fmt->fmtp = NULL;
free( p_64_sps );
free( p_64_pps );
}
if( rtp_fmt->fmtp == NULL )
// 若没有额外数据信息,则默认初始化如下格式设置
rtp_fmt->fmtp = strdup( "packetization-mode=1" );
break;
// TODO 下面这几个编码格式目前暂时不分析,只重点研究H264码流处理过程,后续有时间再分析
case VLC_CODEC_HEVC:
{
rtp_fmt->ptname = "H265";
rtp_fmt->pf_packetize = rtp_packetize_h265;
rtp_fmt->fmtp = NULL;
int i_profile = p_fmt->i_profile;
int i_level = p_fmt->i_level;
int i_tiers = -1;
int i_space = -1;
struct nalset_e
{
const uint8_t i_nal;
const uint8_t i_extend;
const char *psz_name;
char *psz_64;
} nalsets[4] = {
{ 32, 0, "vps", NULL },
{ 33, 0, "sps", NULL },
{ 34, 0, "pps", NULL },
{ 39, 1, "sei", NULL },
};
if( p_fmt->i_extra > 0 )
{
hxxx_iterator_ctx_t it;
for(int i=0; i<4; i++)
{
struct nalset_e *set = &nalsets[i];
hxxx_iterator_init( &it, p_fmt->p_extra, p_fmt->i_extra, 0 );
const uint8_t *p_nal;
size_t i_nal;
while( hxxx_annexb_iterate_next( &it, &p_nal, &i_nal ) )
{
const uint8_t i_nal_type = (p_nal[0] & 0x7E) >> 1;
if( i_nal_type < set->i_nal ||
i_nal_type > set->i_nal + set->i_extend )
continue;
msg_Dbg( obj, "we found a startcode for NAL with TYPE:%" PRIu8, i_nal_type );
char *psz_temp = vlc_b64_encode_binary( p_nal, i_nal );
if( psz_temp )
{
if( set->psz_64 == NULL )
{
set->psz_64 = psz_temp;
}
else
{
char *psz_merged;
if( asprintf( &psz_merged, "%s,%s", set->psz_64, psz_temp ) != -1 )
{
free( set->psz_64 );
set->psz_64 = psz_merged;
}
free( psz_temp );
}
}
if( i_nal_type == 33 && i_nal > 12 )
{
if( i_profile < 0 )
i_profile = p_nal[1] & 0x1F;
if( i_space < 0 )
i_space = p_nal[1] >> 6;
if( i_tiers < 0 )
i_tiers = !!(p_nal[1] & 0x20);
if( i_level < 0 )
i_level = p_nal[12];
}
}
}
}
rtp_fmt->fmtp = strdup( "tx-mode=SRST;" );
if( rtp_fmt->fmtp )
{
char *psz_fmtp;
if( i_profile >= 0 &&
asprintf( &psz_fmtp, "%sprofile-id=%d;",
rtp_fmt->fmtp, i_profile ) != -1 )
{
free( rtp_fmt->fmtp );
rtp_fmt->fmtp = psz_fmtp;
}
if( i_level >= 0 &&
asprintf( &psz_fmtp, "%slevel-id=%d;",
rtp_fmt->fmtp, i_level ) != -1 )
{
free( rtp_fmt->fmtp );
rtp_fmt->fmtp = psz_fmtp;
}
if( i_tiers >= 0 &&
asprintf( &psz_fmtp, "%stier-flag=%d;",
rtp_fmt->fmtp, i_tiers ) != -1 )
{
free( rtp_fmt->fmtp );
rtp_fmt->fmtp = psz_fmtp;
}
if( i_space >= 0 &&
asprintf( &psz_fmtp, "%sprofile-space=%d;",
rtp_fmt->fmtp, i_space ) != -1 )
{
free( rtp_fmt->fmtp );
rtp_fmt->fmtp = psz_fmtp;
}
for(int i=0; i<4; i++)
{
struct nalset_e *set = &nalsets[i];
if( set->psz_64 &&
asprintf( &psz_fmtp, "%ssprop-%s=%s;",
rtp_fmt->fmtp,
set->psz_name,
set->psz_64 ) != -1 )
{
free( rtp_fmt->fmtp );
rtp_fmt->fmtp = psz_fmtp;
}
}
}
for(int i=0; i<4; i++)
free( nalsets[i].psz_64 );
break;
}
// ... 省略部分代码
case VLC_CODEC_R420:
rtp_fmt->ptname = "RAW";
rtp_fmt->pf_packetize = rtp_packetize_r420;
if( asprintf( &rtp_fmt->fmtp,
"sampling=YCbCr-4:2:0; width=%d; height=%d; "
"depth=8; colorimetry=BT%s",
p_fmt->video.i_visible_width, p_fmt->video.i_visible_height,
p_fmt->video.i_visible_height > 576 ? "709-2" : "601-5") == -1 )
{
rtp_fmt->fmtp = NULL;
return VLC_ENOMEM;
}
break;
case VLC_CODEC_RGB24:
rtp_fmt->ptname = "RAW";
rtp_fmt->pf_packetize = rtp_packetize_rgb24;
if( asprintf( &rtp_fmt->fmtp,
"sampling=RGB; width=%d; height=%d; "
"depth=8; colorimetry=SMPTE240M",
p_fmt->video.i_visible_width,
p_fmt->video.i_visible_height ) == -1 )
{
rtp_fmt->fmtp = NULL;
return VLC_ENOMEM;
}
break;
case VLC_CODEC_MJPG:
case VLC_CODEC_JPEG:
rtp_fmt->ptname = "JPEG";
rtp_fmt->payload_type = 26;
rtp_fmt->pf_packetize = rtp_packetize_jpeg;
break;
default:
msg_Err( obj, "cannot add this stream (unsupported "
"codec: %4.4s)", (char*)&p_fmt->i_codec );
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
1.2.1、hxxx_annexb_iterate_next实现分析:
//【vlc/modules/packetizer/hxxx_nal.h】
static inline bool hxxx_annexb_iterate_next( hxxx_iterator_ctx_t *p_ctx, const uint8_t **pp_start, size_t *pi_size )
{
if( !p_ctx->p_head )
return false;
// 起始码处理寻找AnnexB流格式数据开始位置
p_ctx->p_head = startcode_FindAnnexB( p_ctx->p_head, p_ctx->p_tail );
if( !p_ctx->p_head )
return false;
// 起始码处理寻找AnnexB流格式数据结尾位置
// 加3表示跳过AnnexB数据起始码【0x00 0x00 0x01】3字节大小
const uint8_t *p_end = startcode_FindAnnexB( p_ctx->p_head + 3, p_ctx->p_tail );
if( !p_end )
// 若为空则将传入的值附上
p_end = p_ctx->p_tail;
// 注译:修正3到4字节大小的起始码偏移量和删除任何尾随零
// 其实作用就是:去掉原始编码数据后面的结尾标记添加的字节对齐0
/* fix 3 to 4 startcode offset and strip any trailing zeros */
while( p_end > p_ctx->p_head && p_end[-1] == 0 )
p_end--;
// 当前AnnexB数据开始位置赋值【注意包括了起始码】
*pp_start = p_ctx->p_head;
// 当前AnnexB数据的大小
*pi_size = p_end - p_ctx->p_head;
// 指针指向AnnexB结尾数据位置,起始就是将其数据指针往后移,指向后面的数据
p_ctx->p_head = p_end;
// 去掉任何的AnnexB数据流格式中的起始码【三字节和四字节的起始码】
// 见下面的分析
return hxxx_strip_AnnexB_startcode( pp_start, pi_size );
}
//【vlc/modules/packetizer/startcode_helper.h】
/* That code is adapted from libav's ff_avc_find_startcode_internal
* and i believe the trick originated from
* https://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord
*/
static inline const uint8_t * startcode_FindAnnexB( const uint8_t *p, const uint8_t *end )
{
#if defined(CAN_COMPILE_SSE2) || defined(HAVE_SSE2_INTRINSICS)
// 英特尔设计的CPU SSE2整数指令集,拥有更快的【图像】处理速度
if (vlc_CPU_SSE2())
// 此处不展开分析,因为内部实现设计一些该寄存器指令算术的实现
return startcode_FindAnnexB_SSE2(p, end);
#endif
const uint8_t *a = p + 4 - ((intptr_t)p & 3);
// 循环查找起始码值【3个byte,其他类型数据起始码为4个】
// 每次指针指向地址往后移动1个字节大小,
// 若前三个起始码等于001字节序列值即【0000 0000 0001】起始码值,
// 则表示找到Annex B流格式,并将其数据流开始地址指针返回
for (end -= 3; p < a && p <= end; p++) {
if (p[0] == 0 && p[1] == 0 && p[2] == 1)
return p;
}
// 若上面的匹配查找失败,则进行4字节大小往后移动指针来匹配
for (end -= 3; p < end; p += 4) {
uint32_t x = *(const uint32_t*)p;
// ~x表示x的二进制位都取反包括符号位
if ((x - 0x01010101) & (~x) & 0x80808080)
{// 通过使用比单字节查找快4倍的技巧有效地查找AnnexB数据流格式起始码0x00 0x00 0x01。
// 该匹配算法不是很快
/* matching DW isn't faster */
// 见下面的宏定义
TRY_MATCH(p, 0);
}
}
// 若上面两次匹配查找都失败,则执行更慢的查找匹配算法即从头开始每个字节移动指针匹配
for (end += 3; p <= end; p++) {
if (p[0] == 0 && p[1] == 0 && p[2] == 1)
return p;
}
return NULL;
}
//【vlc/modules/packetizer/startcode_helper.h】
/* Looks up efficiently for an AnnexB startcode 0x00 0x00 0x01
* by using a 4 times faster trick than single byte lookup. */
#define TRY_MATCH(p,a) {\
if (p[a+1] == 0) {\
if (p[a+0] == 0 && p[a+2] == 1)\
return a+p;\
if (p[a+2] == 0 && p[a+3] == 1)\
return a+p+1;\
}\
if (p[a+3] == 0) {\
if (p[a+2] == 0 && p[a+4] == 1)\
return a+p+2;\
if (p[a+4] == 0 && p[a+5] == 1)\
return a+p+3;\
}\
}
//【vlc/modules/packetizer/hxxx_nal.h】
// 注译:去掉任何AnnexB数据流中的起始码【三字节和四字节的起始码】
/* strips any AnnexB startcode [0] 0 0 1 */
static inline bool hxxx_strip_AnnexB_startcode( const uint8_t **pp_data, size_t *pi_data )
{
// 定义的【比特流】记录
unsigned bitflow = 0;
const uint8_t *p_data = *pp_data;
size_t i_data = *pi_data;
// 循环查找来判断是否已跳过【指针往后移动】起始码,成功则放回true并修改参数变量值
while( i_data && p_data[0] <= 1 )
{// 注意此处的【bitflow】作用是:标记循环执行次数有效性【见下分析】
bitflow = (bitflow << 1) | (!p_data[0]);
p_data++;
i_data--;
if( !(bitflow & 0x01) )
{// 当第一次【p_data[0]】为1时才会进来,因此根据起始码【[0] 0 0 1】,前面最少有两个0参与循环了
// 因此此处bitflow的值后八位二进制为:0000 0111,因此与运算后变为0000 0110
if( (bitflow & 0x06) == 0x06 ) /* there was at least 2 leading zeros */
{// 若是理论上的循环次数则表示正确,然后将去掉起始码的数据保存返回
*pi_data = i_data;
*pp_data = p_data;
return true;
}
return false;
}
}
return false;
}
1.3、rtp_listen_thread实现分析:
// [vlc/modules/stream_out/rtp.c]
// 注译:RTP服务器端socket监听线程将传入的链接【DCCP流】出队处理
/* This thread dequeues incoming connections (DCCP streaming) */
static void *rtp_listen_thread( void *data )
{
sout_stream_id_sys_t *id = data;
assert( id->listen.fd != NULL );
// 开启线程循环
for( ;; )
{
// RTP服务器端socket accept接收客户端请求
// 见下面的分析
int fd = net_Accept( id->p_stream, id->listen.fd );
if( fd == -1 )
continue;
int canc = vlc_savecancel( );
// rtp流添加到数据输出端发送数据给客户端套接字fd,【true表示RTCP复用同一socket应答】
// 见1.1.2小节分析
rtp_add_sink( id, fd, true, NULL );
vlc_restorecancel( canc );
}
vlc_assert_unreachable();
}
// 该方法会wait等待新连接进入
// [vlc/modules/stream_out/tcp.c]
/**
* Accepts an new connection on a set of listening sockets.
* If there are no pending connections, this function will wait.
* @note If the thread needs to handle events other than incoming connections,
* you need to use poll() and net_AcceptSingle() instead.
*
* @param p_this VLC object for logging and object kill signal
* @param pi_fd listening socket set
* @return -1 on error (may be transient error due to network issues),
* a new socket descriptor on success.
*/
int net_Accept (vlc_object_t *p_this, int *pi_fd)
{
assert (pi_fd != NULL);
// 此处处理为:从前面的net_Listen方法实现中可知,
// 返回的可能是多个套接字接口【即打开多个socket进行连接】,最后结尾赋值了为-1。
// 因此此处是计算pi_fd即套接字描述符的个数
unsigned n = 0;
while (pi_fd[n] != -1)
n++;
// 初始化套接字描述符集合[pollfd是个网络相关结构体]
struct pollfd ufd[n];
/* Initialize file descriptor set */
for (unsigned i = 0; i < n; i++)
{
ufd[i].fd = pi_fd[i];
ufd[i].events = POLLIN;
}
// 开启循环
for (;;)
{
// poll是Linux中的字符设备驱动中的一个函数。此处超时时间设为了-1
// 返回值:
// >0:数组fds中准备好读、写或出错状态的那些socket描述符的总数量;
// ==0:数组fds中没有任何socket描述符准备好读、写,或出错;
// 此时poll超时,超时时间是timeout毫秒;
// 换句话说,如果所检测的socket描述符上没有任何事件发生的话,
// 那么poll()函数会阻塞timeout所指定的毫秒时间长度之后返回,
// 如果timeout==0,那么poll() 函数立即返回而不阻塞,
// 如果timeout==INFTIM,那么poll() 函数会一直阻塞下去,
// 直到所检测的socket描述符上的感兴趣的事件发生是才返回,
// 如果感兴趣的事件永远不发生,那么poll()就会永远阻塞下去;
// -1: poll函数调用失败,同时会自动设置全局变量errno;
while (poll (ufd, n, -1) == -1)
{// 若失败原因不是【EINTR】则重新poll
if (net_errno != EINTR)
{
msg_Err (p_this, "poll error: %s", vlc_strerror_c(net_errno));
return -1;
}
}
// 若上面检测到有事件发生则处理接收到的事件,
// 循环处理多个打开的socket套接字连接。
for (unsigned i = 0; i < n; i++)
{
// 若该socket套接字没有接收到事件,则下一个处理
if (ufd[i].revents == 0)
continue;
// 接收到事件
int sfd = ufd[i].fd;
int fd = net_AcceptSingle (p_this, sfd);
if (fd == -1)
continue;
// 注译:将正在监听的套接字移到末尾,让其他套接口在下次机会进行处理。
// 即循环处理每个打开的套接字
/*
* Move listening socket to the end to let the others in the
* set a chance next time.
*/
memmove (pi_fd + i, pi_fd + i + 1, n - (i + 1));
pi_fd[n - 1] = sfd;
return fd;
}
}
return -1;
}
// [vlc/modules/stream_out/tcp.c]
int net_AcceptSingle (vlc_object_t *obj, int lfd)
{
// 内部会调用accept网络接口函数
// 【会阻塞,直到有客户端连接上来为止】
int fd = vlc_accept (lfd, NULL, NULL, true);
if (fd == -1)
{// 接收客户端连接失败
if (net_errno != EAGAIN)
#if (EAGAIN != EWOULDBLOCK)
if (net_errno != EWOULDBLOCK)
#endif
msg_Err (obj, "accept failed (from socket %d): %s", lfd,
vlc_strerror_c(net_errno));
return -1;
}
// 从本机套接口接收了客户端套接接口的请求
msg_Dbg (obj, "accepted socket %d (from socket %d)", fd, lfd);
// 设置套接字的选项
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int));
return fd;
}
1.4、net_ConnectDgram实现分析:
// 【vlc/src/network/udp.c】
/*****************************************************************************
* net_ConnectDgram:
*****************************************************************************
* Open a datagram socket to send data to a defined destination, with an
* optional hop limit.
*****************************************************************************/
int net_ConnectDgram( vlc_object_t *p_this, const char *psz_host, int i_port,
int i_hlim, int proto )
{
// 地址信息:类型为数据报类套接口
struct addrinfo hints = {
.ai_socktype = SOCK_DGRAM,
.ai_protocol = proto,
.ai_flags = AI_NUMERICSERV | AI_IDN,
}, *res;
int i_handle = -1;
bool b_unreach = false;
// TTL生存时间值【大致是经过的交换机个数】,未设置时此处获取此前计算出的TTL
if( i_hlim < 0 )
i_hlim = var_InheritInteger( p_this, "ttl" );
// 连接推流地址处理
msg_Dbg( p_this, "net: connecting to [%s]:%d", psz_host, i_port );
// 内部调用getaddrinfo网络接口函数:
// getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,
// 返回的是一个sockaddr结构的链表而不是一个地址清单。
// 这些sockaddr结构随后可由套接口函数直接使用
int val = vlc_getaddrinfo (psz_host, i_port, &hints, &res);
if (val)
{
msg_Err (p_this, "cannot resolve [%s]:%d : %s", psz_host, i_port,
gai_strerror (val));
return -1;
}
// 与前面分析过的地址信息处理类似,打开socket连接
for (struct addrinfo *ptr = res; ptr != NULL; ptr = ptr->ai_next)
{
char *str;
// 打开socket连接
int fd = net_Socket (p_this, ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (fd == -1)
continue;
// 让内核来检测当前地址上是否可以进行广播信息发送
/* Allow broadcast sending */
setsockopt (fd, SOL_SOCKET, SO_BROADCAST, &(int){ 1 }, sizeof (int));
if( i_hlim >= 0 )
// 设置当前多播地址跳数限制【TTL值】,内部通过setsockopt设置
net_SetMcastHopLimit( p_this, fd, ptr->ai_family, i_hlim );
// 得到此前保存的多播地址接口
str = var_InheritString (p_this, "miface");
if (str != NULL)
{// 设置多播输出接口选项【调用setsockopt】
net_SetMcastOut (p_this, fd, ptr->ai_family, str);
free (str);
}
// DSCP差分服务代码点(Differentiated Services Code Point),
// IETF于1998年12月发布了Diff-Serv(Differentiated Service)的QoS分类标准。
// 它在每个数据包IP头部的服务类别TOS标识字节中,利用已使用的6比特和未使用的2比特,
// 通过编码值来区分优先级。
// 保证通信的QoS。 内部通过【setsockopt】设置DSCP值
net_SetDSCP (fd, var_InheritInteger (p_this, "dscp"));
// 【connect】方法为socket连接方法:创建与指定外部端口的连接,
// 将参数fd 的socket 连至参数serv_addr 指定的网络地址
if( connect( fd, ptr->ai_addr, ptr->ai_addrlen ) == 0 )
{// 连接成功则退出
/* success */
i_handle = fd;
break;
}
#if defined( _WIN32 )
if( WSAGetLastError () == WSAENETUNREACH )
#else
if( errno == ENETUNREACH )
#endif
b_unreach = true;
else
msg_Warn( p_this, "%s port %d : %s", psz_host, i_port,
vlc_strerror_c(errno) );
net_Close( fd );
}
freeaddrinfo( res );
if( i_handle == -1 )
{// 若失败则表示不能打开当前设置的主机地址和端口号
if( b_unreach )
msg_Err( p_this, "Host %s port %d is unreachable", psz_host,
i_port );
return -1;
}
return i_handle;
}
1.5、RtspAddId实现分析:
// [vlc/modules/stream_out/rtsp.c]
rtsp_stream_id_t *RtspAddId( rtsp_stream_t *rtsp, sout_stream_id_sys_t *sid,
uint32_t ssrc, unsigned clock_rate,
int mcast_fd)
{
// 通过前面分析可知ssrc值为一个随机生成值,此处已转化为网络字节顺序值便于网上传输
// 检查rtsp数据流的track id,若大于999则失败
if (rtsp->track_id > 999)
{
msg_Err(rtsp->owner, "RTSP: too many IDs!");
return NULL;
}
char *urlbuf;
rtsp_stream_id_t *id = malloc( sizeof( *id ) );
httpd_url_t *url;
if( id == NULL )
return NULL;
id->stream = rtsp;
id->sout_id = sid;
id->track_id = rtsp->track_id;
id->ssrc = ssrc;
id->clock_rate = clock_rate;
// 打开链接的套接字描述符
id->mcast_fd = mcast_fd;
// 解析rtsp推流地址
// 见下面的分析
urlbuf = RtspAppendTrackPath( id, rtsp->psz_path );
if( urlbuf == NULL )
{
free( id );
return NULL;
}
msg_Dbg( rtsp->owner, "RTSP: adding %s", urlbuf );
// 获取设置的RTSP链接的用户名和密码
char *user = var_InheritString(rtsp->owner, "sout-rtsp-user");
char *pwd = var_InheritString(rtsp->owner, "sout-rtsp-pwd");
// 注册一个新的URL
// 见下面的分析
url = id->url = httpd_UrlNew( rtsp->host, urlbuf, user, pwd );
free( user );
free( pwd );
free( urlbuf );
if( url == NULL )
{
free( id );
return NULL;
}
// 注册RTSP服务请求URL上能接受处理的消息类型的回调方法
// 目前VLC中实现RTSP服务端接收请求方法:DESCRIBE SETUP PLAY PAUSE GETPARAMETER TEARDOWN
// [httpd_UrlCatch]见下面的分析
// [RtspCallbackId]后续章节发送数据处理时分析 TODO
httpd_UrlCatch( url, HTTPD_MSG_DESCRIBE, RtspCallbackId, (void *)id );
httpd_UrlCatch( url, HTTPD_MSG_SETUP, RtspCallbackId, (void *)id );
httpd_UrlCatch( url, HTTPD_MSG_PLAY, RtspCallbackId, (void *)id );
httpd_UrlCatch( url, HTTPD_MSG_PAUSE, RtspCallbackId, (void *)id );
httpd_UrlCatch( url, HTTPD_MSG_GETPARAMETER, RtspCallbackId, (void *)id );
httpd_UrlCatch( url, HTTPD_MSG_TEARDOWN, RtspCallbackId, (void *)id );
// RTSP数据流的track id加1【即为下一个新ID】
rtsp->track_id++;
return id;
}
// [vlc/modules/stream_out/rtsp.c]
char *RtspAppendTrackPath( rtsp_stream_id_t *id, const char *base )
{
// 若推流路径不为空则在路径最后检查是否需要添加'/'反斜杠
const char *sep = strlen( base ) > 0 && base[strlen( base ) - 1] == '/' ?
"" : "/";
char *url;
// 然后将当前地址和数据拼接起来,作为RTSP推流URL放回
if( asprintf( &url, "%s%strackID=%u", base, sep, id->track_id ) == -1 )
url = NULL;
return url;
}
// [vlc/src/network/httpd.c]
/* register a new url */
httpd_url_t *httpd_UrlNew(httpd_host_t *host, const char *psz_url,
const char *psz_user, const char *psz_password)
{
httpd_url_t *url;
assert(psz_url);
vlc_mutex_lock(&host->lock);
for (int i = 0; i < host->i_url; i++)
// 判断是否当前RTSP推流URL已注册到host对象中了,若已注册则失败处理
if (!strcmp(psz_url, host->url[i]->psz_url)) {
msg_Warn(host, "cannot add '%s' (url already defined)", psz_url);
vlc_mutex_unlock(&host->lock);
return NULL;
}
// xmalloc效率低代价高,而标准库高效的的malloc和free
url = xmalloc(sizeof(httpd_url_t));
url->host = host;
vlc_mutex_init(&url->lock);
// 复制对应参数值
url->psz_url = xstrdup(psz_url);
url->psz_user = xstrdup(psz_user ? psz_user : "");
url->psz_password = xstrdup(psz_password ? psz_password : "");
// RTSP请求服务类型的消息回调
for (int i = 0; i < HTTPD_MSG_MAX; i++) {
url->catch[i].cb = NULL;
url->catch[i].p_sys = NULL;
}
// 添加当前新的RTSP track URL到URL缓存列表中
TAB_APPEND(host->i_url, host->url, url);
// 通知host的wait事件端发送请求数据等
// 见后续章节该部分分析 TODO
vlc_cond_signal(&host->wait);
vlc_mutex_unlock(&host->lock);
return url;
}
/* register callback on a url */
int httpd_UrlCatch(httpd_url_t *url, int i_msg, httpd_callback_t cb,
httpd_callback_sys_t *p_sys)
{// 赋值即可。 注意此处的[httpd_callback_sys_t]为未实现的结构体名,
// 只是为了缓存当前sys对象指针地址
vlc_mutex_lock(&url->lock);
url->catch[i_msg].cb = cb;
url->catch[i_msg].p_sys= p_sys;
vlc_mutex_unlock(&url->lock);
return VLC_SUCCESS;
}
TODO:此章节系列流媒体处理未分析完整,待后续更新,敬请关注,Thanks♪(・ω・)ノ
【由于本人目前工作内容转为了android framework多媒体框架层的维护、优化等日常工作,因此后续会先更新android framework多媒体框架层实现分析,而vlc-sout流媒体处理部分会延缓更新】