1 基礎組件
live項目主要包含了四個基礎庫、程序入口類(mediaServer)和測試程序(testProgs)。四個基礎庫是UsageEnvironment、BasicUsageEnvironment、groupsock和liveMedia
UsageEnvironment
抽象了兩個類UsageEnvironment和TaskScheduler,UsageEnvironment表示整個運行環境,提供錯誤記錄和輸出的功能。TaskScheduler表示任務調度中心,用于異步事件的讀取和處理。該庫中還有一個抽象類HashTable,這是一個通用的HashTable,在整個項目中都可以使用它。
BasicUsageEnvironment
UsageEnvironment和TaskScheduler的具體實現類
groupsock
網絡接口的封裝,用于數據包的接收和發送,同時支持多播和單播。groupsock庫中包括了GroupEId、Groupsock、GroupsockHelper、NetAddress、NetInterface等類。GroupsockHelper類主要用于讀寫Socket。
liveMedia
基于基類Medium,實現各種流媒體和編解碼類型結構,定義了source(生產者)和sink(消費者)操作。
mediaServer
mediaServer下的live555MediaServer提供了main函數,DynamicRTSPServer繼承了RTSPServer并重寫了虛函數lookupServerMediaSession
2 主程序
2.1 基本概念和實體
MediaServer是服務器的抽象。
- 它創建用于接受客戶端連接的socket。它還是其他實體的容器。
ClientConnection是與客戶端的數據連接的抽象。
- 當客戶端連接服務器時,MediaServer創建ClientConnection的實例,保存在成員fClientConnections[]中。
MediaSession是媒體的抽象。
- 當用戶請求建立媒體連接時,創建MediaSession實例,保存在MediaServer的成員fServerMediaSessions[]中。這是一個Hash表,key值是媒體的名字。
媒體中可以有多個通道,MediaSubsession是媒體通道的抽象。
- 創建MediaSession時,同時為其中的通道創建MediaSubsession實例,保存在成員fSubsessionHead指向的鏈表中。
ClientSession是與客戶端的對話的抽象,承載在ClientConnection上。
- 當客戶端請求開始播放時,創建ClientSession實例,保存在成員fClientSessions[]中。這是一個Hash表,key值是全局唯一的session id。
StreamState是ClientSession用于掛接到MediaSubsession的中介。
- ClientSession要掛接到MediaSession上,獲得媒體源。它的成員fStreamStates[]引用MediaSession中的MediaSubsession實例。
- fStreamStates[]是一個數組,這里的trackNum指它的索引。
2.2 main函數
main函數創建任務調度器,創建RTSPServer實例,將它的socket置于調度器的監聽下,最后運行調度器,處理socket事件。
- 調用BasicTaskScheduler::createNew()創建任務調度器,這是一個BasicTaskScheduler實例。
- 引用調度器實例,創建BasicUsageEnvironment實例。
- 調用DynamicRTSPServer::createNew(),創建RTSPServer實例。
- 調用GenericMediaServer::setUpOurSocket()創建TCP socket。createNew()的參數指定本地端口號,這里先指定端口號554。但綁定可能失敗,如果失敗了,main()會再次調用createNew(),用端口號8554再重試一次。
- 創建的socket保存在GenericMediaServer的成員fServerSocket中。
- 調用TaskSheduler::turnOnBackgroundReadHandling()將socket置于監聽狀態,回調函數為GenericMediaServer::incomingConnectionHandler()。
GenericMediaServer
::GenericMediaServer(UsageEnvironment& env, int ourSocketIPv4, int ourSocketIPv6, Port ourPort,unsigned reclamationSeconds): Medium(env),fServerSocketIPv4(ourSocketIPv4), fServerSocketIPv6(ourSocketIPv6),fServerPort(ourPort), fReclamationSeconds(reclamationSeconds),fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),fClientSessions(HashTable::create(STRING_HASH_KEYS)),fPreviousClientSessionId(0),fTLSCertificateFileName(NULL), fTLSPrivateKeyFileName(NULL) {ignoreSigPipeOnSocket(fServerSocketIPv4); // so that clients on the same host that are killed don't also kill usignoreSigPipeOnSocket(fServerSocketIPv6); // ditto// Arrange to handle connections from others:env.taskScheduler().turnOnBackgroundReadHandling(fServerSocketIPv4, incomingConnectionHandlerIPv4, this);env.taskScheduler().turnOnBackgroundReadHandling(fServerSocketIPv6, incomingConnectionHandlerIPv6, this);
}
void turnOnBackgroundReadHandling(int socketNum, BackgroundHandlerProc* handlerProc, void* clientData) {setBackgroundHandling(socketNum, SOCKET_READABLE, handlerProc, clientData);}
void BasicTaskScheduler::setBackgroundHandling(int socketNum, int conditionSet, BackgroundHandlerProc* handlerProc, void* clientData) {if (socketNum < 0) return;
#if !defined(__WIN32__) && !defined(_WIN32) && defined(FD_SETSIZE)if (socketNum >= (int)(FD_SETSIZE)) return;
#endifFD_CLR((unsigned)socketNum, &fReadSet);FD_CLR((unsigned)socketNum, &fWriteSet);FD_CLR((unsigned)socketNum, &fExceptionSet);if (conditionSet == 0) {fHandlers->clearHandler(socketNum);if (socketNum+1 == fMaxNumSockets) {--fMaxNumSockets;}} else {fHandlers->assignHandler(socketNum, conditionSet, handlerProc, clientData);if (socketNum+1 > fMaxNumSockets) {fMaxNumSockets = socketNum+1;}if (conditionSet&SOCKET_READABLE) FD_SET((unsigned)socketNum, &fReadSet);if (conditionSet&SOCKET_WRITABLE) FD_SET((unsigned)socketNum, &fWriteSet);if (conditionSet&SOCKET_EXCEPTION) FD_SET((unsigned)socketNum, &fExceptionSet);}
}
- 調用RTSPServer::rtspURLPrefix()得到URL信息,其中調用getourIPAddress()得到本地地址。
- 調用BasicTaskScheduler0::doEventLoop()調度任務。
GenericMediaServer::setUpOurSocket()創建TCP socket。
- 調用setupStreamSocket()創建TCP socket。
- 調用increaseSendBufferTo()重新設置增加緩存大小。
- 調用listen()開始監聽。
BasicTaskScheduler0::doEventLoop()消息循環。
- 循環調用BasicTaskScheduler::SingleStep()
- 為所有需要操作的socket執行select。
- 找到第一個socket?handler執行。
- 找到第一個響應事件執行。
- 找到第一個延遲任務執行。
2.3?GenericMediaServer::incomingConnectionHandler()
當有新的數據連接請求時,GenericMediaServer::incomingConnectionHandler()被調用。其中調用incomingConnectionHandlerOnSocket(),參數是成員fServerSocket。
- 調用accept(),返回新數據連接的socket,同時得到客戶端的地址。
- 調用makeSocketNonBlocking()設置socket為非阻塞模式。
- 調用increaseSendBufferTo()增加發送緩存大小。
- 用新連接的socket和客戶端地址,調用RTSPServer::createNewClientConnection()。
在createNewClientConnection()中
- 創建RTSPClientConnection實例。
- ClientConnection的成員fOurSocket保存新連接的socket值,成員fClientAddr保存客戶端地址。
- GenericMediaServer的成員fClientConnections->Add保存ClientConnection的實例。
- 調用TaskScheduler::setBackgroundHandling(),將新連接的socket置于監聽狀態,回調函數為ClientConnection::incomingRequestHandler()。
- 調用RTSPClientConnection::resetRequestBuffer()復位與接收緩存有關的位置指示變量。
2.4?ClientConnection::incomingRequestHandler()
當socket有數據到達時,ClientConnection::incomingRequestHandler()被調用。
- 調用readSocket()讀取數據,這是RTSP請求字符串。接收緩存是成員fRequestBuffer[]。
- 調用RTSPClientConnection::handleRequestBytes解析RTSP請求并處理。
- 調用parseRTSPRequestString()解析請求字符串,得到RTSP命令字及參數,包括:cmdName請求命令字(如:OPTIONS),urlSuffix是文件名,cseq是請求序列號。
- 后面根據cmdName,調用不同的函數處理。如handleCmd_OPTIONS()處理OPTIONS請求等。
- 對于SETUP請求,調用GenericMediaServer::createNewClientSessionWithId創建ClientSession實例。
- 對于SETUP之后的請求,parseRTSPRequestString()得到有效的sessionIdStr,調用GenericMediaServer::lookupClientSession()得到對應的ClientSession實例。然后調用RTSPClientSession::handleCmd_withinSession(),再調用相應的處理函數。
if (authenticationOK("SETUP", urlTotalSuffix, (char const*)fRequestBuffer)) {clientSession= (RTSPServer::RTSPClientSession*)fOurRTSPServer.createNewClientSessionWithId();} else {areAuthenticated = False;}
if (requestIncludedSessionId) {clientSession= (RTSPServer::RTSPClientSession*)(fOurRTSPServer.lookupClientSession(sessionIdStr));if (clientSession != NULL) clientSession->noteLiveness();}
else if (strcmp(cmdName, "TEARDOWN") == 0|| strcmp(cmdName, "PLAY") == 0|| strcmp(cmdName, "PAUSE") == 0|| strcmp(cmdName, "GET_PARAMETER") == 0|| strcmp(cmdName, "SET_PARAMETER") == 0) {if (clientSession != NULL) {clientSession->handleCmd_withinSession(this, cmdName, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);}
2.5?RTSPClientConnection::handleCmd_OPTIONS
填充回復字符串,告訴客戶端支持哪些請求
void RTSPServer::RTSPClientConnection::handleCmd_OPTIONS() {snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,"RTSP/1.0 200 OK\r\nCSeq: %s\r\n%sPublic: %s\r\n\r\n",fCurrentCSeq, dateHeader(), fOurRTSPServer.allowedCommandNames());
}
2.6?RTSPClientConnection::handleCmd_DESCRIBE
- 調用DynamicRTSPServer::lookupServerMediaSession()查找ServerMediaSession實例,因為這時實例還不存在,所以這里會先創建一個。執行回調RTSPClientConnection::handleCmd_DESCRIBE_afterLookup
- 用這個ServerMediaSeesin實例調用generateSDPDescription()。
- 調用ourIPAddress()得到本地地址。
- 構建會話級sdp字符串,本地地址是它的一個組成部分。
- 遍歷ServerMediaSession的成員fSubsessionHead,對其中的subsession,調用OnDemandServerMediaSubsession::sdpLines()得到媒體級sdp字符串。
- 連接會話級sdp字符串和媒體級sdp字符串。
- 調用RTSPServer::rtspURL()得到URL信息。這是頭部字符串的一部分。
DynamicRTSPServer::lookupServerMediaSession()根據key值,在GenericMediaServer的成員fSerMediaSessions中查找對應的實例。fServerMediaSessesions是一個Hash表。lookupServerMediaSession()實現:
- fopen打開文件,如test.264
- 實例不存在調用全局函數createNewSMS()創建新的MediaSession實例。并調用GenericMediaServer::addServerMediaSession()將它加入成員fServerMediaSessions。
在全局函數createSMS()中
- 從文件名test.264中,解析出擴展名“.264”。根據擴展名做不同處理。 對于擴展名“.264”,NEW_SMS("H.264 Video"),創建ServerMediaSession實例。
- 調用H264VideoFileServerMediaSubsession::createNew(),它創建ServerMediaSubsession實例,文件名保存在成員fFileName中。后面創建FileSource實例時將用到它。
- 調用ServerMediaSession::addSubsession把這個ServerMediaSubsession加入ServerMediaSession的子session表。ServerMediaSession的成員fSubsesisionHead和fSubsessionTail指向這個表的首部和尾部。
OnDemandServerMediaSubsession::sdpLines()得到子session的sdp字符串。sdpLines實現:
- 調用createNewStreamSource()創建FramedSource實例
- 調用createGroupsock()創建Groupsock實例
- 基于FramedSource和Groupsock實例,調用createNewRTPSink()創建RTPSink實例。
- 調用setSDPLinesFromRTPSink()。其中調用RTPSink的接口構建sdp字符串。
- 清理掉以上創建的FramedSource、Groupsock、和RTPSink實例。這時只有ServerMediaSession和ServerMediaSubsession的實例保留下來。
2.7?RTSPClientSession::handleCmd_SETUP
先調用createNewClientSessionWithId()創建ClientSession實例,再調用handleCmd_SETUP()處理。注意handleCmd_SETUP是RTSPClientSession的成員函數,handleCmd_DESCRIBE是RTSPClientConnection的成員函數。
createNewClientSessionWithId()實現:
- 調用our_random32()生成sessionid。
- 用sessionid調用RTSPServer::createNewClientSession()創建RTSPClientSession實例。
- ClientConnection的構造函數調用TaskScheduler::resheduleDelayedTask()設置延時任務,回調函數是ClientSession::LivenessTimeoutTask()。這個函數的作用是在這個Client session實例長時間不工作時,刪除它。
- 將這個實例加入GenericMediaServer的成員fClientSessions,這是一個ClientSession實例的Hash表。
RTSPClientSession::handleCmd_SETUP()實現:
- 調用lookupServerMediaSession(),找到對應的ServerMediaSession實例。這個實例在handleCmd_DESCRIBE()中已經創建好了。
- 調用numSubsessions()得到ServerMediaSession中的subsession數量。
- 新建streamState數組fStreamStates[],長度為subsession數量。fStreamStates[]引用ServerMediaSubsession對象,這些對象保存在fSubsessionsHead鏈表中
- 比對subsession的trackId成員和請求串中的trackId(如"track1"),相同則紀錄trackNum。
- 調用parseTransportHeader()從請求串中解析得到傳輸頭中包括的數據流配置信息。如請求串"RTP/AVP;unicast;client_port=65512-65513",代表單播UDP,RTP連接的客戶端端口為65512,RTCP連接的客戶端端口為65513。
- 調用parseRangeHeader()解析得到點播范圍。如果失敗,則再調用parsePlayNowHeader()看能不能找到。沒有設置點播范圍兩個函數都返回失敗,則從頭開始播到尾。
- 用前面找到的trackNum對應的subsession,調用OnDemandServerMediaSubsession::getStreamParameters()創建數據流通道。
- 與subsesssion對應的streamState,它的成員streamToken,是這個函數的一個參數。OnDemandServerMediaSubsession實例將它當做輔助數據結構,它被定義為類StreamState,與streamState只是首字母不同。
- 構造回復字符串。
getStreamParameters()實現:
- 它的參數clientAddress指定了目標地址。這里因為請求中沒有包括“destination=xxx”,所以這個地址是0。這時將目標地址指定ClientConnection的客戶端地址。
- 調用createNewStreamSource()創建FramedSource實例。
- 創建用于RTP連接和RTCP連接的Groupsock實例。
- 從成員fInitialPortNum指定的起始端口開始,嘗試可用的本地端口,作為RTP連接的本地端口。fInitialPortNum在OnDemandServerMediaSubsession的構造函數中,指定缺省值為6970。
- 調用createGroupsock()創建RTP連接的Groupsock實例。
- 如果不復用RTCP連接和RTP連接,則將RTCP連接的端口加1,創建RTCP連接的Groupsock實例。
- 為RTP連接,調用createNewRTPSink()創建RTPSink實例。如:.264格式文件調用H264VideoFileServerMediaSubsession::createNewRTPSink()。
- 基于上述Groupsock和RTPSink實例,創建StreamState實例。
- 創建Destinations實例,保存客戶端地址和端口,將Destination實例加入成員fDestinationHashTable中。
2.8?RTSPClientSession::handleCmd_PLAY
實現:
- 調用parseScaleHeader()和parseRangeHeader(),得到播放的起點和終點。
- 遍歷成員fNumStreamStates,streamState的成員subsession,調用OnDemandServerMediaSubsession::seekStream(),調整播放位置。
- 遍歷成員fNumStreamStates,streamState的成員subsession,調用OnDemandServerMediaSubsession::startStream(),開始播放。
- 構造回復字符串
OnDemandServrMediaSubsession::startStream()實現:
- 根據clientSessionId在成員fDestinationHashTable中查找對應的Destinations實例,其中保存了目標地址。
- 調用StreamState::startPlaying()開始播放。
- 這個StreamState實例是在處理SETUP請求時創建的, streamState結構體的成員streamToken保存了這個實例。
- 調用OnDemandServerMediaSubsession::createRTCP()創建RTCPInstance實例。RTPSink實例和RTCP連接的Groupsock實例作為參數。并調用setAppHandler()和setSpecificRRHandler()設置回調函數。
- 調用MediaSink::startPlaying(),開始解析媒體幀并通過RTP連接發送,同時調用RTCPInstance::sendReport()發送RTCP信息。
接收RTCP信息實現:
- 調用RTCPInstance::setByeHandler設置一個回調函數,當收到bye消息時,該回調函數被調用。回調函數保存在成員fByeHandlerTask。
- 在RTCPInstance構造函數中,調用RTCPInterface::startNetworkReading(),將RTCPInstance::incomingReportHandler()設置為數據可讀時的回調函數。
在RTCPInstance::incomingReportHandler()中
- 調用RTCPInterface::handleRead()讀取數據到本地緩存。
- 調用processIncomingReport()處理緩存。如果是bye消息,調用fByeHandlerTask()進行處理。
MediaSink::startPlaying()實現:
- 調用MultiFramedRTPSink::continuePlaying(),其實是調用MultiFramedRTPSink::buildAndSendPacket()。
- buildAndSendPacket封裝RTP頭,調用packFrame()打包幀數據
- packFrame()從source文件讀取一幀數據,通過回調afterGettingFrame()返回給sink,其實是調用afterGettingFrame1()。
- afterGettingFrame1()向包中打幀數據,包完成則調用sendPacketIfNecessary()發送包,反之則調用packFrame()從source獲取幀數據打包。
- packFrame()從source文件讀取一幀數據,通過回調afterGettingFrame()返回給sink,其實是調用afterGettingFrame1()。
- buildAndSendPacket封裝RTP頭,調用packFrame()打包幀數據
MultiFramedRTPSink中的幀數據和包緩沖區共用一個,只是用一些額外的變量指明緩沖區中屬于包的部分以及屬于幀數據的部分(包以外的數據叫做overflow data)。