/* * 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 - 2005. 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 * Bill May wmay@cisco.com */ #include "src/impl.h" namespace mp4v2 { namespace impl { /////////////////////////////////////////////////////////////////////////////// MP4File::MP4File( ) : m_file ( NULL ) , m_fileOriginalSize ( 0 ) , m_createFlags ( 0 ) { this->Init(); } /** * Initialize member variables (shared among constructors) */ void MP4File::Init() { m_pRootAtom = NULL; m_odTrackId = MP4_INVALID_TRACK_ID; m_useIsma = false; m_pModificationProperty = NULL; m_pTimeScaleProperty = NULL; m_pDurationProperty = NULL; m_memoryBuffer = NULL; m_memoryBufferSize = 0; m_memoryBufferPosition = 0; m_numReadBits = 0; m_bufReadBits = 0; m_numWriteBits = 0; m_bufWriteBits = 0; m_editName = NULL; m_trakName[0] = '\0'; } MP4File::~MP4File() { delete m_pRootAtom; for( uint32_t i = 0; i < m_pTracks.Size(); i++ ) delete m_pTracks[i]; MP4Free( m_memoryBuffer ); // just in case CHECK_AND_FREE( m_editName ); delete m_file; } const std::string & MP4File::GetFilename() const { // No one should call this unless Read, etc. has // succeeded and m_file exists since this method really // only exists for the public API. This helps us // guarantee that MP4GetFilename always returns a valid // string given a valid MP4FileHandle ASSERT(m_file); return m_file->name; } void MP4File::Read( const char* name, const MP4FileProvider* provider ) { Open( name, File::MODE_READ, provider ); ReadFromFile(); CacheProperties(); } void MP4File::Create( const char* fileName, uint32_t flags, int add_ftyp, int add_iods, char* majorBrand, uint32_t minorVersion, char** supportedBrands, uint32_t supportedBrandsCount ) { m_createFlags = flags; Open( fileName, File::MODE_CREATE, NULL ); // generate a skeletal atom tree m_pRootAtom = MP4Atom::CreateAtom(*this, NULL, NULL); m_pRootAtom->Generate(); if (add_ftyp != 0) { MakeFtypAtom(majorBrand, minorVersion, supportedBrands, supportedBrandsCount); } CacheProperties(); // create mdat, and insert it after ftyp, and before moov (void)InsertChildAtom(m_pRootAtom, "mdat", add_ftyp != 0 ? 1 : 0); // start writing m_pRootAtom->BeginWrite(); if (add_iods != 0) { (void)AddChildAtom("moov", "iods"); } } bool MP4File::Use64Bits (const char *atomName) { uint32_t atomid = ATOMID(atomName); if (atomid == ATOMID("mdat") || atomid == ATOMID("stbl")) { return (m_createFlags & MP4_CREATE_64BIT_DATA) == MP4_CREATE_64BIT_DATA; } if (atomid == ATOMID("mvhd") || atomid == ATOMID("tkhd") || atomid == ATOMID("mdhd")) { return (m_createFlags & MP4_CREATE_64BIT_TIME) == MP4_CREATE_64BIT_TIME; } return false; } void MP4File::Check64BitStatus (const char *atomName) { uint32_t atomid = ATOMID(atomName); if (atomid == ATOMID("mdat") || atomid == ATOMID("stbl")) { m_createFlags |= MP4_CREATE_64BIT_DATA; } else if (atomid == ATOMID("mvhd") || atomid == ATOMID("tkhd") || atomid == ATOMID("mdhd")) { m_createFlags |= MP4_CREATE_64BIT_TIME; } } bool MP4File::Modify( const char* fileName ) { Open( fileName, File::MODE_MODIFY, NULL ); ReadFromFile(); // find the moov atom MP4Atom* pMoovAtom = m_pRootAtom->FindAtom("moov"); uint32_t numAtoms; if (pMoovAtom == NULL) { // there isn't one, odd but we can still proceed log.warningf("%s: \"%s\": no moov atom, can't modify", __FUNCTION__, GetFilename().c_str()); return false; //pMoovAtom = AddChildAtom(m_pRootAtom, "moov"); } else { numAtoms = m_pRootAtom->GetNumberOfChildAtoms(); // work backwards thru the top level atoms int32_t i; bool lastAtomIsMoov = true; MP4Atom* pLastAtom = NULL; for (i = numAtoms - 1; i >= 0; i--) { MP4Atom* pAtom = m_pRootAtom->GetChildAtom(i); const char* type = pAtom->GetType(); // get rid of any trailing free or skips if (!strcmp(type, "free") || !strcmp(type, "skip")) { m_pRootAtom->DeleteChildAtom(pAtom); continue; } if (strcmp(type, "moov")) { if (pLastAtom == NULL) { pLastAtom = pAtom; lastAtomIsMoov = false; } continue; } // now at moov atom // multiple moov atoms?!? if (pAtom != pMoovAtom) { throw new Exception( "Badly formed mp4 file, multiple moov atoms", __FILE__,__LINE__,__FUNCTION__); } if (lastAtomIsMoov) { // position to start of moov atom, // effectively truncating file // prior to adding new mdat SetPosition(pMoovAtom->GetStart()); } else { // last atom isn't moov // need to place a free atom MP4Atom* pFreeAtom = MP4Atom::CreateAtom(*this, NULL, "free"); // in existing position of the moov atom m_pRootAtom->InsertChildAtom(pFreeAtom, i); m_pRootAtom->DeleteChildAtom(pMoovAtom); m_pRootAtom->AddChildAtom(pMoovAtom); // write free atom to disk SetPosition(pMoovAtom->GetStart()); pFreeAtom->SetSize(pMoovAtom->GetSize()); pFreeAtom->Write(); // finally set our file position to the end of the last atom SetPosition(pLastAtom->GetEnd()); } break; } ASSERT(i != -1); } CacheProperties(); // of moov atom numAtoms = m_pRootAtom->GetNumberOfChildAtoms(); // insert another mdat prior to moov atom (the last atom) MP4Atom* pMdatAtom = InsertChildAtom(m_pRootAtom, "mdat", numAtoms - 1); // start writing new mdat pMdatAtom->BeginWrite(Use64Bits("mdat")); return true; } void MP4File::Optimize( const char* srcFileName, const char* dstFileName ) { File* src = NULL; File* dst = NULL; // compute destination filename string dname; if( dstFileName ) { dname = dstFileName; } else { // No destination given, so let's kludge together a temporary file. // We'll try to create it in the same directory as the srcFileName, since // it's more likely that directory is writable. In the absence of that, // we'll create it in "./", which is the default pathnameTemp() provides. string s(srcFileName); size_t pos = s.find_last_of("\\/"); const char *d; if (pos == string::npos) { d = "."; } else { s = s.substr(0, pos); d = s.c_str(); } FileSystem::pathnameTemp( dname, d, "tmp", ".mp4" ); } try { // file source to optimize Open( srcFileName, File::MODE_READ, NULL ); ReadFromFile(); CacheProperties(); // of moov atom src = m_file; m_file = NULL; // optimized file destination Open( dname.c_str(), File::MODE_CREATE, NULL ); dst = m_file; SetIntegerProperty( "moov.mvhd.modificationTime", MP4GetAbsTimestamp() ); // writing meta info in the optimal order ((MP4RootAtom*)m_pRootAtom)->BeginOptimalWrite(); // write data in optimal order RewriteMdat( *src, *dst ); // finish writing ((MP4RootAtom*)m_pRootAtom)->FinishOptimalWrite(); } catch (...) { // cleanup and rethrow. Without this, we'd leak memory and an open file handle(s). if(src == NULL && dst == NULL) delete m_file;// We didn't make it far enough to have m_file go to src or dst. m_file = NULL; delete dst; delete src; throw; } // cleanup delete dst; delete src; m_file = NULL; // move temporary file into place if( !dstFileName ) Rename( dname.c_str(), srcFileName ); } void MP4File::RewriteMdat( File& src, File& dst ) { uint32_t numTracks = m_pTracks.Size(); MP4ChunkId* chunkIds = new MP4ChunkId[numTracks]; MP4ChunkId* maxChunkIds = new MP4ChunkId[numTracks]; MP4Timestamp* nextChunkTimes = new MP4Timestamp[numTracks]; for( uint32_t i = 0; i < numTracks; i++ ) { chunkIds[i] = 1; maxChunkIds[i] = m_pTracks[i]->GetNumberOfChunks(); nextChunkTimes[i] = MP4_INVALID_TIMESTAMP; } for( ;; ) { uint32_t nextTrackIndex = (uint32_t)-1; MP4Timestamp nextTime = MP4_INVALID_TIMESTAMP; for( uint32_t i = 0; i < numTracks; i++ ) { if( chunkIds[i] > maxChunkIds[i] ) continue; if( nextChunkTimes[i] == MP4_INVALID_TIMESTAMP ) { MP4Timestamp chunkTime = m_pTracks[i]->GetChunkTime( chunkIds[i] ); nextChunkTimes[i] = MP4ConvertTime( chunkTime, m_pTracks[i]->GetTimeScale(), GetTimeScale() ); } // time is not earliest so far if( nextChunkTimes[i] > nextTime ) continue; // prefer hint tracks to media tracks if times are equal if( nextChunkTimes[i] == nextTime && strcmp( m_pTracks[i]->GetType(), MP4_HINT_TRACK_TYPE )) continue; // this is our current choice of tracks nextTime = nextChunkTimes[i]; nextTrackIndex = i; } if( nextTrackIndex == (uint32_t)-1 ) break; uint8_t* pChunk; uint32_t chunkSize; // point into original mp4 file for read chunk call m_file = &src; m_pTracks[nextTrackIndex]->ReadChunk( chunkIds[nextTrackIndex], &pChunk, &chunkSize ); // point back at the new mp4 file for write chunk m_file = &dst; m_pTracks[nextTrackIndex]->RewriteChunk( chunkIds[nextTrackIndex], pChunk, chunkSize ); MP4Free( pChunk ); chunkIds[nextTrackIndex]++; nextChunkTimes[nextTrackIndex] = MP4_INVALID_TIMESTAMP; } delete [] chunkIds; delete [] maxChunkIds; delete [] nextChunkTimes; } void MP4File::Open( const char* name, File::Mode mode, const MP4FileProvider* provider ) { ASSERT( !m_file ); m_file = new File( name, mode, provider ? new io::CustomFileProvider( *provider ) : NULL ); if( m_file->open() ) { ostringstream msg; msg << "open(" << name << ") failed"; throw new Exception( msg.str(), __FILE__, __LINE__, __FUNCTION__); } switch( mode ) { case File::MODE_READ: case File::MODE_MODIFY: m_fileOriginalSize = m_file->size; break; case File::MODE_CREATE: default: m_fileOriginalSize = 0; break; } } void MP4File::ReadFromFile() { // ensure we start at beginning of file SetPosition(0); // create a new root atom ASSERT(m_pRootAtom == NULL); m_pRootAtom = MP4Atom::CreateAtom(*this, NULL, NULL); uint64_t fileSize = GetSize(); m_pRootAtom->SetStart(0); m_pRootAtom->SetSize(fileSize); m_pRootAtom->SetEnd(fileSize); m_pRootAtom->Read(); // create MP4Track's for any tracks in the file GenerateTracks(); } void MP4File::GenerateTracks() { uint32_t trackIndex = 0; while (true) { char trackName[32]; snprintf(trackName, sizeof(trackName), "moov.trak[%u]", trackIndex); // find next trak atom MP4Atom* pTrakAtom = m_pRootAtom->FindAtom(trackName); // done, no more trak atoms if (pTrakAtom == NULL) { break; } // find track id property MP4Integer32Property* pTrackIdProperty = NULL; (void)pTrakAtom->FindProperty( "trak.tkhd.trackId", (MP4Property**)&pTrackIdProperty); // find track type property MP4StringProperty* pTypeProperty = NULL; (void)pTrakAtom->FindProperty( "trak.mdia.hdlr.handlerType", (MP4Property**)&pTypeProperty); // ensure we have the basics properties if (pTrackIdProperty && pTypeProperty) { m_trakIds.Add(pTrackIdProperty->GetValue()); MP4Track* pTrack = NULL; try { if (!strcmp(pTypeProperty->GetValue(), MP4_HINT_TRACK_TYPE)) { pTrack = new MP4RtpHintTrack(*this, *pTrakAtom); } else { pTrack = new MP4Track(*this, *pTrakAtom); } m_pTracks.Add(pTrack); } catch( Exception* x ) { log.errorf(*x); delete x; } // remember when we encounter the OD track if (pTrack && !strcmp(pTrack->GetType(), MP4_OD_TRACK_TYPE)) { if (m_odTrackId == MP4_INVALID_TRACK_ID) { m_odTrackId = pTrackIdProperty->GetValue(); } else { log.warningf("%s: \"%s\": multiple OD tracks present", __FUNCTION__, GetFilename().c_str() ); } } } else { m_trakIds.Add(0); } trackIndex++; } } void MP4File::CacheProperties() { FindIntegerProperty("moov.mvhd.modificationTime", (MP4Property**)&m_pModificationProperty); FindIntegerProperty("moov.mvhd.timeScale", (MP4Property**)&m_pTimeScaleProperty); FindIntegerProperty("moov.mvhd.duration", (MP4Property**)&m_pDurationProperty); } void MP4File::BeginWrite() { m_pRootAtom->BeginWrite(); } void MP4File::FinishWrite(uint32_t options) { // remove empty moov.udta.meta.ilst { MP4Atom* ilst = FindAtom( "moov.udta.meta.ilst" ); if( ilst ) { if( ilst->GetNumberOfChildAtoms() == 0 ) { ilst->GetParentAtom()->DeleteChildAtom( ilst ); delete ilst; } } } // remove empty moov.udta.meta { MP4Atom* meta = FindAtom( "moov.udta.meta" ); if( meta ) { if( meta->GetNumberOfChildAtoms() == 0 ) { meta->GetParentAtom()->DeleteChildAtom( meta ); delete meta; } else if( meta->GetNumberOfChildAtoms() == 1 ) { if( ATOMID( meta->GetChildAtom( 0 )->GetType() ) == ATOMID( "hdlr" )) { meta->GetParentAtom()->DeleteChildAtom( meta ); delete meta; } } } } // remove empty moov.udta.name { MP4Atom* name = FindAtom( "moov.udta.name" ); if( name ) { unsigned char *val = NULL; uint32_t valSize = 0; GetBytesProperty("moov.udta.name.value", (uint8_t**)&val, &valSize); if( valSize == 0 ) { name->GetParentAtom()->DeleteChildAtom( name ); delete name; } } } // remove empty moov.udta { MP4Atom* udta = FindAtom( "moov.udta" ); if( udta ) { if( udta->GetNumberOfChildAtoms() == 0 ) { udta->GetParentAtom()->DeleteChildAtom( udta ); delete udta; } } } // for all tracks, flush chunking buffers for( uint32_t i = 0; i < m_pTracks.Size(); i++ ) { ASSERT( m_pTracks[i] ); m_pTracks[i]->FinishWrite(options); } // ask root atom to write m_pRootAtom->FinishWrite(); // finished all writes, if position < size then file has shrunk and // we mark remaining bytes as free atom; otherwise trailing garbage remains. if( GetPosition() < GetSize() ) { MP4RootAtom* root = (MP4RootAtom*)FindAtom( "" ); ASSERT( root ); // compute size of free atom; always has 8 bytes of overhead uint64_t size = GetSize() - GetPosition(); if( size < 8 ) size = 0; else size -= 8; MP4FreeAtom* freeAtom = (MP4FreeAtom*)MP4Atom::CreateAtom( *this, NULL, "free" ); ASSERT( freeAtom ); freeAtom->SetSize( size ); root->AddChildAtom( freeAtom ); freeAtom->Write(); } } void MP4File::UpdateDuration(MP4Duration duration) { MP4Duration currentDuration = GetDuration(); if (duration > currentDuration) { SetDuration(duration); } } void MP4File::Dump( bool dumpImplicits ) { log.dump(0, MP4_LOG_VERBOSE1, "\"%s\": Dumping meta-information...", m_file->name.c_str() ); m_pRootAtom->Dump( 0, dumpImplicits); } void MP4File::Close(uint32_t options) { if( IsWriteMode() ) { SetIntegerProperty( "moov.mvhd.modificationTime", MP4GetAbsTimestamp() ); FinishWrite(options); } delete m_file; m_file = NULL; } void MP4File::Rename(const char* oldFileName, const char* newFileName) { if( FileSystem::rename( oldFileName, newFileName )) throw new PlatformException( sys::getLastErrorStr(), sys::getLastError(), __FILE__, __LINE__, __FUNCTION__ ); } void MP4File::ProtectWriteOperation(const char* file, int line, const char* func ) { if( !IsWriteMode() ) throw new Exception( "operation not permitted in read mode", file, line, func ); } MP4Track* MP4File::GetTrack(MP4TrackId trackId) { return m_pTracks[FindTrackIndex(trackId)]; } MP4Atom* MP4File::FindAtom(const char* name) { MP4Atom* pAtom = NULL; if (!name || !strcmp(name, "")) { pAtom = m_pRootAtom; } else { pAtom = m_pRootAtom->FindAtom(name); } return pAtom; } MP4Atom* MP4File::AddChildAtom( const char* parentName, const char* childName) { return AddChildAtom(FindAtom(parentName), childName); } MP4Atom* MP4File::AddChildAtom( MP4Atom* pParentAtom, const char* childName) { return InsertChildAtom(pParentAtom, childName, pParentAtom->GetNumberOfChildAtoms()); } MP4Atom* MP4File::InsertChildAtom( const char* parentName, const char* childName, uint32_t index) { return InsertChildAtom(FindAtom(parentName), childName, index); } MP4Atom* MP4File::InsertChildAtom( MP4Atom* pParentAtom, const char* childName, uint32_t index) { MP4Atom* pChildAtom = MP4Atom::CreateAtom(*this, pParentAtom, childName); ASSERT(pParentAtom); pParentAtom->InsertChildAtom(pChildAtom, index); pChildAtom->Generate(); return pChildAtom; } MP4Atom* MP4File::AddDescendantAtoms( const char* ancestorName, const char* descendantNames) { return AddDescendantAtoms(FindAtom(ancestorName), descendantNames); } MP4Atom* MP4File::AddDescendantAtoms( MP4Atom* pAncestorAtom, const char* descendantNames) { ASSERT(pAncestorAtom); MP4Atom* pParentAtom = pAncestorAtom; MP4Atom* pChildAtom = NULL; while (true) { char* childName = MP4NameFirst(descendantNames); if (childName == NULL) { break; } descendantNames = MP4NameAfterFirst(descendantNames); pChildAtom = pParentAtom->FindChildAtom(childName); if (pChildAtom == NULL) { pChildAtom = AddChildAtom(pParentAtom, childName); } pParentAtom = pChildAtom; MP4Free(childName); } return pChildAtom; } bool MP4File::FindProperty(const char* name, MP4Property** ppProperty, uint32_t* pIndex) { if( pIndex ) *pIndex = 0; // set the default answer for index return m_pRootAtom->FindProperty(name, ppProperty, pIndex); } void MP4File::FindIntegerProperty(const char* name, MP4Property** ppProperty, uint32_t* pIndex) { if (!FindProperty(name, ppProperty, pIndex)) { ostringstream msg; msg << "no such property - " << name; throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__); } switch ((*ppProperty)->GetType()) { case Integer8Property: case Integer16Property: case Integer24Property: case Integer32Property: case Integer64Property: break; default: ostringstream msg; msg << "type mismatch - property " << name << " type " << (*ppProperty)->GetType(); throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__); } } uint64_t MP4File::GetIntegerProperty(const char* name) { MP4Property* pProperty; uint32_t index; FindIntegerProperty(name, &pProperty, &index); return ((MP4IntegerProperty*)pProperty)->GetValue(index); } void MP4File::SetIntegerProperty(const char* name, uint64_t value) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); MP4Property* pProperty = NULL; uint32_t index = 0; FindIntegerProperty(name, &pProperty, &index); ((MP4IntegerProperty*)pProperty)->SetValue(value, index); } void MP4File::FindFloatProperty(const char* name, MP4Property** ppProperty, uint32_t* pIndex) { if (!FindProperty(name, ppProperty, pIndex)) { ostringstream msg; msg << "no such property - " << name; throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__); } if ((*ppProperty)->GetType() != Float32Property) { ostringstream msg; msg << "type mismatch - property " << name << " type " << (*ppProperty)->GetType(); throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__); } } float MP4File::GetFloatProperty(const char* name) { MP4Property* pProperty; uint32_t index; FindFloatProperty(name, &pProperty, &index); return ((MP4Float32Property*)pProperty)->GetValue(index); } void MP4File::SetFloatProperty(const char* name, float value) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); MP4Property* pProperty; uint32_t index; FindFloatProperty(name, &pProperty, &index); ((MP4Float32Property*)pProperty)->SetValue(value, index); } void MP4File::FindStringProperty(const char* name, MP4Property** ppProperty, uint32_t* pIndex) { if (!FindProperty(name, ppProperty, pIndex)) { ostringstream msg; msg << "no such property - " << name; throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__); } if ((*ppProperty)->GetType() != StringProperty) { ostringstream msg; msg << "type mismatch - property " << name << " type " << (*ppProperty)->GetType(); throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__); } } const char* MP4File::GetStringProperty(const char* name) { MP4Property* pProperty; uint32_t index; FindStringProperty(name, &pProperty, &index); return ((MP4StringProperty*)pProperty)->GetValue(index); } void MP4File::SetStringProperty(const char* name, const char* value) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); MP4Property* pProperty; uint32_t index; FindStringProperty(name, &pProperty, &index); ((MP4StringProperty*)pProperty)->SetValue(value, index); } void MP4File::FindBytesProperty(const char* name, MP4Property** ppProperty, uint32_t* pIndex) { if (!FindProperty(name, ppProperty, pIndex)) { ostringstream msg; msg << "no such property " << name; throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__); } if ((*ppProperty)->GetType() != BytesProperty) { ostringstream msg; msg << "type mismatch - property " << name << " - type " << (*ppProperty)->GetType(); throw new Exception(msg.str(), __FILE__, __LINE__, __FUNCTION__); } } void MP4File::GetBytesProperty(const char* name, uint8_t** ppValue, uint32_t* pValueSize) { MP4Property* pProperty; uint32_t index; FindBytesProperty(name, &pProperty, &index); ((MP4BytesProperty*)pProperty)->GetValue(ppValue, pValueSize, index); } void MP4File::SetBytesProperty(const char* name, const uint8_t* pValue, uint32_t valueSize) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); MP4Property* pProperty; uint32_t index; FindBytesProperty(name, &pProperty, &index); ((MP4BytesProperty*)pProperty)->SetValue(pValue, valueSize, index); } // track functions MP4TrackId MP4File::AddTrack(const char* type, uint32_t timeScale) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); // create and add new trak atom MP4Atom* pTrakAtom = AddChildAtom("moov", "trak"); ASSERT(pTrakAtom); // allocate a new track id MP4TrackId trackId = AllocTrackId(); m_trakIds.Add(trackId); // set track id MP4Integer32Property* pInteger32Property = NULL; (void)pTrakAtom->FindProperty("trak.tkhd.trackId", (MP4Property**)&pInteger32Property); ASSERT(pInteger32Property); pInteger32Property->SetValue(trackId); // set track type const char* normType = MP4NormalizeTrackType(type); // sanity check for user defined types if (strlen(normType) > 4) { log.warningf("%s: \"%s\": type truncated to four characters", __FUNCTION__, GetFilename().c_str()); // StringProperty::SetValue() will do the actual truncation } MP4StringProperty* pStringProperty = NULL; (void)pTrakAtom->FindProperty("trak.mdia.hdlr.handlerType", (MP4Property**)&pStringProperty); ASSERT(pStringProperty); pStringProperty->SetValue(normType); // set track time scale pInteger32Property = NULL; (void)pTrakAtom->FindProperty("trak.mdia.mdhd.timeScale", (MP4Property**)&pInteger32Property); ASSERT(pInteger32Property); pInteger32Property->SetValue(timeScale ? timeScale : 1000); // now have enough to create MP4Track object MP4Track* pTrack = NULL; if (!strcmp(normType, MP4_HINT_TRACK_TYPE)) { pTrack = new MP4RtpHintTrack(*this, *pTrakAtom); } else { pTrack = new MP4Track(*this, *pTrakAtom); } m_pTracks.Add(pTrack); // mark non-hint tracks as enabled if (strcmp(normType, MP4_HINT_TRACK_TYPE)) { SetTrackIntegerProperty(trackId, "tkhd.flags", 1); } // mark track as contained in this file // LATER will provide option for external data references AddDataReference(trackId, NULL); return trackId; } void MP4File::AddTrackToIod(MP4TrackId trackId) { MP4DescriptorProperty* pDescriptorProperty = NULL; (void)m_pRootAtom->FindProperty("moov.iods.esIds", (MP4Property**)&pDescriptorProperty); ASSERT(pDescriptorProperty); MP4Descriptor* pDescriptor = pDescriptorProperty->AddDescriptor(MP4ESIDIncDescrTag); ASSERT(pDescriptor); MP4Integer32Property* pIdProperty = NULL; (void)pDescriptor->FindProperty("id", (MP4Property**)&pIdProperty); ASSERT(pIdProperty); pIdProperty->SetValue(trackId); } void MP4File::RemoveTrackFromIod(MP4TrackId trackId, bool shallHaveIods) { MP4DescriptorProperty* pDescriptorProperty = NULL; if (!m_pRootAtom->FindProperty("moov.iods.esIds",(MP4Property**)&pDescriptorProperty) || pDescriptorProperty == NULL) return; for (uint32_t i = 0; i < pDescriptorProperty->GetCount(); i++) { /* static */ char name[32]; snprintf(name, sizeof(name), "esIds[%u].id", i); MP4Integer32Property* pIdProperty = NULL; (void)pDescriptorProperty->FindProperty(name, (MP4Property**)&pIdProperty); // wmay ASSERT(pIdProperty); if (pIdProperty != NULL && pIdProperty->GetValue() == trackId) { pDescriptorProperty->DeleteDescriptor(i); break; } } } void MP4File::AddTrackToOd(MP4TrackId trackId) { if (!m_odTrackId) { return; } AddTrackReference(MakeTrackName(m_odTrackId, "tref.mpod"), trackId); } void MP4File::RemoveTrackFromOd(MP4TrackId trackId) { if (!m_odTrackId) { return; } RemoveTrackReference(MakeTrackName(m_odTrackId, "tref.mpod"), trackId); } /* * Try to obtain the properties of this reference track, if not found then return * NULL in *ppCountProperty and *ppTrackIdProperty. */ void MP4File::GetTrackReferenceProperties(const char* trefName, MP4Property** ppCountProperty, MP4Property** ppTrackIdProperty) { char propName[1024]; snprintf(propName, sizeof(propName), "%s.%s", trefName, "entryCount"); (void)m_pRootAtom->FindProperty(propName, ppCountProperty); snprintf(propName, sizeof(propName), "%s.%s", trefName, "entries.trackId"); (void)m_pRootAtom->FindProperty(propName, ppTrackIdProperty); } void MP4File::AddTrackReference(const char* trefName, MP4TrackId refTrackId) { MP4Integer32Property* pCountProperty = NULL; MP4Integer32Property* pTrackIdProperty = NULL; GetTrackReferenceProperties(trefName, (MP4Property**)&pCountProperty, (MP4Property**)&pTrackIdProperty); if (pCountProperty && pTrackIdProperty) { pTrackIdProperty->AddValue(refTrackId); pCountProperty->IncrementValue(); } } uint32_t MP4File::FindTrackReference(const char* trefName, MP4TrackId refTrackId) { MP4Integer32Property* pCountProperty = NULL; MP4Integer32Property* pTrackIdProperty = NULL; GetTrackReferenceProperties(trefName, (MP4Property**)&pCountProperty, (MP4Property**)&pTrackIdProperty); if (pCountProperty && pTrackIdProperty) { for (uint32_t i = 0; i < pCountProperty->GetValue(); i++) { if (refTrackId == pTrackIdProperty->GetValue(i)) { return i + 1; // N.B. 1 not 0 based index } } } return 0; } void MP4File::RemoveTrackReference(const char* trefName, MP4TrackId refTrackId) { MP4Integer32Property* pCountProperty = NULL; MP4Integer32Property* pTrackIdProperty = NULL; GetTrackReferenceProperties(trefName, (MP4Property**)&pCountProperty, (MP4Property**)&pTrackIdProperty); if (pCountProperty && pTrackIdProperty) { for (uint32_t i = 0; i < pCountProperty->GetValue(); i++) { if (refTrackId == pTrackIdProperty->GetValue(i)) { pTrackIdProperty->DeleteValue(i); pCountProperty->IncrementValue(-1); } } } } void MP4File::AddDataReference(MP4TrackId trackId, const char* url) { MP4Atom* pDrefAtom = FindAtom(MakeTrackName(trackId, "mdia.minf.dinf.dref")); ASSERT(pDrefAtom); MP4Integer32Property* pCountProperty = NULL; (void)pDrefAtom->FindProperty("dref.entryCount", (MP4Property**)&pCountProperty); ASSERT(pCountProperty); pCountProperty->IncrementValue(); MP4Atom* pUrlAtom = AddChildAtom(pDrefAtom, "url "); if (url && url[0] != '\0') { pUrlAtom->SetFlags(pUrlAtom->GetFlags() & 0xFFFFFE); MP4StringProperty* pUrlProperty = NULL; (void)pUrlAtom->FindProperty("url .location", (MP4Property**)&pUrlProperty); ASSERT(pUrlProperty); pUrlProperty->SetValue(url); } else { pUrlAtom->SetFlags(pUrlAtom->GetFlags() | 1); } } MP4TrackId MP4File::AddSystemsTrack(const char* type, uint32_t timeScale) { const char* normType = MP4NormalizeTrackType(type); // TBD if user type, fix name to four chars, and warn MP4TrackId trackId = AddTrack(type, timeScale); (void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "nmhd", 0); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "mp4s"); AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.name"); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the mp4s atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4s.esds.ESID", 0 ); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr.objectTypeId", MP4SystemsV1ObjectType); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr.streamType", ConvertTrackTypeToStreamType(normType)); return trackId; } MP4TrackId MP4File::AddODTrack() { // until a demonstrated need emerges // we limit ourselves to one object description track if (m_odTrackId != MP4_INVALID_TRACK_ID) { throw new Exception("object description track already exists",__FILE__, __LINE__, __FUNCTION__); } m_odTrackId = AddSystemsTrack(MP4_OD_TRACK_TYPE); AddTrackToIod(m_odTrackId); (void)AddDescendantAtoms(MakeTrackName(m_odTrackId, NULL), "tref.mpod"); return m_odTrackId; } MP4TrackId MP4File::AddSceneTrack() { MP4TrackId trackId = AddSystemsTrack(MP4_SCENE_TRACK_TYPE); AddTrackToIod(trackId); AddTrackToOd(trackId); return trackId; } bool MP4File::ShallHaveIods() { // NULL terminated list of brands which require the IODS atom const char* brandsWithIods[] = { "mp42", "isom", NULL }; MP4FtypAtom* ftyp = (MP4FtypAtom*)m_pRootAtom->FindAtom( "ftyp" ); if( !ftyp ) return false; // check major brand const char* brand = ftyp->majorBrand.GetValue(); for( uint32_t i = 0; brandsWithIods[i] != NULL; i++ ) { if( !strcasecmp( brandsWithIods[i], brand )) return true; } // check compatible brands uint32_t max = ftyp->compatibleBrands.GetCount(); for( uint32_t i = 0; i < max; i++ ) { brand = ftyp->compatibleBrands.GetValue( i ); for( uint32_t j = 0; brandsWithIods[j] != NULL ; j++) { if( !strcasecmp( brandsWithIods[j], brand )) return true; } } return false; } void MP4File::SetAmrVendor( MP4TrackId trackId, uint32_t vendor) { SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.*.damr.vendor", vendor); } void MP4File::SetAmrDecoderVersion( MP4TrackId trackId, uint8_t decoderVersion) { SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.*.damr.decoderVersion", decoderVersion); } void MP4File::SetAmrModeSet( MP4TrackId trackId, uint16_t modeSet) { SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.*.damr.modeSet", modeSet); } uint16_t MP4File::GetAmrModeSet(MP4TrackId trackId) { return GetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.*.damr.modeSet"); } MP4TrackId MP4File::AddAmrAudioTrack( uint32_t timeScale, uint16_t modeSet, uint8_t modeChangePeriod, uint8_t framesPerSample, bool isAmrWB) { uint32_t fixedSampleDuration = (timeScale * 20)/1000; // 20mSec/Sample MP4TrackId trackId = AddTrack(MP4_AUDIO_TRACK_TYPE, timeScale); AddTrackToOd(trackId); SetTrackFloatProperty(trackId, "tkhd.volume", 1.0); (void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), isAmrWB ? "sawb" : "samr"); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the mp4a atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.*.timeScale", timeScale); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.*.damr.modeSet", modeSet); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.*.damr.modeChangePeriod", modeChangePeriod); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.*.damr.framesPerSample", framesPerSample); m_pTracks[FindTrackIndex(trackId)]-> SetFixedSampleDuration(fixedSampleDuration); return trackId; } MP4TrackId MP4File::AddULawAudioTrack( uint32_t timeScale) { uint32_t fixedSampleDuration = (timeScale * 20)/1000; // 20mSec/Sample MP4TrackId trackId = AddTrack(MP4_AUDIO_TRACK_TYPE, timeScale); AddTrackToOd(trackId); SetTrackFloatProperty(trackId, "tkhd.volume", 1.0); (void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "ulaw"); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the mp4a atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.ulaw.timeScale", timeScale<<16); m_pTracks[FindTrackIndex(trackId)]->SetFixedSampleDuration(fixedSampleDuration); return trackId; } MP4TrackId MP4File::AddALawAudioTrack( uint32_t timeScale) { uint32_t fixedSampleDuration = (timeScale * 20)/1000; // 20mSec/Sample MP4TrackId trackId = AddTrack(MP4_AUDIO_TRACK_TYPE, timeScale); AddTrackToOd(trackId); SetTrackFloatProperty(trackId, "tkhd.volume", 1.0); (void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "alaw"); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the mp4a atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.alaw.timeScale", timeScale<<16); m_pTracks[FindTrackIndex(trackId)]->SetFixedSampleDuration(fixedSampleDuration); return trackId; } MP4TrackId MP4File::AddAudioTrack( uint32_t timeScale, MP4Duration sampleDuration, uint8_t audioType) { MP4TrackId trackId = AddTrack(MP4_AUDIO_TRACK_TYPE, timeScale); AddTrackToOd(trackId); SetTrackFloatProperty(trackId, "tkhd.volume", 1.0); (void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "mp4a"); AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.name"); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the mp4a atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4a.timeScale", timeScale << 16); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4a.esds.ESID", 0 ); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4a.esds.decConfigDescr.objectTypeId", audioType); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4a.esds.decConfigDescr.streamType", MP4AudioStreamType); m_pTracks[FindTrackIndex(trackId)]-> SetFixedSampleDuration(sampleDuration); return trackId; } MP4TrackId MP4File::AddAC3AudioTrack( uint32_t samplingRate, uint8_t fscod, uint8_t bsid, uint8_t bsmod, uint8_t acmod, uint8_t lfeon, uint8_t bit_rate_code) { MP4TrackId trackId = AddTrack(MP4_AUDIO_TRACK_TYPE, samplingRate); AddTrackToOd(trackId); SetTrackFloatProperty(trackId, "tkhd.volume", 1.0); InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0); AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "ac-3"); // Set Ac3 settings MP4Integer16Property* pSampleRateProperty = NULL; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.samplingRate"), (MP4Property**)&pSampleRateProperty); if (pSampleRateProperty) { pSampleRateProperty->SetValue(samplingRate); } else { throw new Exception("no ac-3.samplingRate property", __FILE__, __LINE__, __FUNCTION__); } MP4BitfieldProperty* pBitfieldProperty = NULL; FindProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.dac3.fscod"), (MP4Property**)&pBitfieldProperty); if (pBitfieldProperty) { pBitfieldProperty->SetValue(fscod); pBitfieldProperty = NULL; } else { throw new Exception("no dac3.fscod property", __FILE__, __LINE__, __FUNCTION__); } FindProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.dac3.bsid"), (MP4Property**)&pBitfieldProperty); if (pBitfieldProperty) { pBitfieldProperty->SetValue(bsid); pBitfieldProperty = NULL; } else { throw new Exception("no dac3.bsid property", __FILE__, __LINE__, __FUNCTION__); } FindProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.dac3.bsmod"), (MP4Property**)&pBitfieldProperty); if (pBitfieldProperty) { pBitfieldProperty->SetValue(bsmod); pBitfieldProperty = NULL; } else { throw new Exception("no dac3.bsmod property", __FILE__, __LINE__, __FUNCTION__); } FindProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.dac3.acmod"), (MP4Property**)&pBitfieldProperty); if (pBitfieldProperty) { pBitfieldProperty->SetValue(acmod); pBitfieldProperty = NULL; } else { throw new Exception("no dac3.acmod property", __FILE__, __LINE__, __FUNCTION__); } FindProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.dac3.lfeon"), (MP4Property**)&pBitfieldProperty); if (pBitfieldProperty) { pBitfieldProperty->SetValue(lfeon); pBitfieldProperty = NULL; } else { throw new Exception("no dac3.lfeon property", __FILE__, __LINE__, __FUNCTION__); } FindProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.ac-3.dac3.bit_rate_code"), (MP4Property**)&pBitfieldProperty); if (pBitfieldProperty) { pBitfieldProperty->SetValue(bit_rate_code); pBitfieldProperty = NULL; } else { throw new Exception("no dac3.bit_rate_code property", __FILE__, __LINE__, __FUNCTION__); } AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.name"); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the mp4a atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); m_pTracks[FindTrackIndex(trackId)]-> SetFixedSampleDuration(1536); return trackId; } MP4TrackId MP4File::AddEncAudioTrack(uint32_t timeScale, MP4Duration sampleDuration, uint8_t audioType, uint32_t scheme_type, uint16_t scheme_version, uint8_t key_ind_len, uint8_t iv_len, bool selective_enc, const char *kms_uri, bool use_ismacryp ) { uint32_t original_fmt = 0; MP4TrackId trackId = AddTrack(MP4_AUDIO_TRACK_TYPE, timeScale); AddTrackToOd(trackId); SetTrackFloatProperty(trackId, "tkhd.volume", 1.0); (void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "enca"); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the enca atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); /* set all the ismacryp-specific values */ // original format is mp4a if (use_ismacryp) { original_fmt = ATOMID("mp4a"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.enca.sinf.frma.data-format", original_fmt); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.enca.sinf"), "schm"); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.enca.sinf"), "schi"); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.enca.sinf.schi"), "iKMS"); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.enca.sinf.schi"), "iSFM"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.enca.sinf.schm.scheme_type", scheme_type); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.enca.sinf.schm.scheme_version", scheme_version); SetTrackStringProperty(trackId, "mdia.minf.stbl.stsd.enca.sinf.schi.iKMS.kms_URI", kms_uri); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.enca.sinf.schi.iSFM.selective-encryption", selective_enc); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.enca.sinf.schi.iSFM.key-indicator-length", key_ind_len); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.enca.sinf.schi.iSFM.IV-length", iv_len); /* end ismacryp */ } SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.enca.timeScale", timeScale); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.enca.esds.ESID", 0 ); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.enca.esds.decConfigDescr.objectTypeId", audioType); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.enca.esds.decConfigDescr.streamType", MP4AudioStreamType); m_pTracks[FindTrackIndex(trackId)]-> SetFixedSampleDuration(sampleDuration); return trackId; } MP4TrackId MP4File::AddCntlTrackDefault (uint32_t timeScale, MP4Duration sampleDuration, const char *type) { MP4TrackId trackId = AddTrack(MP4_CNTL_TRACK_TYPE, timeScale); (void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "nmhd", 0); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), type); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the mp4v atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsz.sampleSize", sampleDuration); m_pTracks[FindTrackIndex(trackId)]-> SetFixedSampleDuration(sampleDuration); return trackId; } MP4TrackId MP4File::AddHrefTrack (uint32_t timeScale, MP4Duration sampleDuration, const char *base_url) { MP4TrackId trackId = AddCntlTrackDefault(timeScale, sampleDuration, "href"); if (base_url != NULL) { (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.href"), "burl"); SetTrackStringProperty(trackId, "mdia.minf.stbl.stsd.href.burl.base_url", base_url); } return trackId; } MP4TrackId MP4File::AddVideoTrackDefault( uint32_t timeScale, MP4Duration sampleDuration, uint16_t width, uint16_t height, const char *videoType) { MP4TrackId trackId = AddTrack(MP4_VIDEO_TRACK_TYPE, timeScale); AddTrackToOd(trackId); SetTrackFloatProperty(trackId, "tkhd.width", width); SetTrackFloatProperty(trackId, "tkhd.height", height); (void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "vmhd", 0); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), videoType); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the mp4v atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsz.sampleSize", sampleDuration); m_pTracks[FindTrackIndex(trackId)]-> SetFixedSampleDuration(sampleDuration); return trackId; } MP4TrackId MP4File::AddMP4VideoTrack( uint32_t timeScale, MP4Duration sampleDuration, uint16_t width, uint16_t height, uint8_t videoType) { MP4TrackId trackId = AddVideoTrackDefault(timeScale, sampleDuration, width, height, "mp4v"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.width", width); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.height", height); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.esds.ESID", 0 ); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.esds.decConfigDescr.objectTypeId", videoType); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.esds.decConfigDescr.streamType", MP4VisualStreamType); return trackId; } // ismacrypted MP4TrackId MP4File::AddEncVideoTrack(uint32_t timeScale, MP4Duration sampleDuration, uint16_t width, uint16_t height, uint8_t videoType, mp4v2_ismacrypParams *icPp, const char *oFormat ) { uint32_t original_fmt = 0; MP4TrackId trackId = AddVideoTrackDefault(timeScale, sampleDuration, width, height, "encv"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.width", width); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.height", height); /* set all the ismacryp-specific values */ original_fmt = ATOMID(oFormat); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.frma.data-format", original_fmt); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf"), "schm"); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf"), "schi"); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi"), "iKMS"); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi"), "iSFM"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schm.scheme_type", icPp->scheme_type); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schm.scheme_version", icPp->scheme_version); SetTrackStringProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi.iKMS.kms_URI", icPp->kms_uri); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.selective-encryption", icPp->selective_enc); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.key-indicator-length", icPp->key_ind_len); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.IV-length", icPp->iv_len); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.esds.ESID", 0 ); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.esds.decConfigDescr.objectTypeId", videoType); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.esds.decConfigDescr.streamType", MP4VisualStreamType); return trackId; } MP4TrackId MP4File::AddH264VideoTrack( uint32_t timeScale, MP4Duration sampleDuration, uint16_t width, uint16_t height, uint8_t AVCProfileIndication, uint8_t profile_compat, uint8_t AVCLevelIndication, uint8_t sampleLenFieldSizeMinusOne) { MP4TrackId trackId = AddVideoTrackDefault(timeScale, sampleDuration, width, height, "avc1"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.width", width); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.height", height); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.avcC.AVCProfileIndication", AVCProfileIndication); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.avcC.profile_compatibility", profile_compat); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.avcC.AVCLevelIndication", AVCLevelIndication); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.avcC.lengthSizeMinusOne", sampleLenFieldSizeMinusOne); return trackId; } MP4TrackId MP4File::AddEncH264VideoTrack( uint32_t timeScale, MP4Duration sampleDuration, uint16_t width, uint16_t height, MP4Atom *srcAtom, mp4v2_ismacrypParams *icPp) { uint32_t original_fmt = 0; MP4Atom *avcCAtom; MP4TrackId trackId = AddVideoTrackDefault(timeScale, sampleDuration, width, height, "encv"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.width", width); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.height", height); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv"), "avcC"); // create default values avcCAtom = FindAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.avcC")); // export source atom ((MP4AvcCAtom *) srcAtom)->Clone((MP4AvcCAtom *)avcCAtom); /* set all the ismacryp-specific values */ (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf"), "schm"); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf"), "schi"); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi"), "iKMS"); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi"), "iSFM"); // per ismacrypt E&A V1.1 section 9.1.2.1 'avc1' is renamed '264b' // avc1 must not appear as a sample entry name or original format name original_fmt = ATOMID("264b"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.frma.data-format", original_fmt); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schm.scheme_type", icPp->scheme_type); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schm.scheme_version", icPp->scheme_version); SetTrackStringProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi.iKMS.kms_URI", icPp->kms_uri); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.selective-encryption", icPp->selective_enc); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.key-indicator-length", icPp->key_ind_len); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.encv.sinf.schi.iSFM.IV-length", icPp->iv_len); return trackId; } void MP4File::AddH264SequenceParameterSet (MP4TrackId trackId, const uint8_t *pSequence, uint16_t sequenceLen) { const char *format; MP4Atom *avcCAtom; // get 4cc media format - can be avc1 or encv for ismacrypted track format = GetTrackMediaDataName(trackId); if (!strcasecmp(format, "avc1")) avcCAtom = FindAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.avc1.avcC")); else if (!strcasecmp(format, "encv")) avcCAtom = FindAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.avcC")); else // huh? unknown track format return; MP4BitfieldProperty *pCount; MP4Integer16Property *pLength; MP4BytesProperty *pUnit; if ((avcCAtom->FindProperty("avcC.numOfSequenceParameterSets", (MP4Property **)&pCount) == false) || (avcCAtom->FindProperty("avcC.sequenceEntries.sequenceParameterSetLength", (MP4Property **)&pLength) == false) || (avcCAtom->FindProperty("avcC.sequenceEntries.sequenceParameterSetNALUnit", (MP4Property **)&pUnit) == false)) { log.errorf("%s: \"%s\": Could not find avcC properties", __FUNCTION__, GetFilename().c_str() ); return; } uint32_t count = pCount->GetValue(); if (count > 0) { // see if we already exist for (uint32_t index = 0; index < count; index++) { if (pLength->GetValue(index) == sequenceLen) { uint8_t *seq; uint32_t seqlen; pUnit->GetValue(&seq, &seqlen, index); if (memcmp(seq, pSequence, sequenceLen) == 0) { free(seq); return; } free(seq); } } } pLength->AddValue(sequenceLen); pUnit->AddValue(pSequence, sequenceLen); pCount->IncrementValue(); return; } void MP4File::AddH264PictureParameterSet (MP4TrackId trackId, const uint8_t *pPict, uint16_t pictLen) { MP4Atom *avcCAtom = FindAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.avc1.avcC")); MP4Integer8Property *pCount; MP4Integer16Property *pLength; MP4BytesProperty *pUnit; if ((avcCAtom->FindProperty("avcC.numOfPictureParameterSets", (MP4Property **)&pCount) == false) || (avcCAtom->FindProperty("avcC.pictureEntries.pictureParameterSetLength", (MP4Property **)&pLength) == false) || (avcCAtom->FindProperty("avcC.pictureEntries.pictureParameterSetNALUnit", (MP4Property **)&pUnit) == false)) { log.errorf("%s: \"%s\": Could not find avcC picture table properties", __FUNCTION__, GetFilename().c_str()); return; } ASSERT(pCount); uint32_t count = pCount->GetValue(); if (count > 0) { // see if we already exist for (uint32_t index = 0; index < count; index++) { if (pLength->GetValue(index) == pictLen) { uint8_t *seq; uint32_t seqlen; pUnit->GetValue(&seq, &seqlen, index); if (memcmp(seq, pPict, pictLen) == 0) { log.verbose1f("\"%s\": picture matches %d", GetFilename().c_str(), index); free(seq); return; } free(seq); } } } pLength->AddValue(pictLen); pUnit->AddValue(pPict, pictLen); pCount->IncrementValue(); log.verbose1f("\"%s\": new picture added %d", GetFilename().c_str(), pCount->GetValue()); return; } void MP4File::SetH263Vendor( MP4TrackId trackId, uint32_t vendor) { SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.s263.d263.vendor", vendor); } void MP4File::SetH263DecoderVersion( MP4TrackId trackId, uint8_t decoderVersion) { SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.s263.d263.decoderVersion", decoderVersion); } void MP4File::SetH263Bitrates( MP4TrackId trackId, uint32_t avgBitrate, uint32_t maxBitrate) { SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.s263.d263.bitr.avgBitrate", avgBitrate); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.s263.d263.bitr.maxBitrate", maxBitrate); } MP4TrackId MP4File::AddH263VideoTrack( uint32_t timeScale, MP4Duration sampleDuration, uint16_t width, uint16_t height, uint8_t h263Level, uint8_t h263Profile, uint32_t avgBitrate, uint32_t maxBitrate) { MP4TrackId trackId = AddVideoTrackDefault(timeScale, sampleDuration, width, height, "s263"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.s263.width", width); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.s263.height", height); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.s263.d263.h263Level", h263Level); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.s263.d263.h263Profile", h263Profile); // Add the bitr atom (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.s263.d263"), "bitr"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.s263.d263.bitr.avgBitrate", avgBitrate); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.s263.d263.bitr.maxBitrate", maxBitrate); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsz.sampleSize", sampleDuration); return trackId; } MP4TrackId MP4File::AddHintTrack(MP4TrackId refTrackId) { // validate reference track id (void)FindTrackIndex(refTrackId); MP4TrackId trackId = AddTrack(MP4_HINT_TRACK_TYPE, GetTrackTimeScale(refTrackId)); (void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "hmhd", 0); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "rtp "); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the rtp atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.rtp .tims.timeScale", GetTrackTimeScale(trackId)); (void)AddDescendantAtoms(MakeTrackName(trackId, NULL), "tref.hint"); AddTrackReference(MakeTrackName(trackId, "tref.hint"), refTrackId); (void)AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.hnti.sdp "); (void)AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.hinf"); return trackId; } MP4TrackId MP4File::AddTextTrack(MP4TrackId refTrackId) { // validate reference track id (void)FindTrackIndex(refTrackId); MP4TrackId trackId = AddTrack(MP4_TEXT_TRACK_TYPE, GetTrackTimeScale(refTrackId)); (void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "gmhd", 0); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "text"); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the text atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); return trackId; } MP4TrackId MP4File::AddSubtitleTrack(uint32_t timescale, uint16_t width, uint16_t height) { MP4TrackId trackId = AddTrack(MP4_SUBTITLE_TRACK_TYPE, timescale); InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "nmhd", 0); AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "tx3g"); SetTrackFloatProperty(trackId, "tkhd.width", width); SetTrackFloatProperty(trackId, "tkhd.height", height); // Hardcoded crap... add the ftab atom and add one font entry MP4Atom* pFtabAtom = AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.tx3g"), "ftab"); ((MP4Integer16Property*)pFtabAtom->GetProperty(0))->IncrementValue(); MP4Integer16Property* pfontID = (MP4Integer16Property*)((MP4TableProperty*)pFtabAtom->GetProperty(1))->GetProperty(0); pfontID->AddValue(1); MP4StringProperty* pName = (MP4StringProperty*)((MP4TableProperty*)pFtabAtom->GetProperty(1))->GetProperty(1); pName->AddValue("Arial"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.tx3g.fontID", 1); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the tx3g atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); return trackId; } MP4TrackId MP4File::AddSubpicTrack(uint32_t timescale, uint16_t width, uint16_t height) { MP4TrackId trackId = AddTrack(MP4_SUBPIC_TRACK_TYPE, timescale); InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "nmhd", 0); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "mp4s"); SetTrackFloatProperty(trackId, "tkhd.width", width); SetTrackFloatProperty(trackId, "tkhd.height", height); SetTrackIntegerProperty(trackId, "tkhd.layer", 0); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the mp4s atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4s.esds.ESID", 0 ); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr.objectTypeId", MP4SubpicObjectType); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr.streamType", MP4NeroSubpicStreamType); return trackId; } MP4TrackId MP4File::AddChapterTextTrack(MP4TrackId refTrackId, uint32_t timescale) { // validate reference track id (void)FindTrackIndex(refTrackId); if (0 == timescale) { timescale = GetTrackTimeScale(refTrackId); } MP4TrackId trackId = AddTrack(MP4_TEXT_TRACK_TYPE, timescale); (void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "gmhd", 0); (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "text"); // stsd is a unique beast in that it has a count of the number // of child atoms that needs to be incremented after we add the text atom MP4Integer32Property* pStsdCountProperty; FindIntegerProperty( MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"), (MP4Property**)&pStsdCountProperty); pStsdCountProperty->IncrementValue(); // add a "text" atom to the generic media header // this is different to the stsd "text" atom added above // truth be told, it's not clear what this second "text" atom does, // but all iTunes Store movies (with chapter markers) have it, // as do all movies with chapter tracks made by hand in QuickTime Pro (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.gmhd"), "text"); // disable the chapter text track // it won't display anyway, as it has zero display size, // but nonetheless it's good to disable it // the track still operates as a chapter track when disabled MP4Atom *pTkhdAtom = FindAtom(MakeTrackName(trackId, "tkhd")); if (pTkhdAtom) { pTkhdAtom->SetFlags(0xE); } // add a "chapter" track reference to our reference track, // pointing to this new chapter track (void)AddDescendantAtoms(MakeTrackName(refTrackId, NULL), "tref.chap"); AddTrackReference(MakeTrackName(refTrackId, "tref.chap"), trackId); return trackId; } MP4TrackId MP4File::AddPixelAspectRatio(MP4TrackId trackId, uint32_t hSpacing, uint32_t vSpacing) { // validate reference track id (void)FindTrackIndex(trackId); const char *format = GetTrackMediaDataName (trackId); if (!strcasecmp(format, "avc1")) { (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.avc1"), "pasp"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.pasp.hSpacing", hSpacing); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.pasp.vSpacing", vSpacing); } else if (!strcasecmp(format, "mp4v")) { (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.mp4v"), "pasp"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.pasp.hSpacing", hSpacing); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.pasp.vSpacing", vSpacing); } return trackId; } MP4TrackId MP4File::AddColr(MP4TrackId trackId, uint16_t primariesIndex, uint16_t transferFunctionIndex, uint16_t matrixIndex) { // validate reference track id (void)FindTrackIndex(trackId); const char *format = GetTrackMediaDataName (trackId); if (!strcasecmp(format, "avc1")) { AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.avc1"), "colr"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.colr.primariesIndex", primariesIndex); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.colr.transferFunctionIndex", transferFunctionIndex); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.avc1.colr.matrixIndex", matrixIndex); } else if (!strcasecmp(format, "mp4v")) { AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.mp4v"), "colr"); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.colr.primariesIndex", primariesIndex); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.colr.transferFunctionIndex", transferFunctionIndex); SetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.mp4v.colr.matrixIndex", matrixIndex); } return trackId; } void MP4File::AddChapter(MP4TrackId chapterTrackId, MP4Duration chapterDuration, const char *chapterTitle) { if (MP4_INVALID_TRACK_ID == chapterTrackId) { throw new Exception("No chapter track given",__FILE__, __LINE__, __FUNCTION__); } uint32_t sampleLength = 0; uint8_t sample[1040] = {0}; int textLen = 0; char *text = (char *)&(sample[2]); if(chapterTitle != NULL) { textLen = min((uint32_t)strlen(chapterTitle), (uint32_t)MP4V2_CHAPTER_TITLE_MAX); if (0 < textLen) { strncpy(text, chapterTitle, textLen); } } else { MP4Track * pChapterTrack = GetTrack(chapterTrackId); snprintf( text, 1023, "Chapter %03d", pChapterTrack->GetNumberOfSamples() + 1 ); textLen = (uint32_t)strlen(text); } sampleLength = textLen + 2 + 12; // Account for text length code and other marker // 2-byte length marker sample[0] = (textLen >> 8) & 0xff; sample[1] = textLen & 0xff; int x = 2 + textLen; // Modifier Length Marker sample[x] = 0x00; sample[x+1] = 0x00; sample[x+2] = 0x00; sample[x+3] = 0x0C; // Modifier Type Code sample[x+4] = 'e'; sample[x+5] = 'n'; sample[x+6] = 'c'; sample[x+7] = 'd'; // Modifier Value sample[x+8] = 0x00; sample[x+9] = 0x00; sample[x+10] = (256 >> 8) & 0xff; sample[x+11] = 256 & 0xff; WriteSample(chapterTrackId, sample, sampleLength, chapterDuration); } void MP4File::AddNeroChapter(MP4Timestamp chapterStart, const char * chapterTitle) { MP4Atom * pChpl = FindAtom("moov.udta.chpl"); if (!pChpl) { pChpl = AddDescendantAtoms("", "moov.udta.chpl"); } MP4Integer32Property * pCount = (MP4Integer32Property*)pChpl->GetProperty(3); pCount->IncrementValue(); char buffer[256]; if (0 == chapterTitle) { snprintf( buffer, 255, "Chapter %03d", pCount->GetValue() ); } else { int len = min((uint32_t)strlen(chapterTitle), (uint32_t)255); strncpy( buffer, chapterTitle, len ); buffer[len] = 0; } MP4TableProperty * pTable; if (pChpl->FindProperty("chpl.chapters", (MP4Property **)&pTable)) { MP4Integer64Property * pStartTime = (MP4Integer64Property *) pTable->GetProperty(0); MP4StringProperty * pName = (MP4StringProperty *) pTable->GetProperty(1); if (pStartTime && pTable) { pStartTime->AddValue(chapterStart); pName->AddValue(buffer); } } } MP4TrackId MP4File::FindChapterReferenceTrack(MP4TrackId chapterTrackId, char * trackName, int trackNameSize) { for (uint32_t i = 0; i < m_pTracks.Size(); i++) { if( MP4_IS_VIDEO_TRACK_TYPE( m_pTracks[i]->GetType() ) || MP4_IS_AUDIO_TRACK_TYPE( m_pTracks[i]->GetType() ) ) { MP4TrackId refTrackId = m_pTracks[i]->GetId(); char *name = MakeTrackName(refTrackId, "tref.chap"); if( FindTrackReference( name, chapterTrackId ) ) { if( 0 != trackName ) { int nameLen = min((uint32_t)strlen(name), (uint32_t)trackNameSize); strncpy(trackName, name, nameLen); trackName[nameLen] = 0; } return m_pTracks[i]->GetId(); } } } return MP4_INVALID_TRACK_ID; } MP4TrackId MP4File::FindChapterTrack(char * trackName, int trackNameSize) { for (uint32_t i = 0; i < m_pTracks.Size(); i++) { if( !strcasecmp(MP4_TEXT_TRACK_TYPE, m_pTracks[i]->GetType()) ) { MP4TrackId refTrackId = FindChapterReferenceTrack(m_pTracks[i]->GetId(), trackName, trackNameSize); if (MP4_INVALID_TRACK_ID != refTrackId) { return m_pTracks[i]->GetId(); } } } return MP4_INVALID_TRACK_ID; } MP4ChapterType MP4File::DeleteChapters(MP4ChapterType chapterType, MP4TrackId chapterTrackId) { MP4ChapterType deletedType = MP4ChapterTypeNone; if (MP4ChapterTypeAny == chapterType || MP4ChapterTypeNero == chapterType) { MP4Atom * pChpl = FindAtom("moov.udta.chpl"); if (pChpl) { MP4Atom * pParent = pChpl->GetParentAtom(); pParent->DeleteChildAtom(pChpl); deletedType = MP4ChapterTypeNero; } } if (MP4ChapterTypeAny == chapterType || MP4ChapterTypeQt == chapterType) { char trackName[128] = {0}; // no text track given, find a suitable if (MP4_INVALID_TRACK_ID == chapterTrackId) { chapterTrackId = FindChapterTrack(trackName, 127); } if (MP4_INVALID_TRACK_ID != chapterTrackId) { FindChapterReferenceTrack(chapterTrackId, trackName, 127); } if (MP4_INVALID_TRACK_ID != chapterTrackId && 0 != trackName[0]) { // remove the reference MP4Atom * pChap = FindAtom( trackName ); if( pChap ) { MP4Atom * pTref = pChap->GetParentAtom(); if( pTref ) { pTref->DeleteChildAtom( pChap ); MP4Atom* pParent = pTref->GetParentAtom(); pParent->DeleteChildAtom( pTref ); } } // remove the chapter track DeleteTrack(chapterTrackId); deletedType = MP4ChapterTypeNone == deletedType ? MP4ChapterTypeQt : MP4ChapterTypeAny; } } return deletedType; } MP4ChapterType MP4File::GetChapters(MP4Chapter_t ** chapterList, uint32_t * chapterCount, MP4ChapterType fromChapterType) { *chapterList = 0; *chapterCount = 0; if (MP4ChapterTypeAny == fromChapterType || MP4ChapterTypeQt == fromChapterType) { uint8_t * sample = 0; uint32_t sampleSize = 0; MP4Timestamp startTime = 0; MP4Duration duration = 0; // get the chapter track MP4TrackId chapterTrackId = FindChapterTrack(); if (MP4_INVALID_TRACK_ID == chapterTrackId) { if (MP4ChapterTypeQt == fromChapterType) { return MP4ChapterTypeNone; } } else { // get infos about the chapters MP4Track * pChapterTrack = GetTrack(chapterTrackId); uint32_t counter = pChapterTrack->GetNumberOfSamples(); if (0 < counter) { uint32_t timescale = pChapterTrack->GetTimeScale(); MP4Chapter_t * chapters = (MP4Chapter_t*)MP4Malloc(sizeof(MP4Chapter_t) * counter); // process all chapter sample for (uint32_t i = 0; i < counter; ++i) { // get the sample corresponding to the starttime MP4SampleId sampleId = pChapterTrack->GetSampleIdFromTime(startTime + duration, true); pChapterTrack->ReadSample(sampleId, &sample, &sampleSize); // get the starttime and duration pChapterTrack->GetSampleTimes(sampleId, &startTime, &duration); // we know that sample+2 contains the title (sample[0] and sample[1] is the length) const char * title = (const char *)&(sample[2]); int titleLen = min((uint32_t)((sample[0] << 8) | sample[1]), (uint32_t)MP4V2_CHAPTER_TITLE_MAX); strncpy(chapters[i].title, title, titleLen); chapters[i].title[titleLen] = 0; // write the duration (in milliseconds) chapters[i].duration = MP4ConvertTime(duration, timescale, MP4_MILLISECONDS_TIME_SCALE); // we're done with this sample MP4Free(sample); sample = 0; } *chapterList = chapters; *chapterCount = counter; // we got chapters so we are done return MP4ChapterTypeQt; } } } if (MP4ChapterTypeAny == fromChapterType || MP4ChapterTypeNero == fromChapterType) { MP4Atom * pChpl = FindAtom("moov.udta.chpl"); if (!pChpl) { return MP4ChapterTypeNone; } MP4Integer32Property * pCounter = 0; if (!pChpl->FindProperty("chpl.chaptercount", (MP4Property **)&pCounter)) { log.warningf("%s: \"%s\": Nero chapter count does not exist", __FUNCTION__, GetFilename().c_str()); return MP4ChapterTypeNone; } uint32_t counter = pCounter->GetValue(); if (0 == counter) { log.warningf("%s: \"%s\": No Nero chapters available", __FUNCTION__, GetFilename().c_str()); return MP4ChapterTypeNone; } MP4TableProperty * pTable = 0; MP4Integer64Property * pStartTime = 0; MP4StringProperty * pName = 0; MP4Duration chapterDurationSum = 0; const char * name = 0; if (!pChpl->FindProperty("chpl.chapters", (MP4Property **)&pTable)) { log.warningf("%s: \"%s\": Nero chapter list does not exist", __FUNCTION__, GetFilename().c_str()); return MP4ChapterTypeNone; } if (0 == (pStartTime = (MP4Integer64Property *) pTable->GetProperty(0))) { log.warningf("%s: \"%s\": List of Chapter starttimes does not exist", __FUNCTION__, GetFilename().c_str()); return MP4ChapterTypeNone; } if (0 == (pName = (MP4StringProperty *) pTable->GetProperty(1))) { log.warningf("%s: \"%s\": List of Chapter titles does not exist", __FUNCTION__, GetFilename().c_str()); return MP4ChapterTypeNone; } MP4Chapter_t * chapters = (MP4Chapter_t*)MP4Malloc(sizeof(MP4Chapter_t) * counter); // get the name of the first chapter name = pName->GetValue(); // process remaining chapters uint32_t i, j; for (i = 0, j = 1; i < counter; ++i, ++j) { // insert the chapter title uint32_t len = min((uint32_t)strlen(name), (uint32_t)MP4V2_CHAPTER_TITLE_MAX); strncpy(chapters[i].title, name, len); chapters[i].title[len] = 0; // calculate the duration MP4Duration duration = 0; if (j < counter) { duration = MP4ConvertTime(pStartTime->GetValue(j), (MP4_NANOSECONDS_TIME_SCALE / 100), MP4_MILLISECONDS_TIME_SCALE) - chapterDurationSum; // now get the name of the chapter (to be written next) name = pName->GetValue(j); } else { // last chapter duration = MP4ConvertTime(GetDuration(), GetTimeScale(), MP4_MILLISECONDS_TIME_SCALE) - chapterDurationSum; } // sum up the chapter duration chapterDurationSum += duration; // insert the chapter duration chapters[i].duration = duration; } *chapterList = chapters; *chapterCount = counter; return MP4ChapterTypeNero; } return MP4ChapterTypeNone; } MP4ChapterType MP4File::SetChapters(MP4Chapter_t * chapterList, uint32_t chapterCount, MP4ChapterType toChapterType) { MP4ChapterType setType = MP4ChapterTypeNone; // first remove any existing chapters DeleteChapters(toChapterType, MP4_INVALID_TRACK_ID); if( MP4ChapterTypeAny == toChapterType || MP4ChapterTypeNero == toChapterType ) { MP4Duration duration = 0; for( uint32_t i = 0; i < chapterCount; ++i ) { AddNeroChapter(duration, chapterList[i].title); duration += 10 * MP4_MILLISECONDS_TIME_SCALE * chapterList[i].duration; } setType = MP4ChapterTypeNero; } if (MP4ChapterTypeAny == toChapterType || MP4ChapterTypeQt == toChapterType) { // find the first video or audio track MP4TrackId refTrack = MP4_INVALID_TRACK_ID; for( uint32_t i = 0; i < m_pTracks.Size(); i++ ) { if( MP4_IS_VIDEO_TRACK_TYPE( m_pTracks[i]->GetType() ) || MP4_IS_AUDIO_TRACK_TYPE( m_pTracks[i]->GetType() ) ) { refTrack = m_pTracks[i]->GetId(); break; } } if( refTrack == MP4_INVALID_TRACK_ID ) { return setType; } // create the chapter track MP4TrackId chapterTrack = AddChapterTextTrack(refTrack, MP4_MILLISECONDS_TIME_SCALE); for( uint32_t i = 0 ; i < chapterCount; ++i ) { // create and write the chapter track sample AddChapter( chapterTrack, chapterList[i].duration, chapterList[i].title ); } setType = MP4ChapterTypeNone == setType ? MP4ChapterTypeQt : MP4ChapterTypeAny; } return setType; } MP4ChapterType MP4File::ConvertChapters(MP4ChapterType toChapterType) { MP4ChapterType sourceType = MP4ChapterTypeNone; const char* errMsg = 0; if( MP4ChapterTypeQt == toChapterType ) { sourceType = MP4ChapterTypeNero; errMsg = "Could not find Nero chapter markers"; } else if( MP4ChapterTypeNero == toChapterType ) { sourceType = MP4ChapterTypeQt; errMsg = "Could not find QuickTime chapter markers"; } else { return MP4ChapterTypeNone; } MP4Chapter_t * chapters = 0; uint32_t chapterCount = 0; GetChapters(&chapters, &chapterCount, sourceType); if (0 == chapterCount) { log.warningf("%s: \"%s\": %s", __FUNCTION__, GetFilename().c_str(), errMsg); return MP4ChapterTypeNone; } SetChapters(chapters, chapterCount, toChapterType); MP4Free(chapters); return toChapterType; } void MP4File::ChangeMovieTimeScale(uint32_t timescale) { uint32_t origTimeScale = GetTimeScale(); if (timescale == origTimeScale) { // already done return; } MP4Duration movieDuration = GetDuration(); // set movie header timescale and duration SetTimeScale(timescale); SetDuration(MP4ConvertTime(movieDuration, origTimeScale, timescale)); // set track header duration (calculated with movie header timescale) uint32_t trackCount = GetNumberOfTracks(); for (uint32_t i = 0; i < trackCount; ++i) { MP4Track * track = GetTrack(FindTrackId(i)); MP4Atom & trackAtom = track->GetTrakAtom(); MP4IntegerProperty * duration; if (trackAtom.FindProperty("trak.tkhd.duration", (MP4Property**)&duration)) { duration->SetValue(MP4ConvertTime(duration->GetValue(), origTimeScale, timescale)); } } } void MP4File::DeleteTrack(MP4TrackId trackId) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); uint32_t trakIndex = FindTrakAtomIndex(trackId); uint16_t trackIndex = FindTrackIndex(trackId); MP4Track* pTrack = m_pTracks[trackIndex]; MP4Atom& trakAtom = pTrack->GetTrakAtom(); MP4Atom* pMoovAtom = FindAtom("moov"); ASSERT(pMoovAtom); RemoveTrackFromIod(trackId, ShallHaveIods()); RemoveTrackFromOd(trackId); if (trackId == m_odTrackId) { m_odTrackId = 0; } pMoovAtom->DeleteChildAtom(&trakAtom); m_trakIds.Delete(trakIndex); m_pTracks.Delete(trackIndex); delete pTrack; delete &trakAtom; } uint32_t MP4File::GetNumberOfTracks(const char* type, uint8_t subType) { if (type == NULL) { return m_pTracks.Size(); } uint32_t typeSeen = 0; const char* normType = MP4NormalizeTrackType(type); for (uint32_t i = 0; i < m_pTracks.Size(); i++) { if (!strcmp(normType, m_pTracks[i]->GetType())) { if (subType) { if (strcmp(normType, MP4_AUDIO_TRACK_TYPE) == 0) { if (subType != GetTrackEsdsObjectTypeId(m_pTracks[i]->GetId())) { continue; } } else if (strcmp(normType, MP4_VIDEO_TRACK_TYPE)) { if (subType != GetTrackEsdsObjectTypeId(m_pTracks[i]->GetId())) { continue; } } // else unknown subtype, ignore it } typeSeen++; } } return typeSeen; } MP4TrackId MP4File::AllocTrackId() { MP4TrackId trackId = GetIntegerProperty("moov.mvhd.nextTrackId"); if (trackId <= 0xFFFF) { // check that nextTrackid is correct try { (void)FindTrackIndex(trackId); // ERROR, this trackId is in use } catch (Exception* x) { // OK, this trackId is not in use, proceed delete x; SetIntegerProperty("moov.mvhd.nextTrackId", trackId + 1); return trackId; } } // we need to search for a track id for (trackId = 1; trackId <= 0xFFFF; trackId++) { try { (void)FindTrackIndex(trackId); // KEEP LOOKING, this trackId is in use } catch (Exception* x) { // OK, this trackId is not in use, proceed delete x; return trackId; } } // extreme case where mp4 file has 2^16 tracks in it throw new Exception("too many existing tracks", __FILE__, __LINE__, __FUNCTION__); return MP4_INVALID_TRACK_ID; // to keep MSVC happy } MP4TrackId MP4File::FindTrackId(uint16_t trackIndex, const char* type, uint8_t subType) { if (type == NULL) { return m_pTracks[trackIndex]->GetId(); } uint32_t typeSeen = 0; const char* normType = MP4NormalizeTrackType(type); for (uint32_t i = 0; i < m_pTracks.Size(); i++) { if (!strcmp(normType, m_pTracks[i]->GetType())) { if (subType) { if (strcmp(normType, MP4_AUDIO_TRACK_TYPE) == 0) { if (subType != GetTrackEsdsObjectTypeId(m_pTracks[i]->GetId())) { continue; } } else if (strcmp(normType, MP4_VIDEO_TRACK_TYPE) == 0) { if (subType != GetTrackEsdsObjectTypeId(m_pTracks[i]->GetId())) { continue; } } // else unknown subtype, ignore it } if (trackIndex == typeSeen) { return m_pTracks[i]->GetId(); } typeSeen++; } } ostringstream msg; msg << "Track index doesn't exist - track " << trackIndex << " type " << type; throw new Exception(msg.str(),__FILE__, __LINE__, __FUNCTION__); return MP4_INVALID_TRACK_ID; // satisfy MS compiler } uint16_t MP4File::FindTrackIndex(MP4TrackId trackId) { for (uint32_t i = 0; i < m_pTracks.Size() && i <= 0xFFFF; i++) { if (m_pTracks[i]->GetId() == trackId) { return (uint16_t)i; } } ostringstream msg; msg << "Track id " << trackId << " doesn't exist"; throw new Exception(msg.str(),__FILE__, __LINE__, __FUNCTION__); return (uint16_t)-1; // satisfy MS compiler } uint16_t MP4File::FindTrakAtomIndex(MP4TrackId trackId) { if (trackId) { for (uint32_t i = 0; i < m_trakIds.Size(); i++) { if (m_trakIds[i] == trackId) { return i; } } } ostringstream msg; msg << "Track id " << trackId << " doesn't exist"; throw new Exception(msg.str(),__FILE__, __LINE__, __FUNCTION__); return (uint16_t)-1; // satisfy MS compiler } uint32_t MP4File::GetSampleSize(MP4TrackId trackId, MP4SampleId sampleId) { return m_pTracks[FindTrackIndex(trackId)]->GetSampleSize(sampleId); } uint32_t MP4File::GetTrackMaxSampleSize(MP4TrackId trackId) { return m_pTracks[FindTrackIndex(trackId)]->GetMaxSampleSize(); } MP4SampleId MP4File::GetSampleIdFromTime(MP4TrackId trackId, MP4Timestamp when, bool wantSyncSample) { return m_pTracks[FindTrackIndex(trackId)]-> GetSampleIdFromTime(when, wantSyncSample); } MP4Timestamp MP4File::GetSampleTime( MP4TrackId trackId, MP4SampleId sampleId) { MP4Timestamp timestamp; m_pTracks[FindTrackIndex(trackId)]-> GetSampleTimes(sampleId, ×tamp, NULL); return timestamp; } MP4Duration MP4File::GetSampleDuration( MP4TrackId trackId, MP4SampleId sampleId) { MP4Duration duration; m_pTracks[FindTrackIndex(trackId)]-> GetSampleTimes(sampleId, NULL, &duration); return duration; } MP4Duration MP4File::GetSampleRenderingOffset( MP4TrackId trackId, MP4SampleId sampleId) { return m_pTracks[FindTrackIndex(trackId)]-> GetSampleRenderingOffset(sampleId); } bool MP4File::GetSampleSync(MP4TrackId trackId, MP4SampleId sampleId) { return m_pTracks[FindTrackIndex(trackId)]->IsSyncSample(sampleId); } void MP4File::ReadSample( MP4TrackId trackId, MP4SampleId sampleId, uint8_t** ppBytes, uint32_t* pNumBytes, MP4Timestamp* pStartTime, MP4Duration* pDuration, MP4Duration* pRenderingOffset, bool* pIsSyncSample, bool* hasDependencyFlags, uint32_t* dependencyFlags ) { m_pTracks[FindTrackIndex(trackId)]->ReadSample( sampleId, ppBytes, pNumBytes, pStartTime, pDuration, pRenderingOffset, pIsSyncSample, hasDependencyFlags, dependencyFlags ); } void MP4File::WriteSample( MP4TrackId trackId, const uint8_t* pBytes, uint32_t numBytes, MP4Duration duration, MP4Duration renderingOffset, bool isSyncSample ) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); m_pTracks[FindTrackIndex(trackId)]->WriteSample( pBytes, numBytes, duration, renderingOffset, isSyncSample ); m_pModificationProperty->SetValue( MP4GetAbsTimestamp() ); } void MP4File::WriteSampleDependency( MP4TrackId trackId, const uint8_t* pBytes, uint32_t numBytes, MP4Duration duration, MP4Duration renderingOffset, bool isSyncSample, uint32_t dependencyFlags ) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); m_pTracks[FindTrackIndex(trackId)]->WriteSampleDependency( pBytes, numBytes, duration, renderingOffset, isSyncSample, dependencyFlags ); m_pModificationProperty->SetValue( MP4GetAbsTimestamp() ); } void MP4File::SetSampleRenderingOffset(MP4TrackId trackId, MP4SampleId sampleId, MP4Duration renderingOffset) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); m_pTracks[FindTrackIndex(trackId)]-> SetSampleRenderingOffset(sampleId, renderingOffset); m_pModificationProperty->SetValue(MP4GetAbsTimestamp()); } void MP4File::MakeFtypAtom( char* majorBrand, uint32_t minorVersion, char** compatibleBrands, uint32_t compatibleBrandsCount) { MP4FtypAtom* ftyp = (MP4FtypAtom*)m_pRootAtom->FindAtom( "ftyp" ); if (ftyp == NULL) ftyp = (MP4FtypAtom*)InsertChildAtom( m_pRootAtom, "ftyp", 0 ); // bail if majorbrand is not specified; defaults suffice. if (majorBrand == NULL) return; ftyp->majorBrand.SetValue( majorBrand ); ftyp->minorVersion.SetValue( minorVersion ); ftyp->compatibleBrands.SetCount( compatibleBrandsCount ); for( uint32_t i = 0; i < compatibleBrandsCount; i++ ) ftyp->compatibleBrands.SetValue( compatibleBrands[i], i ); } char* MP4File::MakeTrackName(MP4TrackId trackId, const char* name) { uint16_t trakIndex = FindTrakAtomIndex(trackId); if (name == NULL || name[0] == '\0') { snprintf(m_trakName, sizeof(m_trakName), "moov.trak[%u]", trakIndex); } else { snprintf(m_trakName, sizeof(m_trakName), "moov.trak[%u].%s", trakIndex, name); } return m_trakName; } MP4Atom *MP4File::FindTrackAtom (MP4TrackId trackId, const char *name) { return FindAtom(MakeTrackName(trackId, name)); } uint64_t MP4File::GetTrackIntegerProperty(MP4TrackId trackId, const char* name) { return GetIntegerProperty(MakeTrackName(trackId, name)); } void MP4File::SetTrackIntegerProperty(MP4TrackId trackId, const char* name, int64_t value) { SetIntegerProperty(MakeTrackName(trackId, name), value); } float MP4File::GetTrackFloatProperty(MP4TrackId trackId, const char* name) { return GetFloatProperty(MakeTrackName(trackId, name)); } void MP4File::SetTrackFloatProperty(MP4TrackId trackId, const char* name, float value) { SetFloatProperty(MakeTrackName(trackId, name), value); } const char* MP4File::GetTrackStringProperty(MP4TrackId trackId, const char* name) { return GetStringProperty(MakeTrackName(trackId, name)); } void MP4File::SetTrackStringProperty(MP4TrackId trackId, const char* name, const char* value) { SetStringProperty(MakeTrackName(trackId, name), value); } void MP4File::GetTrackBytesProperty(MP4TrackId trackId, const char* name, uint8_t** ppValue, uint32_t* pValueSize) { GetBytesProperty(MakeTrackName(trackId, name), ppValue, pValueSize); } void MP4File::SetTrackBytesProperty(MP4TrackId trackId, const char* name, const uint8_t* pValue, uint32_t valueSize) { SetBytesProperty(MakeTrackName(trackId, name), pValue, valueSize); } bool MP4File::GetTrackLanguage( MP4TrackId trackId, char* code ) { ostringstream oss; oss << "moov.trak[" << FindTrakAtomIndex(trackId) << "].mdia.mdhd.language"; MP4Property* prop; if( !m_pRootAtom->FindProperty( oss.str().c_str(), &prop )) return false; if( prop->GetType() != LanguageCodeProperty ) return false; MP4LanguageCodeProperty& lang = *static_cast(prop); string slang; bmff::enumLanguageCode.toString( lang.GetValue(), slang ); if( slang.length() != 3 ) { memset( code, '\0', 4 ); } else { memcpy( code, slang.c_str(), 3 ); code[3] = '\0'; } return true; } bool MP4File::SetTrackLanguage( MP4TrackId trackId, const char* code ) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); ostringstream oss; oss << "moov.trak[" << FindTrakAtomIndex(trackId) << "].mdia.mdhd.language"; MP4Property* prop; if( !m_pRootAtom->FindProperty( oss.str().c_str(), &prop )) return false; if( prop->GetType() != LanguageCodeProperty ) return false; MP4LanguageCodeProperty& lang = *static_cast(prop); lang.SetValue( bmff::enumLanguageCode.toType( code )); return true; } bool MP4File::GetTrackName( MP4TrackId trackId, char** name ) { unsigned char *val = NULL; uint32_t valSize = 0; MP4Atom *pMetaAtom; pMetaAtom = m_pRootAtom->FindAtom(MakeTrackName(trackId,"udta.name")); if (pMetaAtom) { GetBytesProperty(MakeTrackName(trackId,"udta.name.value"), (uint8_t**)&val, &valSize); } if (valSize > 0) { *name = (char*)malloc((valSize+1)*sizeof(char)); if (*name == NULL) { free(val); return false; } memcpy(*name, val, valSize*sizeof(unsigned char)); free(val); (*name)[valSize] = '\0'; return true; } return false; } bool MP4File::SetTrackName( MP4TrackId trackId, const char* name ) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); char atomstring[40]; MP4Atom *pMetaAtom; MP4BytesProperty *pMetadataProperty = NULL; snprintf(atomstring, 40, "%s", MakeTrackName(trackId,"udta.name")); pMetaAtom = m_pRootAtom->FindAtom(atomstring); if (!pMetaAtom) { if (!AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.name")) return false; pMetaAtom = m_pRootAtom->FindAtom(atomstring); if (pMetaAtom == NULL) return false; } ASSERT(pMetaAtom->FindProperty("name.value", (MP4Property**)&pMetadataProperty)); ASSERT(pMetadataProperty); pMetadataProperty->SetValue((uint8_t*)name, (uint32_t)strlen(name)); return true; } // file level convenience functions MP4Duration MP4File::GetDuration() { return m_pDurationProperty->GetValue(); } void MP4File::SetDuration(MP4Duration value) { m_pDurationProperty->SetValue(value); } uint32_t MP4File::GetTimeScale() { return m_pTimeScaleProperty->GetValue(); } void MP4File::SetTimeScale(uint32_t value) { if (value == 0) { throw new Exception("invalid value", __FILE__, __LINE__, __FUNCTION__); } m_pTimeScaleProperty->SetValue(value); } uint8_t MP4File::GetODProfileLevel() { return GetIntegerProperty("moov.iods.ODProfileLevelId"); } void MP4File::SetODProfileLevel(uint8_t value) { SetIntegerProperty("moov.iods.ODProfileLevelId", value); } uint8_t MP4File::GetSceneProfileLevel() { return GetIntegerProperty("moov.iods.sceneProfileLevelId"); } void MP4File::SetSceneProfileLevel(uint8_t value) { SetIntegerProperty("moov.iods.sceneProfileLevelId", value); } uint8_t MP4File::GetVideoProfileLevel() { return GetIntegerProperty("moov.iods.visualProfileLevelId"); } void MP4File::SetVideoProfileLevel(uint8_t value) { SetIntegerProperty("moov.iods.visualProfileLevelId", value); } uint8_t MP4File::GetAudioProfileLevel() { return GetIntegerProperty("moov.iods.audioProfileLevelId"); } void MP4File::SetAudioProfileLevel(uint8_t value) { SetIntegerProperty("moov.iods.audioProfileLevelId", value); } uint8_t MP4File::GetGraphicsProfileLevel() { return GetIntegerProperty("moov.iods.graphicsProfileLevelId"); } void MP4File::SetGraphicsProfileLevel(uint8_t value) { SetIntegerProperty("moov.iods.graphicsProfileLevelId", value); } const char* MP4File::GetSessionSdp() { return GetStringProperty("moov.udta.hnti.rtp .sdpText"); } void MP4File::SetSessionSdp(const char* sdpString) { (void)AddDescendantAtoms("moov", "udta.hnti.rtp "); SetStringProperty("moov.udta.hnti.rtp .sdpText", sdpString); } void MP4File::AppendSessionSdp(const char* sdpFragment) { const char* oldSdpString = NULL; try { oldSdpString = GetSessionSdp(); } catch (Exception* x) { delete x; SetSessionSdp(sdpFragment); return; } char* newSdpString = (char*)MP4Malloc(strlen(oldSdpString) + strlen(sdpFragment) + 1); strcpy(newSdpString, oldSdpString); strcat(newSdpString, sdpFragment); SetSessionSdp(newSdpString); MP4Free(newSdpString); } // // ismacrypt API - retrieve OriginalFormatBox // // parameters are assumed to have been sanity tested in mp4.cpp // don't call this unless media data name is 'encv', // results may otherwise be unpredictable. // // input: // trackID - valid encv track ID for this file // buflen - length of oFormat, minimum is 5 (4cc plus null terminator) // // output: // oFormat - buffer to return null terminated string containing // track original format // return: // 0 - original format returned OK // 1 - buffer length error or problem retrieving track property // // bool MP4File::GetTrackMediaDataOriginalFormat(MP4TrackId trackId, char *originalFormat, uint32_t buflen) { uint32_t format; if (buflen < 5) return false; format = GetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.*.sinf.frma.data-format"); IDATOM(format, originalFormat); return true; } // track level convenience functions MP4SampleId MP4File::GetTrackNumberOfSamples(MP4TrackId trackId) { return m_pTracks[FindTrackIndex(trackId)]->GetNumberOfSamples(); } const char* MP4File::GetTrackType(MP4TrackId trackId) { return m_pTracks[FindTrackIndex(trackId)]->GetType(); } const char *MP4File::GetTrackMediaDataName (MP4TrackId trackId) { MP4Atom *pChild; MP4Atom *pAtom = FindAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd")); if (pAtom->GetNumberOfChildAtoms() != 1) { log.errorf("%s: \"%s\": track %d has more than 1 child atoms in stsd", __FUNCTION__, GetFilename().c_str(), trackId); return NULL; } pChild = pAtom->GetChildAtom(0); return pChild->GetType(); } uint32_t MP4File::GetTrackTimeScale(MP4TrackId trackId) { return m_pTracks[FindTrackIndex(trackId)]->GetTimeScale(); } void MP4File::SetTrackTimeScale(MP4TrackId trackId, uint32_t value) { if (value == 0) { throw new Exception("invalid value", __FILE__, __LINE__, __FUNCTION__); } SetTrackIntegerProperty(trackId, "mdia.mdhd.timeScale", value); } MP4Duration MP4File::GetTrackDuration(MP4TrackId trackId) { return GetTrackIntegerProperty(trackId, "mdia.mdhd.duration"); } uint8_t MP4File::GetTrackEsdsObjectTypeId(MP4TrackId trackId) { // changed mp4a to * to handle enca case try { return GetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.*.esds.decConfigDescr.objectTypeId"); } catch (Exception *x) { delete x; return GetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.*.*.esds.decConfigDescr.objectTypeId"); } } uint8_t MP4File::GetTrackAudioMpeg4Type(MP4TrackId trackId) { // verify that track is an MPEG-4 audio track if (GetTrackEsdsObjectTypeId(trackId) != MP4_MPEG4_AUDIO_TYPE) { return MP4_MPEG4_INVALID_AUDIO_TYPE; } uint8_t* pEsConfig = NULL; uint32_t esConfigSize; // The Mpeg4 audio type (AAC, CELP, HXVC, ...) // is the first 5 bits of the ES configuration GetTrackESConfiguration(trackId, &pEsConfig, &esConfigSize); if (esConfigSize < 1) { free(pEsConfig); return MP4_MPEG4_INVALID_AUDIO_TYPE; } uint8_t mpeg4Type = ((pEsConfig[0] >> 3) & 0x1f); // TTTT TXXX XXX potentially 6 bits of extension. if (mpeg4Type == 0x1f) { if (esConfigSize < 2) { free(pEsConfig); return MP4_MPEG4_INVALID_AUDIO_TYPE; } mpeg4Type = 32 + (((pEsConfig[0] & 0x7) << 3) | ((pEsConfig[1] >> 5) & 0x7)); } free(pEsConfig); return mpeg4Type; } MP4Duration MP4File::GetTrackFixedSampleDuration(MP4TrackId trackId) { return m_pTracks[FindTrackIndex(trackId)]->GetFixedSampleDuration(); } double MP4File::GetTrackVideoFrameRate(MP4TrackId trackId) { MP4SampleId numSamples = GetTrackNumberOfSamples(trackId); uint64_t msDuration = ConvertFromTrackDuration(trackId, GetTrackDuration(trackId), MP4_MSECS_TIME_SCALE); if (msDuration == 0) { return 0.0; } return ((double)numSamples / double(msDuration)) * MP4_MSECS_TIME_SCALE; } int MP4File::GetTrackAudioChannels (MP4TrackId trackId) { return GetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.*[0].channels"); } // true if media track encrypted according to ismacryp bool MP4File::IsIsmaCrypMediaTrack(MP4TrackId trackId) { if (GetTrackIntegerProperty(trackId, "mdia.minf.stbl.stsd.*.sinf.frma.data-format") != (uint64_t)-1) { return true; } return false; } bool MP4File::IsWriteMode() { if( !m_file ) return false; switch( m_file->mode ) { case File::MODE_READ: return false; case File::MODE_MODIFY: case File::MODE_CREATE: default: break; } return true; } void MP4File::GetTrackESConfiguration(MP4TrackId trackId, uint8_t** ppConfig, uint32_t* pConfigSize) { try { GetTrackBytesProperty(trackId, "mdia.minf.stbl.stsd.*[0].esds.decConfigDescr.decSpecificInfo[0].info", ppConfig, pConfigSize); } catch (Exception *x) { delete x; GetTrackBytesProperty(trackId, "mdia.minf.stbl.stsd.*[0].*.esds.decConfigDescr.decSpecificInfo[0].info", ppConfig, pConfigSize); } } void MP4File::GetTrackVideoMetadata(MP4TrackId trackId, uint8_t** ppConfig, uint32_t* pConfigSize) { GetTrackBytesProperty(trackId, "mdia.minf.stbl.stsd.*[0].*.metadata", ppConfig, pConfigSize); } void MP4File::SetTrackESConfiguration(MP4TrackId trackId, const uint8_t* pConfig, uint32_t configSize) { // get a handle on the track decoder config descriptor MP4DescriptorProperty* pConfigDescrProperty = NULL; if (FindProperty(MakeTrackName(trackId, "mdia.minf.stbl.stsd.*[0].esds.decConfigDescr.decSpecificInfo"), (MP4Property**)&pConfigDescrProperty) == false || pConfigDescrProperty == NULL) { // probably trackId refers to a hint track throw new Exception("no such property", __FILE__, __LINE__, __FUNCTION__); } // lookup the property to store the configuration MP4BytesProperty* pInfoProperty = NULL; (void)pConfigDescrProperty->FindProperty("decSpecificInfo[0].info", (MP4Property**)&pInfoProperty); // configuration being set for the first time if (pInfoProperty == NULL) { // need to create a new descriptor to hold it MP4Descriptor* pConfigDescr = pConfigDescrProperty->AddDescriptor(MP4DecSpecificDescrTag); pConfigDescr->Generate(); (void)pConfigDescrProperty->FindProperty( "decSpecificInfo[0].info", (MP4Property**)&pInfoProperty); ASSERT(pInfoProperty); } // set the value pInfoProperty->SetValue(pConfig, configSize); } void MP4File::GetTrackH264SeqPictHeaders (MP4TrackId trackId, uint8_t ***pppSeqHeader, uint32_t **ppSeqHeaderSize, uint8_t ***pppPictHeader, uint32_t **ppPictHeaderSize) { uint32_t count; const char *format; MP4Atom *avcCAtom; *pppSeqHeader = NULL; *pppPictHeader = NULL; *ppSeqHeaderSize = NULL; *ppPictHeaderSize = NULL; // get 4cc media format - can be avc1 or encv for ismacrypted track format = GetTrackMediaDataName (trackId); if (!strcasecmp(format, "avc1")) avcCAtom = FindAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.avc1.avcC")); else if (!strcasecmp(format, "encv")) avcCAtom = FindAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd.encv.avcC")); else // huh? unknown track format return; MP4BitfieldProperty *pSeqCount; MP4IntegerProperty *pSeqLen, *pPictCount, *pPictLen; MP4BytesProperty *pSeqVal, *pPictVal; if ((avcCAtom->FindProperty("avcC.numOfSequenceParameterSets", (MP4Property **)&pSeqCount) == false) || (avcCAtom->FindProperty("avcC.sequenceEntries.sequenceParameterSetLength", (MP4Property **)&pSeqLen) == false) || (avcCAtom->FindProperty("avcC.sequenceEntries.sequenceParameterSetNALUnit", (MP4Property **)&pSeqVal) == false)) { log.errorf("%s: \"%s\": Could not find avcC properties", __FUNCTION__, GetFilename().c_str()); return ; } uint8_t **ppSeqHeader = (uint8_t **)malloc((pSeqCount->GetValue() + 1) * sizeof(uint8_t *)); if (ppSeqHeader == NULL) return; *pppSeqHeader = ppSeqHeader; uint32_t *pSeqHeaderSize = (uint32_t *)malloc((pSeqCount->GetValue() + 1) * sizeof(uint32_t *)); if (pSeqHeaderSize == NULL) return; *ppSeqHeaderSize = pSeqHeaderSize; for (count = 0; count < pSeqCount->GetValue(); count++) { pSeqVal->GetValue(&(ppSeqHeader[count]), &(pSeqHeaderSize[count]), count); } ppSeqHeader[count] = NULL; pSeqHeaderSize[count] = 0; if ((avcCAtom->FindProperty("avcC.numOfPictureParameterSets", (MP4Property **)&pPictCount) == false) || (avcCAtom->FindProperty("avcC.pictureEntries.pictureParameterSetLength", (MP4Property **)&pPictLen) == false) || (avcCAtom->FindProperty("avcC.pictureEntries.pictureParameterSetNALUnit", (MP4Property **)&pPictVal) == false)) { log.errorf("%s: \"%s\": Could not find avcC picture table properties", __FUNCTION__, GetFilename().c_str()); return ; } uint8_t **ppPictHeader = (uint8_t **)malloc((pPictCount->GetValue() + 1) * sizeof(uint8_t *)); if (ppPictHeader == NULL) return; uint32_t *pPictHeaderSize = (uint32_t *)malloc((pPictCount->GetValue() + 1)* sizeof(uint32_t *)); if (pPictHeaderSize == NULL) { free(ppPictHeader); return; } *pppPictHeader = ppPictHeader; *ppPictHeaderSize = pPictHeaderSize; for (count = 0; count < pPictCount->GetValue(); count++) { pPictVal->GetValue(&(ppPictHeader[count]), &(pPictHeaderSize[count]), count); } ppPictHeader[count] = NULL; pPictHeaderSize[count] = 0; return ; } const char* MP4File::GetHintTrackSdp(MP4TrackId hintTrackId) { return GetTrackStringProperty(hintTrackId, "udta.hnti.sdp .sdpText"); } void MP4File::SetHintTrackSdp(MP4TrackId hintTrackId, const char* sdpString) { MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } (void)AddDescendantAtoms( MakeTrackName(hintTrackId, NULL), "udta.hnti.sdp "); SetTrackStringProperty(hintTrackId, "udta.hnti.sdp .sdpText", sdpString); } void MP4File::AppendHintTrackSdp(MP4TrackId hintTrackId, const char* sdpFragment) { const char* oldSdpString = NULL; try { oldSdpString = GetHintTrackSdp(hintTrackId); } catch (Exception* x) { delete x; SetHintTrackSdp(hintTrackId, sdpFragment); return; } char* newSdpString = (char*)MP4Malloc(strlen(oldSdpString) + strlen(sdpFragment) + 1); strcpy(newSdpString, oldSdpString); strcat(newSdpString, sdpFragment); SetHintTrackSdp(hintTrackId, newSdpString); MP4Free(newSdpString); } void MP4File::GetHintTrackRtpPayload( MP4TrackId hintTrackId, char** ppPayloadName, uint8_t* pPayloadNumber, uint16_t* pMaxPayloadSize, char **ppEncodingParams) { MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } ((MP4RtpHintTrack*)pTrack)->GetPayload( ppPayloadName, pPayloadNumber, pMaxPayloadSize, ppEncodingParams); } void MP4File::SetHintTrackRtpPayload(MP4TrackId hintTrackId, const char* payloadName, uint8_t* pPayloadNumber, uint16_t maxPayloadSize, const char *encoding_params, bool include_rtp_map, bool include_mpeg4_esid) { MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } uint8_t payloadNumber; if (pPayloadNumber && *pPayloadNumber != MP4_SET_DYNAMIC_PAYLOAD) { payloadNumber = *pPayloadNumber; } else { payloadNumber = AllocRtpPayloadNumber(); if (pPayloadNumber) { *pPayloadNumber = payloadNumber; } } ((MP4RtpHintTrack*)pTrack)->SetPayload( payloadName, payloadNumber, maxPayloadSize, encoding_params, include_rtp_map, include_mpeg4_esid); } uint8_t MP4File::AllocRtpPayloadNumber() { MP4Integer32Array usedPayloads; uint32_t i; // collect rtp payload numbers in use by existing tracks for (i = 0; i < m_pTracks.Size(); i++) { MP4Atom& trakAtom = m_pTracks[i]->GetTrakAtom(); MP4Integer32Property* pPayloadProperty = NULL; if (trakAtom.FindProperty("trak.udta.hinf.payt.payloadNumber", (MP4Property**)&pPayloadProperty) && pPayloadProperty) { usedPayloads.Add(pPayloadProperty->GetValue()); } } // search dynamic payload range for an available slot uint8_t payload; for (payload = 96; payload < 128; payload++) { for (i = 0; i < usedPayloads.Size(); i++) { if (payload == usedPayloads[i]) { break; } } if (i == usedPayloads.Size()) { break; } } if (payload >= 128) { throw new Exception("no more available rtp payload numbers", __FILE__, __LINE__, __FUNCTION__); } return payload; } MP4TrackId MP4File::GetHintTrackReferenceTrackId( MP4TrackId hintTrackId) { MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } MP4Track* pRefTrack = ((MP4RtpHintTrack*)pTrack)->GetRefTrack(); if (pRefTrack == NULL) { return MP4_INVALID_TRACK_ID; } return pRefTrack->GetId(); } void MP4File::ReadRtpHint( MP4TrackId hintTrackId, MP4SampleId hintSampleId, uint16_t* pNumPackets) { MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } ((MP4RtpHintTrack*)pTrack)-> ReadHint(hintSampleId, pNumPackets); } uint16_t MP4File::GetRtpHintNumberOfPackets( MP4TrackId hintTrackId) { MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } return ((MP4RtpHintTrack*)pTrack)->GetHintNumberOfPackets(); } int8_t MP4File::GetRtpPacketBFrame( MP4TrackId hintTrackId, uint16_t packetIndex) { MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } return ((MP4RtpHintTrack*)pTrack)->GetPacketBFrame(packetIndex); } int32_t MP4File::GetRtpPacketTransmitOffset( MP4TrackId hintTrackId, uint16_t packetIndex) { MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } return ((MP4RtpHintTrack*)pTrack)->GetPacketTransmitOffset(packetIndex); } void MP4File::ReadRtpPacket( MP4TrackId hintTrackId, uint16_t packetIndex, uint8_t** ppBytes, uint32_t* pNumBytes, uint32_t ssrc, bool includeHeader, bool includePayload) { MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } ((MP4RtpHintTrack*)pTrack)->ReadPacket( packetIndex, ppBytes, pNumBytes, ssrc, includeHeader, includePayload); } MP4Timestamp MP4File::GetRtpTimestampStart( MP4TrackId hintTrackId) { MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } return ((MP4RtpHintTrack*)pTrack)->GetRtpTimestampStart(); } void MP4File::SetRtpTimestampStart( MP4TrackId hintTrackId, MP4Timestamp rtpStart) { MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } ((MP4RtpHintTrack*)pTrack)->SetRtpTimestampStart(rtpStart); } void MP4File::AddRtpHint(MP4TrackId hintTrackId, bool isBframe, uint32_t timestampOffset) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } ((MP4RtpHintTrack*)pTrack)->AddHint(isBframe, timestampOffset); } void MP4File::AddRtpPacket( MP4TrackId hintTrackId, bool setMbit, int32_t transmitOffset) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } ((MP4RtpHintTrack*)pTrack)->AddPacket(setMbit, transmitOffset); } void MP4File::AddRtpImmediateData(MP4TrackId hintTrackId, const uint8_t* pBytes, uint32_t numBytes) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } ((MP4RtpHintTrack*)pTrack)->AddImmediateData(pBytes, numBytes); } void MP4File::AddRtpSampleData(MP4TrackId hintTrackId, MP4SampleId sampleId, uint32_t dataOffset, uint32_t dataLength) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } ((MP4RtpHintTrack*)pTrack)->AddSampleData( sampleId, dataOffset, dataLength); } void MP4File::AddRtpESConfigurationPacket(MP4TrackId hintTrackId) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } ((MP4RtpHintTrack*)pTrack)->AddESConfigurationPacket(); } void MP4File::WriteRtpHint(MP4TrackId hintTrackId, MP4Duration duration, bool isSyncSample) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { throw new Exception("track is not a hint track", __FILE__, __LINE__, __FUNCTION__); } ((MP4RtpHintTrack*)pTrack)->WriteHint(duration, isSyncSample); } uint64_t MP4File::ConvertFromMovieDuration( MP4Duration duration, uint32_t timeScale) { return MP4ConvertTime((uint64_t)duration, GetTimeScale(), timeScale); } uint64_t MP4File::ConvertFromTrackTimestamp( MP4TrackId trackId, MP4Timestamp timeStamp, uint32_t timeScale) { return MP4ConvertTime(timeStamp, GetTrackTimeScale(trackId), timeScale); } MP4Timestamp MP4File::ConvertToTrackTimestamp( MP4TrackId trackId, uint64_t timeStamp, uint32_t timeScale) { return (MP4Timestamp)MP4ConvertTime(timeStamp, timeScale, GetTrackTimeScale(trackId)); } uint64_t MP4File::ConvertFromTrackDuration( MP4TrackId trackId, MP4Duration duration, uint32_t timeScale) { return MP4ConvertTime((uint64_t)duration, GetTrackTimeScale(trackId), timeScale); } MP4Duration MP4File::ConvertToTrackDuration( MP4TrackId trackId, uint64_t duration, uint32_t timeScale) { return (MP4Duration)MP4ConvertTime(duration, timeScale, GetTrackTimeScale(trackId)); } uint8_t MP4File::ConvertTrackTypeToStreamType(const char* trackType) { uint8_t streamType; if (!strcmp(trackType, MP4_OD_TRACK_TYPE)) { streamType = MP4ObjectDescriptionStreamType; } else if (!strcmp(trackType, MP4_SCENE_TRACK_TYPE)) { streamType = MP4SceneDescriptionStreamType; } else if (!strcmp(trackType, MP4_CLOCK_TRACK_TYPE)) { streamType = MP4ClockReferenceStreamType; } else if (!strcmp(trackType, MP4_MPEG7_TRACK_TYPE)) { streamType = MP4Mpeg7StreamType; } else if (!strcmp(trackType, MP4_OCI_TRACK_TYPE)) { streamType = MP4OCIStreamType; } else if (!strcmp(trackType, MP4_IPMP_TRACK_TYPE)) { streamType = MP4IPMPStreamType; } else if (!strcmp(trackType, MP4_MPEGJ_TRACK_TYPE)) { streamType = MP4MPEGJStreamType; } else { streamType = MP4UserPrivateStreamType; } return streamType; } // edit list char* MP4File::MakeTrackEditName( MP4TrackId trackId, MP4EditId editId, const char* name) { char* trakName = MakeTrackName(trackId, NULL); if (m_editName == NULL) { m_editName = (char *)malloc(1024); if (m_editName == NULL) return NULL; } snprintf(m_editName, 1024, "%s.edts.elst.entries[%u].%s", trakName, editId - 1, name); return m_editName; } MP4EditId MP4File::AddTrackEdit( MP4TrackId trackId, MP4EditId editId) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); return m_pTracks[FindTrackIndex(trackId)]->AddEdit(editId); } void MP4File::DeleteTrackEdit( MP4TrackId trackId, MP4EditId editId) { ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__); m_pTracks[FindTrackIndex(trackId)]->DeleteEdit(editId); } uint32_t MP4File::GetTrackNumberOfEdits( MP4TrackId trackId) { return GetTrackIntegerProperty(trackId, "edts.elst.entryCount"); } MP4Duration MP4File::GetTrackEditTotalDuration( MP4TrackId trackId, MP4EditId editId) { return m_pTracks[FindTrackIndex(trackId)]->GetEditTotalDuration(editId); } MP4Timestamp MP4File::GetTrackEditStart( MP4TrackId trackId, MP4EditId editId) { return m_pTracks[FindTrackIndex(trackId)]->GetEditStart(editId); } MP4Timestamp MP4File::GetTrackEditMediaStart( MP4TrackId trackId, MP4EditId editId) { return GetIntegerProperty( MakeTrackEditName(trackId, editId, "mediaTime")); } void MP4File::SetTrackEditMediaStart( MP4TrackId trackId, MP4EditId editId, MP4Timestamp startTime) { SetIntegerProperty( MakeTrackEditName(trackId, editId, "mediaTime"), startTime); } MP4Duration MP4File::GetTrackEditDuration( MP4TrackId trackId, MP4EditId editId) { return GetIntegerProperty( MakeTrackEditName(trackId, editId, "segmentDuration")); } void MP4File::SetTrackEditDuration( MP4TrackId trackId, MP4EditId editId, MP4Duration duration) { SetIntegerProperty( MakeTrackEditName(trackId, editId, "segmentDuration"), duration); } bool MP4File::GetTrackEditDwell( MP4TrackId trackId, MP4EditId editId) { return (GetIntegerProperty( MakeTrackEditName(trackId, editId, "mediaRate")) == 0); } void MP4File::SetTrackEditDwell( MP4TrackId trackId, MP4EditId editId, bool dwell) { SetIntegerProperty( MakeTrackEditName(trackId, editId, "mediaRate"), (dwell ? 0 : 1)); } MP4SampleId MP4File::GetSampleIdFromEditTime( MP4TrackId trackId, MP4Timestamp when, MP4Timestamp* pStartTime, MP4Duration* pDuration) { return m_pTracks[FindTrackIndex(trackId)]->GetSampleIdFromEditTime( when, pStartTime, pDuration); } MP4Duration MP4File::GetTrackDurationPerChunk( MP4TrackId trackId ) { return m_pTracks[FindTrackIndex(trackId)]->GetDurationPerChunk(); } void MP4File::SetTrackDurationPerChunk( MP4TrackId trackId, MP4Duration duration ) { m_pTracks[FindTrackIndex(trackId)]->SetDurationPerChunk( duration ); } void MP4File::CopySample( MP4File* srcFile, MP4TrackId srcTrackId, MP4SampleId srcSampleId, MP4File* dstFile, MP4TrackId dstTrackId, MP4Duration dstSampleDuration ) { // Note: we leave it up to the caller to ensure that the // source and destination tracks are compatible. // i.e. copying audio samples into a video track // is unlikely to do anything useful uint8_t* pBytes = NULL; uint32_t numBytes = 0; MP4Duration sampleDuration; MP4Duration renderingOffset; bool isSyncSample; bool hasDependencyFlags; uint32_t dependencyFlags; srcFile->ReadSample( srcTrackId, srcSampleId, &pBytes, &numBytes, NULL, &sampleDuration, &renderingOffset, &isSyncSample, &hasDependencyFlags, &dependencyFlags ); if( !dstFile ) dstFile = srcFile; if( dstTrackId == MP4_INVALID_TRACK_ID ) dstTrackId = srcTrackId; if( dstSampleDuration != MP4_INVALID_DURATION ) sampleDuration = dstSampleDuration; if( hasDependencyFlags ) { dstFile->WriteSampleDependency( dstTrackId, pBytes, numBytes, sampleDuration, renderingOffset, isSyncSample, dependencyFlags ); } else { dstFile->WriteSample( dstTrackId, pBytes, numBytes, sampleDuration, renderingOffset, isSyncSample ); } free( pBytes ); } void MP4File::EncAndCopySample( MP4File* srcFile, MP4TrackId srcTrackId, MP4SampleId srcSampleId, encryptFunc_t encfcnp, uint32_t encfcnparam1, MP4File* dstFile, MP4TrackId dstTrackId, MP4Duration dstSampleDuration ) { // Note: we leave it up to the caller to ensure that the // source and destination tracks are compatible. // i.e. copying audio samples into a video track // is unlikely to do anything useful uint8_t* pBytes = NULL; uint32_t numBytes = 0; uint8_t* encSampleData = NULL; uint32_t encSampleLength = 0; MP4Duration sampleDuration; MP4Duration renderingOffset; bool isSyncSample; bool hasDependencyFlags; uint32_t dependencyFlags; ASSERT(srcFile); srcFile->ReadSample( srcTrackId, srcSampleId, &pBytes, &numBytes, NULL, &sampleDuration, &renderingOffset, &isSyncSample, &hasDependencyFlags, &dependencyFlags ); if( !dstFile ) dstFile = srcFile; ASSERT(dstFile); if( dstTrackId == MP4_INVALID_TRACK_ID ) dstTrackId = srcTrackId; if( dstSampleDuration != MP4_INVALID_DURATION ) sampleDuration = dstSampleDuration; //if( ismacrypEncryptSampleAddHeader( ismaCryptSId, numBytes, pBytes, &encSampleLength, &encSampleData ) != 0) if( encfcnp( encfcnparam1, numBytes, pBytes, &encSampleLength, &encSampleData ) != 0 ) log.errorf("%s(%s,%s) Can't encrypt the sample and add its header %u", __FUNCTION__, srcFile->GetFilename().c_str(), dstFile->GetFilename().c_str(), srcSampleId ); if( hasDependencyFlags ) { dstFile->WriteSampleDependency( dstTrackId, pBytes, numBytes, sampleDuration, renderingOffset, isSyncSample, dependencyFlags ); } else { dstFile->WriteSample( dstTrackId, encSampleData, encSampleLength, sampleDuration, renderingOffset, isSyncSample ); } free( pBytes ); if( encSampleData != NULL ) free( encSampleData ); } /////////////////////////////////////////////////////////////////////////////// }} // namespace mp4v2::impl