void RTSPRequest::ParseTransportOptionsHeader() { StringParser theRTPOptionsParser(fHeaderDictionary.GetValue(qtssXTransportOptionsHeader)); StrPtrLen theRTPOptionsSubHeader; do { static StrPtrLen sLateTolerance("late-tolerance"); if (theRTPOptionsSubHeader.NumEqualIgnoreCase(sLateTolerance.Ptr, sLateTolerance.Len)) { StringParser theLateTolParser(&theRTPOptionsSubHeader); theLateTolParser.GetThru(NULL,'='); theLateTolParser.ConsumeWhitespace(); fLateTolerance = theLateTolParser.ConsumeFloat(); fLateToleranceStr = theRTPOptionsSubHeader; } (void)theRTPOptionsParser.GetThru(&theRTPOptionsSubHeader, ';'); } while(theRTPOptionsSubHeader.Len > 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; }
void RelaySDPSourceInfo::Parse(StrPtrLen* inSDPData) { // These are the lines of the SDP file that we are interested in static StrPtrLen sRelayAddr("a=x-qt-relay-addr"); static StrPtrLen sRelayPort("a=x-qt-relay-port"); Assert(fOutputArray == NULL); Assert(fStreamArray == NULL); StrPtrLen sdpLine; StrPtrLen outputAddrs; StringParser trackCounter(inSDPData); UInt32 theDestIPAddr = 0; UInt16 theDestTtl = 0; // // FIRST WALK THROUGH SDP // The first walk is to count up the number of StreamInfo & OutputInfo // objects that we will need. while (true) { // grab a line trackCounter.ConsumeUntil(&sdpLine, StringParser::sEOLMask); if (sdpLine.NumEqualIgnoreCase(sRelayAddr.Ptr, sRelayAddr.Len)) { // there is a x-qt-relay-addr line, look for all IP addrs StringParser relayAddrParser(&sdpLine); relayAddrParser.ConsumeUntil(NULL, StringParser::sDigitMask); // The first IP addr on this line is the destination IP addr of the // source broadcast. theDestIPAddr = SDPSourceInfo::GetIPAddr(&relayAddrParser, ' '); relayAddrParser.ConsumeWhitespace(); // Store this position so we can later go back to it outputAddrs.Ptr = relayAddrParser.GetCurrentPosition(); outputAddrs.Len = relayAddrParser.GetDataRemaining(); StrPtrLen theTtl; while (relayAddrParser.GetDataRemaining() > 0) { relayAddrParser.ConsumeUntil(&theTtl, ' '); relayAddrParser.ConsumeWhitespace(); fNumOutputs++; } fNumOutputs--;// Don't count the ttl as an output! StringParser ttlParser(&theTtl); theDestTtl = (UInt16) ttlParser.ConsumeInteger(NULL); } // Each x=qt-relay-port line corresponds to one source stream. else if (sdpLine.NumEqualIgnoreCase(sRelayPort.Ptr, sRelayPort.Len)) fNumStreams++; //stop when we reach an empty line. if (!trackCounter.ExpectEOL()) break; } // No relay info in this file! if ((fNumStreams == 0) || (fNumOutputs == 0)) return; // x-qt-relay-port lines should always be in pairs (RTP & RTCP) if ((fNumStreams & 1) != 0) return; fNumStreams /= 2; // // CONSTRUCT fStreamInfo AND fOutputInfo ARRAYS fStreamArray = NEW StreamInfo[fNumStreams]; fOutputArray = NEW OutputInfo[fNumOutputs]; // // FILL IN ARRAYS // Filling in the output addresses is easy because the outputAddrs // StrPtrLen points right at the data we want StringParser theOutputAddrParser(&outputAddrs); for (UInt32 x = 0; x < fNumOutputs; x++) { fOutputArray[x].fDestAddr = SDPSourceInfo::GetIPAddr(&theOutputAddrParser, ' '); fOutputArray[x].fLocalAddr = INADDR_ANY; fOutputArray[x].fTimeToLive = theDestTtl; fOutputArray[x].fPortArray = NEW UInt16[fNumStreams];//Each output has one port per stream fOutputArray[x].fNumPorts = fNumStreams; ::memset(fOutputArray[x].fPortArray, 0, fNumStreams * sizeof(UInt16)); fOutputArray[x].fAlreadySetup = false; theOutputAddrParser.ConsumeWhitespace(); Assert(fOutputArray[x].fDestAddr > 0); } StringParser sdpParser(inSDPData); // Now go through and find all the port information on all the x-qt-relay-port lines for (UInt32 theStreamIndex = 0; theStreamIndex < fNumStreams; ) { sdpParser.ConsumeUntil(&sdpLine, StringParser::sEOLMask); // parse through all the x-qt-relay-port lines if (sdpLine.NumEqualIgnoreCase(sRelayPort.Ptr, sRelayPort.Len)) { // Begin parsing... find the first port on the line StringParser relayAddrParser(&sdpLine); relayAddrParser.ConsumeUntil(NULL, StringParser::sDigitMask); // The first port is the source port for this stream fStreamArray[theStreamIndex].fPort = (UInt16) relayAddrParser.ConsumeInteger(NULL); if (fStreamArray[theStreamIndex].fPort & 1) continue; //we only care about RTP ports // Fill in all the fields we can for this stream fStreamArray[theStreamIndex].fDestIPAddr = theDestIPAddr; fStreamArray[theStreamIndex].fTimeToLive = theDestTtl; fStreamArray[theStreamIndex].fTrackID = theStreamIndex + 1; // Now fill in all the output ports for this stream for (UInt32 x = 0; x < fNumOutputs; x++) { relayAddrParser.ConsumeWhitespace(); fOutputArray[x].fPortArray[theStreamIndex] = (UInt16) relayAddrParser.ConsumeInteger(NULL); } theStreamIndex++; } //stop when we reach an empty line. if (!sdpParser.ExpectEOL()) break; } }
QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParamBlock) { // Check and see if this is a request we should handle. We handle all requests with URLs that // end in a '.rtp' char* theFullPathStr = NULL; QTSS_Error theError = QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqLocalPath, 0, &theFullPathStr); QTSSCharArrayDeleter theFullPathDeleter(theFullPathStr); Assert(theError == QTSS_NoErr); StrPtrLen theFullPath(theFullPathStr); if ((theFullPath.Len <= sRTPSuffix.Len) || (!sRTPSuffix.NumEqualIgnoreCase(&theFullPath.Ptr[theFullPath.Len - sRTPSuffix.Len], sRTPSuffix.Len))) return QTSS_RequestFailed; // It is, so let's set everything up... // // Get the FileSession for this DESCRIBE, if any. UInt32 theLen = sizeof(FileSession*); FileSession* theFile = NULL; QTSS_Error theErr = QTSS_NoErr; (void)QTSS_GetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, (void*)&theFile, &theLen); if ( theFile != NULL ) { // // There is already a file for this session. This can happen if there are multiple DESCRIBES, // or a DESCRIBE has been issued with a Session ID, or some such thing. if ( !theFullPath.Equal( *theFile->fFile.GetMoviePath() ) ) { delete theFile; theFile = NULL; // NULL out the attribute value, just in case. (void)QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile)); } } if ( theFile == NULL ) { theErr = CreateRTPFileSession(inParamBlock, theFullPath, &theFile); if (theErr != QTSS_NoErr) { (void)QTSS_Teardown(inParamBlock->inClientSession); return theErr; } // Store this newly created file object in the RTP session. theErr = QTSS_SetValue(inParamBlock->inClientSession, sFileSessionAttr, 0, &theFile, sizeof(theFile)); } //generate the SDP. UInt32 totalSDPLength = sSDPHeader1.Len; iovec theSDPVec[10];//1 for the RTSP header, 6 for the sdp header, 1 for the sdp body theSDPVec[1].iov_base = sSDPHeader1.Ptr; theSDPVec[1].iov_len = sSDPHeader1.Len; //filename goes here //(void)QTSS_GetValuePtr(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, (void**)&theSDPVec[2].iov_base, (UInt32*)&theSDPVec[2].iov_len); char* filenameStr = NULL; (void)QTSS_GetValueAsString(inParamBlock->inRTSPRequest, qtssRTSPReqFilePath, 0, &filenameStr); QTSSCharArrayDeleter filenameStrDeleter(filenameStr); theSDPVec[2].iov_base = filenameStr; theSDPVec[2].iov_len = ::strlen(filenameStr); totalSDPLength += theSDPVec[2].iov_len; //url & admin email goes here theSDPVec[3].iov_base = sSDPHeader2.Ptr; theSDPVec[3].iov_len = sSDPHeader2.Len; totalSDPLength += sSDPHeader2.Len; //connection header theSDPVec[4].iov_base = sSDPHeader3.Ptr; theSDPVec[4].iov_len = sSDPHeader3.Len; totalSDPLength += sSDPHeader3.Len; //append IP addr (void)QTSS_GetValuePtr(inParamBlock->inRTSPSession, qtssRTSPSesLocalAddrStr, 0, (void**)&theSDPVec[5].iov_base, (UInt32*)&theSDPVec[5].iov_len); totalSDPLength += theSDPVec[5].iov_len; //last static sdp line theSDPVec[6].iov_base = sSDPHeader4.Ptr; theSDPVec[6].iov_len = sSDPHeader4.Len; totalSDPLength += sSDPHeader4.Len; //now append content-determined sdp theSDPVec[7].iov_base = theFile->fFile.GetSDPFile()->Ptr; theSDPVec[7].iov_len = theFile->fFile.GetSDPFile()->Len; totalSDPLength += theSDPVec[7].iov_len; Assert(theSDPVec[2].iov_base != NULL); // Append the Last Modified header to be a good caching proxy citizen before sending the Describe //(void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssLastModifiedHeader, // theFile->fFile.GetQTFile()->GetModDateStr(), DateBuffer::kDateBufferLen); (void)QTSS_AppendRTSPHeader(inParamBlock->inRTSPRequest, qtssCacheControlHeader, kCacheControlHeader.Ptr, kCacheControlHeader.Len); //ok, we have a filled out iovec. Let's send it! QTSSModuleUtils::SendDescribeResponse(inParamBlock->inRTSPRequest, inParamBlock->inClientSession, &theSDPVec[0], 8, totalSDPLength); return QTSS_NoErr; }