blob: e67b041553e9f4550cbee72cbb29d039d9329105 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2000-2026
* All rights reserved
*
* This file is part of GPAC / Media Tools sub-project
*
* GPAC is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GPAC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <gpac/internal/media_dev.h>
#include <gpac/internal/isomedia_dev.h>
#include <gpac/constants.h>
#include <gpac/config_file.h>
#ifndef GPAC_DISABLE_ISOM_WRITE
GF_EXPORT
GF_Err gf_media_change_par(GF_ISOFile *file, u32 track, s32 ar_num, s32 ar_den, Bool force_par, Bool rewrite_bs)
{
u32 tk_w, tk_h;
GF_Err e;
Bool get_par_info = GF_FALSE;
e = gf_isom_get_visual_info(file, track, 1, &tk_w, &tk_h);
if (e) return e;
if ((ar_num < 0) || (ar_den < 0)) {
get_par_info = GF_TRUE;
rewrite_bs = GF_FALSE;
}
else if (!ar_num || !ar_den) {
rewrite_bs = GF_FALSE;
}
if (get_par_info || rewrite_bs) {
u32 stype = gf_isom_get_media_subtype(file, track, 1);
if ((stype==GF_ISOM_SUBTYPE_AVC_H264)
|| (stype==GF_ISOM_SUBTYPE_AVC2_H264)
|| (stype==GF_ISOM_SUBTYPE_AVC3_H264)
|| (stype==GF_ISOM_SUBTYPE_AVC4_H264)
) {
#ifndef GPAC_DISABLE_AV_PARSERS
GF_AVCConfig *avcc = gf_isom_avc_config_get(file, track, 1);
if (rewrite_bs) {
gf_avc_change_par(avcc, ar_num, ar_den);
e = gf_isom_avc_config_update(file, track, 1, avcc);
} else {
GF_NALUFFParam *sl = gf_list_get(avcc->sequenceParameterSets, 0);
if (sl) {
gf_avc_get_sps_info(sl->data, sl->size, NULL, NULL, NULL, &ar_num, &ar_den);
} else {
ar_num = ar_den = 0;
}
}
gf_odf_avc_cfg_del(avcc);
if (e) return e;
#endif
}
#if !defined(GPAC_DISABLE_AV_PARSERS)
else if ((stype==GF_ISOM_SUBTYPE_HVC1) || (stype==GF_ISOM_SUBTYPE_HVC2)
|| (stype==GF_ISOM_SUBTYPE_HEV1) || (stype==GF_ISOM_SUBTYPE_HEV2)
) {
GF_HEVCConfig *hvcc = gf_isom_hevc_config_get(file, track, 1);
if (rewrite_bs) {
gf_hevc_change_par(hvcc, ar_num, ar_den);
e = gf_isom_hevc_config_update(file, track, 1, hvcc);
} else {
u32 i=0;
GF_NALUFFParamArray *ar;
ar_num = ar_den = 0;
while ( (ar = gf_list_enum(hvcc->param_array, &i))) {
if (ar->type==GF_HEVC_NALU_SEQ_PARAM) {
GF_NALUFFParam *sl = gf_list_get(ar->nalus, 0);
if (sl)
gf_hevc_get_sps_info(sl->data, sl->size, NULL, NULL, NULL, &ar_num, &ar_den);
break;
}
}
}
gf_odf_hevc_cfg_del(hvcc);
if (e) return e;
}
else if (stype == GF_ISOM_SUBTYPE_AV01) {
//GF_AV1Config *av1c = gf_isom_av1_config_get(file, track, 1);
//TODO: e = gf_isom_av1_config_update(file, track, 1, av1c);
//gf_odf_av1_cfg_del(av1c);
//if (e) return e;
return GF_NOT_SUPPORTED;
}
else if ((stype==GF_ISOM_SUBTYPE_VVC1) || (stype==GF_ISOM_SUBTYPE_VVI1)
) {
GF_VVCConfig *vvcc = gf_isom_vvc_config_get(file, track, 1);
if (rewrite_bs) {
gf_vvc_change_par(vvcc, ar_num, ar_den);
e = gf_isom_vvc_config_update(file, track, 1, vvcc);
} else {
u32 i=0;
GF_NALUFFParamArray *ar;
ar_num = ar_den = 0;
while ( (ar = gf_list_enum(vvcc->param_array, &i))) {
if (ar->type==GF_VVC_NALU_SEQ_PARAM) {
GF_NALUFFParam *sl = gf_list_get(ar->nalus, 0);
if (sl)
gf_vvc_get_sps_info(sl->data, sl->size, NULL, NULL, NULL, &ar_num, &ar_den);
break;
}
}
}
gf_odf_vvc_cfg_del(vvcc);
if (e) return e;
}
#endif
else if (stype==GF_ISOM_SUBTYPE_MPEG4) {
GF_ESD *esd = gf_isom_get_esd(file, track, 1);
if (!esd || !esd->decoderConfig || (esd->decoderConfig->streamType!=4) ) {
if (esd) gf_odf_desc_del((GF_Descriptor *) esd);
return GF_NOT_SUPPORTED;
}
#ifndef GPAC_DISABLE_AV_PARSERS
if (esd->decoderConfig->objectTypeIndication==GF_CODECID_MPEG4_PART2) {
if (rewrite_bs) {
e = gf_m4v_rewrite_par(&esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength, ar_num, ar_den);
if (!e) e = gf_isom_change_mpeg4_description(file, track, 1, esd);
} else {
GF_M4VDecSpecInfo dsi;
e = gf_m4v_get_config(esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi);
ar_num = dsi.par_num;
ar_den = dsi.par_den;
}
}
#endif
gf_odf_desc_del((GF_Descriptor *) esd);
if (e) return e;
} else {
u32 mtype = gf_isom_get_media_type(file, track);
if (gf_isom_is_video_handler_type(mtype)) {
if (rewrite_bs) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER,
("[ISOBMF] Warning: changing pixel ratio of media subtype \"%s\" is not supported, changing only \"pasp\" signaling\n",
gf_4cc_to_str(gf_isom_get_media_subtype(file, track, 1)) ));
}
} else {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[ISOBMF] Error: changing pixel ratio on non-video track.\n"));
return GF_BAD_PARAM;
}
}
//auto mode
if (get_par_info && ((ar_num<=0) || (ar_den<=0))) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[ISOBMF] No sample AR info present in sample description, ignoring SAR update\n"));
if (force_par)
return gf_isom_set_pixel_aspect_ratio(file, track, 1, 1, 1, force_par);
return GF_OK;
}
}
e = gf_isom_set_pixel_aspect_ratio(file, track, 1, ar_num, ar_den, force_par);
if (e) return e;
if ((ar_den>0) && (ar_num>0)) {
tk_w = tk_w * ar_num / ar_den;
}
/*PASP has been removed or forced to 1:1, revert to full frame for track layout*/
else {
e = gf_isom_get_visual_info(file, track, 1, &tk_w, &tk_h);
if (e) return e;
}
return gf_isom_set_track_layout_info(file, track, tk_w<<16, tk_h<<16, 0, 0, 0);
}
GF_EXPORT
GF_Err gf_media_change_color(GF_ISOFile *file, u32 track, s32 fullrange, s32 vidformat, s32 colorprim, s32 transfer, s32 colmatrix)
{
#ifndef GPAC_DISABLE_AV_PARSERS
GF_Err e;
u32 stype = gf_isom_get_media_subtype(file, track, 1);
if ((stype==GF_ISOM_SUBTYPE_AVC_H264)
|| (stype==GF_ISOM_SUBTYPE_AVC2_H264)
|| (stype==GF_ISOM_SUBTYPE_AVC3_H264)
|| (stype==GF_ISOM_SUBTYPE_AVC4_H264)
) {
GF_AVCConfig *avcc = gf_isom_avc_config_get(file, track, 1);
gf_avc_change_color(avcc, fullrange, vidformat, colorprim, transfer, colmatrix);
e = gf_isom_avc_config_update(file, track, 1, avcc);
gf_odf_avc_cfg_del(avcc);
if (e) return e;
//remove any colr box
return gf_isom_set_visual_color_info(file, track, 1, 0, 0, 0, 0, 0, NULL, 0);
}
if ((stype==GF_ISOM_SUBTYPE_HEV1)
|| (stype==GF_ISOM_SUBTYPE_HEV2)
|| (stype==GF_ISOM_SUBTYPE_HVC1)
|| (stype==GF_ISOM_SUBTYPE_HVC2)
|| (stype==GF_ISOM_SUBTYPE_LHV1)
|| (stype==GF_ISOM_SUBTYPE_LHE1)
) {
GF_HEVCConfig *hvcc = gf_isom_hevc_config_get(file, track, 1);
gf_hevc_change_color(hvcc, fullrange, vidformat, colorprim, transfer, colmatrix);
e = gf_isom_hevc_config_update(file, track, 1, hvcc);
gf_odf_hevc_cfg_del(hvcc);
if (e) return e;
//remove any colr box
return gf_isom_set_visual_color_info(file, track, 1, 0, 0, 0, 0, 0, NULL, 0);
}
if ((stype==GF_ISOM_SUBTYPE_VVC1) || (stype==GF_ISOM_SUBTYPE_VVI1) ) {
GF_VVCConfig *vvcc = gf_isom_vvc_config_get(file, track, 1);
gf_vvc_change_color(vvcc, fullrange, vidformat, colorprim, transfer, colmatrix);
e = gf_isom_vvc_config_update(file, track, 1, vvcc);
gf_odf_vvc_cfg_del(vvcc);
if (e) return e;
//remove any colr box
return gf_isom_set_visual_color_info(file, track, 1, 0, 0, 0, 0, 0, NULL, 0);
}
#endif
return GF_NOT_SUPPORTED;
}
GF_EXPORT
GF_Err gf_media_remove_non_rap(GF_ISOFile *file, u32 track, Bool non_ref_only)
{
GF_Err e;
u32 i, count, di;
u64 offset, dur, last_dts;
Bool all_raps = (gf_isom_has_sync_points(file, track)==0) ? 1 : 0;
if (all_raps) return GF_OK;
last_dts = 0;
dur = gf_isom_get_media_duration(file, track);
gf_isom_set_cts_packing(file, track, GF_TRUE);
count = gf_isom_get_sample_count(file, track);
for (i=0; i<count; i++) {
Bool remove = GF_TRUE;
GF_ISOSample *samp = gf_isom_get_sample_info(file, track, i+1, &di, &offset);
if (!samp) return gf_isom_last_error(file);
if (samp->IsRAP) remove = GF_FALSE;
else if (non_ref_only) {
u32 isLeading, dependsOn, dependedOn, redundant;
gf_isom_get_sample_flags(file, track, i+1, &isLeading, &dependsOn, &dependedOn, &redundant);
if (dependedOn != 2) {
remove = GF_FALSE;
}
}
if (!remove) {
last_dts = samp->DTS;
gf_isom_sample_del(&samp);
continue;
}
gf_isom_sample_del(&samp);
e = gf_isom_remove_sample(file, track, i+1);
if (e) return e;
i--;
count--;
}
gf_isom_set_cts_packing(file, track, GF_FALSE);
gf_isom_set_last_sample_duration(file, track, (u32) (dur - last_dts) );
return GF_OK;
}
#endif /*GPAC_DISABLE_ISOM_WRITE*/
GF_EXPORT
GF_Err gf_media_get_file_hash(const char *file, u8 hash[20])
{
u8 block[4096];
u32 read;
u64 size, tot;
FILE *in;
GF_SHA1Context *ctx;
GF_Err e = GF_OK;
#ifndef GPAC_DISABLE_ISOM
GF_BitStream *bs = NULL;
Bool is_isom = gf_isom_probe_file(file);
#endif
in = gf_fopen(file, "rb");
if (!in) return GF_URL_ERROR;
size = gf_fsize(in);
ctx = gf_sha1_starts();
tot = 0;
#ifndef GPAC_DISABLE_ISOM
if (is_isom) bs = gf_bs_from_file(in, GF_BITSTREAM_READ);
#endif
while (tot<size) {
#ifndef GPAC_DISABLE_ISOM
if (is_isom) {
u64 box_size = gf_bs_peek_bits(bs, 32, 0);
u32 box_type = gf_bs_peek_bits(bs, 32, 4);
/*64-bit size*/
if (box_size==1) box_size = gf_bs_peek_bits(bs, 64, 8);
/*till end of file*/
if (!box_size) box_size = size - tot;
/*skip all MutableDRMInformation*/
if (box_type==GF_ISOM_BOX_TYPE_MDRI) {
gf_bs_skip_bytes(bs, box_size);
tot += box_size;
} else {
u64 bsize = 0;
while (bsize<box_size) {
u32 to_read = (u32) ((box_size-bsize<4096) ? (box_size-bsize) : 4096);
read = gf_bs_read_data(bs, (char *) block, to_read);
if (!read || (read != to_read) ) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("corrupted isobmf file, box read "LLU" but expected still "LLU" bytes\n", bsize, box_size));
break;
}
gf_sha1_update(ctx, block, to_read);
bsize += to_read;
}
tot += box_size;
}
} else
#endif
{
read = (u32) gf_fread(block, 4096, in);
if ((s32) read <= 0) {
if (ferror(in))
e = GF_IO_ERR;
break;
}
gf_sha1_update(ctx, block, read);
tot += read;
}
}
gf_sha1_finish(ctx, hash);
#ifndef GPAC_DISABLE_ISOM
if (bs) gf_bs_del(bs);
#endif
gf_fclose(in);
return e;
}
#ifndef GPAC_DISABLE_ISOM
#ifndef GPAC_DISABLE_ISOM_WRITE
static const u32 ISMA_VIDEO_OD_ID = 20;
static const u32 ISMA_AUDIO_OD_ID = 10;
static const u32 ISMA_VIDEO_ES_ID = 201;
static const u32 ISMA_AUDIO_ES_ID = 101;
/*ISMA audio*/
static const u8 ISMA_BIFS_AUDIO[] =
{
0xC0, 0x10, 0x12, 0x81, 0x30, 0x2A, 0x05, 0x7C
};
/*ISMA video*/
static const u8 ISMA_GF_BIFS_VIDEO[] =
{
0xC0, 0x10, 0x12, 0x60, 0x42, 0x82, 0x28, 0x29,
0xD0, 0x4F, 0x00
};
/*ISMA audio-video*/
static const u8 ISMA_BIFS_AV[] =
{
0xC0, 0x10, 0x12, 0x81, 0x30, 0x2A, 0x05, 0x72,
0x60, 0x42, 0x82, 0x28, 0x29, 0xD0, 0x4F, 0x00
};
/*image only - uses same visual OD ID as video*/
static const u8 ISMA_BIFS_IMAGE[] =
{
0xC0, 0x11, 0xA4, 0xCD, 0x53, 0x6A, 0x0A, 0x44,
0x13, 0x00
};
/*ISMA audio-image*/
static const u8 ISMA_BIFS_AI[] =
{
0xC0, 0x11, 0xA5, 0x02, 0x60, 0x54, 0x0A, 0xE4,
0xCD, 0x53, 0x6A, 0x0A, 0x44, 0x13, 0x00
};
GF_EXPORT
GF_Err gf_media_make_isma(GF_ISOFile *mp4file, Bool keepESIDs, Bool keepImage, Bool no_ocr)
{
u32 AudioTrack, VideoTrack, Tracks, i, mType, bifsT, odT, descIndex, VID, AID, bifsID, odID;
u32 bifs, w, h;
Bool is_image, image_track;
GF_ESD *a_esd, *v_esd, *_esd;
GF_ObjectDescriptor *od;
GF_ODUpdate *odU;
GF_ODCodec *codec;
GF_ISOSample *samp;
GF_BitStream *bs;
u8 audioPL, visualPL;
switch (gf_isom_get_mode(mp4file)) {
case GF_ISOM_OPEN_EDIT:
case GF_ISOM_OPEN_WRITE:
case GF_ISOM_WRITE_EDIT:
break;
default:
return GF_BAD_PARAM;
}
Tracks = gf_isom_get_track_count(mp4file);
AID = VID = 0;
is_image = 0;
//search for tracks
for (i=0; i<Tracks; i++) {
GF_ESD *esd = gf_isom_get_esd(mp4file, i+1, 1);
//remove from IOD
gf_isom_remove_track_from_root_od(mp4file, i+1);
mType = gf_isom_get_media_type(mp4file, i+1);
switch (mType) {
case GF_ISOM_MEDIA_VISUAL:
case GF_ISOM_MEDIA_AUXV:
case GF_ISOM_MEDIA_PICT:
image_track = 0;
if (esd && esd->decoderConfig && ((esd->decoderConfig->objectTypeIndication==GF_CODECID_JPEG) || (esd->decoderConfig->objectTypeIndication==GF_CODECID_PNG)) )
image_track = 1;
/*remove image tracks if wanted*/
if (keepImage || !image_track) {
/*only ONE video stream possible with ISMA*/
if (VID) {
if (esd) gf_odf_desc_del((GF_Descriptor*)esd);
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[ISMA convert] More than one video track found, cannot convert file - remove extra track(s)\n"));
return GF_NOT_SUPPORTED;
}
VID = gf_isom_get_track_id(mp4file, i+1);
is_image = image_track;
} else {
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ISMA convert] Visual track ID %d: only one sample found, assuming image and removing track\n", gf_isom_get_track_id(mp4file, i+1) ) );
gf_isom_remove_track(mp4file, i+1);
i -= 1;
Tracks = gf_isom_get_track_count(mp4file);
}
break;
case GF_ISOM_MEDIA_AUDIO:
if (AID) {
if (esd) gf_odf_desc_del((GF_Descriptor*)esd);
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[ISMA convert] More than one audio track found, cannot convert file - remove extra track(s)\n") );
return GF_NOT_SUPPORTED;
}
AID = gf_isom_get_track_id(mp4file, i+1);
break;
/*clean file*/
default:
if (mType==GF_ISOM_MEDIA_HINT) {
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ISMA convert] Removing Hint track ID %d\n", gf_isom_get_track_id(mp4file, i+1) ));
} else {
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ISMA convert] Removing track ID %d\n", gf_isom_get_track_id(mp4file, i+1) ));
}
gf_isom_remove_track(mp4file, i+1);
i -= 1;
Tracks = gf_isom_get_track_count(mp4file);
break;
}
if (esd) gf_odf_desc_del((GF_Descriptor*)esd);
}
//no audio no video
if (!AID && !VID) return GF_OK;
/*reset all PLs*/
visualPL = 0xFE;
audioPL = 0xFE;
od = (GF_ObjectDescriptor *) gf_isom_get_root_od(mp4file);
if (od && (od->tag==GF_ODF_IOD_TAG)) {
audioPL = ((GF_InitialObjectDescriptor*)od)->audio_profileAndLevel;
visualPL = ((GF_InitialObjectDescriptor*)od)->visual_profileAndLevel;
}
if (od) gf_odf_desc_del((GF_Descriptor *)od);
//create the OD AU
bifs = 0;
odU = (GF_ODUpdate *) gf_odf_com_new(GF_ODF_OD_UPDATE_TAG);
a_esd = v_esd = NULL;
gf_isom_set_root_od_id(mp4file, 1);
bifsID = 1;
odID = 2;
if (keepESIDs) {
bifsID = 1;
while ((bifsID==AID) || (bifsID==VID)) bifsID++;
odID = 2;
while ((odID==AID) || (odID==VID) || (odID==bifsID)) odID++;
}
VideoTrack = gf_isom_get_track_by_id(mp4file, VID);
AudioTrack = gf_isom_get_track_by_id(mp4file, AID);
w = h = 0;
if (VideoTrack) {
bifs = 1;
od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG);
od->objectDescriptorID = ISMA_VIDEO_OD_ID;
if (!keepESIDs && (VID != ISMA_VIDEO_ES_ID)) {
gf_isom_set_track_id(mp4file, VideoTrack, ISMA_VIDEO_ES_ID);
}
v_esd = gf_isom_get_esd(mp4file, VideoTrack, 1);
if (v_esd) {
v_esd->OCRESID = no_ocr ? 0 : bifsID;
gf_odf_desc_add_desc((GF_Descriptor *)od, (GF_Descriptor *)v_esd);
gf_list_add(odU->objectDescriptors, od);
gf_isom_get_track_layout_info(mp4file, VideoTrack, &w, &h, NULL, NULL, NULL);
if (!w || !h) {
gf_isom_get_visual_info(mp4file, VideoTrack, 1, &w, &h);
#ifndef GPAC_DISABLE_AV_PARSERS
if (v_esd->decoderConfig
&& (v_esd->decoderConfig->objectTypeIndication==GF_CODECID_MPEG4_PART2)
&& (v_esd->decoderConfig->streamType==GF_STREAM_VISUAL)
&& v_esd->decoderConfig->decoderSpecificInfo
&& v_esd->decoderConfig->decoderSpecificInfo->data
) {
GF_M4VDecSpecInfo dsi;
gf_m4v_get_config(v_esd->decoderConfig->decoderSpecificInfo->data, v_esd->decoderConfig->decoderSpecificInfo->dataLength, &dsi);
if (!is_image && (!w || !h)) {
w = dsi.width;
h = dsi.height;
gf_isom_set_visual_info(mp4file, VideoTrack, 1, w, h);
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ISMA convert] Adjusting visual track size to %d x %d\n", w, h));
}
if (dsi.par_num && dsi.par_den && (dsi.par_den!=dsi.par_num)) {
w *= dsi.par_num;
w /= dsi.par_den;
}
if (dsi.VideoPL) visualPL = dsi.VideoPL;
}
#endif
}
}
}
if (AudioTrack) {
od = (GF_ObjectDescriptor *) gf_odf_desc_new(GF_ODF_OD_TAG);
od->objectDescriptorID = ISMA_AUDIO_OD_ID;
if (!keepESIDs && (AID != ISMA_AUDIO_ES_ID)) {
gf_isom_set_track_id(mp4file, AudioTrack, ISMA_AUDIO_ES_ID);
}
a_esd = gf_isom_get_esd(mp4file, AudioTrack, 1);
if (a_esd) {
a_esd->OCRESID = no_ocr ? 0 : bifsID;
if (!keepESIDs) a_esd->ESID = ISMA_AUDIO_ES_ID;
gf_odf_desc_add_desc((GF_Descriptor *)od, (GF_Descriptor *)a_esd);
gf_list_add(odU->objectDescriptors, od);
if (!bifs) {
bifs = 3;
} else {
bifs = 2;
}
#ifndef GPAC_DISABLE_AV_PARSERS
if (a_esd->decoderConfig && (a_esd->decoderConfig->objectTypeIndication == GF_CODECID_AAC_MPEG4)
&& a_esd->decoderConfig->decoderSpecificInfo
&& a_esd->decoderConfig->decoderSpecificInfo->data
) {
GF_M4ADecSpecInfo cfg;
gf_m4a_get_config(a_esd->decoderConfig->decoderSpecificInfo->data, a_esd->decoderConfig->decoderSpecificInfo->dataLength, &cfg);
audioPL = cfg.audioPL;
}
#endif
}
}
/*update video cfg if needed*/
if (v_esd) gf_isom_change_mpeg4_description(mp4file, VideoTrack, 1, v_esd);
if (a_esd) gf_isom_change_mpeg4_description(mp4file, AudioTrack, 1, a_esd);
/*likely 3GP or other files...*/
if ((!a_esd && AudioTrack) || (!v_esd && VideoTrack)) return GF_OK;
//get the OD sample
codec = gf_odf_codec_new();
samp = gf_isom_sample_new();
gf_odf_codec_add_com(codec, (GF_ODCom *)odU);
gf_odf_codec_encode(codec, 1);
gf_odf_codec_get_au(codec, &samp->data, &samp->dataLength);
gf_odf_codec_del(codec);
samp->CTS_Offset = 0;
samp->DTS = 0;
samp->IsRAP = RAP;
/*create the OD track*/
odT = gf_isom_new_track(mp4file, odID, GF_ISOM_MEDIA_OD, gf_isom_get_timescale(mp4file));
if (!odT) return gf_isom_last_error(mp4file);
_esd = gf_odf_desc_esd_new(SLPredef_MP4);
if (!_esd) return GF_OUT_OF_MEM;
_esd->decoderConfig->bufferSizeDB = samp->dataLength;
_esd->decoderConfig->objectTypeIndication = GF_CODECID_OD_V1;
_esd->decoderConfig->streamType = GF_STREAM_OD;
_esd->ESID = odID;
_esd->OCRESID = no_ocr ? 0 : bifsID;
gf_isom_new_mpeg4_description(mp4file, odT, _esd, NULL, NULL, &descIndex);
gf_odf_desc_del((GF_Descriptor *)_esd);
gf_isom_add_sample(mp4file, odT, 1, samp);
gf_isom_sample_del(&samp);
gf_isom_set_track_interleaving_group(mp4file, odT, 1);
/*create the BIFS track*/
bifsT = gf_isom_new_track(mp4file, bifsID, GF_ISOM_MEDIA_SCENE, gf_isom_get_timescale(mp4file));
if (!bifsT) return gf_isom_last_error(mp4file);
_esd = gf_odf_desc_esd_new(SLPredef_MP4);
if (!_esd) return GF_OUT_OF_MEM;
_esd->decoderConfig->bufferSizeDB = 20;
_esd->decoderConfig->objectTypeIndication = GF_CODECID_BIFS_V2;
_esd->decoderConfig->streamType = GF_STREAM_SCENE;
_esd->ESID = bifsID;
_esd->OCRESID = 0;
/*rewrite ISMA BIFS cfg*/
bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
/*empty bifs stuff*/
gf_bs_write_int(bs, 0, 17);
/*command stream*/
gf_bs_write_int(bs, 1, 1);
/*in pixel metrics*/
gf_bs_write_int(bs, 1, 1);
/*with size*/
gf_bs_write_int(bs, 1, 1);
gf_bs_write_int(bs, w, 16);
gf_bs_write_int(bs, h, 16);
gf_bs_align(bs);
gf_bs_get_content(bs, &_esd->decoderConfig->decoderSpecificInfo->data, &_esd->decoderConfig->decoderSpecificInfo->dataLength);
gf_isom_new_mpeg4_description(mp4file, bifsT, _esd, NULL, NULL, &descIndex);
gf_odf_desc_del((GF_Descriptor *)_esd);
gf_bs_del(bs);
gf_isom_set_visual_info(mp4file, bifsT, descIndex, w, h);
samp = gf_isom_sample_new();
samp->CTS_Offset = 0;
samp->DTS = 0;
switch (bifs) {
case 1:
if (is_image) {
samp->data = (char *) ISMA_BIFS_IMAGE;
samp->dataLength = 10;
} else {
samp->data = (char *) ISMA_GF_BIFS_VIDEO;
samp->dataLength = 11;
}
break;
case 2:
if (is_image) {
samp->data = (char *) ISMA_BIFS_AI;
samp->dataLength = 15;
} else {
samp->data = (char *) ISMA_BIFS_AV;
samp->dataLength = 16;
}
break;
case 3:
samp->data = (char *) ISMA_BIFS_AUDIO;
samp->dataLength = 8;
break;
}
samp->IsRAP = RAP;
gf_isom_add_sample(mp4file, bifsT, 1, samp);
samp->data = NULL;
gf_isom_sample_del(&samp);
gf_isom_set_track_interleaving_group(mp4file, bifsT, 1);
gf_isom_set_track_enabled(mp4file, bifsT, GF_TRUE);
gf_isom_set_track_enabled(mp4file, odT, GF_TRUE);
gf_isom_add_track_to_root_od(mp4file, bifsT);
gf_isom_add_track_to_root_od(mp4file, odT);
gf_isom_set_pl_indication(mp4file, GF_ISOM_PL_SCENE, 1);
gf_isom_set_pl_indication(mp4file, GF_ISOM_PL_GRAPHICS, 1);
gf_isom_set_pl_indication(mp4file, GF_ISOM_PL_OD, 1);
gf_isom_set_pl_indication(mp4file, GF_ISOM_PL_AUDIO, audioPL);
gf_isom_set_pl_indication(mp4file, GF_ISOM_PL_VISUAL, (u8) (is_image ? 0xFE : visualPL));
gf_isom_set_brand_info(mp4file, GF_ISOM_BRAND_MP42, 1);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_ISOM, GF_TRUE);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GP5, GF_TRUE);
return GF_OK;
}
GF_EXPORT
GF_Err gf_media_make_3gpp(GF_ISOFile *mp4file)
{
u32 Tracks, i, mType, stype, nb_vid, nb_avc, nb_aud, nb_txt, nb_non_mp4, nb_dims;
Bool is_3g2 = 0;
switch (gf_isom_get_mode(mp4file)) {
case GF_ISOM_OPEN_EDIT:
case GF_ISOM_OPEN_WRITE:
case GF_ISOM_WRITE_EDIT:
break;
default:
return GF_BAD_PARAM;
}
Tracks = gf_isom_get_track_count(mp4file);
nb_vid = nb_aud = nb_txt = nb_avc = nb_non_mp4 = nb_dims = 0;
for (i=0; i<Tracks; i++) {
gf_isom_remove_track_from_root_od(mp4file, i+1);
mType = gf_isom_get_media_type(mp4file, i+1);
stype = gf_isom_get_media_subtype(mp4file, i+1, 1);
switch (mType) {
case GF_ISOM_MEDIA_VISUAL:
case GF_ISOM_MEDIA_AUXV:
case GF_ISOM_MEDIA_PICT:
/*remove image tracks if wanted*/
if (gf_isom_get_sample_count(mp4file, i+1)<=1) {
GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[3GPP convert] Visual track ID %d: only one sample found\n", gf_isom_get_track_id(mp4file, i+1) ));
//goto remove_track;
}
if (stype == GF_ISOM_SUBTYPE_MPEG4_CRYP) gf_isom_get_ismacryp_info(mp4file, i+1, 1, &stype, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
switch (stype) {
case GF_ISOM_SUBTYPE_3GP_H263:
nb_vid++;
nb_non_mp4 ++;
break;
case GF_ISOM_SUBTYPE_AVC_H264:
case GF_ISOM_SUBTYPE_AVC2_H264:
case GF_ISOM_SUBTYPE_AVC3_H264:
case GF_ISOM_SUBTYPE_AVC4_H264:
case GF_ISOM_SUBTYPE_SVC_H264:
case GF_ISOM_SUBTYPE_MVC_H264:
nb_vid++;
nb_avc++;
break;
case GF_ISOM_SUBTYPE_MPEG4:
{
GF_ESD *esd = gf_isom_get_esd(mp4file, i+1, 1);
u32 oti = (esd && esd->decoderConfig) ? esd->decoderConfig->objectTypeIndication : 0;
gf_odf_desc_del((GF_Descriptor *)esd);
/*both MPEG4-Video and H264/AVC/SVC are supported*/
switch (oti) {
case GF_CODECID_MPEG4_PART2:
case GF_CODECID_AVC:
case GF_CODECID_SVC:
case GF_CODECID_MVC:
nb_vid++;
break;
default:
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[3GPP convert] Video format not supported by 3GP - removing track ID %d\n", gf_isom_get_track_id(mp4file, i+1) ));
goto remove_track;
}
}
break;
default:
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[3GPP convert] Video format not supported by 3GP - removing track ID %d\n", gf_isom_get_track_id(mp4file, i+1) ));
goto remove_track;
}
break;
case GF_ISOM_MEDIA_AUDIO:
if (stype == GF_ISOM_SUBTYPE_MPEG4_CRYP) gf_isom_get_ismacryp_info(mp4file, i+1, 1, &stype, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
switch (stype) {
case GF_ISOM_SUBTYPE_3GP_EVRC:
case GF_ISOM_SUBTYPE_3GP_QCELP:
case GF_ISOM_SUBTYPE_3GP_SMV:
is_3g2 = 1;
nb_aud++;
break;
case GF_ISOM_SUBTYPE_3GP_AMR:
case GF_ISOM_SUBTYPE_3GP_AMR_WB:
nb_aud++;
nb_non_mp4 ++;
break;
case GF_ISOM_SUBTYPE_MPEG4:
{
GF_ESD *esd = gf_isom_get_esd(mp4file, i+1, 1);
u32 oti = (esd && esd->decoderConfig) ? esd->decoderConfig->objectTypeIndication : 0;
gf_odf_desc_del((GF_Descriptor *)esd);
switch (oti) {
case GF_CODECID_QCELP:
case GF_CODECID_EVRC:
case GF_CODECID_SMV:
is_3g2 = 1;
case GF_CODECID_AAC_MPEG4:
nb_aud++;
break;
default:
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[3GPP convert] Audio format not supported by 3GP - removing track ID %d\n", gf_isom_get_track_id(mp4file, i+1) ));
goto remove_track;
}
}
break;
default:
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[3GPP convert] Audio format not supported by 3GP - removing track ID %d\n", gf_isom_get_track_id(mp4file, i+1) ));
goto remove_track;
}
break;
case GF_ISOM_MEDIA_SUBT:
gf_isom_set_media_type(mp4file, i+1, GF_ISOM_MEDIA_TEXT);
case GF_ISOM_MEDIA_TEXT:
case GF_ISOM_MEDIA_MPEG_SUBT:
nb_txt++;
break;
case GF_ISOM_MEDIA_SCENE:
if (stype == GF_ISOM_MEDIA_DIMS) {
nb_dims++;
break;
}
/*clean file*/
default:
if (mType==GF_ISOM_MEDIA_HINT) {
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[3GPP convert] Removing Hint track ID %d\n", gf_isom_get_track_id(mp4file, i+1) ));
} else {
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[3GPP convert] Removing system track ID %d\n", gf_isom_get_track_id(mp4file, i+1) ));
}
remove_track:
gf_isom_remove_track(mp4file, i+1);
i -= 1;
Tracks = gf_isom_get_track_count(mp4file);
break;
}
}
/*no more IOD*/
gf_isom_remove_root_od(mp4file);
if (is_3g2) {
gf_isom_set_brand_info(mp4file, GF_ISOM_BRAND_3G2A, 65536);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GP6, GF_FALSE);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GP5, GF_FALSE);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GG6, GF_FALSE);
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[3GPP convert] Setting major brand to 3GPP2\n"));
} else {
/*update FType*/
if ((nb_vid>1) || (nb_aud>1) || (nb_txt>1)) {
/*3GPP general purpose*/
gf_isom_set_brand_info(mp4file, GF_ISOM_BRAND_3GG6, 1024);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GP6, GF_FALSE);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GP5, GF_FALSE);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GP4, GF_FALSE);
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[3GPP convert] Setting major brand to 3GPP Generic file\n"));
} else if (nb_txt) {
gf_isom_set_brand_info(mp4file, GF_ISOM_BRAND_3GP6, 1024);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GP5, GF_TRUE);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GP4, GF_TRUE);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GG6, GF_FALSE);
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[3GPP convert] Setting major brand to 3GPP V6 file\n"));
} else if (nb_avc) {
gf_isom_set_brand_info(mp4file, GF_ISOM_BRAND_3GP6, 0/*1024*/);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_AVC1, GF_TRUE);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GP5, GF_FALSE);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GP4, GF_FALSE);
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[3GPP convert] Setting major brand to 3GPP V6 file + AVC compatible\n"));
} else {
gf_isom_set_brand_info(mp4file, GF_ISOM_BRAND_3GP5, 0/*1024*/);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GP6, 0);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GP4, GF_TRUE);
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_3GG6, GF_FALSE);
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[3GPP convert] Setting major brand to 3GPP V5 file\n"));
}
}
/*add/remove MP4 brands and add isom*/
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_MP41, (u8) ((nb_avc||is_3g2||nb_non_mp4) ? GF_FALSE : GF_TRUE));
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_MP42, (u8) (nb_non_mp4 ? GF_FALSE : GF_TRUE));
gf_isom_modify_alternate_brand(mp4file, GF_ISOM_BRAND_ISOM, GF_TRUE);
return GF_OK;
}
GF_EXPORT
GF_Err gf_media_make_psp(GF_ISOFile *mp4)
{
u32 i, count;
u32 nb_a, nb_v;
/*psp track UUID*/
bin128 psp_track_uuid = {0x55, 0x53, 0x4D, 0x54, 0x21, 0xD2, 0x4F, 0xCE, 0xBB, 0x88, 0x69, 0x5C, 0xFA, 0xC9, 0xC7, 0x40};
u8 psp_track_sig [] = {0x00, 0x00, 0x00, 0x1C, 0x4D, 0x54, 0x44, 0x54, 0x00, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x0A, 0x55, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
/*psp mov UUID*/
//bin128 psp_uuid = {0x50, 0x52, 0x4F, 0x46, 0x21, 0xD2, 0x4F, 0xCE, 0xBB, 0x88, 0x69, 0x5C, 0xFA, 0xC9, 0xC7, 0x40};
nb_a = nb_v = 0;
count = gf_isom_get_track_count(mp4);
for (i=0; i<count; i++) {
switch (gf_isom_get_media_type(mp4, i+1)) {
case GF_ISOM_MEDIA_VISUAL:
nb_v++;
break;
case GF_ISOM_MEDIA_AUDIO:
nb_a++;
break;
}
}
if ((nb_v != 1) && (nb_a!=1)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[PSP convert] Movies need one audio track and one video track\n" ));
return GF_BAD_PARAM;
}
for (i=0; i<count; i++) {
switch (gf_isom_get_media_type(mp4, i+1)) {
case GF_ISOM_MEDIA_VISUAL:
case GF_ISOM_MEDIA_AUDIO:
/*if no edit list, add one*/
if (!gf_isom_get_edits_count(mp4, i+1)) {
GF_ISOSample *samp = gf_isom_get_sample_info(mp4, i+1, 1, NULL, NULL);
if (samp) {
gf_isom_append_edit(mp4, i+1, gf_isom_get_track_duration(mp4, i+1), samp->CTS_Offset, GF_ISOM_EDIT_NORMAL);
gf_isom_sample_del(&samp);
}
}
/*add PSP UUID*/
gf_isom_remove_uuid(mp4, i+1, psp_track_uuid);
gf_isom_add_uuid(mp4, i+1, psp_track_uuid, (char *) psp_track_sig, 28);
break;
default:
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[PSP convert] Removing track ID %d\n", gf_isom_get_track_id(mp4, i+1) ));
gf_isom_remove_track(mp4, i+1);
i -= 1;
count -= 1;
break;
}
}
gf_isom_set_brand_info(mp4, GF_ISOM_BRAND_MSNV, 0);
gf_isom_modify_alternate_brand(mp4, GF_ISOM_BRAND_MSNV, GF_TRUE);
return GF_OK;
}
GF_Err gf_media_get_color_info(GF_ISOFile *file, u32 track, u32 sampleDescriptionIndex, u32 *colour_type, u16 *colour_primaries, u16 *transfer_characteristics, u16 *matrix_coefficients, Bool *full_range_flag)
{
#ifndef GPAC_DISABLE_AV_PARSERS
u32 stype = gf_isom_get_media_subtype(file, track, sampleDescriptionIndex);
if ((stype==GF_ISOM_SUBTYPE_AVC_H264)
|| (stype==GF_ISOM_SUBTYPE_AVC2_H264)
|| (stype==GF_ISOM_SUBTYPE_AVC3_H264)
|| (stype==GF_ISOM_SUBTYPE_AVC4_H264)
) {
AVCState *avc_state;
GF_AVCConfig *avcc = gf_isom_avc_config_get(file, track, sampleDescriptionIndex);
if (!avcc) return GF_NOT_FOUND;
u32 i;
s32 idx;
GF_NALUFFParam *slc;
GF_SAFEALLOC(avc_state, AVCState);
if (!avc_state) return GF_OUT_OF_MEM;
avc_state->sps_active_idx = -1;
i=0;
while ((slc = (GF_NALUFFParam *)gf_list_enum(avcc->sequenceParameterSets, &i))) {
idx = gf_avc_read_sps(slc->data, slc->size, avc_state, 0, NULL);
if (idx<0) continue;
if (! avc_state->sps[idx].vui_parameters_present_flag )
continue;
*colour_type = avc_state->sps[idx].vui.video_format;
*colour_primaries = avc_state->sps[idx].vui.colour_primaries;
*transfer_characteristics = avc_state->sps[idx].vui.transfer_characteristics;
*matrix_coefficients = avc_state->sps[idx].vui.matrix_coefficients;
*full_range_flag = avc_state->sps[idx].vui.video_full_range_flag;
gf_odf_avc_cfg_del(avcc);
gf_free(avc_state);
return GF_OK;
}
gf_odf_avc_cfg_del(avcc);
gf_free(avc_state);
return GF_NOT_FOUND;
}
if ((stype==GF_ISOM_SUBTYPE_HEV1)
|| (stype==GF_ISOM_SUBTYPE_HEV2)
|| (stype==GF_ISOM_SUBTYPE_HVC1)
|| (stype==GF_ISOM_SUBTYPE_HVC2)
|| (stype==GF_ISOM_SUBTYPE_LHV1)
|| (stype==GF_ISOM_SUBTYPE_LHE1)
) {
HEVCState *hvc_state;
GF_HEVCConfig *hvcc = gf_isom_hevc_config_get(file, track, sampleDescriptionIndex);
if (!hvcc) return GF_NOT_FOUND;
u32 i;
GF_NALUFFParamArray *pa;
GF_SAFEALLOC(hvc_state, HEVCState);
if (!hvc_state) return GF_OUT_OF_MEM;
hvc_state->sps_active_idx = -1;
i=0;
while ((pa = (GF_NALUFFParamArray *)gf_list_enum(hvcc->param_array, &i))) {
GF_NALUFFParam *slc;
u32 j;
s32 idx;
if (pa->type != GF_HEVC_NALU_SEQ_PARAM) continue;
j=0;
while ((slc = (GF_NALUFFParam *)gf_list_enum(pa->nalus, &j))) {
idx = gf_hevc_read_sps(slc->data, slc->size, hvc_state);
if (idx<0) continue;
if (! hvc_state->sps[idx].vui_parameters_present_flag)
continue;
*colour_type = hvc_state->sps[idx].video_format;
*colour_primaries = hvc_state->sps[idx].colour_primaries;
*transfer_characteristics = hvc_state->sps[idx].transfer_characteristic;
*matrix_coefficients = hvc_state->sps[idx].matrix_coeffs;
*full_range_flag = hvc_state->sps[idx].video_full_range_flag;
gf_odf_hevc_cfg_del(hvcc);
gf_free(hvc_state);
return GF_OK;
}
}
gf_odf_hevc_cfg_del(hvcc);
gf_free(hvc_state);
return GF_NOT_FOUND;
}
if (stype==GF_ISOM_SUBTYPE_AV01) {
AV1State *av1_state;
GF_SAFEALLOC(av1_state, AV1State);
if (!av1_state) return GF_OUT_OF_MEM;
gf_av1_init_state(av1_state);
av1_state->config = gf_isom_av1_config_get(file, track, sampleDescriptionIndex);
if (av1_state->config) {
u32 i;
for (i=0; i<gf_list_count(av1_state->config->obu_array); i++) {
GF_BitStream *bs;
ObuType obu_type = 0;
u32 hdr_size = 0;
u64 obu_size = 0;
GF_AV1_OBUArrayEntry *obu = gf_list_get(av1_state->config->obu_array, i);
bs = gf_bs_new(obu->obu, (u32) obu->obu_length, GF_BITSTREAM_READ);
gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, av1_state);
gf_bs_del(bs);
if (av1_state->color_description_present_flag) {
*colour_type = 0;
*colour_primaries = av1_state->color_primaries;
*transfer_characteristics = av1_state->transfer_characteristics;
*matrix_coefficients = av1_state->matrix_coefficients;
*full_range_flag = av1_state->color_range;
if (av1_state->config) gf_odf_av1_cfg_del(av1_state->config);
gf_av1_reset_state(av1_state, GF_TRUE);
gf_free(av1_state);
return GF_OK;
}
}
}
if (av1_state->config) gf_odf_av1_cfg_del(av1_state->config);
gf_av1_reset_state(av1_state, GF_TRUE);
gf_free(av1_state);
return GF_NOT_FOUND;
}
#endif
return GF_NOT_SUPPORTED;
}
GF_Err gf_isom_set_meta_qt(GF_ISOFile *file);
GF_EXPORT
GF_Err gf_media_check_qt_prores(GF_ISOFile *mp4)
{
u32 i, count, timescale, def_dur=0, video_tk=0;
u32 prores_type = 0;
GF_Err e;
u32 colour_type=0;
u16 colour_primaries=0, transfer_characteristics=0, matrix_coefficients=0;
Bool full_range_flag=GF_FALSE;
u32 hspacing=0, vspacing=0;
u32 nb_video_tracks;
u32 target_ts = 0, w=0, h=0, chunk_size=0;
nb_video_tracks = 0;
count = gf_isom_get_track_count(mp4);
for (i=0; i<count; i++) {
u32 mtype = gf_isom_get_media_type(mp4, i+1);
if (mtype!=GF_ISOM_MEDIA_VISUAL) continue;
nb_video_tracks++;
if (!video_tk)
video_tk = i+1;
}
if ((nb_video_tracks==1) && video_tk) {
u32 video_subtype = gf_isom_get_media_subtype(mp4, video_tk, 1);
switch (video_subtype) {
case GF_QT_SUBTYPE_APCH:
case GF_QT_SUBTYPE_APCO:
case GF_QT_SUBTYPE_APCN:
case GF_QT_SUBTYPE_APCS:
case GF_QT_SUBTYPE_AP4X:
case GF_QT_SUBTYPE_AP4H:
prores_type=video_subtype;
break;
}
}
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[QTFF/ProRes] Adjusting %s compliancy\n", prores_type ? "ProRes" : "QTFF"));
//adjust audio tracks
for (i=0; i<count; i++) {
u32 mtype = gf_isom_get_media_type(mp4, i+1);
//remove bitrate info (isobmff)
gf_isom_update_bitrate(mp4, i+1, 1, 0, 0, 0);
if (mtype==GF_ISOM_MEDIA_AUDIO) {
u32 sr, nb_ch, bps;
gf_isom_get_audio_info(mp4, i+1, 1, &sr, &nb_ch, &bps);
gf_isom_set_audio_info(mp4, i+1, 1, sr, nb_ch, bps, GF_IMPORT_AUDIO_SAMPLE_ENTRY_v1_QTFF);
gf_isom_hint_max_chunk_duration(mp4, i+1, gf_isom_get_media_timescale(mp4, i+1) / 2);
continue;
}
}
//make QT
gf_isom_remove_root_od(mp4);
if (gf_isom_get_mode(mp4) != GF_ISOM_OPEN_WRITE) {
gf_isom_set_brand_info(mp4, GF_ISOM_BRAND_QT, 512);
gf_isom_reset_alt_brands(mp4);
} else {
u32 brand, version;
gf_isom_get_brand_info(mp4, &brand, &version, NULL);
if (brand != GF_ISOM_BRAND_QT) {
GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[ProRes] Cannot change brand from \"%s\" to \"qt \", flat storage used. Try using different storage mode\n", gf_4cc_to_str(brand)));
}
}
gf_isom_set_meta_qt(mp4);
if (!video_tk) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[QTFF] No visual track\n"));
return GF_OK;
}
if (nb_video_tracks>1) {
if (prores_type) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("QTFF] cannot adjust params to prores, %d video tracks present\n", nb_video_tracks));
return GF_BAD_PARAM;
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[ProRes] no prores codec found but %d video tracks, not adjusting file\n", nb_video_tracks));
return GF_OK;
}
if (prores_type) {
char *comp_name = NULL;
switch (prores_type) {
case GF_QT_SUBTYPE_APCH: comp_name = "\x0013""Apple ProRes 422 HQ"; break;
case GF_QT_SUBTYPE_APCO: comp_name = "\x0016""Apple ProRes 422 Proxy"; break;
case GF_QT_SUBTYPE_APCN: comp_name = "\x0010""Apple ProRes 422"; break;
case GF_QT_SUBTYPE_APCS: comp_name = "\x0013""Apple ProRes 422 LT"; break;
case GF_QT_SUBTYPE_AP4X: comp_name = "\x0014""Apple ProRes 4444 XQ"; break;
case GF_QT_SUBTYPE_AP4H: comp_name = "\x0011""Apple ProRes 4444"; break;
}
gf_isom_update_video_sample_entry_fields(mp4, video_tk, 1, 0, GF_4CC('a','p','p','l'), 0, 0x3FF, 72<<16, 72<<16, 1, comp_name, -1);
}
timescale = gf_isom_get_media_timescale(mp4, video_tk);
def_dur = gf_isom_get_constant_sample_duration(mp4, video_tk);
if (!def_dur) {
def_dur = gf_isom_get_sample_duration(mp4, video_tk, 2);
if (!def_dur) {
def_dur = gf_isom_get_sample_duration(mp4, video_tk, 1);
}
}
if (!def_dur) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[ProRes] cannot estimate default sample duration for video track\n"));
return GF_NON_COMPLIANT_BITSTREAM;
}
gf_isom_get_pixel_aspect_ratio(mp4, video_tk, 1, &hspacing, &vspacing);
//force 1:1
if ((hspacing<=1) || (vspacing<=1)) {
hspacing = vspacing = 1;
gf_isom_set_pixel_aspect_ratio(mp4, video_tk, 1, 1, 1, GF_TRUE);
}
//patch enof/prof/clef
if (prores_type) {
gf_isom_update_aperture_info(mp4, video_tk, GF_FALSE);
}
e = gf_isom_get_color_info(mp4, video_tk, 1, &colour_type, &colour_primaries, &transfer_characteristics, &matrix_coefficients, &full_range_flag);
if (e==GF_NOT_FOUND) {
colour_primaries = transfer_characteristics = matrix_coefficients = 0;
if (prores_type) {
u32 di;
GF_ISOSample *s = gf_isom_get_sample(mp4, video_tk, 1, &di);
if (s && s->dataLength>24) {
GF_BitStream *bs = gf_bs_new(s->data, s->dataLength, GF_BITSTREAM_READ);
gf_bs_read_u32(bs); //frame size
gf_bs_read_u32(bs); //frame ID
gf_bs_read_u32(bs); //frame header size + reserved + bs version
gf_bs_read_u32(bs); //encoder id
gf_bs_read_u32(bs); //w and h
gf_bs_read_u16(bs); //bunch of flags
colour_primaries = gf_bs_read_u8(bs);
transfer_characteristics = gf_bs_read_u8(bs);
matrix_coefficients = gf_bs_read_u8(bs);
gf_bs_del(bs);
}
gf_isom_sample_del(&s);
} else {
e = gf_media_get_color_info(mp4, video_tk, 1, &colour_type, &colour_primaries, &transfer_characteristics, &matrix_coefficients, &full_range_flag);
if (e)
colour_primaries=0;
}
if (!colour_primaries) {
GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[ProRes] No color info present in visual track, defaulting to BT709\n"));
colour_primaries = 1;
transfer_characteristics = 1;
matrix_coefficients = 1;
} else {
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ProRes] No color info present in visual track, extracting from %s\n", prores_type ? "first ProRes frame" : "sample description"));
}
gf_isom_set_visual_color_info(mp4, video_tk, 1, GF_4CC('n','c','l','c'), colour_primaries, transfer_characteristics, matrix_coefficients, GF_FALSE, NULL, 0);
} else if (e) {
return e;
}
gf_isom_get_visual_info(mp4, video_tk, 1, &w, &h);
u32 ifps;
Double FPS = timescale;
FPS /= def_dur;
FPS *= 100;
ifps = (u32) FPS;
if (ifps>= 2996 && ifps<=2998) target_ts = 30000; //29.97
else if (ifps>= 2999 && ifps<=3001) target_ts = 3000; //30
else if (ifps>= 2495 && ifps<=2505) target_ts = 2500; //25
else if (!(def_dur%125) && !(timescale % 2997)) target_ts = 2997; //23.97
else if (ifps >= 2396 && ifps<=2398) target_ts = 24000; //23.97
else if ((ifps>=2399) && (ifps<=2401)) target_ts = 2400; //24
else if (ifps>= 4990 && ifps<=5010) target_ts = 5000; //50
else if (ifps>= 5993 && ifps<=5995) target_ts = 60000; //59.94
else if (ifps>= 5996 && ifps<=6004) target_ts = 6000; //60
else if (ifps>= 4794 && ifps<=4796) target_ts = 48000; //47.952
else if (ifps>= 4799 && ifps<=4801) target_ts = 4800; //48
if (!target_ts) {
if (prores_type) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[ProRes] Unrecognized frame rate %g\n", ((Double)timescale)/def_dur ));
return GF_NON_COMPLIANT_BITSTREAM;
} else {
target_ts = timescale;
}
}
if (target_ts != timescale) {
GF_LOG(GF_LOG_INFO, GF_LOG_MEDIA, ("[ProRes] Adjusting timescale to %d\n", target_ts));
gf_isom_set_media_timescale(mp4, video_tk, target_ts, 0, 0);
}
gf_isom_set_timescale(mp4, target_ts);
if ((w<=720) && (h<=576)) chunk_size = 2000000;
else chunk_size = 4000000;
gf_isom_set_interleave_time(mp4, 500);
gf_isom_hint_max_chunk_size(mp4, video_tk, chunk_size);
return GF_OK;
}
#endif /*GPAC_DISABLE_ISOM_WRITE*/
GF_EXPORT
GF_ESD *gf_media_map_esd(GF_ISOFile *mp4, u32 track, u32 stsd_idx)
{
GF_ESD *esd;
u32 subtype;
if (!stsd_idx) stsd_idx = 1;
subtype = gf_isom_get_media_subtype(mp4, track, stsd_idx);
/*all types with an official MPEG-4 mapping*/
switch (subtype) {
case GF_ISOM_SUBTYPE_MPEG4:
case GF_ISOM_SUBTYPE_MPEG4_CRYP:
case GF_ISOM_SUBTYPE_AVC_H264:
case GF_ISOM_SUBTYPE_AVC2_H264:
case GF_ISOM_SUBTYPE_AVC3_H264:
case GF_ISOM_SUBTYPE_AVC4_H264:
case GF_ISOM_SUBTYPE_SVC_H264:
case GF_ISOM_SUBTYPE_MVC_H264:
case GF_ISOM_SUBTYPE_3GP_EVRC:
case GF_ISOM_SUBTYPE_3GP_QCELP:
case GF_ISOM_SUBTYPE_3GP_SMV:
case GF_ISOM_SUBTYPE_HVC1:
case GF_ISOM_SUBTYPE_HEV1:
case GF_ISOM_SUBTYPE_HVC2:
case GF_ISOM_SUBTYPE_HEV2:
case GF_ISOM_SUBTYPE_LHV1:
case GF_ISOM_SUBTYPE_LHE1:
case GF_ISOM_SUBTYPE_AV01:
case GF_ISOM_SUBTYPE_DAV1:
case GF_ISOM_SUBTYPE_VP09:
case GF_ISOM_SUBTYPE_VP08:
case GF_ISOM_SUBTYPE_VVC1:
case GF_ISOM_SUBTYPE_VVI1:
case GF_ISOM_SUBTYPE_MH3D_MHA1:
case GF_ISOM_SUBTYPE_MH3D_MHA2:
case GF_ISOM_SUBTYPE_MH3D_MHM1:
case GF_ISOM_SUBTYPE_MH3D_MHM2:
return gf_isom_get_esd(mp4, track, stsd_idx);
}
if (subtype == GF_ISOM_SUBTYPE_OPUS) {
esd = gf_isom_get_esd(mp4, track, 1);
if (!esd) return NULL;
if (!esd->decoderConfig) {
gf_odf_desc_del((GF_Descriptor *)esd);
return NULL;
}
esd->decoderConfig->objectTypeIndication = GF_CODECID_OPUS;
return esd;
}
if (subtype == GF_ISOM_SUBTYPE_3GP_DIMS) {
GF_BitStream *bs;
GF_DIMSDescription dims;
GF_Err e = gf_isom_get_dims_description(mp4, track, 1, &dims);
if (e!=GF_OK) return NULL;
esd = gf_odf_desc_esd_new(0);
if (!esd) return NULL;
esd->slConfig->timestampResolution = gf_isom_get_media_timescale(mp4, track);
esd->ESID = gf_isom_get_track_id(mp4, track);
esd->OCRESID = esd->ESID;
esd->decoderConfig->streamType = GF_STREAM_SCENE;
/*use private DSI*/
esd->decoderConfig->objectTypeIndication = GF_CODECID_DIMS;
bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
/*format ext*/
gf_bs_write_u8(bs, dims.profile);
gf_bs_write_u8(bs, dims.level);
gf_bs_write_int(bs, dims.pathComponents, 4);
gf_bs_write_int(bs, dims.fullRequestHost, 1);
gf_bs_write_int(bs, dims.streamType, 1);
gf_bs_write_int(bs, dims.containsRedundant, 2);
if (dims.textEncoding)
gf_bs_write_data(bs, (char*)dims.textEncoding, (u32) strlen(dims.textEncoding)+1);
else
gf_bs_write_data(bs, "", 1);
if (dims.contentEncoding)
gf_bs_write_data(bs, (char*)dims.contentEncoding, (u32) strlen(dims.contentEncoding)+1);
else
gf_bs_write_data(bs, "", 1);
gf_bs_get_content(bs, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
gf_bs_del(bs);
return esd;
}
if (mp4->convert_streaming_text && ((subtype == GF_ISOM_SUBTYPE_TEXT) || (subtype == GF_ISOM_SUBTYPE_TX3G))
) {
return gf_isom_get_esd(mp4, track, stsd_idx);
}
return NULL;
}
GF_EXPORT
GF_ESD *gf_media_map_item_esd(GF_ISOFile *mp4, u32 item_id)
{
u32 item_type;
u32 prot_scheme, prot_scheme_version;
Bool is_self_ref;
const char *name;
const char *mime;
const char *encoding;
const char *url;
const char *urn;
GF_ESD *esd;
GF_Err e;
u32 item_idx = gf_isom_get_meta_item_by_id(mp4, GF_TRUE, 0, item_id);
if (!item_idx) return NULL;
e = gf_isom_get_meta_item_info(mp4, GF_TRUE, 0, item_idx, &item_id, &item_type, &prot_scheme, &prot_scheme_version, &is_self_ref, &name, &mime, &encoding, &url, &urn);
if (e != GF_OK) return NULL;
GF_ImageItemProperties *props;
GF_SAFEALLOC(props, GF_ImageItemProperties);
if (!props) return NULL;
esd = gf_odf_desc_esd_new(0);
if (!esd) {
gf_free(props);
return NULL;
}
if (item_type == GF_ISOM_SUBTYPE_HVC1) {
if (item_id > (1 << 16)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n"));
}
esd->ESID = (u16)item_id;
esd->OCRESID = esd->ESID;
esd->decoderConfig->streamType = GF_STREAM_VISUAL;
esd->decoderConfig->objectTypeIndication = GF_CODECID_HEVC;
e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL);
if (e == GF_OK && props->config) {
GF_HEVCConfigurationBox *hvcc = props->config;
if (hvcc->type==GF_ISOM_BOX_TYPE_HVCC) {
gf_odf_hevc_cfg_write(hvcc->config, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
} else {
gf_odf_desc_del((GF_Descriptor*)esd);
gf_free(props);
return NULL;
}
}
esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1;
esd->slConfig->useTimestampsFlag = 1;
esd->slConfig->timestampResolution = 1000;
gf_free(props);
return esd;
}
if (item_type == GF_ISOM_SUBTYPE_AVC_H264) {
if (item_id > (1 << 16)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n"));
}
esd->ESID = (u16)item_id;
esd->OCRESID = esd->ESID;
esd->decoderConfig->streamType = GF_STREAM_VISUAL;
esd->decoderConfig->objectTypeIndication = GF_CODECID_AVC;
e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL);
if (e == GF_OK && props->config) {
GF_AVCConfigurationBox *avcc = props->config;
if (avcc->type==GF_ISOM_BOX_TYPE_AVCC) {
gf_odf_avc_cfg_write(avcc->config, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
} else {
gf_odf_desc_del((GF_Descriptor*)esd);
gf_free(props);
return NULL;
}
}
esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1;
esd->slConfig->useTimestampsFlag = 1;
esd->slConfig->timestampResolution = 1000;
gf_free(props);
return esd;
}
if (item_type == GF_ISOM_SUBTYPE_AV01) {
if (item_id > (1 << 16)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n"));
}
esd->ESID = (u16)item_id;
esd->OCRESID = esd->ESID;
esd->decoderConfig->streamType = GF_STREAM_VISUAL;
esd->decoderConfig->objectTypeIndication = GF_CODECID_AV1;
e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL);
if (e == GF_OK && props->config) {
GF_AV1ConfigurationBox *av1c = props->config;
if (av1c->type==GF_ISOM_BOX_TYPE_AV1C) {
gf_odf_av1_cfg_write(av1c->config, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
} else {
gf_odf_desc_del((GF_Descriptor*)esd);
gf_free(props);
return NULL;
}
}
esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1;
esd->slConfig->useTimestampsFlag = 1;
esd->slConfig->timestampResolution = 1000;
gf_free(props);
return esd;
}
if ((item_type == GF_ISOM_SUBTYPE_JPEG) || (mime && !strcmp(mime, "image/jpeg")) ){
if (item_id > (1 << 16)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n"));
}
esd->ESID = (u16)item_id;
esd->OCRESID = esd->ESID;
esd->decoderConfig->streamType = GF_STREAM_VISUAL;
esd->decoderConfig->objectTypeIndication = GF_CODECID_JPEG;
e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL);
if (e == GF_OK && props->config) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("JPEG image item decoder config not supported, patch welcome\n"));
}
esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1;
esd->slConfig->useTimestampsFlag = 1;
esd->slConfig->timestampResolution = 1000;
gf_free(props);
return esd;
}
if ((item_type == GF_ISOM_SUBTYPE_PNG) || (mime && !strcmp(mime, "image/png")) ){
if (item_id > (1 << 16)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n"));
}
esd->ESID = (u16)item_id;
esd->OCRESID = esd->ESID;
esd->decoderConfig->streamType = GF_STREAM_VISUAL;
esd->decoderConfig->objectTypeIndication = GF_CODECID_PNG;
e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL);
if (e) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Error fetching item properties %s\n", gf_error_to_string(e) ));
}
esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1;
esd->slConfig->useTimestampsFlag = 1;
esd->slConfig->timestampResolution = 1000;
gf_free(props);
return esd;
}
if (item_type == GF_ISOM_SUBTYPE_VVC1) {
if (item_id > (1 << 16)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n"));
}
esd->ESID = (u16)item_id;
esd->OCRESID = esd->ESID;
esd->decoderConfig->streamType = GF_STREAM_VISUAL;
esd->decoderConfig->objectTypeIndication = GF_CODECID_VVC;
e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL);
if (e == GF_OK && props->config) {
GF_VVCConfigurationBox *vvcc = props->config;
if (vvcc->type == GF_ISOM_BOX_TYPE_VVCC) {
gf_odf_vvc_cfg_write(vvcc->config, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
} else {
gf_odf_desc_del((GF_Descriptor*)esd);
gf_free(props);
return NULL;
}
}
esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1;
esd->slConfig->useTimestampsFlag = 1;
esd->slConfig->timestampResolution = 1000;
gf_free(props);
return esd;
}
if (item_type == GF_4CC('j','2','k','1')) {
if (item_id > (1 << 16)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n"));
}
esd->ESID = (u16)item_id;
esd->OCRESID = esd->ESID;
esd->decoderConfig->streamType = GF_STREAM_VISUAL;
esd->decoderConfig->objectTypeIndication = GF_CODECID_J2K;
e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, NULL);
if (e == GF_OK && props->config) {
GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
gf_isom_box_write((GF_Box*)props->config, bs);
gf_bs_get_content(bs, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
gf_bs_del(bs);
}
esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1;
esd->slConfig->useTimestampsFlag = 1;
esd->slConfig->timestampResolution = 1000;
gf_free(props);
return esd;
}
if ((item_type == GF_ISOM_SUBTYPE_UNCV) || (item_type == GF_ISOM_ITEM_TYPE_UNCI)) {
if (item_id > (1 << 16)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CORE, ("Item ID greater than 16 bits, does not fit on ES ID\n"));
}
esd->ESID = (u16)item_id;
esd->OCRESID = esd->ESID;
esd->decoderConfig->streamType = GF_STREAM_VISUAL;
esd->decoderConfig->objectTypeIndication = GF_CODECID_RAW;
GF_List *other_props = gf_list_new();
e = gf_isom_get_meta_image_props(mp4, GF_TRUE, 0, item_id, props, other_props);
#ifndef GPAC_DISABLE_ISOM_WRITE
if ((e == GF_OK) && gf_list_count(other_props)) {
GF_BitStream *bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
gf_isom_box_array_write(NULL, other_props, bs);
gf_bs_get_content(bs, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
gf_bs_del(bs);
}
#endif // GPAC_DISABLE_ISOM_WRITE
gf_list_del(other_props);
esd->slConfig->hasRandomAccessUnitsOnlyFlag = 1;
esd->slConfig->useTimestampsFlag = 1;
esd->slConfig->timestampResolution = 1000;
gf_free(props);
return esd;
}
gf_odf_desc_del((GF_Descriptor*)esd);
gf_free(props);
return NULL;
}
#endif /*GPAC_DISABLE_ISOM*/
#ifndef GPAC_DISABLE_MEDIA_IMPORT
static s32 gf_get_DQId(GF_ISOFile *file, u32 track)
{
GF_AVCConfig *svccfg;
GF_ISOSample *samp;
u32 di = 0, cur_extract_mode;
char *buffer;
GF_BitStream *bs;
u32 max_size = 4096;
u32 size, nalu_size_length;
u8 nal_type;
s32 DQId=0;
samp = NULL;
bs = NULL;
cur_extract_mode = gf_isom_get_nalu_extract_mode(file, track);
gf_isom_set_nalu_extract_mode(file, track, GF_ISOM_NALU_EXTRACT_INSPECT);
buffer = (char*)gf_malloc(sizeof(char) * max_size);
svccfg = gf_isom_svc_config_get(file, track, 1);
if (!svccfg)
{
DQId = 0;
goto exit;
}
samp = gf_isom_get_sample(file, track, 1, &di);
if (!samp)
{
DQId = -1;
goto exit;
}
bs = gf_bs_new(samp->data, samp->dataLength, GF_BITSTREAM_READ);
nalu_size_length = 8 * svccfg->nal_unit_size;
while (gf_bs_available(bs))
{
size = gf_bs_read_int(bs, nalu_size_length);
if (size>max_size) {
buffer = (char*)gf_realloc(buffer, sizeof(char)*size);
max_size = size;
}
gf_bs_read_data(bs, buffer, size);
nal_type = buffer[0] & 0x1F;
if (nal_type == GF_AVC_NALU_SVC_SLICE)
{
DQId = buffer[2] & 0x7F;
goto exit;
}
}
exit:
if (svccfg) gf_odf_avc_cfg_del(svccfg);
if (samp) gf_isom_sample_del(&samp);
if (buffer) gf_free(buffer);
if (bs) gf_bs_del(bs);
gf_isom_set_nalu_extract_mode(file, track, cur_extract_mode);
return DQId;
}
#ifndef GPAC_DISABLE_AV_PARSERS
static Bool gf_isom_has_svc_explicit(GF_ISOFile *file, u32 track)
{
GF_AVCConfig *svccfg;
GF_NALUFFParam *slc;
u32 i;
u8 type;
Bool ret = 0;
svccfg = gf_isom_svc_config_get(file, track, 1);
if (!svccfg)
return 0;
for (i = 0; i < gf_list_count(svccfg->sequenceParameterSets); i++)
{
slc = (GF_NALUFFParam *)gf_list_get(svccfg->sequenceParameterSets, i);
type = slc->data[0] & 0x1F;
if (type == GF_AVC_NALU_SEQ_PARAM)
{
ret = 1;
break;
}
}
gf_odf_avc_cfg_del(svccfg);
return ret;
}
static u32 gf_isom_get_track_id_max(GF_ISOFile *file)
{
u32 num_track, i, trackID;
u32 max_id = 0;
num_track = gf_isom_get_track_count(file);
for (i = 1; i <= num_track; i++)
{
trackID = gf_isom_get_track_id(file, i);
if (max_id < trackID)
max_id = trackID;
}
return max_id;
}
#endif
/* Split SVC layers */
GF_EXPORT
GF_Err gf_media_split_svc(GF_ISOFile *file, u32 track, Bool splitAll)
{
#ifndef GPAC_DISABLE_AV_PARSERS
GF_AVCConfig *svccfg, *cfg;
u32 num_svc_track, num_sample, svc_track, dst_track, ref_trackID, ref_trackNum, max_id, di, width, height, size, nalu_size_length, i, j, t, max_size, num_pps, num_sps, num_subseq, NALUnitHeader, data_offset, data_length, count, timescale, cur_extract_mode;
GF_Err e;
GF_NALUFFParam *slc, *sl;
AVCState *avc_state=NULL;
s32 sps_id, pps_id;
GF_ISOSample *samp, *dst_samp;
GF_BitStream *bs, *dst_bs;
GF_BitStream ** sample_bs;
u8 nal_type, track_ref_index;
char *buffer;
s32 *sps_track, *sps, *pps;
u64 offset;
Bool is_splitted;
Bool *first_sample_track, *is_subseq_pps;
u64 *first_DTS_track;
s8 sample_offset;
max_size = 4096;
e = GF_OK;
samp = dst_samp = NULL;
bs = NULL;
sample_bs = NULL;
sps_track = sps = pps = NULL;
first_DTS_track = NULL;
first_sample_track = is_subseq_pps = NULL;
buffer = NULL;
cfg = NULL;
num_svc_track=0;
cur_extract_mode = gf_isom_get_nalu_extract_mode(file, track);
gf_isom_set_nalu_extract_mode(file, track, GF_ISOM_NALU_EXTRACT_INSPECT);
svccfg = gf_isom_svc_config_get(file, track, 1);
if (!svccfg)
{
e = GF_OK;
goto exit;
}
num_sps = gf_list_count(svccfg->sequenceParameterSets);
if (!num_sps)
{
e = GF_OK;
goto exit;
}
num_pps = gf_list_count(svccfg->pictureParameterSets);
if ((gf_isom_get_avc_svc_type(file, track, 1) == GF_ISOM_AVCTYPE_SVC_ONLY) && !gf_isom_has_svc_explicit(file, track))
is_splitted = 1;
else
is_splitted = 0;
num_subseq = gf_isom_has_svc_explicit(file, track) ? num_sps - 1 : num_sps;
if (is_splitted)
{
/*this track has only one SVC ...*/
if (num_sps == 1)
{
/*use 'all' mode -> stop*/
if (splitAll)
goto exit;
/*use 'base' mode -> merge SVC tracks*/
else
{
e = gf_media_merge_svc(file, track, 0);
goto exit;
}
}
/*this file has been in 'splitbase' mode*/
else if (!splitAll)
goto exit;
}
timescale = gf_isom_get_media_timescale(file, track);
num_svc_track = splitAll ? num_subseq : 1;
max_id = gf_isom_get_track_id_max(file);
di = 0;
GF_SAFEALLOC(avc_state, AVCState);
if (!avc_state) {
e = GF_OUT_OF_MEM;
goto exit;
}
avc_state->sps_active_idx = -1;
nalu_size_length = 8 * svccfg->nal_unit_size;
/*read all sps, but we need only the subset sequence parameter sets*/
sps = (s32 *) gf_malloc(num_subseq * sizeof(s32));
sps_track = (s32 *) gf_malloc(num_subseq * sizeof(s32));
count = 0;
for (i = 0; i < num_sps; i++)
{
slc = (GF_NALUFFParam *)gf_list_get(svccfg->sequenceParameterSets, i);
nal_type = slc->data[0] & 0x1F;
sps_id = gf_avc_read_sps(slc->data, slc->size, avc_state, 0, NULL);
if (sps_id < 0) {
e = GF_NON_COMPLIANT_BITSTREAM;
goto exit;
}
if (nal_type == GF_AVC_NALU_SVC_SUBSEQ_PARAM)
{
sps[count] = sps_id;
sps_track[count] = i;
count++;
}
}
/*for testing*/
gf_assert(count == num_subseq);
/*read all pps*/
pps = (s32 *) gf_malloc(num_pps * sizeof(s32));
for (j = 0; j < num_pps; j++)
{
slc = (GF_NALUFFParam *)gf_list_get(svccfg->pictureParameterSets, j);
pps_id = gf_avc_read_pps(slc->data, slc->size, avc_state);
if (pps_id < 0) {
e = GF_NON_COMPLIANT_BITSTREAM;
goto exit;
}
pps[j] = pps_id;
}
if (!is_splitted)
ref_trackID = gf_isom_get_track_id(file, track);
else
{
gf_isom_get_reference(file, track, GF_ISOM_REF_BASE, 1, &ref_trackNum);
ref_trackID = gf_isom_get_track_id(file, ref_trackNum);
}
buffer = (char*)gf_malloc(sizeof(char) * max_size);
/*read first sample for determinating the order of SVC tracks*/
count = 0;
samp = gf_isom_get_sample(file, track, 1, &di);
if (!samp)
{
e = gf_isom_last_error(file);
goto exit;
}
bs = gf_bs_new(samp->data, samp->dataLength, GF_BITSTREAM_READ);
offset = 0;
is_subseq_pps = (Bool *) gf_malloc(num_pps*sizeof(Bool));
for (i = 0; i < num_pps; i++)
is_subseq_pps[i] = 0;
while (gf_bs_available(bs))
{
gf_bs_enable_emulation_byte_removal(bs, GF_FALSE);
size = gf_bs_read_int(bs, nalu_size_length);
if (size>max_size) {
buffer = (char*)gf_realloc(buffer, sizeof(char)*size);
max_size = size;
}
gf_avc_parse_nalu(bs, avc_state);
nal_type = avc_state->last_nal_type_parsed;
e = gf_bs_seek(bs, offset+nalu_size_length/8);
if (e)
goto exit;
gf_bs_read_data(bs, buffer, size);
offset += size + nalu_size_length/8;
if (nal_type == GF_AVC_NALU_SVC_SLICE)
{
if (!avc_state->s_info.pps)
continue;
for (i = 0; i < num_pps; i++)
{
if (avc_state->s_info.pps->id == pps[i])
{
is_subseq_pps[i] = 1;
break;
}
}
if ((count > 0) && (avc_state->s_info.pps->sps_id == sps[count-1]))
continue;
/*verify the order of SPS, reorder if necessary*/
if (avc_state->s_info.pps->sps_id != sps[count])
{
for (i = count+1; i < num_subseq; i++)
{
/*swap two SPS*/
if (avc_state->s_info.pps->sps_id == sps[i])
{
sps[i] = sps[count];
sps[count] = avc_state->s_info.pps->sps_id;
sps_track[count] = i;
break;
}
}
}
count++;
}
}
gf_bs_del(bs);
bs = NULL;
gf_isom_sample_del(&samp);
samp = NULL;
for (t = 0; t < num_svc_track; t++)
{
svc_track = gf_isom_new_track(file, t+1+max_id, GF_ISOM_MEDIA_VISUAL, timescale);
if (!svc_track)
{
e = gf_isom_last_error(file);
goto exit;
}
gf_isom_set_track_enabled(file, svc_track, GF_TRUE);
gf_isom_set_track_reference(file, svc_track, GF_ISOM_REF_BASE, ref_trackID);
//copy over edit list
for (i=0; i<gf_isom_get_edits_count(file, track); i++) {
GF_ISOEditType emode;
u64 etime, edur, mtime;
gf_isom_get_edit(file, track, i+1, &etime, &edur, &mtime, &emode);
gf_isom_set_edit(file, svc_track, etime, edur, mtime, emode);
}
cfg = gf_odf_avc_cfg_new();
cfg->complete_representation = 1; //SVC
/*this layer depends on the base layer and the lower layers*/
gf_isom_set_track_reference(file, svc_track, GF_ISOM_REF_SCAL, ref_trackID);
for (i = 0; i < t; i++)
gf_isom_set_track_reference(file, svc_track, GF_ISOM_REF_SCAL, i+1+max_id);
e = gf_isom_svc_config_new(file, svc_track, cfg, NULL, NULL, &di);
if (e)
goto exit;
if (splitAll)
{
sps_id = sps[t];
width = avc_state->sps[sps_id].width;
height = avc_state->sps[sps_id].height;
gf_isom_set_visual_info(file, svc_track, di, width, height);
cfg->configurationVersion = 1;
cfg->chroma_bit_depth = 8 + avc_state->sps[sps_id].chroma_bit_depth_m8;
cfg->chroma_format = avc_state->sps[sps_id].chroma_format;
cfg->luma_bit_depth = 8 + avc_state->sps[sps_id].luma_bit_depth_m8;
cfg->profile_compatibility = avc_state->sps[sps_id].prof_compat;
cfg->AVCLevelIndication = avc_state->sps[sps_id].level_idc;
cfg->AVCProfileIndication = avc_state->sps[sps_id].profile_idc;
cfg->nal_unit_size = svccfg->nal_unit_size;
slc = (GF_NALUFFParam *)gf_list_get(svccfg->sequenceParameterSets, sps_track[t]);
sl = (GF_NALUFFParam*)gf_malloc(sizeof(GF_NALUFFParam));
sl->id = slc->id;
sl->size = slc->size;
sl->data = (char*)gf_malloc(sizeof(char)*sl->size);
memcpy(sl->data, slc->data, sizeof(char)*sl->size);
gf_list_add(cfg->sequenceParameterSets, sl);
for (j = 0; j < num_pps; j++)
{
pps_id = pps[j];
if (is_subseq_pps[j] && (avc_state->pps[pps_id].sps_id == sps_id))
{
slc = (GF_NALUFFParam *)gf_list_get(svccfg->pictureParameterSets, j);
sl = (GF_NALUFFParam*)gf_malloc(sizeof(GF_NALUFFParam));
sl->id = slc->id;
sl->size = slc->size;
sl->data = (char*)gf_malloc(sizeof(char)*sl->size);
memcpy(sl->data, slc->data, sizeof(char)*sl->size);
gf_list_add(cfg->pictureParameterSets, sl);
}
}
}
else
{
for (i = 0; i < num_subseq; i++)
{
sps_id = sps[i];
width = avc_state->sps[sps_id].width;
height = avc_state->sps[sps_id].height;
gf_isom_set_visual_info(file, svc_track, di, width, height);
cfg->configurationVersion = 1;
cfg->chroma_bit_depth = 8 + avc_state->sps[sps_id].chroma_bit_depth_m8;
cfg->chroma_format = avc_state->sps[sps_id].chroma_format;
cfg->luma_bit_depth = 8 + avc_state->sps[sps_id].luma_bit_depth_m8;
cfg->profile_compatibility = avc_state->sps[sps_id].prof_compat;
cfg->AVCLevelIndication = avc_state->sps[sps_id].level_idc;
cfg->AVCProfileIndication = avc_state->sps[sps_id].profile_idc;
cfg->nal_unit_size = svccfg->nal_unit_size;
slc = (GF_NALUFFParam *)gf_list_get(svccfg->sequenceParameterSets, sps_track[i]);
sl = (GF_NALUFFParam*)gf_malloc(sizeof(GF_NALUFFParam));
sl->id = slc->id;
sl->size = slc->size;
sl->data = (char*)gf_malloc(sizeof(char)*sl->size);
memcpy(sl->data, slc->data, sizeof(char)*sl->size);
gf_list_add(cfg->sequenceParameterSets, sl);
for (j = 0; j < num_pps; j++)
{
pps_id = pps[j];
if (avc_state->pps[pps_id].sps_id == sps_id)
{
slc = (GF_NALUFFParam *)gf_list_get(svccfg->pictureParameterSets, j);
sl = (GF_NALUFFParam*)gf_malloc(sizeof(GF_NALUFFParam));
sl->id = slc->id;
sl->size = slc->size;
sl->data = (char*)gf_malloc(sizeof(char)*sl->size);
memcpy(sl->data, slc->data, sizeof(char)*sl->size);
gf_list_add(cfg->pictureParameterSets, sl);
}
}
}
}
e = gf_isom_svc_config_update(file, svc_track, 1, cfg, 0);
if (e)
goto exit;
gf_odf_avc_cfg_del(cfg);
cfg = NULL;
}
num_sample = gf_isom_get_sample_count(file, track);
first_sample_track = (Bool *) gf_malloc((num_svc_track+1) * sizeof(Bool));
for (t = 0; t <= num_svc_track; t++)
first_sample_track[t] = 1;
first_DTS_track = (u64 *) gf_malloc((num_svc_track+1) * sizeof(u64));
for (t = 0; t <= num_svc_track; t++)
first_DTS_track[t] = 0;
for (i = 1; i <= num_sample; i++)
{
/*reset*/
memset(buffer, 0, max_size);
samp = gf_isom_get_sample(file, track, i, &di);
if (!samp)
{
e = GF_IO_ERR;
goto exit;
}
/* Create (num_svc_track) SVC bitstreams + 1 AVC bitstream*/
sample_bs = (GF_BitStream **) gf_malloc(sizeof(GF_BitStream *) * (num_svc_track+1));
for (j = 0; j <= num_svc_track; j++)
sample_bs[j] = (GF_BitStream *) gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
/*write extractor*/
for (t = 0; t < num_svc_track; t++)
{
//reference to base layer
gf_bs_write_int(sample_bs[t+1], 14, nalu_size_length); // extractor 's size = 14
NALUnitHeader = 0; //reset
NALUnitHeader |= 0x1F000000; // NALU type = 31
gf_bs_write_u32(sample_bs[t+1], NALUnitHeader);
track_ref_index = (u8) gf_isom_has_track_reference(file, t+1+max_id, GF_ISOM_REF_SCAL, ref_trackID);
if (!track_ref_index)
{
e = GF_CORRUPTED_DATA;
goto exit;
}
gf_bs_write_u8(sample_bs[t+1], track_ref_index);
sample_offset = 0;
gf_bs_write_u8(sample_bs[t+1], sample_offset);
data_offset = 0;
gf_bs_write_u32(sample_bs[t+1], data_offset);
data_length = 0;
gf_bs_write_u32(sample_bs[t+1], data_length);
//reference to previous layer(s)
for (j = 0; j < t; j++)
{
gf_bs_write_int(sample_bs[t+1], 14, nalu_size_length);
NALUnitHeader = 0;
NALUnitHeader |= 0x1F000000;
gf_bs_write_u32(sample_bs[t+1], NALUnitHeader);
track_ref_index = (u8) gf_isom_has_track_reference(file, t+1+max_id, GF_ISOM_REF_SCAL, j+1+max_id);
if (!track_ref_index)
{
e = GF_CORRUPTED_DATA;
goto exit;
}
gf_bs_write_u8(sample_bs[t+1], track_ref_index);
sample_offset = 0;
gf_bs_write_u8(sample_bs[t+1], sample_offset);
data_offset = (j+1) * (nalu_size_length/8 + 14); // (nalu_size_length/8) bytes of NALU length field + 14 bytes of extractor per layer
gf_bs_write_u32(sample_bs[t+1], data_offset);
data_length = 0;
gf_bs_write_u32(sample_bs[t+1], data_length);
}
}
bs = gf_bs_new(samp->data, samp->dataLength, GF_BITSTREAM_READ);
offset = 0;
while (gf_bs_available(bs))
{
gf_bs_enable_emulation_byte_removal(bs, GF_FALSE);
size = gf_bs_read_int(bs, nalu_size_length);
if (size>max_size) {
buffer = (char*)gf_realloc(buffer, sizeof(char)*size);
max_size = size;
}
gf_avc_parse_nalu(bs, avc_state);
nal_type = avc_state->last_nal_type_parsed;
e = gf_bs_seek(bs, offset+nalu_size_length/8);
if (e)
goto exit;
gf_bs_read_data(bs, buffer, size);
offset += size + nalu_size_length/8;
switch (nal_type) {
case GF_AVC_NALU_PIC_PARAM:
pps_id = avc_state->last_ps_idx;
j = 0;
dst_track = 0;
while (j < num_pps)
{
if (pps_id == pps[j])
break;
j++;
}
if ((j < num_pps) && (is_subseq_pps[j]))
{
if (splitAll)
{
for (t = 0; t < num_svc_track; t++)
{
if (sps[t] == avc_state->pps[pps_id].sps_id)
{
dst_track = t + 1;
break;
}
}
}
else
dst_track = 1;
}
dst_bs = sample_bs[dst_track];
break;
case GF_AVC_NALU_SVC_SUBSEQ_PARAM:
sps_id = avc_state->last_ps_idx;
dst_track = 0;
if (splitAll)
{
for (t = 0; t < num_svc_track; t++)
{
if (sps[t] == sps_id)
{
dst_track = t + 1;
break;
}
}
}
else
dst_track = 1;
dst_bs = sample_bs[dst_track];
break;
case GF_AVC_NALU_SVC_SLICE:
dst_track = 0;
if (splitAll)
{
for (t = 0; t < num_svc_track; t++)
{
if (sps[t] == (avc_state->s_info.pps)->sps_id)
{
dst_track = t + 1;
break;
}
}
}
else
dst_track = 1;
dst_bs = sample_bs[dst_track];
break;
default:
dst_bs = sample_bs[0];
}
gf_bs_write_int(dst_bs, size, nalu_size_length);
gf_bs_write_data(dst_bs, buffer, size);
}
for (j = 0; j <= num_svc_track; j++)
{
if (gf_bs_get_position(sample_bs[j]))
{
if (first_sample_track[j])
{
first_sample_track[j] = 0;
first_DTS_track[j] = samp->DTS;
}
dst_samp = gf_isom_sample_new();
dst_samp->CTS_Offset = samp->CTS_Offset;
dst_samp->DTS = samp->DTS - first_DTS_track[j];
dst_samp->IsRAP = samp->IsRAP;
gf_bs_get_content(sample_bs[j], &dst_samp->data, &dst_samp->dataLength);
if (j) //SVC
e = gf_isom_add_sample(file, track+j, di, dst_samp);
else
e = gf_isom_update_sample(file, track, i, dst_samp, 1);
if (e)
goto exit;
gf_isom_sample_del(&dst_samp);
dst_samp = NULL;
}
gf_bs_del(sample_bs[j]);
sample_bs[j] = NULL;
}
gf_free(sample_bs);
sample_bs = NULL;
gf_bs_del(bs);
bs = NULL;
gf_isom_sample_del(&samp);
samp = NULL;
}
/*add Editlist entry if DTS of the first sample is not zero*/
for (t = 0; t <= num_svc_track; t++)
{
if (first_DTS_track[t])
{
u32 media_ts, moov_ts, ts_offset;
u64 dur;
media_ts = gf_isom_get_media_timescale(file, t);
moov_ts = gf_isom_get_timescale(file);
ts_offset = (u32)(first_DTS_track[t]) * moov_ts / media_ts;
dur = gf_isom_get_media_duration(file, t) * moov_ts / media_ts;
gf_isom_set_edit(file, t, 0, ts_offset, 0, GF_ISOM_EDIT_EMPTY);
gf_isom_set_edit(file, t, ts_offset, dur, 0, GF_ISOM_EDIT_NORMAL);
}
}
/*if this is a merged file*/
if (!is_splitted)
{
/*a normal stream: delete SVC config*/
if (!gf_isom_has_svc_explicit(file, track))
{
gf_isom_svc_config_del(file, track, 1);
}
else
{
s32 shift=0;
for (i = 0; i < gf_list_count(svccfg->sequenceParameterSets); i++)
{
slc = (GF_NALUFFParam *)gf_list_get(svccfg->sequenceParameterSets, i);
sps_id = gf_avc_read_sps(slc->data, slc->size, avc_state, 0, NULL);
if (sps_id < 0) {
e = GF_NON_COMPLIANT_BITSTREAM;
goto exit;
}
nal_type = slc->data[0] & 0x1F;
if (nal_type == GF_AVC_NALU_SVC_SUBSEQ_PARAM)
{
gf_list_rem(svccfg->sequenceParameterSets, i);
gf_free(slc->data);
gf_free(slc);
i--;
}
}
for (j = 0; j < gf_list_count(svccfg->pictureParameterSets); j++)
{
slc = (GF_NALUFFParam *)gf_list_get(svccfg->pictureParameterSets, j);
pps_id = gf_avc_read_pps(slc->data, slc->size, avc_state);
if (pps_id < 0) {
e = GF_NON_COMPLIANT_BITSTREAM;
goto exit;
}
if (is_subseq_pps[j+shift])
{
gf_list_rem(svccfg->pictureParameterSets, j);
gf_free(slc->data);
gf_free(slc);
j--;
}
}
e = gf_isom_svc_config_update(file, track, 1, svccfg, 0);
if (e)
goto exit;
}
}
/*if this is as splitted file: delete this track*/
else
{
gf_isom_remove_track(file, track);
}
exit:
if (svccfg) gf_odf_avc_cfg_del(svccfg);
if (cfg) gf_odf_avc_cfg_del(cfg);
if (samp) gf_isom_sample_del(&samp);
if (dst_samp) gf_isom_sample_del(&dst_samp);
if (bs) gf_bs_del(bs);
if (sample_bs)
{
for (i = 0; i <= num_svc_track; i++)
gf_bs_del(sample_bs[i]);
gf_free(sample_bs);
}
if (sps_track) gf_free(sps_track);
if (sps) gf_free(sps);
if (pps) gf_free(pps);
if (first_sample_track) gf_free(first_sample_track);
if (first_DTS_track) gf_free(first_DTS_track);
if (buffer) gf_free(buffer);
if (is_subseq_pps) gf_free(is_subseq_pps);
if (avc_state) gf_free(avc_state);
gf_isom_set_nalu_extract_mode(file, track, cur_extract_mode);
return e;
#else
return GF_NOT_SUPPORTED;
#endif
}
/* Merge SVC layers*/
GF_EXPORT
GF_Err gf_media_merge_svc(GF_ISOFile *file, u32 track, Bool mergeAll)
{
GF_AVCConfig *svccfg, *cfg;
u32 merge_track, num_track, num_sample, size, i, t, di, max_size, nalu_size_length, ref_trackNum, ref_trackID, count, width, height, nb_EditList, media_ts, moov_ts;
GF_ISOSample *avc_samp, *samp, *dst_samp;
GF_BitStream *bs, *dst_bs;
GF_Err e;
char *buffer;
s32 *DQId;
u32 *list_track_sorted, *cur_sample, *max_sample;
u64 *DTS_offset;
u64 EditTime, SegmentDuration, MediaTime;
GF_ISOEditType EditMode;
u8 nal_type;
Bool first_sample;
u64 first_DTS, offset, dur;
GF_NALUFFParam *slc, *sl;
e = GF_OK;
di = 1;
max_size = 4096;
width = height = 0;
avc_samp = samp = dst_samp = NULL;
svccfg = cfg = NULL;
buffer = NULL;
bs = dst_bs = NULL;
DQId = NULL;
list_track_sorted = cur_sample = max_sample = NULL;
DTS_offset = NULL;
if (gf_isom_get_avc_svc_type(file, track, 1) == GF_ISOM_AVCTYPE_AVC_SVC)
goto exit;
num_track = gf_isom_get_track_count(file);
if (num_track == 1)
goto exit;
gf_isom_get_reference(file, track, GF_ISOM_REF_BASE, 1, &ref_trackNum);
ref_trackID = gf_isom_get_track_id(file, ref_trackNum);
if (!ref_trackID)
{
e = GF_ISOM_INVALID_MEDIA;
goto exit;
}
list_track_sorted = (u32 *) gf_malloc(num_track * sizeof(u32));
memset(list_track_sorted, 0, num_track * sizeof(u32));
DQId = (s32 *) gf_malloc(num_track * sizeof(s32));
memset(DQId, 0, num_track * sizeof(s32));
count = 0;
for (t = 1; t <= num_track; t++) {
u32 pos = 0;
s32 track_DQId = gf_get_DQId(file, t);
if (track_DQId < 0) {
e = GF_ISOM_INVALID_MEDIA;
goto exit;
}
if (!gf_isom_has_track_reference(file, t, GF_ISOM_REF_BASE, ref_trackID))
{
if (t != ref_trackNum) continue;
else if (!mergeAll) continue;
}
while ((pos < count ) && (DQId[pos] <= track_DQId))
pos++;
for (i = count; i > pos; i--)
{
list_track_sorted[i] = list_track_sorted[i-1];
DQId[i] = DQId[i-1];
}
list_track_sorted[pos] = t;
DQId[pos] = track_DQId;
count++;
}
merge_track = list_track_sorted[0];
gf_isom_set_track_enabled(file, merge_track, GF_TRUE);
/*rewrite svccfg*/
svccfg = gf_odf_avc_cfg_new();
svccfg->complete_representation = 1;
/*rewrite visual info*/
if (!mergeAll)
{
for (t = 0; t < count; t++)
gf_isom_get_visual_info(file, list_track_sorted[t], 1, &width, &height);
gf_isom_set_visual_info(file, merge_track, 1, width, height);
}
for (t = 0; t < count; t++)
{
cfg = gf_isom_svc_config_get(file, list_track_sorted[t], 1);
if (!cfg)
continue;
svccfg->configurationVersion = 1;
svccfg->chroma_bit_depth = cfg->chroma_bit_depth;
svccfg->chroma_format = cfg->chroma_format;
svccfg->luma_bit_depth = cfg->luma_bit_depth;
svccfg->profile_compatibility = cfg->profile_compatibility;
svccfg->AVCLevelIndication = cfg->AVCLevelIndication;
svccfg->AVCProfileIndication = cfg->AVCProfileIndication;
svccfg->nal_unit_size = cfg->nal_unit_size;
for (i = 0; i < gf_list_count(cfg->sequenceParameterSets); i++)
{
slc = (GF_NALUFFParam *)gf_list_get(cfg->sequenceParameterSets, i);
sl = (GF_NALUFFParam*)gf_malloc(sizeof(GF_NALUFFParam));
sl->id = slc->id;
sl->size = slc->size;
sl->data = (char*)gf_malloc(sizeof(char)*sl->size);
memcpy(sl->data, slc->data, sizeof(char)*sl->size);
gf_list_add(svccfg->sequenceParameterSets, sl);
}
for (i = 0; i < gf_list_count(cfg->pictureParameterSets); i++)
{
slc = (GF_NALUFFParam *)gf_list_get(cfg->pictureParameterSets, i);
sl = (GF_NALUFFParam*)gf_malloc(sizeof(GF_NALUFFParam));
sl->id = slc->id;
sl->size = slc->size;
sl->data = (char*)gf_malloc(sizeof(char)*sl->size);
memcpy(sl->data, slc->data, sizeof(char)*sl->size);
gf_list_add(svccfg->pictureParameterSets, sl);
}
if (mergeAll)
{
gf_isom_svc_config_update(file, merge_track, 1, svccfg, 1);
}
else
gf_isom_svc_config_update(file, merge_track, 1, svccfg, 0);
gf_odf_avc_cfg_del(cfg);
cfg = NULL;
}
cur_sample = (u32 *) gf_malloc(count * sizeof(u32));
max_sample = (u32 *) gf_malloc(count * sizeof(u32));
for (t = 0; t < count; t++)
{
cur_sample[t] = 1;
max_sample[t] = gf_isom_get_sample_count(file, list_track_sorted[t]);
}
DTS_offset = (u64 *) gf_malloc(count * sizeof(u64));
for (t = 0; t < count; t++) {
DTS_offset[t] = 0;
nb_EditList = gf_isom_get_edits_count(file, list_track_sorted[t]);
if (nb_EditList) {
media_ts = gf_isom_get_media_timescale(file, list_track_sorted[t]);
moov_ts = gf_isom_get_timescale(file);
for (i = 1; i <= nb_EditList; i++) {
e = gf_isom_get_edit(file, list_track_sorted[t], i, &EditTime, &SegmentDuration, &MediaTime, &EditMode);
if (e) goto exit;
if (!EditMode) {
DTS_offset[t] = SegmentDuration * media_ts / moov_ts;
}
}
}
}
num_sample = gf_isom_get_sample_count(file, ref_trackNum);
nalu_size_length = 8 * svccfg->nal_unit_size;
first_sample = 1;
first_DTS = 0;
buffer = (char*)gf_malloc(sizeof(char) * max_size);
for (t = 1; t <= num_track; t++)
gf_isom_set_nalu_extract_mode(file, t, GF_ISOM_NALU_EXTRACT_INSPECT);
for (i = 1; i <= num_sample; i++)
{
dst_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
/*add extractor if nessassary*/
if (!mergeAll)
{
u32 NALUnitHeader = 0;
u8 track_ref_index;
s8 sample_offset;
u32 data_offset;
u32 data_length;
gf_bs_write_int(dst_bs, 14, nalu_size_length); // extractor 's size = 14
NALUnitHeader |= 0x1F000000; // NALU type = 31
gf_bs_write_u32(dst_bs, NALUnitHeader);
track_ref_index = (u8) gf_isom_has_track_reference(file, merge_track, GF_ISOM_REF_SCAL, ref_trackID);
if (!track_ref_index)
{
e = GF_CORRUPTED_DATA;
goto exit;
}
gf_bs_write_u8(dst_bs, track_ref_index);
sample_offset = 0;
gf_bs_write_u8(dst_bs, sample_offset);
data_offset = 0;
gf_bs_write_u32(dst_bs, data_offset);
data_length = 0;
gf_bs_write_u32(dst_bs, data_length);
}
avc_samp = gf_isom_get_sample(file, ref_trackNum, i, &di);
if (!avc_samp) {
e = gf_isom_last_error(file);
goto exit;
}
for (t = 0; t < count; t++)
{
if (cur_sample[t] > max_sample[t])
continue;
samp = gf_isom_get_sample(file, list_track_sorted[t], cur_sample[t], &di);
if (!samp) {
e = gf_isom_last_error(file);
goto exit;
}
if ((samp->DTS + DTS_offset[t]) != avc_samp->DTS) {
gf_isom_sample_del(&samp);
samp = NULL;
continue;
}
bs = gf_bs_new(samp->data, samp->dataLength, GF_BITSTREAM_READ);
/*reset*/
memset(buffer, 0, sizeof(char) * max_size);
while (gf_bs_available(bs))
{
size = gf_bs_read_int(bs, nalu_size_length);
if (size>max_size) {
buffer = (char*)gf_realloc(buffer, sizeof(char)*size);
max_size = size;
}
gf_bs_read_data(bs, buffer, size);
nal_type = buffer[0] & 0x1F;
/*skip extractor*/
if (nal_type == GF_AVC_NALU_FF_EXTRACTOR)
continue;
/*copy to new bitstream*/
gf_bs_write_int(dst_bs, size, nalu_size_length);
gf_bs_write_data(dst_bs, buffer, size);
}
gf_bs_del(bs);
bs = NULL;
gf_isom_sample_del(&samp);
samp = NULL;
cur_sample[t]++;
}
/*add sapmle to track*/
if (gf_bs_get_position(dst_bs))
{
if (first_sample)
{
first_DTS = avc_samp->DTS;
first_sample = 0;
}
dst_samp = gf_isom_sample_new();
dst_samp->CTS_Offset = avc_samp->CTS_Offset;
dst_samp->DTS = avc_samp->DTS - first_DTS;
dst_samp->IsRAP = avc_samp->IsRAP;
gf_bs_get_content(dst_bs, &dst_samp->data, &dst_samp->dataLength);
e = gf_isom_update_sample(file, merge_track, i, dst_samp, 1);
if (e)
goto exit;
}
gf_isom_sample_del(&avc_samp);
avc_samp = NULL;
gf_bs_del(dst_bs);
dst_bs = NULL;
gf_isom_sample_del(&dst_samp);
dst_samp = NULL;
}
/*Add EditList if nessessary*/
if (!first_DTS)
{
media_ts = gf_isom_get_media_timescale(file, merge_track);
moov_ts = gf_isom_get_timescale(file);
offset = (u32)(first_DTS) * moov_ts / media_ts;
dur = gf_isom_get_media_duration(file, merge_track) * moov_ts / media_ts;
gf_isom_set_edit(file, merge_track, 0, offset, 0, GF_ISOM_EDIT_EMPTY);
gf_isom_set_edit(file, merge_track, offset, dur, 0, GF_ISOM_EDIT_NORMAL);
}
/*Delete SVC track(s) that references to ref_track*/
for (t = 1; t <= num_track; t++)
{
if (gf_isom_has_track_reference(file, t, GF_ISOM_REF_BASE, ref_trackID) && (t != merge_track))
{
gf_isom_remove_track(file, t);
num_track--; //we removed one track from file
t--;
}
}
exit:
if (avc_samp) gf_isom_sample_del(&avc_samp);
if (samp) gf_isom_sample_del(&samp);
if (dst_samp) gf_isom_sample_del(&dst_samp);
if (svccfg) gf_odf_avc_cfg_del(svccfg);
if (cfg) gf_odf_avc_cfg_del(cfg);
if (bs) gf_bs_del(bs);
if (dst_bs) gf_bs_del(dst_bs);
if (buffer) gf_free(buffer);
if (DQId) gf_free(DQId);
if (list_track_sorted) gf_free(list_track_sorted);
if (cur_sample) gf_free(cur_sample);
if (max_sample) gf_free(max_sample);
if (DTS_offset) gf_free(DTS_offset);
for (t = 1; t <= gf_isom_get_track_count(file); t++)
gf_isom_set_nalu_extract_mode(file, t, GF_ISOM_NALU_EXTRACT_DEFAULT);
return e;
}
#ifndef GPAC_DISABLE_AV_PARSERS
/* Split LHVC layers */
static GF_NALUFFParamArray *alloc_hevc_param_array(GF_HEVCConfig *hevc_cfg, u8 type)
{
GF_NALUFFParamArray *ar;
u32 i, count = hevc_cfg->param_array ? gf_list_count(hevc_cfg->param_array) : 0;
for (i=0; i<count; i++) {
ar = gf_list_get(hevc_cfg->param_array, i);
if (ar->type==type) return ar;
}
GF_SAFEALLOC(ar, GF_NALUFFParamArray);
if (!ar) return NULL;
ar->nalus = gf_list_new();
ar->type = type;
if (ar->type == GF_HEVC_NALU_VID_PARAM)
gf_list_insert(hevc_cfg->param_array, ar, 0);
else
gf_list_add(hevc_cfg->param_array, ar);
return ar;
}
typedef struct{
u8 layer_id_plus_one;
u8 min_temporal_id;
u8 max_temporal_id;
} LInfo;
typedef struct
{
u32 track_num;
u32 layer_id;
GF_HEVCConfig *lhvccfg;
GF_BitStream *bs;
u32 data_offset, data_size;
u32 temporal_id_sample, max_temporal_id_sample;
LInfo layers[64];
u32 width, height;
Bool has_samples;
Bool non_tsa_vcl;
} LHVCTrackInfo;
GF_EXPORT
GF_Err gf_media_filter_hevc(GF_ISOFile *file, u32 track, u8 max_temporal_id_plus_one, u8 max_layer_id_plus_one)
{
GF_HEVCConfig *hevccfg, *lhvccfg;
u32 i, count, cur_extract_mode;
char *nal_data=NULL;
u32 nal_alloc_size, nalu_size;
GF_Err e = GF_OK;
if (!max_temporal_id_plus_one && !max_layer_id_plus_one)
return GF_OK;
hevccfg = gf_isom_hevc_config_get(file, track, 1);
lhvccfg = gf_isom_lhvc_config_get(file, track, 1);
if (!hevccfg && !lhvccfg)
nalu_size = 4;
else
nalu_size = hevccfg ? hevccfg->nal_unit_size : lhvccfg->nal_unit_size;
cur_extract_mode = gf_isom_get_nalu_extract_mode(file, track);
gf_isom_set_nalu_extract_mode(file, track, GF_ISOM_NALU_EXTRACT_INSPECT);
nal_alloc_size = 10000;
nal_data = gf_malloc(sizeof(char) * nal_alloc_size);
if (hevccfg) {
count = gf_list_count(hevccfg->param_array);
for (i=0; i<count; i++) {
u32 j, count2;
GF_NALUFFParamArray *ar = (GF_NALUFFParamArray *)gf_list_get(hevccfg->param_array, i);
count2 = gf_list_count(ar->nalus);
for (j=0; j<count2; j++) {
GF_NALUFFParam *sl = (GF_NALUFFParam *)gf_list_get(ar->nalus, j);
//u8 nal_type = (sl->data[0] & 0x7E) >> 1;
u8 layer_id = ((sl->data[0] & 0x1) << 5) | (sl->data[1] >> 3);
u8 temporal_id_plus_one = sl->data[1] & 0x07;
if ((max_temporal_id_plus_one && (temporal_id_plus_one > max_temporal_id_plus_one)) || (max_layer_id_plus_one && (layer_id+1 > max_layer_id_plus_one))) {
gf_list_rem(ar->nalus, j);
j--;
count2--;
gf_free(sl->data);
gf_free(sl);
}
}
}
}
if (lhvccfg) {
count = gf_list_count(lhvccfg->param_array);
for (i=0; i<count; i++) {
u32 j, count2;
GF_NALUFFParamArray *ar = (GF_NALUFFParamArray *)gf_list_get(lhvccfg->param_array, i);
count2 = gf_list_count(ar->nalus);
for (j=0; j<count2; j++) {
GF_NALUFFParam *sl = (GF_NALUFFParam *)gf_list_get(ar->nalus, j);
//u8 nal_type = (sl->data[0] & 0x7E) >> 1;
u8 layer_id = ((sl->data[0] & 0x1) << 5) | (sl->data[1] >> 3);
u8 temporal_id_plus_one = sl->data[1] & 0x07;
if ((max_temporal_id_plus_one && (temporal_id_plus_one > max_temporal_id_plus_one)) || (max_layer_id_plus_one && (layer_id+1 > max_layer_id_plus_one))) {
gf_list_rem(ar->nalus, j);
j--;
count2--;
gf_free(sl->data);
gf_free(sl);
}
}
}
}
//parse all samples
count = gf_isom_get_sample_count(file, track);
for (i=0; i<count; i++) {
GF_BitStream *bs, *dst_bs;
u32 di;
GF_ISOSample *sample;
sample = gf_isom_get_sample(file, track, i+1, &di);
bs = gf_bs_new(sample->data, sample->dataLength, GF_BITSTREAM_READ);
dst_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
while (gf_bs_available(bs)) {
u32 size = gf_bs_read_int(bs, nalu_size*8);
u8 fzero = gf_bs_read_int(bs, 1);
u8 nal_type = gf_bs_read_int(bs, 6);
u8 layer_id = gf_bs_read_int(bs, 6);
u8 temporal_id_plus_one = gf_bs_read_int(bs, 3);
size -= 2;
if ((max_temporal_id_plus_one && (temporal_id_plus_one > max_temporal_id_plus_one))
|| (max_layer_id_plus_one && (layer_id+1 > max_layer_id_plus_one))
) {
gf_bs_skip_bytes(bs, size);
continue;
}
if (size>nal_alloc_size) {
nal_alloc_size = size;
nal_data = (char *)gf_realloc(nal_data, nal_alloc_size);
}
gf_bs_read_data(bs, nal_data, size);
gf_bs_write_int(dst_bs, size+2, nalu_size*8);
gf_bs_write_int(dst_bs, fzero, 1);
gf_bs_write_int(dst_bs, nal_type, 6);
gf_bs_write_int(dst_bs, layer_id, 6);
gf_bs_write_int(dst_bs, temporal_id_plus_one, 3);
gf_bs_write_data(dst_bs, nal_data, size);
}
gf_bs_del(bs);
gf_free(sample->data);
sample->data = NULL;
sample->dataLength = 0;
gf_bs_get_content(dst_bs, &sample->data, &sample->dataLength);
e = gf_isom_update_sample(file, track, i+1, sample, GF_TRUE);
gf_bs_del(dst_bs);
gf_isom_sample_del(&sample);
if (e)
goto exit;
}
exit:
if (lhvccfg) gf_odf_hevc_cfg_del(lhvccfg);
if (hevccfg) gf_odf_hevc_cfg_del(hevccfg);
gf_isom_set_nalu_extract_mode(file, track, cur_extract_mode);
if (nal_data) gf_free(nal_data);
return e;
}
GF_EXPORT
GF_Err gf_media_split_lhvc(GF_ISOFile *file, u32 track, Bool for_temporal_sublayers, Bool splitAll, GF_LHVCExtractoreMode extractor_mode)
{
#if !defined(GPAC_DISABLE_AV_PARSERS)
GF_HEVCConfig *hevccfg, *lhvccfg;
u32 sample_num, count, cur_extract_mode, j, k, max_layer_id;
char *nal_data=NULL;
u32 nal_alloc_size;
u32 nal_unit_size=0;
Bool single_layer_per_track=GF_TRUE;
GF_Err e = GF_OK;
HEVCState *hvc_state;
GF_SAFEALLOC(hvc_state, HEVCState);
if (!hvc_state) return GF_OUT_OF_MEM;
hevccfg = gf_isom_hevc_config_get(file, track, 1);
lhvccfg = gf_isom_lhvc_config_get(file, track, 1);
if (!lhvccfg && !for_temporal_sublayers) {
if (hevccfg) gf_odf_hevc_cfg_del(hevccfg);
gf_free(hvc_state);
return GF_OK;
}
else if (for_temporal_sublayers) {
if (lhvccfg) {
if (hevccfg) gf_odf_hevc_cfg_del(hevccfg);
gf_odf_hevc_cfg_del(lhvccfg);
gf_free(hvc_state);
return GF_NOT_SUPPORTED;
}
if (!hevccfg) {
gf_free(hvc_state);
return GF_NOT_SUPPORTED;
}
if (hevccfg->numTemporalLayers<=1) {
gf_odf_hevc_cfg_del(hevccfg);
gf_free(hvc_state);
return GF_OK;
}
}
cur_extract_mode = gf_isom_get_nalu_extract_mode(file, track);
gf_isom_set_nalu_extract_mode(file, track, GF_ISOM_NALU_EXTRACT_INSPECT);
LHVCTrackInfo *sti;
sti = gf_malloc(sizeof(LHVCTrackInfo)*64);
if (!sti) return GF_OUT_OF_MEM;
memset(sti, 0, sizeof(LHVCTrackInfo)*64);
sti[0].track_num = track;
sti[0].has_samples=GF_TRUE;
max_layer_id = 0;
nal_unit_size = lhvccfg ? lhvccfg->nal_unit_size : hevccfg->nal_unit_size;
if (!for_temporal_sublayers) {
u32 i, pass, base_layer_pass = GF_TRUE;
GF_HEVCConfig *cur_cfg = hevccfg;
reparse:
//split all SPS/PPS/VPS from lhvccfg
for (pass=0; pass<3; pass++) {
count = gf_list_count(cur_cfg->param_array);
for (i=0; i<count; i++) {
u32 count2;
GF_NALUFFParamArray *s_ar;
GF_NALUFFParamArray *ar = gf_list_get(cur_cfg->param_array, i);
if ((pass==0) && (ar->type!=GF_HEVC_NALU_VID_PARAM)) continue;
else if ((pass==1) && (ar->type!=GF_HEVC_NALU_SEQ_PARAM)) continue;
else if ((pass==2) && (ar->type!=GF_HEVC_NALU_PIC_PARAM)) continue;
count2 = gf_list_count(ar->nalus);
for (j=0; j<count2; j++) {
GF_NALUFFParam *sl = gf_list_get(ar->nalus, j);
// u8 nal_type = (sl->data[0] & 0x7E) >> 1;
u8 layer_id = ((sl->data[0] & 0x1) << 5) | (sl->data[1] >> 3);
if (ar->type==GF_HEVC_NALU_SEQ_PARAM) {
u32 lw, lh;
s32 idx = gf_hevc_get_sps_info_with_state(hvc_state, sl->data, sl->size, NULL, &lw, &lh, NULL, NULL);
if (idx>=0) {
if (lw > sti[layer_id].width) sti[layer_id].width = lw;
if (lh > sti[layer_id].height) sti[layer_id].height = lh;
}
} else if (ar->type==GF_HEVC_NALU_PIC_PARAM) {
gf_hevc_read_pps(sl->data, sl->size, hvc_state);
} else if (ar->type==GF_HEVC_NALU_VID_PARAM) {
gf_hevc_read_vps(sl->data, sl->size, hvc_state);
}
//don't touch base layer
if (!layer_id) {
gf_assert(base_layer_pass);
continue;
}
if (!splitAll) layer_id = 1;
if (max_layer_id < layer_id)
max_layer_id = layer_id;
if (!sti[layer_id].lhvccfg) {
GF_List *backup_list;
sti[layer_id].lhvccfg = gf_odf_hevc_cfg_new();
backup_list = sti[layer_id].lhvccfg->param_array;
memcpy(sti[layer_id].lhvccfg , lhvccfg ? lhvccfg : hevccfg, sizeof(GF_HEVCConfig));
sti[layer_id].lhvccfg->param_array = backup_list;
sti[layer_id].lhvccfg->is_lhvc = 1;
sti[layer_id].lhvccfg->complete_representation = 1;
}
s_ar = alloc_hevc_param_array(sti[layer_id].lhvccfg, ar->type);
gf_list_add(s_ar->nalus, sl);
gf_list_rem(ar->nalus, j);
j--;
count2--;
}
}
}
if (base_layer_pass) {
base_layer_pass = GF_FALSE;
cur_cfg = lhvccfg;
goto reparse;
}
} else {
gf_isom_set_cts_packing(file, track, GF_TRUE);
}
//CLARIFY whether this is correct: we duplicate all VPS in the enhancement layer ...
//we do this because if we split the tracks some info for setting up the enhancement layer
//is in the VPS
if (extractor_mode != GF_LHVC_EXTRACTORS_ON) {
u32 i;
count = gf_list_count(hevccfg->param_array);
for (i=0; i<count; i++) {
u32 count2;
GF_NALUFFParamArray *s_ar;
GF_NALUFFParamArray *ar = gf_list_get(hevccfg->param_array, i);
if (ar->type != GF_HEVC_NALU_VID_PARAM) continue;
count2 = gf_list_count(ar->nalus);
for (j=0; j<count2; j++) {
GF_NALUFFParam *sl = gf_list_get(ar->nalus, j);
u8 layer_id = ((sl->data[0] & 0x1) << 5) | (sl->data[1] >> 3);
if (layer_id) continue;
for (k=0; k <= max_layer_id; k++) {
GF_NALUFFParam *sl2;
if (!sti[k].lhvccfg) continue;
s_ar = alloc_hevc_param_array(sti[k].lhvccfg, ar->type);
s_ar->array_completeness = ar->array_completeness;
GF_SAFEALLOC(sl2, GF_NALUFFParam);
if (!sl2) break;
sl2->data = gf_malloc(sl->size);
if (!sl2->data) {
gf_free(sl2);
break;
}
memcpy(sl2->data, sl->data, sl->size);
sl2->id = sl->id;
sl2->size = sl->size;
gf_list_add(s_ar->nalus, sl2);
}
}
}
}
//update lhvc config
if (for_temporal_sublayers) {
e = gf_isom_lhvc_config_update(file, track, 1, NULL, GF_ISOM_LEHVC_WITH_BASE_BACKWARD);
} else {
e = gf_isom_lhvc_config_update(file, track, 1, NULL, GF_ISOM_LEHVC_WITH_BASE_BACKWARD);
}
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[HEVC] Failed to update HEVC/LHVC config\n"));
goto exit;
}
//purge all linf sample groups
gf_isom_remove_sample_group(file, track, GF_ISOM_SAMPLE_GROUP_LINF);
nal_alloc_size = 10000;
nal_data = gf_malloc(sizeof(char) * nal_alloc_size);
//parse all samples
count = gf_isom_get_sample_count(file, track);
for (sample_num=0; sample_num<count; sample_num++) {
GF_BitStream *bs;
u32 di;
GF_ISOSample *sample;
Bool is_irap;
GF_ISOSampleRollType roll_type;
s32 roll_distance;
u8 cur_max_layer_id = 0;
sample = gf_isom_get_sample(file, track, sample_num+1, &di);
gf_isom_get_sample_rap_roll_info(file, track, sample_num+1, &is_irap, &roll_type, &roll_distance);
bs = gf_bs_new(sample->data, sample->dataLength, GF_BITSTREAM_READ);
while (gf_bs_available(bs)) {
u8 orig_layer_id, nal_size;
u32 size = gf_bs_read_int(bs, nal_unit_size*8);
u32 offset = (u32) gf_bs_get_position(bs);
u8 fzero = gf_bs_read_int(bs, 1);
u8 nal_type = gf_bs_read_int(bs, 6);
u8 layer_id = orig_layer_id = gf_bs_read_int(bs, 6);
u8 temporal_id = gf_bs_read_int(bs, 3);
if (for_temporal_sublayers) {
u32 tid = temporal_id-1;
if (tid && !sti[tid].layer_id) {
sti[tid].layer_id=tid;
}
layer_id = tid;
if ((nal_type <= GF_HEVC_NALU_SLICE_CRA)
&& (nal_type != GF_HEVC_NALU_SLICE_TSA_N)
&& (nal_type != GF_HEVC_NALU_SLICE_TSA_R))
sti[layer_id].non_tsa_vcl = GF_TRUE;
} else {
if (layer_id && !sti[layer_id].layer_id) {
sti[layer_id].layer_id=layer_id;
}
}
if (!splitAll && layer_id) layer_id = 1;
if (cur_max_layer_id < layer_id) {
cur_max_layer_id = layer_id;
}
nal_size = size;
if (!sti[layer_id].bs)
sti[layer_id].bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
gf_bs_write_int(sti[layer_id].bs, size, nal_unit_size*8);
gf_bs_write_int(sti[layer_id].bs, fzero, 1);
gf_bs_write_int(sti[layer_id].bs, nal_type, 6);
gf_bs_write_int(sti[layer_id].bs, orig_layer_id, 6);
gf_bs_write_int(sti[layer_id].bs, temporal_id, 3);
size -= 2;
sti[layer_id].layers[layer_id].layer_id_plus_one = layer_id+1;
sti[layer_id].temporal_id_sample = temporal_id;
if (!sti[layer_id].layers[layer_id].min_temporal_id || (sti[layer_id].layers[layer_id].min_temporal_id > temporal_id)) {
sti[layer_id].layers[layer_id].min_temporal_id = temporal_id;
}
if (!sti[layer_id].layers[layer_id].max_temporal_id || (sti[layer_id].layers[layer_id].max_temporal_id < temporal_id)) {
sti[layer_id].layers[layer_id].max_temporal_id = temporal_id;
}
if (!sti[layer_id].max_temporal_id_sample || (sti[layer_id].max_temporal_id_sample < temporal_id)) {
sti[layer_id].max_temporal_id_sample = temporal_id;
}
if (! for_temporal_sublayers) {
if (nal_type==GF_HEVC_NALU_SEQ_PARAM) {
u32 lw, lh;
s32 idx = gf_hevc_get_sps_info_with_state(hvc_state, sample->data + offset, nal_size, NULL, &lw, &lh, NULL, NULL);
if (idx>=0) {
if (lw > sti[layer_id].width) sti[layer_id].width = lw;
if (lh > sti[layer_id].height) sti[layer_id].height = lh;
}
} else if (nal_type==GF_HEVC_NALU_PIC_PARAM) {
gf_hevc_read_pps(sample->data + offset, nal_size, hvc_state);
} else if (nal_type==GF_HEVC_NALU_VID_PARAM) {
gf_hevc_read_vps(sample->data + offset, nal_size, hvc_state);
}
}
if (size>nal_alloc_size) {
nal_alloc_size = size;
nal_data = gf_realloc(nal_data, nal_alloc_size);
}
gf_bs_read_data(bs, nal_data, size);
gf_bs_write_data(sti[layer_id].bs, nal_data, size);
}
gf_bs_del(bs);
if (cur_max_layer_id>max_layer_id) {
max_layer_id = cur_max_layer_id;
}
if (for_temporal_sublayers && hevccfg->numTemporalLayers>max_layer_id+1) {
max_layer_id = hevccfg->numTemporalLayers-1;
}
gf_free(sample->data);
sample->data = NULL;
sample->dataLength = 0;
//reset all samples on all layers found - we may have layers not present in this sample, we still need to process these layers when extractors are used
for (j=0; j<=max_layer_id; j++) {
if (!for_temporal_sublayers && ! sti[j].bs) {
if (!sti[j].track_num || (extractor_mode != GF_LHVC_EXTRACTORS_ON) ) {
sti[j].data_offset = sti[j].data_size = 0;
continue;
}
}
//clone track
if (! sti[j].track_num) {
u32 track_id = gf_isom_get_track_id(file, track);
e = gf_isom_clone_track(file, track, file, 0, &sti[j].track_num);
if (e) goto exit;
if (! for_temporal_sublayers) {
//happens for inband param
if (!sti[j].lhvccfg) {
GF_List *backup_list;
sti[j].lhvccfg = gf_odf_hevc_cfg_new();
backup_list = sti[j].lhvccfg->param_array;
memcpy(sti[j].lhvccfg , lhvccfg ? lhvccfg : hevccfg, sizeof(GF_HEVCConfig));
sti[j].lhvccfg->param_array = backup_list;
sti[j].lhvccfg->is_lhvc = 1;
sti[j].lhvccfg->complete_representation = 1;
}
e = gf_isom_lhvc_config_update(file, sti[j].track_num, 1, sti[j].lhvccfg, (extractor_mode == GF_LHVC_EXTRACTORS_ON) ? GF_ISOM_LEHVC_WITH_BASE : GF_ISOM_LEHVC_ONLY);
if (e) goto exit;
if (extractor_mode == GF_LHVC_EXTRACTORS_OFF_FORCE_INBAND)
gf_isom_lhvc_force_inband_config(file, sti[j].track_num, 1);
} else {
e = gf_isom_lhvc_config_update(file, sti[j].track_num, 1, NULL, GF_ISOM_LEHVC_WITH_BASE);
if (e) goto exit;
}
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[HEVC] Failed to update HEVC/LHVC config\n"));
goto exit;
}
gf_isom_set_track_reference(file, sti[j].track_num, GF_ISOM_REF_BASE, track_id);
//for an L-HEVC bitstream: only base track carries the 'oinf' sample group, other track have a track reference of type 'oref' to base track
e = gf_isom_remove_sample_group(file, sti[j].track_num, GF_ISOM_SAMPLE_GROUP_OINF);
if (e) goto exit;
//purge all linf sample groups
gf_isom_remove_sample_group(file, sti[j].track_num, GF_ISOM_SAMPLE_GROUP_LINF);
gf_isom_set_track_reference(file, sti[j].track_num, GF_ISOM_REF_OREF, track_id);
gf_isom_set_nalu_extract_mode(file, sti[j].track_num, GF_ISOM_NALU_EXTRACT_INSPECT);
//get lower layer
if (extractor_mode == GF_LHVC_EXTRACTORS_ON) {
for (k=j; k>0; k--) {
if (sti[k-1].track_num) {
u32 track_id_r = gf_isom_get_track_id(file, sti[k-1].track_num);
gf_isom_set_track_reference(file, sti[j].track_num, GF_ISOM_REF_SCAL, track_id_r);
}
}
}
if (!for_temporal_sublayers)
gf_isom_set_visual_info(file, sti[j].track_num, 1, sti[j].width, sti[j].height);
} else {
if (!for_temporal_sublayers)
gf_isom_set_visual_info(file, sti[j].track_num, 1, sti[j].width, sti[j].height);
}
if (j && (extractor_mode == GF_LHVC_EXTRACTORS_ON)) {
GF_BitStream *xbs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
//get all lower layers
for (k=0; k<j; k++) {
u8 trefidx, tid;
if (!sti[k].data_size)
continue;
//extractor size 5
gf_bs_write_int(xbs, 2*nal_unit_size + 5, 8*nal_unit_size);
gf_bs_write_int(xbs, 0, 1);
gf_bs_write_int(xbs, GF_HEVC_NALU_FF_EXTRACTOR, 6); //extractor
gf_bs_write_int(xbs, k, 6);
gf_bs_write_int(xbs, sti[k].max_temporal_id_sample, 3);
gf_bs_write_u8(xbs, 0); //constructor type 0
//set ref track index
trefidx = (u8) gf_isom_has_track_reference(file, sti[j].track_num, GF_ISOM_REF_SCAL, gf_isom_get_track_id(file, sti[k].track_num) );
gf_bs_write_int(xbs, trefidx, 8);
// no sample offset
gf_bs_write_int(xbs, 0, 8);
// data offset: we start from beginning of the sample data, not the extractor
gf_bs_write_int(xbs, sti[k].data_offset, 8*nal_unit_size);
gf_bs_write_int(xbs, sti[k].data_size, 8*nal_unit_size);
tid = sti[k].temporal_id_sample;
sti[j].layers[k].layer_id_plus_one = sti[k].layer_id+1;
if (!sti[j].layers[k].min_temporal_id || (sti[j].layers[k].min_temporal_id > tid)) {
sti[j].layers[k].min_temporal_id = tid;
}
if (!sti[j].layers[k].max_temporal_id || (sti[j].layers[k].max_temporal_id < tid)) {
sti[j].layers[k].max_temporal_id = tid;
}
}
gf_bs_get_content(xbs, &sample->data, &sample->dataLength);
gf_bs_del(xbs);
//we wrote all our references, store offset for upper layers
sti[j].data_offset = sample->dataLength;
e = gf_isom_add_sample(file, sti[j].track_num, 1, sample);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[HEVC] Failed to add HEVC/LHVC sample to track %d\n", sti[j].track_num));
goto exit;
}
gf_free(sample->data);
sample->data = NULL;
//get real content, remember its size and add it to the new bs
if (sti[j].bs) {
gf_bs_get_content(sti[j].bs, &sample->data, &sample->dataLength);
e = gf_isom_append_sample_data(file, sti[j].track_num, sample->data, sample->dataLength);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[HEVC] Failed to append HEVC/LHVC data to sample (track %d)\n", sti[j].track_num));
goto exit;
}
}
}
//get sample content
else if (sti[j].bs) {
//add empty sample at DTS 0
if ( ! sti[j].has_samples) {
if (sample->DTS) {
GF_ISOSample s;
memset(&s, 0, sizeof(GF_ISOSample));
gf_isom_add_sample(file, sti[j].track_num, 1, &s);
}
sti[j].has_samples=GF_TRUE;
}
gf_bs_get_content(sti[j].bs, &sample->data, &sample->dataLength);
sti[j].data_offset = 0;
sti[j].data_size = sample->dataLength;
//add sample
if (j) {
GF_ISOSAPType rap = sample->IsRAP;
if (for_temporal_sublayers && !sti[j].non_tsa_vcl)
sample->IsRAP = RAP;
e = gf_isom_add_sample(file, sti[j].track_num, 1, sample);
sample->IsRAP = rap;
} else {
e = gf_isom_update_sample(file, sti[j].track_num, sample_num+1, sample, 1);
}
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[HEVC] Failed to %s HEVC/LHVC sample (track %d, base sample num %d)\n", j ? "add" : "update", sti[j].track_num, sample_num+1));
goto exit;
}
}
//no data left in sample, update
else if (!j) {
e = gf_isom_remove_sample(file, sti[j].track_num, sample_num+1);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[HEVC] Failed to remove HEVC/LHVC sample (track %d)\n", sti[j].track_num));
goto exit;
}
sample_num--;
count--;
}
gf_bs_del(sti[j].bs);
sti[j].bs = NULL;
if (sample->IsRAP>SAP_TYPE_1) {
u32 sample_idx = gf_isom_get_sample_count(file, sti[j].track_num);
if (is_irap) {
gf_isom_set_sample_rap_group(file, sti[j].track_num, sample_idx, GF_TRUE, 0);
}
else if (roll_type) {
gf_isom_set_sample_roll_group(file, sti[j].track_num, sample_idx, GF_ISOM_SAMPLE_ROLL, (s16) roll_distance);
}
}
if (sample->data) {
gf_free(sample->data);
sample->data = NULL;
}
sample->dataLength = 0;
}
gf_isom_sample_del(&sample);
//reset all scalable info
for (j=0; j<=max_layer_id; j++) {
sti[j].max_temporal_id_sample = 0;
sti[j].temporal_id_sample = 0;
sti[j].data_size = 0;
sti[j].non_tsa_vcl = GF_FALSE;
}
}
exit:
//reset all scalable info
for (j=0; j<=max_layer_id; j++) {
GF_BitStream *bs;
u32 data_size;
u8 *data=NULL;
if (sti[j].lhvccfg) gf_odf_hevc_cfg_del(sti[j].lhvccfg);
//set linf group
bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
gf_bs_write_int(bs, 0, 2);
count = 0;
for (k=0; k<=max_layer_id; k++) {
if (sti[j].layers[k].layer_id_plus_one) count++;
}
gf_bs_write_int(bs, count, 6);
if (count>1)
single_layer_per_track = GF_FALSE;
for (k=0; k<=max_layer_id; k++) {
if (! sti[j].layers[k].layer_id_plus_one) continue;
gf_bs_write_int(bs, 0, 4);
gf_bs_write_int(bs, sti[j].layers[k].layer_id_plus_one - 1, 6);
gf_bs_write_int(bs, sti[j].layers[k].min_temporal_id, 3);
gf_bs_write_int(bs, sti[j].layers[k].max_temporal_id, 3);
gf_bs_write_int(bs, 0, 1);
//track carries the NALUs
if (k==j) {
gf_bs_write_int(bs, 0xFF, 7);
}
//otherwise referenced through extractors, not present natively
else {
gf_bs_write_int(bs, 0, 7);
}
}
gf_bs_get_content(bs, &data, &data_size);
gf_bs_del(bs);
gf_isom_add_sample_group_info(file, sti[j].track_num, GF_ISOM_SAMPLE_GROUP_LINF, data, data_size, GF_TRUE, &count);
gf_free(data);
}
gf_isom_set_nalu_extract_mode(file, track, cur_extract_mode);
if (extractor_mode == GF_LHVC_EXTRACTORS_ON) {
gf_isom_modify_alternate_brand(file, GF_ISOM_BRAND_HVCE, GF_TRUE);
gf_isom_modify_alternate_brand(file, GF_ISOM_BRAND_HVCI, GF_FALSE);
}
//add hvci brand only if single layer per track
else if (single_layer_per_track) {
gf_isom_modify_alternate_brand(file, GF_ISOM_BRAND_HVCI, GF_TRUE);
gf_isom_modify_alternate_brand(file, GF_ISOM_BRAND_HVCE, GF_FALSE);
}
if (lhvccfg) gf_odf_hevc_cfg_del(lhvccfg);
if (hevccfg) gf_odf_hevc_cfg_del(hevccfg);
if (nal_data) gf_free(nal_data);
gf_free(hvc_state);
gf_free(sti);
return e;
#else
return GF_NOT_SUPPORTED;
#endif
}
#endif ///GPAC_DISABLE_AV_PARSERS
GF_EXPORT
GF_Err gf_media_change_pl(GF_ISOFile *file, u32 track, u32 profile, u32 compat, u32 level)
{
u32 i, count, stype;
GF_Err e;
GF_AVCConfig *avcc;
stype = gf_isom_get_media_subtype(file, track, 1);
switch (stype) {
case GF_ISOM_SUBTYPE_AVC_H264:
case GF_ISOM_SUBTYPE_AVC2_H264:
case GF_ISOM_SUBTYPE_AVC3_H264:
case GF_ISOM_SUBTYPE_AVC4_H264:
break;
default:
return GF_OK;
}
avcc = gf_isom_avc_config_get(file, track, 1);
if (!avcc)
return GF_NON_COMPLIANT_BITSTREAM;
if (level) avcc->AVCLevelIndication = level;
if (compat) avcc->profile_compatibility = compat;
if (profile) avcc->AVCProfileIndication = profile;
count = gf_list_count(avcc->sequenceParameterSets);
for (i=0; i<count; i++) {
GF_NALUFFParam *slc = gf_list_get(avcc->sequenceParameterSets, i);
if (profile) slc->data[1] = profile;
if (level) slc->data[3] = level;
}
e = gf_isom_avc_config_update(file, track, 1, avcc);
gf_odf_avc_cfg_del(avcc);
return e;
}
#endif // GPAC_DISABLE_MEDIA_IMPORT
#if !defined(GPAC_DISABLE_AV_PARSERS)
u32 hevc_get_tile_id(HEVCState *hevc, u32 *tile_x, u32 *tile_y, u32 *tile_width, u32 *tile_height)
{
HEVCSliceInfo *si = &hevc->s_info;
u32 i, tbX, tbY, PicWidthInCtbsY, PicHeightInCtbsY, tileX, tileY, oX, oY, val;
PicWidthInCtbsY = si->sps->width / si->sps->max_CU_width;
if (PicWidthInCtbsY * si->sps->max_CU_width < si->sps->width) PicWidthInCtbsY++;
PicHeightInCtbsY = si->sps->height / si->sps->max_CU_width;
if (PicHeightInCtbsY * si->sps->max_CU_width < si->sps->height) PicHeightInCtbsY++;
tbX = si->slice_segment_address % PicWidthInCtbsY;
tbY = si->slice_segment_address / PicWidthInCtbsY;
tileX = tileY = 0;
oX = oY = 0;
for (i=0; i < si->pps->num_tile_columns; i++) {
if (si->pps->uniform_spacing_flag) {
val = (i+1)*PicWidthInCtbsY / si->pps->num_tile_columns - (i)*PicWidthInCtbsY / si->pps->num_tile_columns;
} else {
if (i<si->pps->num_tile_columns-1) {
val = si->pps->column_width[i];
} else {
val = (PicWidthInCtbsY - si->pps->column_width[i-1]);
}
}
*tile_x = oX;
*tile_width = val;
if (oX >= tbX) break;
oX += val;
tileX++;
}
for (i=0; i<si->pps->num_tile_rows; i++) {
if (si->pps->uniform_spacing_flag) {
val = (i+1)*PicHeightInCtbsY / si->pps->num_tile_rows - (i)*PicHeightInCtbsY / si->pps->num_tile_rows;
} else {
if (i<si->pps->num_tile_rows-1) {
val = si->pps->row_height[i];
} else if (i) {
val = (PicHeightInCtbsY - si->pps->row_height[i-1]);
} else {
val = PicHeightInCtbsY;
}
}
*tile_y = oY;
*tile_height = val;
if (oY >= tbY) break;
oY += val;
tileY++;
}
*tile_x = *tile_x * si->sps->max_CU_width;
*tile_y = *tile_y * si->sps->max_CU_width;
*tile_width = *tile_width * si->sps->max_CU_width;
*tile_height = *tile_height * si->sps->max_CU_width;
if (*tile_x + *tile_width > si->sps->width)
*tile_width = si->sps->width - *tile_x;
if (*tile_y + *tile_height > si->sps->height)
*tile_height = si->sps->height - *tile_y;
return tileX + tileY * si->pps->num_tile_columns;
}
#ifndef GPAC_DISABLE_MEDIA_IMPORT
typedef struct
{
u32 track, track_id, sample_count;
u32 tx, ty, tw, th;
u32 data_offset;
GF_BitStream *sample_data;
u32 nb_nalus_in_sample;
Bool all_intra;
} HEVCTileImport;
static void hevc_add_trif(GF_ISOFile *file, u32 track, u32 id, Bool full_picture, u32 independent, Bool filtering_disable, u32 tx, u32 ty, u32 tw, u32 th, Bool is_default)
{
char data[11];
u32 di, data_size=7;
GF_BitStream *bs;
//avoid gcc warnings
memset(data, 0, 11);
//write TRIF sample group description
bs = gf_bs_new((const char*)data, 11, GF_BITSTREAM_WRITE);
gf_bs_write_u16(bs, id); //groupID
gf_bs_write_int(bs, 1, 1); //tile Region flag always true for us
gf_bs_write_int(bs, independent, 2); //independentIDC: set to 1 (motion-constrained tiles but not all tiles RAP)
gf_bs_write_int(bs, full_picture, 1);//full picture: false since we don't do L-HEVC tiles
gf_bs_write_int(bs, filtering_disable, 1); //filtering disabled: set to 1 (always true on our bitstreams for now) - Check xPS to be sure ...
gf_bs_write_int(bs, 0, 1);//has dependency list: false since we don't do L-HEVC tiles
gf_bs_write_int(bs, 0, 2); //reserved
if (!full_picture) {
gf_bs_write_u16(bs, tx);
gf_bs_write_u16(bs, ty);
data_size+=4;
}
gf_bs_write_u16(bs, tw);
gf_bs_write_u16(bs, th);
gf_bs_del(bs);
gf_isom_add_sample_group_info(file, track, GF_ISOM_SAMPLE_GROUP_TRIF, data, data_size, is_default, &di);
}
GF_EXPORT
GF_Err gf_media_split_hevc_tiles(GF_ISOFile *file, u32 signal_mode)
{
#if defined(GPAC_DISABLE_AV_PARSERS)
return GF_NOT_SUPPORTED;
#else
u32 i, j, cur_tile, count, stype, track, nb_tiles, di, nalu_size_length, tx, ty, tw, th;
s32 pps_idx=-1, sps_idx=-1, ret;
GF_Err e = GF_OK;
HEVCState *hvc_state;
HEVCTileImport *tiles;
GF_HEVCConfig *hvcc;
Bool filter_disabled=GF_TRUE;
track = 0;
for (i=0; i<gf_isom_get_track_count(file); i++) {
stype = gf_isom_get_media_subtype(file, i+1, 1);
switch (stype) {
case GF_ISOM_SUBTYPE_HVC1:
case GF_ISOM_SUBTYPE_HEV1:
case GF_ISOM_SUBTYPE_HVC2:
case GF_ISOM_SUBTYPE_HEV2:
if (track) return GF_NOT_SUPPORTED;
track = i+1;
break;
default:
break;
}
}
if (!track) return GF_NOT_SUPPORTED;
hvcc = gf_isom_hevc_config_get(file, track, 1);
nalu_size_length = hvcc->nal_unit_size;
GF_SAFEALLOC(hvc_state, HEVCState);
if (!hvc_state) return GF_OUT_OF_MEM;
count = gf_list_count(hvcc->param_array);
for (i=0; i<count; i++) {
GF_NALUFFParamArray *ar = gf_list_get(hvcc->param_array, i);
for (j=0; j < gf_list_count(ar->nalus); j++) {
GF_NALUFFParam *sl = gf_list_get(ar->nalus, j);
if (!sl) continue;
switch (ar->type) {
case GF_HEVC_NALU_PIC_PARAM:
pps_idx = gf_hevc_read_pps(sl->data, sl->size, hvc_state);
break;
case GF_HEVC_NALU_SEQ_PARAM:
sps_idx = gf_hevc_read_sps(sl->data, sl->size, hvc_state);
break;
case GF_HEVC_NALU_VID_PARAM:
gf_hevc_read_vps(sl->data, sl->size, hvc_state);
break;
}
}
}
gf_isom_hevc_set_tile_config(file, track, 1, hvcc, GF_TRUE);
gf_odf_hevc_cfg_del(hvcc);
//if params sets are inband, get first sps/pps
i=0;
while ((pps_idx==-1) || (sps_idx==-1)) {
GF_ISOSample *sample = gf_isom_get_sample(file, track, i+1, &di);
char *data = sample->data;
u32 size = sample->dataLength;
while (size) {
u8 temporal_id, layer_id;
u8 nal_type = 0;
u32 nalu_size = 0;
for (j=0; j<nalu_size_length; j++) {
nalu_size = (nalu_size<<8) + data[j];
}
gf_hevc_parse_nalu(data + nalu_size_length, nalu_size, hvc_state, &nal_type, &temporal_id, &layer_id);
switch (nal_type) {
case GF_HEVC_NALU_PIC_PARAM:
pps_idx = gf_hevc_read_pps((char *) data+nalu_size_length, nalu_size, hvc_state);
break;
case GF_HEVC_NALU_SEQ_PARAM:
sps_idx = gf_hevc_read_sps((char *) data+nalu_size_length, nalu_size, hvc_state);
break;
case GF_HEVC_NALU_VID_PARAM:
gf_hevc_read_vps((char *) data+nalu_size_length, nalu_size, hvc_state);
break;
}
data += nalu_size + nalu_size_length;
size -= nalu_size + nalu_size_length;
}
gf_isom_sample_del(&sample);
}
if ((pps_idx==-1) || (sps_idx==-1)) {
gf_free(hvc_state);
return GF_BAD_PARAM;
}
if (hvc_state->pps[pps_idx].loop_filter_across_tiles_enabled_flag)
filter_disabled=GF_FALSE;
if (! hvc_state->pps[pps_idx].tiles_enabled_flag) {
hevc_add_trif(file, track, gf_isom_get_track_id(file, track), GF_TRUE, 1, filter_disabled, 0, 0, hvc_state->sps[pps_idx].width, hvc_state->sps[pps_idx].height, GF_TRUE);
GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[HEVC Tiles] Tiles not enabled, signal only single tile full picture\n"));
gf_free(hvc_state);
return GF_OK;
}
nb_tiles = hvc_state->pps[pps_idx].num_tile_columns * hvc_state->pps[pps_idx].num_tile_rows;
tiles = gf_malloc(sizeof(HEVCTileImport) * nb_tiles);
if (!tiles) {
gf_free(hvc_state);
return GF_OUT_OF_MEM;
}
memset(tiles, 0, sizeof(HEVCTileImport) * nb_tiles);
for (i=0; i<nb_tiles; i++) {
if (! signal_mode) {
//first clone tracks
e = gf_isom_clone_track(file, track, file, 0, &tiles[i].track );
if (e) goto err_exit;
tiles[i].track_id = gf_isom_get_track_id(file, tiles[i].track);
gf_isom_hevc_set_tile_config(file, tiles[i].track, 1, NULL, GF_FALSE);
// setup track references from tile track to base
gf_isom_set_track_reference(file, tiles[i].track, GF_ISOM_REF_TBAS, gf_isom_get_track_id(file, track) );
} else {
tiles[i].track_id = gf_isom_get_track_id(file, track) + i+1;
}
tiles[i].all_intra = GF_TRUE;
}
count = gf_isom_get_sample_count(file, track);
for (i=0; i<count; i++) {
u8 *data;
u32 size, nb_nalus=0, nb_nal_entries=0, last_tile_group=(u32) -1;
GF_BitStream *bs=NULL;
GF_ISOSample *sample = gf_isom_get_sample(file, track, i+1, &di);
if (!sample) {
e = gf_isom_last_error(file);
goto err_exit;
}
data = (u8 *) sample->data;
size = sample->dataLength;
if (!signal_mode) {
bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
sample->data = NULL;
sample->dataLength = 0;
for (j=0; j<nb_tiles; j++) {
tiles[j].data_offset = 0;
tiles[j].sample_data = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
}
} else {
for (j=0; j<nb_tiles; j++) {
tiles[j].nb_nalus_in_sample = 0;
}
bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
//write start of nalm group
gf_bs_write_int(bs, 0, 6);//reserved
gf_bs_write_int(bs, 0, 1);//large_size
gf_bs_write_int(bs, (signal_mode==2) ? 1 : 0, 1);//rle
gf_bs_write_u8(bs, 0);//entry_count - will be set at the end
}
sample->data = (char *) data;
while (size) {
u8 temporal_id, layer_id;
u8 nal_type = 0;
u32 nalu_size = 0;
for (j=0; j<nalu_size_length; j++) {
nalu_size = (nalu_size<<8) + data[j];
}
ret = gf_hevc_parse_nalu(data + nalu_size_length, nalu_size, hvc_state, &nal_type, &temporal_id, &layer_id);
//error parsing NAL, set nal to fallback to regular import
if (ret<0) nal_type = GF_HEVC_NALU_VID_PARAM;
switch (nal_type) {
case GF_HEVC_NALU_SLICE_TRAIL_N:
case GF_HEVC_NALU_SLICE_TRAIL_R:
case GF_HEVC_NALU_SLICE_TSA_N:
case GF_HEVC_NALU_SLICE_TSA_R:
case GF_HEVC_NALU_SLICE_STSA_N:
case GF_HEVC_NALU_SLICE_STSA_R:
case GF_HEVC_NALU_SLICE_BLA_W_LP:
case GF_HEVC_NALU_SLICE_BLA_W_DLP:
case GF_HEVC_NALU_SLICE_BLA_N_LP:
case GF_HEVC_NALU_SLICE_IDR_W_DLP:
case GF_HEVC_NALU_SLICE_IDR_N_LP:
case GF_HEVC_NALU_SLICE_CRA:
case GF_HEVC_NALU_SLICE_RADL_R:
case GF_HEVC_NALU_SLICE_RADL_N:
case GF_HEVC_NALU_SLICE_RASL_R:
case GF_HEVC_NALU_SLICE_RASL_N:
tx = ty = tw = th = 0;
cur_tile = hevc_get_tile_id(hvc_state, &tx, &ty, &tw, &th);
if (cur_tile>=nb_tiles) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[HEVC Tiles] Tile index %d is greater than number of tiles %d in PPS\n", cur_tile, nb_tiles));
e = GF_NON_COMPLIANT_BITSTREAM;
}
if (e)
goto err_exit;
tiles[cur_tile].tx = tx;
tiles[cur_tile].ty = ty;
tiles[cur_tile].tw = tw;
tiles[cur_tile].th = th;
if (hvc_state->s_info.slice_type != GF_HEVC_SLICE_TYPE_I) {
tiles[cur_tile].all_intra = 0;
}
if (signal_mode) {
nb_nalus++;
tiles[cur_tile].nb_nalus_in_sample++;
if (signal_mode==1) {
gf_bs_write_u16(bs, tiles[cur_tile].track_id);
nb_nal_entries++;
} else if (last_tile_group != tiles[cur_tile].track_id) {
last_tile_group = tiles[cur_tile].track_id;
gf_bs_write_u8(bs, nb_nalus);
gf_bs_write_u16(bs, tiles[cur_tile].track_id);
nb_nal_entries++;
}
} else {
gf_bs_write_data(tiles[cur_tile].sample_data, (char *) data, nalu_size + nalu_size_length);
if (! gf_isom_has_track_reference(file, track, GF_ISOM_REF_SABT, tiles[cur_tile].track_id)) {
gf_isom_set_track_reference(file, track, GF_ISOM_REF_SABT, tiles[cur_tile].track_id);
}
tiles[cur_tile].data_offset += nalu_size + nalu_size_length;
}
break;
default:
if (! signal_mode) {
gf_bs_write_data(bs, (char *) data, nalu_size + nalu_size_length);
} else {
nb_nalus++;
if (signal_mode==1) {
gf_bs_write_u16(bs, 0);
nb_nal_entries++;
} else if (last_tile_group != 0) {
last_tile_group = 0;
gf_bs_write_u8(bs, nb_nalus);
gf_bs_write_u16(bs, 0);
nb_nal_entries++;
}
}
break;
}
data += nalu_size + nalu_size_length;
size -= nalu_size + nalu_size_length;
}
if (! signal_mode) {
gf_free(sample->data);
gf_bs_get_content(bs, &sample->data, &sample->dataLength);
gf_bs_del(bs);
e = gf_isom_update_sample(file, track, i+1, sample, 1);
if (e) goto err_exit;
gf_free(sample->data);
sample->data = NULL;
for (j=0; j<nb_tiles; j++) {
sample->dataLength = 0;
gf_bs_get_content(tiles[j].sample_data, &sample->data, &sample->dataLength);
if (!sample->data) {
gf_bs_del(tiles[j].sample_data);
tiles[j].sample_data = NULL;
continue;
}
e = gf_isom_add_sample(file, tiles[j].track, 1, sample);
if (e) goto err_exit;
tiles[j].sample_count ++;
gf_bs_del(tiles[j].sample_data);
tiles[j].sample_data = NULL;
gf_free(sample->data);
sample->data = NULL;
e = gf_isom_copy_sample_info(file, tiles[j].track, file, track, i+1);
if (e) goto err_exit;
}
} else {
u32 sdesc;
data=NULL;
size=0;
gf_bs_get_content(bs, &data, &size);
gf_bs_del(bs);
data[1] = nb_nal_entries;
e = gf_isom_add_sample_group_info(file, track, GF_ISOM_SAMPLE_GROUP_NALM, data, size, 1, &sdesc);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[ISOBMF] Error defining NALM group description entry\n" ));
} else {
e = gf_isom_add_sample_info(file, track, i+1, GF_ISOM_SAMPLE_GROUP_NALM, sdesc, GF_ISOM_SAMPLE_GROUP_TRIF);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[ISOBMF] Error associating NALM group description to sample\n" ));
}
}
gf_free(data);
if (e) goto err_exit;
}
gf_isom_sample_del(&sample);
}
for (i=0; i<nb_tiles; i++) {
u32 width, height;
s32 translation_x, translation_y;
s16 layer;
if (! signal_mode) {
tiles[i].track = gf_isom_get_track_by_id(file, tiles[i].track_id);
if (!tiles[i].sample_count) {
gf_isom_remove_track(file, tiles[i].track);
continue;
}
hevc_add_trif(file, tiles[i].track, tiles[i].track_id, GF_FALSE, (tiles[i].all_intra) ? 2 : 1, filter_disabled, tiles[i].tx, tiles[i].ty, tiles[i].tw, tiles[i].th, GF_TRUE);
gf_isom_set_visual_info(file, tiles[i].track, 1, tiles[i].tw, tiles[i].th);
gf_isom_get_track_layout_info(file, track, &width, &height, &translation_x, &translation_y, &layer);
gf_isom_set_track_layout_info(file, tiles[i].track, width<<16, height<<16, translation_x, translation_y, layer);
} else {
hevc_add_trif(file, track, tiles[i].track_id, GF_FALSE, (tiles[i].all_intra) ? 2 : 1, filter_disabled, tiles[i].tx, tiles[i].ty, tiles[i].tw, tiles[i].th, GF_FALSE);
}
}
err_exit:
gf_free(hvc_state);
gf_free(tiles);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[ISOBMF] Could not split HEVC tiles into tracks: %s\n", gf_error_to_string(e) ));
}
return e;
#endif
}
#endif /*GPAC_DISABLE_AV_PARSERS*/
#endif /*GPAC_DISABLE_MEDIA_IMPORT*/
#ifndef GPAC_DISABLE_ISOM_FRAGMENTS
#include <gpac/filters.h>
typedef struct
{
u32 filter_idx_plus_one;
u32 last_prog;
GF_FilterSession *fsess;
} FragCallback;
extern char gf_prog_lf;
static Bool on_frag_event(void *_udta, GF_Event *evt)
{
u32 i, count;
GF_FilterStats stats;
FragCallback *fc = (FragCallback *)_udta;
if (!_udta)
return GF_FALSE;
if (evt && (evt->type != GF_EVENT_PROGRESS))
return GF_FALSE;
stats.report_updated = GF_FALSE;
if (!fc->filter_idx_plus_one) {
count = gf_fs_get_filters_count(fc->fsess);
for (i=0; i<count; i++) {
if (gf_fs_get_filter_stats(fc->fsess, i, &stats) != GF_OK) continue;
if (strcmp(stats.reg_name, "mp4mx")) continue;
fc->filter_idx_plus_one = i+1;
break;
}
if (!fc->filter_idx_plus_one) return GF_FALSE;
} else {
if (gf_fs_get_filter_stats(fc->fsess, fc->filter_idx_plus_one-1, &stats) != GF_OK)
return GF_FALSE;
}
if (! stats.report_updated) return GF_FALSE;
if (stats.percent/100 == fc->last_prog) return GF_FALSE;
fc->last_prog = stats.percent / 100;
#ifndef GPAC_DISABLE_LOG
GF_LOG(GF_LOG_INFO, GF_LOG_APP, ("Fragmenting: % 2.2f %%%c", ((Double)stats.percent) / 100, gf_prog_lf));
#else
fprintf(stderr, "Fragmenting: % 2.2f %%%c", ((Double)stats.percent) / 100, gf_prog_lf);
#endif
return GF_FALSE;
}
GF_EXPORT
GF_Err gf_media_fragment_file(GF_ISOFile *input, const char *output_file, Double max_duration_sec, Bool use_mfra)
{
char szArgs[1024];
FragCallback fc;
GF_Err e = GF_OK;
GF_Filter *f;
GF_FilterSession *fsess = gf_fs_new_defaults(0);
if (!fsess) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("Failed to create filter session\n"));
return GF_OUT_OF_MEM;
}
sprintf(szArgs, "mp4dmx:mov=%p", input);
f = gf_fs_load_filter(fsess, szArgs, &e);
if (!f) { gf_fs_del(fsess); return e; }
gf_strcpy(szArgs, "reframer:FID=1");
f = gf_fs_load_filter(fsess, szArgs, &e);
if (!f) { gf_fs_del(fsess); return e; }
sprintf(szArgs, "%s:SID=1:frag:cdur=%g:abs_offset:fragdur", output_file, max_duration_sec);
if (use_mfra)
gf_strcat(szArgs, ":mfra");
f = gf_fs_load_destination(fsess, szArgs, NULL, NULL, &e);
if (!f) { gf_fs_del(fsess); return e; }
if (!gf_sys_is_test_mode()
#ifndef GPAC_DISABLE_LOG
&& (gf_log_get_tool_level(GF_LOG_APP)!=GF_LOG_QUIET)
#endif
&& !gf_sys_is_quiet()
) {
fc.last_prog=0;
fc.fsess=fsess;
fc.filter_idx_plus_one=0;
gf_fs_enable_reporting(fsess, GF_TRUE);
gf_fs_set_ui_callback(fsess, on_frag_event, &fc);
}
#ifdef GPAC_ENABLE_COVERAGE
if (gf_sys_is_cov_mode())
on_frag_event(NULL, NULL);
#endif
e = gf_fs_run(fsess);
if (e==GF_EOS) e = GF_OK;
if (!e) e = gf_fs_get_last_connect_error(fsess);
if (!e) e = gf_fs_get_last_process_error(fsess);
gf_fs_del(fsess);
return e;
}
#endif /*GPAC_DISABLE_ISOM_FRAGMENTS*/
GF_Err rfc_6381_get_codec_aac(char *szCodec, u32 codec_id, u8 *dsi, u32 dsi_size, Bool force_sbr)
{
if (dsi && dsi_size) {
u8 audio_object_type;
if (dsi_size < 2) {
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[RFC6381] invalid DSI size %u < 2\n", dsi_size));
return GF_NON_COMPLIANT_BITSTREAM;
}
/*5 first bits of AAC config*/
audio_object_type = (dsi[0] & 0xF8) >> 3;
if (audio_object_type == 31) { /*escape code*/
const u8 audio_object_type_ext = ((dsi[0] & 0x07) << 3) + ((dsi[1] & 0xE0) >> 5);
audio_object_type = 32 + audio_object_type_ext;
}
#ifndef GPAC_DISABLE_AV_PARSERS
if (force_sbr && (audio_object_type==2) ) {
GF_M4ADecSpecInfo a_cfg;
GF_Err e = gf_m4a_get_config(dsi, dsi_size, &a_cfg);
if (e==GF_OK) {
if (a_cfg.sbr_sr)
audio_object_type = a_cfg.sbr_object_type;
if (a_cfg.has_ps)
audio_object_type = 29;
}
}
#endif
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4a.%02X.%01d", gf_codecid_oti(codec_id), audio_object_type);
return GF_OK;
}
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4a.%02X", codec_id);
switch (codec_id) {
case GF_CODECID_AAC_MPEG4:
case GF_CODECID_AAC_MPEG2_MP:
case GF_CODECID_AAC_MPEG2_LCP:
case GF_CODECID_AAC_MPEG2_SSRP:
case GF_CODECID_USAC:
GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[RFC6381] Cannot find AAC config, using default %s\n", szCodec));
break;
}
return GF_OK;
}
GF_Err dolby_get_codec_ac4(char *szCodec, u32 codec_id, u8 *dsi, u32 dsi_size)
{
u32 i, lowest_md_compat = GF_UINT_MAX, presentation_version, bitstream_version;
GF_Err e = GF_NON_COMPLIANT_BITSTREAM;
if (!dsi || !dsi_size) return e;
GF_AC4Config cfg = {0};
GF_BitStream *bs = gf_bs_new((const char *)dsi, dsi_size, GF_BITSTREAM_READ);
if (!bs) return GF_OUT_OF_MEM;
e = gf_odf_ac4_cfg_parse_bs(bs, &cfg);
gf_bs_del(bs);
if (e) return e;
for (i=0; i<cfg.stream.n_presentations; i++) {
GF_AC4PresentationV1 *presentations = (GF_AC4PresentationV1*)gf_list_get(cfg.stream.presentations, i);
if (presentations) {
if (presentations->md_compat < lowest_md_compat) {
bitstream_version = cfg.stream.bitstream_version;
presentation_version = presentations->presentation_version;
lowest_md_compat = presentations->md_compat;
}
}
}
if (lowest_md_compat == GF_UINT_MAX) {
gf_odf_ac4_cfg_clean_list(&cfg);
return GF_NON_COMPLIANT_BITSTREAM;
}
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "ac-4.%02d.%02d.%02d", bitstream_version, presentation_version, lowest_md_compat);
gf_odf_ac4_cfg_clean_list(&cfg);
return e;
}
GF_Err rfc_6381_get_codec_m4v(char *szCodec, u32 codec_id, u8 *dsi, u32 dsi_size)
{
#ifndef GPAC_DISABLE_AV_PARSERS
if (dsi && dsi_size) {
GF_M4VDecSpecInfo m4vc;
gf_m4v_get_config(dsi, dsi_size, &m4vc);
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4v.%02X.%01x", codec_id, m4vc.VideoPL);
return GF_OK;
}
#endif
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4v.%02X", codec_id);
GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[RFC6381] Cannot find M4V config, using default %s\n", szCodec));
return GF_OK;
}
GF_Err rfc_6381_get_codec_avc(char *szCodec, u32 subtype, GF_AVCConfig *avcc)
{
gf_assert(avcc);
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%02X%02X%02X", gf_4cc_to_str(subtype), avcc->AVCProfileIndication, avcc->profile_compatibility, avcc->AVCLevelIndication);
return GF_OK;
}
GF_Err rfc_6381_get_codec_hevc(char *szCodec, u32 subtype, GF_HEVCConfig *hvcc)
{
u8 c;
char szTemp[RFC6381_CODEC_NAME_SIZE_MAX];
gf_assert(hvcc);
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.", gf_4cc_to_str(subtype));
if (hvcc->profile_space==1) gf_strlcat(szCodec, "A", RFC6381_CODEC_NAME_SIZE_MAX);
else if (hvcc->profile_space==2) gf_strlcat(szCodec, "B", RFC6381_CODEC_NAME_SIZE_MAX);
else if (hvcc->profile_space==3) gf_strlcat(szCodec, "C", RFC6381_CODEC_NAME_SIZE_MAX);
//profile idc encoded as a decimal number
sprintf(szTemp, "%d", hvcc->profile_idc);
gf_strlcat(szCodec, szTemp, RFC6381_CODEC_NAME_SIZE_MAX);
//general profile compatibility flags: hexa, bit-reversed
{
u32 val = hvcc->general_profile_compatibility_flags;
u32 i, res = 0;
for (i=0; i<32; i++) {
res |= val & 1;
if (i==31) break;
res <<= 1;
val >>=1;
}
sprintf(szTemp, ".%X", res);
gf_strlcat(szCodec, szTemp, RFC6381_CODEC_NAME_SIZE_MAX);
}
if (hvcc->tier_flag) gf_strlcat(szCodec, ".H", RFC6381_CODEC_NAME_SIZE_MAX);
else gf_strlcat(szCodec, ".L", RFC6381_CODEC_NAME_SIZE_MAX);
sprintf(szTemp, "%d", hvcc->level_idc);
gf_strlcat(szCodec, szTemp, RFC6381_CODEC_NAME_SIZE_MAX);
c = hvcc->progressive_source_flag << 7;
c |= hvcc->interlaced_source_flag << 6;
c |= hvcc->non_packed_constraint_flag << 5;
c |= hvcc->frame_only_constraint_flag << 4;
c |= (hvcc->constraint_indicator_flags >> 40);
sprintf(szTemp, ".%X", c);
gf_strlcat(szCodec, szTemp, RFC6381_CODEC_NAME_SIZE_MAX);
if (hvcc->constraint_indicator_flags & 0xFFFFFFFF) {
c = (hvcc->constraint_indicator_flags >> 32) & 0xFF;
sprintf(szTemp, ".%X", c);
gf_strlcat(szCodec, szTemp, RFC6381_CODEC_NAME_SIZE_MAX);
if (hvcc->constraint_indicator_flags & 0x00FFFFFF) {
c = (hvcc->constraint_indicator_flags >> 24) & 0xFF;
sprintf(szTemp, ".%X", c);
gf_strlcat(szCodec, szTemp, RFC6381_CODEC_NAME_SIZE_MAX);
if (hvcc->constraint_indicator_flags & 0x0000FFFF) {
c = (hvcc->constraint_indicator_flags >> 16) & 0xFF;
sprintf(szTemp, ".%X", c);
gf_strlcat(szCodec, szTemp, RFC6381_CODEC_NAME_SIZE_MAX);
if (hvcc->constraint_indicator_flags & 0x000000FF) {
c = (hvcc->constraint_indicator_flags >> 8) & 0xFF;
sprintf(szTemp, ".%X", c);
gf_strlcat(szCodec, szTemp, RFC6381_CODEC_NAME_SIZE_MAX);
c = (hvcc->constraint_indicator_flags ) & 0xFF;
sprintf(szTemp, ".%X", c);
gf_strlcat(szCodec, szTemp, RFC6381_CODEC_NAME_SIZE_MAX);
}
}
}
}
return GF_OK;
}
GF_Err rfc_6381_get_codec_av1(char *szCodec, u32 subtype, GF_AV1Config *av1c, COLR colr)
{
#ifndef GPAC_DISABLE_AV_PARSERS
GF_Err e;
u32 i = 0;
AV1State *av1_state;
gf_assert(av1c);
GF_SAFEALLOC(av1_state, AV1State);
if (!av1_state) return GF_OUT_OF_MEM;
gf_av1_init_state(av1_state);
av1_state->config = av1c;
for (i = 0; i < gf_list_count(av1c->obu_array); ++i) {
GF_BitStream *bs;
GF_AV1_OBUArrayEntry *a = gf_list_get(av1c->obu_array, i);
bs = gf_bs_new(a->obu, a->obu_length, GF_BITSTREAM_READ);
if (!av1_is_obu_header(a->obu_type))
GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[RFC6381] AV1: unexpected obu_type %d - Parsing anyway.\n", a->obu_type, gf_4cc_to_str(subtype)));
e = aom_av1_parse_temporal_unit_from_section5(bs, av1_state);
gf_bs_del(bs);
bs = NULL;
if (e) {
return e;
}
}
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%01u.%02u%c.%02u", gf_4cc_to_str(subtype),
av1_state->config->seq_profile, av1_state->config->seq_level_idx_0, av1_state->config->seq_tier_0 ? 'H' : 'M', av1_state->bit_depth);
if (av1_state->color_description_present_flag) {
char tmp[RFC6381_CODEC_NAME_SIZE_MAX];
snprintf(tmp, RFC6381_CODEC_NAME_SIZE_MAX, ".%01u.%01u%01u%01u.%02u.%02u.%02u.%01u",
av1_state->config->monochrome, av1_state->config->chroma_subsampling_x, av1_state->config->chroma_subsampling_y,
av1_state->config->chroma_subsampling_x && av1_state->config->chroma_subsampling_y ? av1_state->config->chroma_sample_position : 0,
colr.override == GF_TRUE ? colr.colour_primaries : av1_state->color_primaries,
colr.override == GF_TRUE ? colr.transfer_characteristics : av1_state->transfer_characteristics,
colr.override == GF_TRUE ? colr.matrix_coefficients : av1_state->matrix_coefficients,
colr.override == GF_TRUE ? colr.full_range : av1_state->color_range);
gf_strlcat(szCodec, tmp, RFC6381_CODEC_NAME_SIZE_MAX);
} else {
if ((av1_state->color_primaries == 2) && (av1_state->transfer_characteristics == 2) && (av1_state->matrix_coefficients == 2) && av1_state->color_range == GF_FALSE) {
} else {
GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[RFC6381] incoherent color characteristics primaries %d transfer %d matrix %d color range %d\n",
av1_state->color_primaries, av1_state->transfer_characteristics, av1_state->matrix_coefficients, av1_state->color_range));
}
}
gf_av1_reset_state(av1_state, GF_TRUE);
gf_free(av1_state);
return GF_OK;
#else
return GF_NOT_SUPPORTED;
#endif
}
GF_Err rfc_6381_get_codec_vpx(char *szCodec, u32 subtype, GF_VPConfig *vpcc, COLR colr)
{
gf_assert(vpcc);
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%02u.%02u.%02u.%02u.%02u.%02u.%02u.%02u", gf_4cc_to_str(subtype),
vpcc->profile,
vpcc->level,
vpcc->bit_depth,
vpcc->chroma_subsampling,
colr.override == GF_TRUE ? colr.colour_primaries : vpcc->colour_primaries,
colr.override == GF_TRUE ? colr.transfer_characteristics : vpcc->transfer_characteristics,
colr.override == GF_TRUE ? colr.matrix_coefficients : vpcc->matrix_coefficients,
colr.override == GF_TRUE ? colr.full_range : vpcc->video_fullRange_flag);
return GF_OK;
}
GF_Err rfc_6381_get_codec_avs3v(char *szCodec, u32 subtype, GF_AVS3VConfig *av3c, COLR colr)
{
#ifndef GPAC_DISABLE_AV_PARSERS
if (!av3c || !av3c->sequence_header || av3c->sequence_header_length < 6)
return GF_BAD_PARAM;
u8 profile = av3c->sequence_header[4];
u8 level = av3c->sequence_header[5];
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%2x.%2x", gf_4cc_to_str(subtype), profile, level);
return GF_OK;
#else
return GF_NOT_SUPPORTED;
#endif
}
GF_Err rfc_6381_get_codec_dolby_vision(char *szCodec, u32 subtype, GF_DOVIDecoderConfigurationRecord *dovi)
{
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%02u.%02u", gf_4cc_to_str(subtype), dovi->dv_profile, dovi->dv_level);
return GF_OK;
}
static char base32_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
GF_Err rfc_6381_get_codec_vvc(char *szCodec, u32 subtype, GF_VVCConfig *vvcc)
{
gf_assert(vvcc);
u32 i, pos, len;
if ( (subtype==GF_4CC('v','v','c','N')) || (subtype==GF_4CC('v','v','s','1')) ) {
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype));
} else {
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%d.%s%d", gf_4cc_to_str(subtype), vvcc->general_profile_idc, vvcc->general_tier_flag ? "H" : "L", vvcc->general_level_idc);
}
u8 buf[256];
GF_BitStream *bs = gf_bs_new(buf, 256, GF_BITSTREAM_WRITE);
gf_bs_write_int(bs, vvcc->ptl_frame_only_constraint, 1);
gf_bs_write_int(bs, vvcc->ptl_multilayer_enabled, 1);
for (i=0; i<vvcc->num_constraint_info; i++) {
gf_bs_write_int(bs, vvcc->general_constraint_info[i], 8);
}
gf_bs_align(bs);
pos = (u32) gf_bs_get_position(bs);
gf_bs_del(bs);
while (pos && (buf[pos-1]==0)) {
pos--;
}
if (!pos) {
gf_strlcat(szCodec, ".CA", RFC6381_CODEC_NAME_SIZE_MAX);
return GF_OK;
}
gf_strlcat(szCodec, ".C", RFC6381_CODEC_NAME_SIZE_MAX);
len = (u32) strlen(szCodec);
bs = gf_bs_new(buf, pos, GF_BITSTREAM_READ);
while (1) {
u32 nb_bits = (u32) ( 8*gf_bs_available(bs) + gf_bs_bits_available(bs) );
if (!nb_bits) break;
if (nb_bits>5) nb_bits = 5;
u32 c = gf_bs_read_int(bs, nb_bits);
while (nb_bits<5) {
c <<= 1;
nb_bits++;
}
char b32_char = base32_chars[c];
if (len<=RFC6381_CODEC_NAME_SIZE_MAX-2) {
szCodec[len] = b32_char;
len++;
szCodec[len] = 0;
}
else {
szCodec[RFC6381_CODEC_NAME_SIZE_MAX-1] = 0;
GF_LOG(GF_LOG_ERROR, GF_LOG_MEDIA, ("[RFC6381] truncated codec string - current value: %s - trying to write: %c\n", szCodec, b32_char));
gf_bs_del(bs);
return GF_ISOM_INVALID_FILE;
}
}
gf_bs_del(bs);
return GF_OK;
}
GF_Err rfc_6381_get_codec_mpegha(char *szCodec, u32 subtype, u8 *dsi, u32 dsi_size, s32 pl)
{
if (dsi && (dsi_size>=2) ) {
pl = dsi[1];
}
if (pl<0) {
GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[RFC6381] Cannot find MPEG-H Audio Config or audio PL, defaulting to profile 0x01\n"));
}
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.0x%02X", gf_4cc_to_str(subtype), pl);
return GF_OK;
}
GF_Err rfc_6381_get_codec_iamf(char *szCodec, GF_IAConfig *cfg)
{
#ifndef GPAC_DISABLE_AV_PARSERS
IAMFState state;
gf_iamf_init_state(&state);
u32 obu_count = cfg ? gf_list_count(cfg->configOBUs) : 0;
for (u32 i=0; i<obu_count; ++i) {
GF_IamfObu *obu = gf_list_get(cfg->configOBUs, i);
GF_BitStream *bs = gf_bs_new(obu->raw_obu_bytes, obu->obu_length, GF_BITSTREAM_READ);
u64 obu_size = 0;
IamfObuType obu_type;
GF_Err e = gf_iamf_parse_obu(bs, &obu_type, &obu_size, &state);
gf_bs_del(bs);
if (e) {
GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[IAMF] could not parse configOBU #%u. Leaving parsing.\n", i));
return GF_ISOM_INVALID_FILE;
}
}
const char *codec = NULL;
switch (state.codec_id) {
case GF_4CC('O', 'p', 'u', 's'):
codec = "opus";
break;
case GF_4CC('f', 'L', 'a', 'C'):
codec = "fLaC";
break;
case GF_4CC('i', 'p', 'c', 'm'):
codec = "ipcm";
break;
case GF_4CC('m', 'p', '4', 'a'):
codec = "mp4a.40.2";
break;
default:
GF_LOG(GF_LOG_WARNING, GF_LOG_MEDIA, ("[IAMF] Unsupported codec_id=%u. Leaving parsing.\n", state.codec_id));
return GF_ISOM_INVALID_FILE;
}
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "iamf.%03u.%03u.%s", state.primary_profile, state.additional_profile, codec);
return GF_OK;
#else
return GF_NOT_SUPPORTED;
#endif
}
GF_Err rfc_6381_get_codec_uncv(char *szCodec, u32 subtype, u8 *dsi, u32 dsi_size);
// Selected (namespace,identifier) pairs from https://www.w3.org/TR/ttml-profile-registry/
// ordered in decreasing order of preference
static const char *ttml_namespaces[] = {
"im3t", "http://www.w3.org/ns/ttml/profile/imsc1.2/text",
"im2t", "http://www.w3.org/ns/ttml/profile/imsc1.1/text",
"im2i", "http://www.w3.org/ns/ttml/profile/imsc1.1/image",
"im1t", "http://www.w3.org/ns/ttml/profile/imsc1/text",
"im1i", "http://www.w3.org/ns/ttml/profile/imsc1/image"
};
static const int TTML_NAMESPACES_COUNT = GF_ARRAY_LENGTH(ttml_namespaces);
GF_Err rfc_6381_get_codec_stpp(char *szCodec, u32 subtype,
const char *xmlnamespace,
const char *xml_schema_loc,
const char *mimes)
{
// we ignore schema location and auxiliary mime types
// we focus on the provided list of namespaces
if (xmlnamespace != NULL) {
int i;
for (i = 0; i < TTML_NAMESPACES_COUNT; i+=2) {
if(strstr(xmlnamespace, ttml_namespaces[i+1]) != NULL) {
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%s.%s", gf_4cc_to_str(subtype), "ttml", ttml_namespaces[i]);
return GF_OK;
}
}
// if none of the namespaces above have been found, search the default TTML namespace
if(strstr(xmlnamespace, "http://www.w3.org/ns/ttml")) {
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s.%s", gf_4cc_to_str(subtype), "ttml");
return GF_OK;
}
}
// None of the known namespaces are found, default
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype));
return GF_OK;
}
GF_Err rfc6381_codec_name_default(char *szCodec, u32 subtype, u32 codec_id)
{
if (codec_id && (codec_id<GF_CODECID_LAST_MPEG4_MAPPING)) {
u32 stype = gf_codecid_type(codec_id);
if (stype==GF_STREAM_VISUAL)
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4v.%02X", codec_id);
else if (stype==GF_STREAM_AUDIO)
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4a.%02X", codec_id);
else
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4s.%02X", codec_id);
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_MEDIA, ("[RFC6381] Codec parameters not known - using default value \"%s\"\n", gf_4cc_to_str(subtype) ));
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "%s", gf_4cc_to_str(subtype));
}
return GF_OK;
}
#ifndef GPAC_DISABLE_ISOM
GF_EXPORT
GF_Err gf_media_get_rfc_6381_codec_name(GF_ISOFile *movie, u32 track, u32 stsd_idx, char *szCodec, Bool force_inband, Bool force_sbr)
{
GF_ESD *esd;
GF_Err e;
GF_AVCConfig *avcc;
GF_HEVCConfig *hvcc;
u32 subtype = gf_isom_get_media_subtype(movie, track, stsd_idx);
if (subtype == GF_ISOM_SUBTYPE_MPEG4_CRYP) {
u32 originalFormat=0;
if (gf_isom_is_ismacryp_media(movie, track, stsd_idx)) {
e = gf_isom_get_ismacryp_info(movie, track, stsd_idx, &originalFormat, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
} else if (gf_isom_is_omadrm_media(movie, track, stsd_idx)) {
e = gf_isom_get_omadrm_info(movie, track, stsd_idx, &originalFormat, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
} else if(gf_isom_is_cenc_media(movie, track, stsd_idx)) {
e = gf_isom_get_cenc_info(movie, track, stsd_idx, &originalFormat, NULL, NULL);
} else {
GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[RFC6381] Unknown protection scheme type %s\n", gf_4cc_to_str( gf_isom_is_media_encrypted(movie, track, stsd_idx)) ));
e = gf_isom_get_original_format_type(movie, track, stsd_idx, &originalFormat);
}
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[RFC6381] Error fetching protection information\n"));
return e;
}
if (originalFormat) subtype = originalFormat;
}
else if (subtype==GF_ISOM_SUBTYPE_MPEG4) {
u32 stsd_type = gf_isom_get_mpeg4_subtype(movie, track, stsd_idx);
if (stsd_type==GF_ISOM_SUBTYPE_RESV)
gf_isom_get_original_format_type(movie, track, stsd_idx, &subtype);
}
switch (subtype) {
case GF_ISOM_SUBTYPE_MPEG4:
esd = gf_isom_get_esd(movie, track, stsd_idx);
if (esd && esd->decoderConfig) {
switch (esd->decoderConfig->streamType) {
case GF_STREAM_AUDIO:
if (esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) {
e = rfc_6381_get_codec_aac(szCodec, esd->decoderConfig->objectTypeIndication, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, force_sbr);
} else {
e = rfc_6381_get_codec_aac(szCodec, esd->decoderConfig->objectTypeIndication, NULL, 0, force_sbr);
}
break;
case GF_STREAM_VISUAL:
if (esd->decoderConfig->decoderSpecificInfo && esd->decoderConfig->decoderSpecificInfo->data) {
e = rfc_6381_get_codec_m4v(szCodec, esd->decoderConfig->objectTypeIndication, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength);
} else {
e = rfc_6381_get_codec_m4v(szCodec, esd->decoderConfig->objectTypeIndication, NULL, 0);
}
break;
default:
snprintf(szCodec, RFC6381_CODEC_NAME_SIZE_MAX, "mp4s.%02X", esd->decoderConfig->objectTypeIndication);
e = GF_OK;
break;
}
gf_odf_desc_del((GF_Descriptor *)esd);
return e;
}
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[RFC6381] Cannot find ESD. Aborting.\n"));
if (esd) gf_odf_desc_del((GF_Descriptor *)esd);
return GF_ISOM_INVALID_FILE;
case GF_ISOM_SUBTYPE_AVC_H264:
case GF_ISOM_SUBTYPE_AVC2_H264:
case GF_ISOM_SUBTYPE_AVC3_H264:
case GF_ISOM_SUBTYPE_AVC4_H264:
avcc = gf_isom_avc_config_get(movie, track, stsd_idx);
if (force_inband) {
if (subtype==GF_ISOM_SUBTYPE_AVC_H264)
subtype = GF_ISOM_SUBTYPE_AVC3_H264;
else if (subtype==GF_ISOM_SUBTYPE_AVC2_H264)
subtype = GF_ISOM_SUBTYPE_AVC4_H264;
}
if (avcc) {
e = rfc_6381_get_codec_avc(szCodec, subtype, avcc);
gf_odf_avc_cfg_del(avcc);
return e;
}
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[RFC6381] Cannot find AVC configuration box\n"));
return GF_ISOM_INVALID_FILE;
case GF_ISOM_SUBTYPE_SVC_H264:
case GF_ISOM_SUBTYPE_MVC_H264:
avcc = gf_isom_mvc_config_get(movie, track, stsd_idx);
if (!avcc) avcc = gf_isom_svc_config_get(movie, track, stsd_idx);
if (avcc) {
e = rfc_6381_get_codec_avc(szCodec, subtype, avcc);
gf_odf_avc_cfg_del(avcc);
return e;
}
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[RFC6381] Cannot find SVC/MVC configuration box\n"));
return GF_ISOM_INVALID_FILE;
case GF_ISOM_SUBTYPE_HVC1:
case GF_ISOM_SUBTYPE_HEV1:
case GF_ISOM_SUBTYPE_HVC2:
case GF_ISOM_SUBTYPE_HEV2:
case GF_ISOM_SUBTYPE_HVT1:
case GF_ISOM_SUBTYPE_LHV1:
case GF_ISOM_SUBTYPE_LHE1:
if (force_inband) {
if (subtype==GF_ISOM_SUBTYPE_HVC1) subtype = GF_ISOM_SUBTYPE_HEV1;
else if (subtype==GF_ISOM_SUBTYPE_HVC2) subtype = GF_ISOM_SUBTYPE_HEV2;
}
hvcc = gf_isom_hevc_config_get(movie, track, stsd_idx);
if (!hvcc) {
hvcc = gf_isom_lhvc_config_get(movie, track, stsd_idx);
}
if (subtype==GF_ISOM_SUBTYPE_HVT1) {
u32 refTrack;
gf_isom_get_reference(movie, track, GF_ISOM_REF_TBAS, stsd_idx, &refTrack);
if (hvcc) gf_odf_hevc_cfg_del(hvcc);
hvcc = gf_isom_hevc_config_get(movie, refTrack, 1);
}
if (hvcc) {
e = rfc_6381_get_codec_hevc(szCodec, subtype, hvcc);
gf_odf_hevc_cfg_del(hvcc);
return e;
}
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[RFC6381] HEVCConfig not compliant\n"));
return GF_ISOM_INVALID_FILE;
case GF_ISOM_SUBTYPE_AV01:
{
GF_AV1Config *av1c = gf_isom_av1_config_get(movie, track, stsd_idx);
if (av1c) {
u32 colour_type;
COLR colr;
memset(&colr, 0, sizeof(colr));
if (GF_OK == gf_isom_get_color_info(movie, track, stsd_idx, &colour_type, &colr.colour_primaries, &colr.transfer_characteristics, &colr.matrix_coefficients, &colr.full_range)) {
colr.override = GF_TRUE;
}
e = rfc_6381_get_codec_av1(szCodec, subtype, av1c, colr);
gf_odf_av1_cfg_del(av1c);
return e;
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[RFC6381] No config found for AV1 file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype)));
return GF_BAD_PARAM;
}
case GF_ISOM_SUBTYPE_AVS3:
{
GF_AVS3VConfig *av3c = gf_isom_avs3v_config_get(movie, track, stsd_idx);
if (av3c) {
u32 colour_type;
COLR colr;
memset(&colr, 0, sizeof(colr));
if (GF_OK == gf_isom_get_color_info(movie, track, stsd_idx, &colour_type, &colr.colour_primaries, &colr.transfer_characteristics, &colr.matrix_coefficients, &colr.full_range)) {
colr.override = GF_TRUE;
}
e = rfc_6381_get_codec_avs3v(szCodec, subtype, av3c, colr);
gf_odf_avs3v_cfg_del(av3c);
return e;
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[RFC6381] No config found for AVS3 Video file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype)));
return GF_BAD_PARAM;
}
case GF_ISOM_SUBTYPE_VP08:
case GF_ISOM_SUBTYPE_VP09:
{
GF_VPConfig *vpcc = gf_isom_vp_config_get(movie, track, stsd_idx);
if (vpcc) {
u32 colour_type;
COLR colr;
memset(&colr, 0, sizeof(colr));
if (GF_OK == gf_isom_get_color_info(movie, track, stsd_idx, &colour_type, &colr.colour_primaries, &colr.transfer_characteristics, &colr.matrix_coefficients, &colr.full_range)) {
colr.override = GF_TRUE;
}
e = rfc_6381_get_codec_vpx(szCodec, subtype, vpcc, colr);
gf_odf_vp_cfg_del(vpcc);
return e;
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[RFC6381] No config found for VP file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype)));
return GF_BAD_PARAM;
}
case GF_ISOM_SUBTYPE_DVHE:
case GF_ISOM_SUBTYPE_DVH1:
case GF_ISOM_SUBTYPE_DVA1:
case GF_ISOM_SUBTYPE_DVAV:
case GF_ISOM_SUBTYPE_DAV1:
{
GF_DOVIDecoderConfigurationRecord *dovi = gf_isom_dovi_config_get(movie, track, stsd_idx);
if (!dovi) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[RFC6381] No config found for Dolby Vision file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype)));
return GF_NON_COMPLIANT_BITSTREAM;
}
e = rfc_6381_get_codec_dolby_vision(szCodec, subtype, dovi);
gf_odf_dovi_cfg_del(dovi);
return e;
}
case GF_ISOM_SUBTYPE_VVC1:
case GF_ISOM_SUBTYPE_VVI1:
{
GF_VVCConfig *vvcc = gf_isom_vvc_config_get(movie, track, stsd_idx);
if (vvcc) {
if (force_inband) subtype = GF_ISOM_SUBTYPE_VVI1;
e = rfc_6381_get_codec_vvc(szCodec, subtype, vvcc);
gf_odf_vvc_cfg_del(vvcc);
return e;
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[RFC6381] No config found for VVC file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype)));
return GF_BAD_PARAM;
}
case GF_ISOM_SUBTYPE_MH3D_MHA1:
case GF_ISOM_SUBTYPE_MH3D_MHA2:
case GF_ISOM_SUBTYPE_MH3D_MHM1:
case GF_ISOM_SUBTYPE_MH3D_MHM2:
{
esd = gf_media_map_esd(movie, track, stsd_idx);
if (!esd || !esd->decoderConfig || !esd->decoderConfig->decoderSpecificInfo
|| !esd->decoderConfig->decoderSpecificInfo->data
) {
e = rfc_6381_get_codec_mpegha(szCodec, subtype, NULL, 0, -1);
} else {
e = rfc_6381_get_codec_mpegha(szCodec, subtype, esd->decoderConfig->decoderSpecificInfo->data, esd->decoderConfig->decoderSpecificInfo->dataLength, -1);
}
if (esd) gf_odf_desc_del((GF_Descriptor *)esd);
return e;
}
case GF_ISOM_SUBTYPE_IAMF:
{
GF_IAConfig *iamf = gf_isom_iamf_config_get(movie, track, stsd_idx);
if (!iamf) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[RFC6381] No config found for IAMF file (\"%s\") when computing RFC6381.\n", gf_4cc_to_str(subtype)));
return GF_NON_COMPLIANT_BITSTREAM;
}
e = rfc_6381_get_codec_iamf(szCodec, iamf);
gf_odf_iamf_cfg_del(iamf);
return e;
}
case GF_ISOM_SUBTYPE_UNCV:
{
GF_GenericSampleDescription *udesc = gf_isom_get_generic_sample_description(movie, track, stsd_idx);
e = rfc_6381_get_codec_uncv(szCodec, subtype, udesc ? udesc->extension_buf : NULL, udesc ? udesc->extension_buf_size : 0);
if (udesc) {
if (udesc->extension_buf) gf_free(udesc->extension_buf);
gf_free(udesc);
}
return e;
}
case GF_ISOM_SUBTYPE_STPP:
{
const char *xmlnamespace;
const char *xml_schema_loc;
const char *mimes;
e = gf_isom_xml_subtitle_get_description(movie, track, stsd_idx,
&xmlnamespace, &xml_schema_loc, &mimes);
if (e == GF_OK) {
rfc_6381_get_codec_stpp(szCodec, subtype, xmlnamespace, xml_schema_loc, mimes);
}
return e;
}
default:
return rfc6381_codec_name_default(szCodec, subtype, gf_codec_id_from_isobmf(subtype));
}
return GF_OK;
}
GF_Err gf_media_av1_layer_size_get(GF_ISOFile *file, u32 trackNumber, u32 sample_number, u8 op_index, u32 layer_size[3])
{
#ifndef GPAC_DISABLE_AV_PARSERS
u32 i;
AV1State *av1_state;
ObuType obu_type;
u64 obu_size = 0;
u32 hdr_size;
GF_BitStream *bs;
u32 sdidx;
GF_ISOSample *samp;
GF_Err e = GF_OK;
samp = gf_isom_get_sample(file, trackNumber, sample_number, &sdidx);
if (!samp) return GF_BAD_PARAM;
if (!sdidx) {
gf_isom_sample_del(&samp);
return GF_BAD_PARAM;
}
if (gf_isom_get_media_subtype(file, trackNumber, sdidx) != GF_ISOM_SUBTYPE_AV01) {
gf_isom_sample_del(&samp);
return GF_BAD_PARAM;
}
GF_SAFEALLOC(av1_state, AV1State);
if (!av1_state) {
gf_isom_sample_del(&samp);
return GF_OUT_OF_MEM;
}
gf_av1_init_state(av1_state);
av1_state->config = gf_isom_av1_config_get(file, trackNumber, sdidx);
if (!av1_state->config) {
gf_isom_sample_del(&samp);
gf_free(av1_state);
return GF_ISOM_INVALID_FILE;
}
for (i=0; i<gf_list_count(av1_state->config->obu_array); i++) {
GF_AV1_OBUArrayEntry *obu = gf_list_get(av1_state->config->obu_array, i);
bs = gf_bs_new(obu->obu, (u32) obu->obu_length, GF_BITSTREAM_READ);
e = gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, av1_state);
gf_bs_del(bs);
if (e) break;
}
if (!e) {
bs = gf_bs_new(samp->data, samp->dataLength, GF_BITSTREAM_READ);
while (gf_bs_available(bs)) {
e = gf_av1_parse_obu(bs, &obu_type, &obu_size, &hdr_size, av1_state);
if (e) break;
}
gf_bs_del(bs);
}
gf_isom_sample_del(&samp);
if (op_index > av1_state->operating_points_count) {
if (av1_state->config) gf_odf_av1_cfg_del(av1_state->config);
gf_av1_reset_state(av1_state, GF_TRUE);
gf_free(av1_state);
return GF_BAD_PARAM;
}
for (i=0; i<3; i++) {
if (av1_state->layer_size[i+1]==av1_state->layer_size[i]) {
layer_size[i] = 0;
} else {
layer_size[i] = av1_state->layer_size[i];
}
}
if (av1_state->config) gf_odf_av1_cfg_del(av1_state->config);
gf_av1_reset_state(av1_state, GF_TRUE);
gf_free(av1_state);
return e;
#else
return GF_NOT_SUPPORTED;
#endif
}
#ifndef GPAC_DISABLE_ISOM_WRITE
GF_EXPORT
GF_Err gf_media_isom_apply_qt_key(GF_ISOFile *movie, const char *name, const char *val)
{
GF_Err e;
u8 *data=NULL;
GF_QT_UDTAKey key;
if (!name) return GF_BAD_PARAM;
memset(&key, 0, sizeof(GF_QT_UDTAKey));
key.name = (char *) name;
key.ns = GF_4CC('m','d','t','a');
char *sep = strchr(name, '@');
if (sep) {
sep[0] = 0;
key.name = sep+1;
if (strlen(name)!=4) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Unrecognize namespace \"%s\" for key %s\n", name, sep+1));
return GF_BAD_PARAM;
}
key.ns = GF_4CC(name[0], name[1],name[2],name[3]);
sep[0] = '@';
}
if (!val || !val[0]) {
key.type = GF_QT_KEY_REMOVE;
} else if (!strcmp(val, "NULL")) {
key.type = GF_QT_KEY_REMOVE;
} else {
key.type = GF_QT_KEY_UTF8;
key.value.string = val;
if (gf_file_exists(val)) {
e = gf_file_load_data(val, &data, &key.value.data.data_len);
if (e!=GF_OK) {
GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("Failed to load file \"%s\" for key %s: %s\n", val, name, gf_error_to_string(e)));
return e;
}
key.value.data.data = data;
key.type = GF_QT_KEY_OPAQUE;
if (strstr(val, ".bmp")|| strstr(val, ".BMP")) {
key.type = GF_QT_KEY_BMP;
} else if (key.value.data.data_len>3) {
if ((data[0]==0xFF) && (data[1]==0xD8) && (data[2]==0xFF)) {
key.type = GF_QT_KEY_JPEG;
} else if ((data[0]==0x89) && (data[1]==0x50) && (data[2]==0x4E)) {
key.type = GF_QT_KEY_PNG;
}
}
} else {
char *force_str=NULL;
if (val[0] == 'S') {
force_str = (char*)val+1;
val++;
}
char *rect_sep = strchr(val, '@');
char *pos_sep = strchr(val, 'x');
char *arr_sep = strchr(val, ',');
if (rect_sep && pos_sep && (sscanf(val, "%gx%g@%gx%g", &key.value.rect.x, &key.value.rect.y, &key.value.rect.w, &key.value.rect.h) == 4)) {
key.type = GF_QT_KEY_RECTF;
} else if (rect_sep && (sscanf(val, "%g@%g", &key.value.pos_size.x, &key.value.pos_size.y)==2)) {
key.type = GF_QT_KEY_SIZEF;
} else if (pos_sep && (sscanf(val, "%gx%g", &key.value.pos_size.x, &key.value.pos_size.y)==2)) {
key.type = GF_QT_KEY_POINTF;
} else if (arr_sep && strchr(arr_sep, ',')
&& (sscanf(val, "%lg,%lg,%lg,%lg,%lg,%lg,%lg,%lg,%lg", &key.value.matrix[0], &key.value.matrix[1], &key.value.matrix[2]
, &key.value.matrix[3], &key.value.matrix[4], &key.value.matrix[5]
, &key.value.matrix[6], &key.value.matrix[7], &key.value.matrix[8]
)==9)
) {
key.type = GF_QT_KEY_MATRIXF;
} else {
Bool force_flt=GF_FALSE,force_dbl=GF_FALSE,force_sign=GF_FALSE;
u32 force_size=0;
while (1) {
char c = val[0];
if (c=='f') force_flt = GF_TRUE;
else if (c=='d') force_dbl = GF_TRUE;
else if (c=='+') force_sign = GF_TRUE;
else if (c=='b') force_size = 1;
else if (c=='s') force_size = 2;
else if (c=='l') force_size = 3;
else if (c=='L') force_size = 4;
else
break;
val++;
}
if (arr_sep || strchr(val, '.') || force_dbl || force_flt) {
Double res;
if (sscanf(val, "%lg", &res)==1) {
key.value.number = res;
key.type = force_flt ? GF_QT_KEY_FLOAT : GF_QT_KEY_DOUBLE;
}
}
else {
s64 res;
if (sscanf(val, LLD, &res)==1) {
key.value.sint = res;
if (!force_sign && (key.value.sint>0)) {
key.value.uint = key.value.sint;
if (force_size==1) key.type = GF_QT_KEY_UNSIGNED_8;
else if (force_size==2) key.type = GF_QT_KEY_UNSIGNED_16;
else if (force_size==3) key.type = GF_QT_KEY_UNSIGNED_32;
else if (force_size==4) key.type = GF_QT_KEY_UNSIGNED_64;
else key.type = GF_QT_KEY_UNSIGNED_VSIZE;
} else {
if (force_size==1) key.type = GF_QT_KEY_SIGNED_8;
else if (force_size==2) key.type = GF_QT_KEY_SIGNED_16;
else if (force_size==3) key.type = GF_QT_KEY_SIGNED_32;
else if (force_size==4) key.type = GF_QT_KEY_SIGNED_64;
else key.type = GF_QT_KEY_SIGNED_VSIZE;
}
}
}
}
if (force_str && (key.type != GF_QT_KEY_UTF8)) {
key.type = GF_QT_KEY_UTF8;
key.value.string = force_str;
}
}
}
e = gf_isom_set_qt_key(movie, &key);
if (data) gf_free(data);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Failed to add key %s: %s\n", name, gf_error_to_string(e) ));
}
return e;
}
#endif // GPAC_DISABLE_ISOM_WRITE
#endif //GPAC_DISABLE_ISOM