float ServerMediaSession::duration() const
{
    float minSubsessionDuration = 0.0;
    float maxSubsessionDuration = 0.0;
    for (ServerMediaSubsession *subsession = fSubsessionsHead; subsession != NULL;
            subsession = subsession->fNext)
    {
        float ssduration = subsession->duration();
        if (subsession == fSubsessionsHead)   // this is the first subsession
        {
            minSubsessionDuration = maxSubsessionDuration = ssduration;
        }
        else if (ssduration < minSubsessionDuration)
        {
            minSubsessionDuration = ssduration;
        }
        else if (ssduration > maxSubsessionDuration)
        {
            maxSubsessionDuration = ssduration;
        }
    }

    if (maxSubsessionDuration != minSubsessionDuration)
    {
        return -maxSubsessionDuration; // because subsession durations differ
    }
    else
    {
        return maxSubsessionDuration; // all subsession durations are the same
    }
}
Exemplo n.º 2
0
float ServerMediaSession::duration() const {
  float minSubsessionDuration = 0.0;
  float maxSubsessionDuration = 0.0;
  for (ServerMediaSubsession* subsession = fSubsessionsHead; subsession != NULL;
       subsession = subsession->fNext) {
    // Hack: If any subsession supports seeking by 'absolute' time, then return a negative value, to indicate that only subsessions
    // will have a "a=range:" attribute:
    char* absStartTime = NULL; char* absEndTime = NULL;
    subsession->getAbsoluteTimeRange(absStartTime, absEndTime);
    if (absStartTime != NULL) return -1.0f;

    float ssduration = subsession->duration();
    if (subsession == fSubsessionsHead) { // this is the first subsession
      minSubsessionDuration = maxSubsessionDuration = ssduration;
    } else if (ssduration < minSubsessionDuration) {
	minSubsessionDuration = ssduration;
    } else if (ssduration > maxSubsessionDuration) {
	maxSubsessionDuration = ssduration;
    }
  }

  if (maxSubsessionDuration != minSubsessionDuration) {
    return -maxSubsessionDuration; // because subsession durations differ
  } else {
    return maxSubsessionDuration; // all subsession durations are the same
  }
}
Exemplo n.º 3
0
void ServerMediaSession::testScaleFactor(float& scale) {
  // First, try setting all subsessions to the desired scale.
  // If the subsessions' actual scales differ from each other, choose the
  // value that's closest to 1, and then try re-setting all subsessions to that
  // value.  If the subsessions' actual scales still differ, re-set them all to 1.
  float minSSScale = 1.0;
  float maxSSScale = 1.0;
  float bestSSScale = 1.0;
  float bestDistanceTo1 = 0.0;
  ServerMediaSubsession* subsession;
  for (subsession = fSubsessionsHead; subsession != NULL;
       subsession = subsession->fNext) {
    float ssscale = scale;
    subsession->testScaleFactor(ssscale);
    if (subsession == fSubsessionsHead) { // this is the first subsession
      minSSScale = maxSSScale = bestSSScale = ssscale;
      bestDistanceTo1 = (float)fabs(ssscale - 1.0f);
    } else {
      if (ssscale < minSSScale) {
	minSSScale = ssscale;
      } else if (ssscale > maxSSScale) {
	maxSSScale = ssscale;
      }

      float distanceTo1 = (float)fabs(ssscale - 1.0f);
      if (distanceTo1 < bestDistanceTo1) {
	bestSSScale = ssscale;
	bestDistanceTo1 = distanceTo1;
      }
    }
  }
  if (minSSScale == maxSSScale) {
    // All subsessions are at the same scale: minSSScale == bestSSScale == maxSSScale
    scale = minSSScale;
    return;
  }

  // The scales for each subsession differ.  Try to set each one to the value
  // that's closest to 1:
  for (subsession = fSubsessionsHead; subsession != NULL;
       subsession = subsession->fNext) {
    float ssscale = bestSSScale;
    subsession->testScaleFactor(ssscale);
    if (ssscale != bestSSScale) break; // no luck
  }
  if (subsession == NULL) {
    // All subsessions are at the same scale: bestSSScale
    scale = bestSSScale;
    return;
  }

  // Still no luck.  Set each subsession's scale to 1:
  for (subsession = fSubsessionsHead; subsession != NULL;
       subsession = subsession->fNext) {
    float ssscale = 1;
    subsession->testScaleFactor(ssscale);
  }
  scale = 1;
}
Exemplo n.º 4
0
void RTSPServer::RTSPClientSession
::handleCmd_withinSession(char const* cmdName,
			  char const* urlPreSuffix, char const* urlSuffix,
			  char const* cseq, char const* fullRequestStr) {
	// This will either be:
	// - a non-aggregated operation, if "urlPreSuffix" is the session (stream)
	//	 name and "urlSuffix" is the subsession (track) name, or
	// - a aggregated operation, if "urlSuffix" is the session (stream) name,
	//	 or "urlPreSuffix" is the session (stream) name, and "urlSuffix"
	//	 is empty.
	// First, figure out which of these it is:
  if (fOurServerMediaSession == NULL) { // There wasn't a previous SETUP!
	  handleCmd_notSupported(cseq);
	  return;
	}
  ServerMediaSubsession* subsession;
  if (urlSuffix[0] != '\0' &&
		  strcmp(fOurServerMediaSession->streamName(), urlPreSuffix) == 0) {
		// Non-aggregated operation.
		// Look up the media subsession whose track id is "urlSuffix":
	  ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
	  while ((subsession = iter.next()) != NULL) {
		  if (strcmp(subsession->trackId(), urlSuffix) == 0) break; // success
		}
	  if (subsession == NULL) { // no such track!
		  handleCmd_notFound(cseq);
		  return;
		}
	} else if (strcmp(fOurServerMediaSession->streamName(), urlSuffix) == 0 ||
			 strcmp(fOurServerMediaSession->streamName(), urlPreSuffix) == 0) {
		// Aggregated operation
	  subsession = NULL;
	} else { // the request doesn't match a known stream and/or track at all!
	  handleCmd_notFound(cseq);
	  return;
	}

  if (strcmp(cmdName, "TEARDOWN") == 0) {
	  handleCmd_TEARDOWN(subsession, cseq);
	} else if (strcmp(cmdName, "PLAY") == 0) {
	  handleCmd_PLAY(subsession, cseq, fullRequestStr);
	} else if (strcmp(cmdName, "PAUSE") == 0) {
	  handleCmd_PAUSE(subsession, cseq);
	} else if (strcmp(cmdName, "GET_PARAMETER") == 0) {
	  handleCmd_GET_PARAMETER(subsession, cseq, fullRequestStr);
	} else if (strcmp(cmdName, "SET_PARAMETER") == 0) {
	  handleCmd_SET_PARAMETER(subsession, cseq, fullRequestStr);
	}
}
Exemplo n.º 5
0
GMI_RESULT GMI_RtspServer::Add   ( boolean_t EncodeMode, uint32_t SourceId, uint32_t MediaId, uint32_t MediaType, uint32_t CodecType, void_t *CodecParameter, size_t CodecParameterLength, FD_HANDLE *TransportHandle )
{
    if ( MEDIA_VIDEO != MediaType )
    {
        return GMI_INVALID_PARAMETER;
    }

    uint32_t   ServerMulticastAddress = 0;
    GMI_RESULT Result = GetServerMulticastAddress( MediaId, &ServerMulticastAddress );
    if ( FAILED( Result ) )
    {
        return Result;
    }

    uint16_t ServerRtpPort = 0;
    Result = GetServer_RTP_UDP_Port( MediaId, &ServerRtpPort );
    if ( FAILED( Result ) )
    {
        return Result;
    }

    uint16_t ClientRtpPort = 0;
    Result = GetClient_RTP_UDP_Port( MediaId, &ClientRtpPort );
    if ( FAILED( Result ) )
    {
        return Result;
    }

    uint16_t IPCMediaDataDispatchServerPort = 0;
    Result = GetIPCMediaDataDispatchServerPort( MediaId, &IPCMediaDataDispatchServerPort );
    if ( FAILED( Result ) )
    {
        return Result;
    }

    uint16_t IPCMediaDataDispatchClientPort = 0;
    Result = GetIPCMediaDataDispatchClientPort( MediaId, &IPCMediaDataDispatchClientPort );
    if ( FAILED( Result ) )
    {
        return Result;
    }

    StreamInfo *Info = BaseMemoryManager::Instance().New<StreamInfo>();

    Info->s_Scheduler = BasicTaskScheduler::createNew();
    Info->s_Environment = BasicUsageEnvironment::createNew(*Info->s_Scheduler);

    char streamName[32], inputFileName[32];
#if defined( __linux__ )
    sprintf(streamName, "stream%d", (MediaId+1));
    sprintf(inputFileName, "live_stream%d", (MediaId+1));
#elif defined( _WIN32 )
    sprintf_s(streamName, 32, "stream%d", (MediaId+1));
    sprintf_s(inputFileName, 32, "live_stream%d", (MediaId+1));
#endif

    Info->s_MediaSession = ServerMediaSession::createNew(*m_Environment, streamName, streamName, descriptionString, is_ssm /*SSM*/);
    ServerMediaSubsession* subsession = H264VideoFileServerMediaSubsession::createNew(*Info->s_Environment, inputFileName, reuseFirstSource, IPCMediaDataDispatchServerPort, IPCMediaDataDispatchClientPort, ServerRtpPort, ServerMulticastAddress );

    Info->s_MediaSession->addSubsession(subsession);
    subsession->setServerAddressAndPortForSDP(0, ClientRtpPort );
    m_RtspServer->addServerMediaSession(Info->s_MediaSession);

    char *url = m_RtspServer->rtspURL(Info->s_MediaSession);
    *m_Environment << "Play this stream using the URL \"" << url << "\"\n";
    delete[] url;

    m_Streams.push_back( Info );

    *TransportHandle = Info;
    return GMI_SUCCESS;
}
char *ServerMediaSession::generateSDPDescription()
{
    struct in_addr ipAddress;
    ipAddress.s_addr = ourIPAddress(envir());
    char *const ipAddressStr = strDup(our_inet_ntoa(ipAddress));
    unsigned ipAddressStrSize = strlen(ipAddressStr);

    // For a SSM sessions, we need a "a=source-filter: incl ..." line also:
    char *sourceFilterLine;
    if (fIsSSM)
    {
        char const *const sourceFilterFmt =
            "a=source-filter: incl IN IP4 * %s\r\n"
            "a=rtcp-unicast: reflection\r\n";
        unsigned const sourceFilterFmtSize = strlen(sourceFilterFmt) + ipAddressStrSize + 1;

        sourceFilterLine = new char[sourceFilterFmtSize];
        sprintf(sourceFilterLine, sourceFilterFmt, ipAddressStr);
    }
    else
    {
        sourceFilterLine = strDup("");
    }

    char *rangeLine = NULL; // for now
    char *sdp = NULL; // for now

    do
    {
        // Count the lengths of each subsession's media-level SDP lines.
        // (We do this first, because the call to "subsession->sdpLines()"
        // causes correct subsession 'duration()'s to be calculated later.)
        unsigned sdpLength = 0;
        ServerMediaSubsession *subsession;
        for (subsession = fSubsessionsHead; subsession != NULL;
                subsession = subsession->fNext)
        {
            char const *sdpLines = subsession->sdpLines();
            if (sdpLines == NULL) break; // the media's not available
            sdpLength += strlen(sdpLines);
        }
        if (subsession != NULL) break; // an error occurred

        // Unless subsessions have differing durations, we also have a "a=range:" line:
        float dur = duration();
        if (dur == 0.0)
        {
            rangeLine = strDup("a=range:npt=0-\r\n");
        }
        else if (dur > 0.0)
        {
            char buf[100];
            sprintf(buf, "a=range:npt=0-%.3f\r\n", dur);
            rangeLine = strDup(buf);
        }
        else     // subsessions have differing durations, so "a=range:" lines go there
        {
            rangeLine = strDup("");
        }

        char const *const sdpPrefixFmt =
            "v=0\r\n"
            "o=- %ld%06ld %d IN IP4 %s\r\n"
            "s=%s\r\n"
            "i=%s\r\n"
            "t=0 0\r\n"
            "a=tool:%s%s\r\n"
            "a=type:broadcast\r\n"
            "a=control:*\r\n"
            "%s"
            "%s"
            "a=x-qt-text-nam:%s\r\n"
            "a=x-qt-text-inf:%s\r\n"
            "%s";
        sdpLength += strlen(sdpPrefixFmt)
                     + 20 + 6 + 20 + ipAddressStrSize
                     + strlen(fDescriptionSDPString)
                     + strlen(fInfoSDPString)
                     + strlen(libNameStr) + strlen(libVersionStr)
                     + strlen(sourceFilterLine)
                     + strlen(rangeLine)
                     + strlen(fDescriptionSDPString)
                     + strlen(fInfoSDPString)
                     + strlen(fMiscSDPLines);
        sdp = new char[sdpLength];
        if (sdp == NULL) break;

        // Generate the SDP prefix (session-level lines):
        sprintf(sdp, sdpPrefixFmt,
                fCreationTime.tv_sec, fCreationTime.tv_usec, // o= <session id>
                1, // o= <version> // (needs to change if params are modified)
                ipAddressStr, // o= <address>
                fDescriptionSDPString, // s= <description>
                fInfoSDPString, // i= <info>
                libNameStr, libVersionStr, // a=tool:
                sourceFilterLine, // a=source-filter: incl (if a SSM session)
                rangeLine, // a=range: line
                fDescriptionSDPString, // a=x-qt-text-nam: line
                fInfoSDPString, // a=x-qt-text-inf: line
                fMiscSDPLines); // miscellaneous session SDP lines (if any)

        // Then, add the (media-level) lines for each subsession:
        char *mediaSDP = sdp;
        for (subsession = fSubsessionsHead; subsession != NULL;
                subsession = subsession->fNext)
        {
            mediaSDP += strlen(mediaSDP);
            sprintf(mediaSDP, "%s", subsession->sdpLines());
        }
    }
    while (0);

    delete[] rangeLine;
    delete[] sourceFilterLine;
    delete[] ipAddressStr;
    return sdp;
}
void RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming
::handleHTTPCmd_StreamingGET(char const* urlSuffix, char const* /*fullRequestStr*/) {
  // If "urlSuffix" ends with "?segment=<offset-in-seconds>,<duration-in-seconds>", then strip this off, and send the
  // specified segment.  Otherwise, construct and send a playlist that consists of segments from the specified file.
  do {
    char const* questionMarkPos = strrchr(urlSuffix, '?');
    if (questionMarkPos == NULL) break;
    unsigned offsetInSeconds, durationInSeconds;
    if (sscanf(questionMarkPos, "?segment=%u,%u", &offsetInSeconds, &durationInSeconds) != 2) break;

    char* streamName = strDup(urlSuffix);
    streamName[questionMarkPos-urlSuffix] = '\0';

    do {
      ServerMediaSession* session = fOurServer.lookupServerMediaSession(streamName);
      if (session == NULL) {
	handleHTTPCmd_notFound();
	break;
      }

      // We can't send multi-subsession streams over HTTP (because there's no defined way to multiplex more than one subsession).
      // Therefore, use the first (and presumed only) substream:
      ServerMediaSubsessionIterator iter(*session);
      ServerMediaSubsession* subsession = iter.next();
      if (subsession == NULL) {
	// Treat an 'empty' ServerMediaSession the same as one that doesn't exist at all:
	handleHTTPCmd_notFound();
	break;
      }

      // Call "getStreamParameters()" to create the stream's source.  (Because we're not actually streaming via RTP/RTCP, most
      // of the parameters to the call are dummy.)
      ++fClientSessionId;
      Port clientRTPPort(0), clientRTCPPort(0), serverRTPPort(0), serverRTCPPort(0);
      netAddressBits destinationAddress = 0;
      u_int8_t destinationTTL = 0;
      Boolean isMulticast = False;
      void* streamToken;
      subsession->getStreamParameters(fClientSessionId, 0, clientRTPPort,clientRTCPPort, -1,0,0, destinationAddress,destinationTTL, isMulticast, serverRTPPort,serverRTCPPort, streamToken);
      
      // Seek the stream source to the desired place, with the desired duration, and (as a side effect) get the number of bytes:
      double dOffsetInSeconds = (double)offsetInSeconds;
      u_int64_t numBytes;
      subsession->seekStream(fClientSessionId, streamToken, dOffsetInSeconds, (double)durationInSeconds, numBytes);
      unsigned numTSBytesToStream = (unsigned)numBytes;
      
      if (numTSBytesToStream == 0) {
	// For some reason, we do not know the size of the requested range.  We can't handle this request:
	handleHTTPCmd_notSupported();
	break;
      }
      
      // Construct our response:
      snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
	       "HTTP/1.1 200 OK\r\n"
	       "%s"
	       "Server: LIVE555 Streaming Media v%s\r\n"
	       "%s"
	       "Content-Length: %d\r\n"
	       "Content-Type: text/plain; charset=ISO-8859-1\r\n"
	       "\r\n",
	       dateHeader(),
	       LIVEMEDIA_LIBRARY_VERSION_STRING,
	       lastModifiedHeader(streamName),
	       numTSBytesToStream);
      // Send the response now, because we're about to add more data (from the source):
      send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
      fResponseBuffer[0] = '\0'; // We've already sent the response.  This tells the calling code not to send it again.
      
      // Ask the media source to deliver - to the TCP sink - the desired data:
      if (fStreamSource != NULL) { // sanity check
	if (fTCPSink != NULL) fTCPSink->stopPlaying();
	Medium::close(fStreamSource);
      }
      fStreamSource = subsession->getStreamSource(streamToken);
      if (fStreamSource != NULL) {
	if (fTCPSink == NULL) fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket);
	fTCPSink->startPlaying(*fStreamSource, afterStreaming, this);
      }
    } while(0);

    delete[] streamName;
    return;
  } while (0);

  // "urlSuffix" does not end with "?segment=<offset-in-seconds>,<duration-in-seconds>".
  // Construct and send a playlist that describes segments from the specified file.

  // First, make sure that the named file exists, and is streamable:
  ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlSuffix);
  if (session == NULL) {
    handleHTTPCmd_notFound();
    return;
  }

  // To be able to construct a playlist for the requested file, we need to know its duration:
  float duration = session->duration();
  if (duration <= 0.0) {
    // We can't handle this request:
    handleHTTPCmd_notSupported();
    return;
  }

  // Now, construct the playlist.  It will consist of a prefix, one or more media file specifications, and a suffix:
  unsigned const maxIntLen = 10; // >= the maximum possible strlen() of an integer in the playlist
  char const* const playlistPrefixFmt =
    "#EXTM3U\r\n"
    "#EXT-X-ALLOW-CACHE:YES\r\n"
    "#EXT-X-MEDIA-SEQUENCE:0\r\n"
    "#EXT-X-TARGETDURATION:%d\r\n";
  unsigned const playlistPrefixFmt_maxLen = strlen(playlistPrefixFmt) + maxIntLen;

  char const* const playlistMediaFileSpecFmt =
    "#EXTINF:%d,\r\n"
    "%s?segment=%d,%d\r\n";
  unsigned const playlistMediaFileSpecFmt_maxLen = strlen(playlistMediaFileSpecFmt) + maxIntLen + strlen(urlSuffix) + 2*maxIntLen;

  char const* const playlistSuffixFmt =
    "#EXT-X-ENDLIST\r\n";
  unsigned const playlistSuffixFmt_maxLen = strlen(playlistSuffixFmt);

  // Figure out the 'target duration' that will produce a playlist that will fit in our response buffer.  (But make it at least 10s.)
  unsigned const playlistMaxSize = 10000;
  unsigned const mediaFileSpecsMaxSize = playlistMaxSize - (playlistPrefixFmt_maxLen + playlistSuffixFmt_maxLen);
  unsigned const maxNumMediaFileSpecs = mediaFileSpecsMaxSize/playlistMediaFileSpecFmt_maxLen;

  unsigned targetDuration = (unsigned)(duration/maxNumMediaFileSpecs + 1);
  if (targetDuration < 10) targetDuration = 10;

  char* playlist = new char[playlistMaxSize];
  char* s = playlist;
  sprintf(s, playlistPrefixFmt, targetDuration);
  s += strlen(s);

  unsigned durSoFar = 0;
  while (1) {
    unsigned dur = targetDuration < duration ? targetDuration : (unsigned)duration;
    duration -= dur;
    sprintf(s, playlistMediaFileSpecFmt, dur, urlSuffix, durSoFar, dur);
    s += strlen(s);
    if (duration < 1.0) break;

    durSoFar += dur;
  }

  sprintf(s, playlistSuffixFmt);
  s += strlen(s);
  unsigned playlistLen = s - playlist;

  // Construct our response:
  snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
	   "HTTP/1.1 200 OK\r\n"
	   "%s"
	   "Server: LIVE555 Streaming Media v%s\r\n"
	   "%s"
	   "Content-Length: %d\r\n"
	   "Content-Type: application/vnd.apple.mpegurl\r\n"
	   "\r\n",
	   dateHeader(),
	   LIVEMEDIA_LIBRARY_VERSION_STRING,
	   lastModifiedHeader(urlSuffix),
	   playlistLen);

  // Send the response header now, because we're about to add more data (the playlist):
  send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
  fResponseBuffer[0] = '\0'; // We've already sent the response.  This tells the calling code not to send it again.

  // Then, send the playlist.  Because it's large, we don't do so using "send()", because that might not send it all at once.
  // Instead, we stream the playlist over the TCP socket:
  if (fPlaylistSource != NULL) { // sanity check
    if (fTCPSink != NULL) fTCPSink->stopPlaying();
    Medium::close(fPlaylistSource);
  }
  fPlaylistSource = ByteStreamMemoryBufferSource::createNew(envir(), (u_int8_t*)playlist, playlistLen);
  if (fTCPSink == NULL) fTCPSink = TCPStreamSink::createNew(envir(), fClientOutputSocket);
  fTCPSink->startPlaying(*fPlaylistSource, afterStreaming, this);
}
Exemplo n.º 8
0
void RTSPServer::RTSPClientSession
::handleCmd_SETUP(char const* cseq,
		  char const* urlPreSuffix, char const* urlSuffix,
		  char const* fullRequestStr) {
	// "urlPreSuffix" should be the session (stream) name, and
	// "urlSuffix" should be the subsession (track) name.
  char const* streamName = urlPreSuffix;
  char const* trackId = urlSuffix;

	// Check whether we have existing session state, and, if so, whether it's
	// for the session that's named in "streamName".	(Note that we don't
	// support more than one concurrent session on the same client connection.) #####
  if (fOurServerMediaSession != NULL
			&& strcmp(streamName, fOurServerMediaSession->streamName()) != 0) {
	  fOurServerMediaSession = NULL;
	}
  if (fOurServerMediaSession == NULL) {
		// Set up this session's state.

		// Look up the "ServerMediaSession" object for the specified stream:
	  if (streamName[0] != '\0' ||
	fOurServer.lookupServerMediaSession("") != NULL) { // normal case
		} else { // weird case: there was no track id in the URL
		  streamName = urlSuffix;
		  trackId = NULL;
		}
	  fOurServerMediaSession = fOurServer.lookupServerMediaSession(streamName);
	  if (fOurServerMediaSession == NULL) {
		  handleCmd_notFound(cseq);
		  return;
		}

	  fOurServerMediaSession->incrementReferenceCount();

		// Set up our array of states for this session's subsessions (tracks):
	  reclaimStreamStates();
	  ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
	  for (fNumStreamStates = 0; iter.next() != NULL; ++fNumStreamStates) {}
	  fStreamStates = new struct streamState[fNumStreamStates];
	  iter.reset();
	  ServerMediaSubsession* subsession;
	  for (unsigned i = 0; i < fNumStreamStates; ++i) {
		  subsession = iter.next();
		  fStreamStates[i].subsession = subsession;
		  fStreamStates[i].streamToken = NULL; // for now; reset by SETUP later
		}
	}

	// Look up information for the specified subsession (track):
  ServerMediaSubsession* subsession = NULL;
  unsigned streamNum;
  if (trackId != NULL && trackId[0] != '\0') { // normal case
	  for (streamNum = 0; streamNum < fNumStreamStates; ++streamNum) {
		  subsession = fStreamStates[streamNum].subsession;
		  if (subsession != NULL && strcmp(trackId, subsession->trackId()) == 0) break;
		}
	  if (streamNum >= fNumStreamStates) {
			// The specified track id doesn't exist, so this request fails:
		  handleCmd_notFound(cseq);
		  return;
		}
	} else {
		// Weird case: there was no track id in the URL.
		// This works only if we have only one subsession:
	  if (fNumStreamStates != 1) {
		  handleCmd_bad(cseq);
		  return;
		}
	  streamNum = 0;
	  subsession = fStreamStates[streamNum].subsession;
	}
	// ASSERT: subsession != NULL

	// 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, fIsMulticast,
					 clientsDestinationAddressStr, clientsDestinationTTL,
					 clientRTPPortNum, clientRTCPPortNum,
					 rtpChannelId, rtcpChannelId);
  if (streamingMode == RTP_TCP && rtpChannelId == 0xFF) {
		// TCP streaming was requested, but with no "interleaving=" fields.
		// (QuickTime Player sometimes does this.)  Set the RTP and RTCP channel ids to
		// proper values:
	  rtpChannelId = fTCPStreamIdCount; rtcpChannelId = fTCPStreamIdCount+1;
	}
  fTCPStreamIdCount += 2;

  Port clientRTPPort(clientRTPPortNum);
  Port clientRTCPPort(clientRTCPPortNum);

	// Next, check whether a "Range:" 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;
  fStreamAfterSETUP = parseRangeHeader(fullRequestStr, rangeStart, rangeEnd) ||
										  parsePlayNowHeader(fullRequestStr);

	// Then, get server parameters from the 'subsession':
  int tcpSocketNum = streamingMode == RTP_TCP ? fClientSocket : -1;
  netAddressBits destinationAddress = 0;
  u_int8_t destinationTTL = 5;
#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);
  subsession->getStreamParameters(fOurSessionId, fClientAddr.sin_addr.s_addr,
				  clientRTPPort, clientRTCPPort,
				  tcpSocketNum, rtpChannelId, rtcpChannelId,
				  destinationAddress, destinationTTL, fIsMulticast,
				  serverRTPPort, serverRTCPPort,
				  fStreamStates[streamNum].streamToken);
  struct in_addr destinationAddr; destinationAddr.s_addr = destinationAddress;
  char* destAddrStr = strDup(our_inet_ntoa(destinationAddr));
  struct sockaddr_in sourceAddr; SOCKLEN_T namelen = sizeof sourceAddr;
  getsockname(fClientSocket, (struct sockaddr*)&sourceAddr, &namelen);
  char* sourceAddrStr = strDup(our_inet_ntoa(sourceAddr.sin_addr));
  if (fIsMulticast) {
	  switch (streamingMode) {
	  case RTP_UDP:
			  snprintf((char*)fResponseBuffer, sizeof 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\r\n\r\n",
								 cseq,
								 dateHeader(),
								 destAddrStr, sourceAddrStr, ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()), destinationTTL,
								 fOurSessionId);
			  break;
	  case RTP_TCP:
				// multicast streams can't be sent via TCP
			  handleCmd_unsupportedTransport(cseq);
			  break;
	  case RAW_UDP:
			  snprintf((char*)fResponseBuffer, sizeof 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\r\n\r\n",
								 cseq,
								 dateHeader(),
								 streamingModeString, destAddrStr, sourceAddrStr, ntohs(serverRTPPort.num()), destinationTTL,
								 fOurSessionId);
			  break;
		}
	} else {
	  switch (streamingMode) {
	  case RTP_UDP: {
		  snprintf((char*)fResponseBuffer, sizeof 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\r\n\r\n",
				 cseq,
				 dateHeader(),
				 destAddrStr, sourceAddrStr, ntohs(clientRTPPort.num()), ntohs(clientRTCPPort.num()), ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()),
				 fOurSessionId);
		  break;
		}
	  case RTP_TCP: {
		  snprintf((char*)fResponseBuffer, sizeof 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\r\n\r\n",
				 cseq,
				 dateHeader(),
				 destAddrStr, sourceAddrStr, rtpChannelId, rtcpChannelId,
				 fOurSessionId);
		  break;
		}
	  case RAW_UDP: {
		  snprintf((char*)fResponseBuffer, sizeof 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\r\n\r\n",
				 cseq,
				 dateHeader(),
				 streamingModeString, destAddrStr, sourceAddrStr, ntohs(clientRTPPort.num()), ntohs(serverRTPPort.num()),
				 fOurSessionId);
		  break;
		}
		}
	}
  delete[] destAddrStr; delete[] sourceAddrStr; delete[] streamingModeString;
}