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訊息給用戶端,用戶端接收到該訊息後釋放資源,程式退出。
}
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/imliujie/archive/2008/01/30/2072657.aspx
源碼解讀二:
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.0.1/1.mpg)串連伺服器時,select返回對應的scoket,進而根據前面儲存的對應關係,可找到對應處理函數控制代碼,這裡就是前面提到的incomingConnectionHandler了。在incomingConnectionHandler中建立了RTSPClientSession,開始對這個用戶端的會話進行處理。
2. DESCRIBE請求訊息處理過程
RTSP伺服器收到用戶端的DESCRIBE請求後,根據請求URL(rtsp://192.168.0.1/1.mpg),找到對應的流媒體資源,返迴響應訊息。live555中的ServerMediaSession類用來處理會話中描述,它包含多個(音頻或視頻)的子會話描述(ServerMediaSubsession)。
RTSP伺服器收到用戶端的串連請求,建立了RTSPClientSession類,處理單獨的客戶會話。在建立RTSPClientSession的過程中,將建立立的socket控制代碼(clientSocket)和RTSP請求處理函數控制代碼RTSPClientSession::incomingRequestHandler傳給任務調度器,由任務調度器對兩者進行一對一關聯。
當用戶端發出RTSP請求後,伺服器主迴圈中的select調用返回,根據socket控制代碼找到對應的incomingRequestHandler,開始訊息處理。先進行訊息的解析,如果發現請求是DESCRIBE則進入handleCmd_DESCRIBE函數。根據用戶端請求URL的尾碼(如1.mpg),調用成員函數DynamicRTSPServer::lookupServerMediaSession尋找對應的流媒體資訊ServerMediaSession。如果ServerMediaSession不存在,但是本地存在1.mpg檔案,則建立一個新的ServerMediaSession。在建立ServerMediaSession過程中,根據檔案尾碼.mpg,建立媒體MPEG-1or2的解複用器(MPEG1or2FileServerDemux)。再由MPEG1or2FileServerDemux建立一個子會話描述MPEG1or2DemuxedServerMediaSubsession。最後由ServerMediaSession完成組裝響應訊息中的SDP資訊(SDP組裝過程見下面的描述),然後將響應訊息發給用戶端,完成一次訊息互動。
SDP訊息組裝過程:
ServerMediaSession負責產生會話公用描述資訊,子會話描述由MPEG1or2DemuxedServerMediaSubsession產生。 MPEG1or2DemuxedServerMediaSubsession在其父類成員函數OnDemandServerMediaSubsession::sdpLines()中產生會話描述資訊。在sdpLines()實現裡面,建立一個虛構(dummy)的FramedSource(具體實作類別為MPEG1or2AudioStreamFramer和MPEG1or2VideoStreamFramer)和RTPSink(具體實作類別為MPEG1or2AudioRTPSink和MPEG1or2VideoRTPSink),最後調用setSDPLinesFromRTPSink(...)成員函數產生子會話描述。
3. SETUP請求訊息處理過程
RTSPClientSession類用於處理單獨的客戶會話。其類成員函數handleCmd_SETUP()處理用戶端的SETUP請求。調用parseTransportHeader()對SETUP請求的傳輸頭解析,調用子會話(這裡具體實作類別為OnDemandServerMediaSubsession)的getStreamParameters()函數擷取流媒體發送傳輸參數。將這些參數組裝成響應訊息,返回給用戶端。
擷取發送傳輸參數的過程:調用子會話(具體實作類別MPEG1or2DemuxedServerMediaSubsession)的createNewStreamSource(...)建立MPEG1or2VideoStreamFramer,選擇發送傳輸參數,並調用子會話的createNewRTPSink(...)建立MPEG1or2VideoRTPSink。同時將這些資訊儲存在StreamState類對象中,用於記錄流的狀態。
用戶端發送兩個SETUP請求,分別用於建立音頻和視頻的RTP接收。
4. PLAY請求訊息處理過程
RTSPClientSession類成員函數handleCmd_PLAY()處理用戶端的播放請求。首先調用子會話的startStream(),內部調用MediaSink::startPlaying(...),然後是MultiFramedRTPSink::continuePlaying(),接著調用MultiFramedRTPSink::buildAndSendPacket(...)。buildAndSendPacke內部先設定RTP包頭,內部再調用MultiFramedRTPSink::packFrame()填充編碼幀資料。
packFrame內部通過FramedSource::getNextFrame(), 接著MPEGVideoStreamFramer::doGetNextFrame(),再接著經過MPEGVideoStreamFramer::continueReadProcessing(), FramedSource::afterGetting(...), MultiFramedRTPSink::afterGettingFrame(...), MultiFramedRTPSink::afterGettingFrame1(...)等一系列繁瑣調用,最後到了MultiFramedRTPSink::sendPacketIfNecessary(), 這裡才真正發送RTP資料包。然後是計算下一個資料包發送時間,把MultiFramedRTPSink::sendNext(...)函數控制代碼傳給任務調度器,作為一個延時事件調度。在主迴圈中,當MultiFramedRTPSink::sendNext()被調度時,又開始調用MultiFramedRTPSink::buildAndSendPacket(...)開始新的發送資料過程,這樣用戶端可以源源不斷的收到伺服器傳來的RTP包了。
發送RTP資料包的間隔計算方法:
Update the time at which the next packet should be sent, based on the duration of the frame that we just packed into it.