Add even more of the source

This should be about everything needed to build so far?
This commit is contained in:
Darren VanBuren 2017-03-07 17:14:16 -08:00
parent af3619d4fa
commit 849723c9cf
547 changed files with 149239 additions and 0 deletions

View file

@ -0,0 +1,780 @@
/*
*
* @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: QTSSFileModule.cpp
Contains: Implementation of module described in QTSSFileModule.h.
*/
#include <string.h>
#include "QTSSRTPFileModule.h"
#include "RTPFileSession.h"
#include "OSMemory.h"
#include "OSArrayObjectDeleter.h"
#include "QTSSModuleUtils.h"
#include "StringFormatter.h"
#include "SDPSourceInfo.h"
#include "QTSSMemoryDeleter.h"
#include "QTSS.h"
struct FileSession
{
public:
FileSession() : fAdjustedPlayTime(0), fNextPacketLen(0),
fStream(NULL), fSpeed(1), fStartTime(0), fStopTime(-1)
{}
RTPFileSession fFile;
SInt64 fAdjustedPlayTime;
QTSS_PacketStruct fPacketStruct;
UInt32 fNextPacketLen;
QTSS_RTPStreamObject fStream;
Float32 fSpeed;
Float64 fStartTime;
Float64 fStopTime;
};
// ref to the prefs dictionary object
static QTSS_ModulePrefsObject sFileModulePrefs;
static StrPtrLen sRTPSuffix(".rtp");
static StrPtrLen sSDPHeader1("v=0\r\ns=");
static StrPtrLen sSDPHeader2;
static StrPtrLen sSDPHeader3("c=IN IP4 ");
static StrPtrLen sSDPHeader4("\r\na=control:/\r\n");
static const StrPtrLen kCacheControlHeader("must-revalidate");
// ATTRIBUTES IDs
static QTSS_AttributeID sFileSessionAttr = qtssIllegalAttrID;
static QTSS_AttributeID sSeekToNonexistentTimeErr = qtssIllegalAttrID;
static QTSS_AttributeID sBadQTFileErr = qtssIllegalAttrID;
static QTSS_AttributeID sExpectedDigitFilenameErr = qtssIllegalAttrID;
static QTSS_AttributeID sTrackDoesntExistErr = qtssIllegalAttrID;
// OTHER DATA
static UInt32 sFlowControlProbeInterval = 50;
static UInt32 sDefaultFlowControlProbeInterval= 50;
static Float32 sMaxAllowedSpeed = 4;
static Float32 sDefaultMaxAllowedSpeed = 4;
// FUNCTIONS
static QTSS_Error QTSSRTPFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock);
static QTSS_Error Register(QTSS_Register_Params* inParams);
static QTSS_Error Initialize(QTSS_Initialize_Params* inParamBlock);
static QTSS_Error RereadPrefs();
static QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParamBlock);
static QTSS_Error DoDescribe(QTSS_StandardRTSP_Params* inParamBlock);
static QTSS_Error CreateRTPFileSession(QTSS_StandardRTSP_Params* inParamBlock, const StrPtrLen& inPath, FileSession** outFile);
static QTSS_Error DoSetup(QTSS_StandardRTSP_Params* inParamBlock);
static QTSS_Error DoPlay(QTSS_StandardRTSP_Params* inParamBlock);
static QTSS_Error SendPackets(QTSS_RTPSendPackets_Params* inParams);
static QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams);
QTSS_Error QTSSRTPFileModule_Main(void* inPrivateArgs)
{
return _stublibrary_main(inPrivateArgs, QTSSRTPFileModuleDispatch);
}
QTSS_Error QTSSRTPFileModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParamBlock)
{
switch (inRole)
{
case QTSS_Register_Role:
return Register(&inParamBlock->regParams);
case QTSS_Initialize_Role:
return Initialize(&inParamBlock->initParams);
case QTSS_RereadPrefs_Role:
return RereadPrefs();
case QTSS_RTSPPreProcessor_Role:
return ProcessRTSPRequest(&inParamBlock->rtspPreProcessorParams);
case QTSS_RTPSendPackets_Role:
return SendPackets(&inParamBlock->rtpSendPacketsParams);
case QTSS_ClientSessionClosing_Role:
return DestroySession(&inParamBlock->clientSessionClosingParams);
}
return QTSS_NoErr;
}
QTSS_Error Register(QTSS_Register_Params* inParams)
{
// Register for roles
(void)QTSS_AddRole(QTSS_Initialize_Role);
(void)QTSS_AddRole(QTSS_RTSPPreProcessor_Role);
(void)QTSS_AddRole(QTSS_ClientSessionClosing_Role);
(void)QTSS_AddRole(QTSS_RereadPrefs_Role);
// Add text messages attributes
static char* sSeekToNonexistentTimeName = "QTSSFileModuleSeekToNonExistentTime";
static char* sBadQTFileName = "QTSSFileModuleBadQTFile";
static char* sExpectedDigitFilenameName = "QTSSFileModuleExpectedDigitFilename";
static char* sTrackDoesntExistName = "QTSSFileModuleTrackDoesntExist";
(void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sSeekToNonexistentTimeName, NULL, qtssAttrDataTypeCharArray);
(void)QTSS_IDForAttr(qtssTextMessagesObjectType, sSeekToNonexistentTimeName, &sSeekToNonexistentTimeErr);
(void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sBadQTFileName, NULL, qtssAttrDataTypeCharArray);
(void)QTSS_IDForAttr(qtssTextMessagesObjectType, sBadQTFileName, &sBadQTFileErr);
(void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sExpectedDigitFilenameName, NULL, qtssAttrDataTypeCharArray);
(void)QTSS_IDForAttr(qtssTextMessagesObjectType, sExpectedDigitFilenameName, &sExpectedDigitFilenameErr);
(void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sTrackDoesntExistName, NULL, qtssAttrDataTypeCharArray);
(void)QTSS_IDForAttr(qtssTextMessagesObjectType, sTrackDoesntExistName, &sTrackDoesntExistErr);
// Add an RTP session attribute for tracking FileSession objects
static char* sFileSessionName = "QTSSRTPFileModuleSession";
(void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sFileSessionName, NULL, qtssAttrDataTypeVoidPointer);
(void)QTSS_IDForAttr(qtssClientSessionObjectType, sFileSessionName, &sFileSessionAttr);
// Tell the server our name!
static char* sModuleName = "QTSSRTPFileModule";
::strcpy(inParams->outModuleName, sModuleName);
return QTSS_NoErr;
}
QTSS_Error Initialize(QTSS_Initialize_Params* inParams)
{
QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream);
//
// We need some prefs created and maintained by the QTSSFileModule,
// cuz we don't want to duplicate basically the same stuff
StrPtrLen theFileModule("QTSSFileModule");
sFileModulePrefs = QTSSModuleUtils::GetModulePrefsObject(QTSSModuleUtils::GetModuleObjectByName(theFileModule));
static StrPtrLen sEHeader("\r\ne=");
static StrPtrLen sUHeader("\r\nu=");
static StrPtrLen sHTTP("http://");
static StrPtrLen sAdmin("admin@");
// Read our preferences
RereadPrefs();
//build the sdp that looks like: \r\ne=http://streaming.apple.com\r\ne=qts@apple.com\r\n.
// Get the default DNS name of the server
StrPtrLen theDefaultDNS;
(void)QTSS_GetValuePtr(inParams->inServer, qtssSvrDefaultDNSName, 0,
(void**)&theDefaultDNS.Ptr, &theDefaultDNS.Len);
StrPtrLen sdpURL;
StrPtrLen adminEmail;
sdpURL.Ptr = QTSSModuleUtils::GetStringAttribute(sFileModulePrefs, "sdp_url", "");
sdpURL.Len = ::strlen(sdpURL.Ptr);
adminEmail.Ptr = QTSSModuleUtils::GetStringAttribute(sFileModulePrefs, "admin_email", "");
adminEmail.Len = ::strlen(adminEmail.Ptr);
UInt32 sdpURLLen = sdpURL.Len;
UInt32 adminEmailLen = adminEmail.Len;
if (sdpURLLen == 0)
sdpURLLen = theDefaultDNS.Len + sHTTP.Len + 1;
if (adminEmailLen == 0)
adminEmailLen = theDefaultDNS.Len + sAdmin.Len;
//calculate the length of the string & allocate memory
sSDPHeader2.Len = (sEHeader.Len * 2) + sdpURLLen + adminEmailLen + 10;
sSDPHeader2.Ptr = NEW char[sSDPHeader2.Len];
//write it!
StringFormatter sdpFormatter(sSDPHeader2);
sdpFormatter.Put(sUHeader);
//if there are preferences for default URL & admin email, use those. Otherwise, build the
//proper string based on default dns name.
if (sdpURL.Len == 0)
{
sdpFormatter.Put(sHTTP);
sdpFormatter.Put(theDefaultDNS);
sdpFormatter.PutChar('/');
}
else
sdpFormatter.Put(sdpURL);
sdpFormatter.Put(sEHeader);
//now do the admin email.
if (adminEmail.Len == 0)
{
sdpFormatter.Put(sAdmin);
sdpFormatter.Put(theDefaultDNS);
}
else
sdpFormatter.Put(adminEmail);
sdpFormatter.PutEOL();
sSDPHeader2.Len = (UInt32)sdpFormatter.GetCurrentOffset();
delete [] sdpURL.Ptr;
delete [] adminEmail.Ptr;
// Report to the server that this module handles DESCRIBE, SETUP, PLAY, PAUSE, and TEARDOWN
static QTSS_RTSPMethod sSupportedMethods[] = { qtssDescribeMethod, qtssSetupMethod, qtssTeardownMethod, qtssPlayMethod, qtssPauseMethod };
QTSSModuleUtils::SetupSupportedMethods(inParams->inServer, sSupportedMethods, 5);
return QTSS_NoErr;
}
QTSS_Error RereadPrefs()
{
QTSSModuleUtils::GetAttribute(sFileModulePrefs, "flow_control_probe_interval", qtssAttrDataTypeUInt32,
&sFlowControlProbeInterval, &sDefaultFlowControlProbeInterval, sizeof(sFlowControlProbeInterval));
QTSSModuleUtils::GetAttribute(sFileModulePrefs, "max_allowed_speed", qtssAttrDataTypeFloat32,
&sMaxAllowedSpeed, &sDefaultMaxAllowedSpeed, sizeof(sMaxAllowedSpeed));
return QTSS_NoErr;
}
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;
}
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;
}
QTSS_Error CreateRTPFileSession(QTSS_StandardRTSP_Params* inParamBlock, const StrPtrLen& inPath, FileSession** outFile)
{
*outFile = NEW FileSession();
StrPtrLen thePath(inPath);
RTPFileSession::ErrorCode theErr = (*outFile)->fFile.Initialize(thePath, 8);
if (theErr != RTPFileSession::errNoError)
{
delete *outFile;
*outFile = NULL;
if (theErr == RTPFileSession::errFileNotFound)
return QTSSModuleUtils::SendErrorResponse( inParamBlock->inRTSPRequest,
qtssClientNotFound,
sBadQTFileErr);
AssertV(0, theErr);
}
return QTSS_NoErr;
}
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;
}
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;
}
QTSS_Error SendPackets(QTSS_RTPSendPackets_Params* inParams)
{
FileSession** theFile = NULL;
UInt32 theLen = 0;
QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen);
if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)))
{
Assert( 0 );
return QTSS_RequestFailed;
}
while (true)
{
if ((*theFile)->fPacketStruct.packetData == NULL)
{
void* theCookie = NULL;
Float64 theTransmitTime = (*theFile)->fFile.GetNextPacket((UInt8**)&(*theFile)->fPacketStruct.packetData, &(*theFile)->fNextPacketLen, &theCookie);
//
// Check to see if we should stop playing based on a client specified stop time
if (((*theFile)->fStopTime != -1) && (theTransmitTime > (*theFile)->fStopTime))
{
// We should indeed stop playing
(void)QTSS_Pause(inParams->inClientSession);
inParams->outNextPacketTime = qtssDontCallSendPacketsAgain;
return QTSS_NoErr;
}
//
// Adjust transmit time based on speed
Float64 theOffsetFromStartTime = theTransmitTime - (*theFile)->fStartTime;
theTransmitTime = (*theFile)->fStartTime + (theOffsetFromStartTime / (*theFile)->fSpeed);
(*theFile)->fStream = (QTSS_RTPStreamObject)theCookie;
(*theFile)->fPacketStruct.packetTransmitTime = (*theFile)->fAdjustedPlayTime + ((SInt64)(theTransmitTime * 1000));
#if RTP_FILE_MODULE_DEBUGGING >= 8
UInt16* theSeqNumPtr = (UInt16*)(*theFile)->fNextPacket;
UInt32* theTimestampP = (UInt32*)(*theFile)->fNextPacket;
UInt32* theTrackID = NULL;
(void)QTSS_GetValuePtr((*theFile)->fStream, qtssRTPStrTrackID, 0, (void**)&theTrackID, &theLen);
qtss_printf("Got packet. Seq num: %d. Timestamp: %d. TrackID: %d. Transmittime: %f\n",theSeqNumPtr[1], theTimestampP[1], *theTrackID, theTransmitTime);
#endif
}
//We are done playing all streams!
if ((*theFile)->fPacketStruct.packetData == NULL)
{
#if RTP_FILE_MODULE_DEBUGGING >= 8
qtss_printf("done w all packets\n");
#endif
inParams->outNextPacketTime = qtssDontCallSendPacketsAgain;
return QTSS_NoErr;
}
//we have a packet that needs to be sent now
Assert((*theFile)->fStream != NULL);
// Send the packet!
theErr = QTSS_Write((*theFile)->fStream, &(*theFile)->fPacketStruct, (*theFile)->fNextPacketLen, NULL, qtssWriteFlagsIsRTP);
if ( theErr == QTSS_WouldBlock )
{
if ((*theFile)->fPacketStruct.packetTransmitTime == -1)
inParams->outNextPacketTime = sFlowControlProbeInterval; // for buffering, try me again in # MSec
else
{
Assert((*theFile)->fPacketStruct.packetTransmitTime > inParams->inCurrentTime);
inParams->outNextPacketTime = (*theFile)->fPacketStruct.packetTransmitTime - inParams->inCurrentTime;
}
return QTSS_NoErr;
}
(*theFile)->fPacketStruct.packetData = NULL;
}
Assert(0);
return QTSS_NoErr;
}
QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams)
{
FileSession** theFile = NULL;
UInt32 theLen = 0;
QTSS_Error theErr = QTSS_GetValuePtr(inParams->inClientSession, sFileSessionAttr, 0, (void**)&theFile, &theLen);
if ((theErr != QTSS_NoErr) || (theLen != sizeof(FileSession*)) || (theFile == NULL))
return QTSS_RequestFailed;
delete *theFile;
return QTSS_NoErr;
}

View file

@ -0,0 +1,45 @@
/*
*
* @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: QTSSFileModule.h
Contains: Content source module that uses the QTFileLib to serve Hinted QuickTime
files to clients.
*/
#ifndef _RTPRTPFILEMODULE_H_
#define _RTPRTPFILEMODULE_H_
#include "QTSS.h"
extern "C"
{
EXPORT QTSS_Error QTSSRTPFileModule_Main(void* inPrivateArgs);
}
#endif //_RTPRTPFILEMODULE_H_

View file

@ -0,0 +1,571 @@
/*
*
* @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: RTPFileSession.cpp
Contains:
*/
#define RTPFILESESSIONDEBUG 0
#include "RTPFileSession.h"
#include "OSMemory.h"
static OSRefTable sOpenFileMap;
RTPFileSession::RTPFileSession()
: fFileSource(NULL),
fFileLength(0),
fCurrentPosition(0),
fFile(NULL),
fTrackInfo(NULL),
fNumTracksEnabled(0),
fReadBuffer(NULL),
fReadBufferOffset(0),
fDataBuffer(NULL),
fDataBufferSize(0),
fDataBufferLen(0),
fCurrentPacket(NULL),
fAddedTracksRTPBytes(0)
{}
RTPFileSession::~RTPFileSession()
{
// Check to see if we should destroy this file
OSMutexLocker locker (sOpenFileMap.GetMutex());
#if RTPFILESESSIONDEBUG
qtss_printf("Dropping refcount on file\n");
#endif
if (fFile == NULL)
return;
sOpenFileMap.Release(fFile->GetRef());
if (fFile->GetRef()->GetRefCount() == 0)
{
#if RTPFILESESSIONDEBUG
qtss_printf("Refcount dropped to 0. Deleting file\n");
#endif
sOpenFileMap.UnRegister(fFile->GetRef());
delete fFile;
}
// Delete our data buffer
delete [] fDataBuffer;
delete [] fTrackInfo;
}
RTPFileSession::ErrorCode RTPFileSession::Initialize(StrPtrLen& inFilePath, Float32 inBufferSeconds)
{
Assert(fFile == NULL);
// Check to see if this file is already open
OSMutexLocker locker(sOpenFileMap.GetMutex());
OSRef* theFileRef = sOpenFileMap.Resolve((StrPtrLen*)&inFilePath);
if (theFileRef == NULL)
{
//qtss_printf("Didn't find file in map. Creating new one\n");
fFile = NEW RTPFile();
ErrorCode theErr = fFile->Initialize(inFilePath);
if (theErr != errNoError)
{
delete fFile;
fFile = NULL;
return theErr;
}
OS_Error osErr = sOpenFileMap.Register(fFile->GetRef());
Assert(osErr == OS_NoErr);
//unless we do this, the refcount won't increment (and we'll delete the session prematurely
OSRef* debug = sOpenFileMap.Resolve((StrPtrLen*)&inFilePath);
Assert(debug == fFile->GetRef());
}
else
{
//qtss_printf("Found file. Refcounting.\n");
fFile = (RTPFile*)theFileRef->GetObject();
}
//Open the file no matter what
//fFileSource.Set(inFilePath.Ptr);
//Assert(fFileSource.GetLength() > 0);
QTSS_Error theErr = QTSS_OpenFileObject(inFilePath.Ptr, 0, &fFileSource);
Assert(theErr == QTSS_NoErr);
//
// Get the file length
UInt32 theLen = sizeof(fFileLength);
theErr = QTSS_GetValue(fFileSource, qtssFlObjLength, 0, &fFileLength, &theLen);
Assert(theErr == QTSS_NoErr);
Assert(theLen == sizeof(fFileLength));
// Allocate our data buffer
fDataBufferSize = this->PowerOf2Floor((UInt32)(inBufferSeconds * fFile->GetBytesPerSecond()));
// Check to see if the size is out of range. If so, adjust it
if (fDataBufferSize > kMaxDataBufferSize)
fDataBufferSize = kMaxDataBufferSize;
if (fDataBufferSize < kBlockSize)
fDataBufferSize = kBlockSize;
fReadBuffer = fDataBuffer = NEW UInt8[fDataBufferSize];
// Allocate a buffer of TrackInfos
fTrackInfo = NEW RTPFileSessionTrackInfo[fFile->GetMaxTrackNumber() + 1];
::memset(fTrackInfo, 0, fFile->GetMaxTrackNumber() * sizeof(RTPFileSessionTrackInfo));
return errNoError;
}
RTPFileSession::ErrorCode RTPFileSession::AddTrack(UInt32 trackID)
{
if (fFile->TrackExists(trackID))
{
if (!fTrackInfo[trackID].fEnabled)
{
fTrackInfo[trackID].fEnabled = true;
fAddedTracksRTPBytes += fFile->GetTrackBytes(trackID);
fNumTracksEnabled++;
}
else
return errTrackAlreadyAdded;
}
else
return errTrackDoesntExist;
return errNoError;
}
RTPFileSession::ErrorCode RTPFileSession::Seek(Float64 inTime)
{
if ((inTime < 0) || (inTime > fFile->GetMovieDuration()))
return errSeekToNonexistentTime;
UInt64 theBlockLocation = fFile->GetBlockLocation(inTime);
Assert(theBlockLocation >= fFile->fHeader.fDataStartPos);
Assert(theBlockLocation < fFileLength);
// Seek to the right file location.
//fFileSource.Seek(theBlockLocation);
QTSS_Error theErr = QTSS_Seek(fFileSource, theBlockLocation);
Assert(theErr == QTSS_NoErr);
fCurrentPosition = theBlockLocation;
// Read the file data
this->ReadAndAdvise();
for (UInt32 x = 0; x <= fFile->GetMaxTrackNumber(); x++)
fTrackInfo[x].fMarked = false;
//
// We need to find out what the first packet is for each enabled track.
// So scan ahead until we find the very first packet we need to send.
// At that point, "freeze" the current block in memory, and that position,
// because that's the position we'll be starting from when GetNextPacket gets
// called. In order to "freeze" we store lots of info about the current position
// on the stack with the variables defined below.
//
// Keep on going until we find the first packets for all the enabled tracks,
// and if that involves traversing multiple blocks, keep those blocks in a temporary
// buffer, allowing us to easily go back to the start point when done.
UInt8* theStartPos = NULL;
UInt32 origDataBufferLen = 0;
UInt64 currentFileOffset = 0;
UInt32 tracksFound = 0;
// Needed to call GetNextPacket
UInt8* thePacketP = NULL;
UInt32 thePacketLength = 0;
void* theCookie = NULL;
while (tracksFound < fNumTracksEnabled)
{
Float64 theTransmitTime = this->GetNextPacket(&thePacketP, &thePacketLength, &theCookie);
if (thePacketP == NULL)
{
Assert(tracksFound > 0);
break; // We're at the end of the file!
}
// Ignore < 0 timed packets
RTPFilePacket* thePacket = (RTPFilePacket*)thePacketP;
Assert((thePacket - 1)->fTransmitTime == theTransmitTime);
if (theTransmitTime < 0)
theTransmitTime = 0;
UInt32 theTrackID = (thePacket - 1)->fTrackID;
if ((theTransmitTime >= inTime) && (!fTrackInfo[theTrackID].fMarked) &&
(fTrackInfo[theTrackID].fEnabled))
{
// This is the first packet for this track after our fCurrentPtr mark.
// Record the first seq # and timestamp of the packet
UInt16* theSeqNumPtr = (UInt16*)thePacketP;
UInt32* theTimestampPtr = (UInt32*)thePacketP;
fTrackInfo[theTrackID].fSeekSeqNumber = theSeqNumPtr[1];
fTrackInfo[theTrackID].fSeekTimestamp = theTimestampPtr[1];
fTrackInfo[theTrackID].fMarked = true;
if (tracksFound == 0)
{
//
// If this is the first packet that we're going to send (for all
// streams), then mark the position, and make sure that if we
// need to dump this buffer to find first packets for other tracks,
// we'll be able to come back to this very place so we can start streaming.
fReadBuffer = NULL;
theStartPos = (UInt8*)(thePacket-1);
origDataBufferLen = fDataBufferLen;
currentFileOffset = fCurrentPosition;
}
tracksFound++;
}
}
if (fReadBuffer != NULL)
{
// We had to skip ahead in the file. Restore everything to
// the way it was when we found the first packet, so GetNextPacket
// will work fine.
delete [] fReadBuffer;
fReadBuffer = fDataBuffer;
Assert(origDataBufferLen > 0);
fDataBufferLen = origDataBufferLen;
Assert(currentFileOffset > 0);
//fFileSource.Seek(currentFileOffset);
theErr = QTSS_Seek(fFileSource, theBlockLocation);
Assert(theErr == QTSS_NoErr);
}
// Start at the first packet we need to send.
Assert(theStartPos != NULL);
fCurrentPacket = theStartPos;
return errNoError;
}
UInt16 RTPFileSession::GetNextTrackSequenceNumber(UInt32 inTrackID)
{
Assert(inTrackID <= fFile->GetMaxTrackNumber());
Assert(fTrackInfo[inTrackID].fMarked);
return fTrackInfo[inTrackID].fSeekSeqNumber;
}
UInt32 RTPFileSession::GetSeekTimestamp(UInt32 inTrackID)
{
Assert(inTrackID <= fFile->GetMaxTrackNumber());
Assert(fTrackInfo[inTrackID].fMarked);
return fTrackInfo[inTrackID].fSeekTimestamp;
}
Float64 RTPFileSession::GetNextPacket(UInt8** outPacket, UInt32* outPacketLength, void** outCookie)
{
Bool16 isValidPacket = false;
RTPFilePacket* thePacket = NULL;
// Loop until we find a legal packet
while (!isValidPacket)
{
// If we are between blocks, read the next block
if (fCurrentPacket == NULL)
{
if (fCurrentPosition == fFileLength)
{
#if RTPFILESESSIONDEBUG
qtss_printf("RTPFileSession::GetNextPacket fCurrentPosition == fFileLength quit\n");
#endif
*outPacket = NULL;
return -1;
}
this->ReadAndAdvise();
}
Assert(fCurrentPacket != NULL);
thePacket = (RTPFilePacket*)fCurrentPacket;
if (thePacket->fTrackID & kPaddingBit)
{
// We hit a padding packet, move the fCurrentPacket pointer to the next block
fReadBufferOffset += kBlockSize;
fReadBufferOffset &= kBlockMask; //Rounds down to the nearest block size
#if RTPFILESESSIONDEBUG
qtss_printf("Found a pad packet. Moving on\n");
#endif
// Check to make sure we aren't at the end of the buffer
if (fReadBufferOffset >= fDataBufferLen)
fCurrentPacket = NULL;
else
fCurrentPacket = fDataBuffer + fReadBufferOffset;
}
else if (!fTrackInfo[thePacket->fTrackID].fEnabled)
{
// This is a valid packet, but track not enabled, so skip it
Assert(thePacket->fTrackID <= fFile->GetMaxTrackNumber());
this->SkipToNextPacket(thePacket);
}
else
// This is a valid packet, and the track is enabled
isValidPacket = true;
}
// We must have a valid packet here
Assert(thePacket != NULL);
Assert(thePacket->fTrackID <= fFile->GetMaxTrackNumber());
// Set the return values
*outPacket = (UInt8*)(thePacket + 1);
*outPacketLength = thePacket->fPacketLength;
*outCookie = fTrackInfo[thePacket->fTrackID].fCookie;
// Set the packet's SSRC
UInt32* ssrcPtr = (UInt32*)*outPacket;
ssrcPtr[2] = fTrackInfo[thePacket->fTrackID].fSSRC;
Float64 transmitTime = thePacket->fTransmitTime;
this->SkipToNextPacket(thePacket);
return transmitTime;
}
void RTPFileSession::SkipToNextPacket(RTPFilePacket* inCurPacket)
{
// Skip over this packet
Assert(inCurPacket->fPacketLength < 1500);
fReadBufferOffset += inCurPacket->fPacketLength;
fCurrentPacket += (inCurPacket->fPacketLength + sizeof(RTPFilePacket));
// Check to see if we need to read more data
if (fReadBufferOffset >= fDataBufferLen)
{
#if RTPFILESESSIONDEBUG
qtss_printf("In SkipToNextPacket. Out of data\n");
#endif
fCurrentPacket = NULL;
}
}
void RTPFileSession::ReadAndAdvise()
{
if (fReadBuffer == NULL)
{
// In some situations, callers of this function may not want the fDataBuffer
// to be disturbed (see Seek()). If that's the case, the caller will set
// fReadBuffer to NULL, and we must allocate it here
fReadBuffer = NEW UInt8[fDataBufferSize];
}
// Read the next block. There should always be at least one packet
// here, as we have a valid block in the block table.
#if RTPFILESESSIONDEBUG
//qtss_printf("Moving onto next block. File loc: %qd\n",fFileSource.GetCurOffset());
#endif
fDataBufferLen = 0;
//(void)fFileSource.Read(fDataBuffer, fDataBufferSize, &fDataBufferLen);
QTSS_Error theErr = QTSS_Read(fFileSource, fDataBuffer, fDataBufferSize, &fDataBufferLen);
Assert(theErr == QTSS_NoErr);
Assert(fDataBufferLen > sizeof(RTPFilePacket));
fCurrentPosition += fDataBufferLen;
// Now do an advise for the next block, if this block isn't the last.
if (fCurrentPosition < fFileLength)
{
Assert(fDataBufferLen == fDataBufferSize);
//fFileSource.Advise(fFileSource.GetCurOffset(), fDataBufferSize);
theErr = QTSS_Advise(fFileSource, fCurrentPosition, fDataBufferSize);
}
fReadBufferOffset = 0;
fCurrentPacket = fDataBuffer;
}
UInt32 RTPFileSession::PowerOf2Floor(UInt32 inNumToFloor)
{
UInt32 retVal = 0x10000000;
while (retVal > 0)
{
if (retVal & inNumToFloor)
return retVal;
else
retVal >>= 1;
}
return retVal;
}
RTPFile::RTPFile()
: fTrackInfo(NULL),
fBlockMap(NULL),
fBytesPerSecond(0),
fMaxTrackNumber(0)
{}
RTPFile::~RTPFile()
{
delete [] fFilePath.Ptr;
delete [] fSDPData.Ptr;
delete [] fTrackInfo;
delete [] fBlockMap;
}
RTPFileSession::ErrorCode RTPFile::Initialize(const StrPtrLen& inFilePath)
{
//OSFileSource theFile(inFilePath.Ptr);
QTSS_Object theFile = NULL;
QTSS_Error theErr = QTSS_OpenFileObject(inFilePath.Ptr, 0, &theFile);
if (theErr != QTSS_NoErr)
return RTPFileSession::errFileNotFound;
// Copy the path.
fFilePath.Ptr = NEW char[inFilePath.Len + 1];
::memcpy(fFilePath.Ptr, inFilePath.Ptr, inFilePath.Len);
fFilePath.Len = inFilePath.Len;
// Setup our osref
fRef.Set(fFilePath, this);
// Read the header
//OS_Error theErr = theFile.Read(&fHeader, sizeof(fHeader));
UInt32 theLengthRead = 0;
theErr = QTSS_Read(theFile, &fHeader, sizeof(fHeader), &theLengthRead);
Assert(theErr == QTSS_NoErr);
Assert(theLengthRead == sizeof(fHeader));
// Read the SDP data
fSDPData.Len = fHeader.fSDPLen;
fSDPData.Ptr = NEW char[fSDPData.Len + 1];
//theErr = theFile.Read(fSDPData.Ptr, fSDPData.Len);
theErr = QTSS_Read(theFile, fSDPData.Ptr, fSDPData.Len, &theLengthRead);
Assert(theErr == QTSS_NoErr);
Assert(theLengthRead == fSDPData.Len);
// Parse the SDP Information
fSourceInfo.Parse(fSDPData.Ptr, fSDPData.Len);
// Read the track info
fTrackInfo = NEW RTPFileTrackInfo[fHeader.fNumTracks];
//theErr = theFile.Read(fTrackInfo, sizeof(RTPFileTrackInfo) * fHeader.fNumTracks);
theErr = QTSS_Read(theFile, fTrackInfo, sizeof(RTPFileTrackInfo) * fHeader.fNumTracks, &theLengthRead);
Assert(theErr == QTSS_NoErr);
Assert(theLengthRead == sizeof(RTPFileTrackInfo) * fHeader.fNumTracks);
// Create and read the block map
fBlockMap = NEW UInt8[fHeader.fBlockMapSize];
//theErr = theFile.Read(fBlockMap, fHeader.fBlockMapSize);
theErr = QTSS_Read(theFile, fBlockMap, fHeader.fBlockMapSize, &theLengthRead);
Assert(theErr == QTSS_NoErr);
Assert(theLengthRead == fHeader.fBlockMapSize);
// Calculate bit rate of all the tracks combined
Float64 totalBytes = 0;
for (UInt32 x = 0; x < fHeader.fNumTracks; x++)
totalBytes += (Float64)fTrackInfo[x].fBytesInTrack;
totalBytes /= fHeader.fMovieDuration;
fBytesPerSecond = (UInt32)totalBytes;
//Get the max track number
fMaxTrackNumber = 0;
for (UInt32 y = 0; y < fHeader.fNumTracks; y++)
if (fTrackInfo[y].fID > fMaxTrackNumber)
fMaxTrackNumber = fTrackInfo[y].fID;
(void)QTSS_CloseFileObject(theFile);
return RTPFileSession::errNoError;
}
SInt64 RTPFile::GetBlockLocation(Float64 inTimeInSecs)
{
if (inTimeInSecs == 0)
return fHeader.fDataStartPos;
UInt32 theTime = 0;
UInt32 x = 0;
for ( ; x < fHeader.fBlockMapSize ; x++)
{
theTime += fBlockMap[x];
if (theTime >= (UInt32)inTimeInSecs)
{
if ((theTime > (UInt32)inTimeInSecs) && (x > 0))
// If we've moved too far, go back to the previous block
x--;
return fHeader.fDataStartPos + (kBlockSize * x);
}
}
return fHeader.fDataStartPos + (kBlockSize * x);// The requested time must be in the last block (or nonexistent).
}
Float64 RTPFile::GetTrackDuration(UInt32 inTrackID)
{
for (UInt32 x = 0; x < fHeader.fNumTracks; x++)
{
if (fTrackInfo[x].fID == inTrackID)
return fTrackInfo[x].fDuration;
}
Assert(0);
return -1;
}
UInt32 RTPFile::GetTrackTimeScale(UInt32 inTrackID)
{
for (UInt32 x = 0; x < fHeader.fNumTracks; x++)
{
if (fTrackInfo[x].fID == inTrackID)
return fTrackInfo[x].fTimescale;
}
Assert(0);
return 0;
}
UInt64 RTPFile::GetTrackBytes(UInt32 inTrackID)
{
for (UInt32 x = 0; x < fHeader.fNumTracks; x++)
{
if (fTrackInfo[x].fID == inTrackID)
return fTrackInfo[x].fBytesInTrack;
}
Assert(0);
return 0;
}
Bool16 RTPFile::TrackExists(UInt32 inTrackID)
{
for (UInt32 x = 0; x < fHeader.fNumTracks; x++)
{
if (fTrackInfo[x].fID == inTrackID)
return true;
}
return false;
}

View file

@ -0,0 +1,227 @@
/*
*
* @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: RTPFileSession.h
Contains:
*/
#ifndef __RTPFILESESSIONH__
#define __RTPFILESESSIONH__
#include "RTPFileDefs.h"
#include "OSHeaders.h"
#include "OSRef.h"
#include "QTSS.h" // This object uses QTSS API file I/O
#include "SDPSourceInfo.h"
class RTPFile;
class RTPFileSession
{
public:
// One per client
RTPFileSession();
~RTPFileSession();
//
// Class error codes
enum ErrorCode
{
errNoError = 0,
errFileNotFound = 1,
errTrackAlreadyAdded = 2,
errTrackDoesntExist = 3,
errSeekToNonexistentTime = 4
};
//
// INITIALIZE
ErrorCode Initialize(StrPtrLen& inFilePath, Float32 inBufferSeconds);
//
// ACCESSORS
//
// Global information
inline Float64 GetMovieDuration();
inline StrPtrLen* GetSDPFile();
inline SourceInfo* GetSourceInfo();
inline StrPtrLen* GetMoviePath();
UInt64 GetAddedTracksRTPBytes() { return fAddedTracksRTPBytes; }
//
// Track functions
inline Float64 GetTrackDuration(UInt32 inTrackID);
inline UInt32 GetTrackTimeScale(UInt32 inTrackID);
UInt16 GetNextTrackSequenceNumber(UInt32 inTrackID);
UInt32 GetSeekTimestamp(UInt32 TrackID);
//
// MODIFIERS
//
// Track modifiers
ErrorCode AddTrack(UInt32 inTrackID);
void SetTrackSSRC(UInt32 inTrackID, UInt32 inSSRC) { fTrackInfo[inTrackID].fSSRC = inSSRC; }
void SetTrackCookie(UInt32 inTrackID, void *inCookie){ fTrackInfo[inTrackID].fCookie = inCookie; }
// Seek to a time
ErrorCode Seek(Float64 inTime);
// GetNextPacket. Returns the transmit time for this packet
Float64 GetNextPacket(UInt8** outPacket, UInt32* outPacketLength, void** outCookie);
private:
// Utility functions
void SkipToNextPacket(RTPFilePacket* inCurPacket);
void ReadAndAdvise();
UInt32 PowerOf2Floor(UInt32 inNumToFloor);
enum
{
kMaxDataBufferSize = 262144
};
struct RTPFileSessionTrackInfo
{
Bool16 fEnabled;
Bool16 fMarked; // is the seek timestamp, seq num recorded?
UInt32 fSSRC;
UInt32 fSeekTimestamp;
UInt16 fSeekSeqNumber;
void* fCookie;
};
QTSS_Object fFileSource;
//OSFileSource fFileSource;
UInt64 fFileLength;
UInt64 fCurrentPosition;
RTPFile* fFile;
RTPFileSessionTrackInfo* fTrackInfo;
UInt32 fNumTracksEnabled;
UInt8* fReadBuffer; // Buffer that file data gets read into.
// Usually same as fDataBuffer
UInt32 fReadBufferOffset;
UInt8* fDataBuffer; // Buffer for file data
UInt32 fDataBufferSize;
UInt32 fDataBufferLen;
UInt8* fCurrentPacket;
UInt64 fAddedTracksRTPBytes;
friend class RTPFile;
};
class RTPFile
{
public:
// One per file
RTPFile();
~RTPFile();
RTPFileSession::ErrorCode Initialize(const StrPtrLen& inFilePath);
Float64 GetMovieDuration() { return fHeader.fMovieDuration; }
Float64 GetTrackDuration(UInt32 inTrackID);
UInt32 GetTrackTimeScale(UInt32 inTrackID);
UInt64 GetTrackBytes(UInt32 inTrackID);
Bool16 TrackExists(UInt32 inTrackID);
UInt32 GetBytesPerSecond() { return fBytesPerSecond; }
UInt32 GetMaxTrackNumber() { return fMaxTrackNumber; }
StrPtrLen* GetSDPFile() { return &fSDPData; }
SourceInfo* GetSourceInfo() { return &fSourceInfo; }
OSRef* GetRef() { return &fRef; }
// Returns the location in the file corresponding to this time,
// rounded to the nearest start of block.
SInt64 GetBlockLocation(Float64 inTimeInSecs);
private:
RTPFileTrackInfo* fTrackInfo;
RTPFileHeader fHeader;
UInt8* fBlockMap;
StrPtrLen fSDPData;
SDPSourceInfo fSourceInfo;
UInt32 fBytesPerSecond;
UInt32 fMaxTrackNumber;
OSRef fRef;
StrPtrLen fFilePath;
friend class RTPFileSession;
};
inline StrPtrLen* RTPFileSession::GetSDPFile()
{
return fFile->GetSDPFile();
}
inline SourceInfo* RTPFileSession::GetSourceInfo()
{
return &fFile->fSourceInfo;
}
inline StrPtrLen* RTPFileSession::GetMoviePath()
{
return &fFile->fFilePath;
}
inline Float64 RTPFileSession::GetMovieDuration()
{
return fFile->fHeader.fMovieDuration;
}
inline Float64 RTPFileSession::GetTrackDuration(UInt32 inTrackID)
{
return fFile->GetTrackDuration(inTrackID);
}
inline UInt32 RTPFileSession::GetTrackTimeScale(UInt32 inTrackID)
{
return fFile->GetTrackTimeScale(inTrackID);
}
#endif