| /* |
| * 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 "src/impl.h" |
| |
| namespace mp4v2 { namespace impl { |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| #define AMR_UNINITIALIZED -1 |
| #define AMR_TRUE 0 |
| #define AMR_FALSE 1 |
| |
| MP4Track::MP4Track(MP4File& file, MP4Atom& trakAtom) |
| : m_File(file) |
| , m_trakAtom(trakAtom) |
| { |
| 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_sizeOfDataInChunkBuffer = 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_cachedSttsSid = MP4_INVALID_SAMPLE_ID; |
| m_cachedCttsSid = MP4_INVALID_SAMPLE_ID; |
| |
| bool success = true; |
| |
| MP4Integer32Property* pTrackIdProperty; |
| success &= m_trakAtom.FindProperty( |
| "trak.tkhd.trackId", |
| (MP4Property**)&pTrackIdProperty); |
| if (success) { |
| m_trackId = pTrackIdProperty->GetValue(); |
| } |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.mdhd.timeScale", |
| (MP4Property**)&m_pTimeScaleProperty); |
| if (success) { |
| // default chunking is 1 second of samples |
| m_durationPerChunk = m_pTimeScaleProperty->GetValue(); |
| } |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.tkhd.duration", |
| (MP4Property**)&m_pTrackDurationProperty); |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.mdhd.duration", |
| (MP4Property**)&m_pMediaDurationProperty); |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.tkhd.modificationTime", |
| (MP4Property**)&m_pTrackModificationProperty); |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.mdhd.modificationTime", |
| (MP4Property**)&m_pMediaModificationProperty); |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.hdlr.handlerType", |
| (MP4Property**)&m_pTypeProperty); |
| |
| // get handles on sample size information |
| |
| |
| m_pStszFixedSampleSizeProperty = NULL; |
| bool have_stsz = |
| m_trakAtom.FindProperty("trak.mdia.minf.stbl.stsz.sampleSize", |
| (MP4Property**)&m_pStszFixedSampleSizeProperty); |
| |
| if (have_stsz) { |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stsz.sampleCount", |
| (MP4Property**)&m_pStszSampleCountProperty); |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stsz.entries.entrySize", |
| (MP4Property**)&m_pStszSampleSizeProperty); |
| m_stsz_sample_bits = 32; |
| } else { |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stz2.sampleCount", |
| (MP4Property**)&m_pStszSampleCountProperty); |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stz2.entries.entrySize", |
| (MP4Property**)&m_pStszSampleSizeProperty); |
| MP4Integer8Property *stz2_field_size; |
| if (m_trakAtom.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_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stsc.entryCount", |
| (MP4Property**)&m_pStscCountProperty); |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stsc.entries.firstChunk", |
| (MP4Property**)&m_pStscFirstChunkProperty); |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stsc.entries.samplesPerChunk", |
| (MP4Property**)&m_pStscSamplesPerChunkProperty); |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stsc.entries.sampleDescriptionIndex", |
| (MP4Property**)&m_pStscSampleDescrIndexProperty); |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stsc.entries.firstSample", |
| (MP4Property**)&m_pStscFirstSampleProperty); |
| |
| bool haveStco = m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stco.entryCount", |
| (MP4Property**)&m_pChunkCountProperty); |
| |
| if (haveStco) { |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stco.entries.chunkOffset", |
| (MP4Property**)&m_pChunkOffsetProperty); |
| } else { |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.co64.entryCount", |
| (MP4Property**)&m_pChunkCountProperty); |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.co64.entries.chunkOffset", |
| (MP4Property**)&m_pChunkOffsetProperty); |
| } |
| |
| // get handles on sample timing info |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stts.entryCount", |
| (MP4Property**)&m_pSttsCountProperty); |
| |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stts.entries.sampleCount", |
| (MP4Property**)&m_pSttsSampleCountProperty); |
| |
| success &= m_trakAtom.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_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.ctts.entryCount", |
| (MP4Property**)&m_pCttsCountProperty); |
| |
| if (haveCtts) { |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.ctts.entries.sampleCount", |
| (MP4Property**)&m_pCttsSampleCountProperty); |
| |
| success &= m_trakAtom.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_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stss.entryCount", |
| (MP4Property**)&m_pStssCountProperty); |
| |
| if (haveStss) { |
| success &= m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stss.entries.sampleNumber", |
| (MP4Property**)&m_pStssSampleProperty); |
| } |
| |
| // edit list |
| (void)InitEditListProperties(); |
| |
| // was everything found? |
| if (!success) { |
| throw new Exception("invalid track", __FILE__, __LINE__, __FUNCTION__ ); |
| } |
| CalculateBytesPerSample(); |
| |
| // update sdtp log from sdtp atom |
| MP4SdtpAtom* sdtp = (MP4SdtpAtom*)m_trakAtom.FindAtom( "trak.mdia.minf.stbl.sdtp" ); |
| if( sdtp ) { |
| uint8_t* buffer; |
| uint32_t bufsize; |
| sdtp->data.GetValue( &buffer, &bufsize ); |
| m_sdtpLog.assign( (char*)buffer, bufsize ); |
| free( buffer ); |
| } |
| } |
| |
| MP4Track::~MP4Track() |
| { |
| MP4Free(m_pCachedReadSample); |
| m_pCachedReadSample = NULL; |
| MP4Free(m_pChunkBuffer); |
| m_pChunkBuffer = NULL; |
| } |
| |
| const char* MP4Track::GetType() |
| { |
| return m_pTypeProperty->GetValue(); |
| } |
| |
| void MP4Track::SetType(const char* type) |
| { |
| m_pTypeProperty->SetValue(MP4NormalizeTrackType(type)); |
| } |
| |
| void MP4Track::ReadSample( |
| MP4SampleId sampleId, |
| uint8_t** ppBytes, |
| uint32_t* pNumBytes, |
| MP4Timestamp* pStartTime, |
| MP4Duration* pDuration, |
| MP4Duration* pRenderingOffset, |
| bool* pIsSyncSample, |
| bool* hasDependencyFlags, |
| uint32_t* dependencyFlags ) |
| { |
| if( sampleId == MP4_INVALID_SAMPLE_ID ) |
| throw new Exception( "sample id can't be zero", __FILE__, __LINE__, __FUNCTION__ ); |
| |
| if( hasDependencyFlags ) |
| *hasDependencyFlags = !m_sdtpLog.empty(); |
| |
| if( dependencyFlags ) { |
| if( m_sdtpLog.empty() ) { |
| *dependencyFlags = 0; |
| } |
| else { |
| if( sampleId > m_sdtpLog.size() ) |
| throw new Exception( "sample id > sdtp logsize", __FILE__, __LINE__, __FUNCTION__ ); |
| *dependencyFlags = m_sdtpLog[sampleId-1]; // sampleId is 1-based |
| } |
| } |
| |
| // 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* fin = GetSampleFile( sampleId ); |
| if( fin == (File*)-1 ) |
| throw new Exception( "sample is located in an inaccessible file", __FILE__, __LINE__, __FUNCTION__ ); |
| |
| uint64_t fileOffset = GetSampleFileOffset(sampleId); |
| |
| uint32_t sampleSize = GetSampleSize(sampleId); |
| if (*ppBytes != NULL && *pNumBytes < sampleSize) { |
| throw new Exception("sample buffer is too small", |
| __FILE__, __LINE__, __FUNCTION__ ); |
| } |
| *pNumBytes = sampleSize; |
| |
| log.verbose3f("\"%s\": ReadSample: track %u id %u offset 0x%" PRIx64 " size %u (0x%x)", |
| GetFile().GetFilename().c_str(), m_trackId, sampleId, fileOffset, *pNumBytes, *pNumBytes); |
| |
| bool bufferMalloc = false; |
| if (*ppBytes == NULL) { |
| *ppBytes = (uint8_t*)MP4Malloc(*pNumBytes); |
| bufferMalloc = true; |
| } |
| |
| uint64_t oldPos = m_File.GetPosition( fin ); // only used in mode == 'w' |
| try { |
| m_File.SetPosition( fileOffset, fin ); |
| m_File.ReadBytes( *ppBytes, *pNumBytes, fin ); |
| |
| if (pStartTime || pDuration) { |
| GetSampleTimes(sampleId, pStartTime, pDuration); |
| |
| log.verbose3f("\"%s\": ReadSample: start %" PRIu64 " duration %" PRId64, |
| GetFile().GetFilename().c_str(), (pStartTime ? *pStartTime : 0), |
| (pDuration ? *pDuration : 0)); |
| } |
| if (pRenderingOffset) { |
| *pRenderingOffset = GetSampleRenderingOffset(sampleId); |
| |
| log.verbose3f("\"%s\": ReadSample: renderingOffset %" PRId64, |
| GetFile().GetFilename().c_str(), *pRenderingOffset); |
| } |
| if (pIsSyncSample) { |
| *pIsSyncSample = IsSyncSample(sampleId); |
| |
| log.verbose3f("\"%s\": ReadSample: isSyncSample %u", |
| GetFile().GetFilename().c_str(), *pIsSyncSample); |
| } |
| } |
| |
| catch (Exception* x) { |
| if( bufferMalloc ) { |
| MP4Free( *ppBytes ); |
| *ppBytes = NULL; |
| } |
| |
| if( m_File.IsWriteMode() ) |
| m_File.SetPosition( oldPos, fin ); |
| |
| throw x; |
| } |
| |
| if( m_File.IsWriteMode() ) |
| m_File.SetPosition( oldPos, fin ); |
| } |
| |
| void MP4Track::ReadSampleFragment( |
| MP4SampleId sampleId, |
| uint32_t sampleOffset, |
| uint16_t sampleLength, |
| uint8_t* pDest) |
| { |
| if (sampleId == MP4_INVALID_SAMPLE_ID) { |
| throw new Exception("invalid sample id", |
| __FILE__, __LINE__, __FUNCTION__ ); |
| } |
| |
| 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 Exception("offset and/or length are too large", |
| __FILE__, __LINE__, __FUNCTION__ ); |
| } |
| |
| memcpy(pDest, &m_pCachedReadSample[sampleOffset], sampleLength); |
| } |
| |
| void MP4Track::WriteSample( |
| const uint8_t* pBytes, |
| uint32_t numBytes, |
| MP4Duration duration, |
| MP4Duration renderingOffset, |
| bool isSyncSample ) |
| { |
| uint8_t curMode = 0; |
| |
| log.verbose3f("\"%s\": WriteSample: track %u id %u size %u (0x%x) ", |
| GetFile().GetFilename().c_str(), |
| m_trackId, m_writeSampleId, numBytes, numBytes); |
| |
| if (pBytes == NULL && numBytes > 0) { |
| throw new Exception("no sample data", __FILE__, __LINE__, __FUNCTION__ ); |
| } |
| |
| if (m_isAmr == AMR_UNINITIALIZED ) { |
| // figure out if this is an AMR audio track |
| if (m_trakAtom.FindAtom("trak.mdia.minf.stbl.stsd.samr") || |
| m_trakAtom.FindAtom("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(); |
| } |
| |
| log.verbose3f("\"%s\": duration %" PRIu64, GetFile().GetFilename().c_str(), |
| duration); |
| |
| if ((m_isAmr == AMR_TRUE) && |
| (m_curMode != curMode)) { |
| WriteChunkBuffer(); |
| m_curMode = curMode; |
| } |
| |
| // append sample bytes to chunk buffer |
| if( m_sizeOfDataInChunkBuffer + numBytes > m_chunkBufferSize ) { |
| m_pChunkBuffer = (uint8_t*)MP4Realloc(m_pChunkBuffer, m_chunkBufferSize + numBytes); |
| if (m_pChunkBuffer == NULL) |
| return; |
| |
| m_chunkBufferSize += numBytes; |
| } |
| |
| memcpy(&m_pChunkBuffer[m_sizeOfDataInChunkBuffer], pBytes, numBytes); |
| m_sizeOfDataInChunkBuffer += 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::WriteSampleDependency( |
| const uint8_t* pBytes, |
| uint32_t numBytes, |
| MP4Duration duration, |
| MP4Duration renderingOffset, |
| bool isSyncSample, |
| uint32_t dependencyFlags ) |
| { |
| m_sdtpLog.push_back( dependencyFlags ); // record dependency flags for processing at finish |
| WriteSample( pBytes, numBytes, duration, renderingOffset, isSyncSample ); |
| } |
| |
| void MP4Track::WriteChunkBuffer() |
| { |
| if (m_sizeOfDataInChunkBuffer == 0) { |
| return; |
| } |
| |
| uint64_t chunkOffset = m_File.GetPosition(); |
| |
| // write chunk buffer |
| m_File.WriteBytes(m_pChunkBuffer, m_sizeOfDataInChunkBuffer); |
| |
| log.verbose3f("\"%s\": WriteChunk: track %u offset 0x%" PRIx64 " size %u (0x%x) numSamples %u", |
| GetFile().GetFilename().c_str(), |
| m_trackId, chunkOffset, m_sizeOfDataInChunkBuffer, |
| m_sizeOfDataInChunkBuffer, m_chunkSamples); |
| |
| UpdateSampleToChunk(m_writeSampleId, |
| m_pChunkCountProperty->GetValue() + 1, |
| m_chunkSamples); |
| |
| UpdateChunkOffsets(chunkOffset); |
| |
| // note: we do not free our chunk buffer; we reuse it, expanding as needed. |
| // It gets zapped when this class goes out of scope |
| m_sizeOfDataInChunkBuffer = 0; |
| m_chunkSamples = 0; |
| m_chunkDuration = 0; |
| } |
| |
| void MP4Track::FinishWrite(uint32_t options) |
| { |
| FinishSdtp(); |
| |
| // 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_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stsd.*.esds.decConfigDescr.bufferSizeDB", |
| (MP4Property**)&pBufferSizeProperty)) { |
| pBufferSizeProperty->SetValue(GetMaxSampleSize()); |
| } |
| |
| // don't overwrite bitrate if it was requested in the Close call |
| if( !(options & MP4_CLOSE_DO_NOT_COMPUTE_BITRATE)) { |
| MP4Integer32Property* pBitrateProperty; |
| |
| if (m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stsd.*.esds.decConfigDescr.maxBitrate", |
| (MP4Property**)&pBitrateProperty)) { |
| pBitrateProperty->SetValue(GetMaxBitrate()); |
| } |
| |
| if (m_trakAtom.FindProperty( |
| "trak.mdia.minf.stbl.stsd.*.esds.decConfigDescr.avgBitrate", |
| (MP4Property**)&pBitrateProperty)) { |
| pBitrateProperty->SetValue(GetAvgBitrate()); |
| } |
| } |
| |
| // cleaup trak.udta |
| MP4BytesProperty* nameProperty = NULL; |
| m_trakAtom.FindProperty("trak.udta.name.value", (MP4Property**) &nameProperty); |
| if( nameProperty != NULL && nameProperty->GetValueSize() == 0 ){ |
| // Zero length name value--delete name, and then udta if no child atoms |
| MP4Atom* name = m_trakAtom.FindChildAtom("udta.name"); |
| if( name ) { |
| MP4Atom* udta = name->GetParentAtom(); |
| udta->DeleteChildAtom( name ); |
| delete name; |
| |
| if( udta->GetNumberOfChildAtoms() == 0 ) { |
| udta->GetParentAtom()->DeleteChildAtom( udta ); |
| delete udta; |
| } |
| } |
| } |
| } |
| |
| // Process sdtp log and add sdtp atom. |
| // |
| // Testing (subjective) showed a marked improvement with QuickTime |
| // player on Mac OS X when scrubbing. Best results were obtained |
| // from encodings using low number of bframes. It's expected sdtp may help |
| // other QT-based players. |
| // |
| void MP4Track::FinishSdtp() |
| { |
| // bail if log is empty -- indicates dependency information was not written |
| if( m_sdtpLog.empty() ) |
| return; |
| |
| MP4SdtpAtom* sdtp = (MP4SdtpAtom*)m_trakAtom.FindAtom( "trak.mdia.minf.stbl.sdtp" ); |
| if( !sdtp ) |
| sdtp = (MP4SdtpAtom*)AddAtom( "trak.mdia.minf.stbl", "sdtp" ); |
| sdtp->data.SetValue( (const uint8_t*)m_sdtpLog.data(), (uint32_t)m_sdtpLog.size() ); |
| |
| // add avc1 compatibility indicator if not present |
| MP4FtypAtom* ftyp = (MP4FtypAtom*)m_File.FindAtom( "ftyp" ); |
| if( ftyp ) { |
| bool found = false; |
| const uint32_t max = ftyp->compatibleBrands.GetCount(); |
| for( uint32_t i = 0; i < max; i++ ) { |
| if( !strcmp( ftyp->compatibleBrands.GetValue( i ), "avc1" )) { |
| found = true; |
| break; |
| } |
| } |
| |
| if( !found ) |
| ftyp->compatibleBrands.AddValue( "avc1" ); |
| } |
| } |
| |
| bool MP4Track::IsChunkFull(MP4SampleId sampleId) |
| { |
| if (m_samplesPerChunk) { |
| return m_chunkSamples >= m_samplesPerChunk; |
| } |
| |
| ASSERT(m_durationPerChunk); |
| return m_chunkDuration >= m_durationPerChunk; |
| } |
| |
| uint32_t MP4Track::GetNumberOfSamples() |
| { |
| return m_pStszSampleCountProperty->GetValue(); |
| } |
| |
| uint32_t MP4Track::GetSampleSize(MP4SampleId sampleId) |
| { |
| if (m_pStszFixedSampleSizeProperty != NULL) { |
| uint32_t fixedSampleSize = |
| m_pStszFixedSampleSizeProperty->GetValue(); |
| |
| if (fixedSampleSize != 0) { |
| return fixedSampleSize * m_bytesPerSample; |
| } |
| } |
| // 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 m_bytesPerSample * value; |
| } |
| return m_bytesPerSample * |
| m_pStszSampleSizeProperty->GetValue(sampleId - 1); |
| } |
| |
| uint32_t MP4Track::GetMaxSampleSize() |
| { |
| if (m_pStszFixedSampleSizeProperty != NULL) { |
| uint32_t fixedSampleSize = |
| m_pStszFixedSampleSizeProperty->GetValue(); |
| |
| if (fixedSampleSize != 0) { |
| return fixedSampleSize * m_bytesPerSample; |
| } |
| } |
| |
| uint32_t maxSampleSize = 0; |
| uint32_t numSamples = m_pStszSampleSizeProperty->GetCount(); |
| for (MP4SampleId sid = 1; sid <= numSamples; sid++) { |
| uint32_t sampleSize = |
| m_pStszSampleSizeProperty->GetValue(sid - 1); |
| if (sampleSize > maxSampleSize) { |
| maxSampleSize = sampleSize; |
| } |
| } |
| return maxSampleSize * m_bytesPerSample; |
| } |
| |
| uint64_t MP4Track::GetTotalOfSampleSizes() |
| { |
| uint64_t retval; |
| if (m_pStszFixedSampleSizeProperty != NULL) { |
| uint32_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 |
| uint64_t totalSampleSizes = 0; |
| uint32_t numSamples = m_pStszSampleSizeProperty->GetCount(); |
| for (MP4SampleId sid = 1; sid <= numSamples; sid++) { |
| uint32_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, uint32_t numBytes) |
| { |
| if (m_bytesPerSample > 1) { |
| if ((numBytes % m_bytesPerSample) != 0) { |
| // error |
| log.errorf("%s: \"%s\": numBytes %u not divisible by bytesPerSample %u sampleId %u", |
| __FUNCTION__, GetFile().GetFilename().c_str(), |
| numBytes, m_bytesPerSample, sampleId); |
| } |
| numBytes /= m_bytesPerSample; |
| } |
| // for first sample |
| // wmay - if we are adding, we want to make sure that |
| // we don't inadvertently set up the fixed size again. |
| // so, we check the number of samples |
| if (sampleId == 1 && GetNumberOfSamples() == 0) { |
| 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 |
| |
| uint32_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 |
| m_pStszFixedSampleSizeProperty->SetValue(0); |
| |
| // and create sizes for all previous samples |
| // use GetNumberOfSamples due to needing the total number |
| // not just the appended part of the file |
| uint32_t samples = GetNumberOfSamples(); |
| for (MP4SampleId sid = 1; sid <= samples; sid++) { |
| SampleSizePropertyAddValue(fixedSampleSize); |
| } |
| } |
| // add size value for this sample |
| SampleSizePropertyAddValue(numBytes); |
| } |
| } |
| // either way, we increment the number of samples. |
| m_pStszSampleCountProperty->IncrementValue(); |
| } |
| |
| uint32_t MP4Track::GetAvgBitrate() |
| { |
| if (GetDuration() == 0) { |
| return 0; |
| } |
| |
| double calc = double(GetTotalOfSampleSizes()); |
| // this is a bit better - we use the whole duration |
| calc *= 8.0; |
| calc *= GetTimeScale(); |
| calc /= double(GetDuration()); |
| // we might want to think about rounding to the next 100 or 1000 |
| return (uint32_t) ceil(calc); |
| } |
| |
| uint32_t MP4Track::GetMaxBitrate() |
| { |
| uint32_t timeScale = GetTimeScale(); |
| MP4SampleId numSamples = GetNumberOfSamples(); |
| uint32_t maxBytesPerSec = 0; |
| uint32_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; |
| // now, calculate the number of bytes we overflowed. Round up. |
| if( lastSampleDur > 0 ) { |
| uint32_t overflow_bytes = 0; |
| 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; |
| } |
| |
| uint32_t MP4Track::GetSampleStscIndex(MP4SampleId sampleId) |
| { |
| uint32_t stscIndex; |
| uint32_t numStscs = m_pStscCountProperty->GetValue(); |
| |
| if (numStscs == 0) { |
| throw new Exception("No data chunks exist", __FILE__, __LINE__, __FUNCTION__ ); |
| } |
| |
| 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 ) |
| { |
| uint32_t stscIndex = GetSampleStscIndex( sampleId ); |
| uint32_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_trakAtom.FindAtom( "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 ) |
| { |
| throw new Exception( "invalid stsd entry", __FILE__, __LINE__, __FUNCTION__ ); |
| } |
| |
| uint32_t drefIndex = pDrefIndexProperty->GetValue(); |
| |
| MP4Atom* pDrefAtom = m_trakAtom.FindAtom( "trak.mdia.minf.dinf.dref" ); |
| ASSERT(pDrefAtom); |
| |
| MP4Atom* pUrlAtom = pDrefAtom->GetChildAtom( drefIndex - 1 ); |
| ASSERT( pUrlAtom ); |
| |
| File* file; |
| |
| // make sure this is actually a url atom (somtimes it's "cios", like in iTunes videos) |
| if( strcmp(pUrlAtom->GetType(), "url ") || |
| pUrlAtom->GetFlags() & 1 ) { |
| file = NULL; // self-contained |
| } |
| else { |
| MP4StringProperty* pLocationProperty = NULL; |
| ASSERT( pUrlAtom->FindProperty( "*.location", (MP4Property**)&pLocationProperty) ); |
| ASSERT( pLocationProperty ); |
| |
| const char* url = pLocationProperty->GetValue(); |
| |
| log.verbose3f("\"%s\": dref url = %s", GetFile().GetFilename().c_str(), |
| url); |
| |
| file = (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 ) { |
| file = new File( fileName, File::MODE_READ ); |
| if( !file->open() ) { |
| delete file; |
| file = (File*)-1; |
| } |
| } |
| } |
| } |
| |
| if( m_lastSampleFile ) |
| m_lastSampleFile->close(); |
| |
| // cache the answer |
| m_lastStsdIndex = stsdIndex; |
| m_lastSampleFile = file; |
| |
| return file; |
| } |
| |
| uint64_t MP4Track::GetSampleFileOffset(MP4SampleId sampleId) |
| { |
| uint32_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. |
| uint32_t firstChunk = |
| m_pStscFirstChunkProperty->GetValue(stscIndex); |
| |
| MP4SampleId firstSample = |
| m_pStscFirstSampleProperty->GetValue(stscIndex); |
| |
| uint32_t samplesPerChunk = |
| m_pStscSamplesPerChunkProperty->GetValue(stscIndex); |
| |
| // chunkId tells which is the absolute chunk number that this sample |
| // is stored in. |
| MP4ChunkId chunkId = firstChunk + |
| ((sampleId - firstSample) / samplesPerChunk); |
| |
| // chunkOffset is the file offset (absolute) for the start of the chunk |
| uint64_t chunkOffset = m_pChunkOffsetProperty->GetValue(chunkId - 1); |
| |
| MP4SampleId firstSampleInChunk = |
| sampleId - ((sampleId - firstSample) % samplesPerChunk); |
| |
| // need cumulative samples sizes from firstSample to sampleId - 1 |
| uint32_t sampleOffset = 0; |
| for (MP4SampleId i = firstSampleInChunk; i < sampleId; i++) { |
| sampleOffset += GetSampleSize(i); |
| } |
| |
| return chunkOffset + sampleOffset; |
| } |
| |
| void MP4Track::UpdateSampleToChunk(MP4SampleId sampleId, |
| MP4ChunkId chunkId, uint32_t samplesPerChunk) |
| { |
| uint32_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(uint64_t chunkOffset) |
| { |
| if (m_pChunkOffsetProperty->GetType() == Integer32Property) { |
| ((MP4Integer32Property*)m_pChunkOffsetProperty)->AddValue(chunkOffset); |
| } else { |
| ((MP4Integer64Property*)m_pChunkOffsetProperty)->AddValue(chunkOffset); |
| } |
| m_pChunkCountProperty->IncrementValue(); |
| } |
| |
| MP4Duration MP4Track::GetFixedSampleDuration() |
| { |
| uint32_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) |
| { |
| uint32_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) |
| { |
| uint32_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 (uint32_t sttsIndex = m_cachedSttsIndex; sttsIndex < numStts; sttsIndex++) { |
| uint32_t sampleCount = |
| m_pSttsSampleCountProperty->GetValue(sttsIndex); |
| uint32_t sampleDelta = |
| m_pSttsSampleDeltaProperty->GetValue(sttsIndex); |
| |
| if (sampleId <= sid + sampleCount - 1) { |
| if (pStartTime) { |
| *pStartTime = (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 Exception("sample id out of range", |
| __FILE__, __LINE__, __FUNCTION__ ); |
| } |
| |
| MP4SampleId MP4Track::GetSampleIdFromTime( |
| MP4Timestamp when, |
| bool wantSyncSample) |
| { |
| uint32_t numStts = m_pSttsCountProperty->GetValue(); |
| MP4SampleId sid = 1; |
| MP4Duration elapsed = 0; |
| |
| for (uint32_t sttsIndex = 0; sttsIndex < numStts; sttsIndex++) { |
| uint32_t sampleCount = |
| m_pSttsSampleCountProperty->GetValue(sttsIndex); |
| uint32_t sampleDelta = |
| m_pSttsSampleDeltaProperty->GetValue(sttsIndex); |
| |
| if (sampleDelta == 0 && sttsIndex < numStts - 1) { |
| log.warningf("%s: \"%s\": Zero sample duration, stts entry %u", |
| __FUNCTION__, GetFile().GetFilename().c_str(), sttsIndex); |
| } |
| |
| MP4Duration d = when - elapsed; |
| |
| if (d <= sampleCount * sampleDelta) { |
| MP4SampleId sampleId = sid; |
| if (sampleDelta) { |
| sampleId += (d / sampleDelta); |
| } |
| |
| if (wantSyncSample) { |
| return GetNextSyncSample(sampleId); |
| } |
| return sampleId; |
| } |
| |
| sid += sampleCount; |
| elapsed += sampleCount * sampleDelta; |
| } |
| |
| throw new Exception("time out of range", |
| __FILE__, __LINE__, __FUNCTION__); |
| |
| return 0; // satisfy MS compiler |
| } |
| |
| void MP4Track::UpdateSampleTimes(MP4Duration duration) |
| { |
| uint32_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();; |
| } |
| } |
| |
| uint32_t MP4Track::GetSampleCttsIndex(MP4SampleId sampleId, |
| MP4SampleId* pFirstSampleId) |
| { |
| uint32_t numCtts = m_pCttsCountProperty->GetValue(); |
| MP4SampleId sid; |
| |
| if (m_cachedCttsSid != MP4_INVALID_SAMPLE_ID && sampleId >= m_cachedCttsSid) { |
| sid = m_cachedCttsSid; |
| } else { |
| m_cachedCttsIndex = 0; |
| sid = 1; |
| } |
| |
| for (uint32_t cttsIndex = m_cachedCttsIndex; cttsIndex < numCtts; cttsIndex++) { |
| uint32_t sampleCount = |
| m_pCttsSampleCountProperty->GetValue(cttsIndex); |
| |
| if (sampleId <= sid + sampleCount - 1) { |
| if (pFirstSampleId) { |
| *pFirstSampleId = sid; |
| } |
| |
| m_cachedCttsIndex = cttsIndex; |
| m_cachedCttsSid = sid; |
| |
| return cttsIndex; |
| } |
| sid += sampleCount; |
| } |
| |
| throw new Exception("sample id out of range", |
| __FILE__, __LINE__, __FUNCTION__ ); |
| return 0; // satisfy MS compiler |
| } |
| |
| MP4Duration MP4Track::GetSampleRenderingOffset(MP4SampleId sampleId) |
| { |
| if (m_pCttsCountProperty == NULL) { |
| return 0; |
| } |
| if (m_pCttsCountProperty->GetValue() == 0) { |
| return 0; |
| } |
| |
| uint32_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) |
| |
| uint32_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 |
| uint32_t afterSamples = GetNumberOfSamples() - sampleId; |
| |
| if (afterSamples) { |
| m_pCttsSampleCountProperty->AddValue(afterSamples); |
| m_pCttsSampleOffsetProperty->AddValue(0); |
| m_pCttsCountProperty->IncrementValue();; |
| } |
| |
| return; |
| } |
| |
| MP4SampleId firstSampleId; |
| uint32_t cttsIndex = GetSampleCttsIndex(sampleId, &firstSampleId); |
| |
| // do nothing in the degenerate case |
| if (renderingOffset == |
| m_pCttsSampleOffsetProperty->GetValue(cttsIndex)) { |
| return; |
| } |
| |
| uint32_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); |
| uint32_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; |
| } |
| |
| uint32_t numStss = m_pStssCountProperty->GetValue(); |
| uint32_t stssLIndex = 0; |
| uint32_t stssRIndex = numStss - 1; |
| |
| while (stssRIndex >= stssLIndex) { |
| uint32_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; |
| } |
| |
| uint32_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; |
| } |
| |
| 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 |
| uint32_t samples = GetNumberOfSamples(); |
| for (MP4SampleId sid = 1; sid < samples; sid++) { |
| m_pStssSampleProperty->AddValue(sid); |
| m_pStssCountProperty->IncrementValue(); |
| } |
| } // else nothing to do |
| } |
| } |
| |
| MP4Atom* MP4Track::AddAtom(const char* parentName, const char* childName) |
| { |
| MP4Atom* pParentAtom = m_trakAtom.FindAtom(parentName); |
| ASSERT(pParentAtom); |
| |
| MP4Atom* pChildAtom = MP4Atom::CreateAtom(m_File, pParentAtom, childName); |
| |
| pParentAtom->AddChildAtom(pChildAtom); |
| |
| pChildAtom->Generate(); |
| |
| return pChildAtom; |
| } |
| |
| uint64_t MP4Track::GetDuration() |
| { |
| return m_pMediaDurationProperty->GetValue(); |
| } |
| |
| uint32_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( |
| m_pMediaDurationProperty->GetValue()); |
| m_pTrackDurationProperty->SetValue(movieDuration); |
| |
| m_File.UpdateDuration(m_pTrackDurationProperty->GetValue()); |
| } |
| |
| MP4Duration MP4Track::ToMovieDuration(MP4Duration trackDuration) |
| { |
| return (trackDuration * m_File.GetTimeScale()) |
| / m_pTimeScaleProperty->GetValue(); |
| } |
| |
| void MP4Track::UpdateModificationTimes() |
| { |
| // update media and track modification times |
| MP4Timestamp now = MP4GetAbsTimestamp(); |
| m_pMediaModificationProperty->SetValue(now); |
| m_pTrackModificationProperty->SetValue(now); |
| } |
| |
| uint32_t MP4Track::GetNumberOfChunks() |
| { |
| return m_pChunkOffsetProperty->GetCount(); |
| } |
| |
| uint32_t MP4Track::GetChunkStscIndex(MP4ChunkId chunkId) |
| { |
| uint32_t stscIndex; |
| uint32_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) |
| { |
| uint32_t stscIndex = GetChunkStscIndex(chunkId); |
| |
| MP4ChunkId firstChunkId = |
| m_pStscFirstChunkProperty->GetValue(stscIndex); |
| |
| MP4SampleId firstSample = |
| m_pStscFirstSampleProperty->GetValue(stscIndex); |
| |
| uint32_t samplesPerChunk = |
| m_pStscSamplesPerChunkProperty->GetValue(stscIndex); |
| |
| MP4SampleId firstSampleInChunk = |
| firstSample + ((chunkId - firstChunkId) * samplesPerChunk); |
| |
| MP4Timestamp chunkTime; |
| |
| GetSampleTimes(firstSampleInChunk, &chunkTime, NULL); |
| |
| return chunkTime; |
| } |
| |
| uint32_t MP4Track::GetChunkSize(MP4ChunkId chunkId) |
| { |
| uint32_t stscIndex = GetChunkStscIndex(chunkId); |
| |
| MP4ChunkId firstChunkId = |
| m_pStscFirstChunkProperty->GetValue(stscIndex); |
| |
| MP4SampleId firstSample = |
| m_pStscFirstSampleProperty->GetValue(stscIndex); |
| |
| uint32_t samplesPerChunk = |
| m_pStscSamplesPerChunkProperty->GetValue(stscIndex); |
| |
| MP4SampleId firstSampleInChunk = |
| firstSample + ((chunkId - firstChunkId) * samplesPerChunk); |
| |
| // need cumulative sizes of samples in chunk |
| uint32_t chunkSize = 0; |
| for (uint32_t i = 0; i < samplesPerChunk; i++) { |
| chunkSize += GetSampleSize(firstSampleInChunk + i); |
| } |
| |
| return chunkSize; |
| } |
| |
| void MP4Track::ReadChunk(MP4ChunkId chunkId, |
| uint8_t** ppChunk, uint32_t* pChunkSize) |
| { |
| ASSERT(chunkId); |
| ASSERT(ppChunk); |
| ASSERT(pChunkSize); |
| |
| uint64_t chunkOffset = |
| m_pChunkOffsetProperty->GetValue(chunkId - 1); |
| |
| *pChunkSize = GetChunkSize(chunkId); |
| *ppChunk = (uint8_t*)MP4Malloc(*pChunkSize); |
| |
| log.verbose3f("\"%s\": ReadChunk: track %u id %u offset 0x%" PRIx64 " size %u (0x%x)", |
| GetFile().GetFilename().c_str(), |
| m_trackId, chunkId, chunkOffset, *pChunkSize, *pChunkSize); |
| |
| uint64_t oldPos = m_File.GetPosition(); // only used in mode == 'w' |
| try { |
| m_File.SetPosition( chunkOffset ); |
| m_File.ReadBytes( *ppChunk, *pChunkSize ); |
| } |
| catch( Exception* x ) { |
| MP4Free( *ppChunk ); |
| *ppChunk = NULL; |
| |
| if( m_File.IsWriteMode() ) |
| m_File.SetPosition( oldPos ); |
| |
| throw x; |
| } |
| |
| if( m_File.IsWriteMode() ) |
| m_File.SetPosition( oldPos ); |
| } |
| |
| void MP4Track::RewriteChunk(MP4ChunkId chunkId, |
| uint8_t* pChunk, uint32_t chunkSize) |
| { |
| uint64_t chunkOffset = m_File.GetPosition(); |
| |
| m_File.WriteBytes(pChunk, chunkSize); |
| |
| m_pChunkOffsetProperty->SetValue(chunkOffset, chunkId - 1); |
| |
| log.verbose3f("\"%s\": RewriteChunk: track %u id %u offset 0x%" PRIx64 " size %u (0x%x)", |
| GetFile().GetFilename().c_str(), |
| 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_trakAtom.FindAtom("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_File.AddDescendantAtoms(&m_trakAtom, "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 Exception("edit id can't be zero", |
| __FILE__, __LINE__, __FUNCTION__ ); |
| } |
| |
| if (!m_pElstCountProperty |
| || m_pElstCountProperty->GetValue() == 0) { |
| throw new Exception("no edits exist", |
| __FILE__, __LINE__, __FUNCTION__ ); |
| } |
| |
| 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_trakAtom.DeleteChildAtom( |
| m_trakAtom.FindAtom("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) |
| { |
| uint32_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; |
| uint32_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; |
| } |
| |
| log.verbose2f("\"%s\": GetSampleIdFromEditTime: when %" PRIu64 " " |
| "sampleId %u start %" PRIu64 " duration %" PRId64, |
| GetFile().GetFilename().c_str(), |
| editWhen, sampleId, |
| editSampleStartTime, editSampleDuration); |
| |
| return sampleId; |
| } |
| |
| throw new Exception("time out of range", |
| __FILE__, __LINE__, __FUNCTION__ ); |
| |
| } else { // no edit list |
| sampleId = GetSampleIdFromTime(editWhen, false); |
| |
| if (pStartTime || pDuration) { |
| GetSampleTimes(sampleId, pStartTime, pDuration); |
| } |
| } |
| |
| return sampleId; |
| } |
| |
| void MP4Track::CalculateBytesPerSample () |
| { |
| MP4Atom *pMedia = m_trakAtom.FindAtom("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); |
| } |
| } |
| |
| MP4Duration MP4Track::GetDurationPerChunk() |
| { |
| return m_durationPerChunk; |
| } |
| |
| void MP4Track::SetDurationPerChunk( MP4Duration duration ) |
| { |
| m_durationPerChunk = duration; |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| }} // namespace mp4v2::impl |