live555源码分析系列
live555源码分析(一)live555初体验
live555源码分析(二)基本组件上
live555源码分析(三)基本组件下
live555源码分析(四)RTSPServer分析
live555源码分析(五)DESCRIBE请求的处理
live555源码分析(六)SETUP和PLAY请求的处理
live555源码分析(七)播放过程
live555源码分析(八)多播
本文将分析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请求回复后,就会开始播放
接下来分析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请求处理中比较重要的一个函数,其作用是RTSPClientSession
和ServerMediaSubsession
之间传递数据
对于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信息后,会将其删除
在此处创建数据源和消费者不会将其立即删除,此处的目的是用于播放,发送数据给客户端
关于createNewStreamSource
和createNewRTPSink
的分析,请阅读live555源码分析(五)DESCRIBE请求的处理
在创建好数据源和消费者后,会创建一个StreamState
,RTSPClientSession
中的streamState
的额外数据(streamToken)指向它
注意这里的StreamState
是属于OnDemandServerMediaSubsession
的,和RTSPClientSession
中的streamState
是不一样的,它们的关系如下
RtspClientSession
中有一个streamState
数组,每个数组元素都指向会话中对应的一个子会话,此外streamToken
指向额外的数据
我们看一看OnDemandServerMediaSubsession
的StreamState
的定义
class StreamState {
public:
...
void startPlaying(...);
...
private:
...
RTPSink* fRTPSink;
FramedSource* fMediaSource;
...
};
回到我们的OnDemandServerMediaSubsession::getStreamParameters
函数,在指定好streamToken
之后,接下就是将客户端目的加入到哈希表中,这些信息将在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,...);
}
可以看到会调用streamState
的startPlaying
函数,这个streamState
是从RTSPClientSession
的fStreamStates[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
后就会开始播放,至于这个播放流程是如何进行的,我们将在下一篇文章讲解