/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2001 Apple Computer, Inc.  All Rights Reserved. The
 * contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License.  Please
 * obtain a copy of the License at http://www.apple.com/publicsource and
 * read it before using this file.
 *
 * This 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:	
					
	$Log: RTPFileSession.cpp,v $
	Revision 1.7  2001/11/22 00:48:28  murata
	Expand 'hinf' atom support to include 8 byte atom values used by some mpeg4 files.
	Bug #:
	Submitted by:
	Reviewed by:
	
	Revision 1.6  2001/10/23 03:35:32  lecroy
	getop linux build problems
	typo in streamingserver.xml file
	Bug #:
	Submitted by:
	Reviewed by:
	
	Revision 1.5  2001/10/18 02:00:54  lecroy
	removed "pragma mark"s
	Bug #:
	Submitted by:
	Reviewed by:
	
	Revision 1.4  2001/10/18 01:45:33  lecroy
	fixed my big screw up when trying to remove pragma mark's
	Bug #:
	Submitted by:
	Reviewed by:
	
	Revision 1.2  2001/03/20 02:19:19  mythili
	updated/added APSL 1.2
	
	Revision 1.1  2000/10/20 07:24:52  serenyi
	Created
	
	
*/

#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
	printf("Dropping refcount on file\n");
#endif
	if (fFile == NULL)
		return;
		
	sOpenFileMap.Release(fFile->GetRef());
	if (fFile->GetRef()->GetRefCount() == 0)
	{
#if RTPFILESESSIONDEBUG
		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(const 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)
	{
		//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
	{
		//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)
			{
				*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
			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
		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
	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;
}

