1544 lines
47 KiB
C++
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;
|
|
}
|