此章節分析承接上一章分析:
【十五】【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流媒體處理部分會延緩更新】