Esempio n. 1
0
QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams)
{
    QTSS_RTSPMethod* theMethod = NULL;
    UInt32 theMethodLen = 0;
    if ((QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqMethod, 0,
            (void**)&theMethod, &theMethodLen) != QTSS_NoErr) || (theMethodLen != sizeof(QTSS_RTSPMethod)))
    {
        Assert(0);
        return QTSS_RequestFailed;
    }
    
    switch (*theMethod)
    {
        case qtssDescribeMethod:
            return DoDescribe(inParams);
        case qtssSetupMethod:
            return DoSetup(inParams);
        case qtssPlayMethod:
            return DoPlay(inParams);
        case qtssTeardownMethod:
            // Tell the server that this session should be killed, and send a TEARDOWN response
            (void)QTSS_Teardown(inParams->inClientSession);
            (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0);
            break;
        case qtssPauseMethod:
            (void)QTSS_Pause(inParams->inClientSession);
            (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0);
            break;
        default:
            break;
    }           

    return QTSS_NoErr;
}
Esempio n. 2
0
void    QTSSModuleUtils::SendDescribeResponse(QTSS_RTSPRequestObject inRequest,
                                                    QTSS_ClientSessionObject inSession,
                                                    iovec* describeData,
                                                    UInt32 inNumVectors,
                                                    UInt32 inTotalLength)
{
    //write content size header
    char buf[32];
    qtss_sprintf(buf, "%"_S32BITARG_"", inTotalLength);
    (void)QTSS_AppendRTSPHeader(inRequest, qtssContentLengthHeader, &buf[0], ::strlen(&buf[0]));

    (void)QTSS_SendStandardRTSPResponse(inRequest, inSession, 0);

        // On solaris, the maximum # of vectors is very low (= 16) so to ensure that we are still able to
        // send the SDP if we have a number greater than the maximum allowed, we coalesce the vectors into
        // a single big buffer
#ifdef __solaris__
    if (inNumVectors > IOV_MAX )
    {
            char* describeDataBuffer = QTSSModuleUtils::CoalesceVectors(describeData, inNumVectors, inTotalLength);
            (void)QTSS_Write(inRequest, (void *)describeDataBuffer, inTotalLength, NULL, qtssWriteFlagsNoFlags);
            // deleting memory allocated by the CoalesceVectors call
            delete [] describeDataBuffer;
    }
    else
        (void)QTSS_WriteV(inRequest, describeData, inNumVectors, inTotalLength, NULL);
#else
    (void)QTSS_WriteV(inRequest, describeData, inNumVectors, inTotalLength, NULL);
#endif

}
QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParams, ReflectorSession* inSession)
{
    QTSS_Error theErr = QTSS_NoErr;
    UInt32 flags = 0;
    UInt32 theLen = 0;
    
    if (inSession == NULL) // it is a broadcast session so store the broadcast session.
		return QTSS_RequestFailed;
    else
    {   
        UInt32 bitsPerSecond =  inSession->GetBitRate();
        (void)QTSS_SetValue(inParams->inClientSession, qtssCliSesMovieAverageBitRate, 0, &bitsPerSecond, sizeof(bitsPerSecond));
   
        QTSS_Error theErr = QTSS_Play(inParams->inClientSession, inParams->inRTSPRequest, qtssPlayFlagsAppendServerInfo);
        if (theErr != QTSS_NoErr)
            return theErr;
    }
    
	OSRef* theRelaySessionRef = sClientSessionMap->Resolve(inSession->GetRef()->GetString());
	if(theRelaySessionRef != NULL)
	{
		RTSPRelaySession* relaySes = (RTSPRelaySession*)theRelaySessionRef->GetObject();
		QTSS_Error err = relaySes->Start();
		sClientSessionMap->Release(theRelaySessionRef);
	}
	else
	{
		return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssServerInternal, 0);
	}

    (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, flags);
    return QTSS_NoErr;
}
QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams)
{
    QTSS_RTSPMethod* theMethod = NULL;

	UInt32 theLen = 0;
    if ((QTSS_GetValuePtr(inParams->inRTSPRequest, qtssRTSPReqMethod, 0,
            (void**)&theMethod, &theLen) != QTSS_NoErr) || (theLen != sizeof(QTSS_RTSPMethod)))
    {
        Assert(0);
        return QTSS_RequestFailed;
    }

    if (*theMethod == qtssDescribeMethod)
        return DoDescribe(inParams);
    if (*theMethod == qtssSetupMethod)
        return DoSetup(inParams);
        
    RTPSessionOutput** theOutput = NULL;
    QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(RTPSessionOutput*))) // a broadcaster push session
    {   if (*theMethod == qtssPlayMethod || *theMethod == qtssRecordMethod)
            return DoPlay(inParams, NULL);
        else
            return QTSS_RequestFailed;
    }

    switch (*theMethod)
    {
        case qtssPlayMethod:
            return DoPlay(inParams, (*theOutput)->GetReflectorSession());
        case qtssTeardownMethod:
            // Tell the server that this session should be killed, and send a TEARDOWN response
            (void)QTSS_Teardown(inParams->inClientSession);
            (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0);
            break;
        case qtssPauseMethod:
            (void)QTSS_Pause(inParams->inClientSession);
            (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, inParams->inClientSession, 0);
            break;
        default:
            break;
    }           
    return QTSS_NoErr;
}
QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParams)
{
    ReflectorSession* theSession = NULL;
	UInt32 len = sizeof(theSession);
	QTSS_GetValue(inParams->inRTSPSession, sRTSPBroadcastSessionAttr, 0, &theSession, &len);

	if(theSession == NULL)
		return QTSS_RequestFailed;

    Bool16 foundSession = false;
    UInt32 theLen = 0;
    RTPSessionOutput** theOutput = NULL;
    QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sOutputAttr, 0, (void**)&theOutput, &theLen);
    if (theLen != sizeof(RTPSessionOutput*))
	{
        if (theErr != QTSS_NoErr)
        {            
            RTPSessionOutput* theNewOutput = NEW RTPSessionOutput(inParams->inClientSession, theSession, sServerPrefs, sStreamCookieAttr );
            theSession->AddOutput(theNewOutput,true);
            (void)QTSS_SetValue(inParams->inClientSession, sOutputAttr, 0, &theNewOutput, sizeof(theNewOutput));
        }
    }

    //unless there is a digit at the end of this path (representing trackID), don't
    //even bother with the request
    char* theDigitStr = NULL;
    (void)QTSS_GetValueAsString(inParams->inRTSPRequest, qtssRTSPReqFileDigit, 0, &theDigitStr);
    QTSSCharArrayDeleter theDigitStrDeleter(theDigitStr);
    if (theDigitStr == NULL)
    {
        return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest,sExpectedDigitFilenameErr);
    }
    
    UInt32 theTrackID = ::strtol(theDigitStr, NULL, 10);
   
    // Get info about this trackID
    SourceInfo::StreamInfo* theStreamInfo = theSession->GetSourceInfo()->GetStreamInfoByTrackID(theTrackID);
    // If theStreamInfo is NULL, we don't have a legit track, so return an error
    if (theStreamInfo == NULL)
        return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest,
                                                    sReflectorBadTrackIDErr);
                                                    
    StrPtrLen* thePayloadName = &theStreamInfo->fPayloadName;
    QTSS_RTPPayloadType thePayloadType = theStreamInfo->fPayloadType;

    StringParser parser(thePayloadName);
    
    parser.GetThru(NULL, '/');
    theStreamInfo->fTimeScale = parser.ConsumeInteger(NULL);
    if (theStreamInfo->fTimeScale == 0)
        theStreamInfo->fTimeScale = 90000;
    
    QTSS_RTPStreamObject newStream = NULL;
    {
        // Ok, this is completely crazy but I can't think of a better way to do this that's
        // safe so we'll do it this way for now. Because the ReflectorStreams use this session's
        // stream queue, we need to make sure that each ReflectorStream is not reflecting to this
        // session while we call QTSS_AddRTPStream. One brutal way to do this is to grab each
        // ReflectorStream's mutex, which will stop every reflector stream from running.
        
        for (UInt32 x = 0; x < theSession->GetNumStreams(); x++)
            theSession->GetStreamByIndex(x)->GetMutex()->Lock();
            
        theErr = QTSS_AddRTPStream(inParams->inClientSession, inParams->inRTSPRequest, &newStream, 0);

        for (UInt32 y = 0; y < theSession->GetNumStreams(); y++)
            theSession->GetStreamByIndex(y)->GetMutex()->Unlock();
            
        if (theErr != QTSS_NoErr)
            return theErr;
    }
    
    // Set up dictionary items for this stream
    theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadName, 0, thePayloadName->Ptr, thePayloadName->Len);
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadType, 0, &thePayloadType, sizeof(thePayloadType));
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrTrackID, 0, &theTrackID, sizeof(theTrackID));
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrTimescale, 0, &theStreamInfo->fTimeScale, sizeof(theStreamInfo->fTimeScale));
    Assert(theErr == QTSS_NoErr);

    // We only want to allow over buffering to dynamic rate clients   
    SInt32  canDynamicRate = -1;
    theLen = sizeof(canDynamicRate);
    (void) QTSS_GetValue(inParams->inRTSPRequest, qtssRTSPReqDynamicRateState, 0, (void*) &canDynamicRate, &theLen);
    if (canDynamicRate < 1) // -1 no rate field, 0 off
        (void)QTSS_SetValue(inParams->inClientSession, qtssCliSesOverBufferEnabled, 0, &sFalse, sizeof(sFalse));

    // Place the stream cookie in this stream for future reference
    void* theStreamCookie = theSession->GetStreamCookie(theTrackID);
    Assert(theStreamCookie != NULL);
    theErr = QTSS_SetValue(newStream, sStreamCookieAttr, 0, &theStreamCookie, sizeof(theStreamCookie));
    Assert(theErr == QTSS_NoErr);

    // Set the number of quality levels.
    static UInt32 sNumQualityLevels = ReflectorSession::kNumQualityLevels;
    theErr = QTSS_SetValue(newStream, qtssRTPStrNumQualityLevels, 0, &sNumQualityLevels, sizeof(sNumQualityLevels));
    Assert(theErr == QTSS_NoErr);
    
    //send the setup response
    (void)QTSS_AppendRTSPHeader(inParams->inRTSPRequest, qtssCacheControlHeader,
                                kCacheControlHeader.Ptr, kCacheControlHeader.Len);
    (void)QTSS_SendStandardRTSPResponse(inParams->inRTSPRequest, newStream, qtssSetupRespDontWriteSSRC);
    return QTSS_NoErr;
}
Esempio n. 6
0
QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParamBlock)
{
    FileSession** theFile = NULL;
    UInt32 theLen = 0;
    QTSS_Error theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)))
        return QTSS_RequestFailed;

    Float64* theStartTime = 0;
    theErr = QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqStartTime, 0, (void**)&theStartTime, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(Float64)))
        return QTSSModuleUtils::SendErrorResponse(  inParamBlock->inRTSPRequest,
                                                    qtssClientBadRequest, sSeekToNonexistentTimeErr);
                                                    
    RTPFileSession::ErrorCode qtFileErr = (*theFile)->fFile.Seek(*theStartTime);
    if (qtFileErr != RTPFileSession::errNoError)
        return QTSSModuleUtils::SendErrorResponse(  inParamBlock->inRTSPRequest,
                                                    qtssClientBadRequest, sSeekToNonexistentTimeErr);
                                                        
    //make sure to clear the next packet the server would have sent!
    (*theFile)->fPacketStruct.packetData = NULL;
    
    // Set the movie duration and size parameters
    Float64 movieDuration = (*theFile)->fFile.GetMovieDuration();
    (void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieDurationInSecs, 0, &movieDuration, sizeof(movieDuration));
    
    UInt64 movieSize = (*theFile)->fFile.GetAddedTracksRTPBytes();
    (void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieSizeInBytes, 0, &movieSize, sizeof(movieSize));
    
    //UInt32 bitsPerSecond =    (*theFile)->fFile.GetBytesPerSecond() * 8;
    //(void)QTSS_SetValue(inParamBlock->inClientSession, qtssCliSesMovieAverageBitRate, 0, &bitsPerSecond, sizeof(bitsPerSecond));

    //
    // For the purposes of the speed header, check to make sure all tracks are
    // over a reliable transport
    Bool16 allTracksReliable = true;
    
    // Set the timestamp & sequence number parameters for each track.
    QTSS_RTPStreamObject* theRef = NULL;
    for (   UInt32 theStreamIndex = 0;
            QTSS_GetValuePtr(inParamBlock->inClientSession, qtssCliSesStreamObjects, theStreamIndex, (void**)&theRef, &theLen) == QTSS_NoErr;
            theStreamIndex++)
    {
        UInt32* theTrackID = NULL;
        theErr = QTSS_GetValuePtr(*theRef, qtssRTPStrTrackID, 0, (void**)&theTrackID, &theLen);
        Assert(theErr == QTSS_NoErr);
        Assert(theTrackID != NULL);
        Assert(theLen == sizeof(UInt32));
        
        SInt16 theSeqNum = (*theFile)->fFile.GetNextTrackSequenceNumber(*theTrackID);
        SInt32 theTimestamp = (*theFile)->fFile.GetSeekTimestamp(*theTrackID);
        
        Assert(theRef != NULL);
        Assert(theLen == sizeof(QTSS_RTPStreamObject));
        
        theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstSeqNumber, 0, &theSeqNum, sizeof(theSeqNum));
        Assert(theErr == QTSS_NoErr);
        theErr = QTSS_SetValue(*theRef, qtssRTPStrFirstTimestamp, 0, &theTimestamp, sizeof(theTimestamp));
        Assert(theErr == QTSS_NoErr);

        if (allTracksReliable)
        {
            QTSS_RTPTransportType theTransportType = qtssRTPTransportTypeUDP;
            theLen = sizeof(theTransportType);
            theErr = QTSS_GetValue(*theRef, qtssRTPStrTransportType, 0, &theTransportType, &theLen);
            Assert(theErr == QTSS_NoErr);
            
            if (theTransportType == qtssRTPTransportTypeUDP)
                allTracksReliable = false;
        }
    }
    
    //Tell the server to start playing this movie. We do want it to send RTCP SRs, but
    //we DON'T want it to write the RTP header
    theErr = QTSS_Play(inParamBlock->inClientSession, inParamBlock->inRTSPRequest, qtssPlayFlagsSendRTCP);
    if (theErr != QTSS_NoErr)
        return theErr;

    // qtssRTPSesAdjustedPlayTimeInMsec is valid only after calling QTSS_Play
    // theAdjustedPlayTime is a way to delay the sending of data until a time after the 
    // the Play response should have been received.
    SInt64* theAdjustedPlayTime = 0;
    theErr = QTSS_GetValuePtr(inParamBlock->inClientSession, qtssCliSesAdjustedPlayTimeInMsec, 0, (void**)&theAdjustedPlayTime, &theLen);
    Assert(theErr == QTSS_NoErr);
    Assert(theAdjustedPlayTime != NULL);
    Assert(theLen == sizeof(SInt64));
    (*theFile)->fAdjustedPlayTime = *theAdjustedPlayTime;
    
    //
    // This module supports the Speed header if the client wants the stream faster than normal.
    Float32 theSpeed = 1;
    theLen = sizeof(theSpeed);
    theErr = QTSS_GetValue(inParamBlock->inRTSPRequest, qtssRTSPReqSpeed, 0, &theSpeed, &theLen);
    Assert(theErr != QTSS_BadArgument);
    Assert(theErr != QTSS_NotEnoughSpace);
    
    if (theErr == QTSS_NoErr)
    {
        if (theSpeed > sMaxAllowedSpeed)
            theSpeed = sMaxAllowedSpeed;
        if ((theSpeed <= 0) || (!allTracksReliable))
            theSpeed = 1;
    }
    
    (*theFile)->fSpeed = theSpeed;
    
    if (theSpeed != 1)
    {
        //
        // If our speed is not 1, append the RTSP speed header in the response
        char speedBuf[32];
        qtss_sprintf(speedBuf, "%10.5f", theSpeed);
        StrPtrLen speedBufPtr(speedBuf);
        (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssSpeedHeader,
                                    speedBufPtr.Ptr, speedBufPtr.Len);
    }
    
    //
    // Record the requested start and stop time.

    (*theFile)->fStopTime = -1;
    theLen = sizeof((*theFile)->fStopTime);
    theErr = QTSS_GetValue(inParamBlock->inRTSPRequest, qtssRTSPReqStopTime, 0, &(*theFile)->fStopTime, &theLen);
    Assert(theErr == QTSS_NoErr);
            
    (*theFile)->fStartTime = 0;
    theLen = sizeof((*theFile)->fStopTime);
    theErr = QTSS_GetValue(inParamBlock->inRTSPRequest, qtssRTSPReqStartTime, 0, &(*theFile)->fStartTime, &theLen);
    Assert(theErr == QTSS_NoErr);

    //the client doesn't necessarily specify this information in a play,
    //if it doesn't, fall back on some defaults.
    if ((*theFile)->fStartTime == -1)
        (*theFile)->fStartTime = 0;
        
    (void)QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, qtssPlayRespWriteTrackInfo);
    return QTSS_NoErr;
}
Esempio n. 7
0
QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParamBlock)
{
    //setup this track in the file object 
    FileSession* theFile = NULL;
    UInt32 theLen = sizeof(FileSession*);
    QTSS_Error theErr = QTSS_GetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, (void*)&theFile, &theLen);
    if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)))
    {
        // This is possible, as clients are not required to send a DESCRIBE. If we haven't set
        // anything up yet, set everything up
		char* theFullPathStr = NULL;
        theErr = QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFullPathStr);
        Assert(theErr == QTSS_NoErr);
		QTSSCharArrayDeleter theFullPathDeleter(theFullPathStr);
		StrPtrLen theFullPath(theFullPathStr);
        	
        if ((theFullPath.Len <= sRTPSuffix.Len) ||
            (!sRTPSuffix.NumEqualIgnoreCase(&theFullPath.Ptr[theFullPath.Len - sRTPSuffix.Len], sRTPSuffix.Len)))
            return QTSS_RequestFailed;

        theErr = CreateRTPFileSession(inParamBlock, theFullPath, &theFile);
        if (theErr != QTSS_NoErr)
            return theErr;

        // Store this newly created file object in the RTP session.
        theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile));
    }

    //unless there is a digit at the end of this path (representing trackID), don't
    //even bother with the request
    char* theDigitStr = NULL;
    (void)QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqFileDigit, 0, &theDigitStr);
    QTSSCharArrayDeleter theDigitStrDeleter(theDigitStr);
	if (theDigitStr == NULL)
        return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest,
                                                    qtssClientBadRequest, sExpectedDigitFilenameErr);
    
    UInt32 theTrackID = ::strtol(theDigitStr, NULL, 10);
	
    RTPFileSession::ErrorCode qtfileErr = theFile->fFile.AddTrack(theTrackID);
    
    //if we get an error back, forward that error to the client
    if (qtfileErr == RTPFileSession::errTrackDoesntExist)
        return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest,
                                                    qtssClientNotFound, sTrackDoesntExistErr);
    else if (qtfileErr != RTPFileSession::errNoError)
        return QTSSModuleUtils::SendErrorResponse(inParamBlock->inRTSPRequest,
                                                    qtssUnsupportedMediaType, sBadQTFileErr);

    //find the payload for this track ID (if applicable)
    StrPtrLen* thePayload = NULL;
    UInt32 thePayloadType = qtssUnknownPayloadType;
    Float32 bufferDelay = (Float32) 3.0; // FIXME need a constant defined for 3.0 value. It is used multiple places
    
    for (UInt32 x = 0; x < theFile->fFile.GetSourceInfo()->GetNumStreams(); x++)
    {
        SourceInfo::StreamInfo* theStreamInfo = theFile->fFile.GetSourceInfo()->GetStreamInfo(x);
        if (theStreamInfo->fTrackID == theTrackID)
        {
            thePayload = &theStreamInfo->fPayloadName;
            thePayloadType = theStreamInfo->fPayloadType;
            bufferDelay = theStreamInfo->fBufferDelay;
            break;
        }   
    }

    //Create a new RTP stream           
    QTSS_RTPStreamObject newStream = NULL;
    theErr = QTSS_AddRTPStream(inParamBlock->inClientSession, inParamBlock->inRTSPRequest, &newStream, 0);
    if (theErr != QTSS_NoErr)
        return theErr;
    
    // Set the payload type, payload name & timescale of this track
    SInt32 theTimescale = theFile->fFile.GetTrackTimeScale(theTrackID);
    
    theErr = QTSS_SetValue(newStream, qtssRTPStrBufferDelayInSecs, 0, &bufferDelay, sizeof(bufferDelay));
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadName, 0, thePayload->Ptr, thePayload->Len);
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrPayloadType, 0, &thePayloadType, sizeof(thePayloadType));
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrTimescale, 0, &theTimescale, sizeof(theTimescale));
    Assert(theErr == QTSS_NoErr);
    theErr = QTSS_SetValue(newStream, qtssRTPStrTrackID, 0, &theTrackID, sizeof(theTrackID));
    Assert(theErr == QTSS_NoErr);
    
    // Set the number of quality levels. Allow up to 6
    static UInt32 sNumQualityLevels = 0;
    
    theErr = QTSS_SetValue(newStream, qtssRTPStrNumQualityLevels, 0, &sNumQualityLevels, sizeof(sNumQualityLevels));
    Assert(theErr == QTSS_NoErr);
    
    // Get the SSRC of this track
    UInt32* theTrackSSRC = NULL;
    UInt32 theTrackSSRCSize = 0;
    (void)QTSS_GetValuePtr(newStream, qtssRTPStrSSRC, 0, (void**)&theTrackSSRC, &theTrackSSRCSize);

    // The RTP stream should ALWAYS have an SSRC assuming QTSS_AddStream succeeded.
    Assert((theTrackSSRC != NULL) && (theTrackSSRCSize == sizeof(UInt32)));
    
    //give the file some info it needs.
    theFile->fFile.SetTrackSSRC(theTrackID, *theTrackSSRC);
    theFile->fFile.SetTrackCookie(theTrackID, newStream);
    
    //
    // Our array has now been updated to reflect the fields requested by the client.
    //send the setup response
    //(void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssLastModifiedHeader,
    //                          theFile->fFile.GetQTFile()->GetModDateStr(), DateBuffer::kDateBufferLen);
    (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssCacheControlHeader,
                                kCacheControlHeader.Ptr, kCacheControlHeader.Len);

    //send the setup response
    (void)QTSS_SendStandardRTSPResponse(inParamBlock->inRTSPRequest, newStream, 0);
    return QTSS_NoErr;
}