最近因項目開發需要,開始學習開源項目live555,特別將個人的一些學習心得做一下記錄,如有理解不正確之處,歡迎各位朋友指出。
首先是源碼的下載,這可以從http://www.live555.com上下載,同時上面亦提供了相關的文檔,文檔雖較爲粗糙,但總比找不到任何文檔說明強。當然,我在學習的過程中,也從網上查找了一段時間,收穫還是有一點的,其中就有諸如:RTSP服務器實例live555源代碼分析、live555源代碼簡介、live555代碼解讀系列、基於live555的rtp-rtcp研究等文章。
我的學習是在VS2008環境下進行的,要能夠在VS環境下順利進行,還得做相關工作。live開源雖說是用C++寫的,代碼風格也非常優秀,但是其是用makefile文件來做的,而對未接觸過makefile文件的我是一個問題。幸運的是在網上偶然發現了一篇介紹在VC6環境下編譯live的文章,然後照着上面所說的做,磕磕碰碰,編譯成功了四個庫並轉移到了VS2008環境中。更幸運的是又是偶然在網上發現了一篇介紹用VS2008編譯live的文章並提供了編譯後的工程下載,download,然後就開始了相對漫長的學習過程。
從程序的結構來看,live項目包括了四個基本庫、程序入口類(在mediaServer中)和一些測試代碼(在testProgs中)。四個基本庫是UsageEnvironment、BasicUsageEnvironment、groupsock和liveMedia。
UsageEnvironment包括抽象類UsageEnvironment和抽象類TaskScheduler,這兩個類用於事件調度,其中包括實現了對事件的異步讀取、對事件句柄的設置及對錯誤信息的輸出等;該庫中還有一個HashTable,這是一個通用的HashTable,在整個項目中都可以使用它,當然該HashTable也是一個抽象類。
BasicUsageEnvironment中的類主要是對UsageEnvironment中對應類的實現。
groupsock,顧名思義,用於數據包的接收和發送,其同時支持多播和單播。groupsock庫中包括了GroupEId、Groupsock、GroupsockHelper、NetAddress、NetInterface等類,其中Groupsock類有兩個構造函數,一個是“for a source-independent multicast group”,另一個是“for a source-specific multicast group”;而GroupsockHelper類主要用於讀寫Socket。
liveMedia是很重要的一個庫,其不僅包含了實現RTSP Server的類,還包含了針對不同流媒體類型(如TS流、PS流等)編碼的類。在該庫中,基類是Medium,層次關係非常清晰。在該庫中,有幾個很重要的類,如RTSPServer、ServerMediaSession、RTPSink、RTPInterface、FramedSource等。
在http://www.live555.com上的相關文檔中提到穿透防火牆的問題,方法是開啓一個HTTP的tunnel,然後我們可以在liveMedia庫中找到一個RTSPOverHTTPServer的類,該類解決了這樣的問題。
mediaServer下的live555MediaServer提供了main函數,DynamicRTSPServer繼承了RTSPServer並重寫了虛函數lookupServerMediaSession
前面已經講到,通過不斷地嘗試(其實要在XP SP3環境下使用VS2008編譯成功還是挺費神的),總算把源代碼編譯成功,同時又參考了新下載的一個用VS2008編譯通過的live555源代碼。結合這些,開始對主要類結構進行初步分析。
鑑於UsageEnvironment庫、BasicUsageEnvironment庫和groupsock庫中的類較少,就暫且不作分析了。這裏主要針對liveMedia庫中的主要類結構進行分析。通過查看類關係圖,可以從整體把握,但是苦於類太多,用類關係圖看起來也不方便,於是就自己重新整理了一下,下面是
l
n
n
n
n
n
u
l
n
n
n
n
n
n
n
n
l
u
n
u
l
n
n
n
u
l
l
n
u
u
u
u
u
u
u
u
u
u
u
u
u
u
u
u
u
l
n
n
l
l
l
l
l
n
n
l
n
l
n
n
n
n
n
n
n
u
n
n
n
n
n
u
u
n
n
n
n
u
l
u
l
n
n
n
n
n
u
u
u
l
n
n
u
u
u
u
u
u
n
u
u
u
u
u
u
n
u
l
u
l
l
n
n
n
n
n
n
n
n
n
l
n
n
n
n
n
n
n
n
l
n
n
n
n
n
n
n
n
l
n
n
u
u
n
n
n
從上面這個主要的類結構可以看出,liveMedia庫中的基類爲Medium,其下又有幾個非常重要的部分,一個是×××Subsession,除Medium父類外,所有的×××Subsession類都繼承於ServerMediaSubsession;一個是×××Source,MediaSource的frameSource下主要包含FramedFileSource、RTPSource、FramedFilter等幾個主要的部分;一個是MediaSink,以繼承於RTPSink的類居多。
此外,還包含了用於處理packet的BufferedPacketFactory和BufferedPacket及其相關子類,用於處理流分析的StreamParser及其子類。
基本上,整個liveMedia庫的主要類結構就是這樣。不過,類太多了,分析起來還是有較大的困難。於是乎,採取去掉枝葉保留主幹的做法,將整個服務器精簡成支持一種格式的服務器,如MP3流服務器。經過分離,一個基於MP3的測試服務器的主要類結構如下所示。(注:其他單類及結構體等不在此列出)
l
n
n
n
n
u
l
n
n
u
l
n
u
l
l
n
u
u
u
l
n
n
u
n
u
u
n
n
u
u
l
n
u
u
n
l
n
l
n
根據上面這種相對簡單的類結構,分析起來就方便多了。於是,開始進入具體的分析細節了,這是一個相對漫長的過程,再加上本人的C++水平有限,這又將是一個相對艱苦的過程,一切慢慢來吧……
末了,講講啓動服務器的過程:
LIVE555是一個純粹的RTSP服務器,其服務器主類爲liveMedia庫下的RTSPServer;mediaServer下的live555MediaServer爲主程序的入口類,DynamicRTSPServer是RTSPServer的實現類。
從live555MediaServer類的入口函數main中可以非常清晰地分析出服務器的啓動過程。
首先是createNew一個TaskSchedulers對象和一個UsageEnvironment對象,這是初始工作。
之後是一段訪問控制的代碼。然後開始進入創建RTSP服務器的代碼段,服務器指定了一個默認端口554和一個可供替代的端口8554。
接下來,createNew一個DynamicRTSPServer,這裏建立了Socket(ourSocket)在TCP的554端口(默認端口)進行監聽,然後把連接處理函數句柄和socket句柄傳給任務調度器(即taskScheduler),既是RTSPServer類中的這句代碼:env.taskScheduler().turnOnBackgroundReadHand
最後,進入主循環(即env->taskScheduler().doEventLoop();),等待客戶端連接。服務器啓動完畢。
l
l
l
l
n
n
handleCmd_DESCRIBE這一個方法比較重要,首先根據urlSuffix查找ServerMediaSession是否存在(調用lookupServerMediaSession
在testOnDemandRTSPServer項目工程中,僅僅是通過streamName來確認session是否爲NULL。而在完整的live555MediaServer項目工程中,則是通過DynamicRTSPServer類來處理,其首先是查找文件是否存在,若文件不存在,則判斷ServerMediaSession(即smsExists)是否存在,如果存在則將其remove(調用removeServerMediaSession
如果通過lookupServerMediaSession
n
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類中),如clientsDestinationAddres
再接着是getStreamParameters(該方法在ServerMediaSession類中被定義爲純虛函數並在OnDemandServerMediaSubse
n
n
n
n
n
l
l
l
l
l
l
如果爲sawScaleHeader,則進行縮放比例的處理(調用setStreamScale方法,該方法在OnDemandServerMediaSubse
如果爲sawRangeHeader,則進行尋找流的處理(即是對流進行定位,調用seekStream方法,該方法在OnDemandServerMediaSubse
在OnDemandServerMediaSubse
同理,OnDemandServerMediaSubse
l
n
n
如果是調用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(),
Step 3.3:針對RTPSink創建RTCP instance(RTP與RTCP的配套使用決定了其必須這麼做,否則可能就跟直接使用UDP發送數據包沒什麼兩樣了^_^),創建RTCP instance時,將incomingReportHandler句柄作爲BackgroundHandlerProc,以便於處理RTCP的報告,並開始startNetworkReading。這裏RTP/RTCP的使用方式有兩種,一種建立在TCP之上,一種建立在UDP之上。
http://blog.csdn.net/huangxinfeng/archive/2010/03/11/5369391.aspx
http://blog.csdn.net/huangxinfeng/archive/2010/03/12/5374721.aspx
http://blog.csdn.net/huangxinfeng/archive/2010/03/22/5404233.aspx