live555學習筆記13-RTPInterface詳解

來源:互聯網
上載者:User
十三:RTPInterface詳解

好幾天沒寫blog了。看源碼真累啊,還要把理解的寫到紙上,還要組織混亂的思想,令人頭痛,所以這需要激情。不過,今天激情又來了。

大家應該已理解了GroupSocket這個類。理論上講那些需要操作udp socket 的類應儲存GroupSocket的執行個體。但事實並不是這樣,可以看一下RTPSink,RTPSource,RTCPInstance等,它們都沒有儲存GroupSocket型的變數。那它們通過哪個類進行socket操作呢?是RTPInterface!!
這些類接收的GroupSocket指標最後都傳給了 RTPInterface 。為什麼用RTPInterface而不直接用GroupSocket呢?這裡面有個故事...扯遠了。

要解答這個問題,讓我們先提出問題吧。
首先請問,Live555即支援rtp over udp,又支援rtp over tcp。那麼在rtp over tcp情況下,用 GroupSocket 怎麼實現呢?GroupSocket可是僅僅代表UDP啊!
那麼RTPInterface既然用於網路讀寫,它就應該既支援tcp收發,也支援udp收發。而且它還要像GroupSocket那樣支援一對多。因為服務端是一對多個用戶端哦。我們看一下RTPInterface的成員:
Groupsock* fGS;
tcpStreamRecord* fTCPStreams; // optional, for RTP-over-TCP streaming/receiving
嘿嘿,這兩個緊靠著,說明它們關係不一般啊(難道他們有一腿?)。fGS--代表了一個udp socket和它對應的多個目的端,fTCPStreams--代表了多個TCP socket,當然這些socket都是從一個socket accept()出來的用戶端socket(tcpStreamRecord是一個鏈表哦)。
看到這個架式,我想大家都要得出結論了:RTPInterface還真是男女通吃啊!不論你用戶端與我建立的是tcp串連,還是udp串連,我RTPInterface一律能接收你們的資料,並向你們發出資料!

證據一:向所有用戶端發出資料:

Boolean RTPInterface::sendPacket(unsigned char* packet, unsigned packetSize)<br />{<br />Boolean success = True; // we'll return False instead if any of the sends fail</p><p>// Normal case: Send as a UDP packet:<br />if (!fGS->output(envir(), fGS->ttl(), packet, packetSize))<br />success = False;</p><p>// Also, send over each of our TCP sockets:<br />for (tcpStreamRecord* streams = fTCPStreams; streams != NULL;<br />streams = streams->fNext) {<br />if (!sendRTPOverTCP(packet, packetSize, streams->fStreamSocketNum,<br />streams->fStreamChannelId)) {<br />success = False;<br />}<br />}</p><p>return success;<br />}很明顯啊,先發送udp資料,一對多的問題在GroupSocket中解決。再發送tcp資料,一對多的問題本地解決。
證據二:從所有用戶端讀取資料:
我現在找不到直接的證據,所以我就憶想一下吧:當udp連接埠或tcp連接埠收到資料時,分析後,是哪個用戶端的資料就發給對應這個用戶端的RTPSink或RTCPInstance。
好像已經把最開始的問題解答完了。下面讓我們來分析一下RTPInterface吧。

void RTPInterface::setStreamSocket(int sockNum, unsigned char streamChannelId)<br />{<br />fGS->removeAllDestinations();<br />addStreamSocket(sockNum, streamChannelId);<br />}</p><p>void RTPInterface::addStreamSocket(int sockNum, unsigned char streamChannelId)<br />{<br />if (sockNum < 0)<br />return;</p><p>for (tcpStreamRecord* streams = fTCPStreams; streams != NULL;<br />streams = streams->fNext) {<br />if (streams->fStreamSocketNum == sockNum<br />&& streams->fStreamChannelId == streamChannelId) {<br />return; // we already have it<br />}<br />}</p><p>fTCPStreams = new tcpStreamRecord(sockNum, streamChannelId, fTCPStreams);<br />}setStreamSocket()沒必要說了吧,看一下addStreamSocke()。從字面意思應能瞭解,添加一個流式Socket,也就是添加tcp 
socket了。迴圈中尋找是否已經存在了,最後如果不存在,就建立之,在tcpStreamRecord的建構函式中己經把自己加入了鏈表。對於參數,sockNum很易理解,就是socket()返回的那個SOCKET型
資料唄,streamChannelId是什麼呢?我們不防再猜測一下(很奇怪,我每次都能猜對,嘿嘿...):rtp over tcp時,這個tcp串連是直接利用了RTSP所用的那個tcp串連,如果同時有很多rtp 
session,再加上rtsp session,大家都用這一個socket通訊,怎麼區分你的還是我的?我想這個channel 
id就是用於解決這個問題。給每個session分配一個唯一的id,在發送自己的包時為包再加上個頭部,頭部中需要有session的標記--也就是這個channel id,包的長度等等欄位。這樣大家就可以穿一條褲子了,術語叫多工,但要注意只有tcp才進行多工,udp是不用的,因為udp是一個session對應一個socket(加上RTCP是兩個)。
想像一下,服務端要從這個tcp socket讀寫資料,必須把一個handler加入TaskScheduler中,這個handler在可讀資料時進行讀,在可寫資料時進行寫。在讀資料時,對讀出的資料進行分析,取得資料包的長度,以及其channel id,跟據channel id找到相應的處handler和對象,交給它們去處理自己的資料。
試想兩個建立在tcp上的rtp session,這個兩個tcp socket既擔負著rtsp通訊,又擔負著rtp通訊。如果這兩個rtp session共用一個stream,那麼最終負責這兩個session通訊的就只有一個RTPInterface,那麼這個RTPInterface中的fTCPStreams這個鏈表中就會有兩項,分別對應這兩個session。tcpStreamRecord主要用於socket number與channel id的對應。這些tcpStreamRecord是通過addStreamSocket()添加的。處理資料的handler是通過startNetworkReading()添加的,看一下下:

void RTPInterface::startNetworkReading(TaskScheduler::BackgroundHandlerProc* handlerProc)<br />{<br />// Normal case: Arrange to read UDP packets:<br />envir().taskScheduler().turnOnBackgroundReadHandling(fGS->socketNum(),handlerProc,<br />fOwner);</p><p>// Also, receive RTP over TCP, on each of our TCP connections:<br />fReadHandlerProc = handlerProc;<br />for (tcpStreamRecord* streams = fTCPStreams; streams != NULL;<br />streams = streams->fNext) {<br />// Get a socket descriptor for "streams->fStreamSocketNum":<br />SocketDescriptor* socketDescriptor = lookupSocketDescriptor(envir(),<br />streams->fStreamSocketNum);</p><p>// Tell it about our subChannel:<br />socketDescriptor->registerRTPInterface(streams->fStreamChannelId, this);<br />}<br />}用UDP時很簡單,直接把處理函數做為handler加入taskScheduler即可。而TCP時,需向所有的session的socket都註冊自己。可以想像,socketDescriptor代表一個tcp socket,並且它有一個鏈表之類的東西,其中儲存了所有的對這個socket感興趣的RTPInterface,同時也記錄了RTPInterface對應的channal id。只有向socketDescriptor註冊了自己,socketDescriptor在讀取資料時,才能跟據分析出的channel
id找到對應的RTPInterface,才能調用RTPInterface中的資料處理handler,當然,這個函數也不是RTPInteface自己的,而是從startNetworkReading()這個函數接收到的調用者的。
上述主要講的是一個RTPInterface對應多個用戶端tcp socket的情形。現在又發現一個問題:SocketDescriptor為什麼需要對應多個RTPInterface呢?上面已經講了,是為了多工,因為這個socket即負擔rtsp通訊又負擔rtp通訊還負擔RTCP通訊。SocketDescriptor記錄多工資料(也就是RTPInterface與channel id)用了一個Hash table:HashTable* fSubChannelHashTable。SocketDescriptor讀資料使用函數:static
void tcpReadHandler(SocketDescriptor*, int mask)。證據如下:

void SocketDescriptor::registerRTPInterface(<br />unsigned char streamChannelId,<br />RTPInterface* rtpInterface)<br />{<br />Boolean isFirstRegistration = fSubChannelHashTable->IsEmpty();<br />fSubChannelHashTable->Add((char const*) (long) streamChannelId,<br />rtpInterface);</p><p>if (isFirstRegistration) {<br />// Arrange to handle reads on this TCP socket:<br />TaskScheduler::BackgroundHandlerProc* handler =<br />(TaskScheduler::BackgroundHandlerProc*) &tcpReadHandler;<br />fEnv.taskScheduler().turnOnBackgroundReadHandling(fOurSocketNum,<br />handler, this);<br />}<br />}可見在註冊第一個多工對象時啟動reand handler。看一下函數主體:

void SocketDescriptor::tcpReadHandler1(int mask)<br />{<br />// We expect the following data over the TCP channel:<br />// optional RTSP command or response bytes (before the first '$' character)<br />// a '$' character<br />// a 1-byte channel id<br />// a 2-byte packet size (in network byte order)<br />// the packet data.<br />// However, because the socket is being read asynchronously, this data might arrive in pieces.</p><p>u_int8_t c;<br />struct sockaddr_in fromAddress;<br />if (fTCPReadingState != AWAITING_PACKET_DATA) {<br />int result = readSocket(fEnv, fOurSocketNum, &c, 1, fromAddress);<br />if (result != 1) { // error reading TCP socket, or no more data available<br />if (result < 0) { // error<br />fEnv.taskScheduler().turnOffBackgroundReadHandling(<br />fOurSocketNum); // stops further calls to us<br />}<br />return;<br />}<br />}</p><p>switch (fTCPReadingState) {<br />case AWAITING_DOLLAR: {<br />if (c == '$') {<br />fTCPReadingState = AWAITING_STREAM_CHANNEL_ID;<br />} else {<br />// This character is part of a RTSP request or command, which is handled separately:<br />if (fServerRequestAlternativeByteHandler != NULL) {<br />(*fServerRequestAlternativeByteHandler)(<br />fServerRequestAlternativeByteHandlerClientData, c);<br />}<br />}<br />break;<br />}<br />case AWAITING_STREAM_CHANNEL_ID: {<br />// The byte that we read is the stream channel id.<br />if (lookupRTPInterface(c) != NULL) { // sanity check<br />fStreamChannelId = c;<br />fTCPReadingState = AWAITING_SIZE1;<br />} else {<br />// This wasn't a stream channel id that we expected. We're (somehow) in a strange state. Try to recover:<br />fTCPReadingState = AWAITING_DOLLAR;<br />}<br />break;<br />}<br />case AWAITING_SIZE1: {<br />// The byte that we read is the first (high) byte of the 16-bit RTP or RTCP packet 'size'.<br />fSizeByte1 = c;<br />fTCPReadingState = AWAITING_SIZE2;<br />break;<br />}<br />case AWAITING_SIZE2: {<br />// The byte that we read is the second (low) byte of the 16-bit RTP or RTCP packet 'size'.<br />unsigned short size = (fSizeByte1 << 8) | c;</p><p>// Record the information about the packet data that will be read next:<br />RTPInterface* rtpInterface = lookupRTPInterface(fStreamChannelId);<br />if (rtpInterface != NULL) {<br />rtpInterface->fNextTCPReadSize = size;<br />rtpInterface->fNextTCPReadStreamSocketNum = fOurSocketNum;<br />rtpInterface->fNextTCPReadStreamChannelId = fStreamChannelId;<br />}<br />fTCPReadingState = AWAITING_PACKET_DATA;<br />break;<br />}<br />case AWAITING_PACKET_DATA: {<br />// Call the appropriate read handler to get the packet data from the TCP stream:<br />RTPInterface* rtpInterface = lookupRTPInterface(fStreamChannelId);<br />if (rtpInterface != NULL) {<br />if (rtpInterface->fNextTCPReadSize == 0) {<br />// We've already read all the data for this packet.<br />fTCPReadingState = AWAITING_DOLLAR;<br />break;<br />}<br />if (rtpInterface->fReadHandlerProc != NULL) {<br />rtpInterface->fReadHandlerProc(rtpInterface->fOwner, mask);<br />}<br />}<br />return;<br />}<br />}<br />}最開始的注釋中解釋了多工頭的格式。這一段引起了我的興趣:

case AWAITING_DOLLAR: {<br />if (c == $) {<br />fTCPReadingState = AWAITING_STREAM_CHANNEL_ID;<br />} else {<br />// This character is part of a RTSP request or command, which is handled separately:<br />if (fServerRequestAlternativeByteHandler != NULL) {<br />(*fServerRequestAlternativeByteHandler)(<br />fServerRequestAlternativeByteHandlerClientData, c);<br />}<br />}<br />break;<br />}啊!原來ServerRequestAlternativeByteHandler是用於處理RTSP資料的。也就是從這個socket收到RTSP資料時,調用ServerRequestAlternativeByteHandler。如果收到RTP/RTCP資料時,先查看其channel id,跟據id找到RTPInterface(RTCP也是用了RTPIterface進行通訊),設定RTPInterface中與讀緩衝有關的變數,然後當讀到包資料的開始位置時,調用rtpInterface中儲存的資料處理handler。還記得吧,rtpInterface中的這個資料處理handler在UDP時也被使用,在這個函數中要做的是讀取一個包的資料,然後處理這個包。而SocketDescriptor把讀取位置置於包資料開始的位置再交給資料處理handler,正好可以使用與UDP相同的資料處理handler!
還有,socketDescriptor們並不屬於任何RTPInterface,而是單獨儲存在一個Hash table中,這樣多個RTPInterface都可以註冊到一個socketDescriptor中,以實現多工。
總結一下通過RTPInterface,live555不僅實現了rtp over udp,還實現了rtp over tcp,而且還實現了同時即有rtp over tcp,又有rtp over udp!
最後,channel id是從哪裡來的呢?是在RTSP請求中指定的。在哪個請求中呢?自己找去吧。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.