/*
 * 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.  All Rights Reserved.
 *
 * Contributor(s):
 *      Dave Mackie       dmackie@cisco.com
 *              Alix Marchandise-Franquet alix@cisco.com
 */

#include "mp4common.h"

MP4File::MP4File(u_int32_t verbosity)
{
    m_fileName = NULL;
    m_pFile = NULL;
    m_orgFileSize = 0;
    m_fileSize = 0;
    m_pRootAtom = NULL;
    m_odTrackId = MP4_INVALID_TRACK_ID;

    m_verbosity = verbosity;
    m_mode = 0;
    m_use64bits = false;
    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;

#ifdef USE_FILE_CALLBACKS
    // These are the default for when no callbacks are specified
    m_userData = (void*)this;
    m_MP4fopen = MP4fopen_cb;
    m_MP4fclose = MP4fclose_cb;
    m_MP4fread = MP4fread_cb;
    m_MP4fwrite = MP4fwrite_cb;
    m_MP4fgetpos = MP4fgetpos_cb;
    m_MP4fsetpos = MP4fsetpos_cb;
    m_MP4filesize = MP4filesize_cb;
#endif
}

MP4File::~MP4File()
{
    MP4Free(m_fileName);
    delete m_pRootAtom;
    for (u_int32_t i = 0; i < m_pTracks.Size(); i++) {
        delete m_pTracks[i];
    }
    MP4Free(m_memoryBuffer);    // just in case
}

void MP4File::Read(const char* fileName)
{
    m_fileName = MP4Stralloc(fileName);
    m_mode = 'r';

    Open("rb");

    ReadFromFile();

    CacheProperties();
}

void MP4File::Create(const char* fileName, bool use64bits)
{
    m_fileName = MP4Stralloc(fileName);
    m_mode = 'w';
    m_use64bits = use64bits;

    Open("wb+");

    // generate a skeletal atom tree
    m_pRootAtom = MP4Atom::CreateAtom(NULL);
    m_pRootAtom->SetFile(this);
    m_pRootAtom->Generate();

    CacheProperties();

    // create mdat, and insert it after ftyp, and before moov
    InsertChildAtom(m_pRootAtom, "mdat", 1);

    // start writing
    m_pRootAtom->BeginWrite();
}

void MP4File::Modify(const char* fileName)
{
    m_fileName = MP4Stralloc(fileName);
    m_mode = 'r';

    Open("rb+");
    ReadFromFile();

    m_mode = 'w';

    // find the moov atom
    MP4Atom* pMoovAtom = m_pRootAtom->FindAtom("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();
}

void MP4File::Optimize(const char* orgFileName, const char* newFileName)
{
#if 1//ndef USE_FILE_CALLBACKS
    m_fileName = MP4Stralloc(orgFileName);
    m_mode = 'r';

    // first load meta-info into memory
    Open("rb");
    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);
    }

    FILE* pReadFile = m_pFile;
    m_pFile = NULL;
    m_mode = 'w';

    Open("wb");

    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);

    // finish writing
    ((MP4RootAtom*)m_pRootAtom)->FinishOptimalWrite();

    // cleanup
    fclose(m_pFile);
    m_pFile = NULL;
    fclose(pReadFile);

    // move temporary file into place
    if (newFileName == NULL) {
        Rename(m_fileName, orgFileName);
    }
#else
    throw new MP4Error(errno, "Function not supported when using callbacks", "MP4Optimize");
#endif
}

void MP4File::RewriteMdat(FILE* pReadFile, FILE* pWriteFile)
{
    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_mode = 'r';

        u_int8_t* pChunk;
        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_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 char* fmode)
{
    ASSERT(m_pFile == NULL);

#ifndef USE_FILE_CALLBACKS
#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) {
        m_pFile = fdopen(fd, fmode);
    }
#else
    m_pFile = fopen(m_fileName, fmode);
#endif
    if (m_pFile == NULL) {
        throw new MP4Error(errno, "failed", "MP4Open");
    }
#else
    u_int32_t rc = m_MP4fopen(m_fileName, fmode, m_userData);
    if (rc == 0) {
        throw new MP4Error(errno, "failed", "MP4Open");
    }
#endif

    if (m_mode == 'r') {
#ifndef USE_FILE_CALLBACKS
        struct stat s;
        if (fstat(fileno(m_pFile), &s) < 0) {
            throw new MP4Error(errno, "stat failed", "MP4Open");
        }
        m_orgFileSize = m_fileSize = s.st_size;
#else
        int64_t s = m_MP4filesize(m_userData);
        if (s < 0) {
            throw new MP4Error(errno, "retreiving filesize failed", "MP4Open");
        }
        m_orgFileSize = m_fileSize = (u_int64_t)s;
#endif
    } 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->FindAtom(trackName);

        // done, no more trak atoms
        if (pTrakAtom == NULL) {
            break;
        }

        // find track id property
        MP4Integer32Property* pTrackIdProperty = NULL;
        pTrakAtom->FindProperty(
            "trak.tkhd.trackId",
            (MP4Property**)&pTrackIdProperty);

        // find track type property
        MP4StringProperty* pTypeProperty = NULL;
        pTrakAtom->FindProperty(
            "trak.mdia.hdlr.handlerType",
            (MP4Property**)&pTypeProperty);

        // ensure we have the basics properties
        if (pTrackIdProperty && pTypeProperty) {

            m_trakIds.Add(pTrackIdProperty->GetValue());

            MP4Track* pTrack = NULL;
            try {
                if (!strcmp(pTypeProperty->GetValue(), MP4_HINT_TRACK_TYPE)) {
                    pTrack = new MP4RtpHintTrack(this, pTrakAtom);
                } else {
                    pTrack = new MP4Track(this, pTrakAtom);
                }
                m_pTracks.Add(pTrack);
            }
            catch (MP4Error* e) {
                VERBOSE_ERROR(m_verbosity, e->Print());
                delete e;
            }

            // remember when we encounter the OD track
            if (pTrack && !strcmp(pTrack->GetType(), MP4_OD_TRACK_TYPE)) {
                if (m_odTrackId == MP4_INVALID_TRACK_ID) {
                    m_odTrackId = pTrackIdProperty->GetValue();
                } else {
                    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);
        pFreeAtom->SetSize((u_int64_t)MAX((int64_t)m_orgFileSize - ((int64_t)m_fileSize + 8), 0));
        pFreeAtom->Write();
        delete pFreeAtom;
    }
}

MP4Duration MP4File::UpdateDuration(MP4Duration duration)
{
    MP4Duration currentDuration = GetDuration();
    if (duration > currentDuration) {
        SetDuration(duration);
        return duration;
    }
    return currentDuration;
}

void MP4File::Dump(FILE* pDumpFile, bool dumpImplicits)
{
    if (pDumpFile == NULL) {
        pDumpFile = stdout;
    }

    fprintf(pDumpFile, "Dumping %s meta-information...\n", m_fileName);
    m_pRootAtom->Dump(pDumpFile, 0, dumpImplicits);
}

void MP4File::Close()
{
    if (m_mode == 'w') {
        SetIntegerProperty("moov.mvhd.modificationTime",
            MP4GetAbsTimestamp());

        FinishWrite();
    }

#ifndef USE_FILE_CALLBACKS
    fclose(m_pFile);
    m_pFile = NULL;
#else
    m_MP4fclose(m_userData);
#endif
}

const 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
    static char tempFileName[64];
    u_int32_t i;
    for (i = getpid(); i < 0xFFFFFFFF; i++) {
        sprintf(tempFileName, "./tmp%u.mp4", i);
        if (access(tempFileName, F_OK) != 0) {
            break;
        }
    }
    if (i == 0xFFFFFFFF) {
        throw new MP4Error("can't create temporary file", "TempFileName");
    }
#else
    static char tempFileName[MAX_PATH + 3];
    GetTempFileName(".", // dir. for temp. files
                    "mp4",                // temp. filename prefix
                    0,                    // create unique name
                    tempFileName);        // buffer for name
#endif

    return tempFileName;
}

void MP4File::Rename(const char* oldFileName, const char* newFileName)
{
    int rc;

#ifdef _WIN32
    rc = remove(newFileName);
    if (rc == 0) {
        rc = rename(oldFileName, newFileName);
    }
    else
        remove(oldFileName);

#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::FindAtom(const char* name)
{
    MP4Atom* pAtom = NULL;
    if (!name || !strcmp(name, "")) {
        pAtom = m_pRootAtom;
    } else {
        pAtom = m_pRootAtom->FindAtom(name);
    }
    return pAtom;
}

MP4Atom* MP4File::AddChildAtom(
    const char* parentName,
    const char* childName)
{
    return AddChildAtom(FindAtom(parentName), childName);
}

MP4Atom* MP4File::AddChildAtom(
    MP4Atom* pParentAtom,
    const char* childName)
{
    return InsertChildAtom(pParentAtom, childName,
        pParentAtom->GetNumberOfChildAtoms());
}

MP4Atom* MP4File::InsertChildAtom(
    const char* parentName,
    const char* childName,
    u_int32_t index)
{
    return InsertChildAtom(FindAtom(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(FindAtom(ancestorName), descendantNames);
}

MP4Atom* MP4File::AddDescendantAtoms(
    MP4Atom* pAncestorAtom, const char* descendantNames)
{
    ASSERT(pAncestorAtom);

    MP4Atom* pParentAtom = pAncestorAtom;
    MP4Atom* pChildAtom = NULL;

    while (true) {
        char* childName = MP4NameFirst(descendantNames);

        if (childName == NULL) {
            break;
        }

        descendantNames = MP4NameAfterFirst(descendantNames);

        pChildAtom = pParentAtom->FindChildAtom(childName);

        if (pChildAtom == NULL) {
            pChildAtom = AddChildAtom(pParentAtom, childName);
        }

        pParentAtom = pChildAtom;

        MP4Free(childName);
    }

    return pChildAtom;
}

bool MP4File::FindProperty(const char* name,
    MP4Property** ppProperty, 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;
    pTrakAtom->FindProperty(
        "trak.tkhd.trackId", (MP4Property**)&pInteger32Property);
    ASSERT(pInteger32Property);
    pInteger32Property->SetValue(trackId);

    // set track type
    const char* normType = MP4Track::NormalizeTrackType(type);

    // 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;
    pTrakAtom->FindProperty(
        "trak.mdia.hdlr.handlerType", (MP4Property**)&pStringProperty);
    ASSERT(pStringProperty);
    pStringProperty->SetValue(normType);

    // set track time scale
    pInteger32Property = NULL;
    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;
    m_pRootAtom->FindProperty("moov.iods.esIds",
        (MP4Property**)&pDescriptorProperty);
    ASSERT(pDescriptorProperty);

    MP4Descriptor* pDescriptor =
        pDescriptorProperty->AddDescriptor(MP4ESIDIncDescrTag);
    ASSERT(pDescriptor);

    MP4Integer32Property* pIdProperty = NULL;
    pDescriptor->FindProperty("id",
        (MP4Property**)&pIdProperty);
    ASSERT(pIdProperty);

    pIdProperty->SetValue(trackId);
}

void MP4File::RemoveTrackFromIod(MP4TrackId trackId)
{
    MP4DescriptorProperty* pDescriptorProperty = NULL;
    m_pRootAtom->FindProperty("moov.iods.esIds",
        (MP4Property**)&pDescriptorProperty);
    ASSERT(pDescriptorProperty);

    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;
        pDescriptorProperty->FindProperty(name,
            (MP4Property**)&pIdProperty);
        ASSERT(pIdProperty);

        if (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");
    m_pRootAtom->FindProperty(propName, ppCountProperty);
    ASSERT(*ppCountProperty);

    snprintf(propName, sizeof(propName), "%s.%s", trefName, "entries.trackId");
    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 =
        FindAtom(MakeTrackName(trackId, "mdia.minf.dinf.dref"));
    ASSERT(pDrefAtom);

    MP4Integer32Property* pCountProperty = NULL;
    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;
        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 = MP4Track::NormalizeTrackType(type);

    // TBD if user type, fix name to four chars, and warn

    MP4TrackId trackId = AddTrack(type, MP4_MSECS_TIME_SCALE);

    InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "nmhd", 0);

    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);

    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;
}

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);

    InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0);

    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)
{
  MP4TrackId trackId = AddTrack(MP4_AUDIO_TRACK_TYPE, timeScale);

  AddTrackToOd(trackId);

  SetTrackFloatProperty(trackId, "tkhd.volume", 1.0);

  InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "smhd", 0);

  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();

  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::AddVideoTrack(
    u_int32_t timeScale,
    MP4Duration sampleDuration,
    u_int16_t width,
    u_int16_t height,
    u_int8_t videoType)
{
    MP4TrackId trackId = AddTrack(MP4_VIDEO_TRACK_TYPE, timeScale);

    AddTrackToOd(trackId);

    SetTrackFloatProperty(trackId, "tkhd.width", width);
    SetTrackFloatProperty(trackId, "tkhd.height", height);

    InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "vmhd", 0);

    AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "mp4v");

    // 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.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);

    SetTrackIntegerProperty(trackId,
        "mdia.minf.stbl.stsz.sampleSize", sampleDuration);

    m_pTracks[FindTrackIndex(trackId)]->
        SetFixedSampleDuration(sampleDuration);

    return trackId;
}

MP4TrackId MP4File::AddEncVideoTrack(u_int32_t timeScale,
                     MP4Duration sampleDuration,
                     u_int16_t width,
                     u_int16_t height,
                     u_int8_t videoType)
{
  MP4TrackId trackId = AddTrack(MP4_VIDEO_TRACK_TYPE, timeScale);

  AddTrackToOd(trackId);

  SetTrackFloatProperty(trackId, "tkhd.width", width);
  SetTrackFloatProperty(trackId, "tkhd.height", height);

  InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "vmhd", 0);

  AddChildAtom(MakeTrackName(trackId, "mdia.minf.stbl.stsd"), "encv");

  // 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 encv atom
  MP4Integer32Property* pStsdCountProperty;
  FindIntegerProperty(
              MakeTrackName(trackId, "mdia.minf.stbl.stsd.entryCount"),
              (MP4Property**)&pStsdCountProperty);
  pStsdCountProperty->IncrementValue();

  SetTrackIntegerProperty(trackId,
              "mdia.minf.stbl.stsd.encv.width", width);
  SetTrackIntegerProperty(trackId,
              "mdia.minf.stbl.stsd.encv.height", height);

  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);

  SetTrackIntegerProperty(trackId,
              "mdia.minf.stbl.stsz.sampleSize", sampleDuration);

  m_pTracks[FindTrackIndex(trackId)]->
    SetFixedSampleDuration(sampleDuration);

  return trackId;
}

MP4TrackId MP4File::AddHintTrack(MP4TrackId refTrackId)
{
    // validate reference track id
    FindTrackIndex(refTrackId);

    MP4TrackId trackId =
        AddTrack(MP4_HINT_TRACK_TYPE, GetTrackTimeScale(refTrackId));

    InsertChildAtom(MakeTrackName(trackId, "mdia.minf"), "hmhd", 0);

    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));

    AddDescendantAtoms(MakeTrackName(trackId, NULL), "tref.hint");

    AddTrackReference(MakeTrackName(trackId, "tref.hint"), refTrackId);

    AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.hnti.sdp ");

    AddDescendantAtoms(MakeTrackName(trackId, NULL), "udta.hinf");

    return trackId;
}

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 = FindAtom("moov");
    ASSERT(pMoovAtom);

    RemoveTrackFromIod(trackId);
    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 = MP4Track::NormalizeTrackType(type);

    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 {
            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 {
            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 = MP4Track::NormalizeTrackType(type);

  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)
{
    return m_pTracks[FindTrackIndex(trackId)]->
        GetSampleIdFromTime(when, wantSyncSample);
}

MP4Timestamp MP4File::GetSampleTime(
    MP4TrackId trackId, MP4SampleId sampleId)
{
    MP4Timestamp timestamp;
    m_pTracks[FindTrackIndex(trackId)]->
        GetSampleTimes(sampleId, &timestamp, 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::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);

    static char trakName[1024];
    if (name == NULL || name[0] == '\0') {
        snprintf(trakName, sizeof(trakName),
            "moov.trak[%u]", trakIndex);
    } else {
        snprintf(trakName, sizeof(trakName),
            "moov.trak[%u].%s", trakIndex, name);
    }
    return trakName;
}

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)
{
    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);
}


// track level convenience functions

MP4SampleId MP4File::GetTrackNumberOfSamples(MP4TrackId trackId)
{
    return m_pTracks[FindTrackIndex(trackId)]->GetNumberOfSamples();
}

const char* MP4File::GetTrackType(MP4TrackId trackId)
{
    return m_pTracks[FindTrackIndex(trackId)]->GetType();
}

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");
}

// now GetTrackEsdsObjectTypeId
u_int8_t MP4File::GetTrackAudioType(MP4TrackId trackId)
{
        return GetTrackIntegerProperty(trackId,
                "mdia.minf.stbl.stsd.mp4a.esds.decConfigDescr.objectTypeId");
}

u_int8_t MP4File::GetTrackEsdsObjectTypeId(MP4TrackId trackId)
{
    // changed mp4a to * to handle enca case
    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) {
        return MP4_MPEG4_INVALID_AUDIO_TYPE;
    }

    u_int8_t mpeg4Type = (pEsConfig[0] >> 3);

    free(pEsConfig);

    return mpeg4Type;
}

// replaced with GetTrackEsdsObjectTypeId
u_int8_t MP4File::GetTrackVideoType(MP4TrackId trackId)
{
    return GetTrackIntegerProperty(trackId,
        "mdia.minf.stbl.stsd.mp4v.esds.decConfigDescr.objectTypeId");
}

MP4Duration MP4File::GetTrackFixedSampleDuration(MP4TrackId trackId)
{
    return m_pTracks[FindTrackIndex(trackId)]->GetFixedSampleDuration();
}

float 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;
}

void MP4File::GetTrackESConfiguration(MP4TrackId trackId,
    u_int8_t** ppConfig, u_int32_t* pConfigSize)
{
    GetTrackBytesProperty(trackId,
        "mdia.minf.stbl.stsd.*[0].esds.decConfigDescr.decSpecificInfo[0].info",
        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;
    FindProperty(MakeTrackName(trackId,
        "mdia.minf.stbl.stsd.*[0].esds.decConfigDescr.decSpecificInfo"),
        (MP4Property**)&pConfigDescrProperty);

    if (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;
    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();

        pConfigDescrProperty->FindProperty(
            "decSpecificInfo[0].info",
            (MP4Property**)&pInfoProperty);
        ASSERT(pInfoProperty);
    }

    // set the value
    pInfoProperty->SetValue(pConfig, configSize);
}

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");
    }

    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;
        pTrakAtom->FindProperty("trak.udta.hinf.payt.payloadNumber",
            (MP4Property**)&pPayloadProperty);

        if (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);

    static char editName[1024];
    snprintf(editName, sizeof(editName),
        "%s.edts.elst.entries[%u].%s",
        trakName, editId - 1, name);
    return 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);
}

