/* * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ * */ /* File: SDPSourceInfo.cpp Contains: Implementation of object defined in .h file */ #include "SDPSourceInfo.h" #include "StringParser.h" #include "StringFormatter.h" #include "OSMemory.h" #include "SocketUtils.h" #include "StrPtrLen.h" #include "SDPUtils.h" #include "OSArrayObjectDeleter.h" static StrPtrLen sCLine("c=IN IP4 0.0.0.0"); static StrPtrLen sControlLine("a=control:*"); static StrPtrLen sVideoStr("video"); static StrPtrLen sAudioStr("audio"); static StrPtrLen sRtpMapStr("rtpmap"); static StrPtrLen sControlStr("control"); static StrPtrLen sBufferDelayStr("x-bufferdelay"); static StrPtrLen sBroadcastControlStr("x-broadcastcontrol"); static StrPtrLen sAutoDisconnect("RTSP"); static StrPtrLen sAutoDisconnectTime("TIME"); SDPSourceInfo::~SDPSourceInfo() { // Not reqd as the destructor of the // base class will take care of delete the stream array // and output array if allocated /* if (fStreamArray != NULL) { char* theCharArray = (char*)fStreamArray; delete [] theCharArray; } */ fSDPData.Delete(); } char* SDPSourceInfo::GetLocalSDP(UInt32* newSDPLen) { Assert(fSDPData.Ptr != NULL); Bool16 appendCLine = true; UInt32 trackIndex = 0; char *localSDP = NEW char[fSDPData.Len * 2]; OSCharArrayDeleter charArrayPathDeleter(localSDP); StringFormatter localSDPFormatter(localSDP, fSDPData.Len * 2); StrPtrLen sdpLine; StringParser sdpParser(&fSDPData); char trackIndexBuffer[50]; // Only generate our own trackIDs if this file doesn't have 'em. // Our assumption here is that either the file has them, or it doesn't. // A file with some trackIDs, and some not, won't work. Bool16 hasControlLine = false; while (sdpParser.GetDataRemaining() > 0) { //stop when we reach an empty line. sdpParser.GetThruEOL(&sdpLine); if (sdpLine.Len == 0) continue; switch (*sdpLine.Ptr) { case 'c': break;//ignore connection information case 'm': { //append new connection information right before the first 'm' if (appendCLine) { localSDPFormatter.Put(sCLine); localSDPFormatter.PutEOL(); if (!hasControlLine) { localSDPFormatter.Put(sControlLine); localSDPFormatter.PutEOL(); } appendCLine = false; } //the last "a=" for each m should be the control a= if ((trackIndex > 0) && (!hasControlLine)) { qtss_sprintf(trackIndexBuffer, "a=control:trackID=%"_S32BITARG_"\r\n",trackIndex); localSDPFormatter.Put(trackIndexBuffer, ::strlen(trackIndexBuffer)); } //now write the 'm' line, but strip off the port information StringParser mParser(&sdpLine); StrPtrLen mPrefix; mParser.ConsumeUntil(&mPrefix, StringParser::sDigitMask); localSDPFormatter.Put(mPrefix); localSDPFormatter.Put("0", 1); (void)mParser.ConsumeInteger(NULL); localSDPFormatter.Put(mParser.GetCurrentPosition(), mParser.GetDataRemaining()); localSDPFormatter.PutEOL(); trackIndex++; break; } case 'a': { StringParser aParser(&sdpLine); aParser.ConsumeLength(NULL, 2);//go past 'a=' StrPtrLen aLineType; aParser.ConsumeWord(&aLineType); if (aLineType.Equal(sControlStr)) { aParser.ConsumeUntil(NULL, '='); aParser.ConsumeUntil(NULL, StringParser::sDigitMask); StrPtrLen aDigitType; (void)aParser.ConsumeInteger(&aDigitType); if (aDigitType.Len > 0) { localSDPFormatter.Put("a=control:trackID=", 18); localSDPFormatter.Put(aDigitType); localSDPFormatter.PutEOL(); hasControlLine = true; break; } } localSDPFormatter.Put(sdpLine); localSDPFormatter.PutEOL(); break; } default: { localSDPFormatter.Put(sdpLine); localSDPFormatter.PutEOL(); } } } if ((trackIndex > 0) && (!hasControlLine)) { qtss_sprintf(trackIndexBuffer, "a=control:trackID=%"_S32BITARG_"\r\n",trackIndex); localSDPFormatter.Put(trackIndexBuffer, ::strlen(trackIndexBuffer)); } *newSDPLen = (UInt32)localSDPFormatter.GetCurrentOffset(); StrPtrLen theSDPStr(localSDP, *newSDPLen);//localSDP is not 0 terminated so initialize theSDPStr with the len. SDPContainer rawSDPContainer; (void) rawSDPContainer.SetSDPBuffer( &theSDPStr ); SDPLineSorter sortedSDP(&rawSDPContainer); return sortedSDP.GetSortedSDPCopy(); // return a new copy of the sorted SDP } void SDPSourceInfo::Parse(char* sdpData, UInt32 sdpLen) { // // There are some situations in which Parse can be called twice. // If that happens, just return and don't do anything the second time. if (fSDPData.Ptr != NULL) return; Assert(fStreamArray == NULL); char *sdpDataCopy = NEW char[sdpLen]; Assert(sdpDataCopy != NULL); memcpy(sdpDataCopy,sdpData, sdpLen); fSDPData.Set(sdpDataCopy, sdpLen); // If there is no trackID information in this SDP, we make the track IDs start // at 1 -> N UInt32 currentTrack = 1; Bool16 hasGlobalStreamInfo = false; StreamInfo theGlobalStreamInfo; //needed if there is one c= header independent of //individual streams StrPtrLen sdpLine; StringParser trackCounter(&fSDPData); StringParser sdpParser(&fSDPData); UInt32 theStreamIndex = 0; //walk through the SDP, counting up the number of tracks // Repeat until there's no more data in the SDP while (trackCounter.GetDataRemaining() > 0) { //each 'm' line in the SDP file corresponds to another track. trackCounter.GetThruEOL(&sdpLine); if ((sdpLine.Len > 0) && (sdpLine.Ptr[0] == 'm')) fNumStreams++; } //We should scale the # of StreamInfos to the # of trax, but we can't because //of an annoying compiler bug... fStreamArray = NEW StreamInfo[fNumStreams]; ::memset(fStreamArray, 0, sizeof(StreamInfo) * fNumStreams); // set the default destination as our default IP address and set the default ttl theGlobalStreamInfo.fDestIPAddr = INADDR_ANY; theGlobalStreamInfo.fTimeToLive = kDefaultTTL; //Set bufferdelay to default of 3 theGlobalStreamInfo.fBufferDelay = (Float32) eDefaultBufferDelay; //Now actually get all the data on all the streams while (sdpParser.GetDataRemaining() > 0) { sdpParser.GetThruEOL(&sdpLine); if (sdpLine.Len == 0) continue;//skip over any blank lines switch (*sdpLine.Ptr) { case 't': { StringParser mParser(&sdpLine); mParser.ConsumeUntil(NULL, StringParser::sDigitMask); UInt32 ntpStart = mParser.ConsumeInteger(NULL); mParser.ConsumeUntil(NULL, StringParser::sDigitMask); UInt32 ntpEnd = mParser.ConsumeInteger(NULL); SetActiveNTPTimes(ntpStart,ntpEnd); } break; case 'm': { if (hasGlobalStreamInfo) { fStreamArray[theStreamIndex].fDestIPAddr = theGlobalStreamInfo.fDestIPAddr; fStreamArray[theStreamIndex].fTimeToLive = theGlobalStreamInfo.fTimeToLive; } fStreamArray[theStreamIndex].fTrackID = currentTrack; currentTrack++; StringParser mParser(&sdpLine); //find out what type of track this is mParser.ConsumeLength(NULL, 2);//go past 'm=' StrPtrLen theStreamType; mParser.ConsumeWord(&theStreamType); if (theStreamType.Equal(sVideoStr)) fStreamArray[theStreamIndex].fPayloadType = qtssVideoPayloadType; else if (theStreamType.Equal(sAudioStr)) fStreamArray[theStreamIndex].fPayloadType = qtssAudioPayloadType; //find the port for this stream mParser.ConsumeUntil(NULL, StringParser::sDigitMask); SInt32 tempPort = mParser.ConsumeInteger(NULL); if ((tempPort > 0) && (tempPort < 65536)) fStreamArray[theStreamIndex].fPort = (UInt16) tempPort; // find out whether this is TCP or UDP mParser.ConsumeWhitespace(); StrPtrLen transportID; mParser.ConsumeWord(&transportID); static const StrPtrLen kTCPTransportStr("RTP/AVP/TCP"); if (transportID.Equal(kTCPTransportStr)) fStreamArray[theStreamIndex].fIsTCP = true; theStreamIndex++; } break; case 'a': { StringParser aParser(&sdpLine); aParser.ConsumeLength(NULL, 2);//go past 'a=' StrPtrLen aLineType; aParser.ConsumeWord(&aLineType); if (aLineType.Equal(sBroadcastControlStr)) { // found a control line for the broadcast (delete at time or delete at end of broadcast/server startup) // qtss_printf("found =%s\n",sBroadcastControlStr); aParser.ConsumeUntil(NULL,StringParser::sWordMask); StrPtrLen sessionControlType; aParser.ConsumeWord(&sessionControlType); if (sessionControlType.Equal(sAutoDisconnect)) { fSessionControlType = kRTSPSessionControl; } else if (sessionControlType.Equal(sAutoDisconnectTime)) { fSessionControlType = kSDPTimeControl; } } //if we haven't even hit an 'm' line yet, just ignore all 'a' lines if (theStreamIndex == 0) break; if (aLineType.Equal(sRtpMapStr)) { //mark the codec type if this line has a codec name on it. If we already //have a codec type for this track, just ignore this line if ((fStreamArray[theStreamIndex - 1].fPayloadName.Len == 0) && (aParser.GetThru(NULL, ' '))) { StrPtrLen payloadNameFromParser; (void)aParser.GetThruEOL(&payloadNameFromParser); char* temp = payloadNameFromParser.GetAsCString(); // qtss_printf("payloadNameFromParser (%x) = %s\n", temp, temp); (fStreamArray[theStreamIndex - 1].fPayloadName).Set(temp, payloadNameFromParser.Len); // qtss_printf("%s\n", fStreamArray[theStreamIndex - 1].fPayloadName.Ptr); } } else if (aLineType.Equal(sControlStr)) { //mark the trackID if that's what this line has aParser.ConsumeUntil(NULL, '='); aParser.ConsumeUntil(NULL, StringParser::sDigitMask); fStreamArray[theStreamIndex - 1].fTrackID = aParser.ConsumeInteger(NULL); } else if (aLineType.Equal(sBufferDelayStr)) { // if a BufferDelay is found then set all of the streams to the same buffer delay (it's global) aParser.ConsumeUntil(NULL, StringParser::sDigitMask); theGlobalStreamInfo.fBufferDelay = aParser.ConsumeFloat(); } } break; case 'c': { //get the IP address off this header StringParser cParser(&sdpLine); cParser.ConsumeLength(NULL, 9);//strip off "c=in ip4 " UInt32 tempIPAddr = SDPSourceInfo::GetIPAddr(&cParser, '/'); //grab the ttl SInt32 tempTtl = kDefaultTTL; if (cParser.GetThru(NULL, '/')) { tempTtl = cParser.ConsumeInteger(NULL); Assert(tempTtl >= 0); Assert(tempTtl < 65536); } if (theStreamIndex > 0) { //if this c= line is part of a stream, it overrides the //global stream information fStreamArray[theStreamIndex - 1].fDestIPAddr = tempIPAddr; fStreamArray[theStreamIndex - 1].fTimeToLive = (UInt16) tempTtl; } else { theGlobalStreamInfo.fDestIPAddr = tempIPAddr; theGlobalStreamInfo.fTimeToLive = (UInt16) tempTtl; hasGlobalStreamInfo = true; } } } } // Add the default buffer delay Float32 bufferDelay = (Float32) eDefaultBufferDelay; if (theGlobalStreamInfo.fBufferDelay != (Float32) eDefaultBufferDelay) bufferDelay = theGlobalStreamInfo.fBufferDelay; UInt32 count = 0; while (count < fNumStreams) { fStreamArray[count].fBufferDelay = bufferDelay; count ++; } } UInt32 SDPSourceInfo::GetIPAddr(StringParser* inParser, char inStopChar) { StrPtrLen ipAddrStr; // Get the IP addr str inParser->ConsumeUntil(&ipAddrStr, inStopChar); if (ipAddrStr.Len == 0) return 0; // NULL terminate it char endChar = ipAddrStr.Ptr[ipAddrStr.Len]; ipAddrStr.Ptr[ipAddrStr.Len] = '\0'; //inet_addr returns numeric IP addr in network byte order, make //sure to convert to host order. UInt32 ipAddr = SocketUtils::ConvertStringToAddr(ipAddrStr.Ptr); // Make sure to put the old char back! ipAddrStr.Ptr[ipAddrStr.Len] = endChar; return ipAddr; }