live555源码分析(六)SETUP和PLAY请求的处理

live555源码分析系列

live555源码分析(一)live555初体验

live555源码分析(二)基本组件上

live555源码分析(三)基本组件下

live555源码分析(四)RTSPServer分析

live555源码分析(五)DESCRIBE请求的处理

live555源码分析(六)SETUP和PLAY请求的处理

live555源码分析(七)播放过程

live555源码分析(八)多播

live555源码分析(六)SETUP和PLAY请求的处理

文章目录

  • live555源码分析(六)SETUP和PLAY请求的处理
    • 一、RTSP的交互过程
    • 二、源码分析
      • 2.1 SETUP
      • 2.2 PLAY

一、RTSP的交互过程

本文将分析SETUP和PLAY请求的处理过程,首先看一看RTSP的SETUP和PLAY请求交互信息

SETUP

  • C–>S

    SETUP rtsp://192.168.31.115:8554/live/track0 RTSP/1.0\r\n
    CSeq: 4\r\n
    Transport: RTP/AVP;unicast;client_port=54492-54493\r\n
    \r\n
    

    客户端会向服务器发起SETUP请求,指定RTP的传输方式(RTP/AVP),是通过UDP还是通过TCP,指定单播或者多播(unicast),发送客户端RTP和RTCP端口(client_port=54492-54493)

  • S–>C

    RTSP/1.0 200 OK\r\n
    CSeq: 4\r\n
    Transport: RTP/AVP;unicast;client_port=54492-54493;server_port=56400-56401\r\n
    Session: 66334873\r\n
    \r\n
    

    服务器会回复客户端,通知服务端的RTP和RTCP端口(server_port=56400-56401),并会为客户端创建一个会话连接,会话ID为(Session: 66334873)

PLAY

  • C–>S

    PLAY rtsp://192.168.31.115:8554/live RTSP/1.0\r\n
    CSeq: 5\r\n
    Session: 66334873\r\n
    Range: npt=0.000-\r\n
    \r\n
    

    指定要播放的会话

  • S–>C

    RTSP/1.0 200 OK\r\n
    CSeq: 5\r\n
    Range: npt=0.000-\r\n
    Session: 66334873; timeout=60\r\n
    \r\n
    

    在play请求回复后,就会开始播放

二、源码分析

2.1 SETUP

接下来分析live555是如何处理SETUP请求的

live555的RTSPClientConnection::handleRequestBytes函数是处理rtsp请求,其定义如下

void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
	/* 解析命令 */
	...
    
	else if (strcmp(cmdName, "SETUP") == 0) {
        clientSession
        	= (RTSPServer::RTSPClientSession*)fOurRTSPServer.createNewClientSessionWithId();

 		clientSession->handleCmd_SETUP(...);   
     
    }
    ...
}

首先解析请求命令,如果是SETUP请求的话,那么就为客户端创建一个客户端会话(clientSession),然后再用客户端会话来处理SETUP请求

RTSPClientSession定义的一系列的在SETUP请求之后的请求处理函数,如下

class RTSPClientSession: public GenericMediaServer::ClientSession {
    ...
protected:
    virtual void handleCmd_SETUP(...);
    virtual void handleCmd_PLAY(...);
    virtual void handleCmd_PAUSE(...);
    ...
};

接下来分析handleCmd_SETUP函数,看是如何处理客户端的SETUP请求的

void RTSPServer::RTSPClientSession
::handleCmd_SETUP(RTSPServer::RTSPClientConnection* ourClientConnection,
		  char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) {
    ...
	ServerMediaSession* sms
        = fOurServer.lookupServerMediaSession(streamName, fOurServerMediaSession == NULL);
    
    ...
    fNumStreamStates = fOurServerMediaSession->numSubsessions(); //子会话的数量
    /* 为每一个子会话创建streamState */
    fStreamStates = new struct streamState[fNumStreamStates];
    for (unsigned i = 0; i < fNumStreamStates; ++i) {
    	fStreamStates[i].subsession = subsession;
    }
    
    ...
    /* 获取子会话 */
	subsession = fStreamStates[trackNum].subsession;
    ...
    subsession->getStreamParameters(...);
       
	...
	/* 产生回复信息 */
	snprintf(...
		     "RTSP/1.0 200 OK\r\n"
		     "CSeq: %s\r\n"
		     "%s"
		     "Transport: RTP/AVP;unicast;destination=%s;source=%s;"
             "client_port=%d-%d;server_port=%d-%d\r\n"
		     "Session: %08X%s\r\n\r\n",
		     ...);
}

首先根据url中的session字段,找到指定的会话

然后获取会话中子会话的数量,为每一个子会话创建一个streamState

看一看streamState的定义

struct streamState {
      ServerMediaSubsession* subsession; //子会话
      int tcpSocketNum;
      void* streamToken; //额外数据
} * fStreamStates;

subsession指向对应的子会话

tcpSocketNum只有在RTP OVER TCP的情况下才使用

streamToken用于指向额外的数据

继续分析handleCmd_SETUP函数

接下来会根据rtsp请求的urlrtsp://192.168.31.115:8554/live/track0中指定的trackn找到对应的子会话

再调用subsession->getStreamParameters(...)getStreamParameters函数是SETUP请求处理中比较重要的一个函数,其作用是RTSPClientSessionServerMediaSubsession之间传递数据

对于H264VideoFileServerMediaSubsession其定义如下

void OnDemandServerMediaSubsession
::getStreamParameters(unsigned clientSessionId,
		      netAddressBits clientAddress,
		      Port const& clientRTPPort,
		      Port const& clientRTCPPort,
		      int tcpSocketNum,
		      unsigned char rtpChannelId,
		      unsigned char rtcpChannelId,
		      netAddressBits& destinationAddress,
		      u_int8_t& /*destinationTTL*/,
		      Boolean& isMulticast,
		      Port& serverRTPPort,
		      Port& serverRTCPPort,
		      void*& streamToken) {
	
    	/* 创建数据源 */
    	FramedSource* mediaSource = 
            = createNewStreamSource(clientSessionId, streamBitrate);
    
    	/* 创建消费者 */
    	rtpSink = createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource);
    
    	...
		/* 服务端的RTP和RTCP端口 */
		serverRTPPort = serverPortNum;
    	serverRTCPPort = ++serverPortNum;
		...
    
    
    	/* 指定额外数据 */
    	streamToken
            = new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink,
                              streamBitrate, mediaSource,
                              rtpGroupsock, rtcpGroupsock);
    
    	/* 将客户端目的添加到哈希表中保存,play阶段使用 */
    	if (tcpSocketNum < 0) { // UDP
            destinations = new Destinations(destinationAddr, clientRTPPort, clientRTCPPort);
         } else { // TCP
            destinations = new Destinations(tcpSocketNum, rtpChannelId, rtcpChannelId);
        }
    
    	fDestinationsHashTable->Add((char const*)clientSessionId, destinations);
            
}

可以看到,首先会创建数据源createNewStreamSource和消费者createNewRTPSink

前面分析的时候,在DESCRIBE请求的时候,也会创建数据源和消费者,那是创建的目的是为了获取sdp的媒体级描述,在获取sdp信息后,会将其删除

在此处创建数据源和消费者不会将其立即删除,此处的目的是用于播放,发送数据给客户端

关于createNewStreamSourcecreateNewRTPSink的分析,请阅读live555源码分析(五)DESCRIBE请求的处理

在创建好数据源和消费者后,会创建一个StreamStateRTSPClientSession中的streamState的额外数据(streamToken)指向它

注意这里的StreamState是属于OnDemandServerMediaSubsession的,和RTSPClientSession中的streamState是不一样的,它们的关系如下

live555源码分析(六)SETUP和PLAY请求的处理_第1张图片

RtspClientSession中有一个streamState数组,每个数组元素都指向会话中对应的一个子会话,此外streamToken指向额外的数据

我们看一看OnDemandServerMediaSubsessionStreamState的定义

class StreamState {
public:
    ...
	void startPlaying(...);
	...
private:
    ...
    RTPSink* fRTPSink;
    FramedSource* fMediaSource;
	...
};

回到我们的OnDemandServerMediaSubsession::getStreamParameters函数,在指定好streamToken之后,接下就是将客户端目的加入到哈希表中,这些信息将在PLAY阶段使用

2.2 PLAY

接下来分析PLAY请求的处理,PLAY请求的处理函数如下

void RTSPServer::RTSPClientSession
::handleCmd_PLAY(...) {
	...
	
	/* 调用所有的子会话开始播放 */
	for (i = 0; i < fNumStreamStates; ++i) {
		fStreamStates[i].subsession->startStream(fOurSessionId,
					       fStreamStates[i].streamToken,
					       ...);
	}
	
	...
  	snprintf(...
	   "RTSP/1.0 200 OK\r\n"
	   "CSeq: %s\r\n"
	   "%s"
	   "%s"
	   "%s"
	   "Session: %08X\r\n"
	);
}

handleCmd_PLAY的内容还是挺多了,这里只抽取最主要的部分

从上述可以看出,handleCmd_PLAY会调用会话中所有的子会话开始播放

下面我们看子会话是如何播放的

H264VideoFileServerMediaSubsession对应的startStream如下

void OnDemandServerMediaSubsession::startStream(...) {
    StreamState* streamState = (StreamState*)streamToken;
    streamState->startPlaying(destinations, clientSessionId,...);
}

可以看到会调用streamStatestartPlaying函数,这个streamState是从RTSPClientSessionfStreamStates[i].streamToken传递过来的,其对应OnDemandServerMediaSubsession中创建的StreamState

下面看一看startPlaying函数

void StreamState
::startPlaying(Destinations* dests, unsigned clientSessionId,
	       TaskFunc* rtcpRRHandler, void* rtcpRRHandlerClientData,
	       ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
	       void* serverRequestAlternativeByteHandlerClientData) {
    /* 将目的RTP和RTCP加入到消费者中 */
	if (dests->isTCP) { //TCP
        fRTPSink->addStreamSocket(dests->tcpSocketNum, dests->rtpChannelId);
        fRTCPInstance->addStreamSocket(dests->tcpSocketNum, dests->rtcpChannelId);
    } else { //UDP
        fRTPgs->addDestination(dests->addr, dests->rtpPort, clientSessionId);
        fRTCPgs->addDestination(dests->addr, dests->rtcpPort, clientSessionId);
    }
    
    /* 调用消费者开始播放 */
	fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this);
}

首先将目的RTP和RTCP添加到消费者中,然后调用消费者的播放函数

调用fRTPSink->startPlaying后就会开始播放,至于这个播放流程是如何进行的,我们将在下一篇文章讲解

你可能感兴趣的:(live555源码分析与应用)