在做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去獲取數據。