以testRTSPClient.cpp測試程序來對Live555 RTSP播放進行一個簡單的分析。同時對Live555幾大模塊的功能及使用進行簡單描述。
因爲我對Live555使用的比較多的是在客戶端播放場景下,所以可能有些不足或者錯誤,請指正。
RTSPClient處於Live555 liveMedia模塊,這部分是Live555流媒體的核心部分,主要是實現了各種流媒體交互流程。我們先了解一些重要的類,以幫助後面分析代碼。(RTSP播放流程分析)
GroupSock
GroupSock是Live555對網絡接口的封裝,支持UDP/TCP,同時也支持單播/組播。
udpGroupsock = new Groupsock(*env, udpIP, udpPort, ttl);
像這樣通過IP及端口,就可以創建一個GroupSock,liveMedia中的模塊就可以通過該socket進行網絡數據的讀寫。
Source、Filter 和Sink
- Source 發送端,流的起點, 可直觀理解爲生產者,負責讀取文件或網絡流的信息。
- Filter 本質上也是Source,因爲可以由多級Source,Filter可以對上級Source進行處理。
- Sink 接收端, 流的終點,可理解爲是消費者。
數據流向簡單來說如下:
- Source 基於
FramedSource
,必須實現virtual void doGetNextFrame() = 0;
函數; - Filter基於
FramedFilter
,FramedFilter
實際上也是基於FramedSource
,Filter一般是會以上一級的Source作爲傳入參數,其實就是從上一級Source中獲取數據在進行處理。第三級、第四級也一樣; - Sink基於
MediaSink
,必須實現virtual Boolean continuePlaying() = 0;
函數。
具體如下:
Source的創建需要有GroupSock作爲參數,指明其讀取的socket。
例如:
udpSource = BasicUDPSource::createNew(*env, udpGroupsock);
rtpSource = SimpleRTPSource::createNew(*env, rtpGroupsock, 33, 90000, "video/MP2T", 0, False /*no 'M' bit*/);
如果還有Filter,就以Source爲參數,創建Filter,當然Filter也需要實現virtual void doGetNextFrame() = 0;
函數;在其中進行Filter的實現處理。以流媒體中最常見的MPEG2TransportStreamFramer爲例,在其自身的afterGettingFrame函數中做了一些TS對齊、解析等操作後,調用的上級Source的doGetNextFrame。
readSource= MPEG2TransportStreamFramer::createNew(*env, rtpSource);
Sink的創建需要Source 作爲參數,並調用startPlaying函數啓動,當啓動後,會調用continuePlaying。如下:
Boolean MediaSink::startPlaying(MediaSource& source,
afterPlayingFunc* afterFunc,
void* afterClientData) {
// Make sure we're not already being played:
if (fSource != NULL) {
envir().setResultMsg("This sink is already being played");
return False;
}
// Make sure our source is compatible:
if (!sourceIsCompatibleWithUs(source)) {
envir().setResultMsg("MediaSink::startPlaying(): source is not compatible!");
return False;
}
fSource = (FramedSource*)&source;
fAfterFunc = afterFunc;
fAfterClientData = afterClientData;
return continuePlaying();
}
要注意這裏面定義了fAfterFunc 就是最後播放結束調用的函數。
例子如下:
sink->startPlaying((FramedSource&)(*readSource), afterPlaying, sink);
continuePlaying中,最基本需要實現的操作就是從Source中讀取下一幀數據getNextFrame
,進而會調用到Source的doGetNextFrame
函數。
getNextFrame
中傳入的幾個參數,fBuffer是存放讀取數據的內存,fBufferSize是讀取的數據大小,afterGettingFrame是讀完一幀後的處理函數。
Boolean FileSink::continuePlaying() {
if (fSource == NULL) return False;
fSource->getNextFrame(fBuffer, fBufferSize,
afterGettingFrame, this,
onSourceClosure, this);
return True;
}
每種類型Source的doGetNextFrame
函數讀取每一幀數據都會有不同的處理,但最後,都會調用父類FramedSource的afterGetting(this);函數,最後調用到在continuePlaying實現中傳進來的獲取每一幀後的處理函數,例如上面的例子就是afterGettingFrame函數。
void FramedSource::afterGetting(FramedSource* source) {
source->fIsCurrentlyAwaitingData = False;
// indicates that we can be read again
// Note that this needs to be done here, in case the "fAfterFunc"
// called below tries to read another frame (which it usually will)
if (source->fAfterGettingFunc != NULL) {
(*(source->fAfterGettingFunc))(source->fAfterGettingClientData,
source->fFrameSize, source->fNumTruncatedBytes,
source->fPresentationTime,
source->fDurationInMicroseconds);
}
}
一般來說,afterGettingFrame函數會完成各種Sink定義的操作後(如播放幀數據),然後消費完這一幀後,又會調用continuePlaying函數,從而完成一個閉環。
例如:
void FileSink::afterGettingFrame(unsigned frameSize,
unsigned numTruncatedBytes,
struct timeval presentationTime) {
if (numTruncatedBytes > 0) {
envir() << "FileSink::afterGettingFrame(): The input frame data was too large for our buffer size ("
<< fBufferSize << "). "
<< numTruncatedBytes << " bytes of trailing data was dropped! Correct this by increasing the \"bufferSize\" parameter in the \"createNew()\" call to at least "
<< fBufferSize + numTruncatedBytes << "\n";
}
addData(fBuffer, frameSize, presentationTime);
if (fOutFid == NULL || fflush(fOutFid) == EOF) {
// The output file has closed. Handle this the same way as if the input source had closed:
if (fSource != NULL) fSource->stopGettingFrames();
onSourceClosure(this);
return;
}
if (fPerFrameFileNameBuffer != NULL) {
if (fOutFid != NULL) { fclose(fOutFid); fOutFid = NULL; }
}
// Then try getting the next frame:
continuePlaying();
}
最後,這樣Souce和Sink就形成一個閉環,不斷生產->消費->生產->消費…
TaskScheduler和BasicTaskScheduler
這兩個是Live555中任務調度的模塊,TaskScheduler定義了接口,BasicTaskScheduler繼承BasicTaskScheduler0,BasicTaskScheduler0繼承TaskScheduler。
在sink調用startPlaying後,最後必須調用TaskScheduler的doEventLoop,來讓整個程序跑起來。
介紹重要的函數及概念:
BasicTaskScheduler0中doEventLoop
爲程序循環函數,實際看SingleStep
中的實現。
void BasicTaskScheduler0::doEventLoop(char volatile* watchVariable) {
// Repeatedly loop, handling readble sockets and timed events:
while (1) {
if (watchVariable != NULL && *watchVariable != 0) break;
SingleStep();
}
}
SingleStep
中根據任務的類別,分成三類的來處理,每一次循環都會按照下面順序來完成調用。
1、首先處理的是Socket-Event。
負責I/O複用,使用select函數等待指定的描述字準備好讀、寫或有異常條件處理。若select返回值大於-1,則轉到相應的處理函數;否則表明發生異常,程序將轉到錯誤處理代碼中去。該類型適合於有I/O操作的任務。
像UDP/RTP Source從Socket的讀取便是這種類型。
相關函數:
turnOnBackgroundReadHandling //加入到後臺IO
setBackgroundHandling //加入到後臺IO
disableBackgroundHandling //禁止後臺IO,即清空
moveSocketHandling //移除指定後臺IO
2、接着是處理觸發器事件(Trigger-Event)。
Live555定義了一個32位的位圖來實現觸發事件,當某一位設置爲1則表明要觸發該位對應的事件。若同時有多個(3個或以上)觸發事件,它們觸發的先後還會跟事件創建的先後有關,因此這一類型僅適合於沒有順序依賴關係的任務。
使用方法:
Trigger-Event的使用需先創建一個EventTrigger,指定其處理函數,需要觸發時,調用triggerEvent。如:
testEventID = scheduler->createEventTrigger(testEventHandler);
void testEventHandler(void* user_data)
{
ALOGD("%s\n", __FUNCTION__);
}
scheduler->triggerEvent(testEventID , this);
相關函數:
createEventTrigger //創建觸發器
deleteEventTrigger //刪除觸發器
triggerEvent //觸發
3、最後一個是定時任務(Delayed Task)。
它是一個帶有時間的任務。當剩餘時間不爲0,則任務不執行。通過調整任務的剩餘時間,可以靈活地安排任務。
使用方法:
創建一個定時任務,給定時間及處理函數即可,如:
if (scs.duration > 0) {
unsigned const delaySlop = 2; // number of seconds extra to delay, after the stream's expected duration. (This is optional.)
scs.duration += delaySlop;
unsigned uSecsToDelay = (unsigned)(scs.duration*1000000);
scs.streamTimerTask = env.taskScheduler().scheduleDelayedTask(uSecsToDelay, (TaskFunc*)streamTimerHandler, rtspClient);
}
void streamTimerHandler(void* clientData) {
ourRTSPClient* rtspClient = (ourRTSPClient*)clientData;
StreamClientState& scs = rtspClient->scs; // alias
scs.streamTimerTask = NULL;
// Shut down the stream:
shutdownStream(rtspClient);
}
相關函數:
scheduleDelayedTask //添加定時任務
unscheduleDelayedTask //移除定時任務
rescheduleDelayedTask //重置定時任務
BasicTaskScheduler0實現以及觸發事件和延遲任務。
BasicTaskScheduler類實現剩下的I/O操作任務接口和三類任務的實際調度(singleStep函數)。
總結
至此,我們瞭解了Live555的基本模塊使用,那麼接下來進入主題,RTSPClient的分析