diff options
Diffstat (limited to 'Src/external_dependencies/libmp4v2/mp4track.cpp')
-rw-r--r-- | Src/external_dependencies/libmp4v2/mp4track.cpp | 1929 |
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); + } +} + |