接着第十五章節【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】