/* * 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 #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(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(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(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); } }