live555源码分析(二):testRTSPClient分析

流程概述

1)创建任务对象(TaskScheduler);

2)创建环境对象(UsageEnvironment);

3)处理用户输入的参数(RTSP地址记录)

4)创建RTSPClient实例(RTSPClient);

5)发出第一个RTSP请求(DESCRIBE);

6)进入事件循环(doEventLoop);

源码分析

建立任务对象

TaskScheduler则提供了任务调度功能。主要负责调度任务,执行任务(任务就是一个函数)。TaskScheduler由于在全局中只有一个(单例),保存在UsageEnvironment中。

TaskScheduler* scheduler = BasicTaskScheduler::createNew();

创建环境对象

BasicUsageEnvironment和UsageEnvironment中的类都是用于整个系统的基础功能类.比如UsageEnvironment代表了整个系统运行的环境,它提供了错误记录和错误报告的功能,无论哪一个类要输出错误,就需要保存UsageEnvironment的指针。

UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);  // scheduler作为参数传入

日志记录示例:

UsageEnvironment& env = rtspClient->envir();

if (resultCode != 0) {
    env << *rtspClient << "Failed to get a SDP description: " << resultString << "\n";
    delete[] resultString;
    break;
}

处理用户输入的参数

主要是记录用户信息。

void usage(UsageEnvironment& env, char const* progName) {
  env << "Usage: " << progName << " <rtsp-url-1> ... <rtsp-url-N>\n";
  env << "\t(where each <rtsp-url-i> is a \"rtsp://\" URL)\n";
}

创建RTSPClient实例

注意:RTSPClient实例在全局中只有一个。

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;
  }

RTSPClient的构造中主要做了一件工作:如果已经与RTSPServer建立连接,则设置事件回调,处理事件响应。

if (socketNumToServer >= 0) {
    fInputSocketNum = fOutputSocketNum = socketNumToServer;
    env.taskScheduler().setBackgroundHandling(fInputSocketNum, SOCKET_READABLE|SOCKET_EXCEPTION,
						  (TaskScheduler::BackgroundHandlerProc*)&incomingDataHandler, this);
  }

发出第一个RTSP请求

自此开启rtsp的信令交互流程。

rtspClient->sendDescribeCommand(continueAfterDESCRIBE); 

发送请求是由虚函数sendRequest来完成的,sendRequest的参数RequestRecord类包装了不同的请求命令。例如:

// responseHandler是一个回调函数, 发送请求成功后将结果返回给上层调用
unsigned RTSPClient::sendDescribeCommand(responseHandler* responseHandler, Authenticator* authenticator) {
  if (fCurrentAuthenticator < authenticator) fCurrentAuthenticator = *authenticator;
  return sendRequest(new RequestRecord(++fCSeq, "DESCRIBE", responseHandler));
}

sendRequest主要完成的工作是组装并发送命令:

unsigned RTSPClient::sendRequest(RequestRecord* request) {
 	...
        
    // write来发送组装后的命令    
    if (write(cmd, strlen(cmd)) < 0) {
      char const* errFmt = "%s write() failed: ";
      unsigned const errLength = strlen(errFmt) + strlen(request->commandName());
      char* err = new char[errLength];
      sprintf(err, errFmt, request->commandName());
      envir().setResultErrMsg(err);
      delete[] err;
      break;
    }
    
    ...
}

// 通过socket发送数据
int RTSPClient::write(const char* data, unsigned count) {
      if (fTLS.isNeeded) {
	return fTLS.write(data, count);
      } else {
	return send(fOutputSocketNum, data, count, 0);
      }
}

来看一下发送并响应成功后的处理,这里以continueAfterDESCRIBE为例分析:

void continueAfterDESCRIBE(RTSPClient* rtspClient, int resultCode, char* resultString) {
  do {
	...

    // 根据返回的sdp信息创建一个媒体会话
    scs.session = MediaSession::createNew(env, sdpDescription);
    
    // 根据媒体会话创建媒体子会话
    scs.iter = new MediaSubsessionIterator(*scs.session);
    setupNextSubsession(rtspClient);
    return;
  } while (0);

  // 出错关闭流
  shutdownStream(rtspClient);
}

说明:MediaSession是流媒体会话,MediaSession和MediaSubsession共同用于执行流媒体传输和状态维护。

创建完会话后,执行setupNextSubsession。setupNextSubsession主要做了一个判断,子会话有没有initiate成功,如果没有成功,发送SETUP命令;如果建立成功了,则发送PLAY命令请求取流。

void setupNextSubsession(RTSPClient* rtspClient) {
  UsageEnvironment& env = rtspClient->envir(); // alias
  StreamClientState& scs = ((ourRTSPClient*)rtspClient)->scs; // alias
  
  scs.subsession = scs.iter->next();
  if (scs.subsession != NULL) {
    if (!scs.subsession->initiate()) {
      // 通过发送SETUP命令建立子会话
      rtspClient->sendSetupCommand(*scs.subsession, continueAfterSETUP, False, REQUEST_STREAMING_OVER_TCP);
    }
    return;
  }

  // 如果已经建立了子会话 则发送PLAY命令
  if (scs.session->absStartTime() != NULL) {
    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到底做了什么事情呢?来看下它的实现:

Boolean MediaSubsession::initiate(int useSpecialRTPoffset) {
  if (fReadSource != NULL) return True; // has already been initiated

  do {
    ...
      
      if (isSSM()) {
	fRTPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, fClientPortNum);
      } else {
	fRTPSocket = new Groupsock(env(), tempAddr, fClientPortNum, 255);
      }
      if (fRTPSocket == NULL) {
	env().setResultMsg("Failed to create RTP socket");
	break;
      }
      
      if (protocolIsRTP) {
	if (fMultiplexRTCPWithRTP) {
	  // Use the RTP 'groupsock' object for RTCP as well:
	  fRTCPSocket = fRTPSocket;
	} else {
	  // Set our RTCP port to be the RTP port + 1:
	  portNumBits const rtcpPortNum = fClientPortNum|1;
	  if (isSSM()) {
	    fRTCPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr, rtcpPortNum);
	  } else {
	    fRTCPSocket = new Groupsock(env(), tempAddr, rtcpPortNum, 255);
	  }
	}
      }
    } 
    
    ...
    
    // Create "fRTPSource" and "fReadSource":
    if (!createSourceObjects(useSpecialRTPoffset)) break;

    if (fReadSource == NULL) {
      env().setResultMsg("Failed to create read source");
      break;
    }
    
    SRTPCryptographicContext* ourCrypto = NULL;
    if (useSRTP) {
      // For SRTP, we need key management.  If MIKEY (key management) state wasn't given
      // to us in the SDP description, then create it now:
      ourCrypto = getCrypto();
      if (ourCrypto == NULL) { // then fMIKEYState is also NULL; create both
	fMIKEYState = new MIKEYState();
	ourCrypto = fCrypto = new SRTPCryptographicContext(*fMIKEYState);
      }

      if (fRTPSource != NULL) fRTPSource->setCrypto(ourCrypto);
    }

    // Finally, create our RTCP instance. (It starts running automatically)
    if (fRTPSource != NULL && fRTCPSocket != NULL) {
      // If bandwidth is specified, use it and add 5% for RTCP overhead.
      // Otherwise make a guess at 500 kbps.
      unsigned totSessionBandwidth
	= fBandwidth ? fBandwidth + fBandwidth / 20 : 500;
      fRTCPInstance = RTCPInstance::createNew(env(), fRTCPSocket,
					      totSessionBandwidth,
					      (unsigned char const*)
					      fParent.CNAME(),
					      NULL /* we're a client */,
					      fRTPSource,
					      False /* we're not a data transmitter */,
					      ourCrypto);
      if (fRTCPInstance == NULL) {
	env().setResultMsg("Failed to create RTCP instance");
	break;
      }
    }

    return True;
  } while (0);

  deInitiate();
  fClientPortNum = 0;
  return False;
}

代码有点长,分析下来主要做了两件事:

1)创建RTP和RTCP连接(RTPSocket和RTCPSocket);

2)创建RTP资源和RTCP资源(RTPSource和RTCPSource);

这里也可以看出,RTP和RTCP的连接是在DESCRIBE之后完成的。而Source可以理解为生产者,对应的Sink可以理解为消费者。

接着我们来看下SETUP响应成功之后做了什么。

void continueAfterSETUP(RTSPClient* rtspClient, int resultCode, char* resultString) {
  do {
    scs.subsession->sink = DummySink::createNew(env, *scs.subsession, rtspClient->url());
      break;
    }

    scs.subsession->miscPtr = rtspClient; 
    scs.subsession->sink->startPlaying(*(scs.subsession->readSource()),
				       subsessionAfterPlaying, scs.subsession);
    
    if (scs.subsession->rtcpInstance() != NULL) {
      scs.subsession->rtcpInstance()->setByeWithReasonHandler(subsessionByeHandler, scs.subsession);
    }
  } while (0);
  delete[] resultString;

  // Set up the next subsession, if any:
  setupNextSubsession(rtspClient);
}

这里主要做了两个工作:

1)创建Sink(即上层消费者,可以理解为接收流的人,DummySink为客户端实现);

2)开始请求流;

流数据接收处理类

上面有介绍到在开始取流之前创建了DummySink类用来接收流数据,来看一下DummySink是怎么实现的。

DummySink* DummySink::createNew(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId) {
  return new DummySink(env, subsession, streamId);
}

DummySink::DummySink(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId)
  : MediaSink(env),
    fSubsession(subsession) {
  fStreamId = strDup(streamId);
  fReceiveBuffer = new u_int8_t[DUMMY_SINK_RECEIVE_BUFFER_SIZE];
}

DummySink::~DummySink() {
  delete[] fReceiveBuffer;
  delete[] fStreamId;
}

// 获取流数据
void DummySink::afterGettingFrame(void* clientData, unsigned frameSize, unsigned numTruncatedBytes,
				  struct timeval presentationTime, unsigned durationInMicroseconds) {
  DummySink* sink = (DummySink*)clientData;
  sink->afterGettingFrame(frameSize, numTruncatedBytes, presentationTime, durationInMicroseconds);
}

#define DEBUG_PRINT_EACH_RECEIVED_FRAME 1

void DummySink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
				  struct timeval presentationTime, unsigned /*durationInMicroseconds*/) {
  continuePlaying();
}

Boolean DummySink::continuePlaying() {
  if (fSource == NULL) return False; 
    
  // fSource是维护在MediaSink中的FramedSource类的对象
  fSource->getNextFrame(fReceiveBuffer, DUMMY_SINK_RECEIVE_BUFFER_SIZE,
                        afterGettingFrame, this,
                        onSourceClosure, this);
  
  return True;
}

DummySink继承于MediaSink类,通过afterGettingFrame函数来接收一次数据回调。 MediaSink保存了FramedSource* fSource; 的指针,可以通过其来获取下一帧数据。

进入事件循环

env->taskScheduler().doEventLoop(&eventLoopWatchVariable);
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值