blob: 7ecc89f4d0f9819cc054a54a1d374135ff22d44c [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2000-2026
* All rights reserved
*
* This file is part of GPAC / ISO Media File Format sub-project
*
* GPAC is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GPAC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <gpac/internal/isomedia_dev.h>
#include <gpac/constants.h>
#ifndef GPAC_DISABLE_ISOM
GF_TrackBox *GetTrackbyID(GF_MovieBox *moov, GF_ISOTrackID TrackID)
{
GF_TrackBox *trak;
u32 i;
if (!moov) return NULL;
i=0;
while ((trak = (GF_TrackBox *)gf_list_enum(moov->trackList, &i))) {
if (trak->Header->trackID == TrackID) return trak;
}
return NULL;
}
GF_TrackBox *gf_isom_get_track(GF_MovieBox *moov, u32 trackNumber)
{
if (!moov) return NULL;
//no need to check for these, they will anyway result in NULL returned from gf_list_get
// if (!moov || !trackNumber || (trackNumber > gf_list_count(moov->trackList))) return NULL;
return (GF_TrackBox*)gf_list_get(moov->trackList, trackNumber - 1);
}
//get the number of a track given its ID
//return 0 if not found error
u32 gf_isom_get_tracknum_from_id(GF_MovieBox *moov, GF_ISOTrackID trackID)
{
u32 i;
GF_TrackBox *trak;
i=0;
while ((trak = (GF_TrackBox *)gf_list_enum(moov->trackList, &i))) {
if (trak->Header->trackID == trackID) return i;
}
return 0;
}
//extraction of the ESD from the track
GF_Err GetESD(GF_MovieBox *moov, GF_ISOTrackID trackID, u32 StreamDescIndex, GF_ESD **outESD)
{
GF_Err e;
GF_ESD *esd;
u32 track_num = 0;
u32 k;
GF_SampleTableBox *stbl;
GF_TrackBox *trak, *OCRTrack;
GF_TrackReferenceTypeBox *dpnd;
GF_SLConfig *slc;
GF_MPEGSampleEntryBox *entry;
if (!moov) return GF_ISOM_INVALID_FILE;
track_num = gf_isom_get_tracknum_from_id(moov, trackID);
dpnd = NULL;
*outESD = NULL;
trak = gf_isom_get_track(moov, track_num);
if (!trak) return GF_ISOM_INVALID_FILE;
e = Media_GetESD(trak->Media, StreamDescIndex, &esd, 0);
if (e) return e;
if (!esd) return GF_NON_COMPLIANT_BITSTREAM;
e = Media_GetSampleDesc(trak->Media, StreamDescIndex, (GF_SampleEntryBox **) &entry, NULL);
if (e) return e;
//set the ID
esd->ESID = trackID;
//find stream dependencies: dpnd, sbas and scal
for (k=0; k<3; k++) {
u32 ref = GF_ISOM_BOX_TYPE_DPND;
if (k==1) ref = GF_ISOM_REF_BASE;
else if (k==2) ref = GF_ISOM_REF_SCAL;
e = Track_FindRef(trak, ref , &dpnd);
if (e) return e;
if (dpnd && dpnd->trackIDCount) {
//ONLY ONE STREAM DEPENDENCY IS ALLOWED
if (!k && (dpnd->trackIDCount != 1)) return GF_ISOM_INVALID_MEDIA;
//fix the spec: where is the index located ??
esd->dependsOnESID = dpnd->trackIDs[0];
break;
} else {
esd->dependsOnESID = 0;
}
}
if (trak->udta) {
GF_UserDataMap *map;
u32 i = 0;
while ((map = (GF_UserDataMap*)gf_list_enum(trak->udta->recordList, &i))) {
if (map->boxType == GF_ISOM_BOX_TYPE_AUXV) {
GF_Descriptor *d = gf_odf_desc_new(GF_ODF_AUX_VIDEO_DATA);
gf_list_add(esd->extensionDescriptors, d);
break;
}
}
}
//OK, get the OCR (in a REAL MP4File, OCR is 0 in ESD and is specified through track reference
dpnd = NULL;
OCRTrack = NULL;
//find OCR dependencies
e = Track_FindRef(trak, GF_ISOM_BOX_TYPE_SYNC, &dpnd);
if (e) return e;
if (dpnd) {
if (dpnd->trackIDCount != 1) return GF_ISOM_INVALID_MEDIA;
esd->OCRESID = dpnd->trackIDs[0];
OCRTrack = gf_isom_get_track_from_id(trak->moov, dpnd->trackIDs[0]);
while (OCRTrack) {
/*if we have a dependency on a track that doesn't have OCR dep, remove that dependency*/
e = Track_FindRef(OCRTrack, GF_ISOM_BOX_TYPE_SYNC, &dpnd);
if (e || !dpnd || !dpnd->trackIDCount) {
OCRTrack = NULL;
goto default_sync;
}
/*this is explicit desync*/
if ((dpnd->trackIDs[0]==0) || (dpnd->trackIDs[0]==OCRTrack->Header->trackID))
break;
/*loop in OCRs, break it*/
if (esd->ESID == (u16) OCRTrack->Header->trackID) {
OCRTrack = NULL;
goto default_sync;
}
/*check next*/
OCRTrack = gf_isom_get_track_from_id(trak->moov, dpnd->trackIDs[0]);
}
if (!OCRTrack) goto default_sync;
} else {
default_sync:
/*all tracks are sync'ed by default*/
if (trak->moov->mov->es_id_default_sync<0) {
if (esd->OCRESID)
trak->moov->mov->es_id_default_sync = esd->OCRESID;
else
trak->moov->mov->es_id_default_sync = esd->ESID;
}
if (trak->moov->mov->es_id_default_sync) esd->OCRESID = (u16) trak->moov->mov->es_id_default_sync;
/*cf ESD writer*/
if (esd->OCRESID == esd->ESID) esd->OCRESID = 0;
}
//update the IPI stuff if needed
if (esd->ipiPtr != NULL) {
dpnd = NULL;
e = Track_FindRef(trak, GF_ISOM_BOX_TYPE_IPIR, &dpnd);
if (e) return e;
if (dpnd) {
if (esd->ipiPtr->tag != GF_ODF_ISOM_IPI_PTR_TAG) return GF_ISOM_INVALID_FILE;
//OK, retrieve the ID: the IPI_ES_Id is currently the ref track
esd->ipiPtr->IPI_ES_Id = dpnd->trackIDs[esd->ipiPtr->IPI_ES_Id - 1];
//and change the tag
esd->ipiPtr->tag = GF_ODF_IPI_PTR_TAG;
} else {
return GF_ISOM_INVALID_FILE;
}
}
if ((trak->Media->mediaHeader->packedLanguage[0] != 'u')
|| (trak->Media->mediaHeader->packedLanguage[1] != 'n')
|| (trak->Media->mediaHeader->packedLanguage[2] != 'd') ) {
if (!esd->langDesc) esd->langDesc = (GF_Language *) gf_odf_desc_new(GF_ODF_LANG_TAG);
esd->langDesc->langCode = trak->Media->mediaHeader->packedLanguage[0];
esd->langDesc->langCode <<= 8;
esd->langDesc->langCode |= trak->Media->mediaHeader->packedLanguage[1];
esd->langDesc->langCode <<= 8;
esd->langDesc->langCode |= trak->Media->mediaHeader->packedLanguage[2];
}
{
u16 rvc_predefined;
u8 *rvc_cfg_data;
const char *mime_type;
u32 rvc_cfg_size;
e = gf_isom_get_rvc_config(moov->mov, track_num, 1, &rvc_predefined, &rvc_cfg_data, &rvc_cfg_size, &mime_type);
if (e==GF_OK) {
if (rvc_predefined) {
esd->decoderConfig->predefined_rvc_config = rvc_predefined;
} else {
if (mime_type && !strcmp(mime_type, "application/rvc-config+xml+gz") ) {
esd->decoderConfig->rvc_config = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG);
#if !defined(GPAC_DISABLE_ZLIB)
gf_gz_decompress_payload_ex(rvc_cfg_data, rvc_cfg_size, &esd->decoderConfig->rvc_config->data, &esd->decoderConfig->rvc_config->dataLength, GF_TRUE);
gf_free(rvc_cfg_data);
#endif
} else if (rvc_cfg_data && rvc_cfg_size) {
esd->decoderConfig->rvc_config = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG);
esd->decoderConfig->rvc_config->data = rvc_cfg_data;
esd->decoderConfig->rvc_config->dataLength = rvc_cfg_size;
}
}
}
}
/*normally all files shall be stored with predefined=SLPredef_MP4, but of course some are broken (philips)
so we just check the ESD_URL. If set, use the given cfg, otherwise always rewrite it*/
if (esd->URLString != NULL) {
*outESD = esd;
return GF_OK;
}
//if we are in publishing mode and we have an SLConfig specified, use it as is
switch (entry->type) {
case GF_ISOM_BOX_TYPE_MP4V:
slc = ((GF_MPEGVisualSampleEntryBox *)entry)->slc;
break;
case GF_ISOM_BOX_TYPE_MP4A:
slc = ((GF_MPEGAudioSampleEntryBox *)entry)->slc;
break;
case GF_ISOM_BOX_TYPE_MP4S:
slc = entry->slc;
break;
default:
slc = NULL;
break;
}
if (slc) {
gf_odf_desc_del((GF_Descriptor *)esd->slConfig);
gf_odf_desc_copy((GF_Descriptor *)slc, (GF_Descriptor **)&esd->slConfig);
*outESD = esd;
return GF_OK;
}
//otherwise use the regular mapping
if (!esd->slConfig)
esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG);
//this is a desc for a media in the file, let's rewrite some param
esd->slConfig->timestampLength = 32;
esd->slConfig->timestampResolution = trak->Media->mediaHeader->timeScale;
//NO OCR from MP4File streams (eg, constant OC Res one)
esd->slConfig->OCRLength = 0;
esd->slConfig->OCRResolution = 0;
// if (OCRTrack) esd->slConfig->OCRResolution = OCRTrack->Media->mediaHeader->timeScale;
stbl = trak->Media->information->sampleTable;
// a little optimization here: if all our samples are sync,
//set the RAPOnly to true... for external users...
if (! stbl->SyncSample) {
if (
#ifndef GPAC_DISABLE_ISOM_FRAGMENTS
moov->mvex &&
#endif
esd->decoderConfig && esd->decoderConfig->streamType &&
(esd->decoderConfig->streamType==GF_STREAM_VISUAL)
) {
esd->slConfig->hasRandomAccessUnitsOnlyFlag = 0;
esd->slConfig->useRandomAccessPointFlag = 1;
if (trak->moov->mov->openMode!=GF_ISOM_OPEN_READ) {
stbl->SyncSample = (GF_SyncSampleBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSS);
if (!stbl->SyncSample) return GF_OUT_OF_MEM;
}
} else {
esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1;
esd->slConfig->useRandomAccessPointFlag = 0;
}
} else {
esd->slConfig->hasRandomAccessUnitsOnlyFlag = 0;
//signal we are NOT using sync points if no info is present in the table
esd->slConfig->useRandomAccessPointFlag = stbl->SyncSample->nb_entries ? 1 : 0;
}
//change to support reflecting OD streams
esd->slConfig->useAccessUnitEndFlag = 1;
esd->slConfig->useAccessUnitStartFlag = 1;
//signal we do have padding flag (since we only use logical SL packet
//the user can decide whether to use the info or not
esd->slConfig->usePaddingFlag = stbl->PaddingBits ? 1 : 0;
//same with degradation priority
esd->slConfig->degradationPriorityLength = stbl->DegradationPriority ? 32 : 0;
//this new SL will be OUT OF THE FILE. Let's set its predefined to 0
esd->slConfig->predefined = 0;
*outESD = esd;
return GF_OK;
}
//extraction of the ESD from the track for the given time
GF_Err GetESDForTime(GF_MovieBox *moov, GF_ISOTrackID trackID, u64 CTS, GF_ESD **outESD)
{
GF_Err e;
u32 sampleDescIndex;
GF_TrackBox *trak;
trak = gf_isom_get_track(moov, gf_isom_get_tracknum_from_id(moov, trackID));
if (!trak) return GF_ISOM_INVALID_FILE;
e = Media_GetSampleDescIndex(trak->Media, CTS, &sampleDescIndex );
if (e) return e;
return GetESD(moov, trackID, sampleDescIndex, outESD);
}
GF_Err Track_FindRef(GF_TrackBox *trak, u32 ReferenceType, GF_TrackReferenceTypeBox **dpnd)
{
GF_TrackReferenceBox *ref;
GF_TrackReferenceTypeBox *a;
u32 i;
if (! trak) return GF_BAD_PARAM;
if (! trak->References) {
*dpnd = NULL;
return GF_OK;
}
ref = trak->References;
i=0;
while ((a = (GF_TrackReferenceTypeBox *)gf_list_enum(ref->child_boxes, &i))) {
if (a->reference_type == ReferenceType) {
*dpnd = a;
return GF_OK;
}
}
*dpnd = NULL;
return GF_OK;
}
Bool Track_IsMPEG4Stream(u32 HandlerType)
{
switch (HandlerType) {
case GF_ISOM_MEDIA_VISUAL:
case GF_ISOM_MEDIA_AUXV:
case GF_ISOM_MEDIA_PICT:
case GF_ISOM_MEDIA_AUDIO:
case GF_ISOM_MEDIA_SUBPIC:
case GF_ISOM_MEDIA_OD:
case GF_ISOM_MEDIA_OCR:
case GF_ISOM_MEDIA_SCENE:
case GF_ISOM_MEDIA_MPEG7:
case GF_ISOM_MEDIA_OCI:
case GF_ISOM_MEDIA_IPMP:
case GF_ISOM_MEDIA_MPEGJ:
case GF_ISOM_MEDIA_ESM:
return 1;
/*Timedtext is NOT an MPEG-4 stream*/
default:
/*consider xxsm as MPEG-4 handlers*/
if ( (((HandlerType>>8) & 0xFF)== 's') && ((HandlerType& 0xFF)== 'm'))
return 1;
return 0;
}
}
GF_Err SetTrackDurationEx(GF_TrackBox *trak, Bool keep_utc)
{
u64 trackDuration=0xFFFFFFFF;
u32 i;
GF_Err e = GF_OK;
//the total duration is the media duration: adjust it in case...
if (!trak->extl) {
e = Media_SetDuration(trak);
if (e) return e;
//assert the timeScales are non-NULL
if (!trak->moov->mvhd || !trak->moov->mvhd->timeScale || !trak->Media->mediaHeader->timeScale) return GF_ISOM_INVALID_FILE;
trackDuration = (trak->Media->mediaHeader->duration * trak->moov->mvhd->timeScale) / trak->Media->mediaHeader->timeScale;
}
//if we have an edit list, the duration is the sum of all the editList
//entries' duration (always expressed in MovieTimeScale)
if (trak->editBox && trak->editBox->editList) {
GF_EdtsEntry *ent;
GF_EditListBox *elst = trak->editBox->editList;
trackDuration = 0;
i=0;
while ((ent = (GF_EdtsEntry*)gf_list_enum(elst->entryList, &i))) {
trackDuration += ent->segmentDuration;
}
}
if (!trackDuration && trak->Media) {
trackDuration = (trak->Media->mediaHeader->duration * trak->moov->mvhd->timeScale) / trak->Media->mediaHeader->timeScale;
}
if (!trak->Header || (trak->extl && (trackDuration==0xFFFFFFFF))) {
return GF_OK;
}
trak->Header->duration = trackDuration;
if (!keep_utc && !trak->moov->mov->keep_utc && !gf_sys_is_test_mode() )
trak->Header->modificationTime = gf_isom_get_mp4time();
return GF_OK;
}
GF_Err SetTrackDuration(GF_TrackBox *trak)
{
return SetTrackDurationEx(trak, GF_FALSE);
}
#ifndef GPAC_DISABLE_ISOM_FRAGMENTS
#ifdef GF_ENABLE_CTRN
GF_TrunEntry *traf_get_sample_entry(GF_TrackFragmentBox *traf, u32 sample_index)
{
u32 i, idx;
GF_TrackFragmentRunBox *trun;
idx=0;
i=0;
while ((trun = (GF_TrackFragmentRunBox *)gf_list_enum(traf->TrackRuns, &i))) {
u32 j;
for (j=0; j<trun->sample_count; j++) {
GF_TrunEntry *ent = gf_list_get(trun->entries, j);
if (idx==sample_index) return ent;
if (ent->nb_pack>1) {
if (idx < sample_index + ent->nb_pack)
return ent;
idx += ent->nb_pack;
} else {
idx++;
}
}
}
return NULL;
}
#endif
static u32 saio_get_index(GF_TrackFragmentBox *traf, u32 sample_idx)
{
u32 k, all_samples=0, saio_idx=0;
for (k=0; k<gf_list_count(traf->TrackRuns); k++) {
GF_TrackFragmentRunBox *trun = gf_list_get(traf->TrackRuns, k);
all_samples+=trun->nb_samples;
if (sample_idx<all_samples) break;
saio_idx++;
}
return saio_idx;
}
GF_Err MergeTrack(GF_TrackBox *trak, GF_TrackFragmentBox *traf, GF_MovieFragmentBox *moof_box, u64 moof_offset, s32 compressed_diff, u64 *cumulated_offset)
{
u32 i, j, chunk_size, track_num;
u64 base_offset, data_offset, traf_duration, tfdt;
u32 def_duration, DescIndex, def_size, def_flags;
u32 duration, size, flags, prev_trun_data_offset, num_first_sample_in_traf;
u8 pad, sync;
u16 degr;
Bool first_samp_in_traf=GF_TRUE;
Bool store_traf_map=GF_FALSE;
u8 *moof_template=NULL;
u32 moof_template_size=0;
Bool is_seg_start = GF_FALSE;
u64 seg_start=0, sidx_start=0, sidx_end=0, frag_start=0, last_dts=0;
GF_TrackFragmentRunBox *trun;
GF_TrunEntry *ent;
#ifdef GF_ENABLE_CTRN
GF_TrackFragmentBox *traf_ref = NULL;
u32 sample_index;
#endif
Bool is_first_merge = !trak->first_traf_merged;
Bool patch_no_dur;
GF_Err stbl_AppendTime(GF_SampleTableBox *stbl, u32 duration, u32 nb_pack);
GF_Err stbl_AppendSize(GF_SampleTableBox *stbl, u32 size, u32 nb_pack);
GF_Err stbl_AppendChunk(GF_SampleTableBox *stbl, u64 offset);
GF_Err stbl_AppendSampleToChunk(GF_SampleTableBox *stbl, u32 DescIndex, u32 samplesInChunk);
GF_Err stbl_AppendCTSOffset(GF_SampleTableBox *stbl, s32 CTSOffset);
GF_Err stbl_AppendRAP(GF_SampleTableBox *stbl, u8 isRap);
GF_Err stbl_AppendPadding(GF_SampleTableBox *stbl, u8 padding);
GF_Err stbl_AppendDegradation(GF_SampleTableBox *stbl, u16 DegradationPriority);
if (trak->Header->trackID != traf->tfhd->trackID) return GF_OK;
if (!trak->Media->information->sampleTable
|| !trak->Media->information->sampleTable->SampleSize
|| !trak->Media->information->sampleTable->TimeToSample
|| !trak->Media->information->sampleTable->SampleToChunk
|| !trak->Media->information->sampleTable->ChunkOffset
) {
return GF_ISOM_INVALID_FILE;
}
if (!traf->trex->track)
traf->trex->track = trak;
//setup all our defaults
DescIndex = (traf->tfhd->flags & GF_ISOM_TRAF_SAMPLE_DESC) ? traf->tfhd->sample_desc_index : traf->trex->def_sample_desc_index;
if (!DescIndex) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] default sample description set to 0, likely broken ! Fixing to 1\n" ));
DescIndex = 1;
} else if (DescIndex > gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] default sample description set to %d but only %d sample description(s), likely broken ! Fixing to 1\n", DescIndex, gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes)));
DescIndex = 1;
}
#ifdef GF_ENABLE_CTRN
if (traf->trex->inherit_from_traf_id) {
u32 traf_count = gf_list_count(moof_box->TrackList);
for (i=0; i<traf_count; i++) {
GF_TrackFragmentBox *atraf = gf_list_get(moof_box->TrackList, i);
if (atraf->tfhd && atraf->tfhd->trackID==traf->trex->inherit_from_traf_id) {
traf_ref = atraf;
break;
}
}
}
#endif
def_duration = (traf->tfhd->flags & GF_ISOM_TRAF_SAMPLE_DUR) ? traf->tfhd->def_sample_duration : traf->trex->def_sample_duration;
def_size = (traf->tfhd->flags & GF_ISOM_TRAF_SAMPLE_SIZE) ? traf->tfhd->def_sample_size : traf->trex->def_sample_size;
def_flags = (traf->tfhd->flags & GF_ISOM_TRAF_SAMPLE_FLAGS) ? traf->tfhd->def_sample_flags : traf->trex->def_sample_flags;
patch_no_dur = gf_isom_is_video_handler_type(trak->Media->handler->handlerType);
//locate base offset, by default use moof (dash-like)
base_offset = moof_offset;
//explicit base offset, use it
if (traf->tfhd->flags & GF_ISOM_TRAF_BASE_OFFSET)
base_offset = traf->tfhd->base_data_offset;
//no moof offset and no explicit offset, the offset is the end of the last written chunk of
//the previous traf. For the first traf, *cumulated_offset is actually moof offset
else if (!(traf->tfhd->flags & GF_ISOM_MOOF_BASE_OFFSET))
base_offset = *cumulated_offset;
chunk_size = 0;
prev_trun_data_offset = 0;
data_offset = 0;
traf_duration = 0;
num_first_sample_in_traf = trak->Media->information->sampleTable->SampleSize->sampleCount;
if (traf->tfdt)
tfdt = 1+traf->tfdt->baseMediaDecodeTime;
else if (traf->tfxd)
tfdt = 1+traf->tfxd->absolute_time_in_track_timescale;
else
tfdt = 0;
if (tfdt) {
tfdt -= 1;
//do this test for each fragment merged as soon as we have a tfdt, so that we detect samples with extended duration
//if trak->moov->mov->NextMoofNumber is 0 we initialize or seek so skip test
if (trak->moov->mov->NextMoofNumber && trak->dts_at_next_frag_start) {
s32 diff = (s32) ((s64) tfdt - (s64) trak->dts_at_next_frag_start);
if (diff < 0) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[iso file] Warning: TFDT timing "LLD" less than cumulated timing "LLD" - using tfdt\n", tfdt, trak->dts_at_next_frag_start ));
}
//sample dur was extended, adjust track duration
else if (diff > 0) {
GF_LOG(GF_LOG_INFO, GF_LOG_CONTAINER, ("[iso file] TFDT timing "LLD" higher than cumulated timing "LLD" (last sample got extended in duration)\n", tfdt, trak->dts_at_next_frag_start ));
traf_duration += diff;
if (!is_first_merge) {
u64 last_DTS;
u32 last_duration;
stbl_GetSampleDTS_and_Duration(trak->Media->information->sampleTable->TimeToSample, trak->Media->information->sampleTable->SampleSize->sampleCount, &last_DTS, &last_duration);
stbl_RemoveDTS(trak->Media->information->sampleTable, trak->Media->information->sampleTable->SampleSize->sampleCount, 1, 0);
stbl_AppendTime(trak->Media->information->sampleTable, last_duration+diff, 1);
}
}
}
//remember dts if this is the first fragment we merge (either after a table reset or at first segment start)
if (is_first_merge) {
trak->dts_at_seg_start = tfdt;
trak->dts_at_next_frag_start = tfdt;
}
} else if (is_first_merge && trak->moov->mov->is_smooth) {
trak->dts_at_seg_start = trak->dts_at_next_frag_start;
}
if (traf->tfxd) {
trak->last_tfxd_value = traf->tfxd->absolute_time_in_track_timescale;
trak->last_tfxd_value += traf->tfxd->fragment_duration_in_track_timescale;
}
if (traf->tfrf) {
if (trak->tfrf) gf_isom_box_del_parent(&trak->child_boxes, (GF_Box *)trak->tfrf);
trak->tfrf = traf->tfrf;
gf_list_del_item(traf->child_boxes, traf->tfrf);
gf_list_add(trak->child_boxes, trak->tfrf);
}
if (traf->SampleRefs) {
if (!trak->Media->information->sampleTable->SampleRefs) {
trak->Media->information->sampleTable->SampleRefs = traf->SampleRefs;
gf_list_add(trak->Media->information->sampleTable->child_boxes, traf->SampleRefs);
gf_list_del_item(traf->child_boxes, traf->SampleRefs);
traf->SampleRefs = NULL;
} else {
gf_list_transfer(trak->Media->information->sampleTable->SampleRefs->entries, traf->SampleRefs->entries);
}
}
if (trak->moov->mov->signal_frag_bounds) {
store_traf_map = GF_TRUE;
if (is_first_merge) {
GF_MovieFragmentBox *moof_clone = NULL;
gf_isom_box_freeze_order((GF_Box *)moof_box);
#ifndef GPAC_DISABLE_ISOM_WRITE
gf_isom_clone_box((GF_Box *)moof_box, (GF_Box **)&moof_clone);
if (moof_clone) {
GF_BitStream *bs;
for (i=0; i<gf_list_count(moof_clone->TrackList); i++) {
GF_TrackFragmentBox *traf_clone = gf_list_get(moof_clone->TrackList, i);
gf_isom_box_array_reset_parent(&traf_clone->child_boxes, traf_clone->TrackRuns);
gf_isom_box_array_reset_parent(&traf_clone->child_boxes, traf_clone->sampleGroups);
gf_isom_box_array_reset_parent(&traf_clone->child_boxes, traf_clone->sampleGroupsDescription);
gf_isom_box_array_reset_parent(&traf_clone->child_boxes, traf_clone->sub_samples);
gf_isom_box_array_reset_parent(&traf_clone->child_boxes, traf_clone->sai_offsets);
gf_isom_box_array_reset_parent(&traf_clone->child_boxes, traf_clone->sai_sizes);
if (traf_clone->sample_encryption) {
gf_isom_box_del_parent(&traf_clone->child_boxes, (GF_Box *) traf_clone->sample_encryption);
traf_clone->sample_encryption = NULL;
}
if (traf_clone->sdtp) {
gf_isom_box_del_parent(&traf_clone->child_boxes, (GF_Box *) traf_clone->sdtp);
traf_clone->sdtp = NULL;
}
if (traf_clone->SampleRefs) {
gf_isom_box_del_parent(&traf_clone->child_boxes, (GF_Box *) traf_clone->SampleRefs);
traf_clone->SampleRefs = NULL;
}
}
gf_isom_box_size((GF_Box *)moof_clone);
bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
if (trak->moov->mov->seg_styp) {
gf_isom_box_size(trak->moov->mov->seg_styp);
gf_isom_box_write(trak->moov->mov->seg_styp, bs);
}
if (trak->moov->mov->root_sidx) {
gf_isom_box_size((GF_Box *)trak->moov->mov->root_sidx);
gf_isom_box_write((GF_Box *)trak->moov->mov->root_sidx, bs);
}
if (trak->moov->mov->seg_ssix) {
gf_isom_box_size(trak->moov->mov->seg_ssix);
gf_isom_box_write(trak->moov->mov->seg_ssix, bs);
}
gf_isom_box_write((GF_Box *)moof_clone, bs);
gf_isom_box_del((GF_Box*)moof_clone);
gf_bs_get_content(bs, &moof_template, &moof_template_size);
gf_bs_del(bs);
}
#endif
}
if (trak->moov->mov->seg_styp) {
is_seg_start = GF_TRUE;
seg_start = trak->moov->mov->styp_start_offset;
}
if (trak->moov->mov->root_sidx) {
is_seg_start = GF_TRUE;
sidx_start = trak->moov->mov->sidx_start_offset;
sidx_end = trak->moov->mov->sidx_end_offset;
if (! seg_start || (sidx_start<seg_start))
seg_start = sidx_start;
}
frag_start = trak->moov->mov->current_top_box_start;
}
else if (trak->moov->mov->store_traf_map) {
store_traf_map = GF_TRUE;
}
u64 max_end = 0;
#ifdef GF_ENABLE_CTRN
sample_index = 0;
#endif
i=0;
while ((trun = (GF_TrackFragmentRunBox *)gf_list_enum(traf->TrackRuns, &i))) {
if (! (trun->flags & (GF_ISOM_TRUN_DURATION | GF_ISOM_TRUN_SIZE | GF_ISOM_TRUN_FLAGS | GF_ISOM_TRUN_CTS_OFFSET) ) ) {
if (!def_size || (trun->sample_count>0x10000)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Invalid track run for track %d - default size %d num samples %d\n", traf->trex->trackID, def_size, trun->sample_count));
return GF_ISOM_INVALID_FILE;
}
}
//merge the run
for (j=0; j<trun->sample_count; j++) {
GF_Err e;
s32 cts_offset=0;
if (j<trun->nb_samples) {
ent = &trun->samples[j];
} else {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Track %d doesn't have enough trun entries (%d) compared to sample count (%d) in run\n", traf->trex->trackID, trun->nb_samples, trun->sample_count ));
break;
}
size = def_size;
duration = def_duration;
flags = def_flags;
//CTS - if flag not set (trun or ctrn) defaults to 0 which is the base value after alloc
//we just need to overwrite its value if inherited
cts_offset = ent->CTS_Offset;
#ifdef GF_ENABLE_CTRN
if (trun->use_ctrn) {
if (!j && (trun->ctrn_flags & GF_ISOM_CTRN_FIRST_SAMPLE) ) {
if (trun->ctrn_first_dur) duration = ent->Duration;
if (trun->ctrn_first_size) size = ent->size;
if (trun->ctrn_first_ctts) flags = ent->flags;
} else {
if (trun->ctrn_dur) duration = ent->Duration;
if (trun->ctrn_size) size = ent->size;
if (trun->ctrn_sample_flags) flags = ent->flags;
}
/*re-override*/
if (trun->ctrn_flags & 0xF0) {
GF_TrunEntry *ref_entry;
if (!traf_ref) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Track %d use traf inheritance to track ID %d but reference traf not found\n", traf->trex->trackID, traf->trex->inherit_from_traf_id ));
break;
}
ref_entry = traf_get_sample_entry(traf_ref, sample_index);
if (!ref_entry) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Track %d use traf inheritance but sample %d not found in reference traf\n", traf->trex->trackID, sample_index+1 ));
break;
}
if (trun->ctrn_flags & GF_ISOM_CTRN_INHERIT_DUR)
duration = ref_entry->Duration;
if (trun->ctrn_flags & GF_ISOM_CTRN_INHERIT_SIZE)
size = ref_entry->size;
if (trun->ctrn_flags & GF_ISOM_CTRN_INHERIT_FLAGS)
flags = ref_entry->flags;
if (trun->ctrn_flags & GF_ISOM_CTRN_INHERIT_CTSO)
cts_offset = ref_entry->CTS_Offset;
}
} else
#endif
{
if (trun->flags & GF_ISOM_TRUN_DURATION) duration = ent->Duration;
if (trun->flags & GF_ISOM_TRUN_SIZE) size = ent->size;
if (trun->flags & GF_ISOM_TRUN_FLAGS) {
flags = ent->flags;
} else if (!j && (trun->flags & GF_ISOM_TRUN_FIRST_FLAG)) {
flags = trun->first_sample_flags;
}
}
#ifdef GF_ENABLE_CTRN
sample_index++;
#endif
//fix for broken truns with empty duration for frame
if (patch_no_dur && !duration && (trun->flags & GF_ISOM_TRUN_DURATION)) {
duration = trun->min_duration;
}
/*store the resolved value in case we have inheritance*/
ent->size = size;
ent->Duration = duration;
ent->flags = flags;
ent->CTS_Offset = cts_offset;
last_dts += duration;
//add size first
if (!trak->Media->information->sampleTable->SampleSize) {
trak->Media->information->sampleTable->SampleSize = (GF_SampleSizeBox *) gf_isom_box_new_parent(&trak->Media->information->sampleTable->child_boxes, GF_ISOM_BOX_TYPE_STSZ);
if (!trak->Media->information->sampleTable->SampleSize)
return GF_OUT_OF_MEM;
}
e = stbl_AppendSize(trak->Media->information->sampleTable, size, ent->nb_pack);
if (e) return e;
//then TS
if (!trak->Media->information->sampleTable->TimeToSample) {
trak->Media->information->sampleTable->TimeToSample = (GF_TimeToSampleBox *) gf_isom_box_new_parent(&trak->Media->information->sampleTable->child_boxes, GF_ISOM_BOX_TYPE_STTS);
if (!trak->Media->information->sampleTable->TimeToSample)
return GF_OUT_OF_MEM;
}
e = stbl_AppendTime(trak->Media->information->sampleTable, duration, ent->nb_pack);
if (e) return e;
//add chunk on first sample
if (!j) {
u64 final_offset;
data_offset = base_offset;
//we have an explicit data offset for this trun
if (trun->flags & GF_ISOM_TRUN_DATA_OFFSET) {
data_offset += trun->data_offset;
/*reset chunk size since data is now relative to this trun*/
chunk_size = 0;
/*remember this data offset for following trun*/
prev_trun_data_offset = trun->data_offset;
/*if mdat is located after the moof, and the moof was compressed, adjust offset
otherwise the offset does not need adjustment*/
if (trun->data_offset>=0) {
data_offset -= compressed_diff;
prev_trun_data_offset -= compressed_diff;
}
}
//we had an explicit data offset for the previous trun, use it + chunk size
else if (prev_trun_data_offset) {
/*data offset is previous chunk size plus previous offset of the trun*/
data_offset += prev_trun_data_offset + chunk_size;
}
//no explicit data offset, continuous data after last data in previous chunk
else {
data_offset += chunk_size;
//data offset of first trun in first traf, adjust if compressed moof
if ((i==1) && (trun->data_offset>=0)) {
data_offset -= compressed_diff;
}
}
final_offset = data_offset;
//adjust offset if moov was also compressed and we are still in the same file
//so that later call to gf_isom_get_sample properly adjust back the offset
if (trak->moov->compressed_diff) {
final_offset += trak->moov->compressed_diff;
}
if (!trak->Media->information->sampleTable->ChunkOffset) {
trak->Media->information->sampleTable->ChunkOffset = gf_isom_box_new_parent(&trak->Media->information->sampleTable->child_boxes, GF_ISOM_BOX_TYPE_STCO);
if (!trak->Media->information->sampleTable->ChunkOffset)
return GF_OUT_OF_MEM;
}
e = stbl_AppendChunk(trak->Media->information->sampleTable, final_offset);
if (e) return e;
//then sampleToChunk
if (!trak->Media->information->sampleTable->SampleToChunk) {
trak->Media->information->sampleTable->SampleToChunk = (GF_SampleToChunkBox *) gf_isom_box_new_parent(&trak->Media->information->sampleTable->child_boxes, GF_ISOM_BOX_TYPE_STSC);
if (!trak->Media->information->sampleTable->SampleToChunk)
return GF_OUT_OF_MEM;
}
e = stbl_AppendSampleToChunk(trak->Media->information->sampleTable,
DescIndex, trun->sample_count);
if (e) return e;
}
chunk_size += size;
if (store_traf_map && first_samp_in_traf) {
first_samp_in_traf = GF_FALSE;
e = stbl_AppendTrafMap(trak->moov->mov, trak->Media->information->sampleTable, is_seg_start, seg_start, frag_start, tfdt ? tfdt : trak->dts_at_next_frag_start, moof_template, moof_template_size, sidx_start, sidx_end, ent->nb_pack);
if (e) return e;
//do not deallocate, the memory is now owned by traf map
moof_template = NULL;
moof_template_size = 0;
}
if (ent->nb_pack>1) {
j+= ent->nb_pack-1;
traf_duration += ent->nb_pack*duration;
last_dts += (ent->nb_pack-1)*duration;
continue;
}
traf_duration += duration;
e = stbl_AppendCTSOffset(trak->Media->information->sampleTable, cts_offset);
if (e) return e;
//flags
sync = GF_ISOM_GET_FRAG_SYNC(flags);
if (trak->Media->information->sampleTable->no_sync_found && sync) {
trak->Media->information->sampleTable->no_sync_found = 0;
}
e = stbl_AppendRAP(trak->Media->information->sampleTable, sync);
if (e) return e;
pad = GF_ISOM_GET_FRAG_PAD(flags);
if (pad) {
e = stbl_AppendPadding(trak->Media->information->sampleTable, pad);
if (e) return e;
}
degr = GF_ISOM_GET_FRAG_DEG(flags);
if (degr) {
e = stbl_AppendDegradation(trak->Media->information->sampleTable, degr);
if (e) return e;
}
e = stbl_AppendDependencyType(trak->Media->information->sampleTable, GF_ISOM_GET_FRAG_LEAD(flags), GF_ISOM_GET_FRAG_DEPENDS(flags), GF_ISOM_GET_FRAG_DEPENDED(flags), GF_ISOM_GET_FRAG_REDUNDANT(flags));
if (e) return e;
}
u64 data_offset_end = data_offset + chunk_size;
if (!max_end || (max_end<data_offset_end))
max_end = data_offset_end;
}
//remember target next dts - last_dts is the duration in media timescale, does not include tfdt
trak->dts_at_next_frag_start += last_dts;
if (traf_duration && trak->editBox && trak->editBox->editList) {
//append to last edit only, adding edits on the fly is not possible in isobmff
GF_EdtsEntry *edts_e = gf_list_last(trak->editBox->editList->entryList);
if (edts_e && (edts_e->was_empty_dur || !edts_e->segmentDuration)) {
//extend last edit duration by the amount of media received in fragment (traf duration)
//regardless of the mediaTime offset of the edit (cf #2985)
u64 extend_dur = traf_duration;
extend_dur *= trak->moov->mvhd->timeScale;
extend_dur /= trak->Media->mediaHeader->timeScale;
edts_e->segmentDuration += extend_dur;
edts_e->was_empty_dur = GF_TRUE;
}
}
//in any case, update the cumulated offset
//this will handle hypothetical files mixing MOOF offset and implicit non-moof offset
*cumulated_offset = data_offset + chunk_size;
/*merge sample groups*/
if (traf->sampleGroups) {
GF_List *groups;
GF_List *groupDescs;
Bool is_identical_sgpd = GF_TRUE;
u32 *new_idx = NULL, new_idx_count=0;
if (!trak->Media->information->sampleTable->sampleGroups)
trak->Media->information->sampleTable->sampleGroups = gf_list_new();
if (!trak->Media->information->sampleTable->sampleGroupsDescription)
trak->Media->information->sampleTable->sampleGroupsDescription = gf_list_new();
groupDescs = trak->Media->information->sampleTable->sampleGroupsDescription;
for (i=0; i<gf_list_count(traf->sampleGroupsDescription); i++) {
GF_SampleGroupDescriptionBox *new_sgdesc = NULL;
GF_SampleGroupDescriptionBox *sgdesc = gf_list_get(traf->sampleGroupsDescription, i);
for (j=0; j<gf_list_count(groupDescs); j++) {
new_sgdesc = gf_list_get(groupDescs, j);
if (new_sgdesc->grouping_type==sgdesc->grouping_type) break;
new_sgdesc = NULL;
}
/*new description, move it to our sample table*/
if (!new_sgdesc) {
gf_list_add(groupDescs, sgdesc);
gf_list_add(trak->Media->information->sampleTable->child_boxes, sgdesc);
gf_list_rem(traf->sampleGroupsDescription, i);
gf_list_del_item(traf->child_boxes, sgdesc);
i--;
}
/*merge descriptions*/
else {
u32 count;
is_identical_sgpd = gf_isom_is_identical_sgpd(new_sgdesc, sgdesc, 0);
if (is_identical_sgpd)
continue;
new_idx_count = gf_list_count(sgdesc->group_descriptions);
new_idx = (u32 *)gf_malloc(new_idx_count * sizeof(u32));
if (!new_idx) return GF_OUT_OF_MEM;
count = 0;
while (gf_list_count(sgdesc->group_descriptions)) {
void *sgpd_entry = gf_list_get(sgdesc->group_descriptions, 0);
Bool new_entry = GF_TRUE;
for (j = 0; j < gf_list_count(new_sgdesc->group_descriptions); j++) {
void *ptr = gf_list_get(new_sgdesc->group_descriptions, j);
if (gf_isom_is_identical_sgpd(sgpd_entry, ptr, new_sgdesc->grouping_type)) {
new_idx[count] = j + 1;
count ++;
new_entry = GF_FALSE;
sgpd_del_entry(new_sgdesc->grouping_type, sgpd_entry, new_sgdesc->is_opaque);
break;
}
}
if (new_entry) {
gf_list_add(new_sgdesc->group_descriptions, sgpd_entry);
new_idx[count] = gf_list_count(new_sgdesc->group_descriptions);
count ++;
}
gf_list_rem(sgdesc->group_descriptions, 0);
}
}
}
groups = trak->Media->information->sampleTable->sampleGroups;
for (i=0; i<gf_list_count(traf->sampleGroups); i++) {
GF_SampleGroupBox *stbl_group = NULL;
GF_SampleGroupBox *frag_group = gf_list_get(traf->sampleGroups, i);
for (j=0; j<gf_list_count(groups); j++) {
stbl_group = gf_list_get(groups, j);
if ((frag_group->grouping_type==stbl_group->grouping_type) && (frag_group->grouping_type_parameter==stbl_group->grouping_type_parameter))
break;
stbl_group = NULL;
}
if (!stbl_group) {
stbl_group = (GF_SampleGroupBox *) gf_isom_box_new_parent(&trak->Media->information->sampleTable->child_boxes, GF_ISOM_BOX_TYPE_SBGP);
if (!stbl_group) return GF_OUT_OF_MEM;
stbl_group->grouping_type = frag_group->grouping_type;
stbl_group->grouping_type_parameter = frag_group->grouping_type_parameter;
stbl_group->version = frag_group->version;
gf_list_add(groups, stbl_group);
//we created a new sample to group, the first num_first_sample_in_traf are not mapped to any description
if (num_first_sample_in_traf) {
stbl_group->entry_count = 1;
stbl_group->sample_entries = gf_malloc(sizeof(GF_SampleGroupEntry));
if (!stbl_group->sample_entries) return GF_OUT_OF_MEM;
stbl_group->sample_entries[0].group_description_index = 0;
stbl_group->sample_entries[0].sample_count = num_first_sample_in_traf;
}
}
if (is_identical_sgpd) {
//adjust sgpd index: in traf index start at 0x1001
for (j = 0; j < frag_group->entry_count; j++)
frag_group->sample_entries[j].group_description_index &= 0x0FFFF;
if (frag_group->entry_count && stbl_group->entry_count &&
(frag_group->sample_entries[0].group_description_index==stbl_group->sample_entries[stbl_group->entry_count-1].group_description_index)
) {
stbl_group->sample_entries[stbl_group->entry_count - 1].sample_count += frag_group->sample_entries[0].sample_count;
if (frag_group->entry_count>1) {
stbl_group->sample_entries = gf_realloc(stbl_group->sample_entries, sizeof(GF_SampleGroupEntry) * (stbl_group->entry_count + frag_group->entry_count - 1));
memcpy(&stbl_group->sample_entries[stbl_group->entry_count], &frag_group->sample_entries[1], sizeof(GF_SampleGroupEntry) * (frag_group->entry_count - 1));
stbl_group->entry_count += frag_group->entry_count - 1;
}
} else {
stbl_group->sample_entries = gf_realloc(stbl_group->sample_entries, sizeof(GF_SampleGroupEntry) * (stbl_group->entry_count + frag_group->entry_count));
memcpy(&stbl_group->sample_entries[stbl_group->entry_count], &frag_group->sample_entries[0], sizeof(GF_SampleGroupEntry) * frag_group->entry_count);
stbl_group->entry_count += frag_group->entry_count;
}
} else {
u32 samples_in_stbl_group = 0;
for (j=0; j<stbl_group->entry_count; j++) {
samples_in_stbl_group += stbl_group->sample_entries[j].sample_count;
}
u32 num_entries = stbl_group->entry_count + frag_group->entry_count;
if (samples_in_stbl_group < num_first_sample_in_traf) num_entries++;
stbl_group->sample_entries = gf_realloc(stbl_group->sample_entries, sizeof(GF_SampleGroupEntry) * num_entries);
//set unmapped entries to 0
if (samples_in_stbl_group < num_first_sample_in_traf) {
stbl_group->sample_entries[stbl_group->entry_count].sample_count = num_first_sample_in_traf - samples_in_stbl_group;
stbl_group->sample_entries[stbl_group->entry_count].group_description_index = 0;
stbl_group->entry_count++;
}
//adjust sgpd index
for (j = 0; j < frag_group->entry_count; j++) {
u32 sgidx = frag_group->sample_entries[j].group_description_index;
if (sgidx > 0x10000) {
sgidx -= 0x10001;
if (sgidx>=new_idx_count) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[isobmf] corrupted sample group index in fragment %d but only %d group descriptions in fragment\n", sgidx, new_idx_count));
} else {
frag_group->sample_entries[j].group_description_index = new_idx[sgidx];
}
}
}
memcpy(&stbl_group->sample_entries[stbl_group->entry_count], &frag_group->sample_entries[0], sizeof(GF_SampleGroupEntry) * frag_group->entry_count);
stbl_group->entry_count += frag_group->entry_count;
}
}
if (new_idx) gf_free(new_idx);
}
u32 samples_in_traf = trak->Media->information->sampleTable->SampleSize->sampleCount - num_first_sample_in_traf;
/*content is encrypted*/
track_num = gf_isom_get_tracknum_from_id(trak->moov, trak->Header->trackID);
if (gf_isom_is_cenc_media(trak->moov->mov, track_num, DescIndex)
|| traf->sample_encryption) {
/*Merge sample auxiliary encryption information*/
GF_SampleEncryptionBox *senc = NULL;
u32 scheme_type=0;
gf_isom_get_cenc_info(trak->moov->mov, track_num, DescIndex, NULL, &scheme_type, NULL);
if (traf->sample_encryption) {
for (i = 0; i < gf_list_count(trak->Media->information->sampleTable->child_boxes); i++) {
GF_Box *a = (GF_Box *)gf_list_get(trak->Media->information->sampleTable->child_boxes, i);
if (a->type != traf->sample_encryption->type) continue;
if ((a->type ==GF_ISOM_BOX_TYPE_UUID) && (((GF_UUIDBox *)a)->internal_4cc == GF_ISOM_BOX_UUID_PSEC)) {
senc = (GF_SampleEncryptionBox *)a;
break;
}
else if (a->type ==GF_ISOM_BOX_TYPE_SENC) {
senc = (GF_SampleEncryptionBox *)a;
break;
}
}
if (!senc && trak->sample_encryption)
senc = trak->sample_encryption;
if (!senc) {
if (traf->sample_encryption->piff_type==1) {
senc = (GF_SampleEncryptionBox *)gf_isom_create_piff_psec_box(1, 0x2, 0, 0, NULL);
} else {
senc = gf_isom_create_samp_enc_box(1, 0x2);
}
if (!trak->Media->information->sampleTable->child_boxes) trak->Media->information->sampleTable->child_boxes = gf_list_new();
trak->sample_encryption = senc;
if (!trak->child_boxes) trak->child_boxes = gf_list_new();
gf_list_add(trak->child_boxes, senc);
}
}
/*get sample auxiliary information by saiz/saio rather than by parsing senc box*/
if (gf_isom_cenc_has_saiz_saio_traf(traf, scheme_type)) {
u32 nb_saio;
u32 aux_info_type;
u64 offset;
GF_Err e;
Bool is_encrypted;
GF_SampleAuxiliaryInfoOffsetBox *saio = NULL;
GF_SampleAuxiliaryInfoSizeBox *saiz = NULL;
offset = nb_saio = 0;
for (i = 0; i < gf_list_count(traf->sai_offsets); i++) {
saio = (GF_SampleAuxiliaryInfoOffsetBox *)gf_list_get(traf->sai_offsets, i);
aux_info_type = saio->aux_info_type;
if (!aux_info_type) aux_info_type = scheme_type;
/*if we have only 1 sai_offsets, assume that its type is cenc*/
if ((aux_info_type == GF_ISOM_CENC_SCHEME) || (aux_info_type == GF_ISOM_CBC_SCHEME) ||
(aux_info_type == GF_ISOM_CENS_SCHEME) || (aux_info_type == GF_ISOM_CBCS_SCHEME) ||
(gf_list_count(traf->sai_offsets) == 1)) {
if (saio->offsets && saio->entry_count) {
offset = saio->offsets[0] + moof_offset;
nb_saio = saio->entry_count;
break;
}
}
saio = NULL;
}
for (i = 0; i < gf_list_count(traf->sai_sizes); i++) {
saiz = (GF_SampleAuxiliaryInfoSizeBox *)gf_list_get(traf->sai_sizes, i);
aux_info_type = saiz->aux_info_type;
if (!aux_info_type) aux_info_type = scheme_type;
/*if we have only 1 sai_sizes, assume that its type is cenc*/
if ((aux_info_type == GF_ISOM_CENC_SCHEME) || (aux_info_type == GF_ISOM_CBC_SCHEME) ||
(aux_info_type == GF_ISOM_CENS_SCHEME) || (aux_info_type == GF_ISOM_CBCS_SCHEME) ||
(gf_list_count(traf->sai_sizes) == 1)) {
break;
}
saiz = NULL;
}
if (saiz && saio && senc) {
u32 saiz_count = saiz->sample_count;
if (saiz_count > samples_in_traf) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isobmf] Number of CENC SAI samples %d greater than samples in traf %d, skipping merge\n", saiz_count, samples_in_traf));
saiz_count = 0;
}
for (i = 0; i < saiz_count; i++) {
const u8 *key_info=NULL;
u32 key_info_size, samp_num;
if (nb_saio != 1) {
u32 saio_idx = saio_get_index(traf, i);
if (saio_idx>=saio->entry_count)
return GF_ISOM_INVALID_FILE;
offset = saio->offsets[saio_idx] + moof_offset;
}
size = saiz->default_sample_info_size ? saiz->default_sample_info_size : saiz->sample_info_size[i];
samp_num = num_first_sample_in_traf + i + 1;
trak->current_traf_stsd_idx = DescIndex;
//add sample_count_at_seg_start since gf_isom_get_sample_cenc_info_internal removes them
e = gf_isom_get_sample_cenc_info_internal(trak, traf, senc, samp_num + trak->sample_count_at_seg_start, &is_encrypted, NULL, NULL, &key_info, &key_info_size);
trak->current_traf_stsd_idx = 0;
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isobmf] could not get cenc info for sample %d: %s\n", i+1, gf_error_to_string(e) ));
return e;
}
if (key_info) {
//not multikey
if (!key_info[0]) {
//size greater than IV
if (size > key_info[3])
senc->flags = 0x00000002;
}
//multikey, always use subsamples
else {
senc->flags = 0x00000002;
}
}
//merge into table using true sample num since start of fragment
e = gf_isom_cenc_merge_saiz_saio(senc, trak->Media->information->sampleTable, samp_num, offset, size);
if (e) return e;
if (offset + size > max_end)
max_end = offset + size;
//we no longer load sai, this will be loaded through saio/saiz when fecthing it
//this avoids too high mem usage
//we do keep it if edit mode to rewrite senc
if (trak->moov->mov->openMode>=GF_ISOM_OPEN_EDIT) {
GF_CENCSampleAuxInfo *sai;
GF_SAFEALLOC(sai, GF_CENCSampleAuxInfo);
if (!sai) return GF_OUT_OF_MEM;
if (is_encrypted) {
sai->cenc_data_size = size;
sai->cenc_data = gf_malloc(sizeof(u8)*size);
if (!sai->cenc_data) return GF_OUT_OF_MEM;
u64 cur_position = gf_bs_get_position(trak->moov->mov->movieFileMap->bs);
gf_bs_seek(trak->moov->mov->movieFileMap->bs, offset - trak->moov->mov->bytes_removed);
gf_bs_read_data(trak->moov->mov->movieFileMap->bs, sai->cenc_data, sai->cenc_data_size);
gf_bs_seek(trak->moov->mov->movieFileMap->bs, cur_position);
} else {
sai->isNotProtected=1;
}
gf_list_add(senc->samp_aux_info, sai);
}
//always increment offset (in case we need saio.nb_entries>1)
offset += size;
}
}
} else if (traf->sample_encryption) {
senc_Parse(trak->moov->mov->movieFileMap->bs, trak, traf, traf->sample_encryption, samples_in_traf);
trak->sample_encryption->AlgorithmID = traf->sample_encryption->AlgorithmID;
if (!trak->sample_encryption->IV_size)
trak->sample_encryption->IV_size = traf->sample_encryption->IV_size;
if (!trak->sample_encryption->samp_aux_info) trak->sample_encryption->samp_aux_info = gf_list_new();
gf_list_transfer(trak->sample_encryption->samp_aux_info, traf->sample_encryption->samp_aux_info);
if (traf->sample_encryption->flags & 0x00000002)
trak->sample_encryption->flags |= 0x00000002;
}
}
/*merge other saio*/
for (i=0; i<gf_list_count(traf->sai_sizes); i++) {
GF_SampleAuxiliaryInfoOffsetBox *saio = NULL;
GF_SampleAuxiliaryInfoSizeBox *saiz = gf_list_get(traf->sai_sizes, i);
switch (saiz->aux_info_type) {
case GF_ISOM_CENC_SCHEME:
case GF_ISOM_CBC_SCHEME:
case GF_ISOM_CENS_SCHEME:
case GF_ISOM_CBCS_SCHEME:
case GF_ISOM_PIFF_SCHEME:
case 0:
continue;
default:
break;
}
for (j=0; j<gf_list_count(traf->sai_offsets); j++) {
saio = gf_list_get(traf->sai_offsets, j);
if ((saio->aux_info_type==saiz->aux_info_type) && (saio->aux_info_type_parameter==saiz->aux_info_type_parameter)) break;
saio=NULL;
}
if (!saio) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[isobmf] No saio for saiz type %s aux info type %d, cannot merge SAI\n", gf_4cc_to_str(saiz->aux_info_type), saiz->aux_info_type_parameter));
continue;
}
if (!saio->offsets) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isobmf] No offset in saio type %s aux info type %d, cannot merge SAI\n", gf_4cc_to_str(saiz->aux_info_type), saiz->aux_info_type_parameter));
continue;
}
u64 offset = saio->offsets[0] + moof_offset;
u32 nb_saio = saio->entry_count;
if ((nb_saio>1) && (saio->entry_count != gf_list_count(traf->TrackRuns))) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isobmf] Number of SAI offset does not match number of fragments, cannot merge SAI %s aux info type %d\n", gf_4cc_to_str(saiz->aux_info_type), saiz->aux_info_type_parameter));
continue;
}
if (saiz->sample_count > samples_in_traf) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isobmf] Number of SAI samples %d greater than samples in traf %d, skipping merge SAI %s aux info type %d\n", saiz->sample_count, samples_in_traf, gf_4cc_to_str(saiz->aux_info_type), saiz->aux_info_type_parameter));
continue;
}
u32 sai_max_size=0;
u8 *sai = NULL;
for (j=0; j < saiz->sample_count; j++) {
if (nb_saio != 1) {
u32 saio_idx = saio_get_index(traf, i);
if (saio_idx>=saio->entry_count) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isobmf] Number of offset less than number of fragments, cannot merge SAI %s aux info type %d\n", gf_4cc_to_str(saiz->aux_info_type), saiz->aux_info_type_parameter));
break;
}
offset = saio->offsets[j] + moof_offset;
}
size = saiz->default_sample_info_size ? saiz->default_sample_info_size : saiz->sample_info_size[j];
if (!size) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isobmf] SAI of size 0 cannot be merged\n"));
continue;
}
u64 cur_position = gf_bs_get_position(trak->moov->mov->movieFileMap->bs);
gf_bs_seek(trak->moov->mov->movieFileMap->bs, offset);
u32 samp_num = num_first_sample_in_traf + j + 1;
if (sai_max_size<size) {
sai_max_size = size;
sai = gf_realloc(sai, sai_max_size);
}
gf_bs_read_data(trak->moov->mov->movieFileMap->bs, sai, size);
gf_bs_seek(trak->moov->mov->movieFileMap->bs, cur_position);
GF_Err e = gf_isom_add_sample_aux_info_internal(trak, NULL, samp_num, saiz->aux_info_type, saiz->aux_info_type_parameter, sai, size);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[isobmf] Failed to merge sai data: %s\n", gf_error_to_string(e) ));
}
//always increment size even for saio.nb_entries>1
offset += size;
if (offset > max_end)
max_end = offset;
}
if (sai) gf_free(sai);
}
//signal max offset from what we could gather - this is just an estimation, as there could be hidden data at the end of
//the containing mdat
if (trak->moov->mov->signal_frag_bounds && !(trak->moov->mov->FragmentsFlags & GF_ISOM_FRAG_READ_DEBUG) ) {
gf_isom_push_mdat_end(trak->moov->mov, max_end, GF_TRUE);
}
if (moof_template)
gf_free(moof_template);
return GF_OK;
}
#endif
#ifndef GPAC_DISABLE_ISOM_WRITE
//used to check if a TrackID is available
u8 RequestTrack(GF_MovieBox *moov, GF_ISOTrackID TrackID)
{
u32 i;
GF_TrackBox *trak;
i=0;
while ((trak = (GF_TrackBox *)gf_list_enum(moov->trackList, &i))) {
if (trak->Header->trackID == TrackID) {
gf_isom_set_last_error(moov->mov, GF_BAD_PARAM);
return 0;
}
}
return 1;
}
GF_Err Track_RemoveRef(GF_TrackBox *trak, u32 ReferenceType)
{
GF_TrackReferenceBox *ref;
GF_Box *a;
u32 i;
if (! trak) return GF_BAD_PARAM;
if (! trak->References) return GF_OK;
ref = trak->References;
i=0;
while ((a = (GF_Box *)gf_list_enum(ref->child_boxes, &i))) {
if (a->type == ReferenceType) {
gf_isom_box_del_parent(&ref->child_boxes, a);
return GF_OK;
}
}
return GF_OK;
}
GF_Err NewMedia(GF_MediaBox **mdia, u32 MediaType, u32 TimeScale)
{
GF_MediaHeaderBox *mdhd;
GF_Box *mediaInfo;
GF_HandlerBox *hdlr;
GF_MediaInformationBox *minf;
GF_DataInformationBox *dinf;
GF_SampleTableBox *stbl;
GF_DataReferenceBox *dref;
char *str="";
GF_Err e;
if (!mdia) return GF_BAD_PARAM;
minf = *mdia ? (*mdia)->information : NULL;
mdhd = *mdia ? (*mdia)->mediaHeader : NULL;
hdlr = *mdia ? (*mdia)->handler : NULL;
dinf = minf ? minf->dataInformation : NULL;
stbl = minf ? minf->sampleTable : NULL;
dref = dinf ? dinf->dref : NULL;
mediaInfo = minf ? minf->InfoHeader : NULL;
//first create the media
if (!*mdia) {
*mdia = (GF_MediaBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MDIA);
if (! *mdia) { e = GF_OUT_OF_MEM; goto err_exit; }
}
if (!mdhd) {
mdhd = (GF_MediaHeaderBox *) gf_isom_box_new_parent( & ((*mdia)->child_boxes), GF_ISOM_BOX_TYPE_MDHD);
if (! mdhd) { e = GF_OUT_OF_MEM; goto err_exit; }
e = mdia_on_child_box((GF_Box*)*mdia, (GF_Box *) mdhd, GF_FALSE);
if (e) goto err_exit;
}
if (!hdlr) {
hdlr = (GF_HandlerBox *) gf_isom_box_new_parent(& ((*mdia)->child_boxes), GF_ISOM_BOX_TYPE_HDLR);
if (! hdlr) { e = GF_OUT_OF_MEM; goto err_exit; }
e = mdia_on_child_box((GF_Box*)*mdia, (GF_Box *) hdlr, GF_FALSE);
if (e) goto err_exit;
}
if (!minf) {
minf = (GF_MediaInformationBox *) gf_isom_box_new_parent(& ((*mdia)->child_boxes), GF_ISOM_BOX_TYPE_MINF);
if (! minf) { e = GF_OUT_OF_MEM; goto err_exit; }
e = mdia_on_child_box((GF_Box*)*mdia, (GF_Box *) minf, GF_FALSE);
if (e) goto err_exit;
}
if (!dinf) {
dinf = (GF_DataInformationBox *) gf_isom_box_new_parent(&minf->child_boxes, GF_ISOM_BOX_TYPE_DINF);
if (! dinf) { e = GF_OUT_OF_MEM; goto err_exit; }
e = minf_on_child_box((GF_Box*)minf, (GF_Box *) dinf, GF_FALSE);
if (e) goto err_exit;
}
if (!mediaInfo) {
//"handler name" is for debugging purposes. Let's stick our name here ;)
switch (MediaType) {
case GF_ISOM_MEDIA_VISUAL:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_VMHD);
str = "GPAC ISO Video Handler";
break;
case GF_ISOM_MEDIA_AUXV:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_VMHD);
str = "GPAC ISO Auxiliary Video Handler";
break;
case GF_ISOM_MEDIA_PICT:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_VMHD);
str = "GPAC ISO Picture Sequence Handler";
break;
case GF_ISOM_MEDIA_AUDIO:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_SMHD);
str = "GPAC ISO Audio Handler";
break;
case GF_ISOM_MEDIA_HINT:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_HMHD);
str = "GPAC ISO Hint Handler";
break;
case GF_ISOM_MEDIA_META:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
str = "GPAC Timed MetaData Handler";
break;
case GF_ISOM_MEDIA_OD:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
str = "GPAC MPEG-4 OD Handler";
break;
case GF_ISOM_MEDIA_OCR:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
str = "GPAC MPEG-4 OCR Handler";
break;
case GF_ISOM_MEDIA_SCENE:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
str = "GPAC MPEG-4 Scene Description Handler";
break;
case GF_ISOM_MEDIA_MPEG7:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
str = "GPAC MPEG-4 MPEG-7 Handler";
break;
case GF_ISOM_MEDIA_OCI:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
str = "GPAC MPEG-4 OCI Handler";
break;
case GF_ISOM_MEDIA_IPMP:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
str = "GPAC MPEG-4 IPMP Handler";
break;
case GF_ISOM_MEDIA_MPEGJ:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
str = "GPAC MPEG-4 MPEG-J Handler";
break;
case GF_ISOM_MEDIA_TEXT:
case GF_ISOM_MEDIA_SUBT:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
str = "GPAC Streaming Text Handler";
break;
case GF_ISOM_MEDIA_MPEG_SUBT:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_STHD);
str = "GPAC MPEG Subtitle Handler";
break;
case GF_ISOM_MEDIA_DIMS:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_VMHD);
MediaType = GF_ISOM_MEDIA_SCENE;
str = "GPAC DIMS Handler";
break;
case GF_ISOM_MEDIA_TIMECODE:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_GMHD);
str = "GPAC TMCD Handler";
break;
default:
mediaInfo = gf_isom_box_new(GF_ISOM_BOX_TYPE_NMHD);
str = "GPAC IsoMedia Handler";
break;
}
if (! mediaInfo) { e = GF_OUT_OF_MEM; goto err_exit; }
if (!minf->child_boxes) minf->child_boxes = gf_list_new();
gf_list_add(minf->child_boxes, mediaInfo);
e = minf_on_child_box((GF_Box*)minf, (GF_Box *) mediaInfo, GF_FALSE);
if (e) goto err_exit;
}
mdhd->timeScale = TimeScale;
hdlr->handlerType = MediaType;
if (!hdlr->nameUTF8)
hdlr->nameUTF8 = gf_strdup(str);
if (!dref) {
//Create a data reference WITHOUT DATA ENTRY (we don't know anything yet about the media Data)
dref = (GF_DataReferenceBox *) gf_isom_box_new_parent(&dinf->child_boxes, GF_ISOM_BOX_TYPE_DREF);
if (! dref) { e = GF_OUT_OF_MEM; goto err_exit; }
e = dinf_on_child_box((GF_Box*)dinf, (GF_Box *)dref, GF_FALSE);
if (e) goto err_exit;
}
if (!stbl) {
//first set-up the sample table...
stbl = (GF_SampleTableBox *) gf_isom_box_new_parent(&minf->child_boxes, GF_ISOM_BOX_TYPE_STBL);
if (! stbl) { e = GF_OUT_OF_MEM; goto err_exit; }
e = minf_on_child_box((GF_Box*)minf, (GF_Box *) stbl, GF_FALSE);
if (e) goto err_exit;
}
if (!stbl->SampleDescription) {
stbl->SampleDescription = (GF_SampleDescriptionBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSD);
if (! stbl->SampleDescription) { e = GF_OUT_OF_MEM; goto err_exit; }
}
//by default create a regular table, 32 but offset and normal sample size
if (!stbl->ChunkOffset) {
stbl->ChunkOffset = gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STCO);
if (! stbl->ChunkOffset) { e = GF_OUT_OF_MEM; goto err_exit; }
}
if (!stbl->SampleSize) {
stbl->SampleSize = (GF_SampleSizeBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSZ);
if (! stbl->SampleSize) { e = GF_OUT_OF_MEM; goto err_exit; }
}
if (!stbl->SampleToChunk) {
stbl->SampleToChunk = (GF_SampleToChunkBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSC);
if (! stbl->SampleToChunk) { e = GF_OUT_OF_MEM; goto err_exit; }
}
if (!stbl->TimeToSample) {
stbl->TimeToSample = (GF_TimeToSampleBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STTS);
if (! stbl->TimeToSample) { e = GF_OUT_OF_MEM; goto err_exit; }
}
if (!stbl->SampleDescription) {
stbl->SampleDescription = (GF_SampleDescriptionBox *) gf_isom_box_new_parent(&stbl->child_boxes, GF_ISOM_BOX_TYPE_STSD);
if (! stbl->SampleDescription) { e = GF_OUT_OF_MEM; goto err_exit; }
}
return GF_OK;
err_exit:
if (mdhd) gf_isom_box_del_parent(& ((*mdia)->child_boxes), (GF_Box *)mdhd);
if (minf) gf_isom_box_del_parent(& ((*mdia)->child_boxes), (GF_Box *)minf);
if (hdlr) {
gf_isom_box_del_parent(& ((*mdia)->child_boxes) , (GF_Box *)hdlr);
}
return e;
}
GF_Err Track_SetStreamDescriptor(GF_TrackBox *trak, u32 StreamDescriptionIndex, u32 DataReferenceIndex, GF_ESD *esd, u32 *outStreamIndex)
{
GF_Err e;
GF_MPEGSampleEntryBox *entry;
GF_MPEGVisualSampleEntryBox *entry_v;
GF_MPEGAudioSampleEntryBox *entry_a;
GF_TrackReferenceBox *tref;
GF_TrackReferenceTypeBox *dpnd;
u16 tmpRef;
entry = NULL;
tref = NULL;
if (!trak || !esd || (!outStreamIndex && !DataReferenceIndex) ) return GF_BAD_PARAM;
if (!Track_IsMPEG4Stream(trak->Media->handler->handlerType)) return GF_ISOM_INVALID_MEDIA;
esd->ESID = 0;
//set SL to predefined if no url
if (esd->URLString == NULL) {
if (!esd->slConfig) esd->slConfig = (GF_SLConfig*) gf_odf_desc_new(GF_ODF_SLC_TAG);
esd->slConfig->predefined = SLPredef_MP4;
esd->slConfig->durationFlag = 0;
esd->slConfig->useTimestampsFlag = 1;
}
//get the REF box if needed
if (esd->dependsOnESID || (esd->OCRESID && (esd->OCRESID != trak->moov->mov->es_id_default_sync)) ) {
if (!trak->References) {
tref = (GF_TrackReferenceBox *) gf_isom_box_new_parent(&trak->child_boxes, GF_ISOM_BOX_TYPE_TREF);
if (!tref) return GF_OUT_OF_MEM;
e = trak_on_child_box((GF_Box*)trak, (GF_Box *)tref, GF_FALSE);
if (e) return e;
}
tref = trak->References;
}
//Update Stream dependencies
e = Track_FindRef(trak, GF_ISOM_REF_DECODE, &dpnd);
if (e) return e;
if (!dpnd && esd->dependsOnESID) {
e = Track_FindRef(trak, GF_ISOM_REF_BASE, &dpnd);
if (e) return e;
}
if (!dpnd && esd->dependsOnESID) {
dpnd = (GF_TrackReferenceTypeBox *) gf_isom_box_new_parent(&tref->child_boxes, GF_ISOM_BOX_TYPE_REFT);
dpnd->reference_type = GF_ISOM_BOX_TYPE_DPND;
e = reftype_AddRefTrack(dpnd, esd->dependsOnESID, NULL);
if (e) return e;
} else if (dpnd && !esd->dependsOnESID) {
Track_RemoveRef(trak, GF_ISOM_BOX_TYPE_DPND);
}
esd->dependsOnESID = 0;
//Update GF_Clock dependencies
e = Track_FindRef(trak, GF_ISOM_REF_OCR, &dpnd);
if (e) return e;
if (!dpnd && esd->OCRESID && (esd->OCRESID != trak->moov->mov->es_id_default_sync)) {
dpnd = (GF_TrackReferenceTypeBox *) gf_isom_box_new_parent(&tref->child_boxes, GF_ISOM_BOX_TYPE_REFT);
if (!dpnd) return GF_OUT_OF_MEM;
dpnd->reference_type = GF_ISOM_BOX_TYPE_SYNC;
e = reftype_AddRefTrack(dpnd, esd->OCRESID, NULL);
if (e) return e;
} else if (dpnd && !esd->OCRESID) {
Track_RemoveRef(trak, GF_ISOM_BOX_TYPE_SYNC);
} else if (dpnd && esd->OCRESID) {
if (dpnd->trackIDCount != 1) return GF_ISOM_INVALID_MEDIA;
dpnd->trackIDs[0] = esd->OCRESID;
}
esd->OCRESID = 0;
//brand new case: we have to change the IPI desc
if (esd->ipiPtr) {
e = Track_FindRef(trak, GF_ISOM_REF_IPI, &dpnd);
if (e) return e;
if (!dpnd) {
tmpRef = 0;
dpnd = (GF_TrackReferenceTypeBox *) gf_isom_box_new_parent(&tref->child_boxes, GF_ISOM_BOX_TYPE_REFT);
if (!dpnd) return GF_OUT_OF_MEM;
dpnd->reference_type = GF_ISOM_BOX_TYPE_IPIR;
e = reftype_AddRefTrack(dpnd, esd->ipiPtr->IPI_ES_Id, &tmpRef);
if (e) return e;
//and replace the tag and value...
esd->ipiPtr->IPI_ES_Id = tmpRef;
esd->ipiPtr->tag = GF_ODF_ISOM_IPI_PTR_TAG;
} else {
//Watch out! ONLY ONE IPI dependency is allowed per stream
if (dpnd->trackIDCount != 1) return GF_ISOM_INVALID_MEDIA;
//if an existing one is there, what shall we do ???
//donno, erase it
dpnd->trackIDs[0] = esd->ipiPtr->IPI_ES_Id;
//and replace the tag and value...
esd->ipiPtr->IPI_ES_Id = 1;
esd->ipiPtr->tag = GF_ODF_ISOM_IPI_PTR_TAG;
}
}
/*don't store the lang desc in ESD, use the media header language info*/
if (esd->langDesc) {
trak->Media->mediaHeader->packedLanguage[0] = (esd->langDesc->langCode>>16)&0xFF;
trak->Media->mediaHeader->packedLanguage[1] = (esd->langDesc->langCode>>8)&0xFF;
trak->Media->mediaHeader->packedLanguage[2] = (esd->langDesc->langCode)&0xFF;
gf_odf_desc_del((GF_Descriptor *)esd->langDesc);
esd->langDesc = NULL;
}
//we have a streamDescriptionIndex, use it
if (StreamDescriptionIndex) {
u32 entry_type;
entry = (GF_MPEGSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, StreamDescriptionIndex - 1);
if (!entry) return GF_ISOM_INVALID_FILE;
entry_type = entry->type;
GF_ProtectionSchemeInfoBox *sinf = (GF_ProtectionSchemeInfoBox *) gf_isom_box_find_child(entry->child_boxes, GF_ISOM_BOX_TYPE_SINF);
if (sinf && sinf->original_format) entry_type = sinf->original_format->data_format;
GF_MPEGSampleEntryBox *mpgs_entry = NULL;
entry_v = NULL;
entry_a = NULL;
switch (entry->internal_type) {
case GF_ISOM_SAMPLE_ENTRY_GENERIC:
break;
case GF_ISOM_SAMPLE_ENTRY_VIDEO:
entry_v = (GF_MPEGVisualSampleEntryBox*) entry;
break;
case GF_ISOM_SAMPLE_ENTRY_AUDIO:
entry_a = (GF_MPEGAudioSampleEntryBox*) entry;
break;
case GF_ISOM_SAMPLE_ENTRY_MP4S:
mpgs_entry = (GF_MPEGSampleEntryBox *) entry;
break;
}
switch (entry_type) {
case GF_ISOM_BOX_TYPE_MP4S:
if (!mpgs_entry) return GF_ISOM_INVALID_FILE;
//OK, delete the previous ESD
gf_odf_desc_del((GF_Descriptor *) entry->esd->desc);
entry->esd->desc = esd;
break;
case GF_ISOM_BOX_TYPE_MP4V:
if (!entry_v) return GF_ISOM_INVALID_MEDIA;
if (entry_v->esd) {
gf_odf_desc_del((GF_Descriptor *) entry_v->esd->desc);
entry_v->esd->desc = esd;
} else {
return GF_ISOM_INVALID_MEDIA;
}
break;
case GF_ISOM_BOX_TYPE_MP4A:
if (!entry_a) return GF_ISOM_INVALID_MEDIA;
if (entry_a->esd) { // some non-conformant files may not have an ESD ...
//OK, delete the previous ESD
gf_odf_desc_del((GF_Descriptor *) entry_a->esd->desc);
entry_a->esd->desc = esd;
} else {
// can't return OK here otherwise we can't know if esd hasn't been used
// and need to be freed
return GF_ISOM_INVALID_MEDIA;
}
break;
case GF_ISOM_BOX_TYPE_AVC1:
case GF_ISOM_BOX_TYPE_AVC2:
case GF_ISOM_BOX_TYPE_AVC3:
case GF_ISOM_BOX_TYPE_AVC4:
case GF_ISOM_BOX_TYPE_SVC1:
case GF_ISOM_BOX_TYPE_MVC1:
case GF_ISOM_BOX_TYPE_HVC1:
case GF_ISOM_BOX_TYPE_HEV1:
case GF_ISOM_BOX_TYPE_HVC2:
case GF_ISOM_BOX_TYPE_HEV2:
case GF_ISOM_BOX_TYPE_LHE1:
case GF_ISOM_BOX_TYPE_LHV1:
case GF_ISOM_BOX_TYPE_HVT1:
case GF_ISOM_BOX_TYPE_VVC1:
case GF_ISOM_BOX_TYPE_VVI1:
if (!entry_v) return GF_ISOM_INVALID_MEDIA;
e = AVC_HEVC_UpdateESD(entry_v, esd);
if (e) return e;
break;
case GF_ISOM_BOX_TYPE_LSR1:
e = LSR_UpdateESD((GF_LASeRSampleEntryBox*)entry, esd);
if (e) return e;
break;
case GF_ISOM_BOX_TYPE_AV01:
case GF_ISOM_BOX_TYPE_DAV1:
case GF_ISOM_BOX_TYPE_AV1C:
case GF_ISOM_BOX_TYPE_AVS3:
case GF_ISOM_BOX_TYPE_AV3C:
case GF_ISOM_BOX_TYPE_OPUS:
case GF_ISOM_BOX_TYPE_DOPS:
case GF_ISOM_BOX_TYPE_STXT:
case GF_ISOM_BOX_TYPE_WVTT:
case GF_ISOM_BOX_TYPE_STPP:
if (esd) gf_odf_desc_del((GF_Descriptor *) esd);
break;
default:
//silently fail, not an MPEG-4 esd
gf_odf_desc_del((GF_Descriptor *) esd);
return GF_OK;
}
} else {
//need to check we're not in URL mode where only ONE description is allowed...
StreamDescriptionIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes);
if (StreamDescriptionIndex) {
GF_ESD *old_esd=NULL;
entry = (GF_MPEGSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, StreamDescriptionIndex - 1);
if (!entry) return GF_ISOM_INVALID_FILE;
//get ESD (only if present, do not emulate)
Media_GetESD(trak->Media, StreamDescriptionIndex, &old_esd, GF_TRUE);
if (old_esd && old_esd->URLString) return GF_BAD_PARAM;
}
//OK, check the handler and create the entry
switch (trak->Media->handler->handlerType) {
case GF_ISOM_MEDIA_AUXV:
case GF_ISOM_MEDIA_PICT:
case GF_ISOM_MEDIA_VISUAL:
if ((esd->decoderConfig->objectTypeIndication==GF_CODECID_AVC) || (esd->decoderConfig->objectTypeIndication==GF_CODECID_SVC) || (esd->decoderConfig->objectTypeIndication==GF_CODECID_MVC)) {
entry_v = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_AVC1);
if (!entry_v) return GF_OUT_OF_MEM;
e = AVC_HEVC_UpdateESD((GF_MPEGVisualSampleEntryBox*)entry_v, esd);
if (e) return e;
} else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_HEVC) {
entry_v = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_HVC1);
if (!entry_v) return GF_OUT_OF_MEM;
e = AVC_HEVC_UpdateESD((GF_MPEGVisualSampleEntryBox*)entry_v, esd);
if (e) return e;
} else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_VVC) {
entry_v = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_VVC1);
if (!entry_v) return GF_OUT_OF_MEM;
e = AVC_HEVC_UpdateESD((GF_MPEGVisualSampleEntryBox*)entry_v, esd);
if (e) return e;
} else {
entry_v = (GF_MPEGVisualSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MP4V);
if (!entry_v) return GF_OUT_OF_MEM;
entry_v->esd = (GF_ESDBox *) gf_isom_box_new_parent(&entry_v->child_boxes, GF_ISOM_BOX_TYPE_ESDS);
if (!entry_v->esd) return GF_OUT_OF_MEM;
entry_v->esd->desc = esd;
}
//type cast possible now
entry = (GF_MPEGSampleEntryBox*) entry_v;
break;
case GF_ISOM_MEDIA_AUDIO:
if (esd->decoderConfig->objectTypeIndication == GF_CODECID_OPUS) {
GF_MPEGAudioSampleEntryBox *opus = (GF_MPEGAudioSampleEntryBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_OPUS);
if (!opus) return GF_OUT_OF_MEM;
opus->cfg_opus = (GF_OpusSpecificBox *)gf_isom_box_new_parent(&opus->child_boxes, GF_ISOM_BOX_TYPE_DOPS);
if (!opus->cfg_opus) return GF_OUT_OF_MEM;
entry = (GF_MPEGSampleEntryBox*)opus;
gf_odf_desc_del((GF_Descriptor *) esd);
} else if (esd->decoderConfig->objectTypeIndication == GF_CODECID_IAMF) {
GF_MPEGAudioSampleEntryBox *iamf = (GF_MPEGAudioSampleEntryBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_IAMF);
if (!iamf) return GF_OUT_OF_MEM;
iamf->cfg_iamf = (GF_IAConfigurationBox *)gf_isom_box_new_parent(&iamf->child_boxes, GF_ISOM_BOX_TYPE_IACB);
if (!iamf->cfg_iamf) return GF_OUT_OF_MEM;
entry = (GF_MPEGSampleEntryBox*)iamf;
gf_odf_desc_del((GF_Descriptor *) esd);
} else if (esd->decoderConfig->objectTypeIndication == GF_CODECID_AC3) {
GF_MPEGAudioSampleEntryBox *ac3 = (GF_MPEGAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_AC3);
if (!ac3) return GF_OUT_OF_MEM;
ac3->cfg_ac3 = (GF_AC3ConfigBox *) gf_isom_box_new_parent(&ac3->child_boxes, GF_ISOM_BOX_TYPE_DAC3);
if (!ac3->cfg_ac3) return GF_OUT_OF_MEM;
entry = (GF_MPEGSampleEntryBox*) ac3;
gf_odf_desc_del((GF_Descriptor *) esd);
} else if (esd->decoderConfig->objectTypeIndication==GF_CODECID_EAC3) {
GF_MPEGAudioSampleEntryBox *eac3 = (GF_MPEGAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_EC3);
if (!eac3) return GF_OUT_OF_MEM;
eac3->cfg_ac3 = (GF_AC3ConfigBox *) gf_isom_box_new_parent(&eac3->child_boxes, GF_ISOM_BOX_TYPE_DEC3);
if (!eac3->cfg_ac3) return GF_OUT_OF_MEM;
entry = (GF_MPEGSampleEntryBox*) eac3;
gf_odf_desc_del((GF_Descriptor *) esd);
} else if (esd->decoderConfig->objectTypeIndication == GF_CODECID_AC4) {
GF_MPEGAudioSampleEntryBox *ac4 = (GF_MPEGAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_AC4);
if (!ac4) return GF_OUT_OF_MEM;
ac4->cfg_ac4 = (GF_AC4ConfigBox *) gf_isom_box_new_parent(&ac4->child_boxes, GF_ISOM_BOX_TYPE_DAC4);
if (!ac4->cfg_ac4) return GF_OUT_OF_MEM;
entry = (GF_MPEGSampleEntryBox*) ac4;
gf_odf_desc_del((GF_Descriptor *) esd);
} else {
entry_a = (GF_MPEGAudioSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MP4A);
if (!entry_a) return GF_OUT_OF_MEM;
entry_a->samplerate_hi = trak->Media->mediaHeader->timeScale;
entry_a->esd = (GF_ESDBox *) gf_isom_box_new_parent(&entry_a->child_boxes, GF_ISOM_BOX_TYPE_ESDS);
if (!entry_a->esd) return GF_OUT_OF_MEM;
entry_a->esd->desc = esd;
//type cast possible now
entry = (GF_MPEGSampleEntryBox*) entry_a;
}
break;
default:
if ((esd->decoderConfig->streamType==0x03) && (esd->decoderConfig->objectTypeIndication==0x09)) {
entry = (GF_MPEGSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_LSR1);
if (!entry) return GF_OUT_OF_MEM;
e = LSR_UpdateESD((GF_LASeRSampleEntryBox*)entry, esd);
if (e) return e;
} else {
entry = (GF_MPEGSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_MP4S);
entry->esd = (GF_ESDBox *) gf_isom_box_new_parent(&entry->child_boxes, GF_ISOM_BOX_TYPE_ESDS);
if (!entry->esd) return GF_OUT_OF_MEM;
entry->esd->desc = esd;
}
break;
}
entry->dataReferenceIndex = DataReferenceIndex;
if (!trak->Media->information->sampleTable->SampleDescription->child_boxes)
trak->Media->information->sampleTable->SampleDescription->child_boxes = gf_list_new();
gf_list_add(trak->Media->information->sampleTable->SampleDescription->child_boxes, entry);
e = stsd_on_child_box((GF_Box*)trak->Media->information->sampleTable->SampleDescription, (GF_Box *) entry, GF_FALSE);
if (e) return e;
if(outStreamIndex) *outStreamIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes);
}
return GF_OK;
}
#endif /*GPAC_DISABLE_ISOM_WRITE*/
#endif /*GPAC_DISABLE_ISOM*/