diff options
author | Jean-Francois Mauguit <jfmauguit@mac.com> | 2024-09-24 09:03:25 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-24 09:03:25 -0400 |
commit | bab614c421ed7ae329d26bf028c4a3b1d2450f5a (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/libmp4v2/mp4file.cpp | |
parent | 4bde6044fddf053f31795b9eaccdd2a5a527d21f (diff) | |
parent | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (diff) | |
download | winamp-bab614c421ed7ae329d26bf028c4a3b1d2450f5a.tar.gz |
Merge pull request #5 from WinampDesktop/community
Merge to main
Diffstat (limited to 'Src/external_dependencies/libmp4v2/mp4file.cpp')
-rw-r--r-- | Src/external_dependencies/libmp4v2/mp4file.cpp | 3728 |
1 files changed, 3728 insertions, 0 deletions
diff --git a/Src/external_dependencies/libmp4v2/mp4file.cpp b/Src/external_dependencies/libmp4v2/mp4file.cpp new file mode 100644 index 00000000..e12ccf51 --- /dev/null +++ b/Src/external_dependencies/libmp4v2/mp4file.cpp @@ -0,0 +1,3728 @@ +/* + * 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 "mp4common.h" + +MP4File::MP4File(u_int32_t verbosity) +{ + m_fileName = NULL; + m_pFile = NULL; + m_virtual_IO = NULL; + m_orgFileSize = 0; + m_fileSize = 0; + m_pRootAtom = NULL; + m_odTrackId = MP4_INVALID_TRACK_ID; + + m_verbosity = verbosity; + m_mode = 0; + m_createFlags = 0; + 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; +#ifndef _WIN32 + m_tempFileName[0] = '\0'; +#endif + m_trakName[0] = '\0'; + m_tempFileName[0] = '\0'; +} + +MP4File::~MP4File() +{ + MP4Free(m_fileName); + if (m_pFile != NULL) { + // not closed ? + m_virtual_IO->Close(m_pFile); + m_pFile = NULL; + } + delete m_pRootAtom; + for (u_int32_t i = 0; i < m_pTracks.Size(); i++) { + delete m_pTracks[i]; + } + MP4Free(m_memoryBuffer); // just in case + CHECK_AND_FREE(m_editName); + +} + +void MP4File::Read(const MP4_FILENAME_CHAR* fileName) +{ + m_fileName = MP4Stralloc(fileName); + m_mode = 'r'; + +#ifdef _WIN32 + Open(L"rb"); +#else + Open("rb"); +#endif + + ReadFromFile(); + + CacheProperties(); +} + +// benski> +void MP4File::ReadEx(const MP4_FILENAME_CHAR *fileName, void *user, Virtual_IO *virtual_IO) +{ + m_fileName = MP4Stralloc(fileName); + m_mode = 'r'; + + m_pFile = user; + m_virtual_IO = virtual_IO; + + ASSERT(m_pFile); + ASSERT(m_virtual_IO) + + m_orgFileSize = m_fileSize = m_virtual_IO->GetFileLength(m_pFile); + + ReadFromFile(); + + CacheProperties(); +} + + +void MP4File::Create(const MP4_FILENAME_CHAR *fileName, u_int32_t flags, + int add_ftyp, int add_iods, + char* majorBrand, u_int32_t minorVersion, + char** supportedBrands, u_int32_t supportedBrandsCount) +{ + m_fileName = MP4Stralloc(fileName); + m_mode = 'w'; + m_createFlags = flags; + +#ifdef _WIN32 + Open(L"wb+"); +#else + Open("wb+"); +#endif + + // generate a skeletal atom tree + m_pRootAtom = MP4Atom::CreateAtom(NULL); + m_pRootAtom->SetFile(this); + 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; + } +} + + +void MP4File::Modify(const MP4_FILENAME_CHAR* fileName) +{ + m_fileName = MP4Stralloc(fileName); + m_mode = 'r'; + +#ifdef _WIN32 + Open(L"rb+"); +#else + Open("rb+"); +#endif + ReadFromFile(); + + m_mode = 'w'; + + // find the moov atom + MP4Atom* pMoovAtom = m_pRootAtom->FindAtomMP4("moov"); + u_int32_t numAtoms; + + if (pMoovAtom == NULL) { + // there isn't one, odd but we can still proceed + 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 MP4Error( + "Badly formed mp4 file, multiple moov atoms", + "MP4Modify"); + } + + 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("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")); +} + +void MP4File::Optimize(const MP4_FILENAME_CHAR* orgFileName, const MP4_FILENAME_CHAR* newFileName) +{ + m_fileName = MP4Stralloc(orgFileName); + m_mode = 'r'; + + // first load meta-info into memory +#ifdef _WIN32 + Open(L"rb"); +#else + Open("rb"); +#endif + ReadFromFile(); + + CacheProperties(); // of moov atom + + // now switch over to writing the new file + MP4Free(m_fileName); + + // create a temporary file if necessary + if (newFileName == NULL) { + m_fileName = MP4Stralloc(TempFileName()); + } else { + m_fileName = MP4Stralloc(newFileName); + } + + void* pReadFile = m_pFile; + Virtual_IO *pReadIO = m_virtual_IO; + m_pFile = NULL; + m_mode = 'w'; +#ifdef _WIN32 + Open(L"wb"); +#else + Open("wb"); +#endif + + SetIntegerProperty("moov.mvhd.modificationTime", + MP4GetAbsTimestamp()); + + // writing meta info in the optimal order + ((MP4RootAtom*)m_pRootAtom)->BeginOptimalWrite(); + + // write data in optimal order + RewriteMdat(pReadFile, m_pFile, pReadIO, m_virtual_IO); + + // finish writing + ((MP4RootAtom*)m_pRootAtom)->FinishOptimalWrite(); + + // cleanup + m_virtual_IO->Close(m_pFile); + m_pFile = NULL; + pReadIO->Close(pReadFile); + + // move temporary file into place + if (newFileName == NULL) { + Rename(m_fileName, orgFileName); + } +} + +void MP4File::RewriteMdat(void* pReadFile, void* pWriteFile, + Virtual_IO *readIO, Virtual_IO *writeIO) +{ + u_int32_t numTracks = m_pTracks.Size(); + + MP4ChunkId* chunkIds = new MP4ChunkId[numTracks]; + MP4ChunkId* maxChunkIds = new MP4ChunkId[numTracks]; + MP4Timestamp* nextChunkTimes = new MP4Timestamp[numTracks]; + + for (u_int32_t i = 0; i < numTracks; i++) { + chunkIds[i] = 1; + maxChunkIds[i] = m_pTracks[i]->GetNumberOfChunks(); + nextChunkTimes[i] = MP4_INVALID_TIMESTAMP; + } + + while (true) { + u_int32_t nextTrackIndex = (u_int32_t)-1; + MP4Timestamp nextTime = MP4_INVALID_TIMESTAMP; + + for (u_int32_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 == (u_int32_t)-1) { + break; + } + + // point into original mp4 file for read chunk call + m_pFile = pReadFile; + m_virtual_IO = readIO; + m_mode = 'r'; + + u_int8_t* pChunk=0; + u_int32_t chunkSize; + + m_pTracks[nextTrackIndex]-> + ReadChunk(chunkIds[nextTrackIndex], &pChunk, &chunkSize); + + // point back at the new mp4 file for write chunk + m_pFile = pWriteFile; + m_virtual_IO = writeIO; + m_mode = 'w'; + + 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 MP4_FILENAME_CHAR *fmode) +{ + ASSERT(m_pFile == NULL); + FILE *openFile = NULL; + +#ifdef O_LARGEFILE + // UGH! fopen doesn't open a file in 64-bit mode, period. + // So we need to use open() and then fdopen() + int fd; + int flags = O_LARGEFILE; + + if (strchr(fmode, '+')) { + flags |= O_CREAT | O_RDWR; + if (fmode[0] == 'w') { + flags |= O_TRUNC; + } + } else { + if (fmode[0] == 'w') { + flags |= O_CREAT | O_TRUNC | O_WRONLY; + } else { + flags |= O_RDONLY; + } + } + fd = open(m_fileName, flags, 0666); + + if (fd >= 0) { + openFile = fdopen(fd, fmode); + } +#elif defined(_WIN32) + openFile = _wfopen(m_fileName, fmode); +#else + openFile = fopen(m_fileName, fmode); +#endif + m_pFile = openFile; + + if (m_pFile == NULL) { + throw new MP4Error(errno, "failed", "MP4Open"); + } + + m_virtual_IO = &FILE_virtual_IO; + if (m_mode == 'r') { + m_orgFileSize = m_fileSize = m_virtual_IO->GetFileLength(m_pFile); // benski + } else { + m_orgFileSize = m_fileSize = 0; + } +} + +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(NULL); + + u_int64_t fileSize = GetSize(); + + m_pRootAtom->SetFile(this); + 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() +{ + u_int32_t trackIndex = 0; + + while (true) { + char trackName[32]; + snprintf(trackName, sizeof(trackName), "moov.trak[%u]", trackIndex); + + // find next trak atom + MP4Atom* pTrakAtom = m_pRootAtom->FindAtomMP4(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 { + const char* value = pTypeProperty->GetValue(); + if (value && !strcmp(value, MP4_HINT_TRACK_TYPE)) { + pTrack = new MP4RtpHintTrack(this, pTrakAtom); + } else { + pTrack = new MP4Track(this, pTrakAtom); + } + m_pTracks.Add(pTrack); + } + catch (MP4Error* e) { + VERBOSE_ERROR(m_verbosity, e->Print()); + delete e; + } + + // remember when we encounter the OD track + const char* track = (pTrack ? pTrack->GetType() : 0); + if (pTrack && !strcmp(track, MP4_OD_TRACK_TYPE)) { + if (m_odTrackId == MP4_INVALID_TRACK_ID) { + m_odTrackId = pTrackIdProperty->GetValue(); + } else { + VERBOSE_READ(GetVerbosity(), + printf("Warning: multiple OD tracks present\n")); + } + } + } 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() +{ + // for all tracks, flush chunking buffers + for (u_int32_t i = 0; i < m_pTracks.Size(); i++) { + ASSERT(m_pTracks[i]); + m_pTracks[i]->FinishWrite(); + } + + // ask root atom to write + m_pRootAtom->FinishWrite(); + + // check if file shrunk, e.g. we deleted a track + if (GetSize() < m_orgFileSize) { + // just use a free atom to mark unused space + // MP4Optimize() should be used to clean up this space + MP4Atom* pFreeAtom = MP4Atom::CreateAtom("free"); + ASSERT(pFreeAtom); + pFreeAtom->SetFile(this); + int64_t size = m_orgFileSize - (m_fileSize + 8); + if (size < 0) size = 0; + pFreeAtom->SetSize(size); + pFreeAtom->Write(); + delete pFreeAtom; + } +} + +void MP4File::UpdateDuration(MP4Duration duration) +{ + MP4Duration currentDuration = GetDuration(); + if (duration > currentDuration) { + SetDuration(duration); + } +} + +void MP4File::Close() +{ + if (m_mode == 'w') { + SetIntegerProperty("moov.mvhd.modificationTime", + MP4GetAbsTimestamp()); + + FinishWrite(); + } + + m_virtual_IO->Close(m_pFile); + m_pFile = NULL; +} + +const MP4_FILENAME_CHAR* MP4File::TempFileName() +{ + // there are so many attempts in libc to get this right + // that for portablity reasons, it's best just to roll our own +#ifndef _WIN32 + u_int32_t i; + for (i = getpid(); i < 0xFFFFFFFF; i++) { + snprintf(m_tempFileName, sizeof(m_tempFileName), + "./tmp%u.mp4", i); + if (access(m_tempFileName, F_OK) != 0) { + break; + } + } + if (i == 0xFFFFFFFF) { + throw new MP4Error("can't create temporary file", "TempFileName"); + } +#else + wchar_t tmppath[MAX_PATH-14]; + GetTempPathW(MAX_PATH-14,tmppath); + GetTempFileNameW(tmppath, // dir. for temp. files + L"mp4", // temp. filename prefix + 0, // create unique name + m_tempFileName); // buffer for name +#endif + + return m_tempFileName; +} + +void MP4File::Rename(const MP4_FILENAME_CHAR* oldFileName, const MP4_FILENAME_CHAR* newFileName) +{ + int rc; + +#ifdef _WIN32 + rc=0; + DeleteFileW(newFileName); + if (MoveFileW(oldFileName,newFileName) == 0) // if the function fails + { + if (!CopyFileW(oldFileName,newFileName, FALSE)) + rc=1; + else + DeleteFileW(oldFileName); + } + /* benski> CUT: + rc = remove(newFileName); + if (rc == 0) { + rc = rename(oldFileName, newFileName); + } + */ +#else + rc = rename(oldFileName, newFileName); +#endif + if (rc != 0) { + throw new MP4Error(errno, "can't overwrite existing file", "Rename"); + } +} + +void MP4File::ProtectWriteOperation(char* where) +{ + if (m_mode == 'r') { + throw new MP4Error("operation not permitted in read mode", where); + } +} + +MP4Track* MP4File::GetTrack(MP4TrackId trackId) +{ + return m_pTracks[FindTrackIndex(trackId)]; +} + +MP4Atom* MP4File::FindAtomMP4File(const char* name) +{ + MP4Atom* pAtom = NULL; + if (!name || !strcmp(name, "")) { + pAtom = m_pRootAtom; + } else { + pAtom = m_pRootAtom->FindAtomMP4(name); + } + return pAtom; +} + +MP4Atom* MP4File::AddChildAtom( + const char* parentName, + const char* childName) +{ + return AddChildAtom(FindAtomMP4File(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, + u_int32_t index) +{ + return InsertChildAtom(FindAtomMP4File(parentName), childName, index); +} + +MP4Atom* MP4File::InsertChildAtom( + MP4Atom* pParentAtom, + const char* childName, + u_int32_t index) +{ + MP4Atom* pChildAtom = MP4Atom::CreateAtom(childName); + + ASSERT(pParentAtom); + pParentAtom->InsertChildAtom(pChildAtom, index); + + pChildAtom->Generate(); + + return pChildAtom; +} + +MP4Atom* MP4File::AddDescendantAtoms( + const char* ancestorName, + const char* descendantNames) +{ + return AddDescendantAtoms(FindAtomMP4File(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, u_int32_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, u_int32_t* pIndex) +{ + if (!FindProperty(name, ppProperty, pIndex)) { + throw new MP4Error("no such property - %s", "MP4File::FindIntegerProperty", name); + } + + switch ((*ppProperty)->GetType()) { + case Integer8Property: + case Integer16Property: + case Integer24Property: + case Integer32Property: + case Integer64Property: + break; + default: + throw new MP4Error("type mismatch - property %s type %d", "MP4File::FindIntegerProperty", name, (*ppProperty)->GetType()); + } +} + +u_int64_t MP4File::GetIntegerProperty(const char* name) +{ + MP4Property* pProperty; + u_int32_t index; + + FindIntegerProperty(name, &pProperty, &index); + + return ((MP4IntegerProperty*)pProperty)->GetValue(index); +} + +void MP4File::SetIntegerProperty(const char* name, u_int64_t value) +{ + ProtectWriteOperation("SetIntegerProperty"); + + MP4Property* pProperty = NULL; + u_int32_t index = 0; + + FindIntegerProperty(name, &pProperty, &index); + + ((MP4IntegerProperty*)pProperty)->SetValue(value, index); +} + +void MP4File::FindFloatProperty(const char* name, + MP4Property** ppProperty, u_int32_t* pIndex) +{ + if (!FindProperty(name, ppProperty, pIndex)) { + throw new MP4Error("no such property - %s", "MP4File::FindFloatProperty", name); + } + if ((*ppProperty)->GetType() != Float32Property) { + throw new MP4Error("type mismatch - property %s type %d", + "MP4File::FindFloatProperty", + name, + (*ppProperty)->GetType()); + } +} + +float MP4File::GetFloatProperty(const char* name) +{ + MP4Property* pProperty; + u_int32_t index; + + FindFloatProperty(name, &pProperty, &index); + + return ((MP4Float32Property*)pProperty)->GetValue(index); +} + +void MP4File::SetFloatProperty(const char* name, float value) +{ + ProtectWriteOperation("SetFloatProperty"); + + MP4Property* pProperty; + u_int32_t index; + + FindFloatProperty(name, &pProperty, &index); + + ((MP4Float32Property*)pProperty)->SetValue(value, index); +} + +void MP4File::FindStringProperty(const char* name, + MP4Property** ppProperty, u_int32_t* pIndex) +{ + if (!FindProperty(name, ppProperty, pIndex)) { + throw new MP4Error("no such property - %s", "MP4File::FindStringProperty", name); + } + if ((*ppProperty)->GetType() != StringProperty) { + throw new MP4Error("type mismatch - property %s type %d", "MP4File::FindStringProperty", + name, (*ppProperty)->GetType()); + } +} + +const char* MP4File::GetStringProperty(const char* name) +{ + MP4Property* pProperty; + u_int32_t index; + + FindStringProperty(name, &pProperty, &index); + + return ((MP4StringProperty*)pProperty)->GetValue(index); +} + +void MP4File::SetStringProperty(const char* name, const char* value) +{ + ProtectWriteOperation("SetStringProperty"); + + MP4Property* pProperty; + u_int32_t index; + + FindStringProperty(name, &pProperty, &index); + + ((MP4StringProperty*)pProperty)->SetValue(value, index); +} + +void MP4File::FindBytesProperty(const char* name, + MP4Property** ppProperty, u_int32_t* pIndex) +{ + if (!FindProperty(name, ppProperty, pIndex)) { + throw new MP4Error("no such property %s", "MP4File::FindBytesProperty", name); + } + if ((*ppProperty)->GetType() != BytesProperty) { + throw new MP4Error("type mismatch - property %s - type %d", "MP4File::FindBytesProperty", name, (*ppProperty)->GetType()); + } +} + +void MP4File::GetBytesProperty(const char* name, + u_int8_t** ppValue, u_int32_t* pValueSize) +{ + MP4Property* pProperty; + u_int32_t index; + + FindBytesProperty(name, &pProperty, &index); + + ((MP4BytesProperty*)pProperty)->GetValue(ppValue, pValueSize, index); +} + +void MP4File::SetBytesProperty(const char* name, + const u_int8_t* pValue, u_int32_t valueSize) +{ + ProtectWriteOperation("SetBytesProperty"); + + MP4Property* pProperty; + u_int32_t index; + + FindBytesProperty(name, &pProperty, &index); + + ((MP4BytesProperty*)pProperty)->SetValue(pValue, valueSize, index); +} + + +// track functions + +MP4TrackId MP4File::AddTrack(const char* type, u_int32_t timeScale) +{ + ProtectWriteOperation("AddTrack"); + + // create and add new trak atom + MP4Atom* pTrakAtom = AddChildAtom("moov", "trak"); + + // 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, m_verbosity); + + // sanity check for user defined types + if (strlen(normType) > 4) { + VERBOSE_WARNING(m_verbosity, + printf("AddTrack: type truncated to four characters\n")); + // 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)) + return; +#if 0 + // we may not have iods + if (shallHaveIods) { + ASSERT(pDescriptorProperty); + } else { + if (!pDescriptorProperty) { + return; + } + } +#else + if (pDescriptorProperty == NULL) { + return; + } +#endif + + for (u_int32_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); +} + +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); + ASSERT(*ppCountProperty); + + snprintf(propName, sizeof(propName), "%s.%s", trefName, "entries.trackId"); + (void)m_pRootAtom->FindProperty(propName, ppTrackIdProperty); + ASSERT(*ppTrackIdProperty); +} + +void MP4File::AddTrackReference(const char* trefName, MP4TrackId refTrackId) +{ + MP4Integer32Property* pCountProperty = NULL; + MP4Integer32Property* pTrackIdProperty = NULL; + + GetTrackReferenceProperties(trefName, + (MP4Property**)&pCountProperty, + (MP4Property**)&pTrackIdProperty); + + pTrackIdProperty->AddValue(refTrackId); + pCountProperty->IncrementValue(); +} + +u_int32_t MP4File::FindTrackReference(const char* trefName, + MP4TrackId refTrackId) +{ + MP4Integer32Property* pCountProperty = NULL; + MP4Integer32Property* pTrackIdProperty = NULL; + + GetTrackReferenceProperties(trefName, + (MP4Property**)&pCountProperty, + (MP4Property**)&pTrackIdProperty); + + for (u_int32_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); + + for (u_int32_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 = + FindAtomMP4File(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) +{ + const char* normType = MP4NormalizeTrackType(type, m_verbosity); + + // TBD if user type, fix name to four chars, and warn + + MP4TrackId trackId = AddTrack(type, MP4_MSECS_TIME_SCALE); + + (void)InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "nmhd", 0); + + (void)AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "mp4s"); + + // 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", +#if 0 + // note - for a file, these values need to + // be 0 - wmay - 04/16/2003 + trackId +#else + 0 +#endif + ); + + 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 MP4Error("object description track already exists", + "AddObjectDescriptionTrack"); + } + + 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; +} + +// NULL terminated list of brands which require the IODS atom +char *brandsWithIods[] = { "mp42", + "isom", + NULL}; + +bool MP4File::ShallHaveIods() +{ + u_int32_t compatibleBrandsCount; + MP4StringProperty *pMajorBrandProperty; + + MP4Atom* ftypAtom = m_pRootAtom->FindAtomMP4("ftyp"); + if (ftypAtom == NULL) return false; + + // Check the major brand + (void)ftypAtom->FindProperty( + "ftyp.majorBrand", + (MP4Property**)&pMajorBrandProperty); + ASSERT(pMajorBrandProperty); + for(u_int32_t j = 0 ; brandsWithIods[j] != NULL ; j++) { + if (!strcasecmp( ((MP4StringProperty*)pMajorBrandProperty)->GetValue(), + brandsWithIods[j])) + return true; + } + + // Check the compatible brands + MP4Integer32Property* pCompatibleBrandsCountProperty; + (void)ftypAtom->FindProperty( + "ftyp.compatibleBrandsCount", + (MP4Property**)&pCompatibleBrandsCountProperty); + ASSERT(pCompatibleBrandsCountProperty); + + compatibleBrandsCount = pCompatibleBrandsCountProperty->GetValue(); + + MP4TableProperty* pCompatibleBrandsProperty; + (void)ftypAtom->FindProperty( + "ftyp.compatibleBrands", + (MP4Property**)&pCompatibleBrandsProperty); + + MP4StringProperty* pBrandProperty = (MP4StringProperty*)pCompatibleBrandsProperty->GetProperty(0); + ASSERT(pBrandProperty); + + for(u_int32_t i = 0 ; i < compatibleBrandsCount ; i++) { + for(u_int32_t j = 0 ; brandsWithIods[j] != NULL ; j++) { + if (!strcasecmp(pBrandProperty->GetValue(i), brandsWithIods[j])) + return true; + } + } + + return false; +} + +void MP4File::SetAmrVendor( + MP4TrackId trackId, + u_int32_t vendor) +{ + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.*.damr.vendor", + vendor); +} + +void MP4File::SetAmrDecoderVersion( + MP4TrackId trackId, + u_int8_t decoderVersion) +{ + + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.*.damr.decoderVersion", + decoderVersion); +} + +void MP4File::SetAmrModeSet( + MP4TrackId trackId, + u_int16_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( + u_int32_t timeScale, + u_int16_t modeSet, + u_int8_t modeChangePeriod, + u_int8_t framesPerSample, + bool isAmrWB) +{ + + u_int32_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::AddAudioTrack( + u_int32_t timeScale, + MP4Duration sampleDuration, + u_int8_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"); + + // 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); + + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.mp4a.esds.ESID", +#if 0 + // note - for a file, these values need to + // be 0 - wmay - 04/16/2003 + trackId +#else + 0 +#endif + ); + + 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::AddEncAudioTrack(u_int32_t timeScale, + MP4Duration sampleDuration, + u_int8_t audioType, + u_int32_t scheme_type, + u_int16_t scheme_version, + u_int8_t key_ind_len, + u_int8_t iv_len, + bool selective_enc, + const char *kms_uri, + bool use_ismacryp + ) +{ + u_int32_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); + #if 0 + if (kms_uri != NULL) { + free((void *)kms_uri); + } + #endif + + 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", +#if 0 + // note - for a file, these values need to + // be 0 - wmay - 04/16/2003 + trackId +#else + 0 +#endif + ); + + 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( + u_int32_t timeScale, + MP4Duration sampleDuration, + u_int16_t width, + u_int16_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( + u_int32_t timeScale, + MP4Duration sampleDuration, + u_int16_t width, + u_int16_t height, + u_int8_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", +#if 0 + // note - for a file, these values need to + // be 0 - wmay - 04/16/2003 + trackId +#else + 0 +#endif + ); + + 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(u_int32_t timeScale, + MP4Duration sampleDuration, + u_int16_t width, + u_int16_t height, + u_int8_t videoType, + mp4v2_ismacrypParams *icPp, + const char *oFormat + ) +{ + u_int32_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); + + #if 0 + if (icPp->kms_uri != NULL) { + free(icPp->kms_uri); + } + #endif + + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.encv.esds.ESID", +#if 0 + // note - for a file, these values need to + // be 0 - wmay - 04/16/2003 + trackId +#else + 0 +#endif + ); + + 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( + u_int32_t timeScale, + MP4Duration sampleDuration, + u_int16_t width, + u_int16_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); + + //FIXME - check this + // shouldn't need this + #if 0 + AddChildAtom(MakeTrackName(trackId, + "mdia.minf.stbl.stsd.avc1"), + "avcC"); + #endif + + 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( + u_int32_t timeScale, + MP4Duration sampleDuration, + u_int16_t width, + u_int16_t height, + MP4Atom *srcAtom, + mp4v2_ismacrypParams *icPp) + +{ + + u_int32_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 = FindAtomMP4File(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 = FindAtomMP4File(MakeTrackName(trackId, "mdia.minf.stbl.stsd.avc1.avcC")); + else if (!strcasecmp(format, "encv")) + avcCAtom = FindAtomMP4File(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)) { + VERBOSE_ERROR(m_verbosity, WARNING("Could not find avcC properties")); + 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 = + FindAtomMP4File(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)) { + VERBOSE_ERROR(m_verbosity, + WARNING("Could not find avcC picture table properties")); + 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) == pictLen) { + uint8_t *seq; + uint32_t seqlen; + pUnit->GetValue(&seq, &seqlen, index); + if (memcmp(seq, pPict, pictLen) == 0) { + VERBOSE_WRITE(m_verbosity, + fprintf(stderr, "picture matches %d\n", index)); + free(seq); + return; + } + free(seq); + } + } + } + pLength->AddValue(pictLen); + pUnit->AddValue(pPict, pictLen); + pCount->IncrementValue(); + VERBOSE_WRITE(m_verbosity, + fprintf(stderr, "new picture added %d\n", pCount->GetValue())); + + return; +} +void MP4File::SetH263Vendor( + MP4TrackId trackId, + u_int32_t vendor) +{ + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.s263.d263.vendor", + vendor); +} + +void MP4File::SetH263DecoderVersion( + MP4TrackId trackId, + u_int8_t decoderVersion) +{ + SetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.s263.d263.decoderVersion", + decoderVersion); +} + +void MP4File::SetH263Bitrates( + MP4TrackId trackId, + u_int32_t avgBitrate, + u_int32_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( + u_int32_t timeScale, + MP4Duration sampleDuration, + u_int16_t width, + u_int16_t height, + u_int8_t h263Level, + u_int8_t h263Profile, + u_int32_t avgBitrate, + u_int32_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::AddChapterTextTrack(MP4TrackId refTrackId, u_int32_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 = FindAtomMP4File(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; +} + + +void MP4File::AddChapter(MP4TrackId chapterTrackId, MP4Duration chapterDuration, u_int32_t chapterNr, const char * chapterTitle) +{ + if (0 == chapterTrackId) { + throw new MP4Error("No chapter track given","AddChapter"); + } + + uint32_t sampleLength = 0; + uint8_t sample[1040] = {0}; + int stringLen = 0; + char *string = (char *)&(sample[2]); + + if( chapterTitle != NULL ) + { + stringLen = (int)strlen(chapterTitle); + strncpy( string, chapterTitle, MIN(stringLen, 1023) ); + } + + if( stringLen == 0 || stringLen >= 1024 ) + { + snprintf( string, 1023, "Chapter %03i", chapterNr ); + stringLen = (int)strlen(string); + } + + sampleLength = stringLen + 2 + 12; // Account for text length code and other marker + + // 2-byte length marker + sample[0] = (stringLen >> 8) & 0xff; + sample[1] = stringLen & 0xff; + + int x = 2 + stringLen; + + // 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::AddChapter(MP4Timestamp chapterStart, const char * chapterTitle) +{ + MP4Atom * pChpl = FindAtomMP4File("moov.udta.chpl"); + if (!pChpl) { + pChpl = AddDescendantAtoms("", "moov.udta.chpl"); + } + + char buffer[256]; + int bufferLen = 0; + + MP4Integer32Property * pCount = (MP4Integer32Property*)pChpl->GetProperty(3); + pCount->IncrementValue(); + u_int32_t count = pCount->GetValue(); + + if (0 == chapterTitle) { + snprintf( buffer, 255, "Chapter %03i", count ); + } else { + int len = MIN(255, (int)strlen(chapterTitle)); + strncpy( buffer, chapterTitle, len ); + buffer[len] = 0; + } + bufferLen = (int)strlen(buffer); + + 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); + } + } +} + +void MP4File::ConvertChapters(boolean toQT) +{ + if (toQT) { + MP4Chapters_t * chapters = 0; + u_int32_t chapterCount = 0; + const char * name = 0; + MP4Duration chapterDurationSum = 0; + + GetChaptersList(&chapters, &chapterCount, false); + if (0 == chapterCount) { + throw new MP4Error("Could not find chapter markers", "ConvertChapters"); + } + + // remove chapter track if there is an existing one + DeleteChapters(); + + // create the chapter track + MP4TrackId refTrack = FindTrackId(0, MP4_AUDIO_TRACK_TYPE); + MP4TrackId chapterTrack = AddChapterTextTrack(refTrack, MP4_MILLISECONDS_TIME_SCALE); + + // calculate the duration of the chapter track + MP4Duration chapterTrackDuration = MP4ConvertTime(GetTrackDuration(refTrack), + GetTrackTimeScale(refTrack), + MP4_MILLISECONDS_TIME_SCALE); + + for (u_int32_t chapterIndex = 0 ; chapterIndex < chapterCount; ++chapterIndex) { + // calculate the duration + MP4Duration duration = chapters[chapterIndex].duration; + + // sum up the chapter duration + chapterDurationSum += duration; + + // create and write the chapter track sample for the previous chapter + AddChapter( chapterTrack, duration, chapterIndex+1, chapters[chapterIndex].title ); + } + + MP4Free(chapters); + } else { + MP4Chapters_t * chapters = 0; + u_int32_t chapterCount = 0; + + GetChaptersList(&chapters, &chapterCount); + if (0 == chapterCount) { + throw new MP4Error("Could not find chapter markers", "ConvertChapters"); + } + + // remove existing chapters + DeleteChapters(0, false); + + MP4Duration startTime = 0; + for (u_int32_t i = 0; i < chapterCount; ++i) { + const char * title = chapters[i].title; + MP4Duration duration = chapters[i].duration; + + AddChapter(startTime, title); + startTime += duration * MILLI2HUNDREDNANO; + } + + MP4Free(chapters); + } +} + +void MP4File::DeleteChapters(MP4TrackId chapterTrackId, boolean deleteQT) +{ + if (!deleteQT) { + MP4Atom * pChpl = FindAtomMP4File("moov.udta.chpl"); + if (pChpl) { + MP4Atom * pParent = pChpl->GetParentAtom(); + pParent->DeleteChildAtom(pChpl); + } + return; + } + + char trackName[128] = {0}; + + // no text track given, find a suitable + if (0 == chapterTrackId) { + chapterTrackId = FindChapterTrack(trackName, 127); + } else { + FindChapterReferenceTrack(chapterTrackId, trackName, 127); + } + + if (0 != chapterTrackId && 0 != trackName[0]) { + // remove the reference + RemoveTrackReference(trackName, chapterTrackId); + + // remove the chapter track + DeleteTrack(chapterTrackId); + } +} + +void MP4File::GetChaptersList(MP4Chapters_t ** chapterList, + u_int32_t * chapterCount, + boolean getQT) +{ + *chapterList = 0; + *chapterCount = 0; + + if (!getQT) { + MP4Atom * pChpl = FindAtomMP4File("moov.udta.chpl"); + if (!pChpl) { + throw new MP4Error("Atom moov.udta.chpl does not exist ", "GetChaptersList"); + } + + MP4Integer32Property * pCounter = 0; + MP4TableProperty * pTable = 0; + MP4Integer64Property * pStartTime = 0; + MP4StringProperty * pName = 0; + MP4Duration chapterDurationSum = 0; + const char * name = 0; + + if (!pChpl->FindProperty("chpl.chaptercount", (MP4Property **)&pCounter)) { + throw new MP4Error("Chapter count does not exist ", "GetChaptersList"); + } + + u_int32_t counter = pCounter->GetValue(); + if (0 == counter) { + return; + } + + if (!pChpl->FindProperty("chpl.chapters", (MP4Property **)&pTable)) { + throw new MP4Error("Chapter list does not exist ", "GetChaptersList"); + } + + if (0 == (pStartTime = (MP4Integer64Property *) pTable->GetProperty(0))) { + throw new MP4Error("List of Chapter starttimes does not exist ", "GetChaptersList"); + } + if (0 == (pName = (MP4StringProperty *) pTable->GetProperty(1))) { + throw new MP4Error("List of Chapter titles does not exist ", "GetChaptersList"); + } + + MP4Chapters_t * chapters = (MP4Chapters_t*)MP4Malloc(sizeof(MP4Chapters_t) * counter); + + // get the name of the first chapter + name = pName->GetValue(); + + // process remaining chapters + u_int32_t i, j; + for (i = 0, j = 1; i < counter; ++i, ++j) { + // insert the chapter title + u_int32_t len = MIN((u_int32_t)strlen(name), CHAPTERTITLELEN); + 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; + + // ok, we're done + return; + } + + + u_int8_t * sample = 0; + u_int32_t sampleSize = 0; + MP4Timestamp startTime = 0; + MP4Duration duration = 0; + + // get the chapter track + MP4TrackId chapterTrackId = FindChapterTrack(); + if (0 == chapterTrackId) { + throw new MP4Error("Could not find a chapter track", "GetChaptersList"); + } + + // get infos about the chapters + MP4Track * pChapterTrack = GetTrack(chapterTrackId); + u_int32_t counter = pChapterTrack->GetNumberOfSamples(); + u_int32_t timescale = pChapterTrack->GetTimeScale(); + + MP4Chapters_t * chapters = (MP4Chapters_t*)MP4Malloc(sizeof(MP4Chapters_t) * counter); + + // process all chapter sample + for (u_int32_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 + const char * title = (const char *)&(sample[2]); + int len = MIN((int)strlen(title), CHAPTERTITLELEN); + strncpy(chapters[i].title, title, len); + chapters[i].title[len] = 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; +} + +MP4TrackId MP4File::FindChapterTrack(char * trackName, int trackNameSize) +{ + for (u_int32_t i = 0; i < m_pTracks.Size(); i++) { + if (!strcmp(MP4_TEXT_TRACK_TYPE, m_pTracks[i]->GetType())) { + MP4TrackId refTrackId = FindChapterReferenceTrack(m_pTracks[i]->GetId(), trackName, trackNameSize); + if (0 != refTrackId) { + return m_pTracks[i]->GetId(); + } + } + } + return 0; +} + +MP4TrackId MP4File::FindChapterReferenceTrack(MP4TrackId chapterTrackId, char * trackName, size_t trackNameSize) +{ + for (u_int32_t i = 0; i < m_pTracks.Size(); i++) { + if (!strcmp(MP4_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) { + strncpy(trackName, name, MIN(strlen(name),trackNameSize)); + } + return m_pTracks[i]->GetId(); + } + } + } + return 0; +} + + +void MP4File::DeleteTrack(MP4TrackId trackId) +{ + ProtectWriteOperation("MP4DeleteTrack"); + + u_int32_t trakIndex = FindTrakAtomIndex(trackId); + u_int16_t trackIndex = FindTrackIndex(trackId); + MP4Track* pTrack = m_pTracks[trackIndex]; + + MP4Atom* pTrakAtom = pTrack->GetTrakAtom(); + ASSERT(pTrakAtom); + + MP4Atom* pMoovAtom = FindAtomMP4File("moov"); + ASSERT(pMoovAtom); + + RemoveTrackFromIod(trackId, ShallHaveIods()); + RemoveTrackFromOd(trackId); + + if (trackId == m_odTrackId) { + m_odTrackId = 0; + } + + pMoovAtom->DeleteChildAtom(pTrakAtom); + + m_trakIds.Delete(trakIndex); + + m_pTracks.Delete(trackIndex); + + delete pTrack; + delete pTrakAtom; +} + +u_int32_t MP4File::GetNumberOfTracks(const char* type, u_int8_t subType) +{ + if (type == NULL) { + return m_pTracks.Size(); + } + + u_int32_t typeSeen = 0; + const char* normType = MP4NormalizeTrackType(type, m_verbosity); + + for (u_int32_t i = 0; i < m_pTracks.Size(); i++) { + if (!strcmp(normType, m_pTracks[i]->GetType())) { + if (subType) { + if (normType == MP4_AUDIO_TRACK_TYPE) { + if (subType != GetTrackEsdsObjectTypeId(m_pTracks[i]->GetId())) { + continue; + } + } else if (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 (MP4Error* e) { + // OK, this trackId is not in use, proceed + delete e; + 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 (MP4Error* e) { + // OK, this trackId is not in use, proceed + delete e; + return trackId; + } + } + + // extreme case where mp4 file has 2^16 tracks in it + throw new MP4Error("too many existing tracks", "AddTrack"); + return MP4_INVALID_TRACK_ID; // to keep MSVC happy +} + +MP4TrackId MP4File::FindTrackId(u_int16_t trackIndex, + const char* type, u_int8_t subType) +{ + if (type == NULL) { + return m_pTracks[trackIndex]->GetId(); + } + + u_int32_t typeSeen = 0; + const char* normType = MP4NormalizeTrackType(type, m_verbosity); + + for (u_int32_t i = 0; i < m_pTracks.Size(); i++) { + if (!strcmp(normType, m_pTracks[i]->GetType())) { + if (subType) { + if (normType == MP4_AUDIO_TRACK_TYPE) { + if (subType != GetTrackEsdsObjectTypeId(m_pTracks[i]->GetId())) { + continue; + } + } else if (normType == MP4_VIDEO_TRACK_TYPE) { + if (subType != GetTrackEsdsObjectTypeId(m_pTracks[i]->GetId())) { + continue; + } + } + // else unknown subtype, ignore it + } + + if (trackIndex == typeSeen) { + return m_pTracks[i]->GetId(); + } + + typeSeen++; + } + } + + throw new MP4Error("Track index doesn't exist - track %d type %s", + "FindTrackId", + trackIndex, type); + return MP4_INVALID_TRACK_ID; // satisfy MS compiler +} + +u_int16_t MP4File::FindTrackIndex(MP4TrackId trackId) +{ + for (u_int32_t i = 0; i < m_pTracks.Size() && i <= 0xFFFF; i++) { + if (m_pTracks[i]->GetId() == trackId) { + return (u_int16_t)i; + } + } + + throw new MP4Error("Track id %d doesn't exist", "FindTrackIndex", trackId); + return (u_int16_t)-1; // satisfy MS compiler +} + +u_int16_t MP4File::FindTrakAtomIndex(MP4TrackId trackId) +{ + if (trackId) { + for (u_int32_t i = 0; i < m_trakIds.Size(); i++) { + if (m_trakIds[i] == trackId) { + return i; + } + } + } + + throw new MP4Error("Track id %d doesn't exist", "FindTrakAtomIndex", + trackId); + return (u_int16_t)-1; // satisfy MS compiler +} + +u_int32_t MP4File::GetSampleSize(MP4TrackId trackId, MP4SampleId sampleId) +{ + return m_pTracks[FindTrackIndex(trackId)]->GetSampleSize(sampleId); +} + +u_int32_t MP4File::GetTrackMaxSampleSize(MP4TrackId trackId) +{ + return m_pTracks[FindTrackIndex(trackId)]->GetMaxSampleSize(); +} + +MP4SampleId MP4File::GetSampleIdFromTime(MP4TrackId trackId, + MP4Timestamp when, bool wantSyncSample, bool rewind) +{ + return m_pTracks[FindTrackIndex(trackId)]-> + GetSampleIdFromTime(when, wantSyncSample, rewind); +} + +MP4ChunkId MP4File::GetChunkIdFromTime(MP4TrackId trackId, MP4Timestamp when) +{ + return m_pTracks[FindTrackIndex(trackId)]-> + GetChunkIdFromTime(when); +} + +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, + u_int8_t** ppBytes, u_int32_t* pNumBytes, + MP4Timestamp* pStartTime, MP4Duration* pDuration, + MP4Duration* pRenderingOffset, bool* pIsSyncSample) +{ + m_pTracks[FindTrackIndex(trackId)]-> + ReadSample(sampleId, ppBytes, pNumBytes, + pStartTime, pDuration, pRenderingOffset, pIsSyncSample); +} + +void MP4File::ReadChunk(MP4TrackId trackId, MP4ChunkId sampleId, + u_int8_t** ppBytes, u_int32_t* pNumBytes, + MP4Timestamp* pStartTime, MP4Duration* pDuration) +{ + m_pTracks[FindTrackIndex(trackId)]-> + ReadChunk(sampleId, ppBytes, pNumBytes, pStartTime, pDuration); +} + +void MP4File::WriteSample(MP4TrackId trackId, + const u_int8_t* pBytes, u_int32_t numBytes, + MP4Duration duration, MP4Duration renderingOffset, bool isSyncSample) +{ + ProtectWriteOperation("MP4WriteSample"); + + m_pTracks[FindTrackIndex(trackId)]-> + WriteSample(pBytes, numBytes, duration, renderingOffset, isSyncSample); + + m_pModificationProperty->SetValue(MP4GetAbsTimestamp()); +} + +void MP4File::SetSampleRenderingOffset(MP4TrackId trackId, + MP4SampleId sampleId, MP4Duration renderingOffset) +{ + ProtectWriteOperation("MP4SetSampleRenderingOffset"); + + m_pTracks[FindTrackIndex(trackId)]-> + SetSampleRenderingOffset(sampleId, renderingOffset); + + m_pModificationProperty->SetValue(MP4GetAbsTimestamp()); +} + +char* MP4File::MakeTrackName(MP4TrackId trackId, const char* name) +{ + u_int16_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 FindAtomMP4File(MakeTrackName(trackId, name)); +} + +u_int64_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, + u_int8_t** ppValue, u_int32_t* pValueSize) +{ + GetBytesProperty(MakeTrackName(trackId, name), ppValue, pValueSize); +} + +void MP4File::SetTrackBytesProperty(MP4TrackId trackId, const char* name, + const u_int8_t* pValue, u_int32_t valueSize) +{ + SetBytesProperty(MakeTrackName(trackId, name), pValue, valueSize); +} + + +// file level convenience functions + +MP4Duration MP4File::GetDuration() +{ + return m_pDurationProperty->GetValue(); +} + +void MP4File::SetDuration(MP4Duration value) +{ + m_pDurationProperty->SetValue(value); +} + +u_int32_t MP4File::GetTimeScale() +{ + return m_pTimeScaleProperty->GetValue(); +} + +void MP4File::SetTimeScale(u_int32_t value) +{ + if (value == 0) { + throw new MP4Error("invalid value", "SetTimeScale"); + } + m_pTimeScaleProperty->SetValue(value); +} + +u_int8_t MP4File::GetODProfileLevel() +{ + return GetIntegerProperty("moov.iods.ODProfileLevelId"); +} + +void MP4File::SetODProfileLevel(u_int8_t value) +{ + SetIntegerProperty("moov.iods.ODProfileLevelId", value); +} + +u_int8_t MP4File::GetSceneProfileLevel() +{ + return GetIntegerProperty("moov.iods.sceneProfileLevelId"); +} + +void MP4File::SetSceneProfileLevel(u_int8_t value) +{ + SetIntegerProperty("moov.iods.sceneProfileLevelId", value); +} + +u_int8_t MP4File::GetVideoProfileLevel() +{ + return GetIntegerProperty("moov.iods.visualProfileLevelId"); +} + +void MP4File::SetVideoProfileLevel(u_int8_t value) +{ + SetIntegerProperty("moov.iods.visualProfileLevelId", value); +} + +u_int8_t MP4File::GetAudioProfileLevel() +{ + return GetIntegerProperty("moov.iods.audioProfileLevelId"); +} + +void MP4File::SetAudioProfileLevel(u_int8_t value) +{ + SetIntegerProperty("moov.iods.audioProfileLevelId", value); +} + +u_int8_t MP4File::GetGraphicsProfileLevel() +{ + return GetIntegerProperty("moov.iods.graphicsProfileLevelId"); +} + +void MP4File::SetGraphicsProfileLevel(u_int8_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 (MP4Error* e) { + delete e; + 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, u_int32_t buflen) +{ + u_int32_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(); +} + +MP4ChunkId MP4File::GetTrackNumberOfChunks(MP4TrackId trackId) +{ + return m_pTracks[FindTrackIndex(trackId)]->GetNumberOfChunks(); +} + +const char* MP4File::GetTrackType(MP4TrackId trackId) +{ + return m_pTracks[FindTrackIndex(trackId)]->GetType(); +} + +const char *MP4File::GetTrackMediaDataName (MP4TrackId trackId) +{ + MP4Atom *pChild; + MP4Atom *pAtom = + FindAtomMP4File(MakeTrackName(trackId, + "mdia.minf.stbl.stsd")); + if (!pAtom || pAtom->GetNumberOfChildAtoms() != 1) { + VERBOSE_ERROR(m_verbosity, + fprintf(stderr, "track %d has more than 1 child atoms in stsd\n", trackId)); + return NULL; + } + pChild = pAtom->GetChildAtom(0); + return pChild->GetType(); +} + + +u_int32_t MP4File::GetTrackTimeScale(MP4TrackId trackId) +{ + return m_pTracks[FindTrackIndex(trackId)]->GetTimeScale(); +} + +void MP4File::SetTrackTimeScale(MP4TrackId trackId, u_int32_t value) +{ + if (value == 0) { + throw new MP4Error("invalid value", "SetTrackTimeScale"); + } + SetTrackIntegerProperty(trackId, "mdia.mdhd.timeScale", value); +} + +MP4Duration MP4File::GetTrackDuration(MP4TrackId trackId) +{ + return GetTrackIntegerProperty(trackId, "mdia.mdhd.duration"); +} + +u_int8_t MP4File::GetTrackEsdsObjectTypeId(MP4TrackId trackId) +{ + // changed mp4a to * to handle enca case + try { + return GetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.*.esds.decConfigDescr.objectTypeId"); + } catch (MP4Error *e) { + delete e; + return GetTrackIntegerProperty(trackId, + "mdia.minf.stbl.stsd.*.*.esds.decConfigDescr.objectTypeId"); + } +} + +u_int8_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; + } + + u_int8_t* pEsConfig = NULL; + u_int32_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; + } + + u_int8_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); + u_int64_t + msDuration = + ConvertFromTrackDuration(trackId, + GetTrackDuration(trackId), MP4_MSECS_TIME_SCALE); + + if (msDuration == 0) { + return 0.0; + } + + return ((double)numSamples / UINT64_TO_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") + != (u_int64_t)-1) { + return true; + } + return false; +} + + +void MP4File::GetTrackESConfiguration(MP4TrackId trackId, + u_int8_t** ppConfig, u_int32_t* pConfigSize) +{ + try { + GetTrackBytesProperty(trackId, + "mdia.minf.stbl.stsd.*[0].esds.decConfigDescr.decSpecificInfo[0].info", + ppConfig, pConfigSize); + } catch (MP4Error *e) { + delete e; + GetTrackBytesProperty(trackId, + "mdia.minf.stbl.stsd.*[0].*.esds.decConfigDescr.decSpecificInfo[0].info", + ppConfig, pConfigSize); + } +} + +void MP4File::GetTrackVideoMetadata(MP4TrackId trackId, + u_int8_t** ppConfig, u_int32_t* pConfigSize) +{ + GetTrackBytesProperty(trackId, + "mdia.minf.stbl.stsd.*[0].*.metadata", + ppConfig, pConfigSize); +} + +void MP4File::SetTrackESConfiguration(MP4TrackId trackId, + const u_int8_t* pConfig, u_int32_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 MP4Error("no such property", "MP4SetTrackESConfiguration"); + } + + // 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 = FindAtomMP4File(MakeTrackName(trackId, "mdia.minf.stbl.stsd.avc1.avcC")); + else if (!strcasecmp(format, "encv")) + avcCAtom = FindAtomMP4File(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)) { + VERBOSE_ERROR(m_verbosity, WARNING("Could not find avcC properties")); + 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)) { + VERBOSE_ERROR(m_verbosity, + WARNING("Could not find avcC picture table properties")); + 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 MP4Error("track is not a hint track", + "MP4SetHintTrackSdp"); + } + + (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 (MP4Error* e) { + delete e; + 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, + u_int8_t* pPayloadNumber, + u_int16_t* pMaxPayloadSize, + char **ppEncodingParams) +{ + MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; + + if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { + throw new MP4Error("track is not a hint track", + "MP4GetHintTrackRtpPayload"); + } + + ((MP4RtpHintTrack*)pTrack)->GetPayload( + ppPayloadName, pPayloadNumber, pMaxPayloadSize, ppEncodingParams); +} + +void MP4File::SetHintTrackRtpPayload(MP4TrackId hintTrackId, + const char* payloadName, u_int8_t* pPayloadNumber, u_int16_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 MP4Error("track is not a hint track", + "MP4SetHintTrackRtpPayload"); + } + + u_int8_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); +} + +u_int8_t MP4File::AllocRtpPayloadNumber() +{ + MP4Integer32Array usedPayloads; + u_int32_t i; + + // collect rtp payload numbers in use by existing tracks + for (i = 0; i < m_pTracks.Size(); i++) { + MP4Atom* pTrakAtom = m_pTracks[i]->GetTrakAtom(); + + MP4Integer32Property* pPayloadProperty = NULL; + if (pTrakAtom->FindProperty("trak.udta.hinf.payt.payloadNumber", + (MP4Property**)&pPayloadProperty) && + pPayloadProperty) { + usedPayloads.Add(pPayloadProperty->GetValue()); + } + } + + // search dynamic payload range for an available slot + u_int8_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 MP4Error("no more available rtp payload numbers", + "AllocRtpPayloadNumber"); + } + + return payload; +} + +MP4TrackId MP4File::GetHintTrackReferenceTrackId( + MP4TrackId hintTrackId) +{ + MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; + + if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { + throw new MP4Error("track is not a hint track", + "MP4GetHintTrackReferenceTrackId"); + } + + MP4Track* pRefTrack = ((MP4RtpHintTrack*)pTrack)->GetRefTrack(); + + if (pRefTrack == NULL) { + return MP4_INVALID_TRACK_ID; + } + return pRefTrack->GetId(); +} + +void MP4File::ReadRtpHint( + MP4TrackId hintTrackId, + MP4SampleId hintSampleId, + u_int16_t* pNumPackets) +{ + MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; + + if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { + throw new MP4Error("track is not a hint track", "MP4ReadRtpHint"); + } + ((MP4RtpHintTrack*)pTrack)-> + ReadHint(hintSampleId, pNumPackets); +} + +u_int16_t MP4File::GetRtpHintNumberOfPackets( + MP4TrackId hintTrackId) +{ + MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; + + if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { + throw new MP4Error("track is not a hint track", + "MP4GetRtpHintNumberOfPackets"); + } + return ((MP4RtpHintTrack*)pTrack)->GetHintNumberOfPackets(); +} + +int8_t MP4File::GetRtpPacketBFrame( + MP4TrackId hintTrackId, + u_int16_t packetIndex) +{ + MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; + + if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { + throw new MP4Error("track is not a hint track", + "MP4GetRtpHintBFrame"); + } + return ((MP4RtpHintTrack*)pTrack)->GetPacketBFrame(packetIndex); +} + +int32_t MP4File::GetRtpPacketTransmitOffset( + MP4TrackId hintTrackId, + u_int16_t packetIndex) +{ + MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; + + if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { + throw new MP4Error("track is not a hint track", + "MP4GetRtpPacketTransmitOffset"); + } + return ((MP4RtpHintTrack*)pTrack)->GetPacketTransmitOffset(packetIndex); +} + +void MP4File::ReadRtpPacket( + MP4TrackId hintTrackId, + u_int16_t packetIndex, + u_int8_t** ppBytes, + u_int32_t* pNumBytes, + u_int32_t ssrc, + bool includeHeader, + bool includePayload) +{ + MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; + + if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { + throw new MP4Error("track is not a hint track", "MP4ReadPacket"); + } + ((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 MP4Error("track is not a hint track", + "MP4GetRtpTimestampStart"); + } + 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 MP4Error("track is not a hint track", + "MP4SetRtpTimestampStart"); + } + ((MP4RtpHintTrack*)pTrack)->SetRtpTimestampStart(rtpStart); +} + +void MP4File::AddRtpHint(MP4TrackId hintTrackId, + bool isBframe, u_int32_t timestampOffset) +{ + ProtectWriteOperation("MP4AddRtpHint"); + + MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; + + if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { + throw new MP4Error("track is not a hint track", "MP4AddRtpHint"); + } + ((MP4RtpHintTrack*)pTrack)->AddHint(isBframe, timestampOffset); +} + +void MP4File::AddRtpPacket( + MP4TrackId hintTrackId, bool setMbit, int32_t transmitOffset) +{ + ProtectWriteOperation("MP4AddRtpPacket"); + + MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; + + if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { + throw new MP4Error("track is not a hint track", "MP4AddRtpPacket"); + } + ((MP4RtpHintTrack*)pTrack)->AddPacket(setMbit, transmitOffset); +} + +void MP4File::AddRtpImmediateData(MP4TrackId hintTrackId, + const u_int8_t* pBytes, u_int32_t numBytes) +{ + ProtectWriteOperation("MP4AddRtpImmediateData"); + + MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; + + if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { + throw new MP4Error("track is not a hint track", + "MP4AddRtpImmediateData"); + } + ((MP4RtpHintTrack*)pTrack)->AddImmediateData(pBytes, numBytes); +} + +void MP4File::AddRtpSampleData(MP4TrackId hintTrackId, + MP4SampleId sampleId, u_int32_t dataOffset, u_int32_t dataLength) +{ + ProtectWriteOperation("MP4AddRtpSampleData"); + + MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; + + if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { + throw new MP4Error("track is not a hint track", + "MP4AddRtpSampleData"); + } + ((MP4RtpHintTrack*)pTrack)->AddSampleData( + sampleId, dataOffset, dataLength); +} + +void MP4File::AddRtpESConfigurationPacket(MP4TrackId hintTrackId) +{ + ProtectWriteOperation("MP4AddRtpESConfigurationPacket"); + + MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; + + if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { + throw new MP4Error("track is not a hint track", + "MP4AddRtpESConfigurationPacket"); + } + ((MP4RtpHintTrack*)pTrack)->AddESConfigurationPacket(); +} + +void MP4File::WriteRtpHint(MP4TrackId hintTrackId, + MP4Duration duration, bool isSyncSample) +{ + ProtectWriteOperation("MP4WriteRtpHint"); + + MP4Track* pTrack = m_pTracks[FindTrackIndex(hintTrackId)]; + + if (strcmp(pTrack->GetType(), MP4_HINT_TRACK_TYPE)) { + throw new MP4Error("track is not a hint track", + "MP4WriteRtpHint"); + } + ((MP4RtpHintTrack*)pTrack)->WriteHint(duration, isSyncSample); +} + +u_int64_t MP4File::ConvertFromMovieDuration( + MP4Duration duration, + u_int32_t timeScale) +{ + return MP4ConvertTime((u_int64_t)duration, + GetTimeScale(), timeScale); +} + +u_int64_t MP4File::ConvertFromTrackTimestamp( + MP4TrackId trackId, + MP4Timestamp timeStamp, + u_int32_t timeScale) +{ + return MP4ConvertTime(timeStamp, + GetTrackTimeScale(trackId), timeScale); +} + +MP4Timestamp MP4File::ConvertToTrackTimestamp( + MP4TrackId trackId, + u_int64_t timeStamp, + u_int32_t timeScale) +{ + return (MP4Timestamp)MP4ConvertTime(timeStamp, + timeScale, GetTrackTimeScale(trackId)); +} + +u_int64_t MP4File::ConvertFromTrackDuration( + MP4TrackId trackId, + MP4Duration duration, + u_int32_t timeScale) +{ + return MP4ConvertTime((u_int64_t)duration, + GetTrackTimeScale(trackId), timeScale); +} + +MP4Duration MP4File::ConvertToTrackDuration( + MP4TrackId trackId, + u_int64_t duration, + u_int32_t timeScale) +{ + return (MP4Duration)MP4ConvertTime(duration, + timeScale, GetTrackTimeScale(trackId)); +} + +u_int8_t MP4File::ConvertTrackTypeToStreamType(const char* trackType) +{ + u_int8_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("AddTrackEdit"); + return m_pTracks[FindTrackIndex(trackId)]->AddEdit(editId); +} + +void MP4File::DeleteTrackEdit( + MP4TrackId trackId, + MP4EditId editId) +{ + ProtectWriteOperation("DeleteTrackEdit"); + m_pTracks[FindTrackIndex(trackId)]->DeleteEdit(editId); +} + +u_int32_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); +} + |