LIVE555-媒體流建立(3)

前一篇文章講解了live555的RTSP的創建流程,接下來我將對流媒體的創建過程進行講解。

目錄

1.基本概念瞭解

2.OPTIONS請求

3.DESCRIBE請求

4.SETUP請求

5.PLAY請求



1.基本概念瞭解

在講解這些之前,咱們先熟悉一下live555中的一些基本類的概念和相互之間的關係:

MediaServer:媒體服務器。一般每個程序中只有一個媒體服務器,該對象是我們運行程序的最高長官,把控全局,其內部記錄了所有的clientsession、CllientConnection和ServerMediaSession等對象,其派生了很多子類,例如上一章的rtspServer、以及在範例中自定義的DynamicRTSPServer等等。

ClientConnection:客戶端連接對象。通過上一篇文章,可以知道每當客戶端連接上服務器後就會創建一個連接對象,負責與客戶端之間的數據交互,此處不在贅述,詳細可以參見上篇文字(live555- RTSP創建)。

ServerMediaSession(簡稱:SMS):流媒體會話。當客戶端請求播放某個流媒體(test.ts)時,live555就會查找此媒體會話,若不存在則創建新的流媒體會話。一個媒體(文件或直播碼流)對應這一個SMS,SMS在MediaServer中是通過流媒體名稱進行區分的,所以每個流媒體文件只有一個SMS,負責處理多個客戶端的流媒體發送,每個會話中有一個或多個ServerMediaSubSession(通道),實際負責處理媒體流數據傳輸的是subSession,SMS相當於一個外殼容器。

ServerMediaSubSession:流媒體子會話(通道)。流媒體子會話可以理解爲媒體傳輸通道,例如視頻通道、音頻通道等,一個SMS中可能包含多個媒體通道。每個媒體通道負責數據的傳輸控制。

StreamState:媒體流。媒體流包含媒體發送和包裝的RTPsink,以及讀取媒體文件或碼流的source,媒體流保存在clientsession中,一個subsession的通道下可能存在多個媒體流(一個客戶端會話對應一個媒體流),也可以設置多個客戶端使用一個媒體流。

ClientSession:客戶端會話。每當客戶端與媒體服務器建立連接(SETUP)後,就會創建一個會話對象,當媒體流傳輸完成後就會銷燬此對象。與網絡中的會話機制類似,用戶登錄後就創建會話,直到登出會話結束。

客戶端會話有一個獨一無二的sessionId,以確保此會話獨一無二,後續與客戶端交互都會帶着Sessionid。

客戶端會話保存着此會話中的媒體流(StramState),通過媒體流就行流媒體產生和推送。

 


ok 熟悉了上面的概念後,咱們就可以繼續進行媒體流創建。後面以VLC作爲客戶端請求test.264文件,通過wireshark抓包分析媒體流的創建過程。

2.OPTIONS請求

首先客戶端發起options請求,詢問服務器支持那些請求命令,

客戶端請求命令抓包截圖如下:

服務器收到信息後進入TSPServer::RTSPClientConnection::handleRequestBytes()函數中,主要進行以下幾步:

1)解析請求字符串內容。

2)獲得請求命令字符串OPTIONS.

3)進入OPTIONS命令處理函數RTSPClientConnection::handleCmd_OPTIONS(),獲得應答字符串。

4)發送應答命令。

服務器應答命令抓包如下:

3.DESCRIBE請求

describe請求從服務器獲取流媒體文件格式信息和傳輸信息,

客戶端請求命令抓包如下:

服務器收到信息後進入TSPServer::RTSPClientConnection::handleRequestBytes()函數中,主要進行以下幾步:

1)解析請求命令DESCRIBE,進入函數RTSPClientConnection::handleCmd_DESCRIBE(),代碼簡化如下:

RTSPClientConnection:handleCmd_DESCRIBE(char const* urlPreSuffix, 
char const* urlSuffix, char const* fullRequestStr) 
{
	ServerMediaSession* session = NULL;
    //! 1.查找對應的SMS,不存在則終止
	session = fOurServer.lookupServerMediaSession(urlTotalSuffix);
	
    //! 2.生成對應的SDP信息,不存在則種植
       sdpDescription = session->generateSDPDescription();
  
    //! 3.拼裝應答語句
	snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,...)
}

在DynamicRTSPServer就是重載了lookupServerMediaSession函數,根據不同的媒體流文件創建對應的媒體流會話(SMS),從而實現文件rtsp服務器,找到SMS後就是通過SMS獲取SDP描述。其中generateSDPDescription函數主要用於拼裝SDP信息,至於SDP大家可以自行了解,需要關注的是此函數調用了 subsession->sdpLines()產生部分SDP信息。下面列出此函數簡化代碼:

char const* OnDemandServerMediaSubsession::sdpLines() {
    //1.創建臨時的源對象
    unsigned estBitrate;
    FramedSource* inputSource = createNewStreamSource(0, estBitrate);
    
    //2.創建臨時的sink對象
    RTPSink* dummyRTPSink = createNewRTPSink(dummyGroupsock, rtpPayloadType, inputSource);
  
    //!3.獲取SDP信息
    setSDPLinesFromRTPSink(dummyRTPSink, inputSource, estBitrate);

    //!4.刪除臨時的source和sink對象
    Medium::close(dummyRTPSink);
    delete dummyGroupsock;
    closeStreamSource(inputSource);
}

此處主要注意的是此處創建的source和sink只是臨時的,用完後會立刻刪除,主要用於生成SDP信息。

 OK,生成SDP信息後,返回describe應答。

  • 服務器應答命令如下:

從上圖可以看到contorl爲通道track1, 其餘sdp信息自行學習。 

4.SETUP請求

setup請求,用於與服務器建立會話,確定數據流傳輸方式。

客戶端請求令如下:

可以看到此處與前面幾個命令不同之處是uri增加了通道信息track1。Transport參數設置了傳輸模式,包的結構:UDP傳輸,單播,以及RTP和RTCP客戶端所有端口。請求命令講解到此,接下來是服務器的響應操作:

服務器收到信息後進入TSPServer::RTSPClientConnection::handleRequestBytes()函數中,主要進行以下幾步:

1)在rtspServrer中查找clientsession,若未找到則創建CLientSession,其中每個ClientSession會創建一個對應的SessionId,其在rtspServrer中是唯一的,然後跳轉到ClientSession的函數handleCmd_SETUP中。

2)接下來我們分析ClientSession的函數handleCmd_SETUP的函數

void RTSPClientSession::handleCmd_SETUP(RTSPServer::RTSPClientConnection* ourClientConnection, char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) 
{
	//1.根據流名 查找SMS
       fOurServerMediaSession = fOurServer.lookupServerMediaSession(streamName, 
                           fOurServerMediaSession == NULL);
	
	//!2.將SMS中所有通道記錄到結構體streamState數組中
	fNumStreamStates = fOurServerMediaSession->numSubsessions();
	fStreamStates = new struct streamState[fNumStreamStates];

	ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
	ServerMediaSubsession* subsession;
	for (unsigned i = 0; i < fNumStreamStates; ++i)
       {
		subsession = iter.next();
		fStreamStates[i].subsession = subsession;
		fStreamStates[i].tcpSocketNum = -1; 
		fStreamStates[i].streamToken = NULL; //在getStreamParameters賦值
	}
	

	//3.根據通道信息查找對應的SUbsussesion和trackNum
	ServerMediaSubsession* subsession = ....
	unsigned trackNum ....

         //4.繼續解析報文,clientPort,udp或tcp,是否創建後就播放等信息
	.........此處主要描述UDP創建的RTP,
		Port clientRTPPort(clientRTPPortNum);
		Port clientRTCPPort(clientRTCPPortNum);

        //5.通過subsession創建對應的媒體流
	subsession->getStreamParameters(fOurSessionId, ourClientConnection->
            fClientAddr.sin_addr.s_addr,
			clientRTPPort, clientRTCPPort,
			fStreamStates[trackNum].tcpSocketNum, rtpChannelId, rtcpChannelId,
			destinationAddress, destinationTTL, fIsMulticast,
			serverRTPPort, serverRTCPPort,
			fStreamStates[trackNum].streamToken);
	
        //6.拼裝應答文本
}

通過上述代碼及本文開頭類圖,可以看出其中存在一個streamState結構體和StreamState類,注意區分,在streamState結構體中的一個成員streamToken指向StreamState對象指針,結構體聲明如下:

  struct streamState {
      ServerMediaSubsession* subsession;
      int tcpSocketNum;
      void* streamToken;
    } * fStreamStates;

言歸正傳,clientSession在解析handleCmd_SETUP函數中,首先找到對應的sms和subsession,然後通過subsession的函數getStreamParameters()對媒體流信息進行賦值(創建),可以看到該函數在基類中是純虛函數,真正的實現實在子類中實現

接下來進入OnDemandServerMediaSubsession的重載函數,其簡化代碼如下所示:

void OnDemandServerMediaSubsession
::getStreamParameters(unsigned clientSessionId,
		      netAddressBits clientAddress,
		      Port const& clientRTPPort,
		      Port const& clientRTCPPort,
		      int tcpSocketNum,
		      unsigned char rtpChannelId,
		      unsigned char rtcpChannelId,
		      netAddressBits& destinationAddress,
		      u_int8_t& /*destinationTTL*/,
		      Boolean& isMulticast,
		      Port& serverRTPPort,
		      Port& serverRTCPPort,
		      void*& streamToken) {

//!1.如果重用流則直接返回上次的流對象的信息
  if (fLastStreamToken != NULL && fReuseFirstSource) {
    // Special case: Rather than creating a new 'StreamState',
    // we reuse the one that we've already created:
    serverRTPPort = ((StreamState*)fLastStreamToken)->serverRTPPort();
    serverRTCPPort = ((StreamState*)fLastStreamToken)->serverRTCPPort();
    ++((StreamState*)fLastStreamToken)->referenceCount();
    streamToken = fLastStreamToken;
  } 
  else {
  //!2.創建此ClientSession的Source
    FramedSource* mediaSource
      = createNewStreamSource(clientSessionId, streamBitrate);

    //!3.創建SINk
	rtpSink = createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource);
	if (rtpSink != NULL && rtpSink->estimatedBitrate() > 0) streamBitrate = rtpSink->estimatedBitrate();
      }


    // 4.創建媒體流StreamState
    streamToken = fLastStreamToken
      = new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink,
			streamBitrate, mediaSource,
			rtpGroupsock, rtcpGroupsock);
}
}

 通過上述代碼可以看出,ClientSession中保存着對應流媒體的stramState數組,其中Clientsession使用的通道streamState.streamToken對象是隸屬於此Clientsession會話的,如果未設置公用則,沒個會話都會創建一個Source,一個SINK,一個StreamState(其中記錄source和SINK)信息,至於sink和source我會在後續文章中講解。

創建媒體流後,就基本完成setup信息了,回退到上一個函數進行應答信息拼裝。

服務器應答命令如下:

Transport:說明UDP方式,非多播,目標地址,源地址以及RTP、RTCP的客戶端端口和服務的端口

Session:ClientSessionID和超時信息。

5.PLAY請求

play請求,用於請求服務器播放視頻,開始媒體流傳輸。

客戶端請求命令如下:

可以看到客戶端發送PLAY命令中帶着sessionid信息,Range是播放時間段。

服務器收到信息後進入TSPServer::RTSPClientConnection::handleRequestBytes()函數中,主要進行以下幾步:

1)在rtspServrer中查找clientsession,然後跳轉到ClientSession的函數handleCmd_PLAY中。

2)在handleCmd_PLAY函數中,首先查找對應的媒體流,進行播放範圍的偏移,然後調用fStreamStates[i].subsession->startStream開始播放,最終跳轉道void StreamState::startPlaying函數中,其代碼如下:

void StreamState
::startPlaying(Destinations* dests, unsigned clientSessionId,
	       TaskFunc* rtcpRRHandler, void* rtcpRRHandlerClientData,
	       ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
	       void* serverRequestAlternativeByteHandlerClientData) {
  if (dests == NULL) return;

  //!1.創建RTCP實例
    fRTCPInstance = fMaster.createRTCP(fRTCPgs, fTotalBW, (unsigned char*)fMaster.fCNAME, fRTPSink);
        // Note: This starts RTCP running automatically
    fRTCPInstance->setAppHandler(fMaster.fAppHandlerTask, fMaster.fAppHandlerClientData);
  }

   //!2.TCP處理
 
  
   //!3.設置目標地址
   
   //!4.發送RTCP信息
  if (fRTCPInstance != NULL) {
    // get RTCP-synchronized presentation times immediately:
    fRTCPInstance->sendReport();


    //!5.開始播放媒體流
      fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this);
      fAreCurrentlyPlaying = True;

}

PRTsink播放視頻不再本文講述之內,後續文章會繼續講解,至此服務器就開始推流了,後面快進、停止等命令就不在詳解了基本一致,最後列出play的應答命令。

服務器應答命令如下:

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