1516 lines
55 KiB
C++
1516 lines
55 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@
|
|
*
|
|
*/
|
|
//
|
|
// QTHintTrack:
|
|
// The central point of control for a track in a QTFile.
|
|
|
|
/*
|
|
|
|
9.21.99 rtucker - CVS is down, so I might forget this by the time tis back up
|
|
|
|
|
|
added:
|
|
|
|
changed GetPacket to be more efficient in get RTP packets from the hint track
|
|
|
|
The FastCopyMacros replace calls to memcpy for moving chars, shorts, longs etc.
|
|
|
|
fixed a bug in B-frame thinning. would previously discard all packets in any sample
|
|
containing one or more b-frame packets. seems unlikely this would ever be seen in
|
|
the real world.
|
|
|
|
*/
|
|
|
|
|
|
// -------------------------------------
|
|
// Includes
|
|
//
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include "SafeStdLib.h"
|
|
#include <string.h>
|
|
|
|
#include "QTFile.h"
|
|
|
|
#include "QTAtom.h"
|
|
#include "QTAtom_hinf.h"
|
|
#include "QTAtom_tref.h"
|
|
|
|
#include "QTHintTrack.h"
|
|
#include "OSMutex.h"
|
|
#include "FastCopyMacros.h"
|
|
#include "MyAssert.h"
|
|
#include "OSMemory.h"
|
|
#include "OS.h"
|
|
|
|
// -------------------------------------
|
|
// Macros
|
|
//
|
|
|
|
#define TEMP_PRINT_ON 0
|
|
|
|
#if TEMP_PRINT_ON
|
|
|
|
#define TEMP_PRINT(s) qtss_printf( s )
|
|
#define TEMP_PRINT_ONE( s, p1 ) qtss_printf( s, p1 )
|
|
#define TEMP_PRINT_TWO( s, p1, p2 ) qtss_printf( s, p1, p2 )
|
|
|
|
#else
|
|
|
|
#define TEMP_PRINT(s)
|
|
#define TEMP_PRINT_ONE( s, p1 )
|
|
#define TEMP_PRINT_TWO( s, p1, p2 )
|
|
|
|
#endif
|
|
|
|
#define DEBUG_PRINT(s) if(fDebug) qtss_printf s
|
|
#define DEEP_DEBUG_PRINT(s) if(fDeepDebug) qtss_printf s
|
|
|
|
#define ENABLE_REPEAT_DROPPING 1
|
|
|
|
#define TESTTIME 0
|
|
|
|
// -------------------------------------
|
|
#if TESTTIME
|
|
#include "OS.h"
|
|
#include <sys/time.h>
|
|
|
|
|
|
static Bool16 timerStarted = false;
|
|
static Bool16 doneMediaCount = false;
|
|
static Bool16 doneHintCount = false;
|
|
static SInt64 totalMediaSampleReadTime = 0;
|
|
static SInt64 totalHintSampleReadTime = 0;
|
|
enum {eMicro = 1000000, eMilli =1000};
|
|
#define kMaxPacketCount 10000
|
|
static SInt32 mediaPacketCount = 0;
|
|
static SInt32 hintPacketCount = 0;
|
|
static SInt32 totalMediaLength = 0;
|
|
static SInt32 totalHintLength = 0;
|
|
static SInt64 totalMediaReadTime = 0;
|
|
static SInt64 totalHintReadTime = 0;
|
|
|
|
|
|
SInt64 GetMicroseconds()
|
|
{
|
|
return OS::Milliseconds();
|
|
}
|
|
#endif
|
|
|
|
// -------------------------------------
|
|
// Class state cookie
|
|
//
|
|
QTHintTrack_HintTrackControlBlock::QTHintTrack_HintTrackControlBlock(QTFile_FileControlBlock * FCB)
|
|
: fFCB(FCB),
|
|
|
|
fCachedSampleNumber(0),
|
|
fCachedSample(NULL),
|
|
fCachedSampleSize(0), fCachedSampleLength(0),
|
|
|
|
fCachedHintTrackSampleNumber(0), fCachedHintTrackSampleOffset(0),
|
|
fCachedHintTrackSample(NULL),
|
|
fCachedHintTrackSampleLength(0),
|
|
fLastPacketNumberFetched(0xFFFF),
|
|
fPointerToNextPacket(NULL),
|
|
|
|
fRTPMetaInfoFieldArray(NULL),
|
|
fSyncSampleCursor(0),
|
|
|
|
fCurrentPacketNumber(0),
|
|
fCurrentPacketPosition(0)
|
|
{
|
|
fMediaTrackSTSC_STCB = NULL;
|
|
fMediaTrackRefIndex = -2;
|
|
}
|
|
|
|
QTHintTrack_HintTrackControlBlock::~QTHintTrack_HintTrackControlBlock(void)
|
|
{
|
|
delete fMediaTrackSTSC_STCB;
|
|
delete []fCachedSample;
|
|
delete []fCachedHintTrackSample;
|
|
|
|
delete [] fRTPMetaInfoFieldArray;
|
|
}
|
|
|
|
void QTHintTrack_HintTrackControlBlock::Reset()
|
|
{
|
|
fSyncSampleCursor = 0;
|
|
fCurrentPacketNumber = 0;
|
|
fCurrentPacketPosition = 0;
|
|
}
|
|
|
|
|
|
// -------------------------------------
|
|
// Constructors and destructors
|
|
//
|
|
QTHintTrack::QTHintTrack(QTFile * File, QTFile::AtomTOCEntry * Atom, Bool16 Debug, Bool16 DeepDebug)
|
|
: QTTrack(File, Atom, Debug, DeepDebug),
|
|
fHintInfoAtom(NULL), fHintTrackReferenceAtom(NULL),
|
|
fTrackRefs(NULL),
|
|
fMaxPacketSize(65536),
|
|
fRTPTimescale(0),
|
|
fFirstRTPTimestamp(0),
|
|
fTimestampRandomOffset(0),
|
|
fSequenceNumberRandomOffset(0),
|
|
fHintTrackInitialized(false),
|
|
fHintType(QTHintTrack::kUnknown),
|
|
fFirstTransmitTime(0.0),
|
|
fAllowInvalidHintRefs(false)
|
|
{
|
|
#if TESTTIME
|
|
qtss_printf(" QTHintTrack initialized \n");
|
|
mediaPacketCount = 0;
|
|
hintPacketCount = 0;
|
|
totalMediaSampleReadTime = 0;
|
|
totalHintSampleReadTime = 0;
|
|
totalMediaReadTime = 0;
|
|
totalHintReadTime = 0;
|
|
#endif
|
|
|
|
}
|
|
|
|
QTHintTrack::~QTHintTrack(void)
|
|
{
|
|
|
|
//
|
|
// Free our variables
|
|
if( fHintInfoAtom != NULL )
|
|
delete fHintInfoAtom;
|
|
if( fHintTrackReferenceAtom != NULL )
|
|
delete fHintTrackReferenceAtom;
|
|
|
|
if( fTrackRefs != NULL )
|
|
delete[] fTrackRefs;
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------
|
|
// Initialization functions
|
|
//
|
|
QTTrack::ErrorCode QTHintTrack::Initialize(void)
|
|
{
|
|
// General vars
|
|
char *sampleDescription, *pSampleDescription;
|
|
UInt32 sampleDescriptionLength;
|
|
|
|
QTFile::AtomTOCEntry *hinfTOCEntry;
|
|
|
|
//
|
|
// Don't initialize more than once.
|
|
if( IsHintTrackInitialized() )
|
|
return errNoError;
|
|
|
|
//
|
|
// Initialize the QTTrack class.
|
|
if( QTTrack::Initialize() != errNoError )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
|
|
//
|
|
// Get the sample description table for this track and verify that it is an
|
|
// RTP track.
|
|
if( !fSampleDescriptionAtom->FindSampleDescription(FOUR_CHARS_TO_INT('r', 't', 'p', ' '), &sampleDescription, &sampleDescriptionLength) )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
::memcpy(&fMaxPacketSize, sampleDescription + 20, 4);
|
|
fMaxPacketSize = ntohl(fMaxPacketSize);
|
|
|
|
for( pSampleDescription = (sampleDescription + 24);
|
|
pSampleDescription < (sampleDescription + sampleDescriptionLength);
|
|
) {
|
|
// General vars
|
|
UInt32 entryLength, dataType;
|
|
|
|
|
|
//
|
|
// Get the entry length and data type of this entry.
|
|
::memcpy( &entryLength, pSampleDescription + 0, 4);
|
|
entryLength = ntohl(entryLength);
|
|
|
|
::memcpy( &dataType, pSampleDescription + 4, 4);
|
|
dataType = ntohl(dataType);
|
|
|
|
//
|
|
// Process this data type.
|
|
switch( dataType ) {
|
|
case FOUR_CHARS_TO_INT('t', 'i', 'm', 's'): // tims RTP timescale
|
|
::memcpy(&fRTPTimescale, pSampleDescription + 8, 4);
|
|
fRTPTimescale = ntohl(fRTPTimescale);
|
|
break;
|
|
|
|
case FOUR_CHARS_TO_INT('t', 's', 'r', 'o'): // tsro Timestamp random offset
|
|
::memcpy(&fTimestampRandomOffset, pSampleDescription + 8, 4);
|
|
fTimestampRandomOffset = ntohl(fTimestampRandomOffset);
|
|
break;
|
|
|
|
case FOUR_CHARS_TO_INT('s', 'n', 'r', 'o'): // snro Sequence number random offset
|
|
::memcpy(&fSequenceNumberRandomOffset, pSampleDescription + 8, 2);
|
|
fSequenceNumberRandomOffset = ntohs(fSequenceNumberRandomOffset);
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Next entry..
|
|
pSampleDescription += entryLength;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Load in the hint info atom for this track.
|
|
if( fFile->FindTOCEntry(":udta:hinf", &hinfTOCEntry, &fTOCEntry) )
|
|
{
|
|
fHintInfoAtom = NEW QTAtom_hinf(fFile, hinfTOCEntry, fDebug, fDeepDebug);
|
|
|
|
if( fHintInfoAtom == NULL )
|
|
return errInternalError;
|
|
|
|
if( !fHintInfoAtom->Initialize() )
|
|
return errInvalidQuickTimeFile;
|
|
}
|
|
|
|
//
|
|
// Load in the hint track reference atom for this track.
|
|
if( !fFile->FindTOCEntry(":tref:hint", &hinfTOCEntry, &fTOCEntry) )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
fHintTrackReferenceAtom = NEW QTAtom_tref(fFile, hinfTOCEntry, fDebug, fDeepDebug);
|
|
if( fHintTrackReferenceAtom == NULL )
|
|
return errInternalError;
|
|
if( !fHintTrackReferenceAtom->Initialize() )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
|
|
//
|
|
// Allocate space for our track reference table.
|
|
UInt32 numTrackRefs = fHintTrackReferenceAtom->GetNumReferences();
|
|
if (numTrackRefs > QTHintTrack::kMaxHintTrackRefs)
|
|
return errInvalidQuickTimeFile;
|
|
|
|
fTrackRefs = NEW QTTrack *[numTrackRefs];
|
|
if( fTrackRefs == NULL )
|
|
return errInternalError;
|
|
|
|
::memset( (void *) fTrackRefs, 0, sizeof(QTTrack*) * numTrackRefs); //initialize ptr array with 0.
|
|
|
|
//
|
|
// Locate all of the tracks that we use, but don't initialize them until we
|
|
// actually try to access them.
|
|
for( UInt32 CurRef = 0; CurRef < numTrackRefs; CurRef++ )
|
|
{
|
|
// General vars
|
|
UInt32 trackID = 0;
|
|
|
|
//
|
|
// Get the reference and make sure it's not empty.
|
|
if( !fHintTrackReferenceAtom->TrackReferenceToTrackID(CurRef, &trackID) )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
//
|
|
// Store away a reference to this track.
|
|
if( !fFile->FindTrack( trackID, &fTrackRefs[CurRef]) )
|
|
if ( !fAllowInvalidHintRefs )
|
|
return errInvalidQuickTimeFile;
|
|
}
|
|
|
|
|
|
//
|
|
// Calculate the first RTP timestamp for this track.
|
|
if( GetFirstEditMovieTime() > 0 )
|
|
{
|
|
UInt64 trackTime = GetFirstEditMovieTime();
|
|
|
|
trackTime *= fRTPTimescale;
|
|
|
|
if( fFile->GetTimeScale() > 0.0 )
|
|
trackTime /= (UInt64)fFile->GetTimeScale();
|
|
|
|
fFirstRTPTimestamp = (UInt32)(trackTime & 0xffffffff);
|
|
|
|
}
|
|
else
|
|
{
|
|
fFirstRTPTimestamp = 0;
|
|
}
|
|
|
|
//
|
|
// This track has been successfully initialiazed.
|
|
fHintTrackInitialized = true;
|
|
|
|
return errNoError;
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------
|
|
// Accessors.
|
|
//
|
|
QTTrack::ErrorCode QTHintTrack::GetSDPFileLength(int * length)
|
|
{
|
|
QTFile::AtomTOCEntry* sdpTOCEntry;
|
|
|
|
|
|
//
|
|
// Locate the 'sdp ' atom for this track.
|
|
if( !fFile->FindTOCEntry(":udta:hnti:sdp ", &sdpTOCEntry, &fTOCEntry) )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
//
|
|
// Return the length.
|
|
*length = (int) sdpTOCEntry->AtomDataLength;
|
|
|
|
|
|
return errNoError;
|
|
}
|
|
|
|
char * QTHintTrack::GetSDPFile(int * length)
|
|
{
|
|
QTFile::AtomTOCEntry* sdpTOCEntry;
|
|
QTAtom* sdpAtom;
|
|
char* sdpBuffer;
|
|
|
|
|
|
//
|
|
// Locate and read in the 'sdp ' atom for this track.
|
|
if( !fFile->FindTOCEntry(":udta:hnti:sdp ", &sdpTOCEntry, &fTOCEntry) )
|
|
return NULL;
|
|
|
|
sdpAtom = NEW QTAtom(fFile, sdpTOCEntry, fDebug, fDeepDebug);
|
|
if( sdpAtom == NULL )
|
|
return NULL;
|
|
if( !sdpAtom->Initialize() )
|
|
return NULL;
|
|
|
|
//
|
|
// Allocate a buffer to hold the SDP file.
|
|
*length = (int) sdpTOCEntry->AtomDataLength;
|
|
//sdpBuffer = (char *)malloc(*Length);
|
|
sdpBuffer = NEW char[*length];
|
|
if( sdpBuffer != NULL )
|
|
sdpAtom->ReadBytes(0, sdpBuffer, *length);
|
|
|
|
//
|
|
// Free the atom and return the buffer.
|
|
delete sdpAtom;
|
|
return sdpBuffer;
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------
|
|
// Sample functions
|
|
//
|
|
|
|
|
|
void QTHintTrack::GetSamplePacketHeaderVars( char *samplePacketPtr,char *maxBuffPtr, QTHintTrackRTPHeaderData &hdrData )
|
|
{
|
|
// Read in this (packetNumber) packet's header.
|
|
// need to acquire the header info on every pass
|
|
|
|
MOVE_WORD( hdrData.hintFlags, samplePacketPtr + 8);
|
|
hdrData.hintFlags = ntohs( hdrData.hintFlags );
|
|
|
|
MOVE_WORD( hdrData.dataEntryCount, samplePacketPtr + 10);
|
|
hdrData.dataEntryCount = ntohs(hdrData.dataEntryCount);
|
|
|
|
hdrData.tlvTimestampOffset = 0;
|
|
|
|
Bool16 tlvOK = false; // reset tlvSize to 0 if the size value or the tlv flag is invalid
|
|
if( hdrData.hintFlags & 0x4 ) do // Extra Information TLV is present
|
|
{
|
|
hdrData.tlvSize = ntohl( *(UInt32*) (samplePacketPtr + 12) );
|
|
char* tlvParser = samplePacketPtr + 16; // start of tlv data
|
|
char* tlvEnd = tlvParser + hdrData.tlvSize;// 1 past the end of tlv data
|
|
|
|
if (tlvEnd >= maxBuffPtr || tlvParser >= maxBuffPtr || tlvEnd < samplePacketPtr || tlvParser < samplePacketPtr)
|
|
{
|
|
WarnV(tlvParser < maxBuffPtr, "incorrect tlv data: ignoring"); // error tlv-start past buffer
|
|
WarnV(tlvEnd < maxBuffPtr, "incorrect tlv data: ignoring"); // error tlv-end past buffer
|
|
WarnV(tlvEnd >= samplePacketPtr, "incorrect tlv data: ignoring"); // error tlv-end before start of sample in buffer
|
|
WarnV(tlvParser >= samplePacketPtr, "incorrect tlv data: ignoring"); // error tlv-start before start of sample in buffer
|
|
break;
|
|
}
|
|
|
|
tlvOK = true; // at this point hdrData.tlvSize is ok. The size is used to calculate the next packet so it better be correct.
|
|
|
|
// if there is a TLV, parse out the 1 field we currently know about, the 'rtpo' field
|
|
while (tlvParser + 12 < tlvEnd) // test for the minimum tlv size (size+type+smallest data size)
|
|
{
|
|
UInt32 fieldSize = ntohl( *(UInt32*) tlvParser );
|
|
UInt32 theType = ntohl( *(UInt32*) (tlvParser + 4) );
|
|
UInt32 theData = ntohl( *(UInt32*) (tlvParser + 8) ); //data can't be smaller 4 and we only know about rtpo data
|
|
|
|
if (theType == FOUR_CHARS_TO_INT( 'r', 't', 'p', 'o' )) //'rtpo'
|
|
{
|
|
// This is the RTP timestamp TLV. Record it
|
|
hdrData.tlvTimestampOffset = theData;
|
|
}
|
|
|
|
tlvParser += fieldSize;// add the field size to current for the next TLV entry
|
|
}
|
|
|
|
} while (false);
|
|
|
|
if (!tlvOK)
|
|
{ hdrData.tlvSize = 0;
|
|
}
|
|
|
|
MOVE_LONG_WORD( hdrData.relativePacketTransmissionTime, samplePacketPtr );
|
|
hdrData.relativePacketTransmissionTime = ntohl(hdrData.relativePacketTransmissionTime);
|
|
|
|
MOVE_WORD( hdrData.rtpHeaderBits, samplePacketPtr + 4);
|
|
MOVE_WORD( hdrData.rtpSequenceNumber, samplePacketPtr + 6);
|
|
hdrData.rtpSequenceNumber = ntohs(hdrData.rtpSequenceNumber);
|
|
|
|
}
|
|
|
|
|
|
QTTrack::ErrorCode QTHintTrack::GetSamplePacketPtr( char ** samplePacketPtr, UInt32 sampleNumber, UInt16 packetNumber
|
|
, QTHintTrackRTPHeaderData &hdrData, QTHintTrack_HintTrackControlBlock& htcb)
|
|
{
|
|
// get a pointer to the packetNumber # in sampleNumber #, from the QTHintTrack_HintTrackControlBlock htcb
|
|
// return a pointer to the data in samplePacketPtr ( past the header info we read into QTHintTrackRTPHeaderData )
|
|
// and fill in the QTHintTrackRTPHeaderData fields
|
|
|
|
// you must insure that the sampleNumber you want is already cached
|
|
// use GetSamplePtr to do this
|
|
|
|
// on exit fLastPacketNumberFetched = packetNumber
|
|
// fPointerToNextPacket points to 1 past the end of the current packet
|
|
|
|
|
|
Assert( sampleNumber == htcb.fCachedSampleNumber );
|
|
|
|
if( htcb.fLastPacketNumberFetched != 0xFFFF && packetNumber == htcb.fLastPacketNumberFetched + 1 )
|
|
{
|
|
// we're still looking at the same cached sample, and we just want the next packet in that sample
|
|
Assert( htcb.fPointerToNextPacket >= htcb.fCachedSample );
|
|
|
|
char* maxBuffPtr = (char*)(htcb.fCachedSample + htcb.fCachedSampleLength);
|
|
Assert( htcb.fPointerToNextPacket < maxBuffPtr );
|
|
|
|
this->GetSamplePacketHeaderVars( htcb.fPointerToNextPacket,maxBuffPtr, hdrData );
|
|
|
|
// adjust fPointerToNextPacket past current header data
|
|
// return this in samplePacketPtr, it points to the hint data table now.
|
|
htcb.fPointerToNextPacket += (4 + 2 + 2 + 2 + 2) + hdrData.tlvSize;
|
|
*samplePacketPtr = htcb.fPointerToNextPacket;
|
|
|
|
// now adjust fPointerToNextPacket to point at the header of the next packet
|
|
htcb.fPointerToNextPacket += hdrData.dataEntryCount * 16;
|
|
|
|
// validate the buffer
|
|
htcb.fLastPacketNumberFetched++;
|
|
Assert( htcb.fPointerToNextPacket <= (char*)( htcb.fCachedSample + htcb.fCachedSampleLength ) );
|
|
|
|
if( htcb.fPointerToNextPacket > ( htcb.fCachedSample + htcb.fCachedSampleLength ) )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
}
|
|
else
|
|
{
|
|
Assert( sampleNumber == htcb.fCachedSampleNumber );
|
|
|
|
char* pSampleBuffer = htcb.fCachedSample;
|
|
char* pSampleBufferEnd = (char*) ( pSampleBuffer + htcb.fCachedSampleLength );
|
|
pSampleBuffer += 4;
|
|
|
|
// Loop through the sample until we find the packet that we want.
|
|
for( UInt16 curPacket = 0; curPacket != packetNumber; curPacket++ )
|
|
{
|
|
|
|
this->GetSamplePacketHeaderVars( pSampleBuffer,pSampleBufferEnd, hdrData );
|
|
|
|
// Adjust (and check) pSampleBuffer)
|
|
pSampleBuffer += (4 + 2 + 2 + 2 + 2) + hdrData.tlvSize;
|
|
if( pSampleBuffer > pSampleBufferEnd )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
// Skip over the data table entries if this is *not* the packet that
|
|
// we want.
|
|
if( (curPacket + 1) != packetNumber )
|
|
{
|
|
pSampleBuffer += hdrData.dataEntryCount * 16;
|
|
if( pSampleBuffer > pSampleBufferEnd )
|
|
return errInvalidQuickTimeFile;
|
|
}
|
|
}
|
|
|
|
|
|
htcb.fPointerToNextPacket = pSampleBuffer + (hdrData.dataEntryCount * 16);
|
|
htcb.fLastPacketNumberFetched = packetNumber;
|
|
|
|
#if DEBUG
|
|
// validate that sample fits within the buffer provided..
|
|
Assert ( htcb.fPointerToNextPacket <= pSampleBufferEnd );
|
|
#endif
|
|
|
|
if ( htcb.fPointerToNextPacket > pSampleBufferEnd )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
*samplePacketPtr = pSampleBuffer;
|
|
}
|
|
|
|
return errNoError;
|
|
}
|
|
|
|
|
|
Bool16 QTHintTrack::GetSamplePtr(UInt32 sampleNumber, char ** samplePtr, UInt32 * length, QTHintTrack_HintTrackControlBlock * htcb)
|
|
{
|
|
// General vars
|
|
UInt32 newSampleLength;
|
|
Assert(htcb != NULL);
|
|
|
|
// See if this sample is in our cache, returning it out of the cache if it
|
|
// is, fetching and caching it if it is not.
|
|
if( sampleNumber == htcb->fCachedSampleNumber )
|
|
{
|
|
*samplePtr = htcb->fCachedSample;
|
|
*length = htcb->fCachedSampleLength;
|
|
return true;
|
|
}
|
|
|
|
//TEMP_PRINT( "QTHintTrack::GetSamplePtr; sample not cached\n" );
|
|
|
|
htcb->fLastPacketNumberFetched = 0xFFFF; // mark to invalid
|
|
htcb->fPointerToNextPacket = NULL;
|
|
|
|
//
|
|
// Get the length of the new sample.
|
|
UInt32 sampleDescriptionIndex;
|
|
UInt64 sampleOffset;
|
|
|
|
if( !this->GetSampleInfo(sampleNumber, &newSampleLength, &sampleOffset, &sampleDescriptionIndex, &htcb->fstscSTCB) )
|
|
return false;
|
|
|
|
//
|
|
// Create a new (bigger) cache samplePtr if the sample wouldn't fit in the
|
|
// old one.
|
|
if( (htcb->fCachedSample == NULL) || (htcb->fCachedSampleSize < newSampleLength) )
|
|
{
|
|
//
|
|
// Free the old cache entry if we had one.
|
|
if( htcb->fCachedSample != NULL )
|
|
{
|
|
htcb->fCachedSampleNumber = 0;
|
|
htcb->fCachedSampleSize = 0;
|
|
delete[] htcb->fCachedSample;
|
|
}
|
|
|
|
//
|
|
// Create a new cache entry.
|
|
htcb->fCachedSampleLength = htcb->fCachedSampleSize = newSampleLength;
|
|
htcb->fCachedSample = NEW char[htcb->fCachedSampleSize];
|
|
if( htcb->fCachedSample == NULL )
|
|
return false;
|
|
}
|
|
|
|
|
|
//
|
|
// Read in the new sample.
|
|
htcb->fCachedSampleLength = newSampleLength;
|
|
|
|
//- this did another GetSampleInfo and we already have that data...
|
|
//if( !this->GetSample(sampleNumber, htcb->fCachedSample, &htcb->fCachedSampleLength, htcb->fFCB, htcb->fstscSTCB) )
|
|
// return false;
|
|
|
|
//
|
|
// Read in the sample
|
|
if( !fDataReferenceAtom->Read( sampleDescriptionIndex, sampleOffset, htcb->fCachedSample, htcb->fCachedSampleLength, htcb->fFCB ) )
|
|
return false;
|
|
//
|
|
// Return the cached sample.
|
|
htcb->fCachedSampleNumber = sampleNumber;
|
|
*samplePtr = htcb->fCachedSample;
|
|
*length = htcb->fCachedSampleLength;
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------
|
|
// Packet functions
|
|
//
|
|
QTTrack::ErrorCode QTHintTrack::GetNumPackets(UInt32 sampleNumber, UInt16 * numPackets, QTHintTrack_HintTrackControlBlock * htcb)
|
|
{
|
|
char *buf;
|
|
UInt32 bufLen;
|
|
UInt16 entryCount;
|
|
|
|
|
|
//
|
|
// Read this sample and figure out how many packets are in it.
|
|
if( !this->GetSamplePtr(sampleNumber, &buf, &bufLen, htcb) )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
MOVE_WORD( entryCount, buf );
|
|
//::memcpy(&entryCount, buf, 2);
|
|
|
|
*numPackets = ntohs(entryCount);
|
|
|
|
return errNoError;
|
|
}
|
|
|
|
|
|
QTTrack::ErrorCode QTHintTrack::GetSampleData( QTHintTrack_HintTrackControlBlock * htcb, char **buffPtr, char **ppPacketBufOut, UInt32 sampleNumber, UInt16 packetNumber, UInt32 buffOutLen )
|
|
{
|
|
|
|
// qtss_printf("GetSampleData sampleNumber = %"_U32BITARG_" packetNumber = %"_U32BITARG_" buffOutLen = %"_U32BITARG_" \n",sampleNumber, packetNumber, buffOutLen);
|
|
// General vars
|
|
SInt8 trackRefIndex = 0;
|
|
UInt16 readLength = 0;
|
|
UInt32 mediaSampleNumber = 0;
|
|
UInt32 readOffset = 0;
|
|
UInt16 bytesPerCompressionBlock = 0;
|
|
UInt16 samplesPerCompressionBlock= 0; // inititialization eliminates a stupid compiler warning :(
|
|
UInt32 sampleDescriptionIndex;
|
|
UInt64 dataOffset;
|
|
char* pBuf = NULL;
|
|
char* maxBuffPtr = NULL;
|
|
char* buffOutPtr = NULL;
|
|
QTTrack *track = NULL;
|
|
UInt32 samplesPerChunk = 0;
|
|
UInt32 chunkNumber = 0;
|
|
UInt64 chunkOffset = 0;
|
|
UInt32 sampleOffsetInChunk = 0;
|
|
UInt32 sampleLength = 0;
|
|
UInt64 cacheHintSampleLen = 0;
|
|
SInt32 hintMaxRead = 0;
|
|
SInt64 sizeOfSamplesInChunk =0;
|
|
SInt64 endOfSampleInChunk = 0;
|
|
SInt64 sampleFirstPartLength = 0;
|
|
SInt64 remainingLength = 0;
|
|
Bool16 isOneForOne = false;
|
|
Bool16 isCompressed = false;
|
|
|
|
QTAtom_stsc_SampleTableControlBlock * mediaTrackSTSC_STCBPtr = NULL;
|
|
|
|
#if TESTTIME
|
|
Bool16 isMediaSample = false;
|
|
Bool16 isHintSample = false;
|
|
SInt64 startTime = GetMicroseconds();
|
|
SInt64 readStart = 0;
|
|
#endif
|
|
|
|
if (NULL == ppPacketBufOut || NULL == *ppPacketBufOut) return errInternalError;
|
|
if (NULL == buffPtr || NULL == *buffPtr) return errInternalError;
|
|
if (buffOutLen <= 0) return errInternalError;
|
|
pBuf = *buffPtr;
|
|
maxBuffPtr = *ppPacketBufOut + buffOutLen -1;
|
|
buffOutPtr = *ppPacketBufOut;
|
|
cacheHintSampleLen = buffOutLen;
|
|
//
|
|
// Get the information about this sample
|
|
trackRefIndex = (SInt8)*(pBuf + 1);
|
|
|
|
MOVE_WORD( readLength, pBuf + 2);
|
|
cacheHintSampleLen = hintMaxRead = readLength = ntohs(readLength);
|
|
|
|
MOVE_LONG_WORD( mediaSampleNumber, pBuf + 4);
|
|
mediaSampleNumber = ntohl(mediaSampleNumber);
|
|
|
|
MOVE_LONG_WORD( readOffset, pBuf + 8);
|
|
readOffset = ntohl(readOffset);
|
|
|
|
MOVE_WORD( bytesPerCompressionBlock, pBuf + 12);
|
|
bytesPerCompressionBlock = ntohs(bytesPerCompressionBlock);
|
|
if( bytesPerCompressionBlock == 0 )
|
|
bytesPerCompressionBlock = 1;
|
|
|
|
MOVE_WORD( samplesPerCompressionBlock, pBuf + 14);
|
|
samplesPerCompressionBlock = ntohs(samplesPerCompressionBlock);
|
|
if( samplesPerCompressionBlock == 0 )
|
|
samplesPerCompressionBlock = 1;
|
|
|
|
DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ....Sample entry found (sample#=%"_U32BITARG_"; offset=%"_U32BITARG_"; length=%u; BPCB=%u; SPCB=%u)\n", mediaSampleNumber, readOffset, readLength, bytesPerCompressionBlock, samplesPerCompressionBlock));
|
|
|
|
if( trackRefIndex == -1 )
|
|
{
|
|
if (kUnknown == fHintType)
|
|
fHintType = kOptimized;
|
|
// qtss_printf("hint track sample = %"_S32BITARG_" \n", mediaSampleNumber);
|
|
//
|
|
// We're getting data out of the hint track..
|
|
#if TESTTIME
|
|
isHintSample = true;
|
|
totalHintLength += readLength;
|
|
#endif
|
|
|
|
track = this;
|
|
|
|
//
|
|
// ..this might be the Sample Description that is stored in the first
|
|
// few samples of this track. See if we have it cached and use that
|
|
// if so. If we do not have this block of data cached, then continue
|
|
// on; we'll cache it later.
|
|
if ( (mediaSampleNumber == htcb->fCachedHintTrackSampleNumber)
|
|
&& (readOffset == htcb->fCachedHintTrackSampleOffset)
|
|
&& (readLength == htcb->fCachedHintTrackSampleLength)
|
|
)
|
|
{
|
|
// qtss_printf("found cached hint sample = %"_S32BITARG_" \n", mediaSampleNumber);
|
|
if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr )
|
|
{ return errInvalidQuickTimeFile;
|
|
}
|
|
|
|
::memcpy( *ppPacketBufOut, htcb->fCachedHintTrackSample, readLength);
|
|
*ppPacketBufOut += readLength;
|
|
|
|
#if TESTTIME
|
|
if (hintPacketCount < kMaxPacketCount)
|
|
{ totalHintSampleReadTime += (GetMicroseconds() - startTime);
|
|
hintPacketCount ++;
|
|
}
|
|
if (hintPacketCount >= kMaxPacketCount)
|
|
{
|
|
// qtss_printf("hintPacketCount = %"_S32BITARG_" hint get info time = %f hint bytes read = %d read time = %f\n", hintPacketCount, (float) (totalHintSampleReadTime - totalHintReadTime) / eMilli,totalHintLength, (float) totalHintReadTime/ eMilli);
|
|
hintPacketCount = 0;
|
|
totalHintSampleReadTime = 0;
|
|
totalHintLength = 0;
|
|
totalHintReadTime = 0;
|
|
}
|
|
#endif
|
|
|
|
return errNoError;
|
|
}
|
|
|
|
//
|
|
// ..or this might be in our currently-cached data sample.
|
|
|
|
if ( (mediaSampleNumber == htcb->fCachedSampleNumber)
|
|
&& ((readOffset + readLength) <= htcb->fCachedSampleLength)
|
|
)
|
|
{
|
|
// qtss_printf("found currently cached sample = %"_S32BITARG_" \n", mediaSampleNumber);
|
|
if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr)
|
|
{ return errInvalidQuickTimeFile;
|
|
}
|
|
|
|
::memcpy( *ppPacketBufOut, htcb->fCachedSample + readOffset, readLength);
|
|
*ppPacketBufOut += readLength;
|
|
|
|
#if TESTTIME
|
|
if (hintPacketCount < kMaxPacketCount)
|
|
{ totalHintSampleReadTime += (GetMicroseconds() - startTime);
|
|
hintPacketCount ++;
|
|
}
|
|
|
|
if (hintPacketCount >= kMaxPacketCount)
|
|
{
|
|
// qtss_printf("hintPacketCount = %"_S32BITARG_" hint get info time = %f hint bytes read = %d read time = %f\n", hintPacketCount, (float) (totalHintSampleReadTime - totalHintReadTime) / eMilli,totalHintLength, (float) totalHintReadTime/ eMilli);
|
|
hintPacketCount = 0;
|
|
totalHintSampleReadTime = 0;
|
|
totalHintLength = 0;
|
|
totalHintReadTime = 0;
|
|
}
|
|
#endif
|
|
|
|
return errNoError;
|
|
}
|
|
|
|
mediaTrackSTSC_STCBPtr = &htcb->fstscSTCB;
|
|
if( !track->GetSampleInfo(mediaSampleNumber,&sampleLength, &dataOffset, &sampleDescriptionIndex, mediaTrackSTSC_STCBPtr ) )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
dataOffset += readOffset;
|
|
|
|
if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr)
|
|
{ return errInvalidQuickTimeFile;
|
|
}
|
|
|
|
if( !track->Read(sampleDescriptionIndex, dataOffset, *ppPacketBufOut, readLength, htcb->fFCB) )
|
|
return (errInvalidQuickTimeFile);
|
|
*ppPacketBufOut += readLength; // point to remainder of buffer;
|
|
|
|
|
|
} // trackRefIndex == -1
|
|
else
|
|
{ // trackRefIndex != -1
|
|
|
|
// We're getting data out of a media track..
|
|
if (kUnknown == fHintType || kOptimized == fHintType) // if uninitialized or self-referencing then reset
|
|
fHintType = kUnoptimized;
|
|
|
|
// qtss_printf("media track sample = %"_S32BITARG_" \n", mediaSampleNumber);
|
|
|
|
#if TESTTIME
|
|
isMediaSample = true;
|
|
totalMediaLength += readLength;
|
|
#endif
|
|
|
|
track = fTrackRefs[trackRefIndex];
|
|
if( 0 == track )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
|
|
// Initialize this track if we haven't done so yet.
|
|
if( !track->IsInitialized() )
|
|
{ TEMP_PRINT_ONE( "QTHintTrack::GetPacket trackRefIndex %li, not initialized,\n", (SInt32)trackRefIndex );
|
|
OSMutexLocker theLocker(fFile->GetMutex());
|
|
|
|
TEMP_PRINT("Initializing media track\n");
|
|
if( track->Initialize() != QTTrack::errNoError )
|
|
return errInvalidQuickTimeFile;
|
|
}
|
|
|
|
if (htcb->fMediaTrackRefIndex == -2) // initial value : -1 is hint track and 0 is first index so use -2 as uninitialized
|
|
{
|
|
htcb->fMediaTrackSTSC_STCB = NEW QTAtom_stsc_SampleTableControlBlock();
|
|
htcb->fMediaTrackRefIndex = trackRefIndex;
|
|
}
|
|
|
|
if(htcb->fMediaTrackRefIndex == trackRefIndex)
|
|
{
|
|
mediaTrackSTSC_STCBPtr = htcb->fMediaTrackSTSC_STCB;
|
|
}
|
|
|
|
if( !track->GetSampleInfo(mediaSampleNumber,&sampleLength, &dataOffset, &sampleDescriptionIndex, mediaTrackSTSC_STCBPtr ) )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
if ( (1 == samplesPerCompressionBlock) && (1 == bytesPerCompressionBlock) )
|
|
isOneForOne = true;
|
|
|
|
|
|
if ( (isOneForOne && sampleLength == 1) // special case (isOneForOne && sampleLength == 1) // the data is compressed and the sample's byte offset is really a byte offset into a chunk
|
|
|| (!isOneForOne) // compressed data is normally defined by bytesPerCompressionBlock or samplesPerCompressionBlock
|
|
)
|
|
{
|
|
// qtss_printf("track = %"_S32BITARG_" sample = %"_S32BITARG_" is compressed \n",this, mediaSampleNumber);
|
|
// qtss_printf("is compressed bytesPerCompressionBlock = %"_S32BITARG_" samplesPerCompressionBlock = %d sampleLength = %"_S32BITARG_"\n", bytesPerCompressionBlock, samplesPerCompressionBlock, sampleLength);
|
|
isCompressed = true;
|
|
}
|
|
|
|
|
|
//
|
|
// Get the information about this sample and compute an offset. If the BPCB
|
|
// and SPCB are 1, then we use the standard sample routines to get the location
|
|
// of this sample, otherwise we have to compute it ourselves.
|
|
|
|
if (isCompressed)
|
|
{ // Media track sample compressed
|
|
|
|
UInt32 compressionBlocksToSkip = (UInt32) ( (Float64) readOffset / (Float64) bytesPerCompressionBlock);
|
|
mediaSampleNumber += compressionBlocksToSkip * samplesPerCompressionBlock;
|
|
readOffset -= compressionBlocksToSkip * bytesPerCompressionBlock; // readoffset should always be 0 after this
|
|
// start gathering chunk info to check sample length against chunk length
|
|
if( !track->SampleToChunkInfo(mediaSampleNumber, &samplesPerChunk, &chunkNumber, NULL, &sampleOffsetInChunk,mediaTrackSTSC_STCBPtr) )
|
|
return (errInvalidQuickTimeFile);
|
|
|
|
if( !track->ChunkOffset(chunkNumber, &chunkOffset) )
|
|
return (errInvalidQuickTimeFile);
|
|
|
|
dataOffset = (UInt64) chunkOffset + (UInt64) ((Float64) sampleOffsetInChunk * ((Float64)bytesPerCompressionBlock / (Float64)samplesPerCompressionBlock));
|
|
|
|
if( !track->GetSizeOfSamplesInChunk(chunkNumber, (UInt32 *) &sizeOfSamplesInChunk , NULL , NULL , mediaTrackSTSC_STCBPtr) )
|
|
return (errInvalidQuickTimeFile);
|
|
|
|
sizeOfSamplesInChunk = (UInt32) ( (Float64) sizeOfSamplesInChunk * ((Float64) bytesPerCompressionBlock / (Float64)samplesPerCompressionBlock) );
|
|
|
|
endOfSampleInChunk = sizeOfSamplesInChunk + chunkOffset;
|
|
sampleFirstPartLength = endOfSampleInChunk - dataOffset; // the first piece length = maxlen - start
|
|
remainingLength = readLength - sampleFirstPartLength; // the read - first piece is either <= 0 or the remaining amount.
|
|
|
|
if ( (remainingLength > 0) && (sampleFirstPartLength > 0) ) // this packet is split across chunks
|
|
{
|
|
// qtss_printf("mediaSampleNumber = %"_S32BITARG_" is compressed and split across chunks first part = %"_S32BITARG_" remaining = %"_S32BITARG_"\n",mediaSampleNumber, readLength, remainingLength);
|
|
readLength = (UInt16) sampleFirstPartLength;
|
|
}
|
|
else
|
|
{ // this is still needed. For some movies the compressed split packet calculation doesn't match the simple dataOffset calc below --a problem with sampleOffsetInChunk
|
|
// qtss_printf("compressed but not split across chunks \n");
|
|
// qtss_printf("chunkOffset = %"_S32BITARG_" ",chunkOffset);
|
|
// qtss_printf("old dataOffset = %qd ",dataOffset);
|
|
remainingLength = 0;
|
|
dataOffset = chunkOffset + readOffset + (UInt64)(sampleOffsetInChunk * ((Float64)bytesPerCompressionBlock / (Float64)samplesPerCompressionBlock));
|
|
// qtss_printf("new dataOffset = %qd \n", dataOffset);
|
|
}
|
|
|
|
hintMaxRead -= readLength;
|
|
if (hintMaxRead < 0)
|
|
{ return (errInvalidQuickTimeFile);
|
|
}
|
|
|
|
#if TESTTIME
|
|
// qtss_printf("Read mediaSampleNumber = %"_S32BITARG_" dataOffset = %qd readLength = %"_S32BITARG_" remaining = %"_S32BITARG_"\n",mediaSampleNumber, dataOffset, readLength, remainingLength);
|
|
readStart = GetMicroseconds();
|
|
|
|
if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr)
|
|
{ return errInvalidQuickTimeFile;
|
|
}
|
|
|
|
if( !track->Read(sampleDescriptionIndex, dataOffset, *ppPacketBufOut, readLength, htcb->fFCB) )
|
|
return (errInvalidQuickTimeFile);
|
|
totalMediaReadTime += GetMicroseconds() - readStart;
|
|
#else
|
|
// qtss_printf("Read mediaSampleNumber = %"_S32BITARG_" dataOffset = %qd readLength = %"_S32BITARG_" remaining = %"_S32BITARG_"\n",mediaSampleNumber, dataOffset, readLength, remainingLength);
|
|
|
|
if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr)
|
|
{ return errInvalidQuickTimeFile;
|
|
}
|
|
|
|
if( !track->Read(sampleDescriptionIndex, dataOffset, *ppPacketBufOut, readLength, htcb->fFCB) )
|
|
return (errInvalidQuickTimeFile);
|
|
|
|
#endif
|
|
|
|
|
|
*ppPacketBufOut += readLength; // point to remainder of buffer;
|
|
|
|
|
|
|
|
while (remainingLength > 0) // loop if packet is split across more than just two chunks
|
|
{
|
|
// qtss_printf("mediaSampleNumber = %"_S32BITARG_" remaining read = %"_S32BITARG_"\n",mediaSampleNumber, remainingLength);
|
|
|
|
readLength = (UInt16) remainingLength; // set the read to what is left
|
|
chunkNumber++; // The rest of the sample is in the next N chunks
|
|
if( !track->ChunkOffset(chunkNumber, &chunkOffset) ) // Get the Next chunk location
|
|
{
|
|
// It seems some movies have sample lengths that overstep the end of the last chunk.
|
|
// if that happens, truncate?
|
|
// Below is a commented out work around for bad offsets. It is commented out because if it happens we should report something is wrong with the file.
|
|
// remainingLength = 0;
|
|
// break;
|
|
return (errInvalidQuickTimeFile);
|
|
}
|
|
|
|
dataOffset = chunkOffset; // the location of the data starting at the beginning of the chunk
|
|
if( !track->GetSizeOfSamplesInChunk(chunkNumber, (UInt32 *) &sizeOfSamplesInChunk , NULL , NULL , mediaTrackSTSC_STCBPtr) )
|
|
return (errInvalidQuickTimeFile);
|
|
|
|
sizeOfSamplesInChunk = (UInt32) ( (Float64) sizeOfSamplesInChunk * ((Float64) bytesPerCompressionBlock / (Float64)samplesPerCompressionBlock) );
|
|
|
|
if (sizeOfSamplesInChunk < remainingLength) // read in the whole chunk and keep going
|
|
{
|
|
remainingLength -= sizeOfSamplesInChunk;
|
|
readLength = (UInt16) sizeOfSamplesInChunk;
|
|
}
|
|
else
|
|
{
|
|
remainingLength = 0; // done reading this packet
|
|
}
|
|
|
|
hintMaxRead -= readLength;
|
|
if (hintMaxRead < 0)
|
|
{ return (errInvalidQuickTimeFile);
|
|
}
|
|
|
|
#if TESTTIME
|
|
// qtss_printf("Read next parts mediaSampleNumber = %"_S32BITARG_" dataOffset = %qd readLength = %"_S32BITARG_" remaining = %"_S32BITARG_"\n",mediaSampleNumber, dataOffset, readLength, remainingLength);
|
|
readStart = GetMicroseconds();
|
|
|
|
if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr)
|
|
{ return errInvalidQuickTimeFile;
|
|
}
|
|
|
|
if( !track->Read(sampleDescriptionIndex, dataOffset, *ppPacketBufOut, readLength, htcb->fFCB) )
|
|
return errInvalidQuickTimeFile;
|
|
totalMediaReadTime += GetMicroseconds() - readStart;
|
|
#else
|
|
if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr)
|
|
{ return errInvalidQuickTimeFile;
|
|
}
|
|
|
|
if( !track->Read(sampleDescriptionIndex, dataOffset, *ppPacketBufOut, readLength, htcb->fFCB) )
|
|
{ return errInvalidQuickTimeFile;
|
|
}
|
|
#endif
|
|
|
|
*ppPacketBufOut += readLength; // point to remainder of buffer;
|
|
}
|
|
|
|
|
|
}
|
|
else
|
|
{ // Media track sample not compressed
|
|
dataOffset += readOffset;
|
|
|
|
if ( (char *) (*ppPacketBufOut + readLength -1) > maxBuffPtr)
|
|
{ return errInvalidQuickTimeFile;
|
|
}
|
|
|
|
|
|
if( !track->Read(sampleDescriptionIndex, dataOffset, *ppPacketBufOut, readLength, htcb->fFCB) )
|
|
return (errInvalidQuickTimeFile);
|
|
|
|
*ppPacketBufOut += readLength; // point to remainder of buffer;
|
|
}
|
|
|
|
|
|
*buffPtr = pBuf;
|
|
|
|
}
|
|
|
|
// If this chunk of data came out of our hint track; then we should cache
|
|
// it, just in case we want it later.
|
|
if( (trackRefIndex == -1) && (mediaSampleNumber < sampleNumber) )
|
|
{
|
|
|
|
if( htcb->fCachedHintTrackSample == NULL )
|
|
{
|
|
// qtss_printf("create a cache buffer for %"_S32BITARG_" of size %"_S32BITARG_"\n",mediaSampleNumber,cacheHintSampleLen);
|
|
htcb->fCachedHintTrackSample = NEW char[ (SInt32) cacheHintSampleLen];
|
|
htcb->fCachedHintTrackBufferLength = (SInt32) cacheHintSampleLen;
|
|
|
|
}
|
|
else if (htcb->fCachedHintTrackBufferLength < cacheHintSampleLen )
|
|
{
|
|
// qtss_printf("reallocate a cache buffer for %"_S32BITARG_" of size %"_S32BITARG_"\n",mediaSampleNumber,cacheHintSampleLen);
|
|
htcb->fCachedHintTrackSampleNumber = 0;
|
|
htcb->fCachedHintTrackSampleOffset = 0;
|
|
delete[] htcb->fCachedHintTrackSample;
|
|
|
|
htcb->fCachedHintTrackSample = NEW char[ (SInt32) cacheHintSampleLen];
|
|
htcb->fCachedHintTrackBufferLength = (SInt32) cacheHintSampleLen;
|
|
}
|
|
|
|
if (htcb->fCachedHintTrackSampleNumber != mediaSampleNumber)
|
|
{
|
|
|
|
if( htcb->fCachedHintTrackSample != NULL )
|
|
{
|
|
// qtss_printf("cache a hint sample sampleNumber %"_S32BITARG_" readLength = %"_S32BITARG_"\n",mediaSampleNumber,cacheHintSampleLen);
|
|
::memcpy(htcb->fCachedHintTrackSample, buffOutPtr, (UInt32) cacheHintSampleLen);
|
|
htcb->fCachedHintTrackSampleNumber = mediaSampleNumber;
|
|
htcb->fCachedHintTrackSampleOffset = readOffset;
|
|
htcb->fCachedHintTrackSampleLength = (UInt32) cacheHintSampleLen;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if TESTTIME
|
|
if (mediaPacketCount < kMaxPacketCount)
|
|
{ totalMediaSampleReadTime += (GetMicroseconds() - startTime);
|
|
mediaPacketCount ++;
|
|
}
|
|
if (mediaPacketCount >= kMaxPacketCount)
|
|
{
|
|
// qtss_printf("mediaPacketCount = %"_S32BITARG_" media get info time = %f media bytes read = %d read time = %f\n", mediaPacketCount, (float) (totalMediaSampleReadTime - totalMediaReadTime) / eMilli, totalMediaLength, (float) totalMediaReadTime/ eMilli);
|
|
totalMediaSampleReadTime = 0;
|
|
mediaPacketCount = 0;
|
|
totalMediaLength = 0;
|
|
totalMediaReadTime = 0;
|
|
}
|
|
|
|
if (isHintSample && (hintPacketCount < kMaxPacketCount))
|
|
{ totalHintSampleReadTime += (GetMicroseconds() - startTime);
|
|
hintPacketCount ++;
|
|
}
|
|
else if (isHintSample && (hintPacketCount >= kMaxPacketCount) )
|
|
{
|
|
// qtss_printf("hintPacketCount = %"_S32BITARG_" hint get info time = %f hint bytes read = %d read time = %f\n", hintPacketCount, (float) (totalHintSampleReadTime - totalHintReadTime) / eMilli,totalHintLength, (float) totalHintReadTime/ eMilli);
|
|
hintPacketCount = 0;
|
|
totalHintSampleReadTime = 0;
|
|
totalHintLength = 0;
|
|
totalHintReadTime = 0;
|
|
}
|
|
#endif
|
|
|
|
|
|
return errNoError;
|
|
|
|
|
|
}
|
|
|
|
|
|
QTTrack::ErrorCode QTHintTrack::GetPacket(UInt32 sampleNumber, UInt16 packetNumber, char * buffer, UInt32 * length
|
|
, Float64 * transmitTime, Bool16 dropBFrames, Bool16 dropRepeatPackets, UInt32 ssrc, QTHintTrack_HintTrackControlBlock * htcb)
|
|
{
|
|
// Temporary vars
|
|
UInt16 tempInt16;
|
|
UInt32 tempInt32;
|
|
|
|
UInt16 curEntry;
|
|
|
|
// General vars
|
|
UInt32 mediaTime;
|
|
|
|
char* buf;
|
|
UInt32 bufLen;
|
|
|
|
char* pSampleBuffer;
|
|
char *pDataTableStart;
|
|
|
|
UInt16 entryCount;
|
|
UInt32 rtpTimestamp;
|
|
|
|
QTHintTrackRTPHeaderData hdrData;
|
|
char* pPacketOutBuf;
|
|
UInt32 packetSize;
|
|
QTTrack::ErrorCode err = errNoError;
|
|
|
|
Float64 timeScale = 1.0;
|
|
|
|
Assert(htcb != NULL);
|
|
|
|
DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - Building packet #%u in sample %"_U32BITARG_".\n", packetNumber, sampleNumber));
|
|
|
|
//
|
|
// Get the RTP timestamp for this sample.
|
|
if( !this->GetSampleMediaTime(sampleNumber, &mediaTime, &htcb->fsttsSTCB) )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
if( fRTPTimescale != this->GetTimeScale() )
|
|
timeScale = (Float64) fRTPTimescale * (Float64) GetTimeScaleRecip();
|
|
|
|
rtpTimestamp = (UInt32) ((Float64) mediaTime * timeScale);
|
|
rtpTimestamp += fFirstRTPTimestamp;
|
|
|
|
//
|
|
// Add the first edit's media time.
|
|
// What about other edits?
|
|
mediaTime += this->GetFirstEditMediaTime();
|
|
|
|
//
|
|
// Read this sample and generate the n'th packet.
|
|
if( !this->GetSamplePtr(sampleNumber, &buf, &bufLen, htcb) )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
MOVE_WORD( entryCount, (char *)buf + 0);
|
|
entryCount = ntohs(entryCount);
|
|
if( (packetNumber-1) > entryCount )
|
|
return errInvalidQuickTimeFile;
|
|
|
|
err = this->GetSamplePacketPtr( &pSampleBuffer, sampleNumber, packetNumber, hdrData, *htcb);
|
|
|
|
if ( err != errNoError )
|
|
{ return err;
|
|
}
|
|
|
|
#if 0 // ignore relativePacketTransmissionTime offsets
|
|
*transmitTime = ( mediaTime * fMediaHeaderAtom->GetTimeScaleRecip() );
|
|
|
|
#else //keep relativePacketTransmissionTime offsets. time 0 base the streams independently. All streams start together regardless of their transmission time.
|
|
|
|
*transmitTime = ( mediaTime * fMediaHeaderAtom->GetTimeScaleRecip() )
|
|
+ ( hdrData.relativePacketTransmissionTime * fMediaHeaderAtom->GetTimeScaleRecip() );
|
|
|
|
#endif
|
|
|
|
// remove negative start times so each track's transmit time is now 0 based
|
|
if ( hdrData.relativePacketTransmissionTime != 0 && (fFirstTransmitTime == 0.0) )
|
|
{ fFirstTransmitTime = *transmitTime * -1;
|
|
}
|
|
*transmitTime += fFirstTransmitTime; // prevent any negative transmission times by making all the streams 0 based.
|
|
|
|
|
|
if ( hdrData.hintFlags )
|
|
{ //qtss_printf( "QTHintTrack::GetPacket hintFlags %lx\n", (SInt32)hdrData.hintFlags );
|
|
}
|
|
|
|
if ( hdrData.hintFlags & kRepeatPacketMask && (dropRepeatPackets))
|
|
{ //qtss_printf( "QTHintTrack::GetPacket repeat packet dropped.\n" );
|
|
return QTTrack::errIsSkippedPacket;
|
|
}
|
|
|
|
|
|
if (( hdrData.hintFlags & kBFrameBitMask) && (dropBFrames))
|
|
{ TEMP_PRINT( "QTHintTrack::GetPacket bframe packet dropped.\n" );
|
|
return QTTrack::errIsSkippedPacket;
|
|
}
|
|
|
|
|
|
|
|
DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ..rtpTimestamp=%"_U32BITARG_"; rtpSequenceNumber=%u; transmitTime=%.2f\n", rtpTimestamp, hdrData.rtpSequenceNumber, *transmitTime));
|
|
|
|
//
|
|
// We found this packet and the start of our data table.
|
|
pDataTableStart = pSampleBuffer;
|
|
|
|
//
|
|
// Our first pass through the data table is done to compute the size of the
|
|
// RTP packet that we will be generating and to validate the contents of
|
|
// the data table.
|
|
|
|
// and now only one pass!! the 2 loops are merged now
|
|
|
|
//
|
|
// Now we go through the data table again, but this time we build the
|
|
// packet.
|
|
|
|
pPacketOutBuf = buffer;
|
|
|
|
//
|
|
// Add in the RTP header.
|
|
tempInt16 = hdrData.rtpHeaderBits | ntohs(0x8000) /* v2 RTP header */;
|
|
COPY_WORD(pPacketOutBuf, &tempInt16);
|
|
|
|
//TEMP_PRINT_ONE( "QTHintTrack::GetPacket rtpHeaderBits %li.\n", (SInt32)rtpHeaderBits );
|
|
pPacketOutBuf += 2;
|
|
|
|
tempInt16 = htons(hdrData.rtpSequenceNumber);
|
|
COPY_WORD(pPacketOutBuf, &tempInt16);
|
|
pPacketOutBuf += 2;
|
|
|
|
tempInt32 = rtpTimestamp;
|
|
tempInt32 += hdrData.tlvTimestampOffset; // rtpo tlv offset
|
|
|
|
tempInt32 = htonl(tempInt32);
|
|
COPY_LONG_WORD(pPacketOutBuf, &tempInt32);
|
|
pPacketOutBuf += 4;
|
|
|
|
tempInt32 = htonl(ssrc);
|
|
COPY_LONG_WORD(pPacketOutBuf, &tempInt32);
|
|
pPacketOutBuf += 4;
|
|
|
|
//
|
|
// Go through each possible field. For each one, see if caller
|
|
// wants the field appended. If so, append the field
|
|
for ( UInt32 fieldCount = 0; fieldCount < RTPMetaInfoPacket::kNumFields; fieldCount++)
|
|
{
|
|
//
|
|
// If there is no field array, don't generate a packet
|
|
if (htcb->fRTPMetaInfoFieldArray == NULL)
|
|
break;
|
|
|
|
//
|
|
// Check if field should be appended
|
|
if (htcb->fRTPMetaInfoFieldArray[fieldCount] == RTPMetaInfoPacket::kFieldNotUsed)
|
|
continue;
|
|
|
|
switch (fieldCount)
|
|
{
|
|
case RTPMetaInfoPacket::kPacketPosField:
|
|
{
|
|
SInt64 curPacketPos = OS::HostToNetworkSInt64(htcb->fCurrentPacketPosition);
|
|
this->WriteMetaInfoField(RTPMetaInfoPacket::kPacketPosField, htcb->fRTPMetaInfoFieldArray[fieldCount], &curPacketPos, sizeof(curPacketPos), &pPacketOutBuf);
|
|
break;
|
|
}
|
|
case RTPMetaInfoPacket::kTransTimeField:
|
|
{
|
|
SInt64 transmitTimeInMsec = OS::HostToNetworkSInt64((SInt64)(*transmitTime * 1000));
|
|
this->WriteMetaInfoField(RTPMetaInfoPacket::kTransTimeField, htcb->fRTPMetaInfoFieldArray[fieldCount], &transmitTimeInMsec, sizeof(transmitTimeInMsec), &pPacketOutBuf);
|
|
break;
|
|
}
|
|
|
|
case RTPMetaInfoPacket::kFrameTypeField:
|
|
{
|
|
UInt16 theFrameType = RTPMetaInfoPacket::kUnknownFrameType;
|
|
|
|
if (!htcb->fIsVideo)
|
|
theFrameType = RTPMetaInfoPacket::kUnknownFrameType;
|
|
else if (hdrData.hintFlags & kBFrameBitMask)
|
|
theFrameType = RTPMetaInfoPacket::kBFrameType;
|
|
else if (this->IsSyncSample(sampleNumber, htcb->fSyncSampleCursor))
|
|
theFrameType = RTPMetaInfoPacket::kKeyFrameType;
|
|
else
|
|
theFrameType = RTPMetaInfoPacket::kPFrameType;
|
|
|
|
theFrameType = htons(theFrameType);
|
|
this->WriteMetaInfoField(RTPMetaInfoPacket::kFrameTypeField, htcb->fRTPMetaInfoFieldArray[fieldCount], &theFrameType, sizeof(theFrameType), &pPacketOutBuf);
|
|
break;
|
|
}
|
|
case RTPMetaInfoPacket::kPacketNumField:
|
|
{
|
|
SInt64 curPacketNum = OS::HostToNetworkSInt64(htcb->fCurrentPacketNumber);
|
|
this->WriteMetaInfoField(RTPMetaInfoPacket::kPacketNumField, htcb->fRTPMetaInfoFieldArray[fieldCount], &curPacketNum, sizeof(curPacketNum), &pPacketOutBuf);
|
|
break;
|
|
}
|
|
case RTPMetaInfoPacket::kSeqNumField:
|
|
{
|
|
tempInt16 = htons(hdrData.rtpSequenceNumber);
|
|
this->WriteMetaInfoField(RTPMetaInfoPacket::kSeqNumField, htcb->fRTPMetaInfoFieldArray[fieldCount], &tempInt16, sizeof(tempInt16), &pPacketOutBuf);
|
|
break;
|
|
}
|
|
case RTPMetaInfoPacket::kMediaDataField:
|
|
{
|
|
//
|
|
// This field cannot be compressed
|
|
Assert(htcb->fRTPMetaInfoFieldArray[fieldCount] == RTPMetaInfoPacket::kUncompressed);
|
|
|
|
//
|
|
// We don't have the data yet, so just write in the header
|
|
this->WriteMetaInfoField(RTPMetaInfoPacket::kMediaDataField, htcb->fRTPMetaInfoFieldArray[fieldCount], NULL, 0, &pPacketOutBuf);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
char* endOfMetaInfo = pPacketOutBuf;
|
|
packetSize = endOfMetaInfo - buffer;
|
|
|
|
DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ..Building packet.\n"));
|
|
TEMP_PRINT_TWO( "QTHintTrack::GetPacket Building packet %li ; hdrData.dataEntryCount %li .\n", (SInt32)packetNumber ,(SInt32)hdrData.dataEntryCount );
|
|
|
|
for( curEntry = 0; curEntry < hdrData.dataEntryCount; curEntry++ )
|
|
{
|
|
//
|
|
// Get the size out of this entry.
|
|
if ( *pSampleBuffer == 0x02 )
|
|
{
|
|
// Sample Mode
|
|
MOVE_WORD( tempInt16, pSampleBuffer + 2);
|
|
tempInt16 = ntohs(tempInt16);
|
|
|
|
DEEP_DEBUG_PRINT (( "QTHintTrack::GetPacket - ....Sample entry found (size=%u)\n", tempInt16 ) );
|
|
packetSize += tempInt16;
|
|
|
|
if( *length < packetSize )
|
|
return errParamError;
|
|
|
|
err = this->GetSampleData( htcb, &pSampleBuffer, &pPacketOutBuf, sampleNumber, packetNumber, *length);
|
|
if ( err != errNoError )
|
|
return err;
|
|
|
|
// GetSampleData increments our out pointer
|
|
}
|
|
else if ( *pSampleBuffer == 0x01 )
|
|
{
|
|
// Immediate Data Mode
|
|
DEEP_DEBUG_PRINT (( "QTHintTrack::GetPacket - ....Immediate entry found (size=%u)\n", (UInt16)*(pSampleBuffer + 1) ) );
|
|
packetSize += *(pSampleBuffer + 1);
|
|
|
|
if ( *length < packetSize )
|
|
return errParamError;
|
|
|
|
//
|
|
// Copy the data straight into the packet.
|
|
// ( it's data <= 16 bytes, padded out to a full 16 byte of header )
|
|
::memcpy(pPacketOutBuf, pSampleBuffer + 2, *(pSampleBuffer + 1));
|
|
|
|
// increment our out pointer
|
|
pPacketOutBuf += *(pSampleBuffer + 1);
|
|
|
|
}
|
|
else if ( *pSampleBuffer == 0x03 )
|
|
{
|
|
|
|
// Sample Description Data Mode
|
|
MOVE_WORD( tempInt16, pSampleBuffer + 2);
|
|
tempInt16 = ntohs(tempInt16);
|
|
|
|
DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ....Sample Description entry found (size=%u)\n", tempInt16));
|
|
packetSize += tempInt16;
|
|
|
|
if( *length < packetSize )
|
|
return errParamError;
|
|
// guess we don't handle these??
|
|
DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ....Sample Description entry found (size=%u)\n", tempInt16));
|
|
Assert(0);
|
|
}
|
|
else if ( *pSampleBuffer == 0x00 )
|
|
{
|
|
// No-Op Data Mode
|
|
DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ....No-Op entry found\n"));
|
|
}
|
|
else
|
|
{
|
|
// qtss_printf("Found unknown RTP data table type!\n");
|
|
Assert(0);
|
|
}
|
|
|
|
//
|
|
// Move to the next entry.
|
|
pSampleBuffer += 16;
|
|
}
|
|
|
|
DEEP_DEBUG_PRINT(("QTHintTrack::GetPacket - ..Packet length is %"_U32BITARG_" bytes.\n", packetSize));
|
|
|
|
*length = packetSize;
|
|
|
|
//
|
|
// Always track packet number and packet position.
|
|
UInt16 thePacketDataLen = pPacketOutBuf - endOfMetaInfo;
|
|
htcb->fCurrentPacketNumber++;
|
|
htcb->fCurrentPacketPosition += thePacketDataLen;
|
|
|
|
//
|
|
// If this is RTP-Meta-Info, well then update the fields we haven't updated yet!!!!!!!
|
|
if (htcb->fRTPMetaInfoFieldArray != NULL)
|
|
{
|
|
//
|
|
// If this is an RTP-Meta-Info packet, and there is no 'md' field, we shouldn't
|
|
// send the media data in the packet, so strip it off.
|
|
if (htcb->fRTPMetaInfoFieldArray[RTPMetaInfoPacket::kMediaDataField] == RTPMetaInfoPacket::kFieldNotUsed)
|
|
*length -= thePacketDataLen;
|
|
else
|
|
{
|
|
// If we do have md, make sure to put the right length in the right place
|
|
thePacketDataLen = htons(thePacketDataLen);
|
|
COPY_WORD(endOfMetaInfo - 2, &thePacketDataLen);
|
|
}
|
|
}
|
|
|
|
//
|
|
// The packet has been generated.
|
|
return err;
|
|
}
|
|
|
|
void QTHintTrack::WriteMetaInfoField( RTPMetaInfoPacket::FieldIndex inFieldIndex,
|
|
RTPMetaInfoPacket::FieldID inFieldID,
|
|
void* inFieldData, UInt32 inFieldLen, char** ioBuffer)
|
|
{
|
|
if (inFieldID == RTPMetaInfoPacket::kUncompressed)
|
|
{
|
|
//
|
|
// Write an uncompressed field
|
|
RTPMetaInfoPacket::FieldName theName = htons(RTPMetaInfoPacket::GetFieldNameForIndex(inFieldIndex));
|
|
COPY_WORD(*ioBuffer, &theName);
|
|
(*ioBuffer)+=2;
|
|
UInt16 theLen = htons((UInt16)inFieldLen);
|
|
COPY_WORD(*ioBuffer, &theLen);
|
|
(*ioBuffer)+=2;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Write a compressed field
|
|
UInt8 theID = (UInt8)inFieldID;
|
|
theID |= 0x80;
|
|
COPY_BYTE(*ioBuffer, &theID);
|
|
(*ioBuffer)+=1;
|
|
UInt8 theLenByte = (UInt8)inFieldLen;
|
|
COPY_BYTE(*ioBuffer, &theLenByte);
|
|
(*ioBuffer)+=1;
|
|
}
|
|
|
|
if (inFieldData != NULL)
|
|
{
|
|
::memcpy(*ioBuffer, inFieldData, inFieldLen);
|
|
(*ioBuffer)+=inFieldLen;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// -------------------------------------
|
|
// Debugging functions
|
|
//
|
|
void QTHintTrack::DumpTrack(void)
|
|
{
|
|
//
|
|
// Dump the QTTrack class.
|
|
QTTrack::DumpTrack();
|
|
|
|
//
|
|
// Dump the sub-atoms of this track.
|
|
if( fHintInfoAtom != NULL )
|
|
fHintInfoAtom->DumpAtom();
|
|
}
|