接着第十五章节【Part 1】小节分析:本章分析【不需要加载流复用器模块功能】时的媒体流处理
承接上一章节:
【十五】【vlc-android】vlc-sout流媒体输出端源码实现分析【Part 1】 1、Add实现分析:
// 【vlc/modules/stream_output/rtp.c】
// 注译:添加一个ES流数据作为一个新的RTP流数据
/** Add an ES as a new RTP stream */
static sout_stream_id_sys_t *Add( sout_stream_t *p_stream,
const es_format_t *p_fmt )
{
// 注译:当p_fmt为null时表示使用非RTP复用格式【流封装格式】
/* NOTE: As a special case, if we use a non-RTP
* mux (TS/PS), then p_fmt is NULL. */
sout_stream_sys_t *p_sys = p_stream->p_sys;
char *psz_sdp;
// 创建输出流ID
sout_stream_id_sys_t *id = malloc( sizeof( *id ) );
if( unlikely(id == NULL) )
return NULL;
id->p_stream = p_stream;
// 分组打包器特有字段
// 最大传输单元(Maximum Transmission Unit,MTU)用来通知对方所能接受数据服务单元的最大尺寸,
// 说明发送方能够接受的有效载荷大小。是包或帧的最大长度,一般以字节记
id->i_mtu = var_InheritInteger( p_stream, "mtu" );
// 若设置太小【<=28】则默认设置MTU大小为548字节数
if( id->i_mtu <= 12 + 16 )
id->i_mtu = 576 - 20 - 8; /* pessimistic */
// 最大的RTP包大小【字节单位】
msg_Dbg( p_stream, "maximum RTP packet size: %d bytes", id->i_mtu );
#ifdef HAVE_SRTP
// 若设置了SRTP (Secure RTP) 安全RTP
id->srtp = NULL;
#endif
vlc_mutex_init( &id->lock_sink );
id->sinkc = 0;
id->sinkv = NULL;
id->rtsp_id = NULL;
id->p_fifo = NULL;
id->listen.fd = NULL;
// 初始化标记为第一个RTP包情况
id->b_first_packet = true;
// 根据设置配置缓冲时间【此处时间转化为了US微秒单位】
id->i_caching =
(int64_t)1000 * var_GetInteger( p_stream, SOUT_CFG_PREFIX "caching");
// 随机初始化数据包的序列号 RTP包字段
vlc_rand_bytes (&id->i_sequence, sizeof (id->i_sequence));
// 随机初始化值
vlc_rand_bytes (id->ssrc, sizeof (id->ssrc));
bool format = false;
if (p_sys->p_vod_media != NULL)
{
// RTP包数据格式信息
id->rtp_fmt.ptname = NULL;
// 一个主机字节顺序的无符号长整形数【从网络字节顺序转换为主机字节顺序】[netlong hostlong]
uint32_t ssrc;
// 匹配一个RTP id到一个VoD媒体ES和RTSP追踪并用已经设置好的数据初始化它
// 【i_seq_sent_next为RTSP包序列号字段值】
// 见1.1小节分析 【内部处理含有RTCP socket的链接处理】
int val = vod_init_id(p_sys->p_vod_media, p_sys->psz_vod_session,
p_fmt ? p_fmt->i_id : 0, id, &id->rtp_fmt,
&ssrc, &id->i_seq_sent_next);
if (val == VLC_SUCCESS)
{
memcpy(id->ssrc, &ssrc, sizeof(id->ssrc));
/* This is ugly, but id->i_seq_sent_next needs to be
* initialized inside vod_init_id() to avoid race
* conditions. */
// 将RTSP包序列号赋值给RTP包序列号
id->i_sequence = id->i_seq_sent_next;
}
/* vod_init_id() may fail either because the ES wasn't found in
* the VoD media, or because the RTSP session is gone. In the
* former case, id->rtp_fmt was left untouched. */
format = (id->rtp_fmt.ptname != NULL);
}
if (!format)
{// id->rtp_fmt.ptname为空时
id->rtp_fmt.fmtp = NULL; /* don't free() garbage on error */
char *psz = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "mux" );
if (p_fmt == NULL && psz == NULL)
goto error;
// 获取RTP SDP格式信息
// 见1.2小节分析
int val = rtp_get_fmt(VLC_OBJECT(p_stream), p_fmt, psz, &id->rtp_fmt);
free( psz );
if (val != VLC_SUCCESS)
goto error;
}
#ifdef HAVE_SRTP
// 若开启SRTP即安全RTP传输,该实现也不展开分析,后续有时间再继续 TODO
// 在使用实时传输协议或实时传输控制协议时,
// 使不使用安全实时传输协议或安全实时传输控制协议是可选的,
// 但即使使用了安全实时传输协议或安全实时传输控制协议,所有它们提供的特性
//(如加密和认证)也都是可选的,这些特性可以被独立地使用或禁用
// 唯一的例外是在使用安全实时传输控制协议时,必须要用到其消息认证特性
char *key = var_GetNonEmptyString (p_stream, SOUT_CFG_PREFIX"key");
if (key)
{
vlc_gcrypt_init ();
id->srtp = srtp_create (SRTP_ENCR_AES_CM, SRTP_AUTH_HMAC_SHA1, 10,
SRTP_PRF_AES_CM, SRTP_RCC_MODE1);
if (id->srtp == NULL)
{
free (key);
goto error;
}
char *salt = var_GetNonEmptyString (p_stream, SOUT_CFG_PREFIX"salt");
int val = srtp_setkeystring (id->srtp, key, salt ? salt : "");
free (salt);
free (key);
if (val)
{
msg_Err (p_stream, "bad SRTP key/salt combination (%s)",
vlc_strerror_c(val));
goto error;
}
id->i_sequence = 0; /* FIXME: awful hack for libvlc_srtp */
}
#endif
id->i_seq_sent_next = id->i_sequence;
int mcast_fd = -1;
if( p_sys->psz_destination != NULL )
{// 此处地址指的是推流地址,不为空时
// 选择端口号
/* Choose the port */
uint16_t i_port = 0;
if( p_fmt == NULL )
;
else
if( p_fmt->i_cat == AUDIO_ES && p_sys->i_port_audio > 0 )
// 当前es数据流为audio,且设置了端口号
i_port = p_sys->i_port_audio;
else
if( p_fmt->i_cat == VIDEO_ES && p_sys->i_port_video > 0 )
// 当前es数据流为video,且设置了端口号
i_port = p_sys->i_port_video;
/* We do not need the ES lock (p_sys->lock_es) here, because
* this is the only one thread that can *modify* the ES table.
* The ES lock protects the other threads from our modifications
* (TAB_APPEND, TAB_REMOVE). */
for (int i = 0; i_port && (i < p_sys->i_es); i++)
// 检查当前设置端口是否和es中使用的端口号重复
if (i_port == p_sys->es[i]->i_port)
i_port = 0; /* Port already in use! */
// 若上面选择的i_port端口值为0则进行for循环,
// 在设置的【p_sys->i_port】值上每次加2来选择一个可用端口号
for (uint16_t p = p_sys->i_port; i_port == 0; p += 2)
{
if (p == 0)
{// 注意:若设置的【p_sys->i_port】为0则失败
// 太多RTP基本流
msg_Err (p_stream, "too many RTP elementary streams");
goto error;
}
i_port = p;
// 再次判断是否和SDP使用的端口号重复
for (int i = 0; i_port && (i < p_sys->i_es); i++)
if (p == p_sys->es[i]->i_port)
i_port = 0;
}
id->i_port = i_port;
// 流类套接口
// 【对于流类套接口(SOCK_STREAM类型),利用名字来与一个远程主机建立连接,
// 一旦套接口调用成功返回,它就能收发数据了。对于数据报类套接口(SOCK_DGRAM类型),
// 则设置成一个缺省的目的地址,并用它来进行后续的send()与recv()调用】
int type = SOCK_STREAM;
switch( p_sys->proto )
{
#ifdef SOCK_DCCP
case IPPROTO_DCCP:
{
const char *code;
switch (id->rtp_fmt.cat)
{
case VIDEO_ES: code = "RTPV"; break;
case AUDIO_ES: code = "RTPARTPV"; break;
case SPU_ES: code = "RTPTRTPV"; break;
default: code = "RTPORTPV"; break;
}
var_SetString (p_stream, "dccp-service", code);
type = SOCK_DCCP;
}
#endif
// 失败?? TODO
/* fall through */
case IPPROTO_TCP:
// 内部通过调用socket、bind、listen三个网络编程接口来创建对应host和端口号的被动socket
// 注意若内部bind失败则会尝试调用另外的网络接口:send和recvmsg函数。
// 注:recvmsg和sendmsg函数:这两个函数是最通用的I/O函数。
// 实际上我们可以把所有read、readv、recv和recvfrom调用替换成recvmsg调用。
// 类似地,各种输出函数调用也可以替换成sendmsg调用。
// 在大多数网络程序中,服务端会作为被动socket被动接受连接,
// 而客户端会作为主动socket主动发起连接。
// 被动socket是一个通过调用listen函数监听要发起连接的socket,
// 当被动socket接受一个连接通常称为被动打开。
// 服务端通过socket函数创建的socket是主动socket,
// 而listen函数就是把这个还未接受连接的主动socket转换为被动socket,
// 因为服务端只需要被动接受客户端的连接请求。
id->listen.fd = net_Listen( VLC_OBJECT(p_stream),
p_sys->psz_destination, i_port,
type, p_sys->proto );
if( id->listen.fd == NULL )
{// 创建被动socket失败
msg_Err( p_stream, "passive COMEDIA RTP socket failed" );
goto error;
}
// 创建RTP服务器端socket链接成功,则开启被动监听请求线程【优先级底】
// 该线程实现分析见1.3小节
if( vlc_clone( &id->listen.thread, rtp_listen_thread, id,
VLC_THREAD_PRIORITY_LOW ) )
{
net_ListenClose( id->listen.fd );
id->listen.fd = NULL;
goto error;
}
break;
default:
{// 否则默认使用数据报类套接口进行socket连接
// 打开数据报类套接口,将数据发送到已定义的目的【推流】地址(带有可选的跳数限制即TTL生存时间值【大致是经过的交换机个数】)。
// 见1.4小节分析
int fd = net_ConnectDgram( p_stream, p_sys->psz_destination,
i_port, -1, p_sys->proto );
if( fd == -1 )
{
msg_Err( p_stream, "cannot create RTP socket" );
goto error;
}
// 注意:此处英文意思大致是vlc推流会忽略不期望的传入连接如RTCP-RR数据包,
// 原因是rtcp-mux设置即rtcp_mux若为true表示RTCP复用同一socket应答
/* Ignore any unexpected incoming packet (including RTCP-RR
* packets in case of rtcp-mux) */
setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &(int){ 0 },
sizeof (int));
// rtp流添加到数据输出端发送数据给客户端套接字fd,【rtcp_mux若为true表示RTCP复用同一socket应答】
// 见1.1.2小节分析
rtp_add_sink( id, fd, p_sys->rtcp_mux, NULL );
/* FIXME: test if this is multicast */
// 是否为多播接口套接字描述符
mcast_fd = fd;
}
}
}
// p_fmt参数不为空时
// [rtp_set_ptime]该方法将MTU缩小到一个固定的分组时间(用于音频)。
if( p_fmt != NULL )
switch( p_fmt->i_codec )
{
case VLC_CODEC_MULAW:
case VLC_CODEC_ALAW:
case VLC_CODEC_U8:
// 参数20表示20毫秒,最后参数1表示位深
rtp_set_ptime (id, 20, 1);
break;
case VLC_CODEC_S16B:
case VLC_CODEC_S16L:
rtp_set_ptime (id, 20, 2);
break;
case VLC_CODEC_S24B:
rtp_set_ptime (id, 20, 3);
break;
default:
break;
}
#if 0 /* No payload formats sets this at the moment */
int cscov = -1;
if( cscov != -1 )
cscov += 8 /* UDP */ + 12 /* RTP */;
if( id->sinkc > 0 )
net_SetCSCov( id->sinkv[0].rtp_fd, cscov, -1 );
#endif
vlc_mutex_lock( &p_sys->lock_ts );
// NPT时间是否已初始化
id->b_ts_init = ( p_sys->i_npt_zero != VLC_TS_INVALID );
vlc_mutex_unlock( &p_sys->lock_ts );
if( id->b_ts_init )
// 已初始化则计算TS时间偏移量
// 见下面的分析
id->i_ts_offset = rtp_compute_ts( id->rtp_fmt.clock_rate,
p_sys->i_pts_offset );
if( p_sys->rtsp != NULL )
// RTSP流对象不为空时,GetDWBE作用为将主机字节顺序【ssrc】转为32位表示的网络字节顺序
// 见1.5小节分析
id->rtsp_id = RtspAddId( p_sys->rtsp, id, GetDWBE( id->ssrc ),
id->rtp_fmt.clock_rate, mcast_fd );
id->p_fifo = block_FifoNew();
if( unlikely(id->p_fifo == NULL) )
goto error;
if( vlc_clone( &id->thread, ThreadSend, id, VLC_THREAD_PRIORITY_HIGHEST ) )
{
block_FifoRelease( id->p_fifo );
id->p_fifo = NULL;
goto error;
}
/* Update p_sys context */
vlc_mutex_lock( &p_sys->lock_es );
TAB_APPEND( p_sys->i_es, p_sys->es, id );
vlc_mutex_unlock( &p_sys->lock_es );
psz_sdp = SDPGenerate( p_stream, NULL );
vlc_mutex_lock( &p_sys->lock_sdp );
free( p_sys->psz_sdp );
p_sys->psz_sdp = psz_sdp;
vlc_mutex_unlock( &p_sys->lock_sdp );
msg_Dbg( p_stream, "sdp=\n%s", p_sys->psz_sdp );
/* Update SDP (sap/file) */
if( p_sys->b_export_sap ) SapSetup( p_stream );
if( p_sys->psz_sdp_file != NULL ) FileSetup( p_stream );
return id;
error:
Del( p_stream, id );
return NULL;
}
// 【vlc/modules/stream_output/rtp.c】
uint32_t rtp_compute_ts( unsigned i_clock_rate, int64_t i_pts )
{
/* This is an overflow-proof way of doing:
* return i_pts * (int64_t)i_clock_rate / CLOCK_FREQ;
*
* NOTE: this plays nice with offsets because the (equivalent)
* calculations are linear. */
// NOTE: 使用PTS偏移量比较好的播放处理,因为(等效)计算是线性的。
// lldiv: 同时计算商(表达式x / y的结果)和余数(表达式x%y的结果)。
lldiv_t q = lldiv(i_pts, CLOCK_FREQ);
return q.quot * (int64_t)i_clock_rate
+ q.rem * (int64_t)i_clock_rate / CLOCK_FREQ;
}
1.1、vod_init_id实现分析:
// 【vlc/modules/stream_output/vod.c】
/* Match an RTP id to a VoD media ES and RTSP track to initialize it
* with the data that was already set up */
int vod_init_id(vod_media_t *p_media, const char *psz_session, int es_id,
sout_stream_id_sys_t *sout_id, rtp_format_t *rtp_fmt,
uint32_t *ssrc, uint16_t *seq_init)
{
// es媒体数据
media_es_t *p_es;
if (p_media->psz_mux != NULL)
{// 有ES复用封装格式该值时,只有一个es媒体数据
assert(p_media->i_es == 1);
p_es = p_media->es[0];
}
else
{
// 不加锁,匹配当前es_id获取当前对应的es out数据即音频、视频或字幕流
p_es = NULL;
/* No locking needed, the ES table can't be modified now */
for (int i = 0; i < p_media->i_es; i++)
{
if (p_media->es[i]->es_id == es_id)
{
p_es = p_media->es[i];
break;
}
}
if (p_es == NULL)
return VLC_EGENERIC;
}
// 复制给参数字段
memcpy(rtp_fmt, &p_es->rtp_fmt, sizeof(*rtp_fmt));
if (p_es->rtp_fmt.fmtp != NULL)
rtp_fmt->fmtp = strdup(p_es->rtp_fmt.fmtp);
// RTSP追踪关联RTP流数据
return RtspTrackAttach(p_media->rtsp, psz_session, p_es->rtsp_id,
sout_id, ssrc, seq_init);
}
// 【vlc/modules/stream_output/rtsp.c】
// 注译:将一个启动VoD RTP id附加到它的RTSP轨道上,并让它使用【SETUP】请求的参数进行初始化
/* Attach a starting VoD RTP id to its RTSP track, and let it
* initialize with the parameters of the SETUP request */
int RtspTrackAttach( rtsp_stream_t *rtsp, const char *name,
rtsp_stream_id_t *id, sout_stream_id_sys_t *sout_id,
uint32_t *ssrc, uint16_t *seq_init )
{
int val = VLC_EGENERIC;
// RTSP unicast单播流session会话信息
rtsp_session_t *session;
vlc_mutex_lock(&rtsp->lock);
// 获取rtsp客户端session会话信息
// 见1.1.1小节分析
session = RtspClientGet(rtsp, name);
if (session == NULL)
goto out;
rtsp_strack_t *tr = NULL;
// trackc表示输出访问ID
for (int i = 0; i < session->trackc; i++)
{
// RTSP单播会话轨道track信息中id和rtsp流ID对象中的id相同时赋值
if (session->trackv[i].id == id)
{
tr = session->trackv + i;
break;
}
}
if (tr != NULL)
{
tr->sout_id = sout_id;
// 【rtp_fd】为播放时被RTP输出端使用的socket,
// 【setup_fd】为【SETUP】请求时创建的socket
// 此处处理为:复制socket,获取套接字描述符
// 注:内部调用fcntl【系统调用】(根据文件描述符来操作文件的特性)
// 针对(文件)描述符提供控制,fcntl函数来加锁文件
tr->rtp_fd = dup_socket(tr->setup_fd);
}
else
{// RTSP会话轨道没有【SETUP】请求,则新建一个
/* The track was not SETUP. We still create one because we'll
* need the sout_id if we set it up later. */
rtsp_strack_t track = { .id = id, .sout_id = sout_id,
.setup_fd = -1, .rtp_fd = -1 };
vlc_rand_bytes (&track.seq_init, sizeof (track.seq_init));
vlc_rand_bytes (&track.ssrc, sizeof (track.ssrc));
// 将当前RTSP会话轨道track对象加入【RTSP unicast单播流session会话信息】的列表字段中
TAB_APPEND(session->trackc, session->trackv, track);
// 将该数据列表中新入的最后一个track赋值
tr = session->trackv + session->trackc - 1;
}
// ntohl函数:将一个无符号长整形数从网络字节顺序转换为主机字节顺序。[netlong hostlong]
*ssrc = ntohl(tr->ssrc);
// RTSP包序列号【随机生成】
*seq_init = tr->seq_init;
if (tr->rtp_fd != -1)
{
// rtp流数据包序列号
uint16_t seq;
// rtp流添加到数据输出端
// 见1.1.2小节分析
rtp_add_sink(tr->sout_id, tr->rtp_fd, false, &seq);
/* To avoid race conditions, sout_id->i_seq_sent_next must
* be set here and now. Make sure the caller did its job
* properly when passing seq_init. */
assert(tr->seq_init == seq);
}
val = VLC_SUCCESS;
out:
vlc_mutex_unlock(&rtsp->lock);
return val;
}
1.1.1、RtspClientGet实现分析:
// 【vlc/modules/stream_output/rtsp.c】
/** rtsp must be locked */
static
rtsp_session_t *RtspClientGet( rtsp_stream_t *rtsp, const char *name )
{
char *end;
uint64_t id;
int i;
// 有上面分析可知,name表示session字符串值
if( name == NULL )
return NULL;
errno = 0;
// 转换成 unsigted long long类型的数值
id = strtoull( name, &end, 0x10 );
if( errno || *end )
return NULL;
// 找到当前rtsp中同一个session id的对应对象返回
/* FIXME: use a hash/dictionary */
for( i = 0; i < rtsp->sessionc; i++ )
{
if( rtsp->sessionv[i]->id == id )
return rtsp->sessionv[i];
}
return NULL;
}
1.1.2、rtp_add_sink实现分析:
// 【vlc/modules/stream_output/rtp.c】
int rtp_add_sink( sout_stream_id_sys_t *id, int fd, bool rtcp_mux, uint16_t *seq )
{
// rtp流输出【发送】信息
rtp_sink_t sink = { fd, NULL };
// 创建RTCP控制发送端对象并打开,使用UDP传输层协议
// 见下面的分析
sink.rtcp = OpenRTCP( VLC_OBJECT( id->p_stream ), fd, IPPROTO_UDP,
rtcp_mux );
if( sink.rtcp == NULL )
msg_Err( id->p_stream, "RTCP failed!" );
vlc_mutex_lock( &id->lock_sink );
// 保存rtp流数据包输出信息对象到对应列表中
TAB_APPEND(id->sinkc, id->sinkv, sink);
if( seq != NULL )
// 将RTSP数据包的序列号赋值给变量返回
*seq = id->i_seq_sent_next;
vlc_mutex_unlock( &id->lock_sink );
return VLC_SUCCESS;
}
// 【vlc/modules/stream_output/rtcp.c】
rtcp_sender_t *OpenRTCP (vlc_object_t *obj, int rtp_fd, int proto,
bool mux)
{
rtcp_sender_t *rtcp;
uint8_t *ptr;
// 表示套接字描述符
int fd;
// 从后续分析可知是保存的IP地址字符串: 如"127.0.0.1:6563'
// 网络接口最大数字主机【max numeric host】
char src[NI_MAXNUMERICHOST];
// RTCP发送端sock端口号
int sport;
// 获取本机协议地址网络IP地址和端口
// 见下面的分析
if (net_GetSockAddress (rtp_fd, src, &sport))
return NULL;
// RTP/RTCP mux复用功能作用:根据其功能处理可知,
// 该参数是用于是否RTP和RTCP使用同一个IP端口,true则使用同一个端口号
if (mux)
{// 若需要RTCP进行复用封装处理,则复制socket,
// 即在同一个端口号上进行传输数据【如同一个客户端网络请求进行应答时】
/* RTP/RTCP mux: duplicate the socket */
#ifndef _WIN32
// 此处处理为:复制socket,获取套接字描述符
// 注:内部调用fcntl【系统调用】(根据文件描述符来操作文件的特性)
// 针对(文件)描述符提供控制,fcntl函数来加锁文件
fd = vlc_dup (rtp_fd);
#else
WSAPROTOCOL_INFO info;
WSADuplicateSocket (rtp_fd, GetCurrentProcessId (), &info);
fd = WSASocket (info.iAddressFamily, info.iSocketType, info.iProtocol,
&info, 0, 0);
#endif
}
else
{// RTCP运行在不同的端口号上
/* RTCP on a separate port */
// 客户端网络IP地址
char dst[NI_MAXNUMERICHOST];
// 客户端IP端口号
int dport;
// 获取客户端协议地址网络IP地址和端口
if (net_GetPeerAddress (rtp_fd, dst, &dport))
return NULL;
// 将本地端口和客户端端口均增加1
sport++;
dport++;
// 使用当前本地和客户端的IP地址、端口号和传输层协议【如UDP】,来打开UDP数据报网络链接
// 并返回套接字描述符
// 见1.1.2.1小节分析
fd = net_OpenDgram (obj, src, sport, dst, dport, proto);
if (fd != -1)
{
// TTL表示Time to live即网络生存时间值
// 【该字段指定IP包被路由器丢弃之前允许通过的最大网段数量。TTL是IPv4报头的一个8 bit字段。
// 注意:TTL与DNS TTL有区别。二者都是生存时间,
// 前者指ICMP包的转发次数(跳数),后者指域名解析信息在DNS中的存在时间】
/* Copy the multicast IPv4 TTL value (useless for IPv6) */
int ttl;
socklen_t len = sizeof (ttl);
// 【rtp_fd】为播放时被RTP输出端使用的socket描述符
// getsockopt()函数用于获取任意类型、任意状态套接口的选项当前值,并把结果存入optval。
if (!getsockopt (rtp_fd, SOL_IP, IP_MULTICAST_TTL, &ttl, &len))
// 获取成功,则根据播放时RTP输出端socket的TTL值设置给当前不同端口的RTCP套接字相同使用即可
setsockopt (fd, SOL_IP, IP_MULTICAST_TTL, &ttl, len);
// 此处设置RTCP端口套接字网络上忽略所有传入的RTCP-RR数据包
// RR--Receiver Reports 接收者报告
/* Ignore all incoming RTCP-RR packets */
setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &(int){ 0 }, sizeof (int));
}
}
if (fd == -1)
return NULL;
rtcp = malloc (sizeof (*rtcp));
if (rtcp == NULL)
{// 关闭套接字连接
net_Close (fd);
return NULL;
}
// RTCP socket处理者即保存的是套接字描述符,此处为IP地址转换后的长整形无符号数值
rtcp->handle = fd;
// 初始化已发送RTP数据包个数、已发送RTP数据字节大小,和从最后一个RTCP包发送的RTP包计数值【counter】
rtcp->bytes = rtcp->packets = rtcp->counter = 0;
// 获取%字符之后的字符串【包括%号】
ptr = (uint8_t *)strchr (src, '%');
if (ptr != NULL)
*ptr = '\0'; /* remove scope ID frop IPv6 addresses */
// RTCP负载数据信息
ptr = rtcp->payload;
// 【SR】数据报文信息
/* Sender report */
ptr[0] = 2 << 6; /* V = 2, P = RC = 0 */
ptr[1] = 200; /* payload type: Sender Report */
// 将值的主机字节顺序转换为网络字节顺序
SetWBE (ptr + 2, 6); /* length = 6 (7 double words) */
// SSRC值未知,因此该4个字符内存初始化为0
memset (ptr + 4, 0, 4); /* SSRC unknown yet */
// 【RTSP中NPT时间(Normal Play Time) 正常播放时间】
// 将该时间值转换为网络字节顺序值
SetQWBE (ptr + 8, NTPtime64 ());
memset (ptr + 16, 0, 12); /* timestamp and counters */
// 指针加整数,此处为指针指向向后移动28个字节【ptr指针元素类型大小为8位】
ptr += 28;
// 【SDES】数据报文信息
/* Source description */
uint8_t *sdes = ptr;
ptr[0] = (2 << 6) | 1; /* V = 2, P = 0, SC = 1 */
ptr[1] = 202; /* payload type: Source Description */
uint8_t *lenptr = ptr + 2;
memset (ptr + 4, 0, 4); /* SSRC unknown yet */
ptr += 8;
ptr[0] = 1; /* CNAME - mandatory */
assert (NI_MAXNUMERICHOST <= 256);
// 本机IP地址字符长度
ptr[1] = strlen (src);
// 然后保存IP地址字符到ptr中
memcpy (ptr + 2, src, ptr[1]);
// 跳过地址长度字节数后之后再跳过2字节
ptr += ptr[1] + 2;
// 定义的数据包全名和版本,如:
// #define PACKAGE_STRING "vlc 3.0.11.1"
static const char tool[] = PACKAGE_STRING;
ptr[0] = 6; /* TOOL */
ptr[1] = (sizeof (tool) > 256) ? 255 : (sizeof (tool) - 1);
memcpy (ptr + 2, tool, ptr[1]);
ptr += ptr[1] + 2;
// 将数据包数据位数对齐为32位,因此若前两位二进制值
while ((ptr - sdes) & 3) /* 32-bits padding */
*ptr++ = 0;
// 注意:此处有个指针向右位移2运算,其实就是除以4之后的商值
SetWBE (lenptr, (ptr - sdes - 1) >> 2);
// 保存RTCP数据包总长度
rtcp->length = ptr - rtcp->payload;
return rtcp;
}
// [vlc/include/vlc-network.h]
static inline int net_GetSockAddress( int fd, char *address, int *port )
{// 从SOCKADDR_STORAGE结构中获取客户端IP地址和端口
struct sockaddr_storage addr;
socklen_t addrlen = sizeof( addr );
// fd参数表示:套接字描述符
// 通过套接字描述符来获取自己的IP地址和连接对端的IP地址,
// 如在未调用bind函数的TCP客户端程序上,
// 可以通过调用getsockname()函数获取由内核赋予该连接的本地IP地址和本地端口号,
// 还可以在TCP的服务器端accept成功后,通过getpeername()函数来获取当前连接的客户端的IP地址和端口号。
// [getpeername]获取与套接口相连的端地址:从端口s中获取与它捆绑的端口名,
// 并把它存放在sockaddr类型的name结构中。它适用于数据报或流类套接口。
// 返回与某个套接字关联的本地协议地址(getsockname),
// 返回与某个套接字关联的外地协议地址即得到对方的地址(getpeername)
// getnameinfo函数:以一个套接口地址为参数,返回一个描述主机的字符串和一个描述服务的字符串
return getsockname( fd, (struct sockaddr *)&addr, &addrlen )
|| vlc_getnameinfo( (struct sockaddr *)&addr, addrlen, address,
NI_MAXNUMERICHOST, port, NI_NUMERICHOST )
? VLC_EGENERIC : 0;
}
// [vlc/include/vlc-network.h]
static inline int net_GetPeerAddress( int fd, char *address, int *port )
{// 从SOCKADDR_STORAGE结构中获取客户端IP地址和端口
struct sockaddr_storage addr;
socklen_t addrlen = sizeof( addr );
// 返回与某个套接字关联的外地协议地址即得到对方的地址(getpeername)
return getpeername( fd, (struct sockaddr *)&addr, &addrlen )
|| vlc_getnameinfo( (struct sockaddr *)&addr, addrlen, address,
NI_MAXNUMERICHOST, port, NI_NUMERICHOST )
? VLC_EGENERIC : 0;
}
1.1.2.1、net_OpenDgram实现分析:
// [vlc/src/network/udp.c]
int net_OpenDgram( vlc_object_t *obj, const char *psz_bind, int i_bind,
const char *psz_server, int i_server, int protocol )
{
if ((psz_server == NULL) || (psz_server[0] == '\0'))
// 若客户端IP地址不存在则只打开服务器进行单独监听【客户端请求】
return net_ListenSingle (obj, psz_bind, i_bind, protocol);
// 本地服务器连接客户端处理
msg_Dbg (obj, "net: connecting to [%s]:%d from [%s]:%d",
psz_server, i_server, psz_bind, i_bind);
// 地址信息【local/remote】
struct addrinfo hints = {
.ai_socktype = SOCK_DGRAM,
.ai_protocol = protocol,
.ai_flags = AI_NUMERICSERV | AI_IDN,
}, *loc, *rem;
// 解析客户端网络地址获取IP地址信息等
int val = vlc_getaddrinfo (psz_server, i_server, &hints, &rem);
if (val)
{
msg_Err (obj, "cannot resolve %s port %d : %s", psz_server, i_server,
gai_strerror (val));
return -1;
}
hints.ai_flags |= AI_PASSIVE;
// 解析本地网络地址获取IP地址相关信息
val = vlc_getaddrinfo (psz_bind, i_bind, &hints, &loc);
if (val)
{
msg_Err (obj, "cannot resolve %s port %d : %s", psz_bind, i_bind,
gai_strerror (val));
freeaddrinfo (rem);
return -1;
}
val = -1;
// 先循环处理本地可用网络地址信息并尝试打开套接字连接
for (struct addrinfo *ptr = loc; ptr != NULL; ptr = ptr->ai_next)
{
// 前面已分析过:打开socket连接
int fd = net_Socket (obj, ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (fd == -1)
continue; // usually, address family not supported
// 前面已分析过:设置socket连接选项并调用bind方法进行socket和地址信息绑定
fd = net_SetupDgramSocket( obj, fd, ptr );
if( fd == -1 )
continue;
// 然后进行客户端可用地址的处理
for (struct addrinfo *ptr2 = rem; ptr2 != NULL; ptr2 = ptr2->ai_next)
{
// 重点:必须客户端的协议族、socket类型、协议类型均相同才能进行互联
if ((ptr2->ai_family != ptr->ai_family)
|| (ptr2->ai_socktype != ptr->ai_socktype)
|| (ptr2->ai_protocol != ptr->ai_protocol))
continue;
// 若当前打开的本地socket地址为多播地址,则尝试将当前本地地址加入到多播组,
// 若失败【客户端地址不能加入本地服务端多播组网络】则重新下一个客户端地址链接;
// 若不为多播地址,则执行connect链接处理
// 【connect】方法为socket连接方法:创建与指定外部端口的连接,
// 将参数sockfd 的socket 连至参数serv_addr 指定的网络地址
if (net_SockAddrIsMulticast (ptr->ai_addr, ptr->ai_addrlen)
? net_SourceSubscribe (obj, fd,
ptr2->ai_addr, ptr2->ai_addrlen,
ptr->ai_addr, ptr->ai_addrlen)
: connect (fd, ptr2->ai_addr, ptr2->ai_addrlen))
{
msg_Err (obj, "cannot connect to %s port %d: %s",
psz_server, i_server, vlc_strerror_c(net_errno));
continue;
}
val = fd;
break;
}
if (val != -1)
// 服务器端和客户端连接成功则退出
break;
net_Close (fd);
}
freeaddrinfo (rem);
freeaddrinfo (loc);
return val;
}
1.1.2.1、net_ListenSingle实现分析:【本地IP地址和端口号】
由于章节篇幅长度问题,见下一小章节分析
【十五】【vlc-android】vlc-sout流媒体输出端源码实现分析【Part 3】【02】