文章出自:http://blog.csdn.net/imliujie/archive/2008/01/30/2072657.aspx
live555源代碼簡介
liveMedia項目的源代碼包括四個基本的庫,各種測試代碼以及IVE555 Media Server。
四個基本的庫分別是UsageEnvironment&TaskScheduler,groupsock,liveMedia,BasicUsageEnvironment。
UsageEnvironment 和TaskScheduler類用于事件的調度,實現異步讀取事件的句柄的設置以及錯誤信息的輸出。另外,還有一個HashTable類定義了一個通用的 hash表,其它代碼要用到這個表。這些都是抽象類,在應用程序中基于這些類實現自己的子類。
groupsock類是對網絡接口的封裝,用于收發數據包。正如名字本身,Groupsock主要是面向多播數據的收發的,它也同時支持單播數據的收發。Groupsock定義了兩個構造函數
??? Groupsock(UsageEnvironment& env, struct in_addr const& groupAddr,
????????????? Port port, u_int8_t ttl);
??? Groupsock(UsageEnvironment& env, struct in_addr const& groupAddr,
????????????? struct in_addr const& sourceFilterAddr,
????????????? Port port);
前 者是用于SIM(source-independent multicast)組,后者用于SSM(source-specific multicast)組。groupsock庫中的Helper例程提供了讀寫socket等函數,并且屏蔽了不同的操作系統之間的區別,這是在 GroupsockHelper.cpp文件中實現的。
liveMedia庫中有一系列類,基類是Medium,這些類針對不同的流媒體類型和編碼。
各種測試代碼在testProgram目錄下,比如openRTSP等,這些代碼有助于理解liveMedia的應用。
LIVE555 Media Server是一個純粹的RTSP服務器。支持多種格式的媒體文件:
????? * TS流文件,擴展名ts。
????? * PS流文件,擴展名mpg。
????? * MPEG-4視頻基本流文件,擴展名m4e。
????? * MP3文件,擴展名mp3。
????? * WAV文件(PCM),擴展名wav。
????? * AMR音頻文件,擴展名.amr。
????? * AAC文件,ADTS格式,擴展名aac。
用live555開發應用程序
基于liveMedia的程序,需要通過繼承UsageEnvironment抽象類和TaskScheduler抽象類,定義相應的類來處理事件調度,數據讀寫以及錯誤處理。live項目的源代碼里有這些類的一個實現,這就是“BasicUsageEnvironment”庫。 BasicUsageEnvironment主要是針對簡單的控制臺應用程序,利用select實現事件獲取和處理。這個庫利用Unix或者 Windows的控制臺作為輸入輸出,處于應用程序原形或者調試的目的,可以用這個庫用戶可以開發傳統的運行與控制臺的應用。
通過使用自定義的“UsageEnvironment”和“TaskScheduler”抽象類的子類,這些應用程序就可以在特定的環境中運行,不需要做過多的修改。需要指出的是在圖形環境(GUI toolkit)下,抽象類 TaskScheduler 的子類在實現 doEventLoop()的時候應該與圖形環境自己的事件處理框架集成。
先來熟悉在liveMedia庫中Source,Sink以及 Filter等概念。Sink就是消費數據的對象,比如把接收到的數據存儲到文件,這個文件就是一個Sink。Source就是生產數據的對象,比如通過 RTP讀取數據。數據流經過多個'source'和'sink's,下面是一個示例:
????? 'source1' -> 'source2' (a filter) -> 'source3' (a filter) -> 'sink'
從其它Source接收數據的source也叫做"filters"。Module是一個sink或者一個filter。
數據接收的終點是Sink類,MediaSink是所有Sink類的基類。MediaSink的定義如下:
class MediaSink: public Medium {
public:
??? static Boolean lookupByName(UsageEnvironment& env, char const* sinkName,
??????????????????????????????? MediaSink*& resultSink);
??? typedef void (afterPlayingFunc)(void* clientData);
??? Boolean startPlaying(MediaSource& source,
???????????????????????? afterPlayingFunc* afterFunc,
???????????????????????? void* afterClientData);
??? virtual void stopPlaying();
??? // Test for specific types of sink:
??? virtual Boolean isRTPSink() const;
??? FramedSource* source() const {return fSource;}
protected:
??? MediaSink(UsageEnvironment& env); // abstract base class
??? virtual ~MediaSink();
??? virtual Boolean sourceIsCompatibleWithUs(MediaSource& source);
??????? // called by startPlaying()
??? virtual Boolean continuePlaying() = 0;
??????? // called by startPlaying()
??? static void onSourceClosure(void* clientData);
??????? // should be called (on ourselves) by continuePlaying() when it
??????? // discovers that the source we're playing from has closed.
??? FramedSource* fSource;
private:
??? // redefined virtual functions:
??? virtual Boolean isSink() const;
private:
??? // The following fields are used when we're being played:
??? afterPlayingFunc* fAfterFunc;
??? void* fAfterClientData;
};
Sink 類實現對數據的處理是通過實現純虛函數continuePlaying(),通常情況下continuePlaying調用 fSource->getNextFrame來為Source設置數據緩沖區,處理數據的回調函數等,fSource是MediaSink的類型為 FramedSource*的類成員;
基于liveMedia的應用程序的控制流程如下:
應用程序是事件驅動的,使用如下方式的循環
????? while (1) {
????????? 通過查找讀網絡句柄的列表和延遲隊列(delay queue)來發現需要完成的任務
????????? 完成這個任務
????? }
對于每個sink,在進入這個循環之前,應用程序通常調用下面的方法來啟動需要做的生成任務:
????? someSinkObject->startPlaying();
任何時候,一個Module需要獲取數據都通過調用剛好在它之前的那個Module的FramedSource::getNextFrame()方法。這是通過純虛函數FramedSource:oGetNextFrame()實現的,每一個Source module都有相應的實現。
Each 'source' module's implementation of "doGetNextFrame()" works by arranging for an 'after getting' function to be called (from an event handler) when new data becomes available for the caller.
注意,任何應用程序都要處理從'sources'到'sinks'的數據流,但是并非每個這樣的數據流都與從網絡接口收發數據相對應。
比 如,一個服務器應用程序發送RTP數據包的時候用到一個或多個"RTPSink" modules。這些"RTPSink" modules以別的方式接收數據,通常是文件 "*Source" modules (e.g., to read data from a file), and, as a side effect, transmit RTP packets.
一個簡單的RTSP客戶端程序
在另一個文章里,給出了這個簡單的客戶端的程序的代碼,可以通過修改Makefile來裁剪liveMedia,使得這個客戶端最小化。此客戶端已經正常運行。
首先是OPTION
然后是DESCRIBE
????? 建立Media Session,調用的函數是 MediaSession::createNew,在文件liveMedia/MediaSession.cpp中實現。
????? 為這個Media Session建立RTPSource,這是通過調用 MediaSubsession::initiate來實現的的,這個方法在liveMedia/MediaSession.cpp中實現。
在然后是SETUP
最后是PLAY
rtp數據的句柄:MultiFramedRTPSource::networkReadHandler 在liveMedia/MultiFramedRTPSource.cpp中
rtcp數據處理的句柄:RTCPInstance::incomingReportHandler 在liveMedia/RTCP.cpp中
rtp數據處理的句柄的設置:MultiFramedRTPSource:oGetNextFrame 在liveMedia/MultiFramedRTPSource.cpp中, 被FileSink::continuePlaying調用在FileSink.cpp中.
rtcp數據處理的句柄設置fRTCPInstance = RTCPInstance::createNew 在/liveMedia/MediaSession.cpp中調用,
createNew調用了構造函數RTCPInstance::RTCPInstance,這個構造函數有如下調用
TaskScheduler::BackgroundHandlerProc* handler = (TaskScheduler::BackgroundHandlerProc*)&incomingReportHandler;??
*********************************************************************************************************************
通過分析live庫提供的例子程序OpenRTSP,可以清晰地了解客戶端接收來自網絡上媒體數據的過程。注意,RTP協議和RTCP協議接收的數據分別是視音頻數據和發送/接收狀況的相關信息,其中,RTP協議只負責接收數據,而RTCP協議除了接收服務器的消息之外,還要向服務器反饋。
A.??????? main函數流程
main(int argc,char *argv[])
{
1.??????????? 創建BasicTaskScheduler對象
2.??????????? 創建BisicUsageEnvironment對象
3.??????????? 分析argv參數,(最簡單的用法是:openRTSP rtsp://172.16.24.240/mpeg4video.mp4)以便在下面設置一些相關參數
4.??????????? 創建RTSPClient對象
5.??????????? 由RTSPClient對象向服務器發送OPTION消息并接受回應
6.??????????? 產生SDPDescription字符串(由RTSPClient對象向服務器發送DESCRIBE消息并接受回應,根據回應的信息產生SDPDescription字符串,其中包括視音頻數據的協議和解碼器類型)
7.??????????? 創建MediaSession對象(根據SDPDescription在MediaSession中創建和初始化MediaSubSession子會話對象)
8.??????????? while循環中配置所有子會話對象(為每個子會話創建RTPSource和RTCPInstance對象,并創建兩個GroupSock對象,分別對應 RTPSource和RTCPInstance對象,把在每個GroupSock對象中創建的socket描述符置入 BasicTaskScheduler::fReadSet中,RTPSource對象的創建的依據是SDPDescription,例如對于MPEG4 文件來說,視音頻RTPSource分別對應MPEG4ESVideoRTPSource和MPEG4GenericRTPSource對象。 RTCPInstance對象在構造函數中完成將Socket描述符、處理接收RTCP數據的函數 (RTCPInstance::incomingReportHandler)以及RTCPInstance本身三者綁定在一個 HandlerDescriptor對象中,并置入BasicTaskScheduler::fReadHandler中。完成綁定后會向服務器發送一條消息。)
9.??????????? 由RTSPClient對象向服務器發送SETUP消息并接受回應。
10.??????? while循環中為每個子會話創建接收器(FileSink對象),在FileSink對象中根據子會話的codec等屬性缺省產生記錄視音頻數據的文件名,視音頻文件名分別為:video-MP4V-ES-1和audio-MPEG4-GENERIC-2,無后綴名
11.??????? while循環中為每個子會話的視音頻數據裝配相應的接收函數,將每個子會話中的RTPSource中的GroupSock對象中的SOCKET描述符,置入BasicTaskScheduler::fReadSet中,并將描述符、處理接收RTP數據的函數 (MultiFramedRTPSource::networkReadHandler)以及RTPSource本身三者綁定在一個 HandlerDescriptor對象中,并置入BasicTaskScheduler::fReadHandler中,并將FileSink的緩沖區和包含寫入文件操作的一個函數指針配置給RTPSource對象,這個緩沖區將會在networkReadHandler中接收來自網絡的視音頻數據(分析和去掉RTP包頭的工作由RTPSource完成),而這個函數指針在networkReadHandler中被調用以完成將緩沖區中的數據寫入文件。
12.??????? 由RTSPClient對象向服務器發送PLAY消息并接受回應。
13.??????? 進入while循環,調用BasicTaskScheduler::SingleStep()函數接受數據,直到服務器發送TREADOWN消息給客戶端,客戶端接收到該消息后釋放資源,程序退出。
}