上篇文章 live555学习笔记【3】---RTSP服务器(一) 上篇文已经将整个RTSP服务器的处理流程分析过了,接下来我们再细看RTSP服务器对客户端报文的处理。本篇博客需要深入分析服务器对客户端报文的处理,所以要求你对RTSP、SDP协议有一定的了解,建议在学习之前,先去看下我的另一篇博客:【RTSP/RTP/RTCP/SDP】协议详解
我们先来分析一下客户端信息处理函数handleRequestBytes(中的)RTSP报文解析函数 parseRTSPRequestString():里面的东西很少,就不上代码了,直接讲一下函数到底做了什么。其实就是按照RTSP协议,解析各个字段的内容,并以传出参数的形式传出来。
我们还是来看看,服务器是怎么处理各个RTSP命令的吧!
1、OPTION命令:
void RTSPServer::RTSPClientConnection::handleCmd_OPTIONS() {
//其实就是按照RTSP协议组装一个响应包,其中allowedCommandNames()的值也是写死的,为:"OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER"
snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n%sPublic: %s\r\n\r\n",
fCurrentCSeq, dateHeader(), fOurRTSPServer.allowedCommandNames());
}
2、SET_PARAMETER及GET_PARAMETER命令
这两个命令服务器什么都不做,直接返回200 OK,如果自己的服务器对这两个命令有要求,就需要自己继承服务器类后,自己实现了。
3、DESCRIBE命令
RTSP服务器收到客户端的DESCRIBE请求后,根据请求URL(rtsp://192.168.0.1/1.mpg),找到对应的流媒体资源,返回响应消息。live555中的ServerMediaSession类用来处理会话中描述,它包含多个(音频或视频)的子会话描述(ServerMediaSubsession)。 RTSP服务器收到客户端的连接请求,建立了RTSPClientSession类,处理单独的客户会话。在建立RTSPClientSession的过程中,将新建立的socket句柄(clientSocket)和RTSP请求处理函数句柄RTSPClientSession::incomingRequestHandler传给任务调度器,由任务调度器对两者进行一对一关联。
收到客户端请求后,如果发现请求是DESCRIBE则进入handleCmd_DESCRIBE函数。根据客户端请求URL的后缀(如1.mpg),调用成员函数DynamicRTSPServer::lookupServerMediaSession查找对应的流媒体信息ServerMediaSession。如果ServerMediaSession不存在,但是本地存在1.mpg文件,则创建一个新的ServerMediaSession。在创建ServerMediaSession过程中,根据文件后缀.mpg,创建媒体MPEG-1or2的解复用器(MPEG1or2FileServerDemux)。再由MPEG1or2FileServerDemux创建一个子会话描述MPEG1or2DemuxedServerMediaSubsession。最后由ServerMediaSession完成组装响应消息中的SDP信息,然后将响应消息发给客户端,完成一次消息交互。
下面我们具体来看看服务器是如何相应DESCRIBE命令的,我会加些注释,帮助你理解。
void RTSPServer::RTSPClientConnection
::handleCmd_DESCRIBE(char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) {
ServerMediaSession* session = NULL;
char* sdpDescription = NULL;
char* rtspURL = NULL;
do {
char urlTotalSuffix[2*RTSP_PARAM_STRING_MAX];
// enough space for urlPreSuffix/urlSuffix'\0'
urlTotalSuffix[0] = '\0';
if (urlPreSuffix[0] != '\0') {
strcat(urlTotalSuffix, urlPreSuffix);
strcat(urlTotalSuffix, "/");
}
//获取客户端请求的媒体的相对路径
strcat(urlTotalSuffix, urlSuffix);
//客户端校验,这里均返回TRUE
if (!authenticationOK("DESCRIBE", urlTotalSuffix, fullRequestStr)) break;
// We should really check that the request contains an "Accept:" #####
// for "application/sdp", because that's what we're sending back #####
// Begin by looking up the "ServerMediaSession" object for the specified "urlTotalSuffix":
//根据路径找到会话描述
session = fOurServer.lookupServerMediaSession(urlTotalSuffix);
if (session == NULL) {
handleCmd_notFound();
break;
}
// Increment the "ServerMediaSession" object's reference count, in case someone removes it
// while we're using it:
//该会话描述自增1,防止会话内容被误删除
session->incrementReferenceCount();
// Then, assemble a SDP description for this session:
//根据媒体信息组装SDP包
sdpDescription = session->generateSDPDescription();
if (sdpDescription == NULL) {
// This usually means that a file name that was specified for a
// "ServerMediaSubsession" does not exist.
setRTSPResponse("404 File Not Found, Or In Incorrect Format");
break;
}
unsigned sdpDescriptionSize = strlen(sdpDescription);
// Also, generate our RTSP URL, for the "Content-Base:" header
// (which is necessary to ensure that the correct URL gets used in subsequent "SETUP" requests).
//返回服务器自己的rtsp地址,防止客户端误发
rtspURL = fOurRTSPServer.rtspURL(session, fClientInputSocket);
snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
"RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
"%s"
"Content-Base: %s/\r\n"
"Content-Type: application/sdp\r\n"
"Content-Length: %d\r\n\r\n"
"%s",
fCurrentCSeq,
dateHeader(),
rtspURL,
sdpDescriptionSize,
sdpDescription);
} while (0);
//根据使用计数的方式,判断当前会话描述是否需要删除
if (session != NULL) {
// Decrement its reference count, now that we're done using it:
session->decrementReferenceCount();
if (session->referenceCount() == 0 && session->deleteWhenUnreferenced()) {
fOurServer.removeServerMediaSession(session);
}
}
delete[] sdpDescription;
delete[] rtspURL;
}
4、SETUP命令
类成员函数handleCmd_SETUP()处理客户端的SETUP请求。调用parseTransportHeader()对SETUP请求的传输头解析,调用子会话(这里具体实现类为OnDemandServerMediaSubsession)的getStreamParameters()函数获取流媒体发送传输参数。将这些参数组装成响应消息,返回给客户端。
获取发送传输参数的过程:调用子会话(具体实现类MPEG1or2DemuxedServerMediaSubsession)的createNewStreamSource(...)创建MPEG1or2VideoStreamFramer,选择发送传输参数,并调用子会话的createNewRTPSink(...)创建MPEG1or2VideoRTPSink。同时将这些信息保存在StreamState类对象中,用于记录流的状态。
客户端发送两个SETUP请求,分别用于建立音频和视频的RTP接收。
SETUP命令的响应相对比较复杂,这里我先整理说明一下,之后再去分析源码,可能效果会更好。首先是判断客户端请求的媒体流是否存在->判断媒体流的子会话是否存在->解析报文头->组装报文到发送缓冲区.
void RTSPServer::RTSPClientSession
::handleCmd_SETUP(RTSPServer::RTSPClientConnection* ourClientConnection,
char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) {
// Normally, "urlPreSuffix" should be the session (stream) name, and "urlSuffix" should be the subsession (track) name.
// However (being "liberal in what we accept"), we also handle 'aggregate' SETUP requests (i.e., without a track name),
// in the special case where we have only a single track. I.e., in this case, we also handle:
// "urlPreSuffix" is empty and "urlSuffix" is the session (stream) name, or
// "urlPreSuffix" concatenated with "urlSuffix" (with "/" inbetween) is the session (stream) name.
char const* streamName = urlPreSuffix; // in the normal case
char const* trackId = urlSuffix; // in the normal case
char* concatenatedStreamName = NULL; // in the normal case
//这里先判断客户端请求的媒体流是否存在
do {
// First, make sure the specified stream name exists:
ServerMediaSession* sms
= fOurServer.lookupServerMediaSession(streamName, fOurServerMediaSession == NULL);
if (sms == NULL) {
// Check for the special case (noted above), before we give up:
if (urlPreSuffix[0] == '\0') {
streamName = urlSuffix;
} else {
concatenatedStreamName = new char[strlen(urlPreSuffix) + strlen(urlSuffix) + 2]; // allow for the "/" and the trailing '\0'
sprintf(concatenatedStreamName, "%s/%s", urlPreSuffix, urlSuffix);
streamName = concatenatedStreamName;
}
trackId = NULL;
// Check again:
sms = fOurServer.lookupServerMediaSession(streamName, fOurServerMediaSession == NULL);
}
if (sms == NULL) {
if (fOurServerMediaSession == NULL) {
// The client asked for a stream that doesn't exist (and this session descriptor has not been used before):
ourClientConnection->handleCmd_notFound();
} else {
// The client asked for a stream that doesn't exist, but using a stream id for a stream that does exist. Bad request:
ourClientConnection->handleCmd_bad();
}
break;
} else {
if (fOurServerMediaSession == NULL) {
// We're accessing the "ServerMediaSession" for the first time.
fOurServerMediaSession = sms;
fOurServerMediaSession->incrementReferenceCount();
} else if (sms != fOurServerMediaSession) {
// The client asked for a stream that's different from the one originally requested for this stream id. Bad request:
ourClientConnection->handleCmd_bad();
break;
}
}
//第一次为该媒体流创建会话,需要再创建几个他的子会话,例如一段264视频,包含视频及音频两个子会话
if (fStreamStates == NULL) {
// This is the first "SETUP" for this session. Set up our array of states for all of this session's subsessions (tracks):
fNumStreamStates = fOurServerMediaSession->numSubsessions();
fStreamStates = new struct streamState[fNumStreamStates];
ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
ServerMediaSubsession* subsession;
for (unsigned i = 0; i < fNumStreamStates; ++i) {
subsession = iter.next();
fStreamStates[i].subsession = subsession;
fStreamStates[i].tcpSocketNum = -1; // for now; may get set for RTP-over-TCP streaming
fStreamStates[i].streamToken = NULL; // for now; it may be changed by the "getStreamParameters()" call that comes later
}
}
//确认客户端请求的子会话是否存在
// Look up information for the specified subsession (track):
ServerMediaSubsession* subsession = NULL;
unsigned trackNum;
if (trackId != NULL && trackId[0] != '\0') { // normal case
for (trackNum = 0; trackNum < fNumStreamStates; ++trackNum) {
subsession = fStreamStates[trackNum].subsession;
if (subsession != NULL && strcmp(trackId, subsession->trackId()) == 0) break;
}
if (trackNum >= fNumStreamStates) {
// The specified track id doesn't exist, so this request fails:
ourClientConnection->handleCmd_notFound();
break;
}
} else {
// Weird case: there was no track id in the URL.
// This works only if we have only one subsession:
if (fNumStreamStates != 1 || fStreamStates[0].subsession == NULL) {
ourClientConnection->handleCmd_bad();
break;
}
trackNum = 0;
subsession = fStreamStates[trackNum].subsession;
}
// ASSERT: subsession != NULL
void*& token = fStreamStates[trackNum].streamToken; // alias
if (token != NULL) {
// We already handled a "SETUP" for this track (to the same client),
// so stop any existing streaming of it, before we set it up again:
subsession->pauseStream(fOurSessionId, token);
fOurRTSPServer.unnoteTCPStreamingOnSocket(fStreamStates[trackNum].tcpSocketNum, this, trackNum);
subsession->deleteStream(fOurSessionId, token);
}
// Look for a "Transport:" header in the request string, to extract client parameters:
StreamingMode streamingMode;
char* streamingModeString = NULL; // set when RAW_UDP streaming is specified
char* clientsDestinationAddressStr;
u_int8_t clientsDestinationTTL;
portNumBits clientRTPPortNum, clientRTCPPortNum;
unsigned char rtpChannelId, rtcpChannelId;
//下面就是解析SETUP请求报文,主要是其中的网络传输类型字段,起止时间、端口号等
parseTransportHeader(fullRequestStr, streamingMode, streamingModeString,
clientsDestinationAddressStr, clientsDestinationTTL,
clientRTPPortNum, clientRTCPPortNum,
rtpChannelId, rtcpChannelId);
if ((streamingMode == RTP_TCP && rtpChannelId == 0xFF) ||
(streamingMode != RTP_TCP && ourClientConnection->fClientOutputSocket != ourClientConnection->fClientInputSocket)) {
// An anomolous situation, caused by a buggy client. Either:
// 1/ TCP streaming was requested, but with no "interleaving=" fields. (QuickTime Player sometimes does this.), or
// 2/ TCP streaming was not requested, but we're doing RTSP-over-HTTP tunneling (which implies TCP streaming).
// In either case, we assume TCP streaming, and set the RTP and RTCP channel ids to proper values:
streamingMode = RTP_TCP;
rtpChannelId = fTCPStreamIdCount; rtcpChannelId = fTCPStreamIdCount+1;
}
if (streamingMode == RTP_TCP) fTCPStreamIdCount += 2;
Port clientRTPPort(clientRTPPortNum);
Port clientRTCPPort(clientRTCPPortNum);
// Next, check whether a "Range:" or "x-playNow:" header is present in the request.
// This isn't legal, but some clients do this to combine "SETUP" and "PLAY":
double rangeStart = 0.0, rangeEnd = 0.0;
char* absStart = NULL; char* absEnd = NULL;
Boolean startTimeIsNow;
if (parseRangeHeader(fullRequestStr, rangeStart, rangeEnd, absStart, absEnd, startTimeIsNow)) {
delete[] absStart; delete[] absEnd;
fStreamAfterSETUP = True;
} else if (parsePlayNowHeader(fullRequestStr)) {
fStreamAfterSETUP = True;
} else {
fStreamAfterSETUP = False;
}
// Then, get server parameters from the 'subsession':
if (streamingMode == RTP_TCP) {
// Note that we'll be streaming over the RTSP TCP connection:
fStreamStates[trackNum].tcpSocketNum = ourClientConnection->fClientOutputSocket;
fOurRTSPServer.noteTCPStreamingOnSocket(fStreamStates[trackNum].tcpSocketNum, this, trackNum);
}
netAddressBits destinationAddress = 0;
u_int8_t destinationTTL = 255;
#ifdef RTSP_ALLOW_CLIENT_DESTINATION_SETTING
if (clientsDestinationAddressStr != NULL) {
// Use the client-provided "destination" address.
// Note: This potentially allows the server to be used in denial-of-service
// attacks, so don't enable this code unless you're sure that clients are
// trusted.
destinationAddress = our_inet_addr(clientsDestinationAddressStr);
}
// Also use the client-provided TTL.
destinationTTL = clientsDestinationTTL;
#endif
delete[] clientsDestinationAddressStr;
Port serverRTPPort(0);
Port serverRTCPPort(0);
// Make sure that we transmit on the same interface that's used by the client (in case we're a multi-homed server):
struct sockaddr_in sourceAddr; SOCKLEN_T namelen = sizeof sourceAddr;
getsockname(ourClientConnection->fClientInputSocket, (struct sockaddr*)&sourceAddr, &namelen);
netAddressBits origSendingInterfaceAddr = SendingInterfaceAddr;
netAddressBits origReceivingInterfaceAddr = ReceivingInterfaceAddr;
// NOTE: The following might not work properly, so we ifdef it out for now:
#ifdef HACK_FOR_MULTIHOMED_SERVERS
ReceivingInterfaceAddr = SendingInterfaceAddr = sourceAddr.sin_addr.s_addr;
#endif
subsession->getStreamParameters(fOurSessionId, ourClientConnection->fClientAddr.sin_addr.s_addr,
clientRTPPort, clientRTCPPort,
fStreamStates[trackNum].tcpSocketNum, rtpChannelId, rtcpChannelId,
destinationAddress, destinationTTL, fIsMulticast,
serverRTPPort, serverRTCPPort,
fStreamStates[trackNum].streamToken);
SendingInterfaceAddr = origSendingInterfaceAddr;
ReceivingInterfaceAddr = origReceivingInterfaceAddr;
AddressString destAddrStr(destinationAddress);
AddressString sourceAddrStr(sourceAddr);
char timeoutParameterString[100];
if (fOurRTSPServer.fReclamationSeconds > 0) {
sprintf(timeoutParameterString, ";timeout=%u", fOurRTSPServer.fReclamationSeconds);
} else {
timeoutParameterString[0] = '\0';
}
if (fIsMulticast) {
switch (streamingMode) {
case RTP_UDP: {
//组装响应报文,并放入响应缓冲区中
snprintf((char*)ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
"RTSP/1.0 200 OK\r\n"
"CSeq: %s\r\n"
"%s"
"Transport: RTP/AVP;multicast;destination=%s;source=%s;port=%d-%d;ttl=%d\r\n"
"Session: %08X%s\r\n\r\n",
ourClientConnection->fCurrentCSeq,
dateHeader(),
destAddrStr.val(), sourceAddrStr.val(), ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()), destinationTTL,
fOurSessionId, timeoutParameterString);
break;
}
case RTP_TCP: {
// multicast streams can't be sent via TCP
ourClientConnection->handleCmd_unsupportedTransport();
break;
}
case RAW_UDP: {
snprintf((char*)ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
"RTSP/1.0 200 OK\r\n"
"CSeq: %s\r\n"
"%s"
"Transport: %s;multicast;destination=%s;source=%s;port=%d;ttl=%d\r\n"
"Session: %08X%s\r\n\r\n",
ourClientConnection->fCurrentCSeq,
dateHeader(),
streamingModeString, destAddrStr.val(), sourceAddrStr.val(), ntohs(serverRTPPort.num()), destinationTTL,
fOurSessionId, timeoutParameterString);
break;
}
}
} else {
switch (streamingMode) {
case RTP_UDP: {
snprintf((char*)ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
"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",
ourClientConnection->fCurrentCSeq,
dateHeader(),
destAddrStr.val(), sourceAddrStr.val(), ntohs(clientRTPPort.num()), ntohs(clientRTCPPort.num()), ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()),
fOurSessionId, timeoutParameterString);
break;
}
case RTP_TCP: {
if (!fOurRTSPServer.fAllowStreamingRTPOverTCP) {
ourClientConnection->handleCmd_unsupportedTransport();
} else {
snprintf((char*)ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
"RTSP/1.0 200 OK\r\n"
"CSeq: %s\r\n"
"%s"
"Transport: RTP/AVP/TCP;unicast;destination=%s;source=%s;interleaved=%d-%d\r\n"
"Session: %08X%s\r\n\r\n",
ourClientConnection->fCurrentCSeq,
dateHeader(),
destAddrStr.val(), sourceAddrStr.val(), rtpChannelId, rtcpChannelId,
fOurSessionId, timeoutParameterString);
}
break;
}
case RAW_UDP: {
snprintf((char*)ourClientConnection->fResponseBuffer, sizeof ourClientConnection->fResponseBuffer,
"RTSP/1.0 200 OK\r\n"
"CSeq: %s\r\n"
"%s"
"Transport: %s;unicast;destination=%s;source=%s;client_port=%d;server_port=%d\r\n"
"Session: %08X%s\r\n\r\n",
ourClientConnection->fCurrentCSeq,
dateHeader(),
streamingModeString, destAddrStr.val(), sourceAddrStr.val(), ntohs(clientRTPPort.num()), ntohs(serverRTPPort.num()),
fOurSessionId, timeoutParameterString);
break;
}
}
}
delete[] streamingModeString;
} while (0);
delete[] concatenatedStreamName;
}
5、PLAY命令
PLAY命令的响应很简单,但是服务器收到PLAY请求后做的处理就很复杂了,这里,我先讲一下PLAY命令的流程吧!收到PLAY命令->解析报文,判断倍速播放(实际上只支持单倍速)->解析报文,判断起止时间->根据上面获取到的参数,设置各子会话的参数->设置RTP/RTCP包的处理函数->组装报文,放入响应缓冲区。
类成员函数handleCmd_PLAY()处理客户端的播放请求。首先调用子会话的startStream(),内部调用MediaSink::startPlaying(...),然后是MultiFramedRTPSink::continuePlaying(),接着调用MultiFramedRTPSink::buildAndSendPacket(...)。buildAndSendPacke内部先设置RTP包头,内部再调用MultiFramedRTPSink::packFrame()填充编码帧数据。
packFrame内部通过FramedSource::getNextFrame(), 接着MPEGVideoStreamFramer::doGetNextFrame(),再接着经过MPEGVideoStreamFramer::continueReadProcessing(), FramedSource::afterGetting(...), MultiFramedRTPSink::afterGettingFrame(...), MultiFramedRTPSink::afterGettingFrame1(...)等一系列繁琐调用,最后到了MultiFramedRTPSink::sendPacketIfNecessary(), 这里才真正发送RTP数据包。然后是计算下一个数据包发送时间,把MultiFramedRTPSink::sendNext(...)函数句柄传给任务调度器,作为一个延时事件调度。在主循环中,当MultiFramedRTPSink::sendNext()被调度时,又开始调用MultiFramedRTPSink::buildAndSendPacket(...)开始新的发送数据过程,这样客户端可以源源不断的收到服务器传来的RTP包了。
6、TEARDOWN命令
直接释放各类资源,然后回复200 OK