Live555 RTSP播放分析(一)--基本模塊介紹

以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基於FramedFilterFramedFilter實際上也是基於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的分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章