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去获取数据。

 

 

 

 

 

 

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