在做rtsp流媒体传输过程,所以去了解了live555的客户端demo,testRTSPClient。
为了解testRTSPClient,需要大致清楚rtsp的协议,配合抓包知道RTSP的流程,便于我们清晰的了解testRTSPClient的整个过程。抓包使用wireshark。
抓包分析可以参考https://www.jianshu.com/p/409f20b7e813。
先看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的代码进行分析。
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
...
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
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
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去进行下一个子会话的创建
...
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
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去获取数据。