/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is MPEG4IP.
 *
 * The Initial Developer of the Original Code is Cisco Systems Inc.
 * Portions created by Cisco Systems Inc. are
 * Copyright (C) Cisco Systems Inc. 2001 - 2004.  All Rights Reserved.
 *
 * 3GPP features implementation is based on 3GPP's TS26.234-v5.60,
 * and was contributed by Ximpo Group Ltd.
 *
 * Portions created by Ximpo Group Ltd. are
 * Copyright (C) Ximpo Group Ltd. 2003, 2004.  All Rights Reserved.
 *
 * Contributor(s):
 *      Dave Mackie       dmackie@cisco.com
 *              Alix Marchandise-Franquet alix@cisco.com
 *              Ximpo Group Ltd.                mp4v2@ximpo.com
 */

#include "src/impl.h"

namespace mp4v2 {
namespace impl {

///////////////////////////////////////////////////////////////////////////////

static const uint8_t BifsV2Config[3] = {
    0x00, 0x00, 0x60 // IsCommandStream = 1, PixelMetric = 1
};

void MP4File::MakeIsmaCompliant(bool addIsmaComplianceSdp)
{
    ProtectWriteOperation(__FILE__, __LINE__, __FUNCTION__);

    if (m_useIsma) {
        // already done
        return;
    }

    // find first audio and/or video tracks

    MP4TrackId audioTrackId = MP4_INVALID_TRACK_ID;
    try {
        audioTrackId = FindTrackId(0, MP4_AUDIO_TRACK_TYPE);
    }
    catch ( Exception *x ) {
        log.errorf(*x);
        delete x;
    }
    MP4TrackId videoTrackId = MP4_INVALID_TRACK_ID;
    try {
        videoTrackId = FindTrackId(0, MP4_VIDEO_TRACK_TYPE);
    }
    catch (Exception* x) {
        log.errorf(*x);
        delete x;
    }
    if (audioTrackId == MP4_INVALID_TRACK_ID &&
            videoTrackId == MP4_INVALID_TRACK_ID) return;

    const char *audio_media_data_name, *video_media_data_name;
    uint8_t videoProfile = 0xff;
    if (audioTrackId != MP4_INVALID_TRACK_ID) {
        audio_media_data_name = MP4GetTrackMediaDataName(this, audioTrackId);
        if (!(ATOMID(audio_media_data_name) == ATOMID("mp4a") ||
                ATOMID(audio_media_data_name) == ATOMID("enca"))) {
            log.errorf("%s: \"%s\": can't make ISMA compliant when file contains an %s track", 
                       __FUNCTION__, GetFilename().c_str(), audio_media_data_name);
            return;
        }
    }
    //
    // Note - might have to check for avc1 here...
    if (videoTrackId != MP4_INVALID_TRACK_ID) {
        video_media_data_name = MP4GetTrackMediaDataName(this, videoTrackId);
        if (!(ATOMID(video_media_data_name) == ATOMID("mp4v") ||
                ATOMID(video_media_data_name) == ATOMID("encv"))) {
            log.errorf("%s: \"%s\": can't make ISMA compliant when file contains an %s track", __FUNCTION__, 
                       GetFilename().c_str(), video_media_data_name);
            return;
        }
        MP4LogLevel verb = log.verbosity;
        log.setVerbosity(MP4_LOG_NONE);
        videoProfile = MP4GetVideoProfileLevel(this, videoTrackId);
        log.setVerbosity(verb);
    }

    m_useIsma = true;

    uint64_t fileMsDuration = 0;
    fileMsDuration =
        ConvertFromMovieDuration(GetDuration(), MP4_MSECS_TIME_SCALE);

    // delete any existing OD track
    if (m_odTrackId != MP4_INVALID_TRACK_ID) {
        DeleteTrack(m_odTrackId);
    }

    if (m_pRootAtom->FindAtom("moov.iods") == NULL) {
        (void)AddChildAtom("moov", "iods");
    }
    (void)AddODTrack();
    SetODProfileLevel(0xFF);

    if (audioTrackId != MP4_INVALID_TRACK_ID) {
        AddTrackToOd(audioTrackId);
        MP4SetAudioProfileLevel(this, 0xf);
    }
    if (videoTrackId != MP4_INVALID_TRACK_ID) {
        AddTrackToOd(videoTrackId);
        MP4SetVideoProfileLevel(this, videoProfile);
    }

    // delete any existing scene track
    MP4TrackId sceneTrackId = MP4_INVALID_TRACK_ID;
    try {
        sceneTrackId = FindTrackId(0, MP4_SCENE_TRACK_TYPE);
    }
    catch (Exception *x) {
        log.errorf(*x);
        delete x;
    }
    if (sceneTrackId != MP4_INVALID_TRACK_ID) {
        DeleteTrack(sceneTrackId);
    }

    // add scene track
    sceneTrackId = AddSceneTrack();
    SetSceneProfileLevel(0xFF);
    SetGraphicsProfileLevel(0xFF);
    SetTrackIntegerProperty(sceneTrackId,
                            "mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr.objectTypeId",
                            MP4SystemsV2ObjectType);

    SetTrackESConfiguration(sceneTrackId,
                            BifsV2Config, sizeof(BifsV2Config));

    uint8_t* pBytes = NULL;
    uint64_t numBytes = 0;

    // write OD Update Command
    CreateIsmaODUpdateCommandFromFileForFile(
        m_odTrackId,
        audioTrackId,
        videoTrackId,
        &pBytes,
        &numBytes);

    WriteSample(m_odTrackId, pBytes, numBytes, fileMsDuration);

    MP4Free(pBytes);
    pBytes = NULL;

    // write BIFS Scene Replace Command
    CreateIsmaSceneCommand(
        MP4_IS_VALID_TRACK_ID(audioTrackId),
        MP4_IS_VALID_TRACK_ID(videoTrackId),
        &pBytes,
        &numBytes);

    WriteSample(sceneTrackId, pBytes, numBytes, fileMsDuration);

    MP4Free(pBytes);
    pBytes = NULL;

    // add session level sdp
    CreateIsmaIodFromFile(
        m_odTrackId,
        sceneTrackId,
        audioTrackId,
        videoTrackId,
        &pBytes,
        &numBytes);

    char* iodBase64 = MP4ToBase64(pBytes, numBytes);

    uint32_t sdpBufLen = (uint32_t)strlen(iodBase64) + 256;
    uint32_t used;
    char* sdpBuf = (char*)MP4Calloc(sdpBufLen);

    if (addIsmaComplianceSdp) {
        strncpy(sdpBuf, "a=isma-compliance:1,1.0,1\015\012", sdpBufLen);
    }

    used = (uint32_t)strlen(sdpBuf);
    sdpBufLen -= used;
    snprintf(&sdpBuf[used], sdpBufLen,
             "a=mpeg4-iod: \042data:application/mpeg4-iod;base64,%s\042\015\012",
             iodBase64);

    SetSessionSdp(sdpBuf);

    log.verbose1f("\"%s\": IOD SDP = %s", GetFilename().c_str(), sdpBuf);

    MP4Free(iodBase64);
    iodBase64 = NULL;
    MP4Free(pBytes);
    pBytes = NULL;
    MP4Free(sdpBuf);
    sdpBuf = NULL;
}

static void CloneIntegerProperty(
    MP4Descriptor* pDest,
    MP4DescriptorProperty* pSrc,
    const char* name)
{
    MP4IntegerProperty* pGetProperty;
    MP4IntegerProperty* pSetProperty;

    if (!pSrc->FindProperty(name, (MP4Property**)&pGetProperty)) return;
    if (!pDest->FindProperty(name, (MP4Property**)&pSetProperty)) return;
    pSetProperty->SetValue(pGetProperty->GetValue());
}

void MP4File::CreateIsmaIodFromFile(
    MP4TrackId odTrackId,
    MP4TrackId sceneTrackId,
    MP4TrackId audioTrackId,
    MP4TrackId videoTrackId,
    uint8_t** ppBytes,
    uint64_t* pNumBytes)
{
    MP4Atom* pIodsAtom = FindAtom("moov.iods");
    ASSERT(pIodsAtom);
    MP4DescriptorProperty* pSrcIod =
        (MP4DescriptorProperty*)pIodsAtom->GetProperty(2);

    MP4Descriptor* pIod = new MP4IODescriptor(*pIodsAtom);
    pIod->SetTag(MP4IODescrTag);
    pIod->Generate();

    CloneIntegerProperty(pIod, pSrcIod, "objectDescriptorId");
    CloneIntegerProperty(pIod, pSrcIod, "ODProfileLevelId");
    CloneIntegerProperty(pIod, pSrcIod, "sceneProfileLevelId");
    CloneIntegerProperty(pIod, pSrcIod, "audioProfileLevelId");
    CloneIntegerProperty(pIod, pSrcIod, "visualProfileLevelId");
    CloneIntegerProperty(pIod, pSrcIod, "graphicsProfileLevelId");

    // mutate esIds from MP4ESIDIncDescrTag to MP4ESDescrTag
    MP4DescriptorProperty* pEsProperty;
    if (!pIod->FindProperty("esIds", (MP4Property**)&pEsProperty)) return;
    pEsProperty->SetTags(MP4ESDescrTag);

    MP4IntegerProperty* pSetProperty;
    MP4IntegerProperty* pSceneESID;
    MP4IntegerProperty* pOdESID;

    // OD
    MP4Descriptor* pOdEsd =
        pEsProperty->AddDescriptor(MP4ESDescrTag);
    pOdEsd->Generate();

    if (!pOdEsd->FindProperty("ESID", (MP4Property**)&pOdESID)) return;

    // we set the OD ESID to a non-zero unique value
    pOdESID->SetValue(m_odTrackId);

    if (pOdEsd->FindProperty("URLFlag",
                             (MP4Property**)&pSetProperty))
        pSetProperty->SetValue(1);

    uint8_t* pBytes;
    uint64_t numBytes;

    CreateIsmaODUpdateCommandFromFileForStream(
        audioTrackId,
        videoTrackId,
        &pBytes,
        &numBytes);

    log.hexDump(0, MP4_LOG_VERBOSE1, pBytes, numBytes, "\"%s\": OD data",
                GetFilename().c_str() );

    char* odCmdBase64 = MP4ToBase64(pBytes, numBytes);

    uint32_t urlBufLen = (uint32_t)strlen(odCmdBase64) + 64;
    char* urlBuf = (char*)MP4Malloc(urlBufLen);

    snprintf(urlBuf, urlBufLen,
             "data:application/mpeg4-od-au;base64,%s",
             odCmdBase64);

    MP4StringProperty* pUrlProperty;
    if (pOdEsd->FindProperty("URL",
                             (MP4Property**)&pUrlProperty))
        pUrlProperty->SetValue(urlBuf);

    log.verbose1f("\"%s\": OD data URL = \042%s\042", GetFilename().c_str(),
                  urlBuf);

    MP4Free(odCmdBase64);
    odCmdBase64 = NULL;
    MP4Free(pBytes);
    pBytes = NULL;
    MP4Free(urlBuf);
    urlBuf = NULL;

    MP4DescriptorProperty* pSrcDcd = NULL;

    // HACK temporarily point to scene decoder config
    (void)FindProperty(MakeTrackName(odTrackId,
                                     "mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr"),
                       (MP4Property**)&pSrcDcd);
    ASSERT(pSrcDcd);
    MP4Property* pOrgOdEsdProperty =
        pOdEsd->GetProperty(8);
    pOdEsd->SetProperty(8, pSrcDcd);

    // bufferSizeDB needs to be set appropriately
    MP4BitfieldProperty* pBufferSizeProperty = NULL;
    if (pOdEsd->FindProperty("decConfigDescr.bufferSizeDB",
                             (MP4Property**)&pBufferSizeProperty)) {
        ASSERT(pBufferSizeProperty);
        pBufferSizeProperty->SetValue(numBytes);
    }

    // SL config needs to change from 2 (file) to 1 (null)
    if (pOdEsd->FindProperty("slConfigDescr.predefined",
                             (MP4Property**)&pSetProperty))
        pSetProperty->SetValue(1);


    // Scene
    MP4Descriptor* pSceneEsd =
        pEsProperty->AddDescriptor(MP4ESDescrTag);
    pSceneEsd->Generate();

    if (pSceneEsd->FindProperty("ESID",
                                (MP4Property**)&pSceneESID)) {
        // we set the Scene ESID to a non-zero unique value
        pSceneESID->SetValue(sceneTrackId);
    }

    if (pSceneEsd->FindProperty("URLFlag",
                                (MP4Property**)&pSetProperty))
        pSetProperty->SetValue(1);

    CreateIsmaSceneCommand(
        MP4_IS_VALID_TRACK_ID(audioTrackId),
        MP4_IS_VALID_TRACK_ID(videoTrackId),
        &pBytes,
        &numBytes);

    log.hexDump(0, MP4_LOG_VERBOSE1, pBytes, numBytes, "\"%s\": Scene data",
                GetFilename().c_str() );

    char *sceneCmdBase64 = MP4ToBase64(pBytes, numBytes);

    urlBuf = (char*)MP4Malloc(strlen(sceneCmdBase64) + 64);
    snprintf(urlBuf, strlen(sceneCmdBase64) + 64,
             "data:application/mpeg4-bifs-au;base64,%s",
             sceneCmdBase64);

    if (pSceneEsd->FindProperty("URL",
                                (MP4Property**)&pUrlProperty))
        pUrlProperty->SetValue(urlBuf);

    log.verbose1f("\"%s\": Scene data URL = \042%s\042",
                  GetFilename().c_str(), urlBuf);

    MP4Free(sceneCmdBase64);
    sceneCmdBase64 = NULL;
    MP4Free(urlBuf);
    urlBuf = NULL;
    MP4Free(pBytes);
    pBytes = NULL;

    // HACK temporarily point to scene decoder config
    ASSERT(FindProperty(MakeTrackName(sceneTrackId,
                                      "mdia.minf.stbl.stsd.mp4s.esds.decConfigDescr"),
                        (MP4Property**)&pSrcDcd));
    ASSERT(pSrcDcd);
    MP4Property* pOrgSceneEsdProperty =
        pSceneEsd->GetProperty(8);
    pSceneEsd->SetProperty(8, pSrcDcd);

    // bufferSizeDB needs to be set
    pBufferSizeProperty = NULL;
    if (pSceneEsd->FindProperty("decConfigDescr.bufferSizeDB",
                                (MP4Property**)&pBufferSizeProperty)) {
        ASSERT(pBufferSizeProperty);
        pBufferSizeProperty->SetValue(numBytes);
    }

    // SL config needs to change from 2 (file) to 1 (null)
    if (pSceneEsd->FindProperty("slConfigDescr.predefined",
                                (MP4Property**)&pSetProperty))
        pSetProperty->SetValue(1);


    // finally get the whole thing written to a memory
    pIod->WriteToMemory(*this, ppBytes, pNumBytes);


    // now carefully replace esd properties before destroying
    pOdEsd->SetProperty(8, pOrgOdEsdProperty);
    pSceneEsd->SetProperty(8, pOrgSceneEsdProperty);
    pSceneESID->SetValue(0); // restore 0 value
    pOdESID->SetValue(0);

    delete pIod;

    log.hexDump(0, MP4_LOG_VERBOSE1, *ppBytes, *pNumBytes, "\"%s\": IOD data",
                GetFilename().c_str() );
}

void MP4File::CreateIsmaIodFromParams(
    uint8_t videoProfile,
    uint32_t videoBitrate,
    uint8_t* videoConfig,
    uint32_t videoConfigLength,
    uint8_t audioProfile,
    uint32_t audioBitrate,
    uint8_t* audioConfig,
    uint32_t audioConfigLength,
    uint8_t** ppIodBytes,
    uint64_t* pIodNumBytes)
{
    MP4IntegerProperty* pInt;
    uint8_t* pBytes = NULL;
    uint64_t numBytes;

    // Descriptor constructors need a parent atom.  In this
    // case we don't have one so create a dummy one.  This
    // is OK as we only build the descriptor and then dump
    // its contents to a buffer
    MP4Atom     dummyParent(*this);

    // Create the IOD
    MP4Descriptor* pIod = new MP4IODescriptor(dummyParent);
    pIod->SetTag(MP4IODescrTag);
    pIod->Generate();

    // Set audio and video profileLevels
    if (pIod->FindProperty("audioProfileLevelId",
                           (MP4Property**)&pInt))
        pInt->SetValue(audioProfile);

    if (pIod->FindProperty("visualProfileLevelId",
                           (MP4Property**)&pInt))
        pInt->SetValue(videoProfile);

    // Mutate esIds from MP4ESIDIncDescrTag to MP4ESDescrTag
    MP4DescriptorProperty* pEsProperty;
    if (!pIod->FindProperty("esIds", (MP4Property**)&pEsProperty)) return;
    pEsProperty->SetTags(MP4ESDescrTag);

    // Add ES Descriptors

    // Scene
    CreateIsmaSceneCommand(
        (audioProfile != 0xFF),
        (videoProfile != 0xFF),
        &pBytes,
        &numBytes);

    log.hexDump(0, MP4_LOG_VERBOSE1, pBytes, numBytes, "\"%s\": Scene data",
                GetFilename().c_str() );

    char* sceneCmdBase64 = MP4ToBase64(pBytes, numBytes);

    char* urlBuf =
        (char*)MP4Malloc(strlen(sceneCmdBase64) + 64);
    snprintf(urlBuf, strlen(sceneCmdBase64) + 64,
             "data:application/mpeg4-bifs-au;base64,%s",
             sceneCmdBase64);

    log.verbose1f("\"%s\": Scene data URL = \042%s\042", GetFilename().c_str(),
                  urlBuf);

    /* MP4Descriptor* pSceneEsd = */
    CreateESD(
        pEsProperty,
        201,                // esid
        MP4SystemsV2ObjectType,
        MP4SceneDescriptionStreamType,
        numBytes,           // bufferSize
        numBytes * 8,       // bitrate
        BifsV2Config,
        sizeof(BifsV2Config),
        urlBuf);
    MP4Free(urlBuf);
    urlBuf = NULL;

    MP4Free(sceneCmdBase64);
    sceneCmdBase64 = NULL;
    MP4Free(pBytes);
    pBytes = NULL;

    // OD

    // Video
    MP4DescriptorProperty* pVideoEsdProperty =
        new MP4DescriptorProperty(dummyParent);
    pVideoEsdProperty->SetTags(MP4ESDescrTag);

    /* MP4Descriptor* pVideoEsd = */
    CreateESD(
        pVideoEsdProperty,
        20,                 // esid
        MP4_MPEG4_VIDEO_TYPE,
        MP4VisualStreamType,
        videoBitrate / 8,   // bufferSize
        videoBitrate,
        videoConfig,
        videoConfigLength,
        NULL);

    // Audio
    MP4DescriptorProperty* pAudioEsdProperty =
        new MP4DescriptorProperty(dummyParent);
    pAudioEsdProperty->SetTags(MP4ESDescrTag);

    /* MP4Descriptor* pAudioEsd = */
    CreateESD(
        pAudioEsdProperty,
        10,                 // esid
        MP4_MPEG4_AUDIO_TYPE,
        MP4AudioStreamType,
        audioBitrate / 8,   // bufferSize
        audioBitrate,
        audioConfig,
        audioConfigLength,
        NULL);

    CreateIsmaODUpdateCommandForStream(
        pAudioEsdProperty,
        pVideoEsdProperty,
        &pBytes,
        &numBytes);

    // cleanup temporary descriptor properties
    delete pAudioEsdProperty;
    delete pVideoEsdProperty;

    log.hexDump(0, MP4_LOG_VERBOSE1,pBytes, numBytes,"\"%s\": OD data = %" PRIu64 " bytes",
                GetFilename().c_str(), numBytes);

    char* odCmdBase64 = MP4ToBase64(pBytes, numBytes);

    urlBuf = (char*)MP4Malloc(strlen(odCmdBase64) + 64);
    if (urlBuf != NULL) {
        snprintf(urlBuf, strlen(odCmdBase64) + 64,
                 "data:application/mpeg4-od-au;base64,%s",
                 odCmdBase64);

        log.verbose1f("\"%s\": OD data URL = \042%s\042", GetFilename().c_str(), urlBuf);

        /* MP4Descriptor* pOdEsd = */
        CreateESD(
            pEsProperty,
            101,
            MP4SystemsV1ObjectType,
            MP4ObjectDescriptionStreamType,
            numBytes,       // bufferSize
            numBytes * 8,   // bitrate
            NULL,           // config
            0,              // configLength
            urlBuf);

        MP4Free(urlBuf);
        urlBuf = NULL;
    }
    MP4Free(odCmdBase64);
    odCmdBase64 = NULL;
    MP4Free(pBytes);
    pBytes = NULL;

    // finally get the whole thing written to a memory
    pIod->WriteToMemory(*this, ppIodBytes, pIodNumBytes);

    delete pIod;

    log.hexDump(0, MP4_LOG_VERBOSE1, *ppIodBytes, *pIodNumBytes,"\"%s\": IOD data",
                GetFilename().c_str() );
}

void MP4File::CreateESD(
    MP4DescriptorProperty* pEsProperty,
    uint32_t esid,
    uint8_t objectType,
    uint8_t streamType,
    uint32_t bufferSize,
    uint32_t bitrate,
    const uint8_t* pConfig,
    uint32_t configLength,
    char* url)
{
    MP4IntegerProperty* pInt;
    MP4StringProperty* pString;
    MP4BytesProperty* pBytes;
    MP4BitfieldProperty* pBits;

    MP4Descriptor* pEsd =
        pEsProperty->AddDescriptor(MP4ESDescrTag);
    pEsd->Generate();

    if (pEsd->FindProperty("ESID",
                           (MP4Property**)&pInt))
        pInt->SetValue(esid);

    if (pEsd->FindProperty("decConfigDescr.objectTypeId",
                           (MP4Property**)&pInt))
        pInt->SetValue(objectType);

    if (pEsd->FindProperty("decConfigDescr.streamType",
                           (MP4Property**)&pInt))
        pInt->SetValue(streamType);

    if (pEsd->FindProperty("decConfigDescr.bufferSizeDB",
                           (MP4Property**)&pInt))
        pInt->SetValue(bufferSize);

    if (pEsd->FindProperty("decConfigDescr.maxBitrate",
                           (MP4Property**)&pInt))
        pInt->SetValue(bitrate);

    if (pEsd->FindProperty("decConfigDescr.avgBitrate",
                           (MP4Property**)&pInt))
        pInt->SetValue(bitrate);

    MP4DescriptorProperty* pConfigDescrProperty;
    if (pEsd->FindProperty("decConfigDescr.decSpecificInfo",
                           (MP4Property**)&pConfigDescrProperty)) {

        MP4Descriptor* pConfigDescr =
            pConfigDescrProperty->AddDescriptor(MP4DecSpecificDescrTag);
        pConfigDescr->Generate();

        if (pConfigDescrProperty->FindProperty("decSpecificInfo[0].info",
                                               (MP4Property**)&pBytes))
            pBytes->SetValue(pConfig, configLength);
    }

    if (pEsd->FindProperty("slConfigDescr.predefined",
                           (MP4Property**)&pInt))
        // changed 12/5/02 from plugfest to value 0
        pInt->SetValue(0);

    if (pEsd->FindProperty("slConfig.useAccessUnitEndFlag",
                           (MP4Property **)&pBits))
        pBits->SetValue(1);

    if (url) {
        if (pEsd->FindProperty("URLFlag",
                               (MP4Property**)&pInt))
            pInt->SetValue(1);

        if (pEsd->FindProperty("URL",
                               (MP4Property**)&pString))
            pString->SetValue(url);
    }

    //return pEsd;
}

void MP4File::CreateIsmaODUpdateCommandFromFileForFile(
    MP4TrackId odTrackId,
    MP4TrackId audioTrackId,
    MP4TrackId videoTrackId,
    uint8_t** ppBytes,
    uint64_t* pNumBytes)
{
    // Descriptor constructors need a parent atom.  In this
    // case we don't have one so create a dummy one.  This
    // is OK as we only build the descriptor and then dump
    // its contents to a buffer
    MP4Atom     dummyParent(*this);
    MP4Descriptor* pCommand = CreateODCommand(dummyParent, MP4ODUpdateODCommandTag);
    pCommand->Generate();

    for (uint8_t i = 0; i < 2; i++) {
        MP4TrackId trackId;
        uint16_t odId;

        if (i == 0) {
            trackId = audioTrackId;
            odId = 10;
        } else {
            trackId = videoTrackId;
            odId = 20;
        }

        if (trackId == MP4_INVALID_TRACK_ID) {
            continue;
        }

        MP4DescriptorProperty* pOdDescrProperty =
            (MP4DescriptorProperty*)(pCommand->GetProperty(0));

        pOdDescrProperty->SetTags(MP4FileODescrTag);

        MP4Descriptor* pOd =
            pOdDescrProperty->AddDescriptor(MP4FileODescrTag);

        pOd->Generate();

        MP4BitfieldProperty* pOdIdProperty = NULL;
        if (pOd->FindProperty("objectDescriptorId",
                              (MP4Property**)&pOdIdProperty))
            pOdIdProperty->SetValue(odId);

        MP4DescriptorProperty* pEsIdsDescriptorProperty = NULL;
        ASSERT(pOd->FindProperty("esIds",
                                 (MP4Property**)&pEsIdsDescriptorProperty));
        ASSERT(pEsIdsDescriptorProperty);

        pEsIdsDescriptorProperty->SetTags(MP4ESIDRefDescrTag);

        MP4Descriptor *pRefDescriptor =
            pEsIdsDescriptorProperty->AddDescriptor(MP4ESIDRefDescrTag);
        pRefDescriptor->Generate();

        MP4Integer16Property* pRefIndexProperty = NULL;
        ASSERT(pRefDescriptor->FindProperty("refIndex",
                                            (MP4Property**)&pRefIndexProperty));
        ASSERT(pRefIndexProperty);

        uint32_t mpodIndex = FindTrackReference(
                                 MakeTrackName(odTrackId, "tref.mpod"), trackId);
        ASSERT(mpodIndex != 0);

        pRefIndexProperty->SetValue(mpodIndex);
    }

    pCommand->WriteToMemory(*this, ppBytes, pNumBytes);

    delete pCommand;
}

void MP4File::CreateIsmaODUpdateCommandFromFileForStream(
    MP4TrackId audioTrackId,
    MP4TrackId videoTrackId,
    uint8_t** ppBytes,
    uint64_t* pNumBytes)
{
    MP4DescriptorProperty* pAudioEsd = NULL;
    MP4Integer8Property* pAudioSLConfigPredef = NULL;
    MP4BitfieldProperty* pAudioAccessUnitEndFlag = NULL;
    int oldAudioUnitEndFlagValue = 0;
    MP4DescriptorProperty* pVideoEsd = NULL;
    MP4Integer8Property* pVideoSLConfigPredef = NULL;
    MP4BitfieldProperty* pVideoAccessUnitEndFlag = NULL;
    int oldVideoUnitEndFlagValue = 0;
    MP4IntegerProperty* pAudioEsdId = NULL;
    MP4IntegerProperty* pVideoEsdId = NULL;

    if (audioTrackId != MP4_INVALID_TRACK_ID) {
        // changed mp4a to * to handle enca case
        MP4Atom* pEsdsAtom =
            FindAtom(MakeTrackName(audioTrackId,
                                   "mdia.minf.stbl.stsd.*.esds"));
        ASSERT(pEsdsAtom);

        pAudioEsd = (MP4DescriptorProperty*)(pEsdsAtom->GetProperty(2));
        // ESID is 0 for file, stream needs to be non-ze
        ASSERT(pAudioEsd->FindProperty("ESID",
                                       (MP4Property**)&pAudioEsdId));

        ASSERT(pAudioEsdId);
        pAudioEsdId->SetValue(audioTrackId);

        // SL config needs to change from 2 (file) to 1 (null)
        if (pAudioEsd->FindProperty("slConfigDescr.predefined",
                                    (MP4Property**)&pAudioSLConfigPredef)) {
            ASSERT(pAudioSLConfigPredef);
            pAudioSLConfigPredef->SetValue(0);
        }

        if (pAudioEsd->FindProperty("slConfigDescr.useAccessUnitEndFlag",
                                    (MP4Property **)&pAudioAccessUnitEndFlag)) {
            oldAudioUnitEndFlagValue =
                pAudioAccessUnitEndFlag->GetValue();
            pAudioAccessUnitEndFlag->SetValue(1);
        }
    }

    if (videoTrackId != MP4_INVALID_TRACK_ID) {
        // changed mp4v to * to handle encv case
        MP4Atom* pEsdsAtom =
            FindAtom(MakeTrackName(videoTrackId,
                                   "mdia.minf.stbl.stsd.*.esds"));
        ASSERT(pEsdsAtom);

        pVideoEsd = (MP4DescriptorProperty*)(pEsdsAtom->GetProperty(2));
        ASSERT(pVideoEsd->FindProperty("ESID",
                                       (MP4Property**)&pVideoEsdId));

        ASSERT(pVideoEsdId);
        pVideoEsdId->SetValue(videoTrackId);

        // SL config needs to change from 2 (file) to 1 (null)
        ASSERT(pVideoEsd->FindProperty("slConfigDescr.predefined",
                                       (MP4Property **)&pVideoSLConfigPredef));
        ASSERT(pVideoSLConfigPredef);
        pVideoSLConfigPredef->SetValue(0);

        if (pVideoEsd->FindProperty("slConfigDescr.useAccessUnitEndFlag",
                                    (MP4Property **)&pVideoAccessUnitEndFlag)) {
            oldVideoUnitEndFlagValue =
                pVideoAccessUnitEndFlag->GetValue();
            pVideoAccessUnitEndFlag->SetValue(1);
        }
    }

    CreateIsmaODUpdateCommandForStream(
        pAudioEsd, pVideoEsd, ppBytes, pNumBytes);
    log.hexDump(0, MP4_LOG_VERBOSE1, *ppBytes, *pNumBytes,
                "\"%s\": After CreateImsaODUpdateCommandForStream len %" PRIu64,
                GetFilename().c_str(), *pNumBytes);

    // return SL config values to 2 (file)
    // return ESID values to 0
    if (pAudioSLConfigPredef) {
        pAudioSLConfigPredef->SetValue(2);
    }
    if (pAudioEsdId) {
        pAudioEsdId->SetValue(0);
    }
    if (pAudioAccessUnitEndFlag) {
        pAudioAccessUnitEndFlag->SetValue(oldAudioUnitEndFlagValue );
    }
    if (pVideoEsdId) {
        pVideoEsdId->SetValue(0);
    }
    if (pVideoSLConfigPredef) {
        pVideoSLConfigPredef->SetValue(2);
    }
    if (pVideoAccessUnitEndFlag) {
        pVideoAccessUnitEndFlag->SetValue(oldVideoUnitEndFlagValue );
    }
}

void MP4File::CreateIsmaODUpdateCommandForStream(
    MP4DescriptorProperty* pAudioEsdProperty,
    MP4DescriptorProperty* pVideoEsdProperty,
    uint8_t** ppBytes,
    uint64_t* pNumBytes)
{
    MP4Descriptor* pAudioOd = NULL;
    MP4Descriptor* pVideoOd = NULL;

    // Descriptor constructors need a parent atom.  In this
    // case we don't have one so create a dummy one.  This
    // is OK as we only build the descriptor and then dump
    // its contents to a buffer
    MP4Atom     dummyParent(*this);
    MP4Descriptor* pCommand =
        CreateODCommand(dummyParent, MP4ODUpdateODCommandTag);
    pCommand->Generate();

    for (uint8_t i = 0; i < 2; i++) {
        uint16_t odId;
        MP4DescriptorProperty* pEsdProperty = NULL;

        if (i == 0) {
            odId = 10;
            pEsdProperty = pAudioEsdProperty;
        } else {
            odId = 20;
            pEsdProperty = pVideoEsdProperty;
        }

        if (pEsdProperty == NULL) {
            continue;
        }

        MP4DescriptorProperty* pOdDescrProperty =
            (MP4DescriptorProperty*)(pCommand->GetProperty(0));

        pOdDescrProperty->SetTags(MP4ODescrTag);

        MP4Descriptor* pOd =
            pOdDescrProperty->AddDescriptor(MP4ODescrTag);
        pOd->Generate();

        if (i == 0) {
            pAudioOd = pOd;
        } else {
            pVideoOd = pOd;
        }

        MP4BitfieldProperty* pOdIdProperty = NULL;
        if (pOd->FindProperty("objectDescriptorId",
                              (MP4Property**)&pOdIdProperty)) {
            pOdIdProperty->SetValue(odId);
        }

        delete (MP4DescriptorProperty*)pOd->GetProperty(4);
        pOd->SetProperty(4, pEsdProperty);
    }

    // serialize OD command
    pCommand->WriteToMemory(*this, ppBytes, pNumBytes);

    // detach from esd descriptor params
    if (pAudioOd) {
        pAudioOd->SetProperty(4, NULL);
    }
    if (pVideoOd) {
        pVideoOd->SetProperty(4, NULL);
    }

    // then destroy
    delete pCommand;
}

void MP4File::CreateIsmaSceneCommand(
    bool hasAudio,
    bool hasVideo,
    uint8_t** ppBytes,
    uint64_t* pNumBytes)
{
    // from ISMA 1.0 Tech Spec Appendix E
    static const uint8_t bifsAudioOnly[] = {
        0xC0, 0x10, 0x12,
        0x81, 0x30, 0x2A, 0x05, 0x6D, 0xC0
    };
    static const uint8_t bifsVideoOnly[] = {
        0xC0, 0x10, 0x12,
        0x61, 0x04,
        0x1F, 0xC0, 0x00, 0x00,
        0x1F, 0xC0, 0x00, 0x00,
        0x44, 0x28, 0x22, 0x82, 0x9F, 0x80
    };
    static const uint8_t bifsAudioVideo[] = {
        0xC0, 0x10, 0x12,
        0x81, 0x30, 0x2A, 0x05, 0x6D, 0x26,
        0x10, 0x41, 0xFC, 0x00, 0x00, 0x01, 0xFC, 0x00, 0x00,
        0x04, 0x42, 0x82, 0x28, 0x29, 0xF8
    };

    if (hasAudio && hasVideo) {
        *pNumBytes = sizeof(bifsAudioVideo);
        *ppBytes = (uint8_t*)MP4Malloc(*pNumBytes);
        memcpy(*ppBytes, bifsAudioVideo, sizeof(bifsAudioVideo));

    } else if (hasAudio) {
        *pNumBytes = sizeof(bifsAudioOnly);
        *ppBytes = (uint8_t*)MP4Malloc(*pNumBytes);
        memcpy(*ppBytes, bifsAudioOnly, sizeof(bifsAudioOnly));

    } else if (hasVideo) {
        *pNumBytes = sizeof(bifsVideoOnly);
        *ppBytes = (uint8_t*)MP4Malloc(*pNumBytes);
        memcpy(*ppBytes, bifsVideoOnly, sizeof(bifsVideoOnly));
    } else {
        *pNumBytes = 0;
        *ppBytes = NULL;
    }
}

///////////////////////////////////////////////////////////////////////////////

}
} // namespace mp4v2::impl
