live555的rtsp客戶端testRTSPClient代碼分析

在做rtsp流媒體傳輸過程,所以去了解了live555的客戶端demo,testRTSPClient。

爲了解testRTSPClient,需要大致清楚rtsp的協議,配合抓包知道RTSP的流程,便於我們清晰的瞭解testRTSPClient的整個過程。抓包使用wireshark。

抓包分析可以參考https://www.jianshu.com/p/409f20b7e813

 

testRTSPClient代碼分析

先看main函數主要代碼

  TaskScheduler* scheduler = BasicTaskScheduler::createNew();
  UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);

  for (int i = 1; i <= argc-1; ++i) {
    openURL(*env, argv[0], argv[i]);
  }

  env->taskScheduler().doEventLoop(&eventLoopWatchVariable);

1.建立環境

2.循環讀取進來的url,進行處理

3.進入事件循環

 

重點需要了解的是每個url的處理,url裏主要是發送DESCRIBE、PLAY等指令和設置對應的回調函數。開始播放後使用DummySink來接收處理數據。

以下就openURL的代碼進行分析。

openURL主要代碼

void openURL(UsageEnvironment& env, char const* progName, char const* rtspURL) {
  // Begin by creating a "RTSPClient" object.  Note that there is a separate "RTSPClient" object for each stream that we wish
  // to receive (even if more than stream uses the same "rtsp://" URL).
  RTSPClient* rtspClient = ourRTSPClient::createNew(env, rtspURL, RTSP_CLIENT_VERBOSITY_LEVEL, progName);
  if (rtspClient == NULL) {
    env << "Failed to create a RTSP client for URL \"" << rtspURL << "\": " << env.getResultMsg() << "\n";
    return;
  }

  ++rtspClientCount;
  rtspClient->sendDescribeCommand(continueAfterDESCRIBE); 
}

創建rtsp客戶端

異步發送DESCRIBE指令,設置得到迴應後的回調函數 continueAfterDESCRIBE

 

continueAfterDESCRIBE:

...
StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
...
scs.session = MediaSession::createNew(env, sdpDescription);
...
scs.iter = new MediaSubsessionIterator(*scs.session);
setupNextSubsession(rtspClient);

對應得到DESCRIBE指令回覆後的操作:

根據SDP創建會話 MediaSession

判斷MediaSession是否有子會話,有就setupNextSubsession創建音視頻對應的子會話,沒有就關閉這個client

 

setupNextSubsession:

scs.subsession = scs.iter->next();
  if (scs.subsession != NULL) {
    if (!scs.subsession->initiate()) {
      env << *rtspClient << "Failed to initiate the \"" << *scs.subsession << "\" subsession: " << env.getResultMsg() << "\n";
      setupNextSubsession(rtspClient); // give up on this subsession; go to the next one
    } else {
      env << *rtspClient << "Initiated the \"" << *scs.subsession << "\" subsession (";
      if (scs.subsession->rtcpIsMuxed()) {
	env << "client port " << scs.subsession->clientPortNum();
      } else {
	env << "client ports " << scs.subsession->clientPortNum() << "-" << scs.subsession->clientPortNum()+1;
      }
      env << ")\n";

      // Continue setting up this subsession, by sending a RTSP "SETUP" command:
      rtspClient->sendSetupCommand(*scs.subsession, continueAfterSETUP, False, REQUEST_STREAMING_OVER_TCP);
    }
    return;
  }

//所有subsession創建完後到這裏
  if (scs.session->absStartTime() != NULL) {
    // Special case: The stream is indexed by 'absolute' time, so send an appropriate "PLAY" command:
    rtspClient->sendPlayCommand(*scs.session, continueAfterPLAY, scs.session->absStartTime(), scs.session->absEndTime());
  } else {
    scs.duration = scs.session->playEndTime() - scs.session->playStartTime();
    rtspClient->sendPlayCommand(*scs.session, continueAfterPLAY);
  }
    

initiate 初始化子會話,打印使用的端口

發送Setup指令,設置得到迴應後的回調函數continueAfterSETUP,返回

(continueAfterSETUP完成後又會回到setupNextSubsession,去創建下一個Subsession)

所有的Subsession完成後,由scs.subsession爲空,執行後面的語句發送Play指令 sendPlayCommand

 

continueAfterSETUP:

do {
...
    scs.subsession->sink = DummySink::createNew(env, *scs.subsession, rtspClient->url());
...
    env << *rtspClient << "Created a data sink for the \"" << *scs.subsession << "\" subsession\n";
    scs.subsession->miscPtr = rtspClient; // a hack to let subsession handler functions get the "RTSPClient" from the subsession 
    scs.subsession->sink->startPlaying(*(scs.subsession->readSource()),
				       subsessionAfterPlaying, scs.subsession);
    // Also set a handler to be called if a RTCP "BYE" arrives for this subsession:
    if (scs.subsession->rtcpInstance() != NULL) {
      scs.subsession->rtcpInstance()->setByeWithReasonHandler(subsessionByeHandler, scs.subsession);
    }
} while (0);
...
setupNextSubsession(rtspClient);

對應得到Setup指令的回覆後的操作:

創建一個繼承MediaSink的DummySink,爲subsession創建sink

調用sink的startPlaying函數,這個讓sink去準備好接收數據,真正的音視頻數據會等到發送RTSP的Play指令纔開始(可能對應抓包裏的發送的兩個無意義的數據包)

設置會話結束的回調函數subsessionAfterPlaying

設置收到BYE時的回調函數 subsessionByeHandler

調用setupNextSubsession去進行下一個子會話的創建

 

subsessionAfterPlaying:

... 
  Medium::close(subsession->sink);
  subsession->sink = NULL;

...
  while ((subsession = iter.next()) != NULL) {
    if (subsession->sink != NULL) return; // this subsession is still active
  }

  // All subsessions' streams have now been closed, so shutdown the client:
  shutdownStream(rtspClient);

對應播放結束的操作:

關閉subsession的sink

所有的sink關閉則關閉client

 

DummySink

class DummySink: public MediaSink

DummySink是負責接收subssion的數據,重寫了虛函數 continuePlaying

startPlaying後,continuePlaying會被調用,continuePlaying會調用 getNextFrame 去請求得到下一幀數據

  fSource->getNextFrame(fReceiveBuffer, DUMMY_SINK_RECEIVE_BUFFER_SIZE,
                        afterGettingFrame, this,
                        onSourceClosure, this);

getNextFrame獲取數據,調用靜態的afterGettingFrame,再去afterGettingFrame裏對數據處理,完成後接着調用continuePlaying去獲取數據。

 

 

 

 

 

 

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