轉載自:http://blog.sina.com.cn/s/blog_77c632410101as1i.html
liveMedia項目的源代碼包括四個基本的庫,各種測試代碼以及Media Server。四個基本的庫分別是:
UsageEnvironment&TaskScheduler, groupsock, liveMedia和BasicUsageEnvironment。
1,基礎類介紹:
BasicUsageEnvironment和UsageEnvironment中的類都是用于整個系統的基礎功能類.用于事件的調度,實現異步讀取事件的句柄的設置以及錯誤信息的輸出。比如UsageEnvironment代表了整個系統運行的環境,它提供了錯誤記錄和錯誤報告的功能,無論哪一個類要輸出錯誤,就需要保存UsageEnvironment的指針.而TaskScheduler則提供了任務調度功能.整個程序的運行發動機就是它,它調度任務,執行任務(任務就是一個函數).TaskScheduler由于在全局中只有一個,所以保存在了UsageEnvironment中.而所有的類又都保存了UsageEnvironment的指針,所以誰想把自己的任務加入調度中,那是很容易的.在此還看到一個結論:整個live555(服務端)只有一個線程.
類HashTable:實現了哈稀表.定義了一個通用的hash表,其它代碼要用到這個表。
liveMedia庫中有一系列類,基類是Medium,這些類針對不同的流媒體類型和編碼。
基于liveMedia的程序,需要通過繼承UsageEnvironment抽象類和TaskScheduler抽象類,定義相應的類來處理事件調度,數據讀寫以及錯誤處理。live項目的源代碼里有這些類的一個基本實現,這就是“BasicUsageEnvironment”庫。BasicUsageEnvironment主要是針對簡單的控制臺應用程序,利用select實現事件獲取和處理。這個庫利用Unix或者Windows的控制臺作為輸入輸出,出于應用程序原形或者調試的目的,用戶可以用這個庫開發傳統的運行與控制臺的應用。
類DelayQueue:譯為"延遲隊列",它是一個隊列,每一項代表了一個要調度的任務(在它的fToken變量中保存).同時保存了這個任務離執行時間點的剩余時間.可以預見,它就是在TaskScheduler中用于管理調度任務的東西.注意,此隊列中的任務只被執行一次!執行完后這一項即被無情拋棄!
類HandlerSet:Handler集合.Handler是什么呢?它是一種專門用于執行socket操作的任務(函數),HandlerSet被TaskScheduler用來管理所有的socket任務(增刪改查).所以TaskScheduler中現在已調度兩種任務了:socket任務(handlerSet)和延遲任務(DelayQueue).其實TaskScheduler還調度第三種任務:Event,這個后面再說.
類Groupsock:這個是放在單獨的庫Groupsock中。它封裝了socket操作,增加了多播支持和一對多單播的功能.但好像不支持TCP。它管理著一個本地socket和多個目的地址,因為是UDP,所以只需知道對方地址和端口即可發送數據。Groupsock的構造函數有一個參數是struct in_addr const& groupAddr,在構造函數中首先會調用父類構造函數創建socket對象,然后判斷這個地址,若是多播地址,則加入多播組。Groupsock的兩個成員變量destRecord* fDests和DirectedNetInterfaceSet fMembers都表示目的地址集和,但貌似這個變量DirectedNetInterfaceSet fMembers沒有用到,且DirectedNetInterfaceSet是一個沒有被繼承的虛類,看起來fMembers沒有什么用。僅fDesk也夠用了,在addDestination()和removeDestination()函數中就是操作fDesk,添加或刪除目的地址。
2,基本概念
先來熟悉在liveMedia庫中Source,Sink以及Filter等概念。Sink就是消費數據的對象,比如把接收到的數據存儲到文件,這個文件就是一個Sink。Source就是生產數據的對象,比如通過RTP讀取數據。數據流經過多個'source'和'sinks',下面是一個示例:
source1' -> 'source2' (a filter) -> 'source3' (a filter) -> 'sink'
從其它Source接收數據的source也叫做"filters"。Module是一個sink或者一個filter。數據接收的終點是Sink類,MediaSink是所有Sink類的基類。Sink類實現對數據的處理是通過實現純虛函數continuePlaying(),通常情況continuePlaying調用fSource -> getNextFrame來為Source設置數據緩沖區,處理數據的回調函數等,fSource是MediaSink的類型為FramedSource*的類成員。
3,計劃任務(TaskScheduler)深入探討
我們且把三種任務命名為:socket handler,event handler,delay task。
這三種任務的特點是,前兩個加入執行隊列后會一直存在,而delay task在執行完一次后會立即棄掉。
socket handler保存在隊列BasicTaskScheduler0::HandlerSet* fHandlers中;
event handler保存在數組BasicTaskScheduler0::TaskFunc *
fTriggeredEventHandlers[MAX_NUM_EVENT_TRIGGERS]中;
delay task保存在隊列BasicTaskScheduler0::DelayQueue fDelayQueue中。
下面看一下三種任務的執行函數的定義:
socket handler為
typedef void BackgroundHandlerProc(void* clientData, int mask);
event handler為
typedef void TaskFunc(void* clientData);
delay task 為
typedef void TaskFunc(void* clientData);//跟event handler一樣。
再看一下向任務調度對象添加三種任務的函數的樣子:
socket handler為:
void setBackgroundHandling(int socketNum, int conditionSet ,BackgroundHandlerProc* handlerProc, void* clientData)
event handler為:
EventTriggerId createEventTrigger(TaskFunc* eventHandlerProc)
delay task為:
TaskToken scheduleDelayedTask(int64_t microseconds, TaskFunc* proc,void* clientData)
socket handler添加時為什么需要那些參數呢?socketNum是需要的,因為要select socket(socketNum即是socket()返回的那個socket對象)。conditionSet也是需要的,它用于表明socket在select時查看哪種裝態,是可讀?可寫?還是出錯?再看BackgroundHandlerProc的參數,socketNum不必解釋,mask是什么呢?它正是對應著conditionSet,但它表明的是select之后的結果,比如一個socket可能需要檢查其讀/寫狀態,而當前只能讀,不能寫,那么mask中就只有表明讀的位被設置。
event handler是被存在數組中。數組大小固定,是32項,用EventTriggerId來表示數組中的項,EventTriggerId是一個32位整數,因為數組是32項,所以用EventTriggerId中的第n位置1表明對應數組中的第n項。成員變量fTriggersAwaitingHandling也是EventTriggerId類型,它里面置1的那些位對應了數組中所有需要處理的項。這樣做節省了內存和計算,但降低了可讀性,而且也不夠靈活,只能支持32項或64項,其它數量不被支持。