aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/libmp4v2/mp4track.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Src/external_dependencies/libmp4v2/mp4track.cpp')
-rw-r--r--Src/external_dependencies/libmp4v2/mp4track.cpp1929
1 files changed, 1929 insertions, 0 deletions
diff --git a/Src/external_dependencies/libmp4v2/mp4track.cpp b/Src/external_dependencies/libmp4v2/mp4track.cpp
new file mode 100644
index 00000000..764e0e37
--- /dev/null
+++ b/Src/external_dependencies/libmp4v2/mp4track.cpp
@@ -0,0 +1,1929 @@
+/*
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is MPEG4IP.
+ *
+ * The Initial Developer of the Original Code is Cisco Systems Inc.
+ * Portions created by Cisco Systems Inc. are
+ * Copyright (C) Cisco Systems Inc. 2001 - 2004. All Rights Reserved.
+ *
+ * 3GPP features implementation is based on 3GPP's TS26.234-v5.60,
+ * and was contributed by Ximpo Group Ltd.
+ *
+ * Portions created by Ximpo Group Ltd. are
+ * Copyright (C) Ximpo Group Ltd. 2003, 2004. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Dave Mackie dmackie@cisco.com
+ * Alix Marchandise-Franquet alix@cisco.com
+ * Ximpo Group Ltd. mp4v2@ximpo.com
+ */
+
+#include "mp4common.h"
+#include <limits.h>
+
+#define AMR_UNINITIALIZED -1
+#define AMR_TRUE 0
+#define AMR_FALSE 1
+
+static uint32_t SafeMultiply(uint32_t bytesPerSample, uint32_t numSamples)
+{
+ if (_UI32_MAX/bytesPerSample < numSamples)
+ return 0;
+ else
+ return numSamples * bytesPerSample;
+}
+
+static bool TrySafeMultiply(uint32_t bytesPerSample, uint32_t numSamples, uint32_t *value)
+{
+ if (_UI32_MAX/bytesPerSample < numSamples)
+ return false;
+ else
+ *value = numSamples * bytesPerSample;
+ return true;
+}
+
+static bool TrySafeAdd(uint32_t val1, uint32_t val2, uint32_t *value)
+{
+ if (_UI32_MAX - val1 < val2)
+ return false;
+ else
+ *value = val1 + val2;
+ return true;
+}
+
+MP4Track::MP4Track(MP4File* pFile, MP4Atom* pTrakAtom)
+{
+ m_pFile = pFile;
+ m_pTrakAtom = pTrakAtom;
+ m_pTypeProperty = NULL;
+ m_lastStsdIndex = 0;
+ m_lastSampleFile = NULL;
+
+ m_cachedReadSampleId = MP4_INVALID_SAMPLE_ID;
+ m_pCachedReadSample = NULL;
+ m_cachedReadSampleSize = 0;
+
+ m_writeSampleId = 1;
+ m_fixedSampleDuration = 0;
+ m_pChunkBuffer = NULL;
+ m_chunkBufferSize = 0;
+ m_chunkSamples = 0;
+ m_chunkDuration = 0;
+
+ // m_bytesPerSample should be set to 1, except for the
+ // quicktime audio constant bit rate samples, which have non-1 values
+ m_bytesPerSample = 1;
+ m_samplesPerChunk = 0;
+ m_durationPerChunk = 0;
+ m_isAmr = AMR_UNINITIALIZED;
+ m_curMode = 0;
+
+ m_pTimeScaleProperty = NULL;
+ m_pTrackDurationProperty = NULL;
+ m_pMediaDurationProperty = NULL;
+ m_pTrackModificationProperty = NULL;
+ m_pMediaModificationProperty = NULL;
+ m_pStszFixedSampleSizeProperty = NULL;
+ m_pStszSampleCountProperty = NULL;
+ m_pStszSampleSizeProperty = NULL;
+ m_pStscCountProperty = NULL;
+ m_pStscFirstChunkProperty = NULL;
+ m_pStscSamplesPerChunkProperty = NULL;
+ m_pStscSampleDescrIndexProperty = NULL;
+ m_pStscFirstSampleProperty = NULL;
+ m_pChunkCountProperty = NULL;
+ m_pChunkOffsetProperty = NULL;
+ m_pSttsCountProperty = NULL;
+ m_pSttsSampleCountProperty = NULL;
+ m_pSttsSampleDeltaProperty = NULL;
+ m_pCttsCountProperty = NULL;
+ m_pCttsSampleCountProperty = NULL;
+ m_pCttsSampleOffsetProperty = NULL;
+ m_pStssCountProperty = NULL;
+ m_pStssSampleProperty = NULL;
+ m_pElstCountProperty = NULL;
+ m_pElstMediaTimeProperty = NULL;
+ m_pElstDurationProperty = NULL;
+ m_pElstRateProperty = NULL;
+ m_pElstReservedProperty = NULL;
+
+ m_cachedSttsIndex = 0;
+ m_cachedSttsElapsed = 0;
+ m_cachedSttsSid = MP4_INVALID_SAMPLE_ID;
+
+ bool success = true;
+
+ MP4Integer32Property* pTrackIdProperty;
+ success &= m_pTrakAtom->FindProperty(
+ "trak.tkhd.trackId",
+ (MP4Property**)&pTrackIdProperty);
+ if (success) {
+ m_trackId = pTrackIdProperty->GetValue();
+ }
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.mdhd.timeScale",
+ (MP4Property**)&m_pTimeScaleProperty);
+ if (success) {
+ // default chunking is 1 second of samples
+ m_durationPerChunk = m_pTimeScaleProperty->GetValue();
+ }
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.tkhd.duration",
+ (MP4Property**)&m_pTrackDurationProperty);
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.mdhd.duration",
+ (MP4Property**)&m_pMediaDurationProperty);
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.tkhd.modificationTime",
+ (MP4Property**)&m_pTrackModificationProperty);
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.mdhd.modificationTime",
+ (MP4Property**)&m_pMediaModificationProperty);
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.hdlr.handlerType",
+ (MP4Property**)&m_pTypeProperty);
+
+ // get handles on sample size information
+
+
+ m_pStszFixedSampleSizeProperty = NULL;
+ bool have_stsz =
+ m_pTrakAtom->FindProperty("trak.mdia.minf.stbl.stsz.sampleSize",
+ (MP4Property**)&m_pStszFixedSampleSizeProperty);
+
+ if (have_stsz) {
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stsz.sampleCount",
+ (MP4Property**)&m_pStszSampleCountProperty);
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stsz.entries.entrySize",
+ (MP4Property**)&m_pStszSampleSizeProperty);
+ m_stsz_sample_bits = 32;
+ } else {
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stz2.sampleCount",
+ (MP4Property**)&m_pStszSampleCountProperty);
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stz2.entries.entrySize",
+ (MP4Property**)&m_pStszSampleSizeProperty);
+ MP4Integer8Property *stz2_field_size;
+ if (m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stz2.fieldSize",
+ (MP4Property **)&stz2_field_size)) {
+ m_stsz_sample_bits = stz2_field_size->GetValue();
+ m_have_stz2_4bit_sample = false;
+ } else success = false;
+ }
+
+
+ // get handles on information needed to map sample id's to file offsets
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stsc.entryCount",
+ (MP4Property**)&m_pStscCountProperty);
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stsc.entries.firstChunk",
+ (MP4Property**)&m_pStscFirstChunkProperty);
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stsc.entries.samplesPerChunk",
+ (MP4Property**)&m_pStscSamplesPerChunkProperty);
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stsc.entries.sampleDescriptionIndex",
+ (MP4Property**)&m_pStscSampleDescrIndexProperty);
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stsc.entries.firstSample",
+ (MP4Property**)&m_pStscFirstSampleProperty);
+
+ bool haveStco = m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stco.entryCount",
+ (MP4Property**)&m_pChunkCountProperty);
+
+ if (haveStco) {
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stco.entries.chunkOffset",
+ (MP4Property**)&m_pChunkOffsetProperty);
+ } else {
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.co64.entryCount",
+ (MP4Property**)&m_pChunkCountProperty);
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.co64.entries.chunkOffset",
+ (MP4Property**)&m_pChunkOffsetProperty);
+ }
+
+ // get handles on sample timing info
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stts.entryCount",
+ (MP4Property**)&m_pSttsCountProperty);
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stts.entries.sampleCount",
+ (MP4Property**)&m_pSttsSampleCountProperty);
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stts.entries.sampleDelta",
+ (MP4Property**)&m_pSttsSampleDeltaProperty);
+
+ // get handles on rendering offset info if it exists
+
+ m_pCttsCountProperty = NULL;
+ m_pCttsSampleCountProperty = NULL;
+ m_pCttsSampleOffsetProperty = NULL;
+
+ bool haveCtts = m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.ctts.entryCount",
+ (MP4Property**)&m_pCttsCountProperty);
+
+ if (haveCtts) {
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.ctts.entries.sampleCount",
+ (MP4Property**)&m_pCttsSampleCountProperty);
+
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.ctts.entries.sampleOffset",
+ (MP4Property**)&m_pCttsSampleOffsetProperty);
+ }
+
+ // get handles on sync sample info if it exists
+
+ m_pStssCountProperty = NULL;
+ m_pStssSampleProperty = NULL;
+
+ bool haveStss = m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stss.entryCount",
+ (MP4Property**)&m_pStssCountProperty);
+
+ if (haveStss) {
+ success &= m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stss.entries.sampleNumber",
+ (MP4Property**)&m_pStssSampleProperty);
+ }
+
+ // edit list
+ (void)InitEditListProperties();
+
+ // was everything found?
+ if (!success) {
+ throw new MP4Error("invalid track", "MP4Track::MP4Track");
+ }
+ CalculateBytesPerSample();
+}
+
+MP4Track::~MP4Track()
+{
+ MP4Free(m_pCachedReadSample);
+ MP4Free(m_pChunkBuffer);
+}
+
+const char* MP4Track::GetType()
+{
+ return m_pTypeProperty->GetValue();
+}
+
+void MP4Track::SetType(const char* type)
+{
+ m_pTypeProperty->SetValue(MP4NormalizeTrackType(type,
+ m_pFile->GetVerbosity()));
+}
+
+void MP4Track::ReadSample(
+ MP4SampleId sampleId,
+ u_int8_t** ppBytes,
+ u_int32_t* pNumBytes,
+ MP4Timestamp* pStartTime,
+ MP4Duration* pDuration,
+ MP4Duration* pRenderingOffset,
+ bool* pIsSyncSample)
+{
+ if (sampleId == MP4_INVALID_SAMPLE_ID) {
+ throw new MP4Error("sample id can't be zero",
+ "MP4Track::ReadSample");
+ }
+
+ // handle unusual case of wanting to read a sample
+ // that is still sitting in the write chunk buffer
+ if (m_pChunkBuffer && sampleId >= m_writeSampleId - m_chunkSamples) {
+ WriteChunkBuffer();
+ }
+
+ FILE *pFile = 0;
+ try {
+ pFile = GetSampleFile(sampleId);
+
+ }
+ catch (MP4Error* e)
+ {
+// PRINT_ERROR(e);
+ delete e;
+ pFile = 0;
+ }
+
+ if (pFile == (FILE*)-1) {
+ throw new MP4Error("sample is located in an inaccessible file",
+ "MP4Track::ReadSample");
+ }
+
+ u_int64_t fileOffset = GetSampleFileOffset(sampleId);
+
+ u_int32_t sampleSize = GetSampleSize(sampleId);
+ if (*ppBytes != NULL && *pNumBytes < sampleSize) {
+ throw new MP4Error("sample buffer is too small",
+ "MP4Track::ReadSample");
+ }
+ *pNumBytes = sampleSize;
+
+ VERBOSE_READ_SAMPLE(m_pFile->GetVerbosity(),
+ printf("ReadSample: track %u id %u offset 0x"X64" size %u (0x%x)\n",
+ m_trackId, sampleId, fileOffset, *pNumBytes, *pNumBytes));
+
+ bool bufferMalloc = false;
+ if (*ppBytes == NULL) {
+ *ppBytes = (u_int8_t*)MP4Malloc(*pNumBytes);
+ bufferMalloc = true;
+ }
+
+ u_int64_t oldPos = m_pFile->GetPosition(pFile); // only used in mode == 'w'
+ try {
+ m_pFile->SetPosition(fileOffset, pFile);
+ m_pFile->ReadBytes(*ppBytes, *pNumBytes, pFile);
+
+ if (pStartTime || pDuration) {
+ GetSampleTimes(sampleId, pStartTime, pDuration);
+
+ VERBOSE_READ_SAMPLE(m_pFile->GetVerbosity(),
+ printf("ReadSample: start "U64" duration "D64"\n",
+ (pStartTime ? *pStartTime : 0),
+ (pDuration ? *pDuration : 0)));
+ }
+ if (pRenderingOffset) {
+ *pRenderingOffset = GetSampleRenderingOffset(sampleId);
+
+ VERBOSE_READ_SAMPLE(m_pFile->GetVerbosity(),
+ printf("ReadSample: renderingOffset "D64"\n",
+ *pRenderingOffset));
+ }
+ if (pIsSyncSample) {
+ *pIsSyncSample = IsSyncSample(sampleId);
+
+ VERBOSE_READ_SAMPLE(m_pFile->GetVerbosity(),
+ printf("ReadSample: isSyncSample %u\n",
+ *pIsSyncSample));
+ }
+ }
+
+ catch (MP4Error* e) {
+ if (bufferMalloc) {
+ // let's not leak memory
+ MP4Free(*ppBytes);
+ *ppBytes = NULL;
+ }
+ if (m_pFile->GetMode() == 'w') {
+ m_pFile->SetPosition(oldPos, pFile);
+ }
+ throw e;
+ }
+
+ if (m_pFile->GetMode() == 'w') {
+ m_pFile->SetPosition(oldPos, pFile);
+ }
+}
+
+void MP4Track::ReadSampleFragment(
+ MP4SampleId sampleId,
+ u_int32_t sampleOffset,
+ u_int16_t sampleLength,
+ u_int8_t* pDest)
+{
+ if (sampleId == MP4_INVALID_SAMPLE_ID) {
+ throw new MP4Error("invalid sample id",
+ "MP4Track::ReadSampleFragment");
+ }
+
+ if (sampleId != m_cachedReadSampleId) {
+ MP4Free(m_pCachedReadSample);
+ m_pCachedReadSample = NULL;
+ m_cachedReadSampleSize = 0;
+ m_cachedReadSampleId = MP4_INVALID_SAMPLE_ID;
+
+ ReadSample(
+ sampleId,
+ &m_pCachedReadSample,
+ &m_cachedReadSampleSize);
+
+ m_cachedReadSampleId = sampleId;
+ }
+
+ if (sampleOffset + sampleLength > m_cachedReadSampleSize) {
+ throw new MP4Error("offset and/or length are too large",
+ "MP4Track::ReadSampleFragment");
+ }
+
+ memcpy(pDest, &m_pCachedReadSample[sampleOffset], sampleLength);
+}
+
+void MP4Track::WriteSample(
+ const u_int8_t* pBytes,
+ u_int32_t numBytes,
+ MP4Duration duration,
+ MP4Duration renderingOffset,
+ bool isSyncSample)
+{
+ u_int8_t curMode = 0;
+
+ VERBOSE_WRITE_SAMPLE(m_pFile->GetVerbosity(),
+ printf("WriteSample: track %u id %u size %u (0x%x) ",
+ m_trackId, m_writeSampleId, numBytes, numBytes));
+
+ if (pBytes == NULL && numBytes > 0) {
+ throw new MP4Error("no sample data", "MP4WriteSample");
+ }
+
+ if (m_isAmr == AMR_UNINITIALIZED ) {
+ // figure out if this is an AMR audio track
+ if (m_pTrakAtom->FindAtomMP4("trak.mdia.minf.stbl.stsd.samr") ||
+ m_pTrakAtom->FindAtomMP4("trak.mdia.minf.stbl.stsd.sawb")) {
+ m_isAmr = AMR_TRUE;
+ m_curMode = (pBytes[0] >> 3) & 0x000F;
+ } else {
+ m_isAmr = AMR_FALSE;
+ }
+ }
+
+ if (m_isAmr == AMR_TRUE) {
+ curMode = (pBytes[0] >> 3) &0x000F; // The mode is in the first byte
+ }
+
+ if (duration == MP4_INVALID_DURATION) {
+ duration = GetFixedSampleDuration();
+ }
+
+ VERBOSE_WRITE_SAMPLE(m_pFile->GetVerbosity(),
+ printf("duration "U64"\n", duration));
+
+ if ((m_isAmr == AMR_TRUE) &&
+ (m_curMode != curMode)) {
+ WriteChunkBuffer();
+ m_curMode = curMode;
+ }
+
+ // append sample bytes to chunk buffer
+ m_pChunkBuffer = (u_int8_t*)MP4Realloc(m_pChunkBuffer,
+ m_chunkBufferSize + numBytes);
+ if (m_pChunkBuffer == NULL) return;
+ memcpy(&m_pChunkBuffer[m_chunkBufferSize], pBytes, numBytes);
+ m_chunkBufferSize += numBytes;
+ m_chunkSamples++;
+ m_chunkDuration += duration;
+
+ UpdateSampleSizes(m_writeSampleId, numBytes);
+
+ UpdateSampleTimes(duration);
+
+ UpdateRenderingOffsets(m_writeSampleId, renderingOffset);
+
+ UpdateSyncSamples(m_writeSampleId, isSyncSample);
+
+ if (IsChunkFull(m_writeSampleId)) {
+ WriteChunkBuffer();
+ m_curMode = curMode;
+ }
+
+ UpdateDurations(duration);
+
+ UpdateModificationTimes();
+
+ m_writeSampleId++;
+}
+
+void MP4Track::WriteChunkBuffer()
+{
+ if (m_chunkBufferSize == 0) {
+ return;
+ }
+
+ u_int64_t chunkOffset = m_pFile->GetPosition();
+
+ // write chunk buffer
+ m_pFile->WriteBytes(m_pChunkBuffer, m_chunkBufferSize);
+
+ VERBOSE_WRITE_SAMPLE(m_pFile->GetVerbosity(),
+ printf("WriteChunk: track %u offset 0x"X64" size %u (0x%x) numSamples %u\n",
+ m_trackId, chunkOffset, m_chunkBufferSize,
+ m_chunkBufferSize, m_chunkSamples));
+
+ UpdateSampleToChunk(m_writeSampleId,
+ m_pChunkCountProperty->GetValue() + 1,
+ m_chunkSamples);
+
+ UpdateChunkOffsets(chunkOffset);
+
+ // clean up chunk buffer
+ MP4Free(m_pChunkBuffer);
+ m_pChunkBuffer = NULL;
+ m_chunkBufferSize = 0;
+ m_chunkSamples = 0;
+ m_chunkDuration = 0;
+}
+
+void MP4Track::FinishWrite()
+{
+ // write out any remaining samples in chunk buffer
+ WriteChunkBuffer();
+ if (m_pStszFixedSampleSizeProperty == NULL &&
+ m_stsz_sample_bits == 4) {
+ if (m_have_stz2_4bit_sample) {
+ ((MP4Integer8Property *)m_pStszSampleSizeProperty)->AddValue(m_stz2_4bit_sample_value);
+ m_pStszSampleSizeProperty->IncrementValue();
+ }
+ }
+
+ // record buffer size and bitrates
+ MP4BitfieldProperty* pBufferSizeProperty;
+
+ if (m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stsd.*.esds.decConfigDescr.bufferSizeDB",
+ (MP4Property**)&pBufferSizeProperty)) {
+ pBufferSizeProperty->SetValue(GetMaxSampleSize());
+ }
+
+ MP4Integer32Property* pBitrateProperty;
+
+ if (m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stsd.*.esds.decConfigDescr.maxBitrate",
+ (MP4Property**)&pBitrateProperty)) {
+ pBitrateProperty->SetValue(GetMaxBitrate());
+ }
+
+ if (m_pTrakAtom->FindProperty(
+ "trak.mdia.minf.stbl.stsd.*.esds.decConfigDescr.avgBitrate",
+ (MP4Property**)&pBitrateProperty)) {
+ pBitrateProperty->SetValue(GetAvgBitrate());
+ }
+}
+
+bool MP4Track::IsChunkFull(MP4SampleId sampleId)
+{
+ if (m_samplesPerChunk) {
+ return m_chunkSamples >= m_samplesPerChunk;
+ }
+
+ ASSERT(m_durationPerChunk);
+ return m_chunkDuration >= m_durationPerChunk;
+}
+
+u_int32_t MP4Track::GetNumberOfSamples()
+{
+ return m_pStszSampleCountProperty->GetValue();
+}
+
+u_int32_t MP4Track::GetSampleSize(MP4SampleId sampleId)
+{
+ if (m_pStszFixedSampleSizeProperty != NULL)
+ {
+ u_int32_t fixedSampleSize =
+ m_pStszFixedSampleSizeProperty->GetValue();
+
+ if (fixedSampleSize != 0)
+ {
+ return SafeMultiply(m_bytesPerSample, fixedSampleSize);
+ }
+ }
+ // will have to check for 4 bit sample size here
+ if (m_stsz_sample_bits == 4) {
+ uint8_t value = m_pStszSampleSizeProperty->GetValue((sampleId - 1) / 2);
+ if ((sampleId - 1) / 2 == 0) {
+ value >>= 4;
+ } else value &= 0xf;
+ return SafeMultiply(m_bytesPerSample, value);
+ }
+ return SafeMultiply(m_bytesPerSample, m_pStszSampleSizeProperty->GetValue(sampleId - 1));
+}
+
+u_int32_t MP4Track::GetMaxSampleSize()
+{
+ if (m_pStszFixedSampleSizeProperty != NULL)
+ {
+ u_int32_t fixedSampleSize =
+ m_pStszFixedSampleSizeProperty->GetValue();
+
+ if (fixedSampleSize != 0)
+ {
+ return SafeMultiply(m_bytesPerSample, fixedSampleSize);
+ }
+ }
+
+ u_int32_t maxSampleSize = 0;
+ u_int32_t numSamples = m_pStszSampleSizeProperty->GetCount();
+ for (MP4SampleId sid = 1; sid <= numSamples; sid++) {
+ u_int32_t sampleSize =
+ m_pStszSampleSizeProperty->GetValue(sid - 1);
+ if (sampleSize > maxSampleSize) {
+ maxSampleSize = sampleSize;
+ }
+ }
+
+ return SafeMultiply(m_bytesPerSample, maxSampleSize);
+}
+
+u_int64_t MP4Track::GetTotalOfSampleSizes()
+{
+ uint64_t retval;
+ if (m_pStszFixedSampleSizeProperty != NULL) {
+ u_int32_t fixedSampleSize =
+ m_pStszFixedSampleSizeProperty->GetValue();
+
+ // if fixed sample size, just need to multiply by number of samples
+ if (fixedSampleSize != 0) {
+ retval = m_bytesPerSample;
+ retval *= fixedSampleSize;
+ retval *= GetNumberOfSamples();
+ return retval;
+ }
+ }
+
+ // else non-fixed sample size, sum them
+ u_int64_t totalSampleSizes = 0;
+ u_int32_t numSamples = m_pStszSampleSizeProperty->GetCount();
+ for (MP4SampleId sid = 1; sid <= numSamples; sid++) {
+ u_int32_t sampleSize =
+ m_pStszSampleSizeProperty->GetValue(sid - 1);
+ totalSampleSizes += sampleSize;
+ }
+ return totalSampleSizes * m_bytesPerSample;
+}
+
+void MP4Track::SampleSizePropertyAddValue (uint32_t size)
+{
+ // this has to deal with different sample size values
+ switch (m_pStszSampleSizeProperty->GetType()) {
+ case Integer32Property:
+ ((MP4Integer32Property *)m_pStszSampleSizeProperty)->AddValue(size);
+ break;
+ case Integer16Property:
+ ((MP4Integer16Property *)m_pStszSampleSizeProperty)->AddValue(size);
+ break;
+ case Integer8Property:
+ if (m_stsz_sample_bits == 4) {
+ if (m_have_stz2_4bit_sample == false) {
+ m_have_stz2_4bit_sample = true;
+ m_stz2_4bit_sample_value = size << 4;
+ return;
+ } else {
+ m_have_stz2_4bit_sample = false;
+ size &= 0xf;
+ size |= m_stz2_4bit_sample_value;
+ }
+ }
+ ((MP4Integer8Property *)m_pStszSampleSizeProperty)->AddValue(size);
+ break;
+ default: break;
+ }
+
+
+ // m_pStszSampleSizeProperty->IncrementValue();
+}
+
+void MP4Track::UpdateSampleSizes(MP4SampleId sampleId, u_int32_t numBytes)
+{
+ if (m_bytesPerSample > 1) {
+ if ((numBytes % m_bytesPerSample) != 0) {
+ // error
+ VERBOSE_ERROR(m_pFile->GetVerbosity(),
+ printf("UpdateSampleSize: numBytes %u not divisible by bytesPerSample %u sampleId %u\n",
+ numBytes, m_bytesPerSample, sampleId);
+ );
+ }
+ numBytes /= m_bytesPerSample;
+ }
+ // for first sample
+ if (sampleId == 1) {
+ if (m_pStszFixedSampleSizeProperty == NULL ||
+ numBytes == 0) {
+ // special case of first sample is zero bytes in length
+ // leave m_pStszFixedSampleSizeProperty at 0
+ // start recording variable sample sizes
+ if (m_pStszFixedSampleSizeProperty != NULL)
+ m_pStszFixedSampleSizeProperty->SetValue(0);
+ SampleSizePropertyAddValue(0);
+ } else {
+ // presume sample size is fixed
+ m_pStszFixedSampleSizeProperty->SetValue(numBytes);
+ }
+ } else { // sampleId > 1
+
+ u_int32_t fixedSampleSize = 0;
+ if (m_pStszFixedSampleSizeProperty != NULL) {
+ fixedSampleSize = m_pStszFixedSampleSizeProperty->GetValue();
+ }
+
+ // if we don't have a fixed size, or the current sample size
+ // doesn't match our sample size, we need to write the current
+ // sample size into the table
+ if (fixedSampleSize == 0 || numBytes != fixedSampleSize) {
+
+ if (fixedSampleSize != 0) {
+ // fixed size was set; we need to clear fixed sample size
+ if (m_pStszFixedSampleSizeProperty != NULL) {
+ m_pStszFixedSampleSizeProperty->SetValue(0);
+ }
+
+ // and create sizes for all previous samples
+ for (MP4SampleId sid = 1; sid < sampleId; sid++) {
+ SampleSizePropertyAddValue(fixedSampleSize);
+ }
+ }
+ // add size value for this sample
+ SampleSizePropertyAddValue(numBytes);
+ }
+ }
+ // either way, we increment the number of samples.
+ m_pStszSampleCountProperty->IncrementValue();
+#if 0
+ printf("track %u sample id %u bytes %u fixed %u count %u prop %u\n",
+ m_trackId, sampleId, numBytes,
+ m_pStszFixedSampleSizeProperty->GetValue(),
+ m_pStszSampleSizeProperty->GetCount(),
+ m_pStszSampleCountProperty->GetValue());
+#endif
+}
+
+u_int32_t MP4Track::GetAvgBitrate()
+{
+ if (GetDuration() == 0) {
+ return 0;
+ }
+
+ double calc = UINT64_TO_DOUBLE(GetTotalOfSampleSizes());
+ // this is a bit better - we use the whole duration
+ calc *= 8.0;
+ calc *= GetTimeScale();
+ calc /= UINT64_TO_DOUBLE(GetDuration());
+ // we might want to think about rounding to the next 100 or 1000
+ return (uint32_t) ceil(calc);
+}
+
+u_int32_t MP4Track::GetMaxBitrate()
+{
+ u_int32_t timeScale = GetTimeScale();
+ MP4SampleId numSamples = GetNumberOfSamples();
+ u_int32_t maxBytesPerSec = 0;
+ u_int32_t bytesThisSec = 0;
+ MP4Timestamp thisSecStart = 0;
+ MP4Timestamp lastSampleTime = 0;
+ uint32_t lastSampleSize = 0;
+
+ MP4SampleId thisSecStartSid = 1;
+ for (MP4SampleId sid = 1; sid <= numSamples; sid++) {
+ uint32_t sampleSize;
+ MP4Timestamp sampleTime;
+
+ sampleSize = GetSampleSize(sid);
+ GetSampleTimes(sid, &sampleTime, NULL);
+
+ if (sampleTime < thisSecStart + timeScale) {
+ bytesThisSec += sampleSize;
+ lastSampleSize = sampleSize;
+ lastSampleTime = sampleTime;
+ } else {
+ // we've already written the last sample and sampleSize.
+ // this means that we've probably overflowed the last second
+ // calculate the time we've overflowed
+ MP4Duration overflow_dur =
+ (thisSecStart + timeScale) - lastSampleTime;
+ // calculate the duration of the last sample
+ MP4Duration lastSampleDur = sampleTime - lastSampleTime;
+ uint32_t overflow_bytes;
+ // now, calculate the number of bytes we overflowed. Round up.
+ overflow_bytes =
+ ((lastSampleSize * overflow_dur) + (lastSampleDur - 1)) / lastSampleDur;
+
+ if (bytesThisSec - overflow_bytes > maxBytesPerSec) {
+ maxBytesPerSec = bytesThisSec - overflow_bytes;
+ }
+
+ // now adjust the values for this sample. Remove the bytes
+ // from the first sample in this time frame
+ lastSampleTime = sampleTime;
+ lastSampleSize = sampleSize;
+ bytesThisSec += sampleSize;
+ bytesThisSec -= GetSampleSize(thisSecStartSid);
+ thisSecStartSid++;
+ GetSampleTimes(thisSecStartSid, &thisSecStart, NULL);
+ }
+ }
+
+ return maxBytesPerSec * 8;
+}
+
+u_int32_t MP4Track::GetSampleStscIndex(MP4SampleId sampleId)
+{
+ u_int32_t stscIndex;
+ u_int32_t numStscs = m_pStscCountProperty->GetValue();
+
+ if (numStscs == 0) {
+ throw new MP4Error("No data chunks exist", "GetSampleStscIndex");
+ }
+
+ for (stscIndex = 0; stscIndex < numStscs; stscIndex++) {
+ if (sampleId < m_pStscFirstSampleProperty->GetValue(stscIndex)) {
+ ASSERT(stscIndex != 0);
+ stscIndex -= 1;
+ break;
+ }
+ }
+ if (stscIndex == numStscs) {
+ ASSERT(stscIndex != 0);
+ stscIndex -= 1;
+ }
+
+ return stscIndex;
+}
+
+FILE* MP4Track::GetSampleFile(MP4SampleId sampleId)
+{
+ u_int32_t stscIndex =
+ GetSampleStscIndex(sampleId);
+
+ u_int32_t stsdIndex =
+ m_pStscSampleDescrIndexProperty->GetValue(stscIndex);
+
+ // check if the answer will be the same as last time
+ if (m_lastStsdIndex && stsdIndex == m_lastStsdIndex) {
+ return m_lastSampleFile;
+ }
+
+ MP4Atom* pStsdAtom =
+ m_pTrakAtom->FindAtomMP4("trak.mdia.minf.stbl.stsd");
+ ASSERT(pStsdAtom);
+
+ MP4Atom* pStsdEntryAtom =
+ pStsdAtom->GetChildAtom(stsdIndex - 1);
+ ASSERT(pStsdEntryAtom);
+
+ MP4Integer16Property* pDrefIndexProperty = NULL;
+ if (!pStsdEntryAtom->FindProperty(
+ "*.dataReferenceIndex",
+ (MP4Property**)&pDrefIndexProperty) ||
+
+ pDrefIndexProperty == NULL) {
+return 0;
+ }
+
+ u_int32_t drefIndex =
+ pDrefIndexProperty->GetValue();
+
+ MP4Atom* pDrefAtom =
+ m_pTrakAtom->FindAtomMP4("trak.mdia.minf.dinf.dref");
+ ASSERT(pDrefAtom);
+
+ MP4Atom* pUrlAtom =
+ pDrefAtom->GetChildAtom(drefIndex - 1);
+ ASSERT(pUrlAtom);
+
+ FILE* pFile;
+
+ if (pUrlAtom->GetFlags() & 1) {
+ pFile = NULL; // self-contained
+ } else {
+ MP4StringProperty* pLocationProperty = NULL;
+ ASSERT(pUrlAtom->FindProperty(
+ "*.location",
+ (MP4Property**)&pLocationProperty));
+ ASSERT(pLocationProperty);
+
+ const char* url = pLocationProperty->GetValue();
+
+ VERBOSE_READ_SAMPLE(m_pFile->GetVerbosity(),
+ printf("dref url = %s\n", url));
+
+ pFile = (FILE*)-1;
+
+ // attempt to open url if it's a file url
+ // currently this is the only thing we understand
+ if (!strncmp(url, "file:", 5)) {
+ const char* fileName = url + 5;
+ if (!strncmp(fileName, "//", 2)) {
+ fileName = strchr(fileName + 2, '/');
+ }
+ if (fileName) {
+ pFile = fopen(fileName, "rb");
+ if (!pFile) {
+ pFile = (FILE*)-1;
+ }
+ }
+ }
+ }
+
+ if (m_lastSampleFile) {
+ fclose(m_lastSampleFile);
+ }
+
+ // cache the answer
+ m_lastStsdIndex = stsdIndex;
+ m_lastSampleFile = pFile;
+
+ return pFile;
+}
+
+u_int64_t MP4Track::GetSampleFileOffset(MP4SampleId sampleId)
+{
+ u_int32_t stscIndex = GetSampleStscIndex(sampleId);
+
+ // firstChunk is the chunk index of the first chunk with
+ // samplesPerChunk samples in the chunk. There may be multiples -
+ // ie: several chunks with the same number of samples per chunk.
+ u_int64_t firstChunk = m_pStscFirstChunkProperty->GetValue(stscIndex);
+
+ MP4SampleId firstSample = m_pStscFirstSampleProperty->GetValue(stscIndex);
+
+ u_int64_t samplesPerChunk = m_pStscSamplesPerChunkProperty->GetValue(stscIndex);
+
+ // chunkId tells which is the absolute chunk number that this sample
+ // is stored in.
+ MP4ChunkId chunkId = firstChunk + ((static_cast<unsigned long long>(sampleId) - firstSample) / samplesPerChunk);
+
+ // chunkOffset is the file offset (absolute) for the start of the chunk
+ u_int64_t chunkOffset = m_pChunkOffsetProperty->GetValue(chunkId - 1);
+
+ MP4SampleId firstSampleInChunk = sampleId - ((static_cast<unsigned long long>(sampleId) - firstSample) % samplesPerChunk);
+
+ // need cumulative samples sizes from firstSample to sampleId - 1
+ u_int64_t sampleOffset = 0;
+ for (MP4SampleId i = firstSampleInChunk; i < sampleId; i++) {
+ sampleOffset += GetSampleSize(i);
+ }
+
+ return chunkOffset + sampleOffset;
+}
+
+void MP4Track::UpdateSampleToChunk(MP4SampleId sampleId,
+ MP4ChunkId chunkId, u_int32_t samplesPerChunk)
+{
+ u_int32_t numStsc = m_pStscCountProperty->GetValue();
+
+ // if samplesPerChunk == samplesPerChunk of last entry
+ if (numStsc && samplesPerChunk ==
+ m_pStscSamplesPerChunkProperty->GetValue(numStsc-1)) {
+
+ // nothing to do
+
+ } else {
+ // add stsc entry
+ m_pStscFirstChunkProperty->AddValue(chunkId);
+ m_pStscSamplesPerChunkProperty->AddValue(samplesPerChunk);
+ m_pStscSampleDescrIndexProperty->AddValue(1);
+ m_pStscFirstSampleProperty->AddValue(sampleId - samplesPerChunk + 1);
+
+ m_pStscCountProperty->IncrementValue();
+ }
+}
+
+void MP4Track::UpdateChunkOffsets(u_int64_t chunkOffset)
+{
+ if (m_pChunkOffsetProperty->GetType() == Integer32Property) {
+ ((MP4Integer32Property*)m_pChunkOffsetProperty)->AddValue(chunkOffset);
+ } else {
+ ((MP4Integer64Property*)m_pChunkOffsetProperty)->AddValue(chunkOffset);
+ }
+ m_pChunkCountProperty->IncrementValue();
+}
+
+MP4Duration MP4Track::GetFixedSampleDuration()
+{
+ u_int32_t numStts = m_pSttsCountProperty->GetValue();
+
+ if (numStts == 0) {
+ return m_fixedSampleDuration;
+ }
+ if (numStts != 1) {
+ return MP4_INVALID_DURATION; // sample duration is not fixed
+ }
+ return m_pSttsSampleDeltaProperty->GetValue(0);
+}
+
+void MP4Track::SetFixedSampleDuration(MP4Duration duration)
+{
+ u_int32_t numStts = m_pSttsCountProperty->GetValue();
+
+ // setting this is only allowed before samples have been written
+ if (numStts != 0) {
+ return;
+ }
+ m_fixedSampleDuration = duration;
+ return;
+}
+
+void MP4Track::GetSampleTimes(MP4SampleId sampleId,
+ MP4Timestamp* pStartTime, MP4Duration* pDuration)
+{
+ u_int32_t numStts = m_pSttsCountProperty->GetValue();
+ MP4SampleId sid;
+ MP4Duration elapsed;
+
+
+ if (m_cachedSttsSid != MP4_INVALID_SAMPLE_ID && sampleId >= m_cachedSttsSid) {
+ sid = m_cachedSttsSid;
+ elapsed = m_cachedSttsElapsed;
+ } else {
+ m_cachedSttsIndex = 0;
+ sid = 1;
+ elapsed = 0;
+ }
+
+ for (u_int32_t sttsIndex = m_cachedSttsIndex; sttsIndex < numStts; sttsIndex++) {
+ MP4SampleId sampleCount =
+ m_pSttsSampleCountProperty->GetValue(sttsIndex);
+ MP4Duration sampleDelta =
+ m_pSttsSampleDeltaProperty->GetValue(sttsIndex);
+
+ if (sampleId <= sid + sampleCount - 1) {
+ if (pStartTime) {
+ *pStartTime = (static_cast<MP4Timestamp>(sampleId) - sid);
+ *pStartTime *= sampleDelta;
+ *pStartTime += elapsed;
+ }
+ if (pDuration) {
+ *pDuration = sampleDelta;
+ }
+
+ m_cachedSttsIndex = sttsIndex;
+ m_cachedSttsSid = sid;
+ m_cachedSttsElapsed = elapsed;
+
+ return;
+ }
+ sid += sampleCount;
+ elapsed += sampleCount * sampleDelta;
+ }
+
+ throw new MP4Error("sample id out of range",
+ "MP4Track::GetSampleTimes");
+}
+
+MP4SampleId MP4Track::GetSampleIdFromTime(
+ MP4Timestamp when,
+ bool wantSyncSample,
+ bool rewind)
+{
+ u_int32_t numStts = m_pSttsCountProperty->GetValue();
+ MP4SampleId sid = 1;
+ MP4Duration elapsed = 0;
+
+ for (u_int32_t sttsIndex = 0; sttsIndex < numStts; sttsIndex++) {
+ MP4SampleId sampleCount =
+ m_pSttsSampleCountProperty->GetValue(sttsIndex);
+ MP4Duration sampleDelta =
+ m_pSttsSampleDeltaProperty->GetValue(sttsIndex);
+
+ if (sampleDelta == 0 && sttsIndex < numStts - 1) {
+ VERBOSE_READ(m_pFile->GetVerbosity(),
+ printf("Warning: Zero sample duration, stts entry %u\n",
+ sttsIndex));
+ }
+
+ MP4Duration d = when - elapsed;
+
+ if (d <= sampleCount * sampleDelta) {
+ MP4SampleId sampleId = sid;
+ if (sampleDelta) {
+ sampleId += (d / sampleDelta);
+ }
+
+ if (wantSyncSample) {
+ return GetSyncSample(sampleId, rewind);
+ }
+ return sampleId;
+ }
+
+ sid += sampleCount;
+ elapsed += sampleCount * sampleDelta;
+ }
+
+ throw new MP4Error("time out of range",
+ "MP4Track::GetSampleIdFromTime");
+
+ return 0; // satisfy MS compiler
+}
+
+MP4ChunkId MP4Track::GetChunkIdFromTime(
+ MP4Timestamp when)
+{
+ MP4ChunkId numChunks = GetNumberOfChunks();
+ for (MP4ChunkId chunk = 1; chunk <= numChunks; chunk++)
+ {
+ MP4Timestamp d = GetChunkTime(chunk);
+ if (d == when)
+ return chunk;
+ else if (d > when)
+ return chunk==1?1:(chunk-1);
+ }
+ return numChunks;
+}
+
+
+void MP4Track::UpdateSampleTimes(MP4Duration duration)
+{
+ u_int32_t numStts = m_pSttsCountProperty->GetValue();
+
+ // if duration == duration of last entry
+ if (numStts
+ && duration == m_pSttsSampleDeltaProperty->GetValue(numStts-1)) {
+ // increment last entry sampleCount
+ m_pSttsSampleCountProperty->IncrementValue(1, numStts-1);
+
+ } else {
+ // add stts entry, sampleCount = 1, sampleDuration = duration
+ m_pSttsSampleCountProperty->AddValue(1);
+ m_pSttsSampleDeltaProperty->AddValue(duration);
+ m_pSttsCountProperty->IncrementValue();;
+ }
+}
+
+u_int32_t MP4Track::GetSampleCttsIndex(MP4SampleId sampleId,
+ MP4SampleId* pFirstSampleId)
+{
+ u_int32_t numCtts = m_pCttsCountProperty->GetValue();
+
+ MP4SampleId sid = 1;
+
+ for (u_int32_t cttsIndex = 0; cttsIndex < numCtts; cttsIndex++) {
+ u_int32_t sampleCount =
+ m_pCttsSampleCountProperty->GetValue(cttsIndex);
+
+ if (sampleId <= sid + sampleCount - 1) {
+ if (pFirstSampleId) {
+ *pFirstSampleId = sid;
+ }
+ return cttsIndex;
+ }
+ sid += sampleCount;
+ }
+
+ throw new MP4Error("sample id out of range",
+ "MP4Track::GetSampleCttsIndex");
+ return 0; // satisfy MS compiler
+}
+
+MP4Duration MP4Track::GetSampleRenderingOffset(MP4SampleId sampleId)
+{
+ if (m_pCttsCountProperty == NULL) {
+ return 0;
+ }
+ if (m_pCttsCountProperty->GetValue() == 0) {
+ return 0;
+ }
+
+ u_int32_t cttsIndex = GetSampleCttsIndex(sampleId);
+
+ return m_pCttsSampleOffsetProperty->GetValue(cttsIndex);
+}
+
+void MP4Track::UpdateRenderingOffsets(MP4SampleId sampleId,
+ MP4Duration renderingOffset)
+{
+ // if ctts atom doesn't exist
+ if (m_pCttsCountProperty == NULL) {
+
+ // no rendering offset, so nothing to do
+ if (renderingOffset == 0) {
+ return;
+ }
+
+ // else create a ctts atom
+ MP4Atom* pCttsAtom = AddAtom("trak.mdia.minf.stbl", "ctts");
+
+ // and get handles on the properties
+ ASSERT(pCttsAtom->FindProperty(
+ "ctts.entryCount",
+ (MP4Property**)&m_pCttsCountProperty));
+
+ ASSERT(pCttsAtom->FindProperty(
+ "ctts.entries.sampleCount",
+ (MP4Property**)&m_pCttsSampleCountProperty));
+
+ ASSERT(pCttsAtom->FindProperty(
+ "ctts.entries.sampleOffset",
+ (MP4Property**)&m_pCttsSampleOffsetProperty));
+
+ // if this is not the first sample
+ if (sampleId > 1) {
+ // add a ctts entry for all previous samples
+ // with rendering offset equal to zero
+ m_pCttsSampleCountProperty->AddValue(sampleId - 1);
+ m_pCttsSampleOffsetProperty->AddValue(0);
+ m_pCttsCountProperty->IncrementValue();;
+ }
+ }
+
+ // ctts atom exists (now)
+
+ u_int32_t numCtts = m_pCttsCountProperty->GetValue();
+
+ // if renderingOffset == renderingOffset of last entry
+ if (numCtts && renderingOffset
+ == m_pCttsSampleOffsetProperty->GetValue(numCtts-1)) {
+
+ // increment last entry sampleCount
+ m_pCttsSampleCountProperty->IncrementValue(1, numCtts-1);
+
+ } else {
+ // add ctts entry, sampleCount = 1, sampleOffset = renderingOffset
+ m_pCttsSampleCountProperty->AddValue(1);
+ m_pCttsSampleOffsetProperty->AddValue(renderingOffset);
+ m_pCttsCountProperty->IncrementValue();
+ }
+}
+
+void MP4Track::SetSampleRenderingOffset(MP4SampleId sampleId,
+ MP4Duration renderingOffset)
+{
+ // check if any ctts entries exist
+ if (m_pCttsCountProperty == NULL
+ || m_pCttsCountProperty->GetValue() == 0) {
+ // if not then Update routine can be used
+ // to create a ctts entry for samples before this one
+ // and a ctts entry for this sample
+ UpdateRenderingOffsets(sampleId, renderingOffset);
+
+ // but we also need a ctts entry
+ // for all samples after this one
+ u_int32_t afterSamples = GetNumberOfSamples() - sampleId;
+
+ if (afterSamples) {
+ m_pCttsSampleCountProperty->AddValue(afterSamples);
+ m_pCttsSampleOffsetProperty->AddValue(0);
+ m_pCttsCountProperty->IncrementValue();;
+ }
+
+ return;
+ }
+
+ MP4SampleId firstSampleId;
+ u_int32_t cttsIndex = GetSampleCttsIndex(sampleId, &firstSampleId);
+
+ // do nothing in the degenerate case
+ if (renderingOffset ==
+ m_pCttsSampleOffsetProperty->GetValue(cttsIndex)) {
+ return;
+ }
+
+ u_int32_t sampleCount =
+ m_pCttsSampleCountProperty->GetValue(cttsIndex);
+
+ // if this sample has it's own ctts entry
+ if (sampleCount == 1) {
+ // then just set the value,
+ // note we don't attempt to collapse entries
+ m_pCttsSampleOffsetProperty->SetValue(renderingOffset, cttsIndex);
+ return;
+ }
+
+ MP4SampleId lastSampleId = firstSampleId + sampleCount - 1;
+
+ // else we share this entry with other samples
+ // we need to insert our own entry
+ if (sampleId == firstSampleId) {
+ // our sample is the first one
+ m_pCttsSampleCountProperty->
+ InsertValue(1, cttsIndex);
+ m_pCttsSampleOffsetProperty->
+ InsertValue(renderingOffset, cttsIndex);
+
+ m_pCttsSampleCountProperty->
+ SetValue(sampleCount - 1, cttsIndex + 1);
+
+ m_pCttsCountProperty->IncrementValue();
+
+ } else if (sampleId == lastSampleId) {
+ // our sample is the last one
+ m_pCttsSampleCountProperty->
+ InsertValue(1, cttsIndex + 1);
+ m_pCttsSampleOffsetProperty->
+ InsertValue(renderingOffset, cttsIndex + 1);
+
+ m_pCttsSampleCountProperty->
+ SetValue(sampleCount - 1, cttsIndex);
+
+ m_pCttsCountProperty->IncrementValue();
+
+ } else {
+ // our sample is in the middle, UGH!
+
+ // insert our new entry
+ m_pCttsSampleCountProperty->
+ InsertValue(1, cttsIndex + 1);
+ m_pCttsSampleOffsetProperty->
+ InsertValue(renderingOffset, cttsIndex + 1);
+
+ // adjust count of previous entry
+ m_pCttsSampleCountProperty->
+ SetValue(sampleId - firstSampleId, cttsIndex);
+
+ // insert new entry for those samples beyond our sample
+ m_pCttsSampleCountProperty->
+ InsertValue(lastSampleId - sampleId, cttsIndex + 2);
+ u_int32_t oldRenderingOffset =
+ m_pCttsSampleOffsetProperty->GetValue(cttsIndex);
+ m_pCttsSampleOffsetProperty->
+ InsertValue(oldRenderingOffset, cttsIndex + 2);
+
+ m_pCttsCountProperty->IncrementValue(2);
+ }
+}
+
+bool MP4Track::IsSyncSample(MP4SampleId sampleId)
+{
+ if (m_pStssCountProperty == NULL) {
+ return true;
+ }
+
+ u_int32_t numStss = m_pStssCountProperty->GetValue();
+ u_int32_t stssLIndex = 0;
+ u_int32_t stssRIndex = numStss - 1;
+
+ while (stssRIndex >= stssLIndex){
+ u_int32_t stssIndex = (stssRIndex + stssLIndex) >> 1;
+ MP4SampleId syncSampleId =
+ m_pStssSampleProperty->GetValue(stssIndex);
+
+ if (sampleId == syncSampleId) {
+ return true;
+ }
+
+ if (sampleId > syncSampleId) {
+ stssLIndex = stssIndex + 1;
+ } else {
+ stssRIndex = stssIndex - 1;
+ }
+ }
+
+ return false;
+}
+
+// N.B. "next" is inclusive of this sample id
+MP4SampleId MP4Track::GetNextSyncSample(MP4SampleId sampleId)
+{
+ if (m_pStssCountProperty == NULL) {
+ return sampleId;
+ }
+
+ u_int32_t numStss = m_pStssCountProperty->GetValue();
+ for (uint32_t stssIndex = 0; stssIndex < numStss; stssIndex++)
+ {
+ MP4SampleId syncSampleId = m_pStssSampleProperty->GetValue(stssIndex);
+
+ if (sampleId > syncSampleId) {
+ continue;
+ }
+ return syncSampleId;
+ }
+
+ // LATER check stsh for alternate sample
+
+ return MP4_INVALID_SAMPLE_ID;
+}
+
+MP4SampleId MP4Track::GetSyncSample(MP4SampleId sampleId, bool rewind)
+{
+ if (m_pStssCountProperty == NULL) {
+ return sampleId;
+ }
+
+ u_int32_t numStss = m_pStssCountProperty->GetValue();
+ MP4SampleId prevSampleId = 1;
+ for (uint32_t stssIndex = 0; stssIndex < numStss; stssIndex++)
+ {
+ MP4SampleId syncSampleId = m_pStssSampleProperty->GetValue(stssIndex);
+
+ if (sampleId > syncSampleId) {
+ prevSampleId = syncSampleId;
+ continue;
+ }
+ return rewind ? prevSampleId : syncSampleId;
+ }
+
+ // LATER check stsh for alternate sample
+
+ return MP4_INVALID_SAMPLE_ID;
+}
+
+void MP4Track::UpdateSyncSamples(MP4SampleId sampleId, bool isSyncSample)
+{
+ if (isSyncSample) {
+ // if stss atom exists, add entry
+ if (m_pStssCountProperty) {
+ m_pStssSampleProperty->AddValue(sampleId);
+ m_pStssCountProperty->IncrementValue();
+ } // else nothing to do (yet)
+
+ } else { // !isSyncSample
+ // if stss atom doesn't exist, create one
+ if (m_pStssCountProperty == NULL) {
+
+ MP4Atom* pStssAtom = AddAtom("trak.mdia.minf.stbl", "stss");
+
+ ASSERT(pStssAtom->FindProperty(
+ "stss.entryCount",
+ (MP4Property**)&m_pStssCountProperty));
+
+ ASSERT(pStssAtom->FindProperty(
+ "stss.entries.sampleNumber",
+ (MP4Property**)&m_pStssSampleProperty));
+
+ // set values for all samples that came before this one
+ for (MP4SampleId sid = 1; sid < sampleId; sid++) {
+ m_pStssSampleProperty->AddValue(sid);
+ m_pStssCountProperty->IncrementValue();
+ }
+ } // else nothing to do
+ }
+}
+
+MP4Atom* MP4Track::AddAtom(char* parentName, char* childName)
+{
+ MP4Atom* pChildAtom = MP4Atom::CreateAtom(childName);
+
+ MP4Atom* pParentAtom = m_pTrakAtom->FindAtomMP4(parentName);
+ ASSERT(pParentAtom);
+
+ pParentAtom->AddChildAtom(pChildAtom);
+
+ pChildAtom->Generate();
+
+ return pChildAtom;
+}
+
+u_int64_t MP4Track::GetDuration()
+{
+ return m_pMediaDurationProperty->GetValue();
+}
+
+u_int32_t MP4Track::GetTimeScale()
+{
+ return m_pTimeScaleProperty->GetValue();
+}
+
+void MP4Track::UpdateDurations(MP4Duration duration)
+{
+ // update media, track, and movie durations
+ m_pMediaDurationProperty->SetValue(
+ m_pMediaDurationProperty->GetValue() + duration);
+
+ MP4Duration movieDuration = ToMovieDuration(duration);
+ m_pTrackDurationProperty->SetValue(
+ m_pTrackDurationProperty->GetValue() + movieDuration);
+
+ m_pFile->UpdateDuration(m_pTrackDurationProperty->GetValue());
+}
+
+MP4Duration MP4Track::ToMovieDuration(MP4Duration trackDuration)
+{
+ return (trackDuration * m_pFile->GetTimeScale())
+ / m_pTimeScaleProperty->GetValue();
+}
+
+void MP4Track::UpdateModificationTimes()
+{
+ // update media and track modification times
+ MP4Timestamp now = MP4GetAbsTimestamp();
+ m_pMediaModificationProperty->SetValue(now);
+ m_pTrackModificationProperty->SetValue(now);
+}
+
+u_int32_t MP4Track::GetNumberOfChunks()
+{
+ return m_pChunkOffsetProperty->GetCount();
+}
+
+u_int32_t MP4Track::GetChunkStscIndex(MP4ChunkId chunkId)
+{
+ u_int32_t stscIndex;
+ u_int32_t numStscs = m_pStscCountProperty->GetValue();
+
+ ASSERT(chunkId);
+ ASSERT(numStscs > 0);
+
+ for (stscIndex = 0; stscIndex < numStscs; stscIndex++) {
+ if (chunkId < m_pStscFirstChunkProperty->GetValue(stscIndex)) {
+ ASSERT(stscIndex != 0);
+ break;
+ }
+ }
+ return stscIndex - 1;
+}
+
+MP4Timestamp MP4Track::GetChunkTime(MP4ChunkId chunkId)
+{
+ u_int32_t stscIndex = GetChunkStscIndex(chunkId);
+
+ MP4ChunkId firstChunkId =
+ m_pStscFirstChunkProperty->GetValue(stscIndex);
+
+ MP4SampleId firstSample =
+ m_pStscFirstSampleProperty->GetValue(stscIndex);
+
+ u_int32_t samplesPerChunk =
+ m_pStscSamplesPerChunkProperty->GetValue(stscIndex);
+
+ MP4SampleId firstSampleInChunk =
+ firstSample + ((chunkId - firstChunkId) * samplesPerChunk);
+
+ MP4Timestamp chunkTime;
+
+ GetSampleTimes(firstSampleInChunk, &chunkTime, NULL);
+
+ return chunkTime;
+}
+
+u_int32_t MP4Track::GetChunkSize(MP4ChunkId chunkId)
+{
+ u_int32_t stscIndex = GetChunkStscIndex(chunkId);
+
+ MP4ChunkId firstChunkId =
+ m_pStscFirstChunkProperty->GetValue(stscIndex);
+
+ MP4SampleId firstSample =
+ m_pStscFirstSampleProperty->GetValue(stscIndex);
+
+ u_int32_t samplesPerChunk =
+ m_pStscSamplesPerChunkProperty->GetValue(stscIndex);
+
+ uint32_t chunkOffsetBytes;
+ if (!TrySafeMultiply(samplesPerChunk, chunkId - firstChunkId, &chunkOffsetBytes))
+ return 0;
+
+ MP4SampleId firstSampleInChunk;
+ if (!TrySafeAdd(firstSample, chunkOffsetBytes, &firstSampleInChunk))
+ return 0;
+
+ // need cumulative sizes of samples in chunk
+ u_int32_t chunkSize = 0;
+ for (u_int32_t i = 0; i < samplesPerChunk; i++)
+ {
+ if (!TrySafeAdd(chunkSize, GetSampleSize(firstSampleInChunk + i), &chunkSize))
+ return 0;
+ }
+
+ return chunkSize;
+}
+
+void MP4Track::ReadChunk(MP4ChunkId chunkId,
+ u_int8_t** ppChunk, u_int32_t* pChunkSize,
+ MP4Timestamp* pStartTime, MP4Duration* pDuration)
+{
+ ASSERT(chunkId);
+ ASSERT(ppChunk);
+ ASSERT(pChunkSize);
+
+ bool do_free=false;
+ u_int64_t chunkOffset =
+ m_pChunkOffsetProperty->GetValue(chunkId - 1);
+
+ *pChunkSize = GetChunkSize(chunkId);
+ if (!*ppChunk)
+ {
+ do_free=true;
+ *ppChunk = (u_int8_t*)MP4Malloc(*pChunkSize);
+ }
+
+ VERBOSE_READ_SAMPLE(m_pFile->GetVerbosity(),
+ printf("ReadChunk: track %u id %u offset 0x"X64" size %u (0x%x)\n",
+ m_trackId, chunkId, chunkOffset, *pChunkSize, *pChunkSize));
+
+ u_int64_t oldPos = m_pFile->GetPosition(); // only used in mode == 'w'
+ try {
+ m_pFile->SetPosition(chunkOffset);
+ m_pFile->ReadBytes(*ppChunk, *pChunkSize);
+ if (pStartTime)
+ *pStartTime = GetChunkTime(chunkId);
+ if (pDuration)
+ *pDuration = m_durationPerChunk;
+ }
+ catch (MP4Error* e) {
+ // let's not leak memory
+ if (do_free)
+ MP4Free(*ppChunk);
+ *ppChunk = NULL;
+
+ if (m_pFile->GetMode() == 'w') {
+ m_pFile->SetPosition(oldPos);
+ }
+ throw e;
+ }
+
+ if (m_pFile->GetMode() == 'w') {
+ m_pFile->SetPosition(oldPos);
+ }
+}
+
+void MP4Track::RewriteChunk(MP4ChunkId chunkId,
+ u_int8_t* pChunk, u_int32_t chunkSize)
+{
+ u_int64_t chunkOffset = m_pFile->GetPosition();
+
+ m_pFile->WriteBytes(pChunk, chunkSize);
+
+ m_pChunkOffsetProperty->SetValue(chunkOffset, chunkId - 1);
+
+ VERBOSE_WRITE_SAMPLE(m_pFile->GetVerbosity(),
+ printf("RewriteChunk: track %u id %u offset 0x"X64" size %u (0x%x)\n",
+ m_trackId, chunkId, chunkOffset, chunkSize, chunkSize));
+}
+
+// map track type name aliases to official names
+
+
+bool MP4Track::InitEditListProperties()
+{
+ m_pElstCountProperty = NULL;
+ m_pElstMediaTimeProperty = NULL;
+ m_pElstDurationProperty = NULL;
+ m_pElstRateProperty = NULL;
+ m_pElstReservedProperty = NULL;
+
+ MP4Atom* pElstAtom =
+ m_pTrakAtom->FindAtomMP4("trak.edts.elst");
+
+ if (!pElstAtom) {
+ return false;
+ }
+
+ (void)pElstAtom->FindProperty(
+ "elst.entryCount",
+ (MP4Property**)&m_pElstCountProperty);
+ (void)pElstAtom->FindProperty(
+ "elst.entries.mediaTime",
+ (MP4Property**)&m_pElstMediaTimeProperty);
+ (void)pElstAtom->FindProperty(
+ "elst.entries.segmentDuration",
+ (MP4Property**)&m_pElstDurationProperty);
+ (void)pElstAtom->FindProperty(
+ "elst.entries.mediaRate",
+ (MP4Property**)&m_pElstRateProperty);
+
+ (void)pElstAtom->FindProperty(
+ "elst.entries.reserved",
+ (MP4Property**)&m_pElstReservedProperty);
+
+ return m_pElstCountProperty
+ && m_pElstMediaTimeProperty
+ && m_pElstDurationProperty
+ && m_pElstRateProperty
+ && m_pElstReservedProperty;
+}
+
+MP4EditId MP4Track::AddEdit(MP4EditId editId)
+{
+ if (!m_pElstCountProperty) {
+ (void)m_pFile->AddDescendantAtoms(m_pTrakAtom, "edts.elst");
+ if (InitEditListProperties() == false) return MP4_INVALID_EDIT_ID;
+ }
+
+ if (editId == MP4_INVALID_EDIT_ID) {
+ editId = m_pElstCountProperty->GetValue() + 1;
+ }
+
+ m_pElstMediaTimeProperty->InsertValue(0, editId - 1);
+ m_pElstDurationProperty->InsertValue(0, editId - 1);
+ m_pElstRateProperty->InsertValue(1, editId - 1);
+ m_pElstReservedProperty->InsertValue(0, editId - 1);
+
+ m_pElstCountProperty->IncrementValue();
+
+ return editId;
+}
+
+void MP4Track::DeleteEdit(MP4EditId editId)
+{
+ if (editId == MP4_INVALID_EDIT_ID) {
+ throw new MP4Error("edit id can't be zero",
+ "MP4Track::DeleteEdit");
+ }
+
+ if (!m_pElstCountProperty
+ || m_pElstCountProperty->GetValue() == 0) {
+ throw new MP4Error("no edits exist",
+ "MP4Track::DeleteEdit");
+ }
+
+ m_pElstMediaTimeProperty->DeleteValue(editId - 1);
+ m_pElstDurationProperty->DeleteValue(editId - 1);
+ m_pElstRateProperty->DeleteValue(editId - 1);
+ m_pElstReservedProperty->DeleteValue(editId - 1);
+
+ m_pElstCountProperty->IncrementValue(-1);
+
+ // clean up if last edit is deleted
+ if (m_pElstCountProperty->GetValue() == 0) {
+ m_pElstCountProperty = NULL;
+ m_pElstMediaTimeProperty = NULL;
+ m_pElstDurationProperty = NULL;
+ m_pElstRateProperty = NULL;
+ m_pElstReservedProperty = NULL;
+
+ m_pTrakAtom->DeleteChildAtom(
+ m_pTrakAtom->FindAtomMP4("trak.edts"));
+ }
+}
+
+MP4Timestamp MP4Track::GetEditStart(
+ MP4EditId editId)
+{
+ if (editId == MP4_INVALID_EDIT_ID) {
+ return MP4_INVALID_TIMESTAMP;
+ } else if (editId == 1) {
+ return 0;
+ }
+ return (MP4Timestamp)GetEditTotalDuration(editId - 1);
+}
+
+MP4Duration MP4Track::GetEditTotalDuration(
+ MP4EditId editId)
+{
+ u_int32_t numEdits = 0;
+
+ if (m_pElstCountProperty) {
+ numEdits = m_pElstCountProperty->GetValue();
+ }
+
+ if (editId == MP4_INVALID_EDIT_ID) {
+ editId = numEdits;
+ }
+
+ if (numEdits == 0 || editId > numEdits) {
+ return MP4_INVALID_DURATION;
+ }
+
+ MP4Duration totalDuration = 0;
+
+ for (MP4EditId eid = 1; eid <= editId; eid++) {
+ totalDuration +=
+ m_pElstDurationProperty->GetValue(eid - 1);
+ }
+
+ return totalDuration;
+}
+
+MP4SampleId MP4Track::GetSampleIdFromEditTime(
+ MP4Timestamp editWhen,
+ MP4Timestamp* pStartTime,
+ MP4Duration* pDuration)
+{
+ MP4SampleId sampleId = MP4_INVALID_SAMPLE_ID;
+ u_int32_t numEdits = 0;
+
+ if (m_pElstCountProperty) {
+ numEdits = m_pElstCountProperty->GetValue();
+ }
+
+ if (numEdits) {
+ MP4Duration editElapsedDuration = 0;
+
+ for (MP4EditId editId = 1; editId <= numEdits; editId++) {
+ // remember edit segment's start time (in edit timeline)
+ MP4Timestamp editStartTime =
+ (MP4Timestamp)editElapsedDuration;
+
+ // accumulate edit segment's duration
+ editElapsedDuration +=
+ m_pElstDurationProperty->GetValue(editId - 1);
+
+ // calculate difference between the specified edit time
+ // and the end of this edit segment
+ if (editElapsedDuration - editWhen <= 0) {
+ // the specified time has not yet been reached
+ continue;
+ }
+
+ // 'editWhen' is within this edit segment
+
+ // calculate the specified edit time
+ // relative to just this edit segment
+ MP4Duration editOffset =
+ editWhen - editStartTime;
+
+ // calculate the media (track) time that corresponds
+ // to the specified edit time based on the edit list
+ MP4Timestamp mediaWhen =
+ m_pElstMediaTimeProperty->GetValue(editId - 1)
+ + editOffset;
+
+ // lookup the sample id for the media time
+ sampleId = GetSampleIdFromTime(mediaWhen, false);
+
+ // lookup the sample's media start time and duration
+ MP4Timestamp sampleStartTime;
+ MP4Duration sampleDuration;
+
+ GetSampleTimes(sampleId, &sampleStartTime, &sampleDuration);
+
+ // calculate the difference if any between when the sample
+ // would naturally start and when it starts in the edit timeline
+ MP4Duration sampleStartOffset =
+ mediaWhen - sampleStartTime;
+
+ // calculate the start time for the sample in the edit time line
+ MP4Timestamp editSampleStartTime =
+ editWhen - MIN(editOffset, sampleStartOffset);
+
+ MP4Duration editSampleDuration = 0;
+
+ // calculate how long this sample lasts in the edit list timeline
+ if (m_pElstRateProperty->GetValue(editId - 1) == 0) {
+ // edit segment is a "dwell"
+ // so sample duration is that of the edit segment
+ editSampleDuration =
+ m_pElstDurationProperty->GetValue(editId - 1);
+
+ } else {
+ // begin with the natural sample duration
+ editSampleDuration = sampleDuration;
+
+ // now shorten that if the edit segment starts
+ // after the sample would naturally start
+ if (editOffset < sampleStartOffset) {
+ editSampleDuration -= sampleStartOffset - editOffset;
+ }
+
+ // now shorten that if the edit segment ends
+ // before the sample would naturally end
+ if (editElapsedDuration
+ < editSampleStartTime + sampleDuration) {
+ editSampleDuration -= (editSampleStartTime + sampleDuration)
+ - editElapsedDuration;
+ }
+ }
+
+ if (pStartTime) {
+ *pStartTime = editSampleStartTime;
+ }
+
+ if (pDuration) {
+ *pDuration = editSampleDuration;
+ }
+
+ VERBOSE_EDIT(m_pFile->GetVerbosity(),
+ printf("GetSampleIdFromEditTime: when "U64" "
+ "sampleId %u start "U64" duration "D64"\n",
+ editWhen, sampleId,
+ editSampleStartTime, editSampleDuration));
+
+ return sampleId;
+ }
+
+ throw new MP4Error("time out of range",
+ "MP4Track::GetSampleIdFromEditTime");
+
+ } else { // no edit list
+ sampleId = GetSampleIdFromTime(editWhen, false);
+
+ if (pStartTime || pDuration) {
+ GetSampleTimes(sampleId, pStartTime, pDuration);
+ }
+ }
+
+ return sampleId;
+}
+
+void MP4Track::CalculateBytesPerSample ()
+{
+ MP4Atom *pMedia = m_pTrakAtom->FindAtomMP4("trak.mdia.minf.stbl.stsd");
+ MP4Atom *pMediaData;
+ const char *media_data_name;
+ if (pMedia == NULL) return;
+
+ if (pMedia->GetNumberOfChildAtoms() != 1) return;
+
+ pMediaData = pMedia->GetChildAtom(0);
+ media_data_name = pMediaData->GetType();
+ if ((ATOMID(media_data_name) == ATOMID("twos")) ||
+ (ATOMID(media_data_name) == ATOMID("sowt"))) {
+ MP4IntegerProperty *chan, *sampleSize;
+ chan = (MP4IntegerProperty *)pMediaData->GetProperty(4);
+ sampleSize = (MP4IntegerProperty *)pMediaData->GetProperty(5);
+ m_bytesPerSample = chan->GetValue() * (sampleSize->GetValue() / 8);
+ }
+}
+