Darwin-Streaming-Server/APIModules/QTSSRTPFileModule/RTPFileSession.cpp

572 lines
18 KiB
C++
Raw Normal View History

/*
*
* @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;
}