2012年6月5日 星期二

live555原始程式碼簡介



live555原始程式碼簡介
liveMedia項目的原始程式碼包括四個基本的庫,各種測試代碼以及IVE555 Media Server

四個基本的庫分別是UsageEnvironment&TaskSchedulergroupsockliveMediaBasicUsageEnvironment


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);
前者是用於SIMsource-independent multicast)組,後者用於SSMsource-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庫中SourceSink以及Filter等概念。Sink就是消費資料的物件,比如把接收到的資料存儲到檔,這個檔就是一個SinkSource就是生產資料的物件,比如通過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設置資料緩衝區,處理資料的回呼函數等,fSourceMediaSink的類型為FramedSource*的類成員;

基於liveMedia的應用程式的控制流程如下:

應用程式是事件驅動的,使用如下方式的迴圈

      while (1) {
         
通過查找讀網路控制碼的清單和延遲佇列(delay queue)來發現需要完成的任務
         
完成這個任務
      }

對於每個sink,在進入這個迴圈之前,應用程式通常調用下面的方法來啟動需要做的生成任務:

      someSinkObject->startPlaying();

任何時候,一個Module需要獲取資料都通過調用剛好在它之前的那個方法。這是通過純虛函數FramedSource:doGetNextFrame()實現的,每一個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:描述: http://sys2.blogcn.com/images/biggrin.gifoGetNextFrame 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消息給用戶端,用戶端接收到該消息後釋放資源,程式退出。
}