Darwin-Streaming-Server/QTFileLib/QTRTPFile.cpp
Darren VanBuren 849723c9cf Add even more of the source
This should be about everything needed to build so far?
2017-03-07 17:14:16 -08:00

1544 lines
47 KiB
C++

/*
*
* @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@
*
*/
//
// QTRTPFile:
// An interface to QTFile for TimeShare.
//
// htons and friends are macros and should not include the global specifier ::
// -------------------------------------
// Includes
//
#include <stdio.h>
#include <stdlib.h>
#include "SafeStdLib.h"
#include <string.h>
#include "OSMutex.h"
#include "QTFile.h"
#include "QTTrack.h"
#include "QTHintTrack.h"
#include "QTRTPFile.h"
#include "OSMemory.h"
#define QT_PROFILE 0
#if QT_PROFILE
#include "StopWatch.h"
#endif
// -------------------------------------
// Macros
//
#define DEBUG_PRINT(s) if(fDebug) qtss_printf s
#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s
//
// This is a declaration of what RTP-Meta-Info fields are supported by QTFileLib.
// The actual support goes into QTHintTrack.h / .cpp
//
// Current fields are: kPacketPosField, kTransTimeField, kFrameTypeField, kPacketNumField, kSeqNumField, kMediaDataField
// See RTPMetaInfoPacket.h for details.
const RTPMetaInfoPacket::FieldID QTRTPFile::kMetaInfoFields[] =
{ RTPMetaInfoPacket::kUncompressed, RTPMetaInfoPacket::kUncompressed, RTPMetaInfoPacket::kUncompressed, RTPMetaInfoPacket::kUncompressed, RTPMetaInfoPacket::kUncompressed, RTPMetaInfoPacket::kUncompressed };
// -------------------------------------
// Protected cache functions and variables.
//
OSMutex *QTRTPFile::gFileCacheMutex,
*QTRTPFile::gFileCacheAddMutex;
QTRTPFile::RTPFileCacheEntry *QTRTPFile::gFirstFileCacheEntry = NULL;
void QTRTPFile::Initialize(void)
{
QTRTPFile::gFileCacheMutex = NEW OSMutex();
QTRTPFile::gFileCacheAddMutex = NEW OSMutex();
}
QTRTPFile::ErrorCode QTRTPFile::new_QTFile(const char * filePath, QTFile ** theQTFile, Bool16 debugFlag, Bool16 deepDebugFlag)
{
// Temporary vars
QTFile::ErrorCode rcFile;
// General vars
OSMutexLocker fileCacheAddMutex(QTRTPFile::gFileCacheAddMutex);
QTRTPFile::RTPFileCacheEntry *fileCacheEntry;
//
// Find and return the QTFile object out of our cache, if it exists.
if( QTRTPFile::FindAndRefcountFileCacheEntry(filePath, &fileCacheEntry) )
{
fileCacheAddMutex.Unlock();
fileCacheEntry->InitMutex->Lock(); // Guaranteed to block as the mutex
// is acquired before it is added to
// the list.
fileCacheEntry->InitMutex->Unlock();// Because we don't actually need it.
*theQTFile = fileCacheEntry->File;
Assert(*theQTFile);
return errNoError;
}
//
// Construct our file object.
*theQTFile = NEW QTFile(debugFlag, deepDebugFlag);
if( *theQTFile == NULL )
return errInternalError;
//
// Open the specified movie.
if( (rcFile = (*theQTFile)->Open(filePath)) != QTFile::errNoError )
{
delete *theQTFile;
switch( rcFile )
{
case errFileNotFound:
return errFileNotFound;
case errInvalidQuickTimeFile:
return errInvalidQuickTimeFile;
default:
return errInternalError;
}
}
//
// Add this file to our cache and release the global add mutex.
QTRTPFile::AddFileToCache(filePath, &fileCacheEntry); // Grabs InitMutex.
fileCacheAddMutex.Unlock();
//
// Finish setting up the fileCacheEntry.
if( fileCacheEntry != NULL )
{ // it may not have been cached..
fileCacheEntry->File = *theQTFile;
fileCacheEntry->InitMutex->Unlock();
}
//
// Return the file object.
return errNoError;
}
void QTRTPFile::delete_QTFile(QTFile * theQTFile)
{
// General vars
OSMutexLocker fileCacheMutex(QTRTPFile::gFileCacheMutex);
QTRTPFile::RTPFileCacheEntry *listEntry;
//
// Find the specified cache entry.
for ( listEntry = QTRTPFile::gFirstFileCacheEntry; listEntry != NULL; listEntry = listEntry->NextEntry )
{
//
// Check for matches.
if( listEntry->File != theQTFile )
continue;
theQTFile->DecBufferUserCount();
//
// Delete the object if the reference count has dropped to zero.
if ( --listEntry->ReferenceCount == 0 )
{
//
// Delete the file.
if( theQTFile != NULL )
{ delete theQTFile;
}
//
// Free our other vars.
if( listEntry->InitMutex != NULL )
delete listEntry->InitMutex;
if( listEntry->fFilename != NULL )
delete [] listEntry->fFilename;
//
// Remove this entry from the list.
if( listEntry->PrevEntry != NULL )
listEntry->PrevEntry->NextEntry = listEntry->NextEntry;
if( listEntry->NextEntry != NULL )
listEntry->NextEntry->PrevEntry = listEntry->PrevEntry;
if( QTRTPFile::gFirstFileCacheEntry == listEntry )
QTRTPFile::gFirstFileCacheEntry = listEntry->NextEntry;
delete listEntry;
}
//
// Return.
return;
}
//
// The object was not in the cache. Delete it
if( theQTFile != NULL )
{ delete theQTFile;
}
}
void QTRTPFile::AddFileToCache(const char *inFilename, QTRTPFile::RTPFileCacheEntry ** newListEntry)
{
// General vars
OSMutexLocker fileCacheMutex(QTRTPFile::gFileCacheMutex);
QTRTPFile::RTPFileCacheEntry* listEntry;
QTRTPFile::RTPFileCacheEntry* lastListEntry;
//
// Add this track object to our track list.
(*newListEntry) = NEW QTRTPFile::RTPFileCacheEntry();
if( (*newListEntry) == NULL )
return;
(*newListEntry)->InitMutex = NEW OSMutex();
if( (*newListEntry)->InitMutex == NULL ) {
delete (*newListEntry);
*newListEntry = NULL;
return;
}
(*newListEntry)->InitMutex->Lock();
(*newListEntry)->fFilename = NEW char[(::strlen(inFilename) + 2)];
::strcpy((*newListEntry)->fFilename, inFilename);
(*newListEntry)->File = NULL;
(*newListEntry)->ReferenceCount = 1;
(*newListEntry)->PrevEntry = NULL;
(*newListEntry)->NextEntry = NULL;
//
// Make this the first entry if there are no entries, otherwise we need to
// find out where this file fits in the list and insert it there.
if( QTRTPFile::gFirstFileCacheEntry == NULL )
{
QTRTPFile::gFirstFileCacheEntry = (*newListEntry);
}
else
{
//
// Go through the cache list until we find an inode number greater than
// the one that we have now. Insert it in the list when we find this.
for( listEntry = lastListEntry = QTRTPFile::gFirstFileCacheEntry; listEntry != NULL; listEntry = listEntry->NextEntry ) {
//
// This is the last list entry that we saw (useful for later).
lastListEntry = listEntry;
//
// Skip this entry if this inode number is smaller than the one
// for our new entry.
if( strcmp(listEntry->fFilename,inFilename) < 0 )
continue;
//
// We've found a larger inode; insert this one in the list.
if( listEntry->PrevEntry == NULL )
QTRTPFile::gFirstFileCacheEntry = (*newListEntry);
else
listEntry->PrevEntry->NextEntry = (*newListEntry);
(*newListEntry)->PrevEntry = listEntry->PrevEntry;
listEntry->PrevEntry = (*newListEntry);
(*newListEntry)->NextEntry = listEntry;
return;
}
//
// We fell out of our loop; this means that we are the largest inode
// in the list; add ourselves to the end of the list.
if( lastListEntry == NULL ) { // this can't happen, but..
QTRTPFile::gFirstFileCacheEntry = (*newListEntry);
}
else
{
lastListEntry->NextEntry = (*newListEntry);
(*newListEntry)->PrevEntry = lastListEntry;
}
}
}
Bool16 QTRTPFile::FindAndRefcountFileCacheEntry(const char *inFilename, QTRTPFile::RTPFileCacheEntry **cacheEntry)
{
// General vars
OSMutexLocker fileCacheMutex(QTRTPFile::gFileCacheMutex);
QTRTPFile::RTPFileCacheEntry *listEntry;
//
// Find the specified cache entry.
for( listEntry = QTRTPFile::gFirstFileCacheEntry; listEntry != NULL; listEntry = listEntry->NextEntry )
{
//
// Check for matches.
if( ::strcmp(listEntry->fFilename, inFilename) != 0 )
continue;
//
// Update the reference count and set the return value.
listEntry->ReferenceCount++;
*cacheEntry = listEntry;
//
// Return.
return true;
}
//
// The search failed.
return false;
}
// -------------------------------------
// Constructors and destructors
//
QTRTPFile::QTRTPFile(Bool16 debugFlag, Bool16 deepDebugFlag)
: fDebug(debugFlag)
, fDeepDebug(deepDebugFlag)
, fFile(NULL)
, fFCB(NULL)
, fNumHintTracks(0)
, fFirstTrack(NULL)
, fLastTrack(NULL)
, fCurSeekTrack(NULL)
, fSDPFile(NULL)
, fSDPFileLength(0)
, fNumSkippedSamples(0)
, fRequestedSeekTime(0.0)
, fSeekTime(0.0)
, fLastPacketTrack(NULL)
, fBytesPerSecond(0)
, fHasRTPMetaInfoFieldArray(false)
, fWasLastSeekASeekToPacketNumber(false)
, fDropRepeatPackets(false)
, fErr(errNoError)
{
fFCB = NEW QTFile_FileControlBlock();
}
QTRTPFile::~QTRTPFile(void)
{
//
// Free our track list (and the associated tracks)
RTPTrackListEntry* trackEntry = fFirstTrack;
RTPTrackListEntry* nextTrackEntry;
while( trackEntry != NULL )
{
nextTrackEntry = trackEntry->NextTrack;
//
// Delete this track entry and move to the next one.
if( trackEntry->HTCB != NULL )
delete trackEntry->HTCB;
delete trackEntry;
trackEntry = nextTrackEntry;
}
//
// Free our variables
if( fSDPFile != NULL )
delete[] fSDPFile;
this->delete_QTFile(fFile);
if( fFCB != NULL )
delete fFCB;
}
// -------------------------------------
// Initialization functions.
//
QTRTPFile::ErrorCode QTRTPFile::Initialize(const char * filePath)
{
// Temporary vars
QTRTPFile::ErrorCode rc;
// General vars
QTTrack *track;
//
// Create our file object.
rc = this->new_QTFile(filePath, &fFile, fDebug, fDeepDebug);
if ( rc != errNoError )
{
fFile = NULL;
return rc;
}
//
// Iterate through all of the tracks, adding hint tracks to our list.
for ( track = NULL; fFile->NextTrack(&track, track); )
{
// General vars
QTHintTrack *hintTrack;
RTPTrackListEntry *listEntry;
//
// Skip over anything that's *not* a hint track.
if( !fFile->IsHintTrack(track) )
continue;
hintTrack = (QTHintTrack *)track;
//
// Add this track object to our track list.
listEntry = NEW RTPTrackListEntry();
if( listEntry == NULL )
return fErr = errNoHintTracks;
listEntry->TrackID = track->GetTrackID();
listEntry->HintTrack = hintTrack;
listEntry->HTCB = NEW QTHintTrack_HintTrackControlBlock(fFCB);
listEntry->IsTrackActive = false;
listEntry->IsPacketAvailable = false;
listEntry->QualityLevel = kAllPackets;
listEntry->Cookie1 = NULL;
listEntry->Cookie2 = 0;
listEntry->SSRC = 0;
listEntry->BaseSequenceNumberRandomOffset = 0;
listEntry->FileSequenceNumberRandomOffset = 0;
listEntry->LastSequenceNumber = 0;
listEntry->SequenceNumberAdditive = 0;
listEntry->BaseTimestampRandomOffset = 0;
listEntry->FileTimestampRandomOffset = 0;
listEntry->CurSampleNumber = 0;
listEntry->ConsecutivePFramesSent = 0;
listEntry->TargetPercentage = 0;
listEntry->SampleToSeekTo = 0;
listEntry->LastSyncSampleNumber = 0;
listEntry->NextSyncSampleNumber = 0;
listEntry->NumPacketsInThisSample = 0;
listEntry->CurPacketNumber = 0;
listEntry->CurPacketTime = 0.0;
listEntry->CurPacketLength = 0;
listEntry->NextTrack = NULL;
if( fFirstTrack == NULL ) {
fFirstTrack = fLastTrack = listEntry;
} else {
fLastTrack->NextTrack = listEntry;
fLastTrack = listEntry;
}
// One more track..
fNumHintTracks++;
}
// If there aren't any hint tracks, there's no way we can stream this movie,
// so notify the caller
if (fNumHintTracks == 0)
return fErr = errNoHintTracks;
// The RTP file has been initialized.
return errNoError;
}
// -------------------------------------
// Accessors
//
Float64 QTRTPFile::GetMovieDuration(void)
{
return fFile->GetDurationInSeconds();
}
UInt64 QTRTPFile::GetAddedTracksRTPBytes(void)
{
// Temporary vars
RTPTrackListEntry *curEntry;
// General vars
UInt64 totalRTPBytes = 0;
//
// Go through all of the tracks, adding up the size of the RTP bytes
// for all active tracks.
for( curEntry = fFirstTrack;
curEntry != NULL;
curEntry = curEntry->NextTrack
)
{
//
// We only want active tracks.
if( !curEntry->IsTrackActive )
continue;
//
// Add this length to our count.
totalRTPBytes += curEntry->HintTrack->GetTotalRTPBytes();
}
//
// Return the size.
return totalRTPBytes;
}
char * QTRTPFile::GetSDPFile(int * sdpFileLength)
{
// Temporary vars
RTPTrackListEntry* curEntry;
UInt32 tempAtomType;
// General vars
QTFile::AtomTOCEntry* globalSDPTOCEntry;
Bool16 haveGlobalSDPAtom = false;
char sdpRangeLine[256];
char* pSDPFile;
//
// Return our cached SDP file if we have one.
if ( fSDPFile != NULL)
{
*sdpFileLength = fSDPFileLength;
return fSDPFile;
}
//
// Build our range header.
qtss_sprintf(sdpRangeLine, "a=range:npt=0-%10.5f\r\n", this->GetMovieDuration());
//
// Figure out how long the SDP file is going to be.
fSDPFileLength = ::strlen(sdpRangeLine);
for ( curEntry = fFirstTrack;
curEntry != NULL;
curEntry = curEntry->NextTrack
)
{
// Temporary vars
int trackSDPLength;
//
// Get the length of this track's SDP file.
if( curEntry->HintTrack->GetSDPFileLength(&trackSDPLength) != QTTrack::errNoError )
continue;
//
// Add it to our count.
fSDPFileLength += trackSDPLength;
}
//
// See if this movie has a global SDP atom.
if( fFile->FindTOCEntry("moov:udta:hnti:rtp ", &globalSDPTOCEntry, NULL) )
{
//
// Verify that this is an SDP atom.
fFile->Read(globalSDPTOCEntry->AtomDataPos, (char *)&tempAtomType, 4);
if ( ntohl(tempAtomType) == FOUR_CHARS_TO_INT('s', 'd', 'p', ' ') )
{
haveGlobalSDPAtom = true;
fSDPFileLength += (UInt32) (globalSDPTOCEntry->AtomDataLength - 4);
}
}
//
// Build the SDP file.
fSDPFile = pSDPFile = NEW char[fSDPFileLength + 1];
if( fSDPFile == NULL )
{ fErr = errInternalError;
return NULL;
}
if( haveGlobalSDPAtom )
{
fFile->Read(globalSDPTOCEntry->AtomDataPos + 4, pSDPFile, (UInt32) (globalSDPTOCEntry->AtomDataLength - 4) );
pSDPFile += globalSDPTOCEntry->AtomDataLength - 4;
}
::memcpy(pSDPFile, sdpRangeLine, ::strlen(sdpRangeLine));
pSDPFile += ::strlen(sdpRangeLine);
for ( curEntry = fFirstTrack;
curEntry != NULL;
curEntry = curEntry->NextTrack
)
{
// Temporary vars
char *trackSDP;
int trackSDPLength;
//
// Get this track's SDP file and add it to our buffer.
trackSDP = curEntry->HintTrack->GetSDPFile(&trackSDPLength);
if( trackSDP == NULL )
continue;
::memcpy(pSDPFile, trackSDP, trackSDPLength);
delete [] trackSDP;//ARGH! GetSDPFile allocates the pointer that is being returned.
pSDPFile += trackSDPLength;
}
//
// Return the (cached) SDP file.
*sdpFileLength = fSDPFileLength;
fSDPFile[fSDPFileLength] = 0;
return fSDPFile;
}
char* QTRTPFile::GetMoviePath()
{
Assert( fFile );
if (!fFile)
{ fErr = errInternalError;
return NULL;
}
else
return fFile->GetMoviePath();
}
// -------------------------------------
// track functions
//
QTRTPFile::ErrorCode QTRTPFile::AddTrack(UInt32 trackID, Bool16 useRandomOffset)
{
// General vars
RTPTrackListEntry *trackEntry = NULL;
//
// Find this track.
if( !this->FindTrackEntry(trackID, &trackEntry) )
return fErr = errTrackIDNotFound;
{
OSMutexLocker locker(fFile->GetMutex());
//
// Initialize this track.
trackEntry->HintTrack->SetAllowInvalidHintRefs(fAllowInvalidHintRefs);
if( trackEntry->HintTrack->Initialize() != QTTrack::errNoError )
return fErr = errInternalError;
}
//
// Set up the sequence number and timestamp offsets.
if( useRandomOffset ) {
trackEntry->BaseSequenceNumberRandomOffset = (UInt16)rand();
trackEntry->BaseTimestampRandomOffset = (UInt32)rand();
} else {
trackEntry->BaseSequenceNumberRandomOffset = 0;
trackEntry->BaseTimestampRandomOffset = 0;
}
trackEntry->FileSequenceNumberRandomOffset = trackEntry->HintTrack->GetRTPSequenceNumberRandomOffset();
trackEntry->FileTimestampRandomOffset = trackEntry->HintTrack->GetRTPTimestampRandomOffset();
trackEntry->LastSequenceNumber = 0;
trackEntry->SequenceNumberAdditive = 0;
//
// Setup RTP-Meta-Info stuff for this track.
//
// This track is now active.
trackEntry->IsTrackActive = true;
//
// The track has been added.
return errNoError;
}
Float64 QTRTPFile::GetTrackDuration(UInt32 trackID)
{
// General vars
RTPTrackListEntry *trackEntry = NULL;
//
// Find this track.
if( !this->FindTrackEntry(trackID, &trackEntry) )
return 0;
//
// Return the duration.
return trackEntry->HintTrack->GetDurationInSeconds();
}
UInt32 QTRTPFile::GetTrackTimeScale(UInt32 trackID)
{
// General vars
RTPTrackListEntry *trackEntry = NULL;
//
// Find this track.
if( !this->FindTrackEntry(trackID, &trackEntry) )
return 0;
//
// Return the duration.
return (UInt32)trackEntry->HintTrack->GetTimeScale();
}
void QTRTPFile::SetTrackSSRC(UInt32 trackID, UInt32 SSRC)
{
// General vars
RTPTrackListEntry *trackEntry = NULL;
//
// Find this track.
if( !this->FindTrackEntry(trackID, &trackEntry) )
return;
//
// Set the SSRC.
trackEntry->SSRC = SSRC;
}
void QTRTPFile::SetTrackCookies(UInt32 TrackID, void * Cookie1, UInt32 Cookie2)
{
// General vars
RTPTrackListEntry *trackEntry = NULL;
//
// Find this track.
if( !this->FindTrackEntry(TrackID, &trackEntry) )
return;
//
// Set the cookie.
trackEntry->Cookie1 = Cookie1;
trackEntry->Cookie2 = Cookie2;
}
void QTRTPFile::SetTrackQualityLevel(RTPTrackListEntry* inEntry, UInt32 inNewQualityLevel)
{
if (inNewQualityLevel < kAllPackets)
inNewQualityLevel = kAllPackets;
if (inNewQualityLevel > kKeyFramesPlusOneP)
inNewQualityLevel = kKeyFramesPlusOneP;
if (inNewQualityLevel != inEntry->QualityLevel)
{
inEntry->QualityLevel = inNewQualityLevel;
if (inEntry->QualityLevel == k75PercentPFrames)
inEntry->TargetPercentage = 75;
else if (inEntry->QualityLevel == k50PercentPFrames)
inEntry->TargetPercentage = 50;
else if (inEntry->QualityLevel == k25PercentPFrames)
inEntry->TargetPercentage = 25;
}
}
void QTRTPFile::SetTrackRTPMetaInfo(UInt32 TrackID, RTPMetaInfoPacket::FieldID* inFieldArray, Bool16 isVideo)
{
// General vars
RTPTrackListEntry *trackEntry = NULL;
//
// Find this track.
if( !this->FindTrackEntry(TrackID, &trackEntry) )
return;
//
// Set the cookie.
trackEntry->HTCB->SetupRTPMetaInfo(inFieldArray, isVideo);
fHasRTPMetaInfoFieldArray = true;
fDropRepeatPackets = false; //force repeat packets on meta info connections.
}
// -------------------------------------
// BytesPerSecond
UInt32 QTRTPFile::GetBytesPerSecond(void)
{
if (NULL == fFile)
return 0;
// Must be a SInt64 bc Win32 doesn't implement UInt64 -> Float64
SInt64 totalBytes = (SInt64)QTRTPFile::GetAddedTracksRTPBytes();
#ifdef USE_RTP_TRACK_DURATION
Float64 duration = 0;
RTPTrackListEntry* curEntry=NULL;
Float64 maxDuration=0;
//
// Go through all of the tracks, getting duration
for( curEntry = fFirstTrack;
curEntry != NULL;
curEntry = curEntry->NextTrack
)
{
//
// We only want active tracks.
if( !curEntry->IsTrackActive )
continue;
duration = curEntry->HintTrack->GetDurationInSeconds();
if (duration > maxDuration)
maxDuration = duration;
}
duration= maxDuration;
#else
Float64 duration = fFile->GetDurationInSeconds();
#endif
UInt32 bytesPerSecond = 0;
if (duration > 0)
bytesPerSecond = (UInt32) ( (Float64) totalBytes / (Float64) duration);
return bytesPerSecond;
}
// -------------------------------------
void QTRTPFile::AllocatePrivateBuffers(UInt32 inUnitSizeInK, UInt32 inNumBuffSizeUnits, UInt32 inMaxBitRateBuffSizeInBlocks)
{
fFCB->EnableCacheBuffers(true);
UInt32 bytesPerSecond = this->GetBytesPerSecond();
UInt32 bitRate = bytesPerSecond * 8;
fFCB->AdjustDataBufferBitRate(inUnitSizeInK, bitRate, inNumBuffSizeUnits, inMaxBitRateBuffSizeInBlocks);
}
// -------------------------------------
// Packet functions
//
QTRTPFile::ErrorCode QTRTPFile::Seek(Float64 seekToTime, Float64 maxBackupTime)
{
//fHasRTPMetaInfoFieldArray = true;
// General vars
RTPTrackListEntry *listEntry = NULL;
Float64 syncToTime = seekToTime;
if (fErr == errCallAgain)
{
fErr = this->ScanToCorrectSample();
return fErr;
}
//
// This is Seek, not SeekToPacketNumber.
fWasLastSeekASeekToPacketNumber = false;
//
// Find the earliest sync sample and sync all of the tracks to that
// sample.
for ( listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack )
{
// General vars
SInt32 mediaTime;
UInt32 newSampleNumber;
UInt32 newSyncSampleNumber;
UInt32 newSampleMediaTime;
Float64 newSampleTime;
//
// Only consider active tracks.
if( !listEntry->IsTrackActive )
continue;
//
// Compute the media time and get the sample at that time.
mediaTime = (SInt32)(seekToTime * listEntry->HintTrack->GetTimeScale());
mediaTime -= listEntry->HintTrack->GetFirstEditMediaTime();
if ( mediaTime < 0 )
mediaTime = 0;
if ( !listEntry->HintTrack->GetSampleNumberFromMediaTime(mediaTime, &newSampleNumber, &listEntry->HTCB->fsttsSTCB) )
continue; // This track is probably done playing.
//
// Find the nearest (moving backwards in time) keyframe.
listEntry->HintTrack->GetPreviousSyncSample(newSampleNumber, &newSyncSampleNumber);
if ( newSampleNumber == newSyncSampleNumber )
continue;
//
// Figure out what time this sample is at.
if( !listEntry->HintTrack->GetSampleMediaTime(newSyncSampleNumber, &newSampleMediaTime, &listEntry->HTCB->fsttsSTCB) )
return errInvalidQuickTimeFile;
newSampleMediaTime += listEntry->HintTrack->GetFirstEditMediaTime();
newSampleTime = (Float64)newSampleMediaTime * listEntry->HintTrack->GetTimeScaleRecip();
//
// Figure out if this is the time that we need to sync to.
if( newSampleTime < syncToTime )
syncToTime = newSampleTime;
}
if (0 == seekToTime) // optimize the first read to align to the first chunk
{
UInt64 firstChunkOffset = ~ (UInt64)0;// max UInt64
UInt64 trackChunkOffset = 0;
QTTrack *track = NULL;
for (; fFile->NextTrack(&track, track) ; )
{
if (track == NULL)
continue;
if ( !track->IsInitialized() && track->Initialize() != QTTrack::errNoError)
continue;
track->ChunkOffset(1, &trackChunkOffset);
if ( (UInt64)trackChunkOffset < firstChunkOffset)
firstChunkOffset = (UInt64) trackChunkOffset;
}
if (~(UInt64)0 != firstChunkOffset)
{
char junk[4];
fFile->Read(firstChunkOffset, junk, sizeof(junk),fFCB);
}
}
//
// Evaluate/Store the seek time
fRequestedSeekTime = seekToTime;
if( (seekToTime - syncToTime) <= maxBackupTime )
fSeekTime = syncToTime;
else
fSeekTime = seekToTime;
for ( listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack )
{
if( !listEntry->IsTrackActive )
continue;
listEntry->IsPacketAvailable = false;
//
// Reset the sample table caches.
listEntry->HTCB->fstscSTCB.Reset();
listEntry->HTCB->Reset();
//
// Compute the media time and get the sample at that time.
SInt32 mediaTime = (SInt32)(fSeekTime * listEntry->HintTrack->GetTimeScale());
mediaTime -= listEntry->HintTrack->GetFirstEditMediaTime();
if( mediaTime < 0 )
mediaTime = 0;
listEntry->SampleToSeekTo = 0;
if (!listEntry->HintTrack->GetSampleNumberFromMediaTime(mediaTime, &listEntry->SampleToSeekTo, &listEntry->HTCB->fsttsSTCB))
continue;
//
// If we are building meta-info packets, we have to build all the packets up until the destination
// point in the movie, we can't just skip there. So, start by jumping to the beginning of the movie,
// then PrefetchPacketForAllTracks will move to the right point in the movie
if (fHasRTPMetaInfoFieldArray)
{
mediaTime = 0;
mediaTime -= listEntry->HintTrack->GetFirstEditMediaTime();
if( mediaTime < 0 )
mediaTime = 0;
listEntry->CurSampleNumber = 0;
if (!listEntry->HintTrack->GetSampleNumberFromMediaTime(mediaTime, &listEntry->CurSampleNumber, &listEntry->HTCB->fsttsSTCB))
continue;
}
else
listEntry->CurSampleNumber = listEntry->SampleToSeekTo;
//
// Clear our current packet information.
listEntry->NumPacketsInThisSample = 0;
listEntry->CurPacketNumber = 0;
if (this->PrefetchNextPacket(listEntry, true))
listEntry->IsPacketAvailable = true;
}
//
// 'Forget' that we had a previous packet.
fLastPacketTrack = NULL;
if (fHasRTPMetaInfoFieldArray)
{
fCurSeekTrack = fFirstTrack;
fErr = this->ScanToCorrectSample();
return fErr;
}
return errNoError;
}
QTRTPFile::ErrorCode QTRTPFile::ScanToCorrectSample()
{
UInt32 packetCount = 0;
//
// Prefetch a packet for all active tracks.
for( ; fCurSeekTrack != NULL; fCurSeekTrack = fCurSeekTrack->NextTrack )
{
//
// Only fetch packets on active tracks.
if( !fCurSeekTrack->IsTrackActive )
continue;
//
// Scan through all the packets in this track until we get to the sample we want
while (fCurSeekTrack->CurSampleNumber < fCurSeekTrack->SampleToSeekTo)
{
if (packetCount > 100)
return errCallAgain;
else
packetCount++;
if (!this->PrefetchNextPacket(fCurSeekTrack))
{
fCurSeekTrack->IsPacketAvailable = false;
break;
}
}
}
return errNoError;
}
QTRTPFile::ErrorCode QTRTPFile::SeekToPacketNumber(UInt32 inTrackID, UInt64 inPacketNumber)
{
if (fErr == errCallAgain)
{
fErr = this->ScanToCorrectPacketNumber(inTrackID, inPacketNumber);
return fErr;
}
//
// We need to track this so that we don't use the sync sample table if we start thinnning
fWasLastSeekASeekToPacketNumber = true;
for (RTPTrackListEntry *listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack )
{
if( !listEntry->IsTrackActive )
continue;
listEntry->IsPacketAvailable = false;
//
// Reset the sample table caches.
listEntry->HTCB->fstscSTCB.Reset();
listEntry->HTCB->Reset();
//
// Jump to the beginning of each track
SInt32 mediaTime = 0;
mediaTime -= listEntry->HintTrack->GetFirstEditMediaTime();
if( mediaTime < 0 )
mediaTime = 0;
listEntry->CurSampleNumber = 0;
if (!listEntry->HintTrack->GetSampleNumberFromMediaTime(mediaTime, &listEntry->CurSampleNumber, &listEntry->HTCB->fsttsSTCB))
continue;
//
// Clear our current packet information.
listEntry->NumPacketsInThisSample = 0;
listEntry->CurPacketNumber = 0;
if (this->PrefetchNextPacket(listEntry, true))
listEntry->IsPacketAvailable = true;
}
if (inPacketNumber == 0)
return errNoError;
fErr = this->ScanToCorrectPacketNumber(inTrackID, inPacketNumber);
return fErr;
}
QTRTPFile::ErrorCode QTRTPFile::ScanToCorrectPacketNumber(UInt32 inTrackID, UInt64 inPacketNumber)
{
int theLen = 0;
for (UInt32 packetCount = 0; packetCount < 100; packetCount++)
{
if ((fLastPacketTrack != NULL) && (fLastPacketTrack->TrackID == inTrackID) && (fLastPacketTrack->HTCB->fCurrentPacketNumber == inPacketNumber))
{
UInt32 newSampleMediaTime;
//
// Figure out what time this sample is at.
if( !fLastPacketTrack->HintTrack->GetSampleMediaTime(fLastPacketTrack->CurSampleNumber, &newSampleMediaTime, &fLastPacketTrack->HTCB->fsttsSTCB) )
return errInvalidQuickTimeFile;
newSampleMediaTime += fLastPacketTrack->HintTrack->GetFirstEditMediaTime();
fRequestedSeekTime = (Float64)newSampleMediaTime * fLastPacketTrack->HintTrack->GetTimeScaleRecip();
//fLastPacketTrack = NULL; // So that when we next call GetNextPacket, we actually get the same packet
return errNoError;
}
char* thePacket = NULL;
(void)this->GetNextPacket(&thePacket, &theLen);
if (thePacket == NULL)
return errInvalidQuickTimeFile;
}
return errCallAgain;
}
UInt32 QTRTPFile::GetSeekTimestamp(UInt32 trackID)
{
// General vars
RTPTrackListEntry *trackEntry = NULL;
UInt32 mediaTime;
UInt32 rtpTimestamp;
UInt32* timeStampP;
DEEP_DEBUG_PRINT(("Calculating RTP timestamp for track #%"_U32BITARG_" at time %.2f.\n", trackID, fRequestedSeekTime));
//
// Find this track.
if( !FindTrackEntry(trackID, &trackEntry) )
return 0;
if (trackEntry->CurPacket) // we have the packet
{
timeStampP = (UInt32 *)((char *)trackEntry->CurPacket + 4);
rtpTimestamp = ntohl(*timeStampP);
}
else
{
//
// Calculate the timestamp at this seek time.
mediaTime = (UInt32)(fRequestedSeekTime * trackEntry->HintTrack->GetTimeScale());
if( trackEntry->HintTrack->GetRTPTimescale() == trackEntry->HintTrack->GetTimeScale() )
rtpTimestamp = mediaTime;
else
rtpTimestamp = (UInt32)(mediaTime * (trackEntry->HintTrack->GetRTPTimescale() * trackEntry->HintTrack->GetTimeScaleRecip()) );
//
// Add the appropriate offsets.
rtpTimestamp += trackEntry->BaseTimestampRandomOffset + trackEntry->FileTimestampRandomOffset;
}
//
// Return the RTP timestamp.
DEEP_DEBUG_PRINT(("..timestamp=%"_U32BITARG_"\n", rtpTimestamp));
return rtpTimestamp;
}
Float64 QTRTPFile::GetFirstPacketTransmitTime()
{
RTPTrackListEntry *listEntry = NULL;
Bool16 haveFirstPacketTime = false;
Float64 firstPacketTime = 0;
//
// Figure out which track is going to produce the next packet.
for( listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack )
{
//
// Only look at active tracks that have packets.
if( !listEntry->IsTrackActive || !listEntry->IsPacketAvailable)
continue;
//
// See if this track has a time earlier than our initial time.
if( (listEntry->CurPacketTime <= firstPacketTime) || !haveFirstPacketTime )
{
haveFirstPacketTime = true;
firstPacketTime = listEntry->CurPacketTime;
}
}
return firstPacketTime;
}
UInt16 QTRTPFile::GetNextTrackSequenceNumber(UInt32 trackID)
{
// General vars
RTPTrackListEntry *trackEntry = NULL;
UInt16 rtpSequenceNumber;
//
// Find this track.
if( !this->FindTrackEntry(trackID, &trackEntry) )
return 0;
//
// Read the sequence number right out of the packet.
::memcpy(&rtpSequenceNumber, (char *)trackEntry->CurPacket + 2, 2);
return ntohs(rtpSequenceNumber);
}
Float64 QTRTPFile::GetNextPacket(char ** outPacket, int * outPacketLength)
{
// General vars
RTPTrackListEntry *listEntry = NULL;
Bool16 haveFirstPacketTime = false;
Float64 firstPacketTime = 0.0;
RTPTrackListEntry *firstPacket = NULL;
//
// Clear the input.
*outPacket = NULL;
*outPacketLength = 0;
//
// Prefetch the next packet of the track that the *last* packet came from.
if( fLastPacketTrack != NULL )
{
if ( !this->PrefetchNextPacket(fLastPacketTrack) )
fLastPacketTrack->IsPacketAvailable = false;
}
//
// Figure out which track is going to produce the next packet.
for( listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack )
{
//
// Only look at active tracks that have packets.
if( !listEntry->IsTrackActive || !listEntry->IsPacketAvailable)
continue;
//
// See if this track has a time earlier than our initial time.
if( (listEntry->CurPacketTime <= firstPacketTime) || !haveFirstPacketTime )
{
haveFirstPacketTime = true;
firstPacketTime = listEntry->CurPacketTime;
firstPacket = listEntry;
}
}
//
// Abort if we didn't find a packet. Either the movie is over, or there
// weren't any packets to begin with.
if( firstPacket == NULL )
return 0.0;
//
// Remember the sequence number of this packet.
firstPacket->LastSequenceNumber = ntohs(*(UInt16 *)((char *)firstPacket->CurPacket + 2));
firstPacket->LastSequenceNumber -= (UInt16) (firstPacket->BaseSequenceNumberRandomOffset + firstPacket->FileSequenceNumberRandomOffset + firstPacket->SequenceNumberAdditive);
//
// Return this packet.
fLastPacketTrack = firstPacket;
*outPacket = firstPacket->CurPacket;
*outPacketLength = firstPacket->CurPacketLength;
return firstPacket->CurPacketTime;
}
// -------------------------------------
// Protected member functions
//
Bool16 QTRTPFile::FindTrackEntry(UInt32 trackID, RTPTrackListEntry **trackEntry)
{
// General vars
RTPTrackListEntry *listEntry;
//
// Find the specified track.
for( listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack )
{
//
// Check for matches.
if( listEntry->TrackID == trackID )
{
*trackEntry = listEntry;
return true;
}
}
//
// The search failed.
return false;
}
SInt32 QTRTPFile::GetMovieHintType()
{
SInt32 movieHintType= 0;
SInt32 trackHintType= 0;
for( RTPTrackListEntry *listEntry = fFirstTrack; listEntry != NULL; listEntry = listEntry->NextTrack )
{
//
// Check for matches.
trackHintType = listEntry->HintTrack->GetHintTrackType();
if (trackHintType > 0) // always set movie hint type to unoptimized if a track is unoptimized
{ movieHintType = trackHintType;
}
else if ( (movieHintType == 0) && trackHintType < 0) // only set optimized if movie type is uninitialized
{ movieHintType = trackHintType;
}
}
return movieHintType;
}
Bool16 QTRTPFile::PrefetchNextPacket(RTPTrackListEntry * trackEntry, Bool16 doSeek)
{
// General vars
UInt16 *pSequenceNumber;
UInt32 *pTimestamp;
Bool16 skipThisSample = false;
// Temporary vars
QTTrack::ErrorCode getPacketErr = QTTrack::errIsSkippedPacket;
// If we are dropping b-frames or repeat packets, QTHintTrack::GetPacket will return the errIsSkippedPacket error to us.
// If we get that error, we should fetch another packet. So, we have this loop here.
while (getPacketErr == QTTrack::errIsSkippedPacket)
{
//
// Next packet (or first packet, since CurPacketNumber starts at 0 above)
trackEntry->CurPacketNumber++;
//
// Do we need to move to the next sample?
if ( (trackEntry->CurPacketNumber > trackEntry->NumPacketsInThisSample)
&& (trackEntry->NumPacketsInThisSample != 0)
)
{
if (trackEntry->HintTrack->IsSyncSample(trackEntry->CurSampleNumber, 0))
{
trackEntry->LastSyncSampleNumber = trackEntry->CurSampleNumber;
if (trackEntry->NextSyncSampleNumber != trackEntry->CurSampleNumber)
trackEntry->NextSyncSampleNumber = 0; // this must have been obsolete
}
//
// If we're only reading sync samples, then we need to find the next
// one to send out, otherwise just increment the sample number and
// move on.
if( trackEntry->QualityLevel == kKeyFramesOnly )
{
trackEntry->HintTrack->GetNextSyncSample(trackEntry->CurSampleNumber, &trackEntry->NextSyncSampleNumber);
if (!fHasRTPMetaInfoFieldArray && !fWasLastSeekASeekToPacketNumber)
{
fNumSkippedSamples += trackEntry->NextSyncSampleNumber - trackEntry->CurSampleNumber;
trackEntry->CurSampleNumber = trackEntry->NextSyncSampleNumber;
}
else
{
//
// If we are generating RTPMetaInfo packets, we need to track
// the total number of packets we are generating. The only way
// to do that is to actually generate all the packets. So do so.
trackEntry->CurSampleNumber++;
if (trackEntry->CurSampleNumber != trackEntry->NextSyncSampleNumber)
{
fNumSkippedSamples++;
skipThisSample = true;
}
else
skipThisSample = false;
}
}
else if (trackEntry->QualityLevel >= k75PercentPFrames)
{
//
// If we are skipping a certain % of p-frames, figure out
// whether we need to skip this p-frame sample
trackEntry->CurSampleNumber++;
skipThisSample = false;
//
// Only skip this sample if it is not a sync sample
if (!trackEntry->HintTrack->IsSyncSample(trackEntry->CurSampleNumber, 0))
{
//
// figure out where the next sync sample is
if (trackEntry->CurSampleNumber >= trackEntry->NextSyncSampleNumber)
{
trackEntry->HintTrack->GetNextSyncSample(trackEntry->CurSampleNumber, &trackEntry->NextSyncSampleNumber);
}
// this shouldn't ever be false, but I'm worried about when people skip backwards in movies
// in that case, just don't thin.
if (trackEntry->CurSampleNumber > trackEntry->LastSyncSampleNumber)
{
Assert(trackEntry->NextSyncSampleNumber != trackEntry->LastSyncSampleNumber);
UInt32 percentageIn = (trackEntry->CurSampleNumber - trackEntry->LastSyncSampleNumber) * 100 /
(trackEntry->NextSyncSampleNumber - trackEntry->LastSyncSampleNumber);
if (trackEntry->QualityLevel == kKeyFramesPlusOneP)
skipThisSample = trackEntry->CurSampleNumber - trackEntry->LastSyncSampleNumber > 1;
else if (percentageIn > trackEntry->TargetPercentage)
skipThisSample = true;
/*
if (trackEntry->QualityLevel == k50PercentPFrames)
skipThisSample = trackEntry->CurSampleNumber % 2 == 0;
else if (trackEntry->QualityLevel == k75PercentPFrames)
skipThisSample = trackEntry->CurSampleNumber % 4 == 0;
else if (trackEntry->QualityLevel == k25PercentPFrames)
skipThisSample = trackEntry->CurSampleNumber % 4 != 0;
*/
}
}
}
else
{
trackEntry->CurSampleNumber++;
skipThisSample = false;
}
//
// We'll need to recompute the number of samples in this packet.
trackEntry->NumPacketsInThisSample = 0;
}
//
// Do we know how many packets are in this sample? If not, figure it out.
while ( trackEntry->NumPacketsInThisSample == 0 )
{
if ( trackEntry->HintTrack->GetNumPackets(trackEntry->CurSampleNumber, &trackEntry->NumPacketsInThisSample, trackEntry->HTCB) != QTTrack::errNoError )
return false;
if ( trackEntry->NumPacketsInThisSample == 0 )
trackEntry->CurSampleNumber++;
trackEntry->CurPacketNumber = 1;
}
//
// Fetch this packet.
trackEntry->CurPacketLength = QTRTPFILE_MAX_PACKET_LENGTH;
#if QT_PROFILE
MicroSecondStopWatch packetTimer;
packetTimer.Start();
#endif
getPacketErr = trackEntry->HintTrack->GetPacket(trackEntry->CurSampleNumber, trackEntry->CurPacketNumber,
trackEntry->CurPacket, &trackEntry->CurPacketLength,
&trackEntry->CurPacketTime,
(trackEntry->QualityLevel >= kNoBFrames),
fDropRepeatPackets,
trackEntry->SSRC,
trackEntry->HTCB);
#if QT_PROFILE
packetTimer.Stop();
qtss_printf( "GetPacket sample time %li NumPackets: %li Packet fetched %li. len: %li\n",
(SInt32)packetTimer.Duration(), (SInt32)trackEntry->NumPacketsInThisSample, (SInt32)trackEntry->CurPacketNumber, (SInt32)trackEntry->CurPacketLength );
#endif
//
// If we are doing keyframes only and this is not a sync sample, don't return this packet.
// This will happen if we are generating RTPMetaInfo packets and thinning.
if (skipThisSample)
getPacketErr = QTTrack::errIsSkippedPacket;
}
if( getPacketErr != QTTrack::errNoError )
{ fErr = errInvalidQuickTimeFile;
return false;
}
//
// Update our sequence number and timestamp. If we seeked to get here,
// then we need to adjust the additive to account for the shift in
// sequence numbers.
pSequenceNumber = (UInt16 *)((char *)trackEntry->CurPacket + 2);
pTimestamp = (UInt32 *)((char *)trackEntry->CurPacket + 4);
if( doSeek || (trackEntry->QualityLevel > kAllPackets) )
trackEntry->SequenceNumberAdditive += (trackEntry->LastSequenceNumber + 1) - ntohs(*pSequenceNumber);
*pSequenceNumber = htons( (SInt16) (((SInt32) ntohs(*pSequenceNumber)) + trackEntry->BaseSequenceNumberRandomOffset + trackEntry->FileSequenceNumberRandomOffset + trackEntry->SequenceNumberAdditive));
*pTimestamp = htonl(ntohl(*pTimestamp) + trackEntry->BaseTimestampRandomOffset + trackEntry->FileTimestampRandomOffset);
//
// Return the packet.
return true;
}