live555 源码分析: SETUP 的处理

来源:互联网 发布:mac的ps怎么安装字体 编辑:程序博客网 时间:2024/06/08 05:58

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

第一步,在这个函数中,首先查找资源对应的 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 streaming        fStreamStates[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 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;    }

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

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

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

第六步,从请求字符串中查找 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 UDP    portNumBits& clientRTCPPortNum, // if UDP    unsigned char& rtpChannelId, // if TCP    unsigned 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 found    if (*buf == '\r' && *(buf+1) == '\n' && *(buf+2) == '\r') return; // end of the headers => not found    if (_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 whitespace    if (*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 specified    char* 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=..." string  startTimeIsNow = False; // by default  double 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 found    if (_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 present  while (1) {    if (*buf == '\0') return False; // not found    if (_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_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);

后面我们再来分析 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 OKCSeq: 3Date: Sat, Sep 02 2017 08:54:03 GMTTransport: RTP/AVP;unicast;destination=10.240.248.20;source=10.240.248.20;client_port=19586-19587;server_port=6970-6971Session: 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 的处理

原创粉丝点击