/* * GPAC - Multimedia Framework C SDK * * Authors: Jean Le Feuvre * Copyright (c) Telecom ParisTech 2000-2012 * 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 GF_EXPORT const char *gf_lang_get_3cc(u32 idx); GF_EXPORT s32 gf_lang_find(const char *lang_or_rfc_5646_code); #if !defined(GPAC_DISABLE_ISOM) && !defined(GPAC_DISABLE_ISOM_WRITE) GF_Err CanAccessMovie(GF_ISOFile *movie, u32 Mode) { if (!movie) return GF_BAD_PARAM; if (movie->openMode < Mode) return GF_ISOM_INVALID_MODE; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (movie->FragmentsFlags & GF_ISOM_FRAG_WRITE_READY) return GF_ISOM_INVALID_MODE; #endif return GF_OK; } static GF_Err unpack_track(GF_TrackBox *trak) { GF_Err e = GF_OK; if (!trak->is_unpacked) { e = stbl_UnpackOffsets(trak->Media->information->sampleTable); if (e) return e; e = stbl_unpackCTS(trak->Media->information->sampleTable); trak->is_unpacked = GF_TRUE; } return e; } GF_Err FlushCaptureMode(GF_ISOFile *movie) { GF_Err e; if (movie->openMode != GF_ISOM_OPEN_WRITE) return GF_OK; /*make sure nothing was added*/ if (gf_bs_get_position(movie->editFileMap->bs)) return GF_OK; /*add all first boxes*/ if (movie->brand) { e = gf_isom_box_size((GF_Box *)movie->brand); if (e) return e; e = gf_isom_box_write((GF_Box *)movie->brand, movie->editFileMap->bs); if (e) return e; } if (movie->pdin) { e = gf_isom_box_size((GF_Box *)movie->pdin); if (e) return e; e = gf_isom_box_write((GF_Box *)movie->pdin, movie->editFileMap->bs); if (e) return e; } /*we have a trick here: the data will be stored on the fly, so the first thing in the file is the MDAT. As we don't know if we have a large file (>4 GB) or not do as if we had one and write 16 bytes: 4 (type) + 4 (size) + 8 (largeSize)...*/ gf_bs_write_int(movie->editFileMap->bs, 0, 128); return GF_OK; } static GF_Err CheckNoData(GF_ISOFile *movie) { if (movie->openMode != GF_ISOM_OPEN_WRITE) return GF_OK; if (gf_bs_get_position(movie->editFileMap->bs)) return GF_BAD_PARAM; return GF_OK; } /************************************************************** File Writing / Editing **************************************************************/ //quick function to add an IOD/OD to the file if not present (iods is optional) GF_Err AddMovieIOD(GF_MovieBox *moov, u8 isIOD) { GF_Descriptor *od; GF_ObjectDescriptorBox *iods; //do we have an IOD ?? If not, create one. if (moov->iods) return GF_OK; if (isIOD) { od = gf_odf_desc_new(GF_ODF_ISOM_IOD_TAG); } else { od = gf_odf_desc_new(GF_ODF_ISOM_OD_TAG); } if (!od) return GF_OUT_OF_MEM; ((GF_IsomObjectDescriptor *)od)->objectDescriptorID = 1; iods = (GF_ObjectDescriptorBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_IODS); iods->descriptor = od; return moov_AddBox((GF_Box*)moov, (GF_Box *)iods); } //add a track to the root OD GF_EXPORT GF_Err gf_isom_add_track_to_root_od(GF_ISOFile *movie, u32 trackNumber) { GF_Err e; GF_ES_ID_Inc *inc; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_insert_moov(movie); if (!movie->moov->iods) AddMovieIOD(movie->moov, 0); if (gf_isom_is_track_in_root_od(movie, trackNumber) == 1) return GF_OK; inc = (GF_ES_ID_Inc *)gf_odf_desc_new(GF_ODF_ESD_INC_TAG); inc->trackID = gf_isom_get_track_id(movie, trackNumber); if (!inc->trackID) { gf_odf_desc_del((GF_Descriptor *)inc); return movie->LastError; } if ((movie->LastError = gf_isom_add_desc_to_root_od(movie, (GF_Descriptor *)inc))) { return movie->LastError; } gf_odf_desc_del((GF_Descriptor *)inc); return GF_OK; } //remove the root OD GF_EXPORT GF_Err gf_isom_remove_root_od(GF_ISOFile *movie) { GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!movie->moov || !movie->moov->iods) return GF_OK; gf_isom_box_del((GF_Box *)movie->moov->iods); movie->moov->iods = NULL; return GF_OK; } //remove a track to the root OD GF_EXPORT GF_Err gf_isom_remove_track_from_root_od(GF_ISOFile *movie, u32 trackNumber) { GF_List *esds; GF_ES_ID_Inc *inc; u32 i; GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!movie->moov) return GF_OK; if (!gf_isom_is_track_in_root_od(movie, trackNumber)) return GF_OK; if (!movie->moov->iods) AddMovieIOD(movie->moov, 0); if (!movie->moov->iods) return GF_OUT_OF_MEM; switch (movie->moov->iods->descriptor->tag) { case GF_ODF_ISOM_IOD_TAG: esds = ((GF_IsomInitialObjectDescriptor *)movie->moov->iods->descriptor)->ES_ID_IncDescriptors; break; case GF_ODF_ISOM_OD_TAG: esds = ((GF_IsomObjectDescriptor *)movie->moov->iods->descriptor)->ES_ID_IncDescriptors; break; default: return GF_ISOM_INVALID_FILE; } //get the desc i = 0; while ((inc = (GF_ES_ID_Inc*)gf_list_enum(esds, &i))) { if (inc->trackID == gf_isom_get_track_id(movie, trackNumber)) { gf_odf_desc_del((GF_Descriptor *)inc); gf_list_rem(esds, i - 1); break; } } //we don't remove the iod for P&Ls and other potential info return GF_OK; } GF_EXPORT GF_Err gf_isom_set_creation_time(GF_ISOFile *movie, u64 time) { if (!movie || !movie->moov) return GF_BAD_PARAM; movie->moov->mvhd->creationTime = time; movie->moov->mvhd->modificationTime = time; return GF_OK; } GF_EXPORT GF_Err gf_isom_set_track_creation_time(GF_ISOFile *movie, u32 trackNumber, u64 time) { GF_TrackBox *trak; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; trak->Header->creationTime = time; trak->Header->modificationTime = time; return GF_OK; } //sets the enable flag of a track GF_EXPORT GF_Err gf_isom_set_track_enabled(GF_ISOFile *movie, u32 trackNumber, u8 enableTrack) { GF_Err e; GF_TrackBox *trak; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (enableTrack) { trak->Header->flags |= 1; } else { trak->Header->flags &= ~1; } return GF_OK; } GF_EXPORT GF_Err gf_isom_set_media_language(GF_ISOFile *movie, u32 trackNumber, char *code) { GF_Err e; GF_TrackBox *trak; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; // Old language-storage processing // if the new code is on 3 chars, we use it // otherwise, we find the associated 3 chars code and use it if (strlen(code) == 3) { memcpy(trak->Media->mediaHeader->packedLanguage, code, sizeof(char) * 3); } else { s32 lang_idx; const char *code_3cc; lang_idx = gf_lang_find(code); if (lang_idx == -1) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("The given code is not a valid one: %s, using 'und' as 3-letter code\n", code)); code_3cc = "und"; } else { code_3cc = gf_lang_get_3cc(lang_idx); } memcpy(trak->Media->mediaHeader->packedLanguage, code_3cc, sizeof(char) * 3); } // New language-storage processing // change the code in the extended language box (if any) // otherwise add an extended language box only if the given code is not 3 chars { u32 i, count; GF_ExtendedLanguageBox *elng; elng = NULL; count = gf_list_count(trak->Media->other_boxes); for (i = 0; i < count; i++) { GF_Box *box = (GF_Box *)gf_list_get(trak->Media->other_boxes, i); if (box->type == GF_ISOM_BOX_TYPE_ELNG) { elng = (GF_ExtendedLanguageBox *)box; break; } } if (!elng && (strlen(code) != 3)) { elng = (GF_ExtendedLanguageBox *)elng_New(); if (!count) { trak->Media->other_boxes = gf_list_new(); } gf_list_add(trak->Media->other_boxes, elng); } if (elng) { if (elng->extended_language) { gf_free(elng->extended_language); } elng->extended_language = gf_strdup(code); } } if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); return GF_OK; } static void gf_isom_set_root_iod(GF_ISOFile *movie) { GF_IsomInitialObjectDescriptor *iod; GF_IsomObjectDescriptor *od; gf_isom_insert_moov(movie); if (!movie->moov->iods) { AddMovieIOD(movie->moov, 1); return; } //if OD, switch to IOD if (movie->moov->iods->descriptor->tag == GF_ODF_ISOM_IOD_TAG) return; od = (GF_IsomObjectDescriptor *)movie->moov->iods->descriptor; iod = (GF_IsomInitialObjectDescriptor*)gf_malloc(sizeof(GF_IsomInitialObjectDescriptor)); memset(iod, 0, sizeof(GF_IsomInitialObjectDescriptor)); iod->ES_ID_IncDescriptors = od->ES_ID_IncDescriptors; od->ES_ID_IncDescriptors = NULL; //not used in root OD iod->ES_ID_RefDescriptors = NULL; iod->extensionDescriptors = od->extensionDescriptors; od->extensionDescriptors = NULL; iod->IPMP_Descriptors = od->IPMP_Descriptors; od->IPMP_Descriptors = NULL; iod->objectDescriptorID = od->objectDescriptorID; iod->OCIDescriptors = od->OCIDescriptors; od->OCIDescriptors = NULL; iod->tag = GF_ODF_ISOM_IOD_TAG; iod->URLString = od->URLString; od->URLString = NULL; gf_odf_desc_del((GF_Descriptor *)od); movie->moov->iods->descriptor = (GF_Descriptor *)iod; } GF_Err gf_isom_add_desc_to_root_od(GF_ISOFile *movie, GF_Descriptor *theDesc) { GF_Err e; GF_Descriptor *desc, *dupDesc; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_insert_moov(movie); if (!movie->moov->iods) AddMovieIOD(movie->moov, 0); if (!movie->moov->iods) return GF_OUT_OF_MEM; if (theDesc->tag == GF_ODF_IPMP_TL_TAG) gf_isom_set_root_iod(movie); desc = movie->moov->iods->descriptor; //the type of desc is handled at the OD/IOD level, we'll be notified //if the desc is not allowed switch (desc->tag) { case GF_ODF_ISOM_IOD_TAG: case GF_ODF_ISOM_OD_TAG: //duplicate the desc e = gf_odf_desc_copy(theDesc, &dupDesc); if (e) return e; //add it (MUST BE (I)OD level desc) movie->LastError = gf_odf_desc_add_desc(desc, dupDesc); if (movie->LastError) gf_odf_desc_del((GF_Descriptor *)dupDesc); break; default: movie->LastError = GF_ISOM_INVALID_FILE; break; } return movie->LastError; } GF_EXPORT GF_Err gf_isom_set_timescale(GF_ISOFile *movie, u32 timeScale) { Double ts_scale; GF_TrackBox *trak; u32 i; GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_insert_moov(movie); if (movie->moov->mvhd->timeScale == timeScale) return GF_OK; /*rewrite all durations and edit lists*/ ts_scale = timeScale; ts_scale /= movie->moov->mvhd->timeScale; movie->moov->mvhd->timeScale = timeScale; movie->interleavingTime = timeScale; movie->moov->mvhd->duration = (u64)(s64)((s64)movie->moov->mvhd->duration * ts_scale); i = 0; while ((trak = (GF_TrackBox*)gf_list_enum(movie->moov->trackList, &i))) { trak->Header->duration = (u64)(s64)((s64)trak->Header->duration * ts_scale); if (trak->editBox && trak->editBox->editList) { u32 j, count = gf_list_count(trak->editBox->editList->entryList); for (j = 0; jeditBox->editList->entryList, j); ent->segmentDuration = (u64)(s64)((s64)ent->segmentDuration * ts_scale); } } } return GF_OK; } GF_EXPORT GF_Err gf_isom_set_pl_indication(GF_ISOFile *movie, u8 PL_Code, u8 ProfileLevel) { GF_IsomInitialObjectDescriptor *iod; GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_set_root_iod(movie); iod = (GF_IsomInitialObjectDescriptor *)movie->moov->iods->descriptor; switch (PL_Code) { case GF_ISOM_PL_AUDIO: iod->audio_profileAndLevel = ProfileLevel; break; case GF_ISOM_PL_GRAPHICS: iod->graphics_profileAndLevel = ProfileLevel; break; case GF_ISOM_PL_OD: iod->OD_profileAndLevel = ProfileLevel; break; case GF_ISOM_PL_SCENE: iod->scene_profileAndLevel = ProfileLevel; break; case GF_ISOM_PL_VISUAL: iod->visual_profileAndLevel = ProfileLevel; break; case GF_ISOM_PL_INLINE: iod->inlineProfileFlag = ProfileLevel ? 1 : 0; break; } return GF_OK; } GF_Err gf_isom_set_root_od_id(GF_ISOFile *movie, u32 OD_ID) { GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_insert_moov(movie); if (!movie->moov->iods) AddMovieIOD(movie->moov, 0); if (!movie->moov->iods) return GF_OUT_OF_MEM; switch (movie->moov->iods->descriptor->tag) { case GF_ODF_ISOM_OD_TAG: ((GF_IsomObjectDescriptor *)movie->moov->iods->descriptor)->objectDescriptorID = OD_ID; break; case GF_ODF_ISOM_IOD_TAG: ((GF_IsomInitialObjectDescriptor *)movie->moov->iods->descriptor)->objectDescriptorID = OD_ID; break; default: return GF_ISOM_INVALID_FILE; } return GF_OK; } GF_Err gf_isom_set_root_od_url(GF_ISOFile *movie, char *url_string) { GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_insert_moov(movie); if (!movie->moov->iods) AddMovieIOD(movie->moov, 0); if (!movie->moov->iods) return GF_OUT_OF_MEM; switch (movie->moov->iods->descriptor->tag) { case GF_ODF_ISOM_OD_TAG: if (((GF_IsomObjectDescriptor *)movie->moov->iods->descriptor)->URLString) gf_free(((GF_IsomObjectDescriptor *)movie->moov->iods->descriptor)->URLString); ((GF_IsomObjectDescriptor *)movie->moov->iods->descriptor)->URLString = url_string ? gf_strdup(url_string) : NULL; break; case GF_ODF_ISOM_IOD_TAG: if (((GF_IsomInitialObjectDescriptor *)movie->moov->iods->descriptor)->URLString) gf_free(((GF_IsomInitialObjectDescriptor *)movie->moov->iods->descriptor)->URLString); ((GF_IsomInitialObjectDescriptor *)movie->moov->iods->descriptor)->URLString = url_string ? gf_strdup(url_string) : NULL; break; default: return GF_ISOM_INVALID_FILE; } return GF_OK; } //creates a new Track. If trackID = 0, the trackID is chosen by the API //returns the track number or 0 if error GF_EXPORT u32 gf_isom_new_track(GF_ISOFile *movie, u32 trakID, u32 MediaType, u32 TimeScale) { GF_Err e; u64 now; u8 isHint; GF_TrackBox *trak; GF_TrackHeaderBox *tkhd; GF_MediaBox *mdia; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) { gf_isom_set_last_error(movie, e); return 0; } gf_isom_insert_moov(movie); isHint = 0; //we're creating a hint track... it's the same, but mode HAS TO BE EDIT if (MediaType == GF_ISOM_MEDIA_HINT) { // if (movie->openMode != GF_ISOM_OPEN_EDIT) return 0; isHint = 1; } mdia = NULL; tkhd = NULL; trak = NULL; if (trakID) { //check if we are in ES_ID boundaries if (!isHint && (trakID > 0xFFFF)) { gf_isom_set_last_error(movie, GF_BAD_PARAM); return 0; } //here we should look for available IDs ... if (!RequestTrack(movie->moov, trakID)) return 0; } else { trakID = movie->moov->mvhd->nextTrackID; if (!trakID) trakID = 1; /*ESIDs are on 16 bits*/ if (!isHint && (trakID > 0xFFFF)) trakID = 1; while (1) { if (RequestTrack(movie->moov, trakID)) break; trakID += 1; if (trakID == 0xFFFFFFFF) break; } if (trakID == 0xFFFFFFFF) { gf_isom_set_last_error(movie, GF_BAD_PARAM); return 0; } if (!isHint && (trakID > 0xFFFF)) { gf_isom_set_last_error(movie, GF_BAD_PARAM); return 0; } } //OK, now create a track... trak = (GF_TrackBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_TRAK); if (!trak) { gf_isom_set_last_error(movie, GF_OUT_OF_MEM); return 0; } tkhd = (GF_TrackHeaderBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_TKHD); if (!tkhd) { gf_isom_set_last_error(movie, GF_OUT_OF_MEM); gf_isom_box_del((GF_Box *)trak); return 0; } now = gf_isom_get_mp4time(); tkhd->creationTime = now; if (!movie->keep_utc) tkhd->modificationTime = now; //OK, set up the media trak e = NewMedia(&mdia, MediaType, TimeScale); if (e) { gf_isom_box_del((GF_Box *)mdia); gf_isom_box_del((GF_Box *)trak); gf_isom_box_del((GF_Box *)tkhd); return 0; } //OK, add this media to our track mdia->mediaTrack = trak; e = trak_AddBox((GF_Box*)trak, (GF_Box *)tkhd); if (e) goto err_exit; e = trak_AddBox((GF_Box*)trak, (GF_Box *)mdia); if (e) goto err_exit; tkhd->trackID = trakID; //some default properties for Audio, Visual or private tracks switch (MediaType) { case GF_ISOM_MEDIA_VISUAL: case GF_ISOM_MEDIA_SCENE: case GF_ISOM_MEDIA_TEXT: case GF_ISOM_MEDIA_SUBT: /*320-240 pix in 16.16*/ tkhd->width = 0x01400000; tkhd->height = 0x00F00000; break; case GF_ISOM_MEDIA_AUDIO: tkhd->volume = 0x0100; break; } mdia->mediaHeader->creationTime = mdia->mediaHeader->modificationTime = now; trak->Header->creationTime = trak->Header->modificationTime = now; //OK, add our trak e = moov_AddBox((GF_Box*)movie->moov, (GF_Box *)trak); if (e) goto err_exit; //set the new ID available if (trakID + 1> movie->moov->mvhd->nextTrackID) movie->moov->mvhd->nextTrackID = trakID + 1; //and return our track number return gf_isom_get_track_by_id(movie, trakID); err_exit: if (tkhd) gf_isom_box_del((GF_Box *)tkhd); if (trak) gf_isom_box_del((GF_Box *)trak); if (mdia) gf_isom_box_del((GF_Box *)mdia); return 0; } //Create a new StreamDescription in the file. The URL and URN are used to describe external media GF_EXPORT GF_Err gf_isom_new_mpeg4_description(GF_ISOFile *movie, u32 trackNumber, GF_ESD *esd, char *URLname, char *URNname, u32 *outDescriptionIndex) { GF_TrackBox *trak; GF_Err e; u32 dataRefIndex; GF_ESD *new_esd; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !trak->Media || !esd || !esd->decoderConfig || !esd->slConfig) return GF_BAD_PARAM; //get or create the data ref e = Media_FindDataRef(trak->Media->information->dataInformation->dref, URLname, URNname, &dataRefIndex); if (e) return e; if (!dataRefIndex) { e = Media_CreateDataRef(trak->Media->information->dataInformation->dref, URLname, URNname, &dataRefIndex); if (e) return e; } //duplicate our desc e = gf_odf_desc_copy((GF_Descriptor *)esd, (GF_Descriptor **)&new_esd); if (e) return e; if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); e = Track_SetStreamDescriptor(trak, 0, dataRefIndex, new_esd, outDescriptionIndex); if (e) { gf_odf_desc_del((GF_Descriptor *)new_esd); return e; } if (new_esd->URLString) { } return e; } //Add samples to a track. Use streamDescriptionIndex to specify the desired stream (if several) GF_EXPORT GF_Err gf_isom_add_sample(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex, const GF_ISOSample *sample) { GF_Err e; GF_TrackBox *trak; GF_SampleEntryBox *entry; u32 dataRefIndex; u64 data_offset; u32 descIndex; GF_DataEntryURLBox *Dentry; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; e = FlushCaptureMode(movie); if (e) return e; e = unpack_track(trak); if (e) return e; //OK, add the sample //1- Get the streamDescriptionIndex and dataRefIndex //not specified, get the latest used... descIndex = StreamDescriptionIndex; if (!StreamDescriptionIndex) { descIndex = trak->Media->information->sampleTable->currentEntryIndex; } e = Media_GetSampleDesc(trak->Media, descIndex, &entry, &dataRefIndex); if (e) return e; if (!entry || !dataRefIndex) return GF_BAD_PARAM; //set the current to this one trak->Media->information->sampleTable->currentEntryIndex = descIndex; //get this dataRef and return false if not self contained Dentry = (GF_DataEntryURLBox*)gf_list_get(trak->Media->information->dataInformation->dref->other_boxes, dataRefIndex - 1); if (!Dentry || Dentry->flags != 1) return GF_BAD_PARAM; //Open our data map. We are adding stuff, so use EDIT e = gf_isom_datamap_open(trak->Media, dataRefIndex, 1); if (e) return e; //Get the offset... data_offset = gf_isom_datamap_get_offset(trak->Media->information->dataHandler); /*rewrite OD frame*/ if (trak->Media->handler->handlerType == GF_ISOM_MEDIA_OD) { GF_ISOSample *od_sample = NULL; e = Media_ParseODFrame(trak->Media, sample, &od_sample); if (e) return e; e = Media_AddSample(trak->Media, data_offset, od_sample, descIndex, 0); if (e) return e; e = gf_isom_datamap_add_data(trak->Media->information->dataHandler, od_sample->data, od_sample->dataLength); if (e) return e; if (od_sample) gf_isom_sample_del(&od_sample); } else { e = Media_AddSample(trak->Media, data_offset, sample, descIndex, 0); if (e) return e; if (sample->dataLength) { e = gf_isom_datamap_add_data(trak->Media->information->dataHandler, sample->data, sample->dataLength); if (e) return e; } } if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); return SetTrackDuration(trak); } GF_Err gf_isom_add_sample_shadow(GF_ISOFile *movie, u32 trackNumber, GF_ISOSample *sample) { GF_Err e; GF_TrackBox *trak; GF_ISOSample *prev; GF_SampleEntryBox *entry; u32 dataRefIndex; u64 data_offset; u32 descIndex; u32 sampleNum, prevSampleNum; GF_DataEntryURLBox *Dentry; Bool offset_times = GF_FALSE; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !sample) return GF_BAD_PARAM; e = FlushCaptureMode(movie); if (e) return e; e = unpack_track(trak); if (e) return e; e = stbl_findEntryForTime(trak->Media->information->sampleTable, sample->DTS, 0, &sampleNum, &prevSampleNum); if (e) return e; /*we need the EXACT match*/ if (!sampleNum) return GF_BAD_PARAM; prev = gf_isom_get_sample_info(movie, trackNumber, sampleNum, &descIndex, NULL); if (!prev) return gf_isom_last_error(movie); /*for conformance*/ if (sample->DTS == prev->DTS) offset_times = GF_TRUE; gf_isom_sample_del(&prev); e = Media_GetSampleDesc(trak->Media, descIndex, &entry, &dataRefIndex); if (e) return e; if (!entry || !dataRefIndex) return GF_BAD_PARAM; trak->Media->information->sampleTable->currentEntryIndex = descIndex; //get this dataRef and return false if not self contained Dentry = (GF_DataEntryURLBox*)gf_list_get(trak->Media->information->dataInformation->dref->other_boxes, dataRefIndex - 1); if (!Dentry || Dentry->flags != 1) return GF_BAD_PARAM; //Open our data map. We are adding stuff, so use EDIT e = gf_isom_datamap_open(trak->Media, dataRefIndex, 1); if (e) return e; data_offset = gf_isom_datamap_get_offset(trak->Media->information->dataHandler); if (offset_times) sample->DTS += 1; /*REWRITE ANY OD STUFF*/ if (trak->Media->handler->handlerType == GF_ISOM_MEDIA_OD) { GF_ISOSample *od_sample = NULL; e = Media_ParseODFrame(trak->Media, sample, &od_sample); if (!e) e = Media_AddSample(trak->Media, data_offset, od_sample, descIndex, sampleNum); if (!e) e = gf_isom_datamap_add_data(trak->Media->information->dataHandler, od_sample->data, od_sample->dataLength); if (od_sample) gf_isom_sample_del(&od_sample); } else { e = Media_AddSample(trak->Media, data_offset, sample, descIndex, sampleNum); if (!e) e = gf_isom_datamap_add_data(trak->Media->information->dataHandler, sample->data, sample->dataLength); } if (e) return e; if (offset_times) sample->DTS -= 1; //OK, update duration e = Media_SetDuration(trak); if (e) return e; if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); return SetTrackDuration(trak); } GF_Err gf_isom_set_sample_rap(GF_ISOFile *movie, u32 trackNumber) { GF_SampleTableBox *stbl; GF_Err e; GF_TrackBox *trak; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; if (!stbl->SyncSample) stbl->SyncSample = (GF_SyncSampleBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_STSS); return stbl_AddRAP(stbl->SyncSample, stbl->SampleSize->sampleCount); } GF_Err gf_isom_append_sample_data(GF_ISOFile *movie, u32 trackNumber, char *data, u32 data_size) { GF_Err e; GF_TrackBox *trak; GF_SampleEntryBox *entry; u32 dataRefIndex; u32 descIndex; GF_DataEntryURLBox *Dentry; if (!data_size) return GF_OK; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (trak->Media->handler->handlerType == GF_ISOM_MEDIA_OD) return GF_BAD_PARAM; //OK, add the sample descIndex = trak->Media->information->sampleTable->currentEntryIndex; e = Media_GetSampleDesc(trak->Media, descIndex, &entry, &dataRefIndex); if (e) return e; if (!entry || !dataRefIndex) return GF_BAD_PARAM; //get this dataRef and return false if not self contained Dentry = (GF_DataEntryURLBox*)gf_list_get(trak->Media->information->dataInformation->dref->other_boxes, dataRefIndex - 1); if (!Dentry || Dentry->flags != 1) return GF_BAD_PARAM; //Open our data map. We are adding stuff, so use EDIT e = gf_isom_datamap_open(trak->Media, dataRefIndex, 1); if (e) return e; //add the media data e = gf_isom_datamap_add_data(trak->Media->information->dataHandler, data, data_size); if (e) return e; //update data size return stbl_SampleSizeAppend(trak->Media->information->sampleTable->SampleSize, data_size); } //Add sample reference to a track. The SampleOffset is the offset of the data in the referenced file //you must have created a StreamDescription with URL or URN specifying your referenced file //the data offset specifies the beginning of the chunk //Use streamDescriptionIndex to specify the desired stream (if several) GF_EXPORT GF_Err gf_isom_add_sample_reference(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex, GF_ISOSample *sample, u64 dataOffset) { GF_TrackBox *trak; GF_SampleEntryBox *entry; u32 dataRefIndex; u32 descIndex; GF_DataEntryURLBox *Dentry; GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; e = unpack_track(trak); if (e) return e; //OD is not allowed as a data ref if (trak->Media->handler->handlerType == GF_ISOM_MEDIA_OD) { return GF_BAD_PARAM; } //OK, add the sample //1- Get the streamDescriptionIndex and dataRefIndex //not specified, get the latest used... descIndex = StreamDescriptionIndex; if (!StreamDescriptionIndex) { descIndex = trak->Media->information->sampleTable->currentEntryIndex; } e = Media_GetSampleDesc(trak->Media, descIndex, &entry, &dataRefIndex); if (e) return e; if (!entry || !dataRefIndex) return GF_BAD_PARAM; //set the current to this one trak->Media->information->sampleTable->currentEntryIndex = descIndex; //get this dataRef and return false if self contained Dentry = (GF_DataEntryURLBox*)gf_list_get(trak->Media->information->dataInformation->dref->other_boxes, dataRefIndex - 1); if (Dentry->flags == 1) return GF_BAD_PARAM; //add the meta data e = Media_AddSample(trak->Media, dataOffset, sample, descIndex, 0); if (e) return e; if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); //OK, update duration e = Media_SetDuration(trak); if (e) return e; return SetTrackDuration(trak); } //set the duration of the last media sample. If not set, the duration of the last sample is the //duration of the previous one if any, or 1000 (default value). GF_EXPORT GF_Err gf_isom_set_last_sample_duration(GF_ISOFile *movie, u32 trackNumber, u32 duration) { GF_TrackBox *trak; GF_SttsEntry *ent; GF_TimeToSampleBox *stts; u64 mdur; GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; mdur = trak->Media->mediaHeader->duration; stts = trak->Media->information->sampleTable->TimeToSample; if (!stts->nb_entries) return GF_BAD_PARAM; //get the last entry ent = (GF_SttsEntry*)&stts->entries[stts->nb_entries - 1]; mdur -= ent->sampleDelta; mdur += duration; //we only have one sample if (ent->sampleCount == 1) { ent->sampleDelta = duration; } else { if (ent->sampleDelta == duration) return GF_OK; ent->sampleCount -= 1; if (stts->nb_entries == stts->alloc_size) { stts->alloc_size++; stts->entries = (GF_SttsEntry*)gf_realloc(stts->entries, sizeof(GF_SttsEntry)*stts->alloc_size); if (!stts->entries) return GF_OUT_OF_MEM; } stts->entries[stts->nb_entries].sampleCount = 1; stts->entries[stts->nb_entries].sampleDelta = duration; stts->nb_entries++; //and update the write cache stts->w_currentSampleNum = trak->Media->information->sampleTable->SampleSize->sampleCount; } if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); trak->Media->mediaHeader->duration = mdur; return SetTrackDuration(trak); } //update a sample data in the media. Note that the sample MUST exists GF_EXPORT GF_Err gf_isom_update_sample(GF_ISOFile *movie, u32 trackNumber, u32 sampleNumber, GF_ISOSample *sample, Bool data_only) { GF_Err e; GF_TrackBox *trak; e = CanAccessMovie(movie, GF_ISOM_OPEN_EDIT); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; e = unpack_track(trak); if (e) return e; //block for hint tracks if (trak->Media->handler->handlerType == GF_ISOM_MEDIA_HINT) return GF_BAD_PARAM; //REWRITE ANY OD STUFF if (trak->Media->handler->handlerType == GF_ISOM_MEDIA_OD) { GF_ISOSample *od_sample = NULL; e = Media_ParseODFrame(trak->Media, sample, &od_sample); if (!e) e = Media_UpdateSample(trak->Media, sampleNumber, od_sample, data_only); if (od_sample) gf_isom_sample_del(&od_sample); } else { e = Media_UpdateSample(trak->Media, sampleNumber, sample, data_only); } if (e) return e; if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); return GF_OK; } //update a sample data in the media. Note that the sample MUST exists, //that sample->data MUST be NULL and sample->dataLength must be NON NULL; GF_EXPORT GF_Err gf_isom_update_sample_reference(GF_ISOFile *movie, u32 trackNumber, u32 sampleNumber, GF_ISOSample *sample, u64 data_offset) { GF_Err e; GF_TrackBox *trak; e = CanAccessMovie(movie, GF_ISOM_OPEN_EDIT); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; //block for hint tracks if (trak->Media->handler->handlerType == GF_ISOM_MEDIA_HINT) return GF_BAD_PARAM; if (!sampleNumber || !sample) return GF_BAD_PARAM; e = unpack_track(trak); if (e) return e; //OD is not allowed as a data ref if (trak->Media->handler->handlerType == GF_ISOM_MEDIA_OD) { return GF_BAD_PARAM; } //OK, update it e = Media_UpdateSampleReference(trak->Media, sampleNumber, sample, data_offset); if (e) return e; if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); return GF_OK; } //Remove a given sample GF_EXPORT GF_Err gf_isom_remove_sample(GF_ISOFile *movie, u32 trackNumber, u32 sampleNumber) { GF_Err e; GF_TrackBox *trak; e = CanAccessMovie(movie, GF_ISOM_OPEN_EDIT); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !sampleNumber || (sampleNumber > trak->Media->information->sampleTable->SampleSize->sampleCount)) return GF_BAD_PARAM; //block for hint tracks if (trak->Media->handler->handlerType == GF_ISOM_MEDIA_HINT) return GF_BAD_PARAM; e = unpack_track(trak); if (e) return e; //remove DTS e = stbl_RemoveDTS(trak->Media->information->sampleTable, sampleNumber, trak->Media->mediaHeader->timeScale); if (e) return e; //remove CTS if any if (trak->Media->information->sampleTable->CompositionOffset) { e = stbl_RemoveCTS(trak->Media->information->sampleTable, sampleNumber); if (e) return e; } //remove size e = stbl_RemoveSize(trak->Media->information->sampleTable->SampleSize, sampleNumber); if (e) return e; //remove sampleToChunk and chunk e = stbl_RemoveChunk(trak->Media->information->sampleTable, sampleNumber); if (e) return e; //remove sync if (trak->Media->information->sampleTable->SyncSample) { e = stbl_RemoveRAP(trak->Media->information->sampleTable, sampleNumber); if (e) return e; } //remove sample dep if (trak->Media->information->sampleTable->SampleDep) { e = stbl_RemoveRedundant(trak->Media->information->sampleTable, sampleNumber); if (e) return e; } //remove shadow if (trak->Media->information->sampleTable->ShadowSync) { e = stbl_RemoveShadow(trak->Media->information->sampleTable->ShadowSync, sampleNumber); if (e) return e; } //remove padding e = stbl_RemovePaddingBits(trak->Media->information->sampleTable, sampleNumber); if (e) return e; e = stbl_RemoveSubSample(trak->Media->information->sampleTable, sampleNumber); if (e) return e; e = stbl_RemoveSampleGroup(trak->Media->information->sampleTable, sampleNumber); if (e) return e; return SetTrackDuration(trak); } GF_EXPORT GF_Err gf_isom_set_final_name(GF_ISOFile *movie, char *filename) { GF_Err e; if (!movie) return GF_BAD_PARAM; //if mode is not OPEN_EDIT file was created under the right name e = CanAccessMovie(movie, GF_ISOM_OPEN_EDIT); if (e) return e; if (filename) { //we don't allow file overwriting if ((movie->openMode == GF_ISOM_OPEN_EDIT) && movie->fileName && !strcmp(filename, movie->fileName)) return GF_BAD_PARAM; if (movie->finalName) gf_free(movie->finalName); movie->finalName = gf_strdup(filename); if (!movie->finalName) return GF_OUT_OF_MEM; } return GF_OK; } //Add a system descriptor to the ESD of a stream(EDIT or WRITE mode only) GF_Err gf_isom_add_desc_to_description(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex, GF_Descriptor *theDesc) { GF_IPIPtr *ipiD; GF_Err e; u16 tmpRef; GF_TrackBox *trak; GF_Descriptor *desc; GF_ESD *esd; GF_TrackReferenceBox *tref; GF_TrackReferenceTypeBox *dpnd; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; /*GETS NATIVE DESCRIPTOR ONLY*/ e = Media_GetESD(trak->Media, StreamDescriptionIndex, &esd, GF_TRUE); if (e) return e; //duplicate the desc e = gf_odf_desc_copy(theDesc, &desc); if (e) return e; //and add it to the ESD EXCEPT IPI PTR (we need to translate from ES_ID to TrackID!!! if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); switch (desc->tag) { case GF_ODF_IPI_PTR_TAG: goto insertIPI; default: return gf_odf_desc_add_desc((GF_Descriptor *)esd, desc); } insertIPI: if (esd->ipiPtr) { gf_odf_desc_del((GF_Descriptor *)esd->ipiPtr); esd->ipiPtr = NULL; } ipiD = (GF_IPIPtr *)desc; //find a tref if (!trak->References) { tref = (GF_TrackReferenceBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_TREF); e = trak_AddBox((GF_Box*)trak, (GF_Box *)tref); if (e) return e; } tref = trak->References; e = Track_FindRef(trak, GF_ISOM_REF_IPI, &dpnd); if (e) return e; if (!dpnd) { tmpRef = 0; dpnd = (GF_TrackReferenceTypeBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_REFT); dpnd->reference_type = GF_ISOM_BOX_TYPE_IPIR; e = tref_AddBox((GF_Box*)tref, (GF_Box *)dpnd); if (e) return e; e = reftype_AddRefTrack(dpnd, ipiD->IPI_ES_Id, &tmpRef); if (e) return e; //and replace the tag and value... ipiD->IPI_ES_Id = tmpRef; ipiD->tag = GF_ODF_ISOM_IPI_PTR_TAG; } else { //Watch out! ONLY ONE IPI dependancy is allowed per stream dpnd->trackIDCount = 1; dpnd->trackIDs[0] = ipiD->IPI_ES_Id; //and replace the tag and value... ipiD->IPI_ES_Id = 1; ipiD->tag = GF_ODF_ISOM_IPI_PTR_TAG; } //and add the desc to the esd... return gf_odf_desc_add_desc((GF_Descriptor *)esd, desc); } //use carefully. Very useful when you made a lot of changes (IPMP, IPI, OCI, ...) //THIS WILL REPLACE THE WHOLE DESCRIPTOR ... GF_EXPORT GF_Err gf_isom_change_mpeg4_description(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex, GF_ESD *newESD) { GF_Err e; GF_ESD *esd; GF_TrackBox *trak; GF_SampleEntryBox *entry; GF_SampleDescriptionBox *stsd; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; if (!stsd) return movie->LastError = GF_ISOM_INVALID_FILE; if (!StreamDescriptionIndex || StreamDescriptionIndex > gf_list_count(stsd->other_boxes)) { return movie->LastError = GF_BAD_PARAM; } entry = (GF_SampleEntryBox *)gf_list_get(stsd->other_boxes, StreamDescriptionIndex - 1); //no support for generic sample entries (eg, no MPEG4 descriptor) if (entry == NULL) return GF_BAD_PARAM; if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); //duplicate our desc e = gf_odf_desc_copy((GF_Descriptor *)newESD, (GF_Descriptor **)&esd); if (e) return e; e = Track_SetStreamDescriptor(trak, StreamDescriptionIndex, entry->dataReferenceIndex, esd, NULL); if (e != GF_OK) { gf_odf_desc_del((GF_Descriptor *)esd); } return e; } GF_EXPORT GF_Err gf_isom_set_visual_info(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex, u32 Width, u32 Height) { GF_Err e; GF_TrackBox *trak; GF_SampleEntryBox *entry; GF_SampleDescriptionBox *stsd; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; if (!stsd) { return movie->LastError = GF_ISOM_INVALID_FILE; } if (!StreamDescriptionIndex || StreamDescriptionIndex > gf_list_count(stsd->other_boxes)) { return movie->LastError = GF_BAD_PARAM; } entry = (GF_SampleEntryBox *)gf_list_get(stsd->other_boxes, StreamDescriptionIndex - 1); //no support for generic sample entries (eg, no MPEG4 descriptor) if (entry == NULL) return GF_BAD_PARAM; if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); //valid for MPEG visual, JPG and 3GPP H263 switch (entry->type) { case GF_ISOM_BOX_TYPE_MP4V: case GF_ISOM_SUBTYPE_3GP_H263: 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_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: ((GF_VisualSampleEntryBox*)entry)->Width = Width; ((GF_VisualSampleEntryBox*)entry)->Height = Height; trak->Header->width = Width << 16; trak->Header->height = Height << 16; return GF_OK; case GF_ISOM_BOX_TYPE_GNRV: ((GF_GenericVisualSampleEntryBox*)entry)->Width = Width; ((GF_GenericVisualSampleEntryBox*)entry)->Height = Height; trak->Header->width = Width << 16; trak->Header->height = Height << 16; return GF_OK; /*check BIFS*/ default: if (trak->Media->handler->handlerType == GF_ISOM_MEDIA_SCENE) { trak->Header->width = Width << 16; trak->Header->height = Height << 16; return GF_OK; } return GF_BAD_PARAM; } } GF_Err gf_isom_set_pixel_aspect_ratio(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex, u32 hSpacing, u32 vSpacing) { GF_Err e; GF_TrackBox *trak; GF_SampleEntryBox *entry; GF_VisualSampleEntryBox*vent; GF_SampleDescriptionBox *stsd; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; if (!stsd) return movie->LastError = GF_ISOM_INVALID_FILE; if (!StreamDescriptionIndex || StreamDescriptionIndex > gf_list_count(stsd->other_boxes)) { return movie->LastError = GF_BAD_PARAM; } entry = (GF_SampleEntryBox *)gf_list_get(stsd->other_boxes, StreamDescriptionIndex - 1); //no support for generic sample entries (eg, no MPEG4 descriptor) if (entry == NULL) return GF_BAD_PARAM; if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); switch (entry->type) { case GF_ISOM_BOX_TYPE_MP4V: case GF_ISOM_SUBTYPE_3GP_H263: 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_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: break; default: return GF_BAD_PARAM; } vent = (GF_VisualSampleEntryBox*)entry; if (!hSpacing || !vSpacing) { if (vent->pasp) gf_isom_box_del((GF_Box*)vent->pasp); vent->pasp = NULL; return GF_OK; } if (!vent->pasp) vent->pasp = (GF_PixelAspectRatioBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_PASP); vent->pasp->hSpacing = hSpacing; vent->pasp->vSpacing = vSpacing; return GF_OK; } GF_EXPORT GF_Err gf_isom_set_audio_info(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex, u32 sampleRate, u32 nbChannels, u8 bitsPerSample) { GF_Err e; GF_TrackBox *trak; GF_SampleEntryBox *entry; GF_SampleDescriptionBox *stsd; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stsd = trak->Media->information->sampleTable->SampleDescription; if (!stsd) { return movie->LastError = GF_ISOM_INVALID_FILE; } if (!StreamDescriptionIndex || StreamDescriptionIndex > gf_list_count(stsd->other_boxes)) { return movie->LastError = GF_BAD_PARAM; } entry = (GF_SampleEntryBox *)gf_list_get(stsd->other_boxes, StreamDescriptionIndex - 1); //no support for generic sample entries (eg, no MPEG4 descriptor) if (entry == NULL) return GF_BAD_PARAM; if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); switch (entry->type) { case GF_ISOM_BOX_TYPE_MP4A: case GF_ISOM_BOX_TYPE_AC3: case GF_ISOM_BOX_TYPE_EC3: case GF_ISOM_SUBTYPE_3GP_AMR: case GF_ISOM_SUBTYPE_3GP_AMR_WB: case GF_ISOM_SUBTYPE_3GP_EVRC: case GF_ISOM_SUBTYPE_3GP_QCELP: case GF_ISOM_SUBTYPE_3GP_SMV: ((GF_AudioSampleEntryBox*)entry)->samplerate_hi = sampleRate; ((GF_AudioSampleEntryBox*)entry)->samplerate_lo = 0; ((GF_AudioSampleEntryBox*)entry)->channel_count = nbChannels; ((GF_AudioSampleEntryBox*)entry)->bitspersample = bitsPerSample; return GF_OK; /*check BIFS*/ default: return GF_BAD_PARAM; } } //set the storage mode of a file (FLAT, STREAMABLE, INTERLEAVED) GF_EXPORT GF_Err gf_isom_set_storage_mode(GF_ISOFile *movie, u8 storageMode) { GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; switch (storageMode) { case GF_ISOM_STORE_FLAT: case GF_ISOM_STORE_STREAMABLE: case GF_ISOM_STORE_INTERLEAVED: case GF_ISOM_STORE_DRIFT_INTERLEAVED: case GF_ISOM_STORE_TIGHT: movie->storageMode = storageMode; return GF_OK; default: return GF_BAD_PARAM; } } GF_EXPORT void gf_isom_force_64bit_chunk_offset(GF_ISOFile *file, Bool set_on) { file->force_co64 = set_on; } //update or insert a new edit segment in the track time line. Edits are used to modify //the media normal timing. EditTime and EditDuration are expressed in Movie TimeScale //If a segment with EditTime already exists, IT IS ERASED GF_EXPORT GF_Err gf_isom_set_edit_segment(GF_ISOFile *movie, u32 trackNumber, u64 EditTime, u64 EditDuration, u64 MediaTime, u8 EditMode) { GF_TrackBox *trak; GF_EditBox *edts; GF_EditListBox *elst; GF_EdtsEntry *ent, *newEnt; u32 i; GF_Err e; u64 startTime; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; edts = trak->editBox; if (!edts) { edts = (GF_EditBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_EDTS); if (!edts) return GF_OUT_OF_MEM; trak_AddBox((GF_Box*)trak, (GF_Box *)edts); } elst = edts->editList; if (!elst) { elst = (GF_EditListBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_ELST); if (!elst) return GF_OUT_OF_MEM; edts_AddBox((GF_Box*)edts, (GF_Box *)elst); } startTime = 0; ent = NULL; //get the prev entry to this startTime if any i = 0; while ((ent = (GF_EdtsEntry *)gf_list_enum(elst->entryList, &i))) { if ((startTime <= EditTime) && (startTime + ent->segmentDuration > EditTime)) goto found; startTime += ent->segmentDuration; } //not found, add a new entry and adjust the prev one if any if (!ent) { newEnt = CreateEditEntry(EditDuration, MediaTime, EditMode); if (!newEnt) return GF_OUT_OF_MEM; gf_list_add(elst->entryList, newEnt); return SetTrackDuration(trak); } startTime -= ent->segmentDuration; found: //if same time, we erase the current one... if (startTime == EditTime) { ent->segmentDuration = EditDuration; switch (EditMode) { case GF_ISOM_EDIT_EMPTY: ent->mediaRate = 1; ent->mediaTime = -1; break; case GF_ISOM_EDIT_DWELL: ent->mediaRate = 0; ent->mediaTime = MediaTime; break; default: ent->mediaRate = 1; ent->mediaTime = MediaTime; break; } return SetTrackDuration(trak); } //adjust so that the prev ent leads to EntryTime //Note: we don't change the next one as it is unknown to us in //a lot of case (the author's changes) ent->segmentDuration = EditTime - startTime; newEnt = CreateEditEntry(EditDuration, MediaTime, EditMode); if (!newEnt) return GF_OUT_OF_MEM; //is it the last entry ??? if (i >= gf_list_count(elst->entryList) - 1) { //add the new entry at the end gf_list_add(elst->entryList, newEnt); return SetTrackDuration(trak); } else { //insert after the current entry (which is i) gf_list_insert(elst->entryList, newEnt, i + 1); return SetTrackDuration(trak); } } //remove the edit segments for the whole track GF_EXPORT GF_Err gf_isom_remove_edit_segments(GF_ISOFile *movie, u32 trackNumber) { GF_Err e; GF_TrackBox *trak; GF_EdtsEntry *ent; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!trak->editBox || !trak->editBox->editList) return GF_OK; while (gf_list_count(trak->editBox->editList->entryList)) { ent = (GF_EdtsEntry*)gf_list_get(trak->editBox->editList->entryList, 0); gf_free(ent); e = gf_list_rem(trak->editBox->editList->entryList, 0); if (e) return e; } //then delete the GF_EditBox... gf_isom_box_del((GF_Box *)trak->editBox); trak->editBox = NULL; return SetTrackDuration(trak); } //remove the edit segments for the whole track GF_Err gf_isom_remove_edit_segment(GF_ISOFile *movie, u32 trackNumber, u32 seg_index) { GF_Err e; GF_TrackBox *trak; GF_EdtsEntry *ent, *next_ent; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !seg_index) return GF_BAD_PARAM; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!trak->editBox || !trak->editBox->editList) return GF_OK; if (gf_list_count(trak->editBox->editList->entryList) <= 1) return gf_isom_remove_edit_segments(movie, trackNumber); ent = (GF_EdtsEntry*)gf_list_get(trak->editBox->editList->entryList, seg_index - 1); gf_list_rem(trak->editBox->editList->entryList, seg_index - 1); next_ent = (GF_EdtsEntry *)gf_list_get(trak->editBox->editList->entryList, seg_index - 1); if (next_ent) next_ent->segmentDuration += ent->segmentDuration; gf_free(ent); return SetTrackDuration(trak); } GF_EXPORT GF_Err gf_isom_append_edit_segment(GF_ISOFile *movie, u32 trackNumber, u64 EditDuration, u64 MediaTime, u8 EditMode) { GF_Err e; GF_TrackBox *trak; GF_EdtsEntry *ent; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!trak->editBox) { GF_EditBox *edts = (GF_EditBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_EDTS); if (!edts) return GF_OUT_OF_MEM; trak_AddBox((GF_Box*)trak, (GF_Box *)edts); } if (!trak->editBox->editList) { GF_EditListBox *elst = (GF_EditListBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_ELST); if (!elst) return GF_OUT_OF_MEM; edts_AddBox((GF_Box*)trak->editBox, (GF_Box *)elst); } ent = (GF_EdtsEntry *)gf_malloc(sizeof(GF_EdtsEntry)); if (!ent) return GF_OUT_OF_MEM; ent->segmentDuration = EditDuration; switch (EditMode) { case GF_ISOM_EDIT_EMPTY: ent->mediaRate = 1; ent->mediaTime = -1; break; case GF_ISOM_EDIT_DWELL: ent->mediaRate = 0; ent->mediaTime = MediaTime; break; default: ent->mediaRate = 1; ent->mediaTime = MediaTime; break; } gf_list_add(trak->editBox->editList->entryList, ent); return SetTrackDuration(trak); } GF_EXPORT GF_Err gf_isom_modify_edit_segment(GF_ISOFile *movie, u32 trackNumber, u32 seg_index, u64 EditDuration, u64 MediaTime, u8 EditMode) { GF_Err e; GF_TrackBox *trak; GF_EdtsEntry *ent; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !seg_index) return GF_BAD_PARAM; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!trak->editBox || !trak->editBox->editList) return GF_OK; if (gf_list_count(trak->editBox->editList->entryList)editBox->editList->entryList, seg_index - 1); ent->segmentDuration = EditDuration; switch (EditMode) { case GF_ISOM_EDIT_EMPTY: ent->mediaRate = 1; ent->mediaTime = -1; break; case GF_ISOM_EDIT_DWELL: ent->mediaRate = 0; ent->mediaTime = MediaTime; break; default: ent->mediaRate = 1; ent->mediaTime = MediaTime; break; } return SetTrackDuration(trak); } //removes the desired track GF_EXPORT GF_Err gf_isom_remove_track(GF_ISOFile *movie, u32 trackNumber) { GF_Err e; GF_TrackBox *the_trak, *trak; GF_TrackReferenceTypeBox *tref; u32 i, j, k, *newRefs, descIndex; u8 found; GF_ISOSample *samp; the_trak = gf_isom_get_track_from_file(movie, trackNumber); if (!the_trak) return GF_BAD_PARAM; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (movie->moov->iods && movie->moov->iods->descriptor) { GF_Descriptor *desc; GF_ES_ID_Inc *inc; GF_List *ESDs; desc = movie->moov->iods->descriptor; if (desc->tag == GF_ODF_ISOM_IOD_TAG) { ESDs = ((GF_IsomInitialObjectDescriptor *)desc)->ES_ID_IncDescriptors; } else if (desc->tag == GF_ODF_ISOM_OD_TAG) { ESDs = ((GF_IsomObjectDescriptor *)desc)->ES_ID_IncDescriptors; } else { return GF_ISOM_INVALID_FILE; } //remove the track ref from the root OD if any i = 0; while ((inc = (GF_ES_ID_Inc *)gf_list_enum(ESDs, &i))) { if (inc->trackID == the_trak->Header->trackID) { gf_odf_desc_del((GF_Descriptor *)inc); i--; gf_list_rem(ESDs, i); } } } //remove the track from the movie gf_list_del_item(movie->moov->trackList, the_trak); //rewrite any OD tracks i = 0; while ((trak = (GF_TrackBox *)gf_list_enum(movie->moov->trackList, &i))) { if (trak->Media->handler->handlerType != GF_ISOM_MEDIA_OD) continue; //this is an OD track... j = gf_isom_get_sample_count(movie, i); for (k = 0; k < j; k++) { //getting the sample will remove the references to the deleted track in the output OD frame samp = gf_isom_get_sample(movie, i, k + 1, &descIndex); if (!samp) break; //so let's update with the new OD frame ! If the sample is empty, remove it if (!samp->dataLength) { e = gf_isom_remove_sample(movie, i, k + 1); if (e) return e; } else { e = gf_isom_update_sample(movie, i, k + 1, samp, GF_TRUE); if (e) return e; } //and don't forget to delete the sample gf_isom_sample_del(&samp); } } //remove the track ref from any "tref" box in all tracks, except the one to delete //note that we don't touch scal references, as we don't want to rewrite AVC/HEVC samples ... i = 0; while ((trak = (GF_TrackBox *)gf_list_enum(movie->moov->trackList, &i))) { if (trak == the_trak) continue; if (!trak->References || !gf_list_count(trak->References->other_boxes)) continue; j = 0; while ((tref = (GF_TrackReferenceTypeBox *)gf_list_enum(trak->References->other_boxes, &j))) { if (tref->reference_type == GF_4CC('s', 'c', 'a', 'l')) continue; found = 0; for (k = 0; ktrackIDCount; k++) { if (tref->trackIDs[k] == the_trak->Header->trackID) found++; } if (!found) continue; //no more refs, remove this ref_type if (found == tref->trackIDCount) { gf_isom_box_del((GF_Box *)tref); j--; gf_list_rem(trak->References->other_boxes, j); } else { newRefs = (u32*)gf_malloc(sizeof(u32) * (tref->trackIDCount - found)); found = 0; for (k = 0; k < tref->trackIDCount; k++) { if (tref->trackIDs[k] != the_trak->Header->trackID) { newRefs[k - found] = tref->trackIDs[k]; } else { found++; } } gf_free(tref->trackIDs); tref->trackIDs = newRefs; tref->trackIDCount -= found; } } //a little opt: remove the ref box if empty... if (!gf_list_count(trak->References->other_boxes)) { gf_isom_box_del((GF_Box *)trak->References); trak->References = NULL; } } //delete the track gf_isom_box_del((GF_Box *)the_trak); /*update next track ID*/ movie->moov->mvhd->nextTrackID = 0; i = 0; while ((trak = (GF_TrackBox *)gf_list_enum(movie->moov->trackList, &i))) { if (trak->Header->trackID>movie->moov->mvhd->nextTrackID) movie->moov->mvhd->nextTrackID = trak->Header->trackID; } if (!gf_list_count(movie->moov->trackList)) { gf_list_del_item(movie->TopBoxes, movie->moov); gf_isom_box_del((GF_Box *)movie->moov); movie->moov = NULL; } return GF_OK; } GF_EXPORT GF_Err gf_isom_set_copyright(GF_ISOFile *movie, const char *threeCharCode, char *notice) { GF_Err e; GF_CopyrightBox *ptr; GF_UserDataMap *map; u32 count, i; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!notice || !threeCharCode) return GF_BAD_PARAM; gf_isom_insert_moov(movie); if (!movie->moov->udta) { e = moov_AddBox((GF_Box*)movie->moov, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); if (e) return e; } map = udta_getEntry(movie->moov->udta, GF_ISOM_BOX_TYPE_CPRT, NULL); if (map) { //try to find one in our language... count = gf_list_count(map->other_boxes); for (i = 0; iother_boxes, i); if (!strcmp(threeCharCode, (const char *)ptr->packedLanguageCode)) { gf_free(ptr->notice); ptr->notice = (char*)gf_malloc(sizeof(char) * (strlen(notice) + 1)); strcpy(ptr->notice, notice); return GF_OK; } } } //nope, create one ptr = (GF_CopyrightBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_CPRT); memcpy(ptr->packedLanguageCode, threeCharCode, 4); ptr->notice = (char*)gf_malloc(sizeof(char) * (strlen(notice) + 1)); strcpy(ptr->notice, notice); return udta_AddBox(movie->moov->udta, (GF_Box *)ptr); } GF_EXPORT GF_Err gf_isom_add_track_kind(GF_ISOFile *movie, u32 trackNumber, const char *schemeURI, const char *value) { GF_Err e; GF_KindBox *ptr; GF_UserDataBox *udta; GF_UserDataMap *map; u32 i, count; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_insert_moov(movie); if (trackNumber) { GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) { e = trak_AddBox((GF_Box*)trak, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); if (e) return e; } udta = trak->udta; } else { return GF_BAD_PARAM; } map = udta_getEntry(udta, GF_ISOM_BOX_TYPE_KIND, NULL); if (map) { count = gf_list_count(map->other_boxes); for (i = 0; iother_boxes, i); if (b->type == GF_ISOM_BOX_TYPE_KIND) { GF_KindBox *kb = (GF_KindBox *)b; if (!strcmp(kb->schemeURI, schemeURI) && ((value && kb->value && !strcmp(value, kb->value)) || (!value && !kb->value))) { // Already there return GF_OK; } } } } ptr = (GF_KindBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_KIND); if (e) return e; ptr->schemeURI = gf_strdup(schemeURI); if (value) ptr->value = gf_strdup(value); return udta_AddBox(udta, (GF_Box *)ptr); } GF_EXPORT GF_Err gf_isom_remove_track_kind(GF_ISOFile *movie, u32 trackNumber, const char *schemeURI, const char *value) { GF_Err e; GF_UserDataBox *udta; GF_UserDataMap *map; u32 i; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_insert_moov(movie); if (trackNumber) { GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) { e = trak_AddBox((GF_Box*)trak, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); if (e) return e; } udta = trak->udta; } else { return GF_OK; } map = udta_getEntry(udta, GF_ISOM_BOX_TYPE_KIND, NULL); if (map) { for (i = 0; iother_boxes); i++) { GF_Box *b = (GF_Box *)gf_list_get(map->other_boxes, i); if (b->type == GF_ISOM_BOX_TYPE_KIND) { GF_KindBox *kb = (GF_KindBox *)b; if (!schemeURI || (!strcmp(kb->schemeURI, schemeURI) && ((value && kb->value && !strcmp(value, kb->value)) || (!value && !kb->value)))) { gf_isom_box_del(b); gf_list_rem(map->other_boxes, i); i--; } } } } return GF_OK; } GF_EXPORT GF_Err gf_isom_add_chapter(GF_ISOFile *movie, u32 trackNumber, u64 timestamp, char *name) { GF_Err e; GF_ChapterListBox *ptr; u32 i, count; GF_ChapterEntry *ce; GF_UserDataBox *udta; GF_UserDataMap *map; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_insert_moov(movie); if (trackNumber) { GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) { e = trak_AddBox((GF_Box*)trak, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); if (e) return e; } udta = trak->udta; } else { if (!movie->moov->udta) { e = moov_AddBox((GF_Box*)movie->moov, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); if (e) return e; } udta = movie->moov->udta; } ptr = NULL; map = udta_getEntry(udta, GF_ISOM_BOX_TYPE_CHPL, NULL); if (!map) { ptr = (GF_ChapterListBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_CHPL); e = udta_AddBox(udta, (GF_Box *)ptr); if (e) return e; map = udta_getEntry(udta, GF_ISOM_BOX_TYPE_CHPL, NULL); } else { ptr = (GF_ChapterListBox*)gf_list_get(map->other_boxes, 0); } if (!map) return GF_OUT_OF_MEM; /*this may happen if original MP4 is not properly formatted*/ if (!ptr) { ptr = (GF_ChapterListBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_CHPL); if (!ptr) return GF_OUT_OF_MEM; gf_list_add(map->other_boxes, ptr); } GF_SAFEALLOC(ce, GF_ChapterEntry); if (!ce) return GF_OUT_OF_MEM; ce->start_time = timestamp * 10000L; ce->name = name ? gf_strdup(name) : NULL; /*insert in order*/ count = gf_list_count(ptr->list); for (i = 0; ilist, i); if (ace->start_time == ce->start_time) { if (ace->name) gf_free(ace->name); ace->name = ce->name; gf_free(ce); return GF_OK; } if (ace->start_time >= ce->start_time) return gf_list_insert(ptr->list, ce, i); } return gf_list_add(ptr->list, ce); } GF_Err gf_isom_remove_chapter(GF_ISOFile *movie, u32 trackNumber, u32 index) { GF_Err e; GF_ChapterListBox *ptr; GF_ChapterEntry *ce; GF_UserDataBox *udta; GF_UserDataMap *map; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_insert_moov(movie); if (trackNumber) { GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) { e = trak_AddBox((GF_Box*)trak, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); if (e) return e; } udta = trak->udta; } else { if (!movie->moov->udta) { e = moov_AddBox((GF_Box*)movie->moov, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); if (e) return e; } udta = movie->moov->udta; } map = udta_getEntry(udta, GF_ISOM_BOX_TYPE_CHPL, NULL); if (!map) return GF_OK; ptr = (GF_ChapterListBox*)gf_list_get(map->other_boxes, 0); if (!ptr) return GF_OK; if (index) { ce = (GF_ChapterEntry *)gf_list_get(ptr->list, index - 1); if (!ce) return GF_BAD_PARAM; if (ce->name) gf_free(ce->name); gf_free(ce); gf_list_rem(ptr->list, index - 1); } else { while (gf_list_count(ptr->list)) { ce = (GF_ChapterEntry *)gf_list_get(ptr->list, 0); if (ce->name) gf_free(ce->name); gf_free(ce); gf_list_rem(ptr->list, 0); } } if (!gf_list_count(ptr->list)) { gf_list_del_item(udta->recordList, map); gf_isom_box_array_del(map->other_boxes); gf_free(map); } return GF_OK; } GF_Err gf_isom_remove_copyright(GF_ISOFile *movie, u32 index) { GF_Err e; GF_CopyrightBox *ptr; GF_UserDataMap *map; u32 count; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_insert_moov(movie); if (!index) return GF_BAD_PARAM; if (!movie->moov->udta) return GF_OK; map = udta_getEntry(movie->moov->udta, GF_ISOM_BOX_TYPE_CPRT, NULL); if (!map) return GF_OK; count = gf_list_count(map->other_boxes); if (index>count) return GF_BAD_PARAM; ptr = (GF_CopyrightBox*)gf_list_get(map->other_boxes, index - 1); if (ptr) { gf_list_rem(map->other_boxes, index - 1); if (ptr->notice) gf_free(ptr->notice); gf_free(ptr); } /*last copyright, remove*/ if (!gf_list_count(map->other_boxes)) { gf_list_del_item(movie->moov->udta->recordList, map); gf_list_del(map->other_boxes); gf_free(map); } return GF_OK; } GF_Err gf_isom_set_watermark(GF_ISOFile *movie, bin128 UUID, u8* data, u32 length) { GF_Err e; GF_UnknownUUIDBox *ptr; GF_UserDataMap *map; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_insert_moov(movie); if (!movie->moov->udta) { e = moov_AddBox((GF_Box*)movie->moov, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); if (e) return e; } map = udta_getEntry(movie->moov->udta, GF_ISOM_BOX_TYPE_UUID, (bin128 *)& UUID); if (map) { ptr = (GF_UnknownUUIDBox *)gf_list_get(map->other_boxes, 0); if (ptr) { gf_free(ptr->data); ptr->data = (char*)gf_malloc(length); memcpy(ptr->data, data, length); ptr->dataSize = length; return GF_OK; } } //nope, create one ptr = (GF_UnknownUUIDBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_UUID); memcpy(ptr->uuid, UUID, 16); ptr->data = (char*)gf_malloc(length); memcpy(ptr->data, data, length); ptr->dataSize = length; return udta_AddBox(movie->moov->udta, (GF_Box *)ptr); } //set the interleaving time of media data (INTERLEAVED mode only) //InterleaveTime is in MovieTimeScale GF_Err gf_isom_set_interleave_time(GF_ISOFile *movie, u32 InterleaveTime) { GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (!InterleaveTime || !movie->moov) return GF_OK; movie->interleavingTime = InterleaveTime; return GF_OK; } u32 gf_isom_get_interleave_time(GF_ISOFile *movie) { return movie ? movie->interleavingTime : 0; } //set the storage mode of a file (FLAT, STREAMABLE, INTERLEAVED) u8 gf_isom_get_storage_mode(GF_ISOFile *movie) { return movie ? movie->storageMode : 0; } //use a compact track version for sample size. This is not usually recommended //except for speech codecs where the track has a lot of small samples //compaction is done automatically while writing based on the track's sample sizes GF_Err gf_isom_use_compact_size(GF_ISOFile *movie, u32 trackNumber, u8 CompactionOn) { GF_TrackBox *trak; u32 i, size; GF_SampleSizeBox *stsz; GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Media || !trak->Media->information || !trak->Media->information->sampleTable || !trak->Media->information->sampleTable->SampleSize) return GF_ISOM_INVALID_FILE; stsz = trak->Media->information->sampleTable->SampleSize; //switch to regular table if (!CompactionOn) { if (stsz->type == GF_ISOM_BOX_TYPE_STSZ) return GF_OK; stsz->type = GF_ISOM_BOX_TYPE_STSZ; //invalidate the sampleSize and recompute it stsz->sampleSize = 0; if (!stsz->sampleCount) return GF_OK; //if the table is empty we can only assume the track is empty (no size indication) if (!stsz->sizes) return GF_OK; size = stsz->sizes[0]; //check whether the sizes are all the same or not for (i = 1; isampleCount; i++) { if (size != stsz->sizes[i]) { size = 0; break; } } if (size) { gf_free(stsz->sizes); stsz->sizes = NULL; stsz->sampleSize = size; } return GF_OK; } //switch to compact table if (stsz->type == GF_ISOM_BOX_TYPE_STZ2) return GF_OK; //fill the table. Although it seems weird , this is needed in case of edition //after the function is called. NOte however than we force regular table //at write time if all samples are of same size if (stsz->sampleSize) { //this is a weird table indeed ;) if (stsz->sizes) gf_free(stsz->sizes); stsz->sizes = (u32*)gf_malloc(sizeof(u32)*stsz->sampleCount); memset(stsz->sizes, stsz->sampleSize, sizeof(u32)); } //set the SampleSize to 0 while the file is open stsz->sampleSize = 0; stsz->type = GF_ISOM_BOX_TYPE_STZ2; return GF_OK; } GF_EXPORT GF_Err gf_isom_set_brand_info(GF_ISOFile *movie, u32 MajorBrand, u32 MinorVersion) { u32 i, *p; if (!MajorBrand) return GF_BAD_PARAM; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (!(movie->FragmentsFlags & GF_ISOM_FRAG_WRITE_READY)) { GF_Err e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = CheckNoData(movie); if (e) return e; } #endif if (!movie->brand) { movie->brand = (GF_FileTypeBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_FTYP); gf_list_add(movie->TopBoxes, movie->brand); } movie->brand->majorBrand = MajorBrand; movie->brand->minorVersion = MinorVersion; if (!movie->brand->altBrand) { movie->brand->altBrand = (u32*)gf_malloc(sizeof(u32)); movie->brand->altBrand[0] = MajorBrand; movie->brand->altCount = 1; return GF_OK; } //if brand already present don't change anything for (i = 0; ibrand->altCount; i++) { if (movie->brand->altBrand[i] == MajorBrand) return GF_OK; } p = (u32*)gf_malloc(sizeof(u32)*(movie->brand->altCount + 1)); if (!p) return GF_OUT_OF_MEM; memcpy(p, movie->brand->altBrand, sizeof(u32)*movie->brand->altCount); p[movie->brand->altCount] = MajorBrand; movie->brand->altCount += 1; gf_free(movie->brand->altBrand); movie->brand->altBrand = p; return GF_OK; } GF_EXPORT GF_Err gf_isom_modify_alternate_brand(GF_ISOFile *movie, u32 Brand, u8 AddIt) { u32 i, k, *p; if (!Brand) return GF_BAD_PARAM; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (!(movie->FragmentsFlags & GF_ISOM_FRAG_WRITE_READY)) { GF_Err e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = CheckNoData(movie); if (e) return e; } #endif if (!movie->brand && AddIt) { movie->brand = (GF_FileTypeBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_FTYP); if (!movie->brand) return GF_OUT_OF_MEM; gf_list_add(movie->TopBoxes, movie->brand); } if (!AddIt && !movie->brand) return GF_OK; //do not mofify major one if (!AddIt && movie->brand->majorBrand == Brand) return GF_OK; if (!AddIt && movie->brand->altCount == 1) { //fixes it in case movie->brand->altBrand[0] = movie->brand->majorBrand; return GF_OK; } //check for the brand for (i = 0; ibrand->altCount; i++) { if (movie->brand->altBrand[i] == Brand) goto found; } //Not found if (!AddIt) return GF_OK; //add it p = (u32*)gf_malloc(sizeof(u32)*(movie->brand->altCount + 1)); if (!p) return GF_OUT_OF_MEM; memcpy(p, movie->brand->altBrand, sizeof(u32)*movie->brand->altCount); p[movie->brand->altCount] = Brand; movie->brand->altCount += 1; gf_free(movie->brand->altBrand); movie->brand->altBrand = p; return GF_OK; found: //found if (AddIt) return GF_OK; assert(movie->brand->altCount>1); //remove it p = (u32*)gf_malloc(sizeof(u32)*(movie->brand->altCount - 1)); if (!p) return GF_OUT_OF_MEM; k = 0; for (i = 0; ibrand->altCount; i++) { if (movie->brand->altBrand[i] == Brand) continue; else { p[k] = movie->brand->altBrand[i]; k++; } } movie->brand->altCount -= 1; gf_free(movie->brand->altBrand); movie->brand->altBrand = p; return GF_OK; } GF_EXPORT GF_Err gf_isom_reset_alt_brands(GF_ISOFile *movie) { u32 *p; #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (!(movie->FragmentsFlags & GF_ISOM_FRAG_WRITE_READY)) { GF_Err e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; e = CheckNoData(movie); if (e) return e; } #endif if (!movie->brand) { movie->brand = (GF_FileTypeBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_FTYP); gf_list_add(movie->TopBoxes, movie->brand); } p = (u32*)gf_malloc(sizeof(u32)); if (!p) return GF_OUT_OF_MEM; p[0] = movie->brand->majorBrand; movie->brand->altCount = 1; gf_free(movie->brand->altBrand); movie->brand->altBrand = p; return GF_OK; } GF_EXPORT GF_Err gf_isom_set_sample_padding_bits(GF_ISOFile *movie, u32 trackNumber, u32 sampleNumber, u8 NbBits) { GF_TrackBox *trak; GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || NbBits > 7) return GF_BAD_PARAM; //set Padding info return stbl_SetPaddingBits(trak->Media->information->sampleTable, sampleNumber, NbBits); } GF_EXPORT GF_Err gf_isom_remove_user_data_item(GF_ISOFile *movie, u32 trackNumber, u32 UserDataType, bin128 UUID, u32 UserDataIndex) { GF_UserDataMap *map; GF_Box *a; u32 i; bin128 t; GF_Err e; GF_TrackBox *trak; GF_UserDataBox *udta; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (UserDataType == GF_ISOM_BOX_TYPE_UUID) UserDataType = 0; memset(t, 1, 16); if (trackNumber) { trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; udta = trak->udta; } else { udta = movie->moov->udta; } if (!udta) return GF_BAD_PARAM; if (!UserDataIndex) return GF_BAD_PARAM; i = 0; while ((map = (GF_UserDataMap*)gf_list_enum(udta->recordList, &i))) { if ((map->boxType == GF_ISOM_BOX_TYPE_UUID) && !memcmp(map->uuid, UUID, 16)) goto found; else if (map->boxType == UserDataType) goto found; } //not found return GF_OK; found: if (UserDataIndex > gf_list_count(map->other_boxes)) return GF_BAD_PARAM; //delete the box a = (GF_Box*)gf_list_get(map->other_boxes, UserDataIndex - 1); gf_list_rem(map->other_boxes, UserDataIndex - 1); gf_isom_box_del(a); //remove the map if empty if (!gf_list_count(map->other_boxes)) { gf_list_rem(udta->recordList, i - 1); gf_isom_box_array_del(map->other_boxes); gf_free(map); } //but we keep the UDTA no matter what return GF_OK; } GF_EXPORT GF_Err gf_isom_remove_user_data(GF_ISOFile *movie, u32 trackNumber, u32 UserDataType, bin128 UUID) { GF_UserDataMap *map; u32 i; GF_Err e; bin128 t; GF_TrackBox *trak; GF_UserDataBox *udta; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (UserDataType == GF_ISOM_BOX_TYPE_UUID) UserDataType = 0; memset(t, 1, 16); if (trackNumber) { trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; udta = trak->udta; } else { udta = movie->moov->udta; } if (!udta) return GF_BAD_PARAM; i = 0; while ((map = (GF_UserDataMap*)gf_list_enum(udta->recordList, &i))) { if ((map->boxType == GF_ISOM_BOX_TYPE_UUID) && !memcmp(map->uuid, UUID, 16)) goto found; else if (map->boxType == UserDataType) goto found; } //not found return GF_OK; found: gf_list_rem(udta->recordList, i - 1); gf_isom_box_array_del(map->other_boxes); gf_free(map); //but we keep the UDTA no matter what return GF_OK; } GF_EXPORT GF_Err gf_isom_add_user_data(GF_ISOFile *movie, u32 trackNumber, u32 UserDataType, bin128 UUID, char *data, u32 DataLength) { GF_UnknownBox *a; GF_Err e; GF_TrackBox *trak; GF_UserDataBox *udta; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (UserDataType == GF_ISOM_BOX_TYPE_UUID) UserDataType = 0; if (trackNumber) { trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) trak_AddBox((GF_Box*)trak, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); udta = trak->udta; } else { if (!movie->moov->udta) moov_AddBox((GF_Box*)movie->moov, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); udta = movie->moov->udta; } if (!udta) return GF_OUT_OF_MEM; //create a default box if (UserDataType) { a = (GF_UnknownBox *)gf_isom_box_new(UserDataType); } else { a = (GF_UnknownBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_UUID); memcpy(((GF_UUIDBox*)a)->uuid, UUID, 16); } if (DataLength) { a->data = (char*)gf_malloc(sizeof(char)*DataLength); memcpy(a->data, data, DataLength); a->dataSize = DataLength; } return udta_AddBox(udta, (GF_Box *)a); } GF_EXPORT GF_Err gf_isom_add_user_data_boxes(GF_ISOFile *movie, u32 trackNumber, char *data, u32 DataLength) { GF_Err e; GF_TrackBox *trak; GF_UserDataBox *udta; GF_BitStream *bs; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; if (trackNumber) { trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->udta) trak_AddBox((GF_Box*)trak, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); udta = trak->udta; } else { if (!movie->moov->udta) moov_AddBox((GF_Box*)movie->moov, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); udta = movie->moov->udta; } if (!udta) return GF_OUT_OF_MEM; bs = gf_bs_new(data, DataLength, GF_BITSTREAM_READ); while (gf_bs_available(bs)) { GF_Box *a; e = gf_isom_parse_box(&a, bs); if (e) break; e = udta_AddBox(udta, a); if (e) break; } gf_bs_del(bs); return e; } GF_Err gf_isom_add_sample_fragment(GF_ISOFile *movie, u32 trackNumber, u32 sampleNumber, u16 FragmentSize) { GF_Err e; GF_TrackBox *trak; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !sampleNumber || !FragmentSize) return GF_BAD_PARAM; //set Padding info return stbl_AddSampleFragment(trak->Media->information->sampleTable, sampleNumber, FragmentSize); } GF_EXPORT GF_Err gf_isom_remove_sample_fragment(GF_ISOFile *movie, u32 trackNumber, u32 sampleNumber) { GF_TrackBox *trak; GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; return stbl_RemoveSampleFragments(trak->Media->information->sampleTable, sampleNumber); } GF_Err gf_isom_remove_sample_fragments(GF_ISOFile *movie, u32 trackNumber) { GF_TrackBox *trak; GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (trak->Media->information->sampleTable->Fragments) { gf_isom_box_del((GF_Box *)trak->Media->information->sampleTable->Fragments); trak->Media->information->sampleTable->Fragments = NULL; } return GF_OK; } GF_EXPORT GF_Err gf_isom_clone_pl_indications(GF_ISOFile *orig, GF_ISOFile *dest) { GF_IsomInitialObjectDescriptor *iod_d; if (!orig || !dest) return GF_BAD_PARAM; if (!orig->moov->iods || !orig->moov->iods->descriptor) return GF_OK; if (orig->moov->iods->descriptor->tag != GF_ODF_ISOM_IOD_TAG) return GF_OK; AddMovieIOD(dest->moov, 1); gf_odf_desc_del((GF_Descriptor *)dest->moov->iods->descriptor); gf_odf_desc_copy((GF_Descriptor *)orig->moov->iods->descriptor, (GF_Descriptor **)&dest->moov->iods->descriptor); iod_d = (GF_IsomInitialObjectDescriptor *)dest->moov->iods->descriptor; while (gf_list_count(iod_d->ES_ID_IncDescriptors)) { GF_Descriptor *d = (GF_Descriptor *)gf_list_get(iod_d->ES_ID_IncDescriptors, 0); gf_list_rem(iod_d->ES_ID_IncDescriptors, 0); gf_odf_desc_del(d); } while (gf_list_count(iod_d->ES_ID_RefDescriptors)) { GF_Descriptor *d = (GF_Descriptor *)gf_list_get(iod_d->ES_ID_RefDescriptors, 0); gf_list_rem(iod_d->ES_ID_RefDescriptors, 0); gf_odf_desc_del(d); } return GF_OK; } GF_Err gf_isom_clone_box(GF_Box *src, GF_Box **dst) { GF_Err e; char *data; u32 data_size; GF_BitStream *bs; if (*dst) { gf_isom_box_del(*dst); *dst = NULL; } bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); if (!bs) return GF_OUT_OF_MEM; e = gf_isom_box_size((GF_Box *)src); if (!e) e = gf_isom_box_write((GF_Box *)src, bs); gf_bs_get_content(bs, &data, &data_size); gf_bs_del(bs); if (e) return e; bs = gf_bs_new(data, data_size, GF_BITSTREAM_READ); if (!bs) return GF_OUT_OF_MEM; e = gf_isom_parse_box(dst, bs); gf_bs_del(bs); gf_free(data); return e; } GF_Err gf_isom_clone_movie(GF_ISOFile *orig_file, GF_ISOFile *dest_file, Bool clone_tracks, Bool keep_hint_tracks, Bool keep_pssh) { GF_Err e; u32 i; GF_Box *box; e = CanAccessMovie(dest_file, GF_ISOM_OPEN_WRITE); if (e) return e; if (orig_file->brand) { gf_list_del_item(dest_file->TopBoxes, dest_file->brand); gf_isom_box_del((GF_Box *)dest_file->brand); dest_file->brand = NULL; gf_isom_clone_box((GF_Box *)orig_file->brand, (GF_Box **)&dest_file->brand); if (dest_file->brand) gf_list_add(dest_file->TopBoxes, dest_file->brand); } if (orig_file->meta) { gf_list_del_item(dest_file->TopBoxes, dest_file->meta); gf_isom_box_del((GF_Box *)dest_file->meta); dest_file->meta = NULL; /*fixme - check imports*/ gf_isom_clone_box((GF_Box *)orig_file->meta, (GF_Box **)&dest_file->meta); if (dest_file->meta) gf_list_add(dest_file->TopBoxes, dest_file->meta); } if (orig_file->moov) { u32 i, dstTrack; GF_Box *iods; GF_List *tracks = gf_list_new(); GF_List *old_tracks = orig_file->moov->trackList; orig_file->moov->trackList = tracks; iods = (GF_Box*)orig_file->moov->iods; orig_file->moov->iods = NULL; e = gf_isom_clone_box((GF_Box *)orig_file->moov, (GF_Box **)&dest_file->moov); if (e) { gf_list_del(tracks); orig_file->moov->trackList = old_tracks; return e; } orig_file->moov->trackList = old_tracks; gf_list_del(tracks); orig_file->moov->iods = (GF_ObjectDescriptorBox*)iods; gf_list_add(dest_file->TopBoxes, dest_file->moov); #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (dest_file->moov->mvex) { gf_isom_box_del((GF_Box *)dest_file->moov->mvex); dest_file->moov->mvex = NULL; } #endif if (clone_tracks) { for (i = 0; imoov->trackList); i++) { GF_TrackBox *trak = (GF_TrackBox*)gf_list_get(orig_file->moov->trackList, i); if (!trak) continue; if (keep_hint_tracks || (trak->Media->handler->handlerType != GF_ISOM_MEDIA_HINT)) { e = gf_isom_clone_track(orig_file, i + 1, dest_file, GF_FALSE, &dstTrack); if (e) return e; } } if (iods) gf_isom_clone_box((GF_Box *)orig_file->moov->iods, (GF_Box **)dest_file->moov->iods); } else { dest_file->moov->mvhd->nextTrackID = 1; gf_isom_clone_pl_indications(orig_file, dest_file); } dest_file->moov->mov = dest_file; } if (!keep_pssh) { i = 0; while ((box = (GF_Box*)gf_list_get(dest_file->moov->other_boxes, i++))) { if (box->type == GF_ISOM_BOX_TYPE_PSSH) { i--; gf_list_rem(dest_file->moov->other_boxes, i); gf_isom_box_del(box); } } } //duplicate other boxes i = 0; while ((box = (GF_Box*)gf_list_get(orig_file->TopBoxes, i++))) { switch (box->type) { case GF_ISOM_BOX_TYPE_MOOV: case GF_ISOM_BOX_TYPE_META: case GF_ISOM_BOX_TYPE_MDAT: case GF_ISOM_BOX_TYPE_FTYP: case GF_ISOM_BOX_TYPE_PDIN: #ifndef GPAC_DISABLE_ISOM_FRAGMENTS case GF_ISOM_BOX_TYPE_STYP: case GF_ISOM_BOX_TYPE_SIDX: case GF_ISOM_BOX_TYPE_MOOF: #endif case GF_4CC('j', 'P', ' ', ' '): break; case GF_ISOM_BOX_TYPE_PSSH: if (!keep_pssh) break; default: { GF_Box *box2 = NULL; gf_isom_clone_box(box, &box2); gf_list_add(dest_file->TopBoxes, box2); } break; } } return GF_OK; } GF_EXPORT GF_Err gf_isom_clone_track(GF_ISOFile *orig_file, u32 orig_track, GF_ISOFile *dest_file, Bool keep_data_ref, u32 *dest_track) { GF_TrackBox *trak, *new_tk; GF_BitStream *bs; char *data; const char *buffer; u32 data_size; Double ts_scale; GF_Err e; GF_SampleEntryBox *entry; GF_SampleTableBox *stbl, *stbl_temp; e = CanAccessMovie(dest_file, GF_ISOM_OPEN_WRITE); if (e) return e; gf_isom_insert_moov(dest_file); /*get orig sample desc and clone it*/ trak = gf_isom_get_track_from_file(orig_file, orig_track); if (!trak || !trak->Media) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; stbl_temp = (GF_SampleTableBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_STBL); /*clone sampleDescription table*/ stbl_temp->SampleDescription = stbl->SampleDescription; /*also clone sampleGroups description tables if any*/ stbl_temp->sampleGroupsDescription = stbl->sampleGroupsDescription; trak->Media->information->sampleTable = stbl_temp; /*clone CompositionToDecode table, we may remove it later*/ stbl_temp->CompositionToDecode = stbl->CompositionToDecode; bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); gf_isom_box_size((GF_Box *)trak); gf_isom_box_write((GF_Box *)trak, bs); gf_bs_get_content(bs, &data, &data_size); gf_bs_del(bs); bs = gf_bs_new(data, data_size, GF_BITSTREAM_READ); e = gf_isom_parse_box((GF_Box **)&new_tk, bs); gf_bs_del(bs); gf_free(data); trak->Media->information->sampleTable = stbl; stbl_temp->SampleDescription = NULL; stbl_temp->sampleGroupsDescription = NULL; stbl_temp->CompositionToDecode = NULL; gf_isom_box_del((GF_Box *)stbl_temp); if (e) return e; /*create default boxes*/ stbl = new_tk->Media->information->sampleTable; stbl->ChunkOffset = gf_isom_box_new(GF_ISOM_BOX_TYPE_STCO); stbl->SampleSize = (GF_SampleSizeBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_STSZ); stbl->SampleToChunk = (GF_SampleToChunkBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_STSC); stbl->TimeToSample = (GF_TimeToSampleBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_STTS); /*check trackID validity before adding track*/ if (gf_isom_get_track_by_id(dest_file, new_tk->Header->trackID)) { u32 ID = 1; while (1) { if (RequestTrack(dest_file->moov, ID)) break; ID += 1; if (ID == 0xFFFFFFFF) break; } new_tk->Header->trackID = ID; } moov_AddBox((GF_Box*)dest_file->moov, (GF_Box *)new_tk); /*set originalID*/ new_tk->originalID = trak->Header->trackID; /*set originalFile*/ buffer = gf_isom_get_filename(orig_file); new_tk->originalFile = gf_crc_32(buffer, sizeof(buffer)); /*rewrite edit list segmentDuration to new movie timescale*/ ts_scale = dest_file->moov->mvhd->timeScale; ts_scale /= orig_file->moov->mvhd->timeScale; new_tk->Header->duration = (u64)(s64)((s64)new_tk->Header->duration * ts_scale); if (new_tk->editBox && new_tk->editBox->editList) { u32 i, count = gf_list_count(new_tk->editBox->editList->entryList); for (i = 0; ieditBox->editList->entryList, i); ent->segmentDuration = (u64)(s64)((s64)ent->segmentDuration * ts_scale); } } /*reset data ref*/ if (!keep_data_ref) { gf_isom_box_array_del(new_tk->Media->information->dataInformation->dref->other_boxes); new_tk->Media->information->dataInformation->dref->other_boxes = gf_list_new(); /*update data ref*/ entry = (GF_SampleEntryBox*)gf_list_get(new_tk->Media->information->sampleTable->SampleDescription->other_boxes, 0); if (entry) { u32 dref; Media_CreateDataRef(new_tk->Media->information->dataInformation->dref, NULL, NULL, &dref); entry->dataReferenceIndex = dref; } } else { u32 i; for (i = 0; iMedia->information->dataInformation->dref->other_boxes); i++) { GF_DataEntryBox *dref_entry = (GF_DataEntryBox *)gf_list_get(new_tk->Media->information->dataInformation->dref->other_boxes, i); if (dref_entry->flags & 1) { dref_entry->flags &= ~1; dref_entry->location = gf_strdup(orig_file->fileName); } } } *dest_track = gf_list_count(dest_file->moov->trackList); if (dest_file->moov->mvhd->nextTrackID <= new_tk->Header->trackID) dest_file->moov->mvhd->nextTrackID = new_tk->Header->trackID + 1; return GF_OK; } GF_Err gf_isom_clone_sample_descriptions(GF_ISOFile *the_file, u32 trackNumber, GF_ISOFile *orig_file, u32 orig_track, Bool reset_existing) { u32 i; GF_TrackBox *dst_trak, *src_trak; GF_Err e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; dst_trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!dst_trak || !dst_trak->Media) return GF_BAD_PARAM; src_trak = gf_isom_get_track_from_file(orig_file, orig_track); if (!src_trak || !src_trak->Media) return GF_BAD_PARAM; if (reset_existing) { gf_isom_box_array_del(dst_trak->Media->information->sampleTable->SampleDescription->other_boxes); dst_trak->Media->information->sampleTable->SampleDescription->other_boxes = gf_list_new(); } for (i = 0; iMedia->information->sampleTable->SampleDescription->other_boxes); i++) { u32 outDesc; e = gf_isom_clone_sample_description(the_file, trackNumber, orig_file, orig_track, i + 1, NULL, NULL, &outDesc); if (e) break; } return e; } GF_EXPORT GF_Err gf_isom_clone_sample_description(GF_ISOFile *the_file, u32 trackNumber, GF_ISOFile *orig_file, u32 orig_track, u32 orig_desc_index, char *URLname, char *URNname, u32 *outDescriptionIndex) { GF_TrackBox *trak; GF_BitStream *bs; char *data; u32 data_size; GF_Box *entry; GF_Err e; u32 dataRefIndex; e = CanAccessMovie(the_file, GF_ISOM_OPEN_WRITE); if (e) return e; /*get orig sample desc and clone it*/ trak = gf_isom_get_track_from_file(orig_file, orig_track); if (!trak || !trak->Media) return GF_BAD_PARAM; entry = (GF_Box*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, orig_desc_index - 1); if (!entry) return GF_BAD_PARAM; bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); gf_isom_box_size(entry); gf_isom_box_write(entry, bs); gf_bs_get_content(bs, &data, &data_size); gf_bs_del(bs); bs = gf_bs_new(data, data_size, GF_BITSTREAM_READ); e = gf_isom_parse_box(&entry, bs); gf_bs_del(bs); gf_free(data); if (e) return e; /*get new track and insert clone*/ trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak || !trak->Media) goto exit; /*get or create the data ref*/ e = Media_FindDataRef(trak->Media->information->dataInformation->dref, URLname, URNname, &dataRefIndex); if (e) goto exit; if (!dataRefIndex) { e = Media_CreateDataRef(trak->Media->information->dataInformation->dref, URLname, URNname, &dataRefIndex); if (e) goto exit; } if (!the_file->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); /*overwrite dref*/ ((GF_SampleEntryBox *)entry)->dataReferenceIndex = dataRefIndex; e = gf_list_add(trak->Media->information->sampleTable->SampleDescription->other_boxes, entry); *outDescriptionIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->other_boxes); /*also clone track w/h info*/ if (gf_isom_get_media_type(the_file, trackNumber) == GF_ISOM_MEDIA_VISUAL) { gf_isom_set_visual_info(the_file, trackNumber, (*outDescriptionIndex), ((GF_VisualSampleEntryBox*)entry)->Width, ((GF_VisualSampleEntryBox*)entry)->Height); } return e; exit: gf_isom_box_del(entry); return e; } GF_Err gf_isom_new_generic_sample_description(GF_ISOFile *movie, u32 trackNumber, char *URLname, char *URNname, GF_GenericSampleDescription *udesc, u32 *outDescriptionIndex) { GF_TrackBox *trak; GF_Err e; u32 dataRefIndex; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !trak->Media || !udesc) return GF_BAD_PARAM; //get or create the data ref e = Media_FindDataRef(trak->Media->information->dataInformation->dref, URLname, URNname, &dataRefIndex); if (e) return e; if (!dataRefIndex) { e = Media_CreateDataRef(trak->Media->information->dataInformation->dref, URLname, URNname, &dataRefIndex); if (e) return e; } if (!movie->keep_utc) trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); if (trak->Media->handler->handlerType == GF_ISOM_MEDIA_VISUAL) { GF_GenericVisualSampleEntryBox *entry; //create a new entry entry = (GF_GenericVisualSampleEntryBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_GNRV); if (!entry) return GF_OUT_OF_MEM; if (!udesc->codec_tag) { entry->EntryType = GF_ISOM_BOX_TYPE_UUID; memcpy(entry->uuid, udesc->UUID, sizeof(bin128)); } else { entry->EntryType = udesc->codec_tag; } entry->dataReferenceIndex = dataRefIndex; entry->vendor = udesc->vendor_code; entry->version = udesc->version; entry->revision = udesc->revision; entry->temporal_quality = udesc->temporal_quality; entry->spatial_quality = udesc->spatial_quality; entry->Width = udesc->width; entry->Height = udesc->height; strcpy(entry->compressor_name, udesc->compressor_name); entry->color_table_index = -1; entry->frames_per_sample = 1; entry->horiz_res = udesc->h_res ? udesc->h_res : 0x00480000; entry->vert_res = udesc->v_res ? udesc->v_res : 0x00480000; entry->bit_depth = udesc->depth ? udesc->depth : 0x18; if (udesc->extension_buf && udesc->extension_buf_size) { entry->data = (char*)gf_malloc(sizeof(char) * udesc->extension_buf_size); if (!entry->data) { gf_isom_box_del((GF_Box *)entry); return GF_OUT_OF_MEM; } memcpy(entry->data, udesc->extension_buf, udesc->extension_buf_size); entry->data_size = udesc->extension_buf_size; } e = gf_list_add(trak->Media->information->sampleTable->SampleDescription->other_boxes, entry); } else if (trak->Media->handler->handlerType == GF_ISOM_MEDIA_AUDIO) { GF_GenericAudioSampleEntryBox *gena; //create a new entry gena = (GF_GenericAudioSampleEntryBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_GNRA); if (!gena) return GF_OUT_OF_MEM; if (!udesc->codec_tag) { gena->EntryType = GF_ISOM_BOX_TYPE_UUID; memcpy(gena->uuid, udesc->UUID, sizeof(bin128)); } else { gena->EntryType = udesc->codec_tag; } gena->dataReferenceIndex = dataRefIndex; gena->vendor = udesc->vendor_code; gena->version = udesc->version; gena->revision = udesc->revision; gena->bitspersample = udesc->bits_per_sample ? udesc->bits_per_sample : 16; gena->channel_count = udesc->nb_channels ? udesc->nb_channels : 2; gena->samplerate_hi = udesc->samplerate; gena->samplerate_lo = 0; if (udesc->extension_buf && udesc->extension_buf_size) { gena->data = (char*)gf_malloc(sizeof(char) * udesc->extension_buf_size); if (!gena->data) { gf_isom_box_del((GF_Box *)gena); return GF_OUT_OF_MEM; } memcpy(gena->data, udesc->extension_buf, udesc->extension_buf_size); gena->data_size = udesc->extension_buf_size; } e = gf_list_add(trak->Media->information->sampleTable->SampleDescription->other_boxes, gena); } else { GF_GenericSampleEntryBox *genm; //create a new entry genm = (GF_GenericSampleEntryBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_GNRM); if (!genm) return GF_OUT_OF_MEM; if (!udesc->codec_tag) { genm->EntryType = GF_ISOM_BOX_TYPE_UUID; memcpy(genm->uuid, udesc->UUID, sizeof(bin128)); } else { genm->EntryType = udesc->codec_tag; } genm->dataReferenceIndex = dataRefIndex; if (udesc->extension_buf && udesc->extension_buf_size) { genm->data = (char*)gf_malloc(sizeof(char) * udesc->extension_buf_size); if (!genm->data) { gf_isom_box_del((GF_Box *)genm); return GF_OUT_OF_MEM; } memcpy(genm->data, udesc->extension_buf, udesc->extension_buf_size); genm->data_size = udesc->extension_buf_size; } e = gf_list_add(trak->Media->information->sampleTable->SampleDescription->other_boxes, genm); } *outDescriptionIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->other_boxes); return e; } //use carefully. Very useful when you made a lot of changes (IPMP, IPI, OCI, ...) //THIS WILL REPLACE THE WHOLE DESCRIPTOR ... GF_Err gf_isom_change_generic_sample_description(GF_ISOFile *movie, u32 trackNumber, u32 StreamDescriptionIndex, GF_GenericSampleDescription *udesc) { GF_TrackBox *trak; GF_Err e; GF_GenericVisualSampleEntryBox *entry; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !trak->Media || !StreamDescriptionIndex) return GF_BAD_PARAM; entry = (GF_GenericVisualSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, StreamDescriptionIndex - 1); if (!entry) return GF_BAD_PARAM; if (entry->type == GF_ISOM_BOX_TYPE_GNRV) { entry->vendor = udesc->vendor_code; entry->version = udesc->version; entry->revision = udesc->revision; entry->temporal_quality = udesc->temporal_quality; entry->spatial_quality = udesc->spatial_quality; entry->Width = udesc->width; entry->Height = udesc->height; strcpy(entry->compressor_name, udesc->compressor_name); entry->color_table_index = -1; entry->frames_per_sample = 1; entry->horiz_res = udesc->h_res ? udesc->h_res : 0x00480000; entry->vert_res = udesc->v_res ? udesc->v_res : 0x00480000; entry->bit_depth = udesc->depth ? udesc->depth : 0x18; if (entry->data) gf_free(entry->data); entry->data = NULL; entry->data_size = 0; if (udesc->extension_buf && udesc->extension_buf_size) { entry->data = (char*)gf_malloc(sizeof(char) * udesc->extension_buf_size); if (!entry->data) { gf_isom_box_del((GF_Box *)entry); return GF_OUT_OF_MEM; } memcpy(entry->data, udesc->extension_buf, udesc->extension_buf_size); entry->data_size = udesc->extension_buf_size; } return GF_OK; } else if (entry->type == GF_ISOM_BOX_TYPE_GNRA) { GF_GenericAudioSampleEntryBox *gena = (GF_GenericAudioSampleEntryBox *)entry; gena->vendor = udesc->vendor_code; gena->version = udesc->version; gena->revision = udesc->revision; gena->bitspersample = udesc->bits_per_sample ? udesc->bits_per_sample : 16; gena->channel_count = udesc->nb_channels ? udesc->nb_channels : 2; gena->samplerate_hi = udesc->samplerate; gena->samplerate_lo = 0; if (gena->data) gf_free(gena->data); gena->data = NULL; gena->data_size = 0; if (udesc->extension_buf && udesc->extension_buf_size) { gena->data = (char*)gf_malloc(sizeof(char) * udesc->extension_buf_size); if (!gena->data) { gf_isom_box_del((GF_Box *)gena); return GF_OUT_OF_MEM; } memcpy(gena->data, udesc->extension_buf, udesc->extension_buf_size); gena->data_size = udesc->extension_buf_size; } return GF_OK; } else if (entry->type == GF_ISOM_BOX_TYPE_GNRM) { GF_GenericSampleEntryBox *genm = (GF_GenericSampleEntryBox *)entry; if (genm->data) gf_free(genm->data); genm->data = NULL; genm->data_size = 0; if (udesc->extension_buf && udesc->extension_buf_size) { genm->data = (char*)gf_malloc(sizeof(char) * udesc->extension_buf_size); if (!genm->data) { gf_isom_box_del((GF_Box *)genm); return GF_OUT_OF_MEM; } memcpy(genm->data, udesc->extension_buf, udesc->extension_buf_size); genm->data_size = udesc->extension_buf_size; } return GF_OK; } return GF_BAD_PARAM; } /*removes given stream description*/ GF_Err gf_isom_remove_sample_description(GF_ISOFile *movie, u32 trackNumber, u32 streamDescIndex) { GF_TrackBox *trak; GF_Err e; GF_Box *entry; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !trak->Media || !streamDescIndex) return GF_BAD_PARAM; entry = (GF_Box*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, streamDescIndex - 1); if (!entry) return GF_BAD_PARAM; gf_list_rem(trak->Media->information->sampleTable->SampleDescription->other_boxes, streamDescIndex - 1); gf_isom_box_del(entry); return GF_OK; } //sets a track reference GF_EXPORT GF_Err gf_isom_set_track_reference(GF_ISOFile *the_file, u32 trackNumber, u32 referenceType, u32 ReferencedTrackID) { GF_Err e; GF_TrackBox *trak; GF_TrackReferenceBox *tref; GF_TrackReferenceTypeBox *dpnd; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; //no tref, create one tref = trak->References; if (!tref) { tref = (GF_TrackReferenceBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_TREF); e = trak_AddBox((GF_Box*)trak, (GF_Box *)tref); if (e) return e; } //find a ref of the given type e = Track_FindRef(trak, referenceType, &dpnd); if (e) return e; if (!dpnd) { dpnd = (GF_TrackReferenceTypeBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_REFT); dpnd->reference_type = referenceType; e = tref_AddBox((GF_Box*)tref, (GF_Box *)dpnd); if (e) return e; } //add the ref return reftype_AddRefTrack(dpnd, ReferencedTrackID, NULL); } //removes a track reference GF_EXPORT GF_Err gf_isom_remove_track_reference(GF_ISOFile *the_file, u32 trackNumber, u32 referenceType, u32 ReferenceIndex) { GF_Err e; GF_TrackBox *trak; GF_TrackReferenceBox *tref; GF_TrackReferenceTypeBox *dpnd, *tmp; u32 i, k, *newIDs; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak || !ReferenceIndex) return GF_BAD_PARAM; //no tref, nothing to remove tref = trak->References; if (!tref) return GF_OK; //find a ref of the given type otherwise return e = Track_FindRef(trak, referenceType, &dpnd); if (e || !dpnd) return GF_OK; //remove the ref if (ReferenceIndex > dpnd->trackIDCount) return GF_BAD_PARAM; //last one if (dpnd->trackIDCount == 1) { i = 0; while ((tmp = (GF_TrackReferenceTypeBox *)gf_list_enum(tref->other_boxes, &i))) { if (tmp == dpnd) { gf_list_rem(tref->other_boxes, i - 1); gf_isom_box_del((GF_Box *)dpnd); return GF_OK; } } } k = 0; newIDs = (u32*)gf_malloc(sizeof(u32)*(dpnd->trackIDCount - 1)); for (i = 0; itrackIDCount; i++) { if (i + 1 != ReferenceIndex) { newIDs[k] = dpnd->trackIDs[i]; k++; } } gf_free(dpnd->trackIDs); dpnd->trackIDCount -= 1; dpnd->trackIDs = newIDs; return GF_OK; } //changes track ID GF_EXPORT GF_Err gf_isom_set_track_id(GF_ISOFile *movie, u32 trackNumber, u32 trackID) { GF_TrackReferenceTypeBox *ref; GF_TrackBox *trak, *a_trak; u32 i, j, k; trak = gf_isom_get_track_from_file(movie, trackNumber); if (trak && (trak->Header->trackID == trackID)) return GF_OK; a_trak = gf_isom_get_track_from_id(movie->moov, trackID); if (!movie || !trak || a_trak) return GF_BAD_PARAM; if (movie->moov->mvhd->nextTrackID <= trackID) movie->moov->mvhd->nextTrackID = trackID; /*rewrite all dependencies*/ i = 0; while ((a_trak = (GF_TrackBox*)gf_list_enum(movie->moov->trackList, &i))) { if (!a_trak->References) continue; j = 0; while ((ref = (GF_TrackReferenceTypeBox *)gf_list_enum(a_trak->References->other_boxes, &j))) { for (k = 0; ktrackIDCount; k++) { if (ref->trackIDs[k] == trak->Header->trackID) { ref->trackIDs[k] = trackID; break; } } } } /*and update IOD if any*/ if (movie->moov->iods && movie->moov->iods->descriptor) { GF_ES_ID_Inc *inc; GF_IsomObjectDescriptor *od = (GF_IsomObjectDescriptor *)movie->moov->iods->descriptor; u32 i = 0; while ((inc = (GF_ES_ID_Inc*)gf_list_enum(od->ES_ID_IncDescriptors, &i))) { if (inc->trackID == trak->Header->trackID) inc->trackID = trackID; } } trak->Header->trackID = trackID; return GF_OK; } /*force to rewrite all dependencies when the trackID of referenced track changes*/ GF_EXPORT GF_Err gf_isom_rewrite_track_dependencies(GF_ISOFile *movie, u32 trackNumber) { GF_TrackReferenceTypeBox *ref; GF_TrackBox *trak, *a_trak; u32 i, k; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->References) return GF_OK; i = 0; while ((ref = (GF_TrackReferenceTypeBox *)gf_list_enum(trak->References->other_boxes, &i))) { for (k = 0; k < ref->trackIDCount; k++) { a_trak = gf_isom_get_track_from_original_id(movie->moov, ref->trackIDs[k], trak->originalFile); if (a_trak) { ref->trackIDs[k] = a_trak->Header->trackID; } else { a_trak = gf_isom_get_track_from_id(movie->moov, ref->trackIDs[k]); /*we should have a track with no original ID (not imported) - should we rewrite the dependency ?*/ if (!a_trak || a_trak->originalID) return GF_BAD_PARAM; } } } return GF_OK; } GF_EXPORT GF_Err gf_isom_modify_cts_offset(GF_ISOFile *the_file, u32 trackNumber, u32 sample_number, u32 offset) { GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->CompositionOffset) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->CompositionOffset->unpack_mode) return GF_BAD_PARAM; /*we're in unpack mode: one entry per sample*/ trak->Media->information->sampleTable->CompositionOffset->entries[sample_number - 1].decodingOffset = offset; return GF_OK; } GF_Err gf_isom_remove_cts_info(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->CompositionOffset) return GF_OK; gf_isom_box_del((GF_Box *)trak->Media->information->sampleTable->CompositionOffset); trak->Media->information->sampleTable->CompositionOffset = NULL; return GF_OK; } GF_EXPORT GF_Err gf_isom_set_cts_packing(GF_ISOFile *the_file, u32 trackNumber, Bool unpack) { GF_Err e; GF_Err stbl_repackCTS(GF_CompositionOffsetBox *ctts); GF_Err stbl_unpackCTS(GF_SampleTableBox *stbl); GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (unpack) { if (!trak->Media->information->sampleTable->CompositionOffset) trak->Media->information->sampleTable->CompositionOffset = (GF_CompositionOffsetBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_CTTS); e = stbl_unpackCTS(trak->Media->information->sampleTable); } else { if (!trak->Media->information->sampleTable->CompositionOffset) return GF_OK; e = stbl_repackCTS(trak->Media->information->sampleTable->CompositionOffset); } if (e) return e; return SetTrackDuration(trak); } GF_EXPORT GF_Err gf_isom_set_track_matrix(GF_ISOFile *the_file, u32 trackNumber, u32 matrix[9]) { GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak || !trak->Header) return GF_BAD_PARAM; memcpy(trak->Header->matrix, matrix, sizeof(trak->Header->matrix)); return GF_OK; } GF_EXPORT GF_Err gf_isom_set_track_layout_info(GF_ISOFile *the_file, u32 trackNumber, u32 width, u32 height, s32 translation_x, s32 translation_y, s16 layer) { GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak || !trak->Header) return GF_BAD_PARAM; trak->Header->width = width; trak->Header->height = height; trak->Header->matrix[6] = translation_x; trak->Header->matrix[7] = translation_y; trak->Header->layer = layer; return GF_OK; } GF_Err gf_isom_set_track_name(GF_ISOFile *the_file, u32 trackNumber, char *name) { GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (trak->name) gf_free(trak->name); trak->name = NULL; if (name) trak->name = gf_strdup(name); return GF_OK; } const char *gf_isom_get_track_name(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak) return NULL; return trak->name; } GF_Err gf_isom_store_movie_config(GF_ISOFile *movie, Bool remove_all) { u32 i, count, len; char *data; GF_BitStream *bs; bin128 binID; if (movie == NULL) return GF_BAD_PARAM; gf_isom_remove_user_data(movie, 0, GF_4CC('G', 'P', 'A', 'C'), binID); count = gf_isom_get_track_count(movie); for (i = 0; istorageMode); gf_bs_write_u32(bs, movie->interleavingTime); gf_bs_get_content(bs, &data, &len); gf_bs_del(bs); gf_isom_add_user_data(movie, 0, GF_4CC('G', 'P', 'A', 'C'), binID, data, len); gf_free(data); /*update tracks: interleaving group/priority and track edit name*/ for (i = 0; iMedia->information->sampleTable->groupID); gf_bs_write_u32(bs, trak->Media->information->sampleTable->trackPriority); len = trak->name ? (u32)strlen(trak->name) : 0; gf_bs_write_u32(bs, len); for (j = 0; jname[j]); gf_bs_get_content(bs, &data, &len); gf_bs_del(bs); gf_isom_add_user_data(movie, i + 1, GF_4CC('G', 'P', 'A', 'C'), binID, data, len); gf_free(data); } return GF_OK; } GF_Err gf_isom_load_movie_config(GF_ISOFile *movie) { u32 i, count, len; char *data; GF_BitStream *bs; Bool found_cfg; bin128 binID; if (movie == NULL) return GF_BAD_PARAM; found_cfg = GF_FALSE; /*restore movie*/ count = gf_isom_get_user_data_count(movie, 0, GF_4CC('G', 'P', 'A', 'C'), binID); for (i = 0; istorageMode = gf_bs_read_u8(bs); movie->interleavingTime = gf_bs_read_u32(bs); gf_bs_del(bs); gf_free(data); found_cfg = GF_TRUE; break; } for (i = 0; iMedia->information->sampleTable->groupID = gf_bs_read_u32(bs); trak->Media->information->sampleTable->trackPriority = gf_bs_read_u32(bs); len = gf_bs_read_u32(bs); if (len) { u32 k; trak->name = (char*)gf_malloc(sizeof(char)*(len + 1)); for (k = 0; kname[k] = gf_bs_read_u8(bs); trak->name[k] = 0; } gf_bs_del(bs); gf_free(data); found_cfg = GF_TRUE; break; } } return found_cfg ? GF_OK : GF_NOT_SUPPORTED; } GF_EXPORT GF_Err gf_isom_set_media_timescale(GF_ISOFile *the_file, u32 trackNumber, u32 newTS, Bool force_rescale) { Double scale; GF_TrackBox *trak; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak || !trak->Media | !trak->Media->mediaHeader) return GF_BAD_PARAM; if (trak->Media->mediaHeader->timeScale == newTS) return GF_OK; scale = newTS; scale /= trak->Media->mediaHeader->timeScale; trak->Media->mediaHeader->timeScale = newTS; if (!force_rescale) { u32 i, k, idx; GF_SampleTableBox *stbl = trak->Media->information->sampleTable; u64 cur_dts; u64*DTSs = NULL; s64*CTSs = NULL; if (trak->editBox) { GF_EdtsEntry *ent; u32 i = 0; while ((ent = (GF_EdtsEntry*)gf_list_enum(trak->editBox->editList->entryList, &i))) { ent->mediaTime = (u32)(scale*ent->mediaTime); } } if (!stbl || !stbl->TimeToSample) { return SetTrackDuration(trak); } idx = 0; cur_dts = 0; //unpack the DTSs DTSs = (u64*)gf_malloc(sizeof(u64) * (stbl->SampleSize->sampleCount)); CTSs = NULL; if (!DTSs) return GF_OUT_OF_MEM; if (stbl->CompositionOffset) { CTSs = (s64*)gf_malloc(sizeof(u64) * (stbl->SampleSize->sampleCount)); } for (i = 0; iTimeToSample->nb_entries; i++) { for (k = 0; kTimeToSample->entries[i].sampleCount; k++) { cur_dts += stbl->TimeToSample->entries[i].sampleDelta; DTSs[idx] = (u64)(cur_dts * scale); if (stbl->CompositionOffset) { s32 cts_o; stbl_GetSampleCTS(stbl->CompositionOffset, idx + 1, &cts_o); CTSs[idx] = (s64)(((s64)cur_dts + cts_o) * scale); } idx++; } } //repack DTS if (stbl->SampleSize->sampleCount) { stbl->TimeToSample->entries = gf_realloc(stbl->TimeToSample->entries, sizeof(GF_SttsEntry)*stbl->SampleSize->sampleCount); memset(stbl->TimeToSample->entries, 0, sizeof(GF_SttsEntry)*stbl->SampleSize->sampleCount); stbl->TimeToSample->nb_entries = 1; stbl->TimeToSample->entries[0].sampleDelta = (u32)DTSs[0]; stbl->TimeToSample->entries[0].sampleCount = 1; idx = 0; for (i = 1; i< stbl->SampleSize->sampleCount - 1; i++) { if (DTSs[i + 1] - DTSs[i] == stbl->TimeToSample->entries[idx].sampleDelta) { stbl->TimeToSample->entries[idx].sampleCount++; } else { idx++; stbl->TimeToSample->entries[idx].sampleDelta = (u32)(DTSs[i + 1] - DTSs[i]); stbl->TimeToSample->entries[idx].sampleCount = 1; } } stbl->TimeToSample->nb_entries = idx + 1; stbl->TimeToSample->entries = gf_realloc(stbl->TimeToSample->entries, sizeof(GF_SttsEntry)*stbl->TimeToSample->nb_entries); } if (CTSs && stbl->SampleSize->sampleCount>0) { //repack CTS stbl->CompositionOffset->entries = gf_realloc(stbl->CompositionOffset->entries, sizeof(GF_DttsEntry)*stbl->SampleSize->sampleCount); memset(stbl->CompositionOffset->entries, 0, sizeof(GF_DttsEntry)*stbl->SampleSize->sampleCount); stbl->CompositionOffset->nb_entries = 1; stbl->CompositionOffset->entries[0].decodingOffset = (s32)(CTSs[0] - DTSs[0]); stbl->CompositionOffset->entries[0].sampleCount = 1; idx = 0; for (i = 1; i< stbl->SampleSize->sampleCount; i++) { s32 cts_o = (s32)(CTSs[i] - DTSs[i]); if (cts_o == stbl->CompositionOffset->entries[idx].decodingOffset) { stbl->CompositionOffset->entries[idx].sampleCount++; } else { idx++; stbl->CompositionOffset->entries[idx].decodingOffset = cts_o; stbl->CompositionOffset->entries[idx].sampleCount = 1; } } stbl->CompositionOffset->nb_entries = idx + 1; stbl->CompositionOffset->entries = gf_realloc(stbl->CompositionOffset->entries, sizeof(GF_DttsEntry)*stbl->CompositionOffset->nb_entries); gf_free(CTSs); } gf_free(DTSs); if (stbl->CompositionToDecode) { stbl->CompositionToDecode->compositionEndTime = (s32)(stbl->CompositionToDecode->compositionEndTime * scale); stbl->CompositionToDecode->compositionStartTime = (s32)(stbl->CompositionToDecode->compositionStartTime * scale); stbl->CompositionToDecode->compositionToDTSShift = (s32)(stbl->CompositionToDecode->compositionToDTSShift * scale); stbl->CompositionToDecode->greatestDecodeToDisplayDelta = (s32)(stbl->CompositionToDecode->greatestDecodeToDisplayDelta * scale); stbl->CompositionToDecode->leastDecodeToDisplayDelta = (s32)(stbl->CompositionToDecode->leastDecodeToDisplayDelta * scale); } } return SetTrackDuration(trak); } GF_EXPORT Bool gf_isom_box_equal(GF_Box *a, GF_Box *b) { Bool ret; char *data1, *data2; u32 data1_size, data2_size; GF_BitStream *bs; if (a == b) return GF_TRUE; if (!a || !b) return GF_FALSE; data1 = data2 = NULL; bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); gf_isom_box_size(a); gf_isom_box_write(a, bs); gf_bs_get_content(bs, &data1, &data1_size); gf_bs_del(bs); bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); gf_isom_box_size(b); gf_isom_box_write(b, bs); gf_bs_get_content(bs, &data2, &data2_size); gf_bs_del(bs); ret = GF_FALSE; if (data1_size == data2_size) { ret = (memcmp(data1, data2, sizeof(char)*data1_size) == 0) ? GF_TRUE : GF_FALSE; } gf_free(data1); gf_free(data2); return ret; } GF_EXPORT Bool gf_isom_is_same_sample_description(GF_ISOFile *f1, u32 tk1, u32 sdesc_index1, GF_ISOFile *f2, u32 tk2, u32 sdesc_index2) { u32 i, count; GF_TrackBox *trak1, *trak2; GF_ESD *esd1, *esd2; Bool need_memcmp; GF_Box *a, *b; /*get orig sample desc and clone it*/ trak1 = gf_isom_get_track_from_file(f1, tk1); if (!trak1 || !trak1->Media) return GF_FALSE; trak2 = gf_isom_get_track_from_file(f2, tk2); if (!trak2 || !trak2->Media) return GF_FALSE; if (trak1->Media->handler->handlerType != trak2->Media->handler->handlerType) return GF_FALSE; count = gf_list_count(trak1->Media->information->sampleTable->SampleDescription->other_boxes); if (count != gf_list_count(trak2->Media->information->sampleTable->SampleDescription->other_boxes)) { if (!sdesc_index1 && !sdesc_index2) return GF_FALSE; } need_memcmp = GF_TRUE; for (i = 0; iMedia->information->sampleTable->SampleDescription->other_boxes, i); GF_Box *ent2 = (GF_Box *)gf_list_get(trak2->Media->information->sampleTable->SampleDescription->other_boxes, i); if (sdesc_index1) ent1 = (GF_Box *)gf_list_get(trak1->Media->information->sampleTable->SampleDescription->other_boxes, sdesc_index1 - 1); if (sdesc_index2) ent2 = (GF_Box *)gf_list_get(trak2->Media->information->sampleTable->SampleDescription->other_boxes, sdesc_index2 - 1); if (!ent1 || !ent2) return GF_FALSE; if (ent1->type != ent2->type) return GF_FALSE; switch (ent1->type) { /*for MPEG-4 streams, only compare decSpecInfo (bitrate may not be the same but that's not an issue)*/ case GF_ISOM_BOX_TYPE_MP4S: case GF_ISOM_BOX_TYPE_MP4A: case GF_ISOM_BOX_TYPE_MP4V: case GF_ISOM_BOX_TYPE_ENCA: case GF_ISOM_BOX_TYPE_ENCV: case GF_ISOM_BOX_TYPE_ENCS: Media_GetESD(trak1->Media, sdesc_index1 ? sdesc_index1 : i + 1, &esd1, GF_TRUE); Media_GetESD(trak2->Media, sdesc_index2 ? sdesc_index2 : i + 1, &esd2, GF_TRUE); if (!esd1 || !esd2) continue; need_memcmp = GF_FALSE; if (esd1->decoderConfig->streamType != esd2->decoderConfig->streamType) return GF_FALSE; if (esd1->decoderConfig->objectTypeIndication != esd2->decoderConfig->objectTypeIndication) return GF_FALSE; if (!esd1->decoderConfig->decoderSpecificInfo && esd2->decoderConfig->decoderSpecificInfo) return GF_FALSE; if (esd1->decoderConfig->decoderSpecificInfo && !esd2->decoderConfig->decoderSpecificInfo) return GF_FALSE; if (!esd1->decoderConfig->decoderSpecificInfo || !esd2->decoderConfig->decoderSpecificInfo) continue; if (memcmp(esd1->decoderConfig->decoderSpecificInfo->data, esd2->decoderConfig->decoderSpecificInfo->data, sizeof(char)*esd1->decoderConfig->decoderSpecificInfo->dataLength) != 0) return GF_FALSE; break; case GF_ISOM_BOX_TYPE_HVT1: return GF_TRUE; 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_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: { GF_MPEGVisualSampleEntryBox *avc1 = (GF_MPEGVisualSampleEntryBox *)ent1; GF_MPEGVisualSampleEntryBox *avc2 = (GF_MPEGVisualSampleEntryBox *)ent2; if (avc1->hevc_config) a = (GF_Box *)avc1->hevc_config; else if (avc1->lhvc_config) a = (GF_Box *)avc1->lhvc_config; else if (avc1->svc_config) a = (GF_Box *)avc1->svc_config; else a = (GF_Box *)avc1->avc_config; if (avc2->hevc_config) b = (GF_Box *)avc2->hevc_config; else if (avc2->lhvc_config) b = (GF_Box *)avc2->lhvc_config; else if (avc2->svc_config) b = (GF_Box *)avc2->svc_config; else b = (GF_Box *)avc2->avc_config; return gf_isom_box_equal(a, b); } break; case GF_ISOM_BOX_TYPE_LSR1: { GF_LASeRSampleEntryBox *lsr1 = (GF_LASeRSampleEntryBox *)ent1; GF_LASeRSampleEntryBox *lsr2 = (GF_LASeRSampleEntryBox *)ent2; if (lsr1->lsr_config && lsr2->lsr_config && lsr1->lsr_config->hdr && lsr2->lsr_config->hdr && (lsr1->lsr_config->hdr_size == lsr2->lsr_config->hdr_size) && !memcmp(lsr1->lsr_config->hdr, lsr2->lsr_config->hdr, lsr2->lsr_config->hdr_size) ) { return GF_TRUE; } return GF_FALSE; } break; case GF_ISOM_BOX_TYPE_STPP: { GF_MetaDataSampleEntryBox *stpp1 = (GF_MetaDataSampleEntryBox *)ent1; GF_MetaDataSampleEntryBox *stpp2 = (GF_MetaDataSampleEntryBox *)ent2; if (stpp1->xml_namespace && stpp2->xml_namespace && !strcmp(stpp1->xml_namespace, stpp2->xml_namespace)) { return GF_TRUE; } return GF_FALSE; } break; case GF_ISOM_BOX_TYPE_SBTT: { return GF_FALSE; } break; case GF_ISOM_BOX_TYPE_STXT: { GF_MetaDataSampleEntryBox *stxt1 = (GF_MetaDataSampleEntryBox *)ent1; GF_MetaDataSampleEntryBox *stxt2 = (GF_MetaDataSampleEntryBox *)ent2; if (stxt1->mime_type && stxt2->mime_type && ((!stxt1->config && !stxt2->config) || (stxt1->config && stxt2->config && stxt1->config->config && stxt2->config->config && !strcmp(stxt1->config->config, stxt2->config->config)))) { return GF_TRUE; } return GF_FALSE; } break; } if (sdesc_index1 && sdesc_index2) break; } if (!need_memcmp) return GF_TRUE; a = (GF_Box *)trak1->Media->information->sampleTable->SampleDescription; b = (GF_Box *)trak2->Media->information->sampleTable->SampleDescription; return gf_isom_box_equal(a, b); } GF_EXPORT u64 gf_isom_estimate_size(GF_ISOFile *movie) { GF_Err e; GF_Box *a; u32 i, count; u64 mdat_size; if (!movie || !movie->moov) return 0; mdat_size = 0; count = gf_list_count(movie->moov->trackList); for (i = 0; i 0xFFFFFFFF) mdat_size += 8; } i = 0; while ((a = (GF_Box*)gf_list_enum(movie->TopBoxes, &i))) { e = gf_isom_box_size(a); if (e == GF_OK) mdat_size += a->size; } return mdat_size; } //set shadowing on/off GF_Err gf_isom_remove_sync_shadows(GF_ISOFile *movie, u32 trackNumber) { GF_TrackBox *trak; GF_SampleTableBox *stbl; if (movie->openMode == GF_ISOM_OPEN_READ) return GF_ISOM_INVALID_MODE; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; if (stbl->ShadowSync) { gf_isom_box_del((GF_Box *)stbl->ShadowSync); stbl->ShadowSync = NULL; } return GF_OK; } //fill the sync shadow table GF_Err gf_isom_set_sync_shadow(GF_ISOFile *movie, u32 trackNumber, u32 sampleNumber, u32 syncSample) { GF_TrackBox *trak; GF_SampleTableBox *stbl; SAPType isRAP; GF_Err e; if (movie->openMode == GF_ISOM_OPEN_READ) return GF_ISOM_INVALID_MODE; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !sampleNumber || !syncSample) return GF_BAD_PARAM; stbl = trak->Media->information->sampleTable; if (!stbl->ShadowSync) stbl->ShadowSync = (GF_ShadowSyncBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_STSH); //if no sync, skip if (!stbl->SyncSample) return GF_OK; //else set the sync shadow. //if the sample is sync, ignore e = stbl_GetSampleRAP(stbl->SyncSample, sampleNumber, &isRAP, NULL, NULL); if (e) return e; if (isRAP) return GF_OK; //if the shadowing sample is not sync, error e = stbl_GetSampleRAP(stbl->SyncSample, syncSample, &isRAP, NULL, NULL); if (e) return e; if (!isRAP) return GF_BAD_PARAM; return stbl_SetSyncShadow(stbl->ShadowSync, sampleNumber, syncSample); } //set the GroupID of a track (only used for interleaving) GF_Err gf_isom_set_track_interleaving_group(GF_ISOFile *movie, u32 trackNumber, u32 GroupID) { GF_TrackBox *trak; if (movie->openMode != GF_ISOM_OPEN_EDIT) return GF_ISOM_INVALID_MODE; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !GroupID) return GF_BAD_PARAM; trak->Media->information->sampleTable->groupID = GroupID; return GF_OK; } //set the Priority of a track within a Group (only used for tight interleaving) //Priority ranges from 1 to 9 GF_Err gf_isom_set_track_priority_in_group(GF_ISOFile *movie, u32 trackNumber, u32 Priority) { GF_TrackBox *trak; if (movie->openMode != GF_ISOM_OPEN_EDIT) return GF_ISOM_INVALID_MODE; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !Priority) return GF_BAD_PARAM; trak->Media->information->sampleTable->trackPriority = Priority > 255 ? 255 : Priority; return GF_OK; } //set the max SamplesPerChunk (for file optimization) GF_Err gf_isom_set_max_samples_per_chunk(GF_ISOFile *movie, u32 trackNumber, u32 maxSamplesPerChunk) { GF_TrackBox *trak; if (movie->openMode == GF_ISOM_OPEN_READ) return GF_ISOM_INVALID_MODE; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !maxSamplesPerChunk) return GF_BAD_PARAM; trak->Media->information->sampleTable->MaxSamplePerChunk = maxSamplesPerChunk; return GF_OK; } GF_EXPORT GF_Err gf_isom_set_extraction_slc(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, GF_SLConfig *slConfig) { GF_TrackBox *trak; GF_SampleEntryBox *entry; GF_Err e; GF_SLConfig **slc; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, StreamDescriptionIndex, &entry, NULL); if (e) return e; //we must be sure we are not using a remote ESD switch (entry->type) { case GF_ISOM_BOX_TYPE_MP4S: if (((GF_MPEGSampleEntryBox *)entry)->esd->desc->slConfig->predefined != SLPredef_MP4) return GF_BAD_PARAM; slc = &((GF_MPEGSampleEntryBox *)entry)->slc; break; case GF_ISOM_BOX_TYPE_MP4A: if (((GF_MPEGAudioSampleEntryBox *)entry)->esd->desc->slConfig->predefined != SLPredef_MP4) return GF_BAD_PARAM; slc = &((GF_MPEGAudioSampleEntryBox *)entry)->slc; break; case GF_ISOM_BOX_TYPE_MP4V: if (((GF_MPEGVisualSampleEntryBox *)entry)->esd->desc->slConfig->predefined != SLPredef_MP4) return GF_BAD_PARAM; slc = &((GF_MPEGVisualSampleEntryBox *)entry)->slc; break; default: return GF_BAD_PARAM; } if (*slc) { gf_odf_desc_del((GF_Descriptor *)*slc); *slc = NULL; } if (!slConfig) return GF_OK; //finally duplicate the SL return gf_odf_desc_copy((GF_Descriptor *)slConfig, (GF_Descriptor **)slc); } GF_Err gf_isom_get_extraction_slc(GF_ISOFile *the_file, u32 trackNumber, u32 StreamDescriptionIndex, GF_SLConfig **slConfig) { GF_TrackBox *trak; GF_SampleEntryBox *entry; GF_Err e; GF_SLConfig *slc; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; e = Media_GetSampleDesc(trak->Media, StreamDescriptionIndex, &entry, NULL); if (e) return e; //we must be sure we are not using a remote ESD slc = NULL; *slConfig = NULL; switch (entry->type) { case GF_ISOM_BOX_TYPE_MP4S: if (((GF_MPEGSampleEntryBox *)entry)->esd->desc->slConfig->predefined != SLPredef_MP4) return GF_BAD_PARAM; slc = ((GF_MPEGSampleEntryBox *)entry)->slc; break; case GF_ISOM_BOX_TYPE_MP4A: if (((GF_MPEGAudioSampleEntryBox *)entry)->esd->desc->slConfig->predefined != SLPredef_MP4) return GF_BAD_PARAM; slc = ((GF_MPEGAudioSampleEntryBox *)entry)->slc; break; case GF_ISOM_BOX_TYPE_MP4V: if (((GF_MPEGVisualSampleEntryBox *)entry)->esd->desc->slConfig->predefined != SLPredef_MP4) return GF_BAD_PARAM; slc = ((GF_MPEGVisualSampleEntryBox *)entry)->slc; break; default: return GF_BAD_PARAM; } if (!slc) return GF_OK; //finally duplicate the SL return gf_odf_desc_copy((GF_Descriptor *)slc, (GF_Descriptor **)slConfig); } u32 gf_isom_get_track_group(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak) return 0; return trak->Media->information->sampleTable->groupID; } u32 gf_isom_get_track_priority_in_group(GF_ISOFile *the_file, u32 trackNumber) { GF_TrackBox *trak; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak) return 0; return trak->Media->information->sampleTable->trackPriority; } GF_EXPORT GF_Err gf_isom_make_interleave(GF_ISOFile *file, Double TimeInSec) { GF_Err e; if (gf_isom_get_mode(file) < GF_ISOM_OPEN_EDIT) return GF_BAD_PARAM; e = gf_isom_set_storage_mode(file, GF_ISOM_STORE_DRIFT_INTERLEAVED); if (e) return e; return gf_isom_set_interleave_time(file, (u32)(TimeInSec * gf_isom_get_timescale(file))); } GF_EXPORT GF_Err gf_isom_set_handler_name(GF_ISOFile *the_file, u32 trackNumber, const char *nameUTF8) { GF_TrackBox *trak; trak = gf_isom_get_track_from_file(the_file, trackNumber); if (!trak) return GF_BAD_PARAM; if (trak->Media->handler->nameUTF8) gf_free(trak->Media->handler->nameUTF8); trak->Media->handler->nameUTF8 = NULL; if (!nameUTF8) return GF_OK; if (!strnicmp(nameUTF8, "file://", 7)) { u8 BOM[4]; FILE *f = gf_fopen(nameUTF8 + 7, "rb"); u64 size; if (!f) return GF_URL_ERROR; gf_fseek(f, 0, SEEK_END); size = gf_ftell(f); gf_fseek(f, 0, SEEK_SET); if (3 != fread(BOM, sizeof(char), 3, f)) { gf_fclose(f); return GF_CORRUPTED_DATA; } /*skip BOM if any*/ if ((BOM[0] == 0xEF) && (BOM[1] == 0xBB) && (BOM[2] == 0xBF)) size -= 3; else if ((BOM[0] == 0xEF) || (BOM[0] == 0xFF)) { gf_fclose(f); return GF_BAD_PARAM; } else gf_fseek(f, 0, SEEK_SET); trak->Media->handler->nameUTF8 = (char*)gf_malloc(sizeof(char)*(size_t)(size + 1)); size = fread(trak->Media->handler->nameUTF8, sizeof(char), (size_t)size, f); trak->Media->handler->nameUTF8[size] = 0; gf_fclose(f); } else { u32 i, j, len; char szOrig[1024], szLine[1024]; strcpy(szOrig, nameUTF8); j = 0; len = (u32)strlen(szOrig); for (i = 0; i> 6) & 0x3); j++; szOrig[i] &= 0xbf; } /*UTF8 2 bytes char */ else if ((szOrig[i] & 0xe0) == 0xc0) { szLine[j] = szOrig[i]; i++; j++; } /*UTF8 3 bytes char */ else if ((szOrig[i] & 0xf0) == 0xe0) { szLine[j] = szOrig[i]; i++; j++; szLine[j] = szOrig[i]; i++; j++; } /*UTF8 4 bytes char */ else if ((szOrig[i] & 0xf8) == 0xf0) { szLine[j] = szOrig[i]; i++; j++; szLine[j] = szOrig[i]; i++; j++; szLine[j] = szOrig[i]; i++; j++; } } szLine[j] = szOrig[i]; j++; } szLine[j] = 0; trak->Media->handler->nameUTF8 = gf_strdup(szLine); } return GF_OK; } GF_Err gf_isom_clone_root_od(GF_ISOFile *input, GF_ISOFile *output) { GF_List *esds; GF_Err e; u32 i; GF_Descriptor *desc; e = gf_isom_remove_root_od(output); if (e) return e; if (!input->moov || !input->moov->iods || !input->moov->iods->descriptor) return GF_OK; gf_isom_insert_moov(output); e = AddMovieIOD(output->moov, 0); if (e) return e; if (output->moov->iods->descriptor) gf_odf_desc_del(output->moov->iods->descriptor); output->moov->iods->descriptor = NULL; gf_odf_desc_copy(input->moov->iods->descriptor, &output->moov->iods->descriptor); switch (output->moov->iods->descriptor->tag) { case GF_ODF_ISOM_IOD_TAG: esds = ((GF_IsomInitialObjectDescriptor *)output->moov->iods->descriptor)->ES_ID_IncDescriptors; break; case GF_ODF_ISOM_OD_TAG: esds = ((GF_IsomObjectDescriptor *)output->moov->iods->descriptor)->ES_ID_IncDescriptors; break; default: return GF_ISOM_INVALID_FILE; } //get the desc i = 0; while ((desc = (GF_Descriptor*)gf_list_enum(esds, &i))) { gf_odf_desc_del(desc); gf_list_rem(esds, i - 1); } return GF_OK; } GF_EXPORT GF_Err gf_isom_set_media_type(GF_ISOFile *movie, u32 trackNumber, u32 new_type) { GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !new_type) return GF_BAD_PARAM; trak->Media->handler->handlerType = new_type; return GF_OK; } GF_EXPORT GF_Err gf_isom_set_media_subtype(GF_ISOFile *movie, u32 trackNumber, u32 sampleDescriptionIndex, u32 new_type) { GF_SampleEntryBox*entry; GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !sampleDescriptionIndex || !new_type) return GF_BAD_PARAM; entry = (GF_SampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, sampleDescriptionIndex - 1); if (!entry) return GF_BAD_PARAM; entry->type = new_type; return GF_OK; } GF_EXPORT GF_Err gf_isom_set_JPEG2000(GF_ISOFile *mov, Bool set_on) { if (!mov) return GF_BAD_PARAM; mov->is_jp2 = set_on; return GF_OK; } GF_Err gf_isom_remove_uuid(GF_ISOFile *movie, u32 trackNumber, bin128 UUID) { u32 i, count; GF_List *list; if (trackNumber == (u32)-1) { if (!movie) return GF_BAD_PARAM; list = movie->TopBoxes; } else if (trackNumber) { GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; list = trak->other_boxes; } else { if (!movie) return GF_BAD_PARAM; list = movie->moov->other_boxes; } count = list ? gf_list_count(list) : 0; for (i = 0; itype != GF_ISOM_BOX_TYPE_UUID) continue; if (memcmp(UUID, uuid->uuid, sizeof(bin128))) continue; gf_list_rem(list, i); i--; count--; gf_isom_box_del((GF_Box*)uuid); } return GF_OK; } GF_EXPORT GF_Err gf_isom_add_uuid(GF_ISOFile *movie, u32 trackNumber, bin128 UUID, const char *data, u32 data_size) { GF_List *list; GF_UnknownUUIDBox *uuid; if (!data_size || !data) return GF_OK; if (trackNumber == (u32)-1) { if (!movie) return GF_BAD_PARAM; list = movie->TopBoxes; } else if (trackNumber) { GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->other_boxes) trak->other_boxes = gf_list_new(); list = trak->other_boxes; } else { if (!movie) return GF_BAD_PARAM; if (!movie->moov->other_boxes) movie->moov->other_boxes = gf_list_new(); list = movie->moov->other_boxes; } GF_SAFEALLOC(uuid, GF_UnknownUUIDBox); if (!uuid) return GF_OUT_OF_MEM; uuid->type = GF_ISOM_BOX_TYPE_UUID; memcpy(uuid->uuid, UUID, sizeof(bin128)); uuid->dataSize = data_size; uuid->data = (char*)gf_malloc(sizeof(char)*data_size); memcpy(uuid->data, data, sizeof(char)*data_size); gf_list_add(list, uuid); return GF_OK; } /*Apple extensions*/ GF_EXPORT GF_Err gf_isom_apple_set_tag(GF_ISOFile *mov, u32 tag, const char *data, u32 data_len) { GF_BitStream *bs; GF_Err e; GF_ItemListBox *ilst; GF_MetaBox *meta; GF_ListItemBox *info; u32 btype, i; e = CanAccessMovie(mov, GF_ISOM_OPEN_WRITE); if (e) return e; meta = gf_isom_apple_create_meta_extensions(mov); if (!meta) return GF_BAD_PARAM; ilst = gf_ismo_locate_box(meta->other_boxes, GF_ISOM_BOX_TYPE_ILST, NULL); if (!ilst) { ilst = (GF_ItemListBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_ILST); if (!meta->other_boxes) meta->other_boxes = gf_list_new(); gf_list_add(meta->other_boxes, ilst); } if (tag == GF_ISOM_ITUNE_GENRE) { btype = data ? GF_ISOM_BOX_TYPE_0xA9GEN : GF_ISOM_BOX_TYPE_GNRE; } else { btype = tag; } /*remove tag*/ i = 0; while ((info = (GF_ListItemBox*)gf_list_enum(ilst->other_boxes, &i))) { if (info->type == btype) { gf_list_rem(ilst->other_boxes, i - 1); gf_isom_box_del((GF_Box *)info); info = NULL; break; } } if (data != NULL) { info = (GF_ListItemBox *)gf_isom_box_new(btype); if (info == NULL) return GF_OUT_OF_MEM; switch (btype) { case GF_ISOM_BOX_TYPE_TRKN: case GF_ISOM_BOX_TYPE_DISK: case GF_ISOM_BOX_TYPE_GNRE: info->data->flags = 0x0; break; case GF_ISOM_BOX_TYPE_PGAP: case GF_ISOM_BOX_TYPE_CPIL: info->data->flags = 0x15; break; default: info->data->flags = 0x1; break; } if (tag == GF_ISOM_ITUNE_COVER_ART) { if (data_len & 0x80000000) { data_len = (data_len & 0x7FFFFFFF); info->data->flags = 14; } else { info->data->flags = 13; } } info->data->dataSize = data_len; info->data->data = (char*)gf_malloc(sizeof(char)*data_len); memcpy(info->data->data, data, sizeof(char)*data_len); } else if (data_len && (tag == GF_ISOM_ITUNE_GENRE)) { info = (GF_ListItemBox *)gf_isom_box_new(btype); if (info == NULL) return GF_OUT_OF_MEM; bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); gf_bs_write_u16(bs, data_len); gf_bs_get_content(bs, &info->data->data, &info->data->dataSize); info->data->flags = 0x0; gf_bs_del(bs); } else if (data_len && (tag == GF_ISOM_ITUNE_COMPILATION)) { info = (GF_ListItemBox *)gf_isom_box_new(btype); if (info == NULL) return GF_OUT_OF_MEM; info->data->data = (char*)gf_malloc(sizeof(char)); info->data->data[0] = 1; info->data->dataSize = 1; info->data->flags = 21; } else if (data_len && (tag == GF_ISOM_ITUNE_TEMPO)) { info = (GF_ListItemBox *)gf_isom_box_new(btype); if (info == NULL) return GF_OUT_OF_MEM; bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); gf_bs_write_u16(bs, data_len); gf_bs_get_content(bs, &info->data->data, &info->data->dataSize); info->data->flags = 0x15; gf_bs_del(bs); } if (!info || (tag == GF_ISOM_ITUNE_ALL)) { if (!gf_list_count(ilst->other_boxes) || (tag == GF_ISOM_ITUNE_ALL)) { gf_list_del_item(meta->other_boxes, ilst); gf_isom_box_del((GF_Box *)ilst); } return GF_OK; } return gf_list_add(ilst->other_boxes, info); } GF_EXPORT GF_Err gf_isom_set_alternate_group_id(GF_ISOFile *movie, u32 trackNumber, u32 groupId) { GF_TrackBox *trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; trak->Header->alternate_group = groupId; return GF_OK; } GF_EXPORT GF_Err gf_isom_set_track_switch_parameter(GF_ISOFile *movie, u32 trackNumber, u32 trackRefGroup, Bool is_switch_group, u32 *switchGroupID, u32 *criteriaList, u32 criteriaListCount) { GF_TrackSelectionBox *tsel; GF_TrackBox *trak; GF_UserDataMap *map; GF_Err e; u32 alternateGroupID = 0; u32 next_switch_group_id = 0; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak || !switchGroupID) return GF_BAD_PARAM; if (trackRefGroup) { GF_TrackBox *trak_ref = gf_isom_get_track_from_file(movie, trackRefGroup); if (trak_ref != trak) { if (!trak_ref || !trak_ref->Header->alternate_group) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Track %d has not an alternate group - skipping\n", trak_ref->Header->trackID)); return GF_BAD_PARAM; } alternateGroupID = trak_ref->Header->alternate_group; } else { alternateGroupID = trak->Header->alternate_group; } } if (!alternateGroupID) { /*there is a function for this ....*/ if (trak->Header->alternate_group) { GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Track %d has already an alternate group - skipping\n", trak->Header->trackID)); return GF_BAD_PARAM; } alternateGroupID = gf_isom_get_next_alternate_group_id(movie); } if (is_switch_group) { u32 i = 0; while (i< gf_isom_get_track_count(movie)) { //locate first available ID GF_TrackBox *a_trak = gf_isom_get_track_from_file(movie, i + 1); if (a_trak->udta) { u32 j, count; map = udta_getEntry(a_trak->udta, GF_ISOM_BOX_TYPE_TSEL, NULL); if (map) { count = gf_list_count(map->other_boxes); for (j = 0; jother_boxes, j); if (*switchGroupID) { if (tsel->switchGroup == next_switch_group_id) { if (a_trak->Header->alternate_group != alternateGroupID) return GF_BAD_PARAM; } } else { if (tsel->switchGroup && (tsel->switchGroup >= next_switch_group_id)) next_switch_group_id = tsel->switchGroup; } } } } i++; } if (!*switchGroupID) *switchGroupID = next_switch_group_id + 1; } trak->Header->alternate_group = alternateGroupID; tsel = NULL; if (*switchGroupID) { if (!trak->udta) { e = trak_AddBox((GF_Box*)trak, gf_isom_box_new(GF_ISOM_BOX_TYPE_UDTA)); if (e) return e; } map = udta_getEntry(trak->udta, GF_ISOM_BOX_TYPE_TSEL, NULL); /*locate tsel box with no switch group*/ if (map) { u32 j, count = gf_list_count(map->other_boxes); for (j = 0; jother_boxes, j); if (tsel->switchGroup == *switchGroupID) break; tsel = NULL; } } if (!tsel) { tsel = (GF_TrackSelectionBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_TSEL); e = udta_AddBox(trak->udta, (GF_Box *)tsel); if (e) return e; } tsel->switchGroup = *switchGroupID; tsel->attributeListCount = criteriaListCount; if (tsel->attributeList) gf_free(tsel->attributeList); tsel->attributeList = (u32*)gf_malloc(sizeof(u32)*criteriaListCount); memcpy(tsel->attributeList, criteriaList, sizeof(u32)*criteriaListCount); } return GF_OK; } void reset_tsel_box(GF_TrackBox *trak) { GF_UserDataMap *map; trak->Header->alternate_group = 0; map = udta_getEntry(trak->udta, GF_ISOM_BOX_TYPE_TSEL, NULL); if (map) { gf_list_del_item(trak->udta->recordList, map); gf_isom_box_array_del(map->other_boxes); gf_free(map); } } GF_EXPORT GF_Err gf_isom_reset_track_switch_parameter(GF_ISOFile *movie, u32 trackNumber, Bool reset_all_group) { GF_TrackBox *trak; u32 alternateGroupID = 0; trak = gf_isom_get_track_from_file(movie, trackNumber); if (!trak) return GF_BAD_PARAM; if (!trak->Header->alternate_group) return GF_OK; alternateGroupID = trak->Header->alternate_group; if (reset_all_group) { u32 i = 0; while (i< gf_isom_get_track_count(movie)) { //locate first available ID GF_TrackBox *a_trak = gf_isom_get_track_from_file(movie, i + 1); if (a_trak->Header->alternate_group == alternateGroupID) reset_tsel_box(a_trak); i++; } } else { reset_tsel_box(trak); } return GF_OK; } GF_EXPORT GF_Err gf_isom_reset_switch_parameters(GF_ISOFile *movie) { u32 i = 0; while (i< gf_isom_get_track_count(movie)) { //locate first available ID GF_TrackBox *a_trak = gf_isom_get_track_from_file(movie, i + 1); reset_tsel_box(a_trak); i++; } return GF_OK; } GF_Err gf_isom_add_subsample(GF_ISOFile *movie, u32 track, u32 sampleNumber, u32 flags, u32 subSampleSize, u8 priority, u32 reserved, Bool discardable) { u32 i, count; GF_SubSampleInformationBox *sub_samples; GF_TrackBox *trak; GF_Err e; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, track); if (!trak || !trak->Media || !trak->Media->information->sampleTable) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->sub_samples) { trak->Media->information->sampleTable->sub_samples = gf_list_new(); } sub_samples = NULL; count = gf_list_count(trak->Media->information->sampleTable->sub_samples); for (i = 0; iMedia->information->sampleTable->sub_samples, i); if (sub_samples->flags == flags) break; sub_samples = NULL; } if (!sub_samples) { sub_samples = (GF_SubSampleInformationBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_SUBS); gf_list_add(trak->Media->information->sampleTable->sub_samples, sub_samples); sub_samples->version = (subSampleSize>0xFFFF) ? 1 : 0; sub_samples->flags = flags; } return gf_isom_add_subsample_info(sub_samples, sampleNumber, subSampleSize, priority, reserved, discardable); } GF_EXPORT GF_Err gf_isom_set_rvc_config(GF_ISOFile *movie, u32 track, u32 sampleDescriptionIndex, u16 rvc_predefined, char *mime, char *data, u32 size) { GF_MPEGVisualSampleEntryBox *entry; GF_Err e; GF_TrackBox *trak; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, track); if (!trak) return GF_BAD_PARAM; entry = (GF_MPEGVisualSampleEntryBox *)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, sampleDescriptionIndex - 1); if (!entry) return GF_BAD_PARAM; switch (entry->type) { case GF_ISOM_BOX_TYPE_MP4V: 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_ENCV: 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: break; default: return GF_BAD_PARAM; } if (entry->rvcc && entry->rvcc->rvc_meta_idx) { gf_isom_remove_meta_item(movie, GF_FALSE, track, entry->rvcc->rvc_meta_idx); entry->rvcc->rvc_meta_idx = 0; } if (!entry->rvcc) { entry->rvcc = (GF_RVCConfigurationBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_RVCC); } entry->rvcc->predefined_rvc_config = rvc_predefined; if (!rvc_predefined) { e = gf_isom_set_meta_type(movie, GF_FALSE, track, GF_4CC('r', 'v', 'c', 'i')); if (e) return e; gf_isom_modify_alternate_brand(movie, GF_ISOM_BRAND_ISO2, 1); e = gf_isom_add_meta_item_memory(movie, GF_FALSE, track, "rvcconfig.xml", 0, GF_4CC('m', 'i', 'm', 'e'), mime, NULL, NULL, data, size, NULL); if (e) return e; entry->rvcc->rvc_meta_idx = gf_isom_get_meta_item_count(movie, GF_FALSE, track); } return GF_OK; } /*for now not exported*/ /*expands sampleGroup table for the given grouping type and sample_number. If sample_number is 0, just appends an entry at the end of the table*/ static GF_Err gf_isom_add_sample_group_entry(GF_List *sampleGroups, u32 sample_number, u32 grouping_type, u32 grouping_type_parameter, u32 sampleGroupDescriptionIndex) { GF_SampleGroupBox *sgroup = NULL; u32 i, count, last_sample_in_entry; count = gf_list_count(sampleGroups); for (i = 0; igrouping_type == grouping_type) break; sgroup = NULL; } if (!sgroup) { sgroup = (GF_SampleGroupBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_SBGP); sgroup->grouping_type = grouping_type; sgroup->grouping_type_parameter = grouping_type_parameter; gf_list_add(sampleGroups, sgroup); } /*used in fragments, means we are adding the last sample*/ if (!sample_number) { sample_number = 1; if (sgroup->entry_count) { for (i = 0; ientry_count; i++) { sample_number += sgroup->sample_entries[i].sample_count; } } } if (!sgroup->entry_count) { u32 idx = 0; sgroup->entry_count = (sample_number>1) ? 2 : 1; sgroup->sample_entries = (GF_SampleGroupEntry*)gf_malloc(sizeof(GF_SampleGroupEntry) * sgroup->entry_count); if (sample_number>1) { sgroup->sample_entries[0].sample_count = sample_number - 1; sgroup->sample_entries[0].group_description_index = 0; idx = 1; } sgroup->sample_entries[idx].sample_count = 1; sgroup->sample_entries[idx].group_description_index = sampleGroupDescriptionIndex; return GF_OK; } last_sample_in_entry = 0; for (i = 0; ientry_count; i++) { /*TODO*/ if (last_sample_in_entry + sgroup->sample_entries[i].sample_count > sample_number) return GF_NOT_SUPPORTED; last_sample_in_entry += sgroup->sample_entries[i].sample_count; } if (last_sample_in_entry == sample_number) { if (sgroup->sample_entries[sgroup->entry_count - 1].group_description_index == sampleGroupDescriptionIndex) return GF_OK; else return GF_NOT_SUPPORTED; } if ((sgroup->sample_entries[sgroup->entry_count - 1].group_description_index == sampleGroupDescriptionIndex) && (last_sample_in_entry + 1 == sample_number)) { sgroup->sample_entries[sgroup->entry_count - 1].sample_count++; return GF_OK; } /*last entry was an empty desc (no group associated), just add the number of samples missing until new one, then add new one*/ if (!sgroup->sample_entries[sgroup->entry_count - 1].group_description_index) { sgroup->sample_entries[sgroup->entry_count - 1].sample_count += sample_number - 1 - last_sample_in_entry; sgroup->sample_entries = (GF_SampleGroupEntry*)gf_realloc(sgroup->sample_entries, sizeof(GF_SampleGroupEntry) * (sgroup->entry_count + 1)); sgroup->sample_entries[sgroup->entry_count].sample_count = 1; sgroup->sample_entries[sgroup->entry_count].group_description_index = sampleGroupDescriptionIndex; sgroup->entry_count++; return GF_OK; } /*we are adding a sample with no desc, add entry at the end*/ if (!sampleGroupDescriptionIndex || (sample_number - 1 - last_sample_in_entry == 0)) { sgroup->sample_entries = (GF_SampleGroupEntry*)gf_realloc(sgroup->sample_entries, sizeof(GF_SampleGroupEntry) * (sgroup->entry_count + 1)); sgroup->sample_entries[sgroup->entry_count].sample_count = 1; sgroup->sample_entries[sgroup->entry_count].group_description_index = sampleGroupDescriptionIndex; sgroup->entry_count++; return GF_OK; } /*need to insert two entries ...*/ sgroup->sample_entries = (GF_SampleGroupEntry*)gf_realloc(sgroup->sample_entries, sizeof(GF_SampleGroupEntry) * (sgroup->entry_count + 2)); sgroup->sample_entries[sgroup->entry_count].sample_count = sample_number - 1 - last_sample_in_entry; sgroup->sample_entries[sgroup->entry_count].group_description_index = 0; sgroup->sample_entries[sgroup->entry_count + 1].sample_count = 1; sgroup->sample_entries[sgroup->entry_count + 1].group_description_index = sampleGroupDescriptionIndex; sgroup->entry_count += 2; return GF_OK; } #ifndef GPAC_DISABLE_ISOM_FRAGMENTS static GF_SampleGroupDescriptionBox *get_sgdp(GF_SampleTableBox *stbl, GF_TrackFragmentBox *traf, u32 grouping_type) #else static GF_SampleGroupDescriptionBox *get_sgdp(GF_SampleTableBox *stbl, void *traf, u32 grouping_type) #endif /* GPAC_DISABLE_ISOM_FRAGMENTS */ { GF_List *groupList; GF_SampleGroupDescriptionBox *sgdesc = NULL; u32 count, i; /*look in stbl or traf for sample sampleGroupsDescription*/ #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (traf) { if (!traf->sampleGroupsDescription) traf->sampleGroupsDescription = gf_list_new(); groupList = traf->sampleGroupsDescription; } else #endif { if (!stbl->sampleGroupsDescription) stbl->sampleGroupsDescription = gf_list_new(); groupList = stbl->sampleGroupsDescription; } count = gf_list_count(groupList); for (i = 0; igrouping_type == grouping_type) break; sgdesc = NULL; } if (!sgdesc) { sgdesc = (GF_SampleGroupDescriptionBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_SGPD); sgdesc->grouping_type = grouping_type; gf_list_add(groupList, sgdesc); } return sgdesc; } #ifndef GPAC_DISABLE_ISOM_FRAGMENTS static GF_Err gf_isom_set_sample_group_info_ex(GF_SampleTableBox *stbl, GF_TrackFragmentBox *traf, u32 sample_number, u32 grouping_type, u32 grouping_type_parameter, void *udta, void *(*sg_create_entry)(void *udta), Bool(*sg_compare_entry)(void *udta, void *entry)) #else static GF_Err gf_isom_set_sample_group_info_ex(GF_SampleTableBox *stbl, void *traf, u32 sample_number, u32 grouping_type, u32 grouping_type_parameter, void *udta, void *(*sg_create_entry)(void *udta), Bool(*sg_compare_entry)(void *udta, void *entry)) #endif /* GPAC_DISABLE_ISOM_FRAGMENTS */ { GF_List *groupList; void *entry; GF_SampleGroupDescriptionBox *sgdesc = NULL; u32 i, entry_idx; if (!stbl && !traf) return GF_BAD_PARAM; sgdesc = get_sgdp(stbl, traf, grouping_type); if (!sgdesc) return GF_OUT_OF_MEM; entry = NULL; for (i = 0; igroup_descriptions); i++) { entry = gf_list_get(sgdesc->group_descriptions, i); if (sg_compare_entry(udta, entry)) break; entry = NULL; } if (!entry) { entry = sg_create_entry(udta); if (!entry) return GF_IO_ERR; gf_list_add(sgdesc->group_descriptions, entry); } entry_idx = 1 + gf_list_find(sgdesc->group_descriptions, entry); /*look in stbl or traf for sample sampleGroups*/ #ifndef GPAC_DISABLE_ISOM_FRAGMENTS if (traf) { if (!traf->sampleGroups) traf->sampleGroups = gf_list_new(); groupList = traf->sampleGroups; entry_idx |= 0x10000; } else #endif { if (!stbl->sampleGroups) stbl->sampleGroups = gf_list_new(); groupList = stbl->sampleGroups; } return gf_isom_add_sample_group_entry(groupList, sample_number, grouping_type, grouping_type_parameter, entry_idx); } /*for now not exported*/ static GF_Err gf_isom_set_sample_group_info(GF_ISOFile *movie, u32 track, u32 sample_number, u32 grouping_type, u32 grouping_type_parameter, void *udta, void *(*sg_create_entry)(void *udta), Bool(*sg_compare_entry)(void *udta, void *entry)) { GF_Err e; GF_TrackBox *trak; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, track); if (!trak) return GF_BAD_PARAM; return gf_isom_set_sample_group_info_ex(trak->Media->information->sampleTable, NULL, sample_number, grouping_type, grouping_type_parameter, udta, sg_create_entry, sg_compare_entry); } GF_EXPORT GF_Err gf_isom_add_sample_group_info(GF_ISOFile *movie, u32 track, u32 grouping_type, void *data, u32 data_size, Bool is_default, u32 *sampleGroupDescriptionIndex) { GF_Err e; GF_TrackBox *trak; GF_DefaultSampleGroupDescriptionEntry *entry = NULL; GF_SampleGroupDescriptionBox *sgdesc = NULL; if (sampleGroupDescriptionIndex) *sampleGroupDescriptionIndex = 0; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, track); if (!trak) return GF_BAD_PARAM; sgdesc = get_sgdp(trak->Media->information->sampleTable, NULL, grouping_type); if (!sgdesc) return GF_OUT_OF_MEM; if (grouping_type == GF_4CC('o', 'i', 'n', 'f')) { GF_OperatingPointsInformation *ptr = gf_isom_oinf_new_entry(); GF_BitStream *bs = gf_bs_new(data, data_size, GF_BITSTREAM_READ); e = gf_isom_oinf_read_entry(ptr, bs); gf_bs_del(bs); if (e) { gf_isom_oinf_del_entry(ptr); return e; } e = gf_list_add(sgdesc->group_descriptions, ptr); if (e) return e; entry = (GF_DefaultSampleGroupDescriptionEntry *)ptr; } else if (grouping_type == GF_4CC('l', 'i', 'n', 'f')) { GF_LHVCLayerInformation *ptr = gf_isom_linf_new_entry(); GF_BitStream *bs = gf_bs_new(data, data_size, GF_BITSTREAM_READ); e = gf_isom_linf_read_entry(ptr, bs); gf_bs_del(bs); if (e) { gf_isom_linf_del_entry(ptr); return e; } e = gf_list_add(sgdesc->group_descriptions, ptr); if (e) return e; entry = (GF_DefaultSampleGroupDescriptionEntry *)ptr; } else { u32 i, count = gf_list_count(sgdesc->group_descriptions); for (i = 0; igroup_descriptions, i); if ((ent->length == data_size) && !memcmp(ent->data, data, data_size)) { entry = ent; break; } entry = NULL; } if (!entry) { GF_SAFEALLOC(entry, GF_DefaultSampleGroupDescriptionEntry); if (!entry) return GF_OUT_OF_MEM; entry->data = (u8*)gf_malloc(sizeof(char) * data_size); if (!entry->data) { gf_free(entry); return GF_OUT_OF_MEM; } entry->length = data_size; memcpy(entry->data, data, sizeof(char) * data_size); e = gf_list_add(sgdesc->group_descriptions, entry); if (e) { gf_free(entry->data); gf_free(entry); return e; } } } if (is_default) { sgdesc->default_description_index = 1 + gf_list_find(sgdesc->group_descriptions, entry); sgdesc->version = 2; } if (sampleGroupDescriptionIndex) *sampleGroupDescriptionIndex = 1 + gf_list_find(sgdesc->group_descriptions, entry); return GF_OK; } GF_EXPORT GF_Err gf_isom_remove_sample_group(GF_ISOFile *movie, u32 track, u32 grouping_type) { GF_Err e; GF_TrackBox *trak; GF_SampleGroupDescriptionBox *sgdesc = NULL; u32 count, i; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, track); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->sampleGroupsDescription) return GF_OK; count = gf_list_count(trak->Media->information->sampleTable->sampleGroupsDescription); for (i = 0; iMedia->information->sampleTable->sampleGroupsDescription, i); if (sgdesc->grouping_type == grouping_type) { gf_isom_box_del((GF_Box*)sgdesc); gf_list_rem(trak->Media->information->sampleTable->sampleGroupsDescription, i); i--; count--; } sgdesc = NULL; } return GF_OK; } GF_Err gf_isom_add_sample_info(GF_ISOFile *movie, u32 track, u32 sample_number, u32 grouping_type, u32 sampleGroupDescriptionIndex, u32 grouping_type_parameter) { GF_Err e; GF_TrackBox *trak; GF_List *groupList; e = CanAccessMovie(movie, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(movie, track); if (!trak) return GF_BAD_PARAM; if (!trak->Media->information->sampleTable->sampleGroups) trak->Media->information->sampleTable->sampleGroups = gf_list_new(); groupList = trak->Media->information->sampleTable->sampleGroups; return gf_isom_add_sample_group_entry(groupList, sample_number, grouping_type, grouping_type_parameter, sampleGroupDescriptionIndex); } void *sg_rap_create_entry(void *udta) { GF_VisualRandomAccessEntry *entry; u32 *num_leading_samples = (u32 *)udta; assert(udta); GF_SAFEALLOC(entry, GF_VisualRandomAccessEntry); if (!entry) return NULL; entry->num_leading_samples = *num_leading_samples; entry->num_leading_samples_known = entry->num_leading_samples ? 1 : 0; return entry; } Bool sg_rap_compare_entry(void *udta, void *entry) { u32 *num_leading_samples = (u32 *)udta; if (*num_leading_samples == ((GF_VisualRandomAccessEntry *)entry)->num_leading_samples) return GF_TRUE; return GF_FALSE; } GF_Err gf_isom_set_sample_rap_group(GF_ISOFile *movie, u32 track, u32 sample_number, u32 num_leading_samples) { return gf_isom_set_sample_group_info(movie, track, sample_number, GF_4CC('r', 'a', 'p', ' '), 0, &num_leading_samples, sg_rap_create_entry, sg_rap_compare_entry); } void *sg_roll_create_entry(void *udta) { GF_RollRecoveryEntry *entry; s16 *roll_distance = (s16 *)udta; GF_SAFEALLOC(entry, GF_RollRecoveryEntry); if (!entry) return NULL; entry->roll_distance = *roll_distance; return entry; } Bool sg_roll_compare_entry(void *udta, void *entry) { s16 *roll_distance = (s16 *)udta; if (*roll_distance == ((GF_RollRecoveryEntry *)entry)->roll_distance) return GF_TRUE; return GF_FALSE; } GF_Err gf_isom_set_sample_roll_group(GF_ISOFile *movie, u32 track, u32 sample_number, s16 roll_distance) { return gf_isom_set_sample_group_info(movie, track, sample_number, GF_4CC('r', 'o', 'l', 'l'), 0, &roll_distance, sg_roll_create_entry, sg_roll_compare_entry); } void *sg_encryption_create_entry(void *udta) { GF_CENCSampleEncryptionGroupEntry *entry; GF_BitStream *bs; GF_SAFEALLOC(entry, GF_CENCSampleEncryptionGroupEntry); if (!entry) return NULL; bs = gf_bs_new((char *)udta, sizeof(GF_CENCSampleEncryptionGroupEntry), GF_BITSTREAM_READ); gf_bs_read_u8(bs); //reserved entry->crypt_byte_block = gf_bs_read_int(bs, 4); entry->skip_byte_block = gf_bs_read_int(bs, 4); entry->IsProtected = gf_bs_read_u8(bs); entry->Per_Sample_IV_size = gf_bs_read_u8(bs); gf_bs_read_data(bs, (char *)entry->KID, 16); if ((entry->IsProtected == 1) && !entry->Per_Sample_IV_size) { entry->constant_IV_size = gf_bs_read_u8(bs); assert((entry->constant_IV_size == 8) || (entry->constant_IV_size == 16)); gf_bs_read_data(bs, (char *)entry->constant_IV, entry->constant_IV_size); } gf_bs_del(bs); return entry; } Bool sg_encryption_compare_entry(void *udta, void *entry) { u8 isEncrypted; u8 IV_size; bin128 KID; u8 constant_IV_size = 0; bin128 constant_IV; u8 crypt_byte_block, skip_byte_block; GF_BitStream *bs; GF_CENCSampleEncryptionGroupEntry *seig = (GF_CENCSampleEncryptionGroupEntry *)entry; Bool is_identical; bs = gf_bs_new((char *)udta, sizeof(GF_CENCSampleEncryptionGroupEntry), GF_BITSTREAM_READ); gf_bs_read_u8(bs); //reserved crypt_byte_block = gf_bs_read_int(bs, 4); skip_byte_block = gf_bs_read_int(bs, 4); isEncrypted = gf_bs_read_u8(bs); IV_size = gf_bs_read_u8(bs); gf_bs_read_data(bs, (char *)KID, 16); if (isEncrypted && !IV_size) { constant_IV_size = gf_bs_read_u8(bs); gf_bs_read_data(bs, (char *)constant_IV, 16); } gf_bs_del(bs); is_identical = GF_TRUE; if ((isEncrypted != seig->IsProtected) || (IV_size != seig->Per_Sample_IV_size) || (strncmp((const char *)KID, (const char *)seig->KID, 16))) is_identical = GF_FALSE; if ((crypt_byte_block != seig->crypt_byte_block) || (skip_byte_block != seig->skip_byte_block)) is_identical = GF_FALSE; if ((isEncrypted == 1) && !IV_size) { if ((constant_IV_size != seig->constant_IV_size) || (strncmp((const char *)constant_IV, (const char *)seig->constant_IV, constant_IV_size))) is_identical = GF_FALSE; } return is_identical; } #ifndef GPAC_DISABLE_ISOM_FRAGMENTS GF_Err gf_isom_copy_sample_group_entry_to_traf(GF_TrackFragmentBox *traf, GF_SampleTableBox *stbl, u32 grouping_type, u32 grouping_type_parameter, u32 sampleGroupDescriptionIndex, Bool sgpd_in_traf) { if (sgpd_in_traf) { void *entry = NULL; u32 i, count; GF_SampleGroupDescriptionBox *sgdesc = NULL; GF_BitStream *bs; count = gf_list_count(stbl->sampleGroupsDescription); for (i = 0; i < count; i++) { sgdesc = (GF_SampleGroupDescriptionBox *)gf_list_get(stbl->sampleGroupsDescription, i); if (sgdesc->grouping_type == grouping_type) break; sgdesc = NULL; } if (!sgdesc) return GF_BAD_PARAM; entry = gf_list_get(sgdesc->group_descriptions, sampleGroupDescriptionIndex - 1); if (!entry) return GF_BAD_PARAM; switch (grouping_type) { case GF_4CC('r', 'a', 'p', ' '): { char udta[2]; bs = gf_bs_new(udta, 2 * sizeof(char), GF_BITSTREAM_WRITE); gf_bs_write_u8(bs, ((GF_VisualRandomAccessEntry *)entry)->num_leading_samples_known); gf_bs_write_u8(bs, ((GF_VisualRandomAccessEntry *)entry)->num_leading_samples); gf_bs_del(bs); return gf_isom_set_sample_group_info_ex(NULL, traf, 0, grouping_type, 0, udta, sg_rap_create_entry, sg_rap_compare_entry); } case GF_4CC('r', 'o', 'l', 'l'): { char udta[2]; bs = gf_bs_new(udta, 2 * sizeof(char), GF_BITSTREAM_WRITE); gf_bs_write_u16(bs, ((GF_RollRecoveryEntry *)entry)->roll_distance); return gf_isom_set_sample_group_info_ex(NULL, traf, 0, grouping_type, 0, udta, sg_roll_create_entry, sg_roll_compare_entry); } case GF_4CC('s', 'e', 'i', 'g'): { char *udta; u32 size; GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); GF_Err e = GF_OK; gf_bs_write_u8(bs, 0x0); gf_bs_write_int(bs, ((GF_CENCSampleEncryptionGroupEntry *)entry)->crypt_byte_block, 4); gf_bs_write_int(bs, ((GF_CENCSampleEncryptionGroupEntry *)entry)->skip_byte_block, 4); gf_bs_write_u8(bs, ((GF_CENCSampleEncryptionGroupEntry *)entry)->IsProtected); gf_bs_write_u8(bs, ((GF_CENCSampleEncryptionGroupEntry *)entry)->Per_Sample_IV_size); gf_bs_write_data(bs, (char *)((GF_CENCSampleEncryptionGroupEntry *)entry)->KID, 16); if ((((GF_CENCSampleEncryptionGroupEntry *)entry)->IsProtected == 1) && !((GF_CENCSampleEncryptionGroupEntry *)entry)->Per_Sample_IV_size) { gf_bs_write_u8(bs, ((GF_CENCSampleEncryptionGroupEntry *)entry)->constant_IV_size); gf_bs_write_data(bs, (char *)((GF_CENCSampleEncryptionGroupEntry *)entry)->constant_IV, ((GF_CENCSampleEncryptionGroupEntry *)entry)->constant_IV_size); } gf_bs_get_content(bs, &udta, &size); gf_bs_del(bs); e = gf_isom_set_sample_group_info_ex(NULL, traf, 0, grouping_type, 0, udta, sg_encryption_create_entry, sg_encryption_compare_entry); gf_free(udta); return e; } default: return GF_BAD_PARAM; } } return gf_isom_add_sample_group_entry(traf->sampleGroups, 0, grouping_type, grouping_type_parameter, sampleGroupDescriptionIndex); } #endif /* GPAC_DISABLE_ISOM_FRAGMENTS */ /*sample encryption information group can be in stbl or traf*/ GF_EXPORT GF_Err gf_isom_set_sample_cenc_group(GF_ISOFile *movie, u32 track, u32 sample_number, u8 isEncrypted, u8 IV_size, bin128 KeyID, u8 crypt_byte_block, u8 skip_byte_block, u8 constant_IV_size, bin128 constant_IV) { char *udta; u32 size; GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); GF_Err e = GF_OK; if ((IV_size != 0) && (IV_size != 8) && (IV_size != 16)) return GF_BAD_PARAM; gf_bs_write_u8(bs, 0x0); gf_bs_write_int(bs, crypt_byte_block, 4); gf_bs_write_int(bs, skip_byte_block, 4); gf_bs_write_u8(bs, isEncrypted); gf_bs_write_u8(bs, IV_size); gf_bs_write_data(bs, (char *)KeyID, 16); if ((isEncrypted == 1) && !IV_size) { gf_bs_write_u8(bs, constant_IV_size); gf_bs_write_data(bs, (char *)constant_IV, constant_IV_size); } gf_bs_get_content(bs, &udta, &size); gf_bs_del(bs); e = gf_isom_set_sample_group_info(movie, track, sample_number, GF_4CC('s', 'e', 'i', 'g'), 0, udta, sg_encryption_create_entry, sg_encryption_compare_entry); gf_free(udta); return e; } static GF_Err gf_isom_set_ctts_v1(GF_ISOFile *file, u32 track, GF_TrackBox *trak) { u32 i, shift; u64 duration; GF_CompositionOffsetBox *ctts; GF_CompositionToDecodeBox *cslg; s32 leastCTTS, greatestCTTS; ctts = trak->Media->information->sampleTable->CompositionOffset; shift = ctts->entries[0].decodingOffset; leastCTTS = GF_INT_MAX; greatestCTTS = 0; for (i = 0; inb_entries; i++) { ctts->entries[i].decodingOffset -= shift; if ((s32)ctts->entries[i].decodingOffset < leastCTTS) leastCTTS = ctts->entries[i].decodingOffset; if ((s32)ctts->entries[i].decodingOffset > greatestCTTS) greatestCTTS = ctts->entries[i].decodingOffset; } ctts->version = 1; gf_isom_remove_edit_segments(file, track); if (!trak->Media->information->sampleTable->CompositionToDecode) trak->Media->information->sampleTable->CompositionToDecode = (GF_CompositionToDecodeBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_CSLG); cslg = trak->Media->information->sampleTable->CompositionToDecode; cslg->compositionToDTSShift = shift; cslg->leastDecodeToDisplayDelta = leastCTTS; cslg->greatestDecodeToDisplayDelta = greatestCTTS; cslg->compositionStartTime = 0; /*for our use case (first CTS set to 0), the composition end time is the media duration if it fits on 32 bits*/ duration = gf_isom_get_media_duration(file, track); cslg->compositionEndTime = (duration<0x7FFFFFFF) ? (s32)duration : 0; gf_isom_modify_alternate_brand(file, GF_ISOM_BRAND_ISO4, GF_TRUE); return GF_OK; } static GF_Err gf_isom_set_ctts_v0(GF_ISOFile *file, GF_TrackBox *trak) { u32 i; s32 shift; GF_CompositionOffsetBox *ctts; GF_CompositionToDecodeBox *cslg; ctts = trak->Media->information->sampleTable->CompositionOffset; if (!trak->Media->information->sampleTable->CompositionToDecode) { shift = 0; for (i = 0; inb_entries; i++) { if (-ctts->entries[i].decodingOffset > shift) shift = -ctts->entries[i].decodingOffset; } if (shift > 0) { for (i = 0; inb_entries; i++) { ctts->entries[i].decodingOffset += shift; } } } else { cslg = trak->Media->information->sampleTable->CompositionToDecode; shift = cslg->compositionToDTSShift; for (i = 0; inb_entries; i++) { ctts->entries[i].decodingOffset += shift; } gf_isom_box_del((GF_Box *)cslg); trak->Media->information->sampleTable->CompositionToDecode = NULL; } if (!trak->editBox && shift>0) { u64 dur = trak->Media->mediaHeader->duration; dur *= file->moov->mvhd->timeScale; dur /= trak->Media->mediaHeader->timeScale; gf_isom_set_edit_segment(file, gf_list_find(file->moov->trackList, trak) + 1, 0, dur, shift, GF_ISOM_EDIT_NORMAL); } ctts->version = 0; gf_isom_modify_alternate_brand(file, GF_ISOM_BRAND_ISO4, GF_FALSE); return GF_OK; } GF_EXPORT GF_Err gf_isom_set_composition_offset_mode(GF_ISOFile *file, u32 track, Bool use_negative_offsets) { GF_Err e; GF_TrackBox *trak; GF_CompositionOffsetBox *ctts; e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(file, track); if (!trak) return GF_BAD_PARAM; ctts = trak->Media->information->sampleTable->CompositionOffset; if (!ctts) return GF_OK; if (use_negative_offsets) { if (ctts->version == 1) return GF_OK; return gf_isom_set_ctts_v1(file, track, trak); } else { if (ctts->version == 0) return GF_OK; return gf_isom_set_ctts_v0(file, trak); } } GF_EXPORT GF_Err gf_isom_set_sync_table(GF_ISOFile *file, u32 track) { GF_Err e; GF_TrackBox *trak; e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(file, track); if (!trak) return GF_BAD_PARAM; trak->Media->information->sampleTable->SyncSample = (GF_SyncSampleBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_STSS); return GF_OK; } Bool gf_isom_is_identical_sgpd(void *ptr1, void *ptr2, u32 grouping_type) { Bool res = GF_FALSE; #ifndef GPAC_DISABLE_ISOM_WRITE GF_BitStream *bs1, *bs2; char *buf1, *buf2; u32 len1, len2; if (!ptr1 || !ptr2) return GF_FALSE; bs1 = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); if (grouping_type) { sgpd_write_entry(grouping_type, ptr1, bs1); } else { sgpd_Write((GF_Box *)ptr1, bs1); } gf_bs_get_content(bs1, &buf1, &len1); gf_bs_del(bs1); bs2 = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); if (grouping_type) { sgpd_write_entry(grouping_type, ptr2, bs2); } else { sgpd_Write((GF_Box *)ptr2, bs2); } gf_bs_get_content(bs2, &buf2, &len2); gf_bs_del(bs2); if ((len1 == len2) && !memcmp(buf1, buf2, len1)) res = GF_TRUE; gf_free(buf1); gf_free(buf2); #endif return res; } GF_EXPORT GF_Err gf_isom_copy_sample_info(GF_ISOFile *dst, u32 dst_track, GF_ISOFile *src, u32 src_track, u32 sampleNumber) { u32 i, count, idx, dst_sample_num, subs_flags; GF_SubSampleInfoEntry *sub_sample; GF_Err e; GF_TrackBox *src_trak, *dst_trak; src_trak = gf_isom_get_track_from_file(src, src_track); if (!src_trak) return GF_BAD_PARAM; dst_trak = gf_isom_get_track_from_file(dst, dst_track); if (!dst_trak) return GF_BAD_PARAM; dst_sample_num = dst_trak->Media->information->sampleTable->SampleSize->sampleCount; /*modify depends flags*/ if (src_trak->Media->information->sampleTable->SampleDep) { u32 isLeading, dependsOn, dependedOn, redundant; isLeading = dependsOn = dependedOn = redundant = 0; e = stbl_GetSampleDepType(src_trak->Media->information->sampleTable->SampleDep, sampleNumber, &isLeading, &dependsOn, &dependedOn, &redundant); if (e) return e; e = stbl_AppendDependencyType(dst_trak->Media->information->sampleTable, isLeading, dependsOn, dependedOn, redundant); if (e) return e; } /*copy subsample info if any*/ idx = 1; while (gf_isom_get_subsample_types(src, src_track, idx, &subs_flags)) { GF_SubSampleInformationBox *dst_subs = NULL; idx++; if (!gf_isom_sample_get_subsample_entry(src, src_track, sampleNumber, subs_flags, &sub_sample)) continue; /*create subsample if needed*/ if (!dst_trak->Media->information->sampleTable->sub_samples) { dst_trak->Media->information->sampleTable->sub_samples = gf_list_new(); } count = gf_list_count(dst_trak->Media->information->sampleTable->sub_samples); for (i = 0; iMedia->information->sampleTable->sub_samples, i); if (dst_subs->flags == subs_flags) break; dst_subs = NULL; } if (!dst_subs) { dst_subs = (GF_SubSampleInformationBox *)gf_isom_box_new(GF_ISOM_BOX_TYPE_SUBS); dst_subs->version = 0; dst_subs->flags = subs_flags; gf_list_add(dst_trak->Media->information->sampleTable->sub_samples, dst_subs); } count = gf_list_count(sub_sample->SubSamples); for (i = 0; iSubSamples, i); e = gf_isom_add_subsample_info(dst_subs, dst_sample_num, entry->subsample_size, entry->subsample_priority, entry->reserved, entry->discardable); if (e) return e; } } /*copy sampleToGroup info if any*/ if (src_trak->Media->information->sampleTable->sampleGroups) { count = gf_list_count(src_trak->Media->information->sampleTable->sampleGroups); for (i = 0; iMedia->information->sampleTable->sampleGroups, i); for (j = 0; jentry_count; j++) { last_sample_in_entry = first_sample_in_entry + sg->sample_entries[j].sample_count - 1; if ((sampleNumberlast_sample_in_entry)) { first_sample_in_entry = last_sample_in_entry + 1; continue; } if (!dst_trak->Media->information->sampleTable->sampleGroups) dst_trak->Media->information->sampleTable->sampleGroups = gf_list_new(); group_desc_index_src = group_desc_index_dst = sg->sample_entries[j].group_description_index; if (group_desc_index_src) { GF_SampleGroupDescriptionBox *sgd_src, *sgd_dst; GF_DefaultSampleGroupDescriptionEntry *sgde_src, *sgde_dst; group_desc_index_dst = 0; //check that the sample group description exists !! sgde_src = gf_isom_get_sample_group_info_entry(src, src_trak, sg->grouping_type, sg->sample_entries[j].group_description_index, &default_index, &sgd_src); if (!sgde_src) break; if (!dst_trak->Media->information->sampleTable->sampleGroupsDescription) dst_trak->Media->information->sampleTable->sampleGroupsDescription = gf_list_new(); sgd_dst = NULL; for (k = 0; k< gf_list_count(dst_trak->Media->information->sampleTable->sampleGroupsDescription); k++) { sgd_dst = gf_list_get(dst_trak->Media->information->sampleTable->sampleGroupsDescription, k); if (sgd_dst->grouping_type == sgd_src->grouping_type) break; sgd_dst = NULL; } if (!sgd_dst) { gf_isom_clone_box((GF_Box *)sgd_src, (GF_Box **)&sgd_dst); if (!sgd_dst) return GF_OUT_OF_MEM; gf_list_add(dst_trak->Media->information->sampleTable->sampleGroupsDescription, sgd_dst); } //find the same entry for (k = 0; kgroup_descriptions); k++) { sgde_dst = gf_list_get(sgd_dst->group_descriptions, i); if (gf_isom_is_identical_sgpd(sgde_src, sgde_dst, sgd_src->grouping_type)) { group_desc_index_dst = k + 1; break; } } if (!group_desc_index_dst) { GF_SampleGroupDescriptionBox *cloned = NULL; gf_isom_clone_box((GF_Box *)sgd_src, (GF_Box **)&cloned); if (!cloned) return GF_OUT_OF_MEM; sgde_dst = gf_list_get(cloned->group_descriptions, group_desc_index_dst); gf_list_rem(cloned->group_descriptions, group_desc_index_dst); gf_isom_box_del((GF_Box *)cloned); gf_list_add(sgd_dst->group_descriptions, sgde_dst); group_desc_index_dst = gf_list_count(sgd_dst->group_descriptions); } } /*found our sample, add it to trak->sampleGroups*/ e = gf_isom_add_sample_group_entry(dst_trak->Media->information->sampleTable->sampleGroups, dst_sample_num, sg->grouping_type, sg->grouping_type_parameter, group_desc_index_dst); if (e) return e; break; } } } return GF_OK; } GF_EXPORT GF_Err gf_isom_text_set_display_flags(GF_ISOFile *file, u32 track, u32 desc_index, u32 flags, GF_TextFlagsMode op_type) { u32 i; GF_Err e; GF_TrackBox *trak; e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(file, track); if (!trak) return GF_BAD_PARAM; for (i = 0; i < gf_list_count(trak->Media->information->sampleTable->SampleDescription->other_boxes); i++) { GF_Tx3gSampleEntryBox *txt; if (desc_index && (i + 1 != desc_index)) continue; txt = (GF_Tx3gSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->other_boxes, i); if (txt->type != GF_ISOM_BOX_TYPE_TX3G) continue; switch (op_type) { case GF_ISOM_TEXT_FLAGS_TOGGLE: txt->displayFlags |= flags; break; case GF_ISOM_TEXT_FLAGS_UNTOGGLE: txt->displayFlags &= ~flags; break; default: txt->displayFlags = flags; break; } } return GF_OK; } GF_EXPORT GF_Err gf_isom_update_duration(GF_ISOFile *movie) { u32 i; u64 maxDur; GF_TrackBox *trak; if (!movie || !movie->moov) return GF_BAD_PARAM; //if file was open in Write or Edit mode, recompute the duration //the duration of a movie is the MaxDuration of all the tracks... maxDur = 0; i = 0; while ((trak = (GF_TrackBox *)gf_list_enum(movie->moov->trackList, &i))) { if ((movie->LastError = SetTrackDuration(trak))) return movie->LastError; if (trak->Header->duration > maxDur) maxDur = trak->Header->duration; } movie->moov->mvhd->duration = maxDur; return GF_OK; } GF_EXPORT GF_Err gf_isom_update_edit_list_duration(GF_ISOFile *file, u32 track) { u32 i; u64 trackDuration; GF_EdtsEntry *ent; GF_EditListBox *elst; GF_Err e; GF_TrackBox *trak; e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(file, track); if (!trak) return GF_BAD_PARAM; //the total duration is the media duration: adjust it in case... e = Media_SetDuration(trak); if (e) return e; //assert the timeScales are non-NULL if (!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) { u64 editListDuration = 0; elst = trak->editBox->editList; i = 0; while ((ent = (GF_EdtsEntry*)gf_list_enum(elst->entryList, &i))) { if (ent->segmentDuration > trackDuration) ent->segmentDuration = trackDuration; if ((ent->mediaTime >= 0) && ((u64)ent->mediaTime >= trak->Media->mediaHeader->duration)) { ent->mediaTime = trak->Media->mediaHeader->duration; } editListDuration += ent->segmentDuration; } trackDuration = editListDuration; } if (!trackDuration) { trackDuration = (trak->Media->mediaHeader->duration * trak->moov->mvhd->timeScale) / trak->Media->mediaHeader->timeScale; } trak->Header->duration = trackDuration; return GF_OK; } GF_EXPORT GF_Err gf_isom_clone_pssh(GF_ISOFile *output, GF_ISOFile *input, Bool in_moof) { GF_Box *a; u32 i; i = 0; while ((a = (GF_Box *)gf_list_enum(input->moov->other_boxes, &i))) { if (a->type == GF_ISOM_BOX_TYPE_PSSH) { GF_ProtectionSystemHeaderBox *pssh = (GF_ProtectionSystemHeaderBox *)pssh_New(); memmove(pssh->SystemID, ((GF_ProtectionSystemHeaderBox *)a)->SystemID, 16); pssh->KID_count = ((GF_ProtectionSystemHeaderBox *)a)->KID_count; pssh->KIDs = (bin128 *)gf_malloc(pssh->KID_count * sizeof(bin128)); memmove(pssh->KIDs, ((GF_ProtectionSystemHeaderBox *)a)->KIDs, pssh->KID_count * sizeof(bin128)); pssh->private_data_size = ((GF_ProtectionSystemHeaderBox *)a)->private_data_size; pssh->private_data = (u8 *)gf_malloc(pssh->private_data_size * sizeof(char)); memmove(pssh->private_data, ((GF_ProtectionSystemHeaderBox *)a)->private_data, pssh->private_data_size); #ifndef GPAC_DISABLE_ISOM_FRAGMENTS gf_isom_box_add_default(in_moof ? (GF_Box*)output->moof : (GF_Box*)output->moov, (GF_Box*)pssh); #else gf_isom_box_add_default((GF_Box*)output->moov, (GF_Box*)pssh); #endif } } return GF_OK; } GF_EXPORT GF_Err gf_isom_set_track_group(GF_ISOFile *file, u32 track_number, u32 track_group_id, u32 group_type, Bool do_add) { u32 i, j; GF_TrackGroupTypeBox *trgt; GF_Err e; GF_TrackBox *trak; e = CanAccessMovie(file, GF_ISOM_OPEN_WRITE); if (e) return e; trak = gf_isom_get_track_from_file(file, track_number); if (!trak) return GF_BAD_PARAM; if (!trak->groups) trak->groups = (GF_TrackGroupBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_TRGR); for (j = 0; jmoov->trackList); j++) { GF_TrackBox *a_trak = gf_list_get(file->moov->trackList, j); if (!a_trak->groups) continue; for (i = 0; igroups->groups); i++) { trgt = gf_list_get(a_trak->groups->groups, i); if (trgt->track_group_id == track_group_id) { if (trgt->group_type != group_type) { GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("A track with same group ID is already defined for different group type %s\n", gf_4cc_to_str(trgt->group_type))); return GF_BAD_PARAM; } if (a_trak == trak) { if (!do_add) { gf_list_rem(trak->groups->groups, i); gf_isom_box_del((GF_Box *)trgt); } return GF_OK; } } } } //not found, add new group trgt = (GF_TrackGroupTypeBox*)gf_isom_box_new(GF_ISOM_BOX_TYPE_TRGT); trgt->track_group_id = track_group_id; trgt->group_type = group_type; return gf_list_add(trak->groups->groups, trgt); } #endif /*!defined(GPAC_DISABLE_ISOM) && !defined(GPAC_DISABLE_ISOM_WRITE)*/