SETUP 请求在 RTSP 的整个工作流程中,用于建立流媒体会话。本文分析 live555 对 SETUP 请求的处理。

RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) 中,通过 RTSPServer::RTSPClientSessionhandleCmd_SETUP() 函数处理 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 casechar const* trackId = urlSuffix; // in the normal casechar* concatenatedStreamName = NULL; // in the normal casedo {// 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;}}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 streamingfStreamStates[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 casefor (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 != NULLvoid*& token = fStreamStates[trackNum].streamToken; // aliasif (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 specifiedchar* clientsDestinationAddressStr;u_int8_t clientsDestinationTTL;portNumBits clientRTPPortNum, clientRTCPPortNum;unsigned char rtpChannelId, rtcpChannelId;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_SETTINGif (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;
#endifdelete[] 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_SERVERSReceivingInterfaceAddr = SendingInterfaceAddr = sourceAddr.sin_addr.s_addr;
#endifsubsession->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 TCPourClientConnection->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;
}

第一步,在这个函数中,首先查找资源对应的 ServerMediaSession,先尝试以资源路径不包含 track id 的部分查找,如果失败则会以资源全路径查找:

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

在这里 lookupServerMediaSession(char const* streamName, Boolean isFirstLookupInSession)isFirstLookupInSession 参数不再是默认值了,而是根据 fOurServerMediaSession 的值来确定。

可见在处理 DESCRIBE 请求时创建的 ServerMediaSession 将总是会被销毁,并重建。

第二步,根据前一步的查找结果做容错处理或更新状态。

如果查找或创建 ServerMediaSession 失败,且 fOurServerMediaSession 为空,向客户端返回 404 错误,这种情况比较容易理解,即在 DESCRIBE 请求之后,资源被移除了;如果查找或创建 ServerMediaSession 失败,且 fOurServerMediaSession 为非空,此时则向客户端返回 400 错误,这种场景似乎是,执行了多次 SETUP 操作,第一次执行的时候资源在,但后面执行的时候,资源不存在了。

查找或创建 ServerMediaSession 失败总是会使函数提前返回。

    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 {

对于查找或创建 ServerMediaSession 成功的情况,如果此时 fOurServerMediaSession 为空,表明这是这个流媒体会话第一次执行 SETUP 操作,此时需要更新 fOurServerMediaSession 并增加其引用计数;如果 fOurServerMediaSession 非空,且与找到的不同,则向客户端返回 400 错误响应消息,不是很明白这究竟是什么样的场景

第三部,根据需要创建流状态。为流媒体的每个子会话创建一个流状态结构 streamState

    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 streamingfStreamStates[i].streamToken = NULL; // for now; it may be changed by the "getStreamParameters()" call that comes later}}

第四步,查找特定子会话(track)的信息。对于 URL 中携带了 track id 的请求,就根据 track id 查找,否则对于只有一个子会话的情况,就以该会话作为 track 会话。

    // Look up information for the specified subsession (track):ServerMediaSubsession* subsession = NULL;unsigned trackNum;if (trackId != NULL && trackId[0] != '\0') { // normal casefor (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;}

第五步,如果 track 子会话的 token 非空,说明已经为该 track 处理过 SETUP 请求,则在重新设置它之前,先停止其已有的流。

    void*& token = fStreamStates[trackNum].streamToken; // aliasif (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);}

会话中流媒体的控制的具体操作过程,暂时先不详细分析。

第六步,从请求字符串中查找 Transport: 头部,并从中提取客户端参数。SETUP 请求的 Transport: 头部看起来可能像下面这样:

Transport: RTP/AVP/UDP;unicast;client_port=19586-19587

在这个头部中,包含有通信的方式,对于 UDP,是单播还是多播,客户端收发 RTP/RTCP 包所用的端口号等。

typedef enum StreamingMode {RTP_UDP,RTP_TCP,RAW_UDP
} StreamingMode;static void parseTransportHeader(char const* buf,StreamingMode& streamingMode,char*& streamingModeString,char*& destinationAddressStr,u_int8_t& destinationTTL,portNumBits& clientRTPPortNum, // if UDPportNumBits& clientRTCPPortNum, // if UDPunsigned char& rtpChannelId, // if TCPunsigned char& rtcpChannelId // if TCP) {// Initialize the result parameters to default values:streamingMode = RTP_UDP;streamingModeString = NULL;destinationAddressStr = NULL;destinationTTL = 255;clientRTPPortNum = 0;clientRTCPPortNum = 1;rtpChannelId = rtcpChannelId = 0xFF;portNumBits p1, p2;unsigned ttl, rtpCid, rtcpCid;// First, find "Transport:"while (1) {if (*buf == '\0') return; // not foundif (*buf == '\r' && *(buf+1) == '\n' && *(buf+2) == '\r') return; // end of the headers => not foundif (_strncasecmp(buf, "Transport:", 10) == 0) break;++buf;}// Then, run through each of the fields, looking for ones we handle:char const* fields = buf + 10;while (*fields == ' ') ++fields;char* field = strDupSize(fields);while (sscanf(fields, "%[^;\r\n]", field) == 1) {if (strcmp(field, "RTP/AVP/TCP") == 0) {streamingMode = RTP_TCP;} else if (strcmp(field, "RAW/RAW/UDP") == 0|| strcmp(field, "MP2T/H2221/UDP") == 0) {streamingMode = RAW_UDP;streamingModeString = strDup(field);} else if (_strncasecmp(field, "destination=", 12) == 0) {delete[] destinationAddressStr;destinationAddressStr = strDup(field+12);} else if (sscanf(field, "ttl%u", &ttl) == 1) {destinationTTL = (u_int8_t)ttl;} else if (sscanf(field, "client_port=%hu-%hu", &p1, &p2) == 2) {clientRTPPortNum = p1;clientRTCPPortNum = streamingMode == RAW_UDP ? 0 : p2; // ignore the second port number if the client asked for raw UDP} else if (sscanf(field, "client_port=%hu", &p1) == 1) {clientRTPPortNum = p1;clientRTCPPortNum = streamingMode == RAW_UDP ? 0 : p1 + 1;} else if (sscanf(field, "interleaved=%u-%u", &rtpCid, &rtcpCid) == 2) {rtpChannelId = (unsigned char)rtpCid;rtcpChannelId = (unsigned char)rtcpCid;}fields += strlen(field);while (*fields == ';' || *fields == ' ' || *fields == '\t') ++fields; // skip over separating ';' chars or whitespaceif (*fields == '\0' || *fields == '\r' || *fields == '\n') break;}delete[] field;
}
. . . . . . // Look for a "Transport:" header in the request string, to extract client parameters:StreamingMode streamingMode;char* streamingModeString = NULL; // set when RAW_UDP streaming is specifiedchar* clientsDestinationAddressStr;u_int8_t clientsDestinationTTL;portNumBits clientRTPPortNum, clientRTCPPortNum;unsigned char rtpChannelId, rtcpChannelId;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);

我们前面在示例 Transport: 头部中,可以看到 “unicast”,但在 live555 中,不是根据 Transport: 头部的内容来确定流用单播还是多播的。

第七步,检查请求中是否有 Range:x-playNow: 头部。这不是标准 RTSP 支持的做法,但一些客户端可能会用这种方法把 SETUPPLAY 结合起来。

Boolean parseRangeParam(char const* paramStr,double& rangeStart, double& rangeEnd,char*& absStartTime, char*& absEndTime,Boolean& startTimeIsNow) {delete[] absStartTime; delete[] absEndTime;absStartTime = absEndTime = NULL; // by default, unless "paramStr" is a "clock=..." stringstartTimeIsNow = False; // by defaultdouble start, end;int numCharsMatched1 = 0, numCharsMatched2 = 0, numCharsMatched3 = 0, numCharsMatched4 = 0;Locale l("C", Numeric);if (sscanf(paramStr, "npt = %lf - %lf", &start, &end) == 2) {rangeStart = start;rangeEnd = end;} else if (sscanf(paramStr, "npt = %n%lf -", &numCharsMatched1, &start) == 1) {if (paramStr[numCharsMatched1] == '-') {// special case for "npt = -<endtime>", which matches here:rangeStart = 0.0; startTimeIsNow = True;rangeEnd = -start;} else {rangeStart = start;rangeEnd = 0.0;}} else if (sscanf(paramStr, "npt = now - %lf", &end) == 1) {rangeStart = 0.0; startTimeIsNow = True;rangeEnd = end;} else if (sscanf(paramStr, "npt = now -%n", &numCharsMatched2) == 0 && numCharsMatched2 > 0) {rangeStart = 0.0; startTimeIsNow = True;rangeEnd = 0.0;} else if (sscanf(paramStr, "clock = %n", &numCharsMatched3) == 0 && numCharsMatched3 > 0) {rangeStart = rangeEnd = 0.0;char const* utcTimes = &paramStr[numCharsMatched3];size_t len = strlen(utcTimes) + 1;char* as = new char[len];char* ae = new char[len];int sscanfResult = sscanf(utcTimes, "%[^-]-%[^\r\n]", as, ae);if (sscanfResult == 2) {absStartTime = as;absEndTime = ae;} else if (sscanfResult == 1) {absStartTime = as;delete[] ae;} else {delete[] as; delete[] ae;return False;}} else if (sscanf(paramStr, "smtpe = %n", &numCharsMatched4) == 0 && numCharsMatched4 > 0) {// We accept "smtpe=" parameters, but currently do not interpret them.} else {return False; // The header is malformed}return True;
}Boolean parseRangeHeader(char const* buf,double& rangeStart, double& rangeEnd,char*& absStartTime, char*& absEndTime,Boolean& startTimeIsNow) {// First, find "Range:"while (1) {if (*buf == '\0') return False; // not foundif (_strncasecmp(buf, "Range: ", 7) == 0) break;++buf;}char const* fields = buf + 7;while (*fields == ' ') ++fields;return parseRangeParam(fields, rangeStart, rangeEnd, absStartTime, absEndTime, startTimeIsNow);
}
. . . . . .
static Boolean parsePlayNowHeader(char const* buf) {// Find "x-playNow:" header, if presentwhile (1) {if (*buf == '\0') return False; // not foundif (_strncasecmp(buf, "x-playNow:", 10) == 0) break;++buf;}return True;
}
. . . . . .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;}

尽管 RTP/RTCP 也支持 TCP 模式,但这种做法不是很主流,后面我们主要来看基于 UDP 单播的模式。

第八步,为会话分配网络资源,如服务器端 RTP 和 RTCP 的端口等。

    netAddressBits destinationAddress = 0;u_int8_t destinationTTL = 255;
#ifdef RTSP_ALLOW_CLIENT_DESTINATION_SETTINGif (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;
#endifdelete[] 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_SERVERSReceivingInterfaceAddr = SendingInterfaceAddr = sourceAddr.sin_addr.s_addr;
#endifsubsession->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);

后面我们再来分析 H264VideoFileServerMediaSubsession 更详细地处理过程。

第九步,生成超时参数字符串。

    if (fOurRTSPServer.fReclamationSeconds > 0) {sprintf(timeoutParameterString, ";timeout=%u",fOurRTSPServer.fReclamationSeconds);} else {timeoutParameterString[0] = '\0';}

第十步,生成响应消息。我们仅来看,RTP 的 UDP 单播模式响应消息的生成。

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

生成的消息看起来大概就像下面这样:

RTSP/1.0 200 OK
CSeq: 3
Date: Sat, Sep 02 2017 08:54:03 GMT
Transport: RTP/AVP;unicast;destination=10.240.248.20;source=10.240.248.20;client_port=19586-19587;server_port=6970-6971
Session: D10C8C71;timeout=65

通过 Transport: 将协商的通信参数,如服务器端为会话分配的用于收发 RTP/RTCP 包的 UDP 端口。

抛开容错,总结一下 SETUP 请求的常规处理流程:
1. 为会话创建 ServerMediaSession
2. 解析请求头 Transport: 中包含的关于客户端请求的传输方式的内容,如使用 UDP 传输还是用 TCP 传输,客户端为 RTP/RTCP 传输开辟的端口等。
3. 解析请求头中的 Range:x-playNow: 以支持 SETUPPLAY 的合并。
4. 为会话分配网络资源,如服务器端的 RTP/RTCP 端口等。
5. 产生响应消息。

ServerMediaSubsession 中,一些更详细地处理过程,留待后面分析。

Done。

live555 源码分析系列文章

live555 源码分析:简介
live555 源码分析:基础设施
live555 源码分析:MediaSever
Wireshark 抓包分析 RTSP/RTP/RTCP 基本工作过程
live555 源码分析:RTSPServer
live555 源码分析: DESCRIBE 的处理
live555 源码分析: SETUP 的处理

live555 源码分析: SETUP 的处理相关推荐

  1. live555 源码分析:播放启动

    本文分析 live555 中,流媒体播放启动,数据开始通过 RTP/RTCP 传输的过程. 如我们在 live555 源码分析:子会话 SETUP 中看到的,一个流媒体子会话的播放启动,由 Strea ...

  2. live555 源码分析:子会话 SDP 行生成

    如我们在前文 live555 源码分析:ServerMediaSession 中看到的,H264VideoFileServerMediaSubsession 的继承层次体系如下图: 在这个继承层次体系 ...

  3. live555 源码分析:ServerMediaSession

    在 live555 中,用一个 ServerMediaSession 表示流媒体会话,它连接了 RTSPServer 和下层流媒体传输逻辑.ServerMediaSession 和 ServerMed ...

  4. live555 源码分析:RTSPServer 组件结构

    前面几篇文章分析了 live555 中 RTSP 的处理逻辑,RTSP 处理有关组件的处理逻辑有点复杂,本文就再来梳理一下它们之间的关系. live555 中 RTSP 处理有关组件关系如下图: 事件 ...

  5. live555 源码分析: PLAY 的处理

    在 SETUP 请求之后,客户端会发起 PLAY 请求,以请求服务器开始传输音视频数据.在 PLAY 请求执行时,一定是已经执行过 SETUP 请求,建立好了客户端会话,因而会与其它要求客户端会话已经 ...

  6. live555 源码分析: DESCRIBE 的处理

    前面在 live555 源码分析:RTSPServer 中分析了 live555 中处理 RTSP 请求的大体流程,并分析了处理起来没有那么复杂的一些方法,如 OPTIONS,GET_PARAMETE ...

  7. live555源码分析(四)RTSPServer分析

    live555源码分析系列 live555源码分析(一)live555初体验 live555源码分析(二)基本组件上 live555源码分析(三)基本组件下 live555源码分析(四)RTSPSer ...

  8. live555 源码分析:RTSPServer

    live555 使用 RTSP/RTP/RTCP 协议来实现流媒体的传输,其中使用 RTSP 来建立流媒体会话,并对流媒体会话进行控制.在 live555 中,通过类 RTSPServerSuppor ...

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

    live555源码分析系列 live555源码分析(一)live555初体验 live555源码分析(二)基本组件上 live555源码分析(三)基本组件下 live555源码分析(四)RTSPSer ...

最新文章

  1. hibernate by example 排除某些列
  2. 【Hexo】deploy出错的解决方法
  3. mac pandas文件路径_Mac进阶必看:如何利用Automator快速获取文件路径
  4. 构件之法读书笔记04
  5. Windows下自动备份Oracle数据库
  6. 在scrapy上使用cookie模拟登录
  7. JavaWeb——拦截器HandlerInterceptor
  8. Linux学习6之软件包管理--yum在线管理
  9. 十大排序算法——冒泡法
  10. 野火征途Pro开发板FPGA数字时钟(秒表)设计学习心得(1)
  11. iOS-图片可以自适应屏幕大小吗,应该会失真吧?
  12. jmeter+ANT接口自动化环境的搭建以及测试报告的优化
  13. VMware® Workstation 16 Pro创建WIN7虚拟机教程
  14. 前端开发都有什么编程语言?
  15. PHP windoews调用OpenOffice实现word/ppt转PDF-附带demo
  16. 给自学者的建议:想学游戏编程开发,你需要做什么?
  17. 安卓学习底层开发学习方法?
  18. 微软笔记本服务器怎么重装系统,笔记本重装系统,详细教您笔记本怎么重装win10系统...
  19. Webstorm的一些常用快捷键
  20. 易语言5.7完美版 全套视频学习教程

热门文章

  1. 制作模块-安装模块压缩包
  2. 微服务 Docker和DevOps
  3. Spring--总体架构
  4. Linux下静态库的创立与使用
  5. 2、内核的配置和移植
  6. Hadoop学习:Map/Reduce初探与小Demo实现
  7. 超级寒潮考验智慧城市万亿投资
  8. Validate + Boostrap tooltip 表单验证示例
  9. 音乐社交APP源码ios版
  10. [XPath] XPath 与 lxml (三)XPath 坐标轴