live555_RTSP連線建立以及請求訊息處理過程
1,RTSP連線的建立過程
RTSPServer類用於構建一個RTSP伺服器,該類同時在其內部定義了一個RTSPClientSession類,用於處理單獨的客戶會話。
首先建立RTSP伺服器(具體實現類是DynamicRTSPServer),在建立過程中,先建立Socket(ourSocket)在TCP的554埠進行監聽,然後把連線處理函式控制代碼
(RTSPServer:: incomingConnectionHandler)和socket控制代碼傳給任務排程器(taskScheduler)。
任務排程器把socket控制代碼放入後面select呼叫中用到的socket控制代碼集(fReadSet)中,同時將socket控制代碼和incomingConnectionHandler控制代碼關聯起來。接著,主程式開始進入任務排程器的主迴圈(doEventLoop),在主迴圈中呼叫系統函式select阻塞,等待網路連線。
當RTSP客戶端輸入(rtsp://192.168.1.109/1.mpg)連線伺服器時,select返回對應的scoket,進而根據前面儲存的對應關係,可找到對應處理函式控制代碼,這裡就是前面提到的incomingConnectionHandler了。在incomingConnectionHandler中建立了RTSPClientSession,
具體分析如下:
DynamicRTSPServer::creatnew():
1.呼叫繼承自RTPSever::setUpOurSocket:
1.呼叫GroupsockHelper 的setupStreamSocket建立一個socket連線,並繫結,
2.設定socket的傳送快取大小,
3.呼叫listen開始監聽埠,設定同時最大能處理連線數LISTEN_BACKLOG_SIZE=20,如果達到這個上限則client端將收到ECONNERREFUSED的錯誤
4.測試繫結埠是否為0,為0的話重新繫結斷口,並返回系統自己選擇的新的埠。
5.返回建立的socket檔案描述符
2.呼叫自己和RTPSever的建構函式:
RTPSever建構函式:
1.用一個UsageEnvironment物件的引用構造其父類Medium
2.設定最大等待回收連線時間reclamationTestSeconds,超過這個時間從客戶端沒有RTSP命令或者RTSP的RR包則收回其RTSPClientSession
3.建立一個HashTable(實際上是一個BasicHashTable),fServerMediaSessions指向這個表。
4.呼叫引數UsageEnvironment物件env的成員,一個TaskScheduler指標所指物件(實際就是一個BasicTaskScheduler物件)的成員函式
turnOnBackgroundReadHandling():
1.呼叫一個HandlerSet::assignHandler()建立一個Handler,把socketNum【此處為伺服器監聽的socket描述符】和處理函式RTSPServer::incomingConnectionHandler(),還有指向RTSPSever的指標繫結在一起。
incomingConnectionHandler作用:
1.呼叫accept返回伺服器與客戶端連線的socket描述符
2.設定客戶端描述符為非阻塞
3.增加客戶端socket描述符的傳送快取為50*1024
4.為此客戶端隨機分配一個sessionId
5.用客戶端socket描述符clientSocket,sessionId,和客戶端地址clientAddr呼叫creatNewClientSession建立一個clientSession。
2,請求訊息處理過程
上節我們談到RTSP伺服器收到客戶端的連線請求,建立了RTSPClientSession類,處理單獨的客戶會話。在建立 RTSPClientSession的過程中,將新建立的socket控制代碼(clientSocket)和RTSP請求處理函式控制代碼RTSPClientSession::incomingRequestHandler傳給任務排程器,由任務排程器對兩者進行一對一關聯。當客戶端發出 RTSP請求後,伺服器主迴圈中的select呼叫返回,根據socket控制代碼找到對應的incomingRequestHandler,開始訊息處理。先進行訊息的解析。
RTSPClientSession::RTSPClientSession()建構函式:
1.重置請求快取
2.呼叫envir().taskScheduler().turnOnBackgroundReadHandling(),這次socketnumber為客戶端socket描述符這次的處理函式是RTSPServer::RTSPClientSession::incomingRequestHandler()
RTSPServer::RTSPClientSession::incomingRequestHandler():
呼叫handleAlternativeRequestByte1(uint8_t requestByte):
1.fRequestBuffer[fRequestBytesAlreadySeen] =requestByte;把請求字元放入請求快取fRequestBuffer
2.呼叫handleRequestBytes(1) 處理請求快取
handleRequestBytes(int newBytesRead):
1.呼叫noteLiveness()檢視請求是否到期,如果伺服器的reclamationTestSeconds>
0,呼叫taskScheduler物件的rescheduleDelayedTask函式: 引數為
( fLivenessCheckTask,fOurServer.fReclamationTestSeconds*1000000,(TaskFunc*)livenessTimeoutTask, this )
其中livenessTimeoutTask()函式作用是刪除new出來的clientSession.
1.呼叫unscheduleDelayedTask(TaskToken&prevTask):
從DelayQueue中刪除prevTask項, prevTask置空.
2.呼叫scheduleDelayedTask(int64_t microseconds,
TaskFunc* proc, void*clientData):
1.建立一個DelayInterval物件timeToDelay,用microseconds初始化。
2.建立一個AlarmHandler物件,用proc, clientData, timeToDelay初始化
3.呼叫fDelayQueue.addEntry(),把這個AlarmHandler物件加入到延遲佇列中
4.返回AlarmHandler物件的token[long型別]的指標
2.如果請求的的長度超過請求快取可讀長度fRequestBufferBytesLeft,結束這個連線。
3.找到請求訊息的結尾:。
4.如果找到訊息結尾,呼叫RTSPServer::RTSPClientSession::handleRequestBytes()[值得關注此函式]把請求字串轉換成命令各部分包括:cmdName[方法],urlPreSuffix[url地址],urlSuffix[要讀取的檔名],sceq[訊息的Cseq],否則函式返回需要繼續從連線中讀取請求。分別存入對應的陣列。
5.如果轉換成功,呼叫handleCmd_xxx()處理對應的cmdName: xxx[此處實現了:OPTIONS,DESCRIBE,SETUP,TEARDOWN,PLAY,PAUSE,GET_PARAMETER,SET_PARAMETER]
其中PLAY,PAUSE,GET_PARAMETER,SET_PARAMETER呼叫handleCmd_withinSession
(cmdName,urlPreSuffix, urlSuffix,cseq,(char const*)fRequestBuffer);
6.清空 RequestBuffer
比如:訊息解析後,如果發現客戶端的請求是DESCRIBE則進入handleCmd_DESCRIBE函式。RTSP伺服器收到客戶端的DESCRIBE請求後,根據請求URL(rtsp://192.168.1.109/1.mpg),找到對應的流媒體資源,返回響應訊息。live555中的ServerMediaSession類用來處理會話中描述,它包含多個(音訊或視訊)的子會話描述(ServerMediaSubsession)。根據客戶端請求URL的字尾(例如是1.mpg), 呼叫成員函式 DynamicRTSPServer::lookupServerMediaSession查詢對應的流媒體資訊 ServerMediaSession。(根據urlSuffix查詢)。
如果ServerMediaSession不存在,查詢檔案是否存在,若檔案不存在,則判斷ServerMediaSession (即smsExists)是否存在,如果存在則將其remove(呼叫removeServerMediaSession方法)。但是如果本地存在1.mpg檔案,則根據檔名建立一個新的 ServerMediaSession(呼叫createNewSMS方法,若在該方法中找不到對應的副檔名,則將返回NULL)。
如果通過lookupServerMediaSession返回的是NULL,則向客戶端傳送響應訊息並將fSessionIsActive置為FALSE;否則,為該session組裝一個SDP描述資訊(呼叫generateSDPDescription方法,該方法在ServerMediaSession類中),組裝完成後,生成一個RTSP URL(呼叫rtspURL方法,該方法在RTSPServer類中)。
在建立ServerMediaSession過程中,根據檔案字尾.mpg,建立媒體MPEG-1or2的解複用器 (MPEG1or2FileServerDemux)。再由MPEG1or2FileServerDemux建立一個子會話描述 MPEG1or2DemuxedServerMediaSubsession。最後由ServerMediaSession完成組裝響應訊息中的SDP資訊(SDP組裝過程見下面的描述),然後將響應訊息發給客戶端,完成一次訊息互動。
===================================================================================================================================
RTSP伺服器處理客戶端點播的基本流程
處理連線請求的基本流程:
l Step 1:與客戶端建立RTSP連線(呼叫incomingConnectionHandler方法),建立ClientSession並關聯fClientSocket與incomingRequestHandler(呼叫incomingConnectionHandler1)。
l Step 2:接收客戶端請求(呼叫incomingRequestHandler方法)。
l Step 3:從客戶端Socket讀取資料,並對請求資料(即the request string)進行轉換(呼叫parseRTSPRequestString方法,該方法在RTSPCommon類中)。
l Step 4:根據分離出來的指令進行分別處理:
n OPTIONS→handleCmd_OPTIONS
n DESCRIBE→handleCmd_DESCRIBE
handleCmd_DESCRIBE這一個方法比較重要,首先根據urlSuffix查詢ServerMediaSession是否存在(呼叫lookupServerMediaSession方法,該方法中通過HashTable來查詢)。
在testOnDemandRTSPServer專案工程中,僅僅是通過streamName來確認session是否為NULL。而在完整的live555MediaServer專案工程中,則是通過DynamicRTSPServer類來處理,其首先是查詢檔案是否存在,若檔案不存在,則判斷ServerMediaSession(即smsExists)是否存在,如果存在則將其remove(呼叫removeServerMediaSession方法);若檔案存在,則根據檔名建立一個ServerMediaSession(呼叫createNewSMS方法,若在該方法中找不到對應的副檔名,則將返回NULL)。
如果通過lookupServerMediaSession返回的是NULL,則向客戶端傳送響應訊息並將fSessionIsActive置為FALSE;否則,為該session組裝一個SDP描述資訊(呼叫generateSDPDescription方法,該方法在ServerMediaSession類中),組裝完成後,生成一個RTSP URL(呼叫rtspURL方法,該方法在RTSPServer類中)。
n SETUP→handleCmd_SETUP
handleCmd_SETUP方法中,有兩個關鍵的名詞,一個是urlPreSuffix,代表了session name(即stream name);一個是urlSuffix,代表了subsession name(即track name),後面經常用到的streamName和trackId分別與這兩個名詞有關。
接下來會建立session's state,包括incrementReferenceCount等。緊接著,會針對確定的subsession(track)查詢相應的資訊。接著,在request string查詢一個"Transport:" header,目的是為了從中提取客戶端請求的一些引數(呼叫parseTransportHeader方法,該方法在RTSPServer類中),如clientsDestinationAddressStr、ClientRTPPortNum等。
再接著是getStreamParameters(該方法在ServerMediaSession類中被定義為純虛擬函式並在OnDemandServerMediaSubsession類中被重定義),然後通過fIsMulticast和streamingMode來組裝不同的響應訊息。
n PLAY→handleCmd_PLAY:處理播放請求,具體的實現流程請參見後面的步驟。
n PAUSE→handleCmd_PAUSE:處理暫停請求,在執行了該請求後,最終會呼叫StopPlaying方法,並將fAreCurrentlyPlaying置為FALSE。
n TEARDOWN→handleCmd_TEARDOWN:處理停止請求,將fSessionIsActive置為FALSE。
n GET_PARAMETER→handleCmd_GET_PARAMETER:該方法主要是維持客戶端與伺服器通訊的生存狀態,just for keep alive。
n SET_PARAMETER→handleCmd_SET_PARAMETER:該方法未針對SET_PARAMETER作實現,使用該方法會呼叫handleCmd_notSupported方法,並將最終引發與客戶端斷開連線。
l Step 5:根據Step 4的不同指令進行訊息響應(呼叫send方法),該訊息響應是即時的。
l Step 6:處理客戶端傳送“SETUP”指令後即開始播放的特殊情況。
l Step 7:將RequestBuffer進行重置,以便於為之後到來的請求做好準備。
l Step 8:檢查fSessionIsActive是否為FALSE,如果是則刪除當前的ClientSession。
處理PLAY的基本流程:
l Step 1:對rtspURL及相關header的處理,涉及較多的細節。
l Step 2:根據不同的header對流進行縮放比例或定位的處理。
如果為sawScaleHeader,則進行縮放比例的處理(呼叫setStreamScale方法,該方法在OnDemandServerMediaSubsession類中實現)。
如果為sawRangeHeader,則進行尋找流的處理(即是對流進行定位,呼叫seekStream方法,該方法在OnDemandServerMediaSubsession類中實現;同時,該方法的呼叫是在初始播放前及播放過程中由於使用者拖動播放進度條而產生的系列請求)。
在OnDemandServerMediaSubsession類中,seekStream方法中呼叫了seekStreamSource方法,該方法在對應的媒體格式檔案的FileServerMediaSubsession類中實現(如針對WAV格式,則在WAVAudioFileServerMediaSubsession類中實現;針對MP3格式,則在MP3AudioFileServerMediaSubsession類中實現)。
同理,OnDemandServerMediaSubsession類中的setStreamScale方法中所呼叫的setStreamSourceScale方法亦是類似的實現機制。
l Step 3:開始進行流式播放(呼叫startStream方法,該方法在OnDemandServerMediaSubsession類中實現)。
n Step 3.1:根據clientSessionId從fDestinationsHashTable中查詢到destinations(包括了客戶端的IP地址、RTP埠號、RTCP埠號等資訊)。
n Step 3.2:呼叫startPlaying方法,在該方法中根據RTPSink或UDPSink分別呼叫startPlaying方法。
如果是呼叫RTPSink的startPlaying方法,則接著會呼叫MediaSink類中的startPlaying方法,並在該方法中呼叫MultiFramedRTPSink類中的continuePlaying方法,之後便是buildAndSendPacket了。這裡已經來到重點了,即是關於不斷讀取Frame並Send的要點。在MultiFramedRTPSink類中,通過buildAndSendPacket、packFrame、afterGettingFrame、afterGettingFrame1、sendPacketIfNecessary和sendNext構成了一個迴圈圈,資料包的讀取和傳送在這裡迴圈進行著。特別注意的是sendPacketIfNecessary方法中的後面程式碼(nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecondsToGo, (TaskFunc*)sendNext, this);),通過Delay amount of time後,繼續下一個Task,並回過來繼續呼叫buildAndSendPacket方法。
在packFrame方法中,正常情況下,需要呼叫getNextFrame方法(該方法在FramedSource類中,並且對不同媒體格式的Frame的獲取出現在FramedSource類的getNextFrame方法中,通過呼叫doGetNextFrame方法來實現)來獲取新的Frame。
如果是呼叫UDPSink的startPlaying方法,則接著會呼叫MediaSink類中的startPlaying方法,並在該方法中呼叫BasicUDPSink類中的continuePlaying方法。在這之後由若干個方法構成了一個迴圈圈:continuePlaying1、afterGettingFrame、afterGettingFrame1、sendNext。並在afterGettingFrame1方法中實現了packet的傳送(fGS->output(envir(), fGS->ttl(),fOutputBuffer, frameSize);)。
Step 3.3:針對RTPSink建立RTCP instance(RTP與RTCP的配套使用決定了其必須這麼做,否則可能就跟直接使用UDP傳送資料包沒什麼兩樣了^_^),建立RTCP instance時,將incomingReportHandler控制代碼作為BackgroundHandlerProc,以便於處理RTCP的報告,並開始startNetworkReading。這裡RTP/RTCP的使用方式有兩種,一種建立在TCP之上,一種建立在UDP之上。