| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2026 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / Scene Management 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/scene_manager.h> | |
| #include <gpac/constants.h> | |
| #include <gpac/media_tools.h> | |
| #include <gpac/bifs.h> | |
| #include <gpac/xml.h> | |
| #include <gpac/internal/scenegraph_dev.h> | |
| #include <gpac/network.h> | |
| GF_EXPORT | |
| GF_SceneManager *gf_sm_new(GF_SceneGraph *graph) | |
| { | |
| GF_SceneManager *tmp; | |
| if (!graph) return NULL; | |
| GF_SAFEALLOC(tmp, GF_SceneManager); | |
| if (!tmp) return NULL; | |
| tmp->streams = gf_list_new(); | |
| tmp->scene_graph = graph; | |
| return tmp; | |
| } | |
| GF_EXPORT | |
| GF_StreamContext *gf_sm_stream_new(GF_SceneManager *ctx, u16 ES_ID, u8 streamType, u32 codec_id) | |
| { | |
| u32 i; | |
| GF_StreamContext *tmp; | |
| i=0; | |
| while ((tmp = (GF_StreamContext*)gf_list_enum(ctx->streams, &i))) { | |
| /*we MUST use the same ST*/ | |
| if (tmp->streamType!=streamType) continue; | |
| /*if no ESID/OTI specified this is a base layer (default stream created by parsers) | |
| if ESID/OTI specified this is a stream already setup | |
| */ | |
| if ( tmp->ESID==ES_ID ) { | |
| //tmp->objectType = objectType; | |
| return tmp; | |
| } | |
| } | |
| GF_SAFEALLOC(tmp, GF_StreamContext); | |
| if (!tmp) return NULL; | |
| tmp->AUs = gf_list_new(); | |
| tmp->ESID = ES_ID; | |
| tmp->streamType = streamType; | |
| tmp->codec_id = codec_id ? codec_id : 1; | |
| tmp->timeScale = 1000; | |
| gf_list_add(ctx->streams, tmp); | |
| return tmp; | |
| } | |
| GF_StreamContext *gf_sm_stream_find(GF_SceneManager *ctx, u16 ES_ID) | |
| { | |
| u32 i, count; | |
| if (!ES_ID) return NULL; | |
| count = gf_list_count(ctx->streams); | |
| for (i=0; i<count; i++) { | |
| GF_StreamContext *tmp = (GF_StreamContext *)gf_list_get(ctx->streams, i); | |
| if (tmp->ESID==ES_ID) return tmp; | |
| } | |
| return NULL; | |
| } | |
| GF_EXPORT | |
| GF_MuxInfo *gf_sm_get_mux_info(GF_ESD *src) | |
| { | |
| u32 i; | |
| GF_MuxInfo *mux; | |
| i=0; | |
| while ((mux = (GF_MuxInfo *)gf_list_enum(src->extensionDescriptors, &i))) { | |
| if (mux->tag == GF_ODF_MUXINFO_TAG) return mux; | |
| } | |
| return NULL; | |
| } | |
| static void gf_sm_au_del(GF_StreamContext *sc, GF_AUContext *au) | |
| { | |
| while (gf_list_count(au->commands)) { | |
| void *comptr = gf_list_last(au->commands); | |
| gf_list_rem_last(au->commands); | |
| switch (sc->streamType) { | |
| case GF_STREAM_OD: | |
| gf_odf_com_del((GF_ODCom**) & comptr); | |
| break; | |
| case GF_STREAM_SCENE: | |
| gf_sg_command_del((GF_Command *)comptr); | |
| break; | |
| } | |
| } | |
| gf_list_del(au->commands); | |
| gf_free(au); | |
| } | |
| static void gf_sm_reset_stream(GF_StreamContext *sc) | |
| { | |
| while (gf_list_count(sc->AUs)) { | |
| GF_AUContext *au = (GF_AUContext *)gf_list_last(sc->AUs); | |
| gf_list_rem_last(sc->AUs); | |
| gf_sm_au_del(sc, au); | |
| } | |
| } | |
| static void gf_sm_delete_stream(GF_StreamContext *sc) | |
| { | |
| gf_sm_reset_stream(sc); | |
| gf_list_del(sc->AUs); | |
| if (sc->name) gf_free(sc->name); | |
| if (sc->dec_cfg) gf_free(sc->dec_cfg); | |
| gf_free(sc); | |
| } | |
| GF_EXPORT | |
| void gf_sm_del(GF_SceneManager *ctx) | |
| { | |
| u32 count; | |
| while ( (count = gf_list_count(ctx->streams)) ) { | |
| GF_StreamContext *sc = (GF_StreamContext *)gf_list_get(ctx->streams, count-1); | |
| gf_list_rem(ctx->streams, count-1); | |
| gf_sm_delete_stream(sc); | |
| } | |
| gf_list_del(ctx->streams); | |
| if (ctx->root_od) gf_odf_desc_del((GF_Descriptor *) ctx->root_od); | |
| gf_free(ctx); | |
| } | |
| #if 0 //unused | |
| void gf_sm_reset(GF_SceneManager *ctx) | |
| { | |
| GF_StreamContext *sc; | |
| u32 i=0; | |
| while ( (sc = gf_list_enum(ctx->streams, &i)) ) { | |
| gf_sm_reset_stream(sc); | |
| } | |
| if (ctx->root_od) gf_odf_desc_del((GF_Descriptor *) ctx->root_od); | |
| ctx->root_od = NULL; | |
| } | |
| #endif | |
| GF_EXPORT | |
| GF_AUContext *gf_sm_stream_au_new(GF_StreamContext *stream, u64 timing, Double time_sec, Bool isRap) | |
| { | |
| u32 i; | |
| GF_AUContext *tmp; | |
| u64 tmp_timing; | |
| tmp_timing = timing ? timing : (u64) (time_sec*1000); | |
| if (stream->imp_exp_time >= tmp_timing) { | |
| /*look for existing AU*/ | |
| i=0; | |
| while ((tmp = (GF_AUContext *)gf_list_enum(stream->AUs, &i))) { | |
| if (timing && (tmp->timing==timing)) return tmp; | |
| else if (time_sec && (tmp->timing_sec == time_sec)) return tmp; | |
| else if (!time_sec && !timing && !tmp->timing && !tmp->timing_sec) return tmp; | |
| /*insert AU*/ | |
| else if ((time_sec && time_sec<tmp->timing_sec) || (timing && timing<tmp->timing)) { | |
| tmp = gf_malloc(sizeof(GF_AUContext)); | |
| if (!tmp) return NULL; | |
| memset(tmp, 0, sizeof(GF_AUContext)); | |
| tmp->commands = gf_list_new(); | |
| if (isRap) tmp->flags = GF_SM_AU_RAP; | |
| tmp->timing = timing; | |
| tmp->timing_sec = time_sec; | |
| tmp->owner = stream; | |
| gf_list_insert(stream->AUs, tmp, i-1); | |
| return tmp; | |
| } | |
| } | |
| } | |
| GF_SAFEALLOC(tmp, GF_AUContext); | |
| if (!tmp) return NULL; | |
| tmp->commands = gf_list_new(); | |
| if (isRap) tmp->flags = GF_SM_AU_RAP; | |
| tmp->timing = timing; | |
| tmp->timing_sec = time_sec; | |
| tmp->owner = stream; | |
| if (stream->disable_aggregation) tmp->flags |= GF_SM_AU_NOT_AGGREGATED; | |
| gf_list_add(stream->AUs, tmp); | |
| stream->imp_exp_time = tmp_timing; | |
| return tmp; | |
| } | |
| static Bool node_in_commands_subtree(GF_Node *node, GF_List *commands) | |
| { | |
| #ifndef GPAC_DISABLE_VRML | |
| u32 i, j, count, nb_fields; | |
| count = gf_list_count(commands); | |
| for (i=0; i<count; i++) { | |
| GF_Command *com = gf_list_get(commands, i); | |
| if (com->tag>=GF_SG_LAST_BIFS_COMMAND) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[Scene Manager] Command check for LASeR/DIMS not supported\n")); | |
| return 0; | |
| } | |
| if (com->tag==GF_SG_SCENE_REPLACE) { | |
| if (gf_node_parent_of(com->node, node)) return 1; | |
| continue; | |
| } | |
| nb_fields = gf_list_count(com->command_fields); | |
| for (j=0; j<nb_fields; j++) { | |
| GF_CommandField *field = gf_list_get(com->command_fields, j); | |
| switch (field->fieldType) { | |
| case GF_SG_VRML_SFNODE: | |
| if (field->new_node) { | |
| if (gf_node_parent_of(field->new_node, node)) return 1; | |
| } | |
| break; | |
| case GF_SG_VRML_MFNODE: | |
| if (field->field_ptr) { | |
| GF_ChildNodeItem *child; | |
| child = field->node_list; | |
| while (child) { | |
| if (gf_node_parent_of(child->node, node)) return 1; | |
| child = child->next; | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| #endif | |
| return 0; | |
| } | |
| static u32 store_or_aggregate(GF_StreamContext *sc, GF_Command *com, GF_List *commands, Bool *has_modif) | |
| { | |
| #ifndef GPAC_DISABLE_VRML | |
| u32 i, count, j, nb_fields; | |
| GF_CommandField *field, *check_field; | |
| /*if our command deals with a node inserted in the commands list, apply command list*/ | |
| if (node_in_commands_subtree(com->node, commands)) return 0; | |
| /*otherwise, check if we can substitute a previous command with this one*/ | |
| count = gf_list_count(commands); | |
| for (i=0; i<count; i++) { | |
| GF_Command *check = gf_list_get(commands, i); | |
| if (sc->streamType == GF_STREAM_SCENE) { | |
| Bool check_index=0; | |
| Bool original_is_index = 0; | |
| Bool apply; | |
| switch (com->tag) { | |
| case GF_SG_INDEXED_REPLACE: | |
| check_index=1; | |
| case GF_SG_MULTIPLE_INDEXED_REPLACE: | |
| case GF_SG_FIELD_REPLACE: | |
| case GF_SG_MULTIPLE_REPLACE: | |
| if (check->node != com->node) break; | |
| /*we may aggregate an indexed insertion and a replace one*/ | |
| if (check_index) { | |
| if (check->tag == GF_SG_INDEXED_REPLACE) {} | |
| else if (check->tag == GF_SG_INDEXED_INSERT) { | |
| original_is_index = 1; | |
| } | |
| else { | |
| break; | |
| } | |
| } else { | |
| if (check->tag != com->tag) break; | |
| } | |
| nb_fields = gf_list_count(com->command_fields); | |
| if (gf_list_count(check->command_fields) != nb_fields) break; | |
| apply=1; | |
| for (j=0; j<nb_fields; j++) { | |
| field = gf_list_get(com->command_fields, j); | |
| check_field = gf_list_get(check->command_fields, j); | |
| if ((field->pos != check_field->pos) || (field->fieldIndex != check_field->fieldIndex)) { | |
| apply=0; | |
| break; | |
| } | |
| } | |
| /*same target node+fields, destroy first command and store new one*/ | |
| if (apply) { | |
| /*if indexed, change command tag*/ | |
| if (original_is_index) com->tag = GF_SG_INDEXED_INSERT; | |
| gf_sg_command_del((GF_Command *)check); | |
| gf_list_rem(commands, i); | |
| if (has_modif) *has_modif = 1; | |
| return 1; | |
| } | |
| break; | |
| case GF_SG_NODE_REPLACE: | |
| if (check->tag != GF_SG_NODE_REPLACE) { | |
| break; | |
| } | |
| /*TODO - THIS IS NOT SUPPORTED IN GPAC SINCE WE NEVER ALLOW FOR DUPLICATE NODE IDs IN THE SCENE !!!*/ | |
| if (gf_node_get_id(check->node) != gf_node_get_id(com->node) ) { | |
| break; | |
| } | |
| /*same node ID, destroy first command and store new one*/ | |
| gf_sg_command_del((GF_Command *)check); | |
| gf_list_rem(commands, i); | |
| if (has_modif) *has_modif = 1; | |
| return 1; | |
| case GF_SG_INDEXED_DELETE: | |
| /*look for an indexed insert before the indexed delete with same target pos and node. If found, discard both commands!*/ | |
| if (check->tag != GF_SG_INDEXED_INSERT) break; | |
| if (com->node != check->node) break; | |
| field = gf_list_get(com->command_fields, 0); | |
| check_field = gf_list_get(check->command_fields, 0); | |
| if (!field || !check_field) break; | |
| if (field->pos != check_field->pos) break; | |
| if (field->fieldIndex != check_field->fieldIndex) break; | |
| gf_sg_command_del((GF_Command *)check); | |
| gf_list_rem(commands, i); | |
| if (has_modif) *has_modif = 1; | |
| return 2; | |
| default: | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[Scene Manager] Stream Aggregation not implemented for command - aggregating on main scene\n")); | |
| break; | |
| } | |
| } | |
| } | |
| /*the command modifies another stream than associated current carousel stream, we have to store it.*/ | |
| if (has_modif) *has_modif=1; | |
| #endif | |
| return 1; | |
| } | |
| static GF_StreamContext *gf_sm_get_stream(GF_SceneManager *ctx, u16 ESID) | |
| { | |
| u32 i, count; | |
| count = gf_list_count(ctx->streams); | |
| for (i=0; i<count; i++) { | |
| GF_StreamContext *sc = gf_list_get(ctx->streams, i); | |
| if (sc->ESID==ESID) return sc; | |
| } | |
| return NULL; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_sm_aggregate(GF_SceneManager *ctx, u16 ESID) | |
| { | |
| GF_Err e; | |
| u32 i, stream_count; | |
| #ifndef GPAC_DISABLE_VRML | |
| u32 j; | |
| GF_AUContext *au; | |
| GF_Command *com; | |
| #endif | |
| e = GF_OK; | |
| #if DEBUG_RAP | |
| com_count = 0; | |
| stream_count = gf_list_count(ctx->streams); | |
| for (i=0; i<stream_count; i++) { | |
| GF_StreamContext *sc = (GF_StreamContext *)gf_list_get(ctx->streams, i); | |
| if (sc->streamType == GF_STREAM_SCENE) { | |
| au_count = gf_list_count(sc->AUs); | |
| for (j=0; j<au_count; j++) { | |
| au = (GF_AUContext *)gf_list_get(sc->AUs, j); | |
| com_count += gf_list_count(au->commands); | |
| } | |
| } | |
| } | |
| GF_LOG(GF_LOG_INFO, GF_LOG_SCENE, ("[SceneManager] Making RAP with %d commands\n", com_count)); | |
| #endif | |
| stream_count = gf_list_count(ctx->streams); | |
| for (i=0; i<stream_count; i++) { | |
| GF_AUContext *carousel_au; | |
| GF_List *carousel_commands; | |
| GF_StreamContext *aggregate_on_stream; | |
| GF_StreamContext *sc = (GF_StreamContext *)gf_list_get(ctx->streams, i); | |
| if (ESID && (sc->ESID!=ESID)) continue; | |
| /*locate the AU in which our commands will be aggregated*/ | |
| carousel_au = NULL; | |
| carousel_commands = NULL; | |
| aggregate_on_stream = sc->aggregate_on_esid ? gf_sm_get_stream(ctx, sc->aggregate_on_esid) : NULL; | |
| if (aggregate_on_stream==sc) { | |
| carousel_commands = gf_list_new(); | |
| } else if (aggregate_on_stream) { | |
| if (!gf_list_count(aggregate_on_stream->AUs)) { | |
| carousel_au = gf_sm_stream_au_new(aggregate_on_stream, 0, 0, 1); | |
| } else { | |
| /* assert we already performed aggregation */ | |
| gf_assert(gf_list_count(aggregate_on_stream->AUs)==1); | |
| carousel_au = gf_list_get(aggregate_on_stream->AUs, 0); | |
| } | |
| carousel_commands = carousel_au->commands; | |
| } | |
| /*TODO - do this as well for ODs*/ | |
| #ifndef GPAC_DISABLE_VRML | |
| if (sc->streamType == GF_STREAM_SCENE) { | |
| Bool has_modif = 0; | |
| /*we check for each stream if it is a base stream (SceneReplace ...) - several streams may carry RAPs if inline nodes are used*/ | |
| Bool base_stream_found = 0; | |
| /*in DIMS we use an empty initial AU with no commands to signal the RAP*/ | |
| if (sc->codec_id == GF_CODECID_DIMS) base_stream_found = 1; | |
| /*apply all commands - this will also apply the SceneReplace*/ | |
| while (gf_list_count(sc->AUs)) { | |
| u32 count; | |
| au = (GF_AUContext *) gf_list_get(sc->AUs, 0); | |
| gf_list_rem(sc->AUs, 0); | |
| /*AU not aggregated*/ | |
| if (au->flags & GF_SM_AU_NOT_AGGREGATED) { | |
| gf_sm_au_del(sc, au); | |
| continue; | |
| } | |
| count = gf_list_count(au->commands); | |
| for (j=0; j<count; j++) { | |
| u32 store=0; | |
| com = gf_list_get(au->commands, j); | |
| if (!base_stream_found) { | |
| switch (com->tag) { | |
| case GF_SG_SCENE_REPLACE: | |
| case GF_SG_LSR_NEW_SCENE: | |
| case GF_SG_LSR_REFRESH_SCENE: | |
| base_stream_found = 1; | |
| break; | |
| } | |
| } | |
| /*aggregate the command*/ | |
| /*if stream doesn't carry a carousel or carries the base carousel (scene replace), always apply the command*/ | |
| if (base_stream_found || !sc->aggregate_on_esid) { | |
| store = 0; | |
| } | |
| /*otherwise, check whether the command should be kept in this stream as is, or can be aggregated on this stream*/ | |
| else { | |
| switch (com->tag) { | |
| /*the following commands do not impact a sub-tree (eg do not deal with nodes), we cannot | |
| aggregate them... */ | |
| case GF_SG_ROUTE_REPLACE: | |
| case GF_SG_ROUTE_DELETE: | |
| case GF_SG_ROUTE_INSERT: | |
| case GF_SG_PROTO_INSERT: | |
| case GF_SG_PROTO_DELETE: | |
| case GF_SG_PROTO_DELETE_ALL: | |
| case GF_SG_GLOBAL_QUANTIZER: | |
| case GF_SG_LSR_RESTORE: | |
| case GF_SG_LSR_SAVE: | |
| case GF_SG_LSR_SEND_EVENT: | |
| case GF_SG_LSR_CLEAN: | |
| /*todo check in which category to put these commands*/ | |
| // case GF_SG_LSR_ACTIVATE: | |
| // case GF_SG_LSR_DEACTIVATE: | |
| store = 1; | |
| break; | |
| /*other commands: | |
| !!! we need to know if the target node of the command has been inserted in this stream !!! | |
| This is a tedious task, for now we will consider the following cases: | |
| - locate a similar command in the stored list: remove the similar one and aggregate on stream | |
| - by default all AUs are stored if the stream is in aggregate mode - we should fix that by checking insertion points: | |
| if a command apllies on a node that has been inserted in this stream, we can aggregate, otherwise store | |
| */ | |
| default: | |
| /*check if we can directly store the command*/ | |
| if (carousel_commands) | |
| store = store_or_aggregate(sc, com, carousel_commands, &has_modif); | |
| else { | |
| gf_assert(0); | |
| } | |
| break; | |
| } | |
| } | |
| switch (store) { | |
| /*command has been merged with a previous command in carousel and needs to be destroyed*/ | |
| case 2: | |
| gf_list_rem(au->commands, j); | |
| j--; | |
| count--; | |
| gf_sg_command_del((GF_Command *)com); | |
| break; | |
| /*command shall be moved to carousel without being applied*/ | |
| case 1: | |
| gf_list_insert(carousel_commands, com, 0); | |
| gf_list_rem(au->commands, j); | |
| j--; | |
| count--; | |
| break; | |
| /*command can be applied*/ | |
| default: | |
| e = gf_sg_command_apply(ctx->scene_graph, com, 0); | |
| break; | |
| } | |
| } | |
| gf_sm_au_del(sc, au); | |
| } | |
| /*and recreate scene replace*/ | |
| if (base_stream_found) { | |
| au = gf_sm_stream_au_new(sc, 0, 0, 1); | |
| switch (sc->codec_id) { | |
| case GF_CODECID_BIFS: | |
| case GF_CODECID_BIFS_V2: | |
| com = gf_sg_command_new(ctx->scene_graph, GF_SG_SCENE_REPLACE); | |
| break; | |
| case GF_CODECID_LASER: | |
| com = gf_sg_command_new(ctx->scene_graph, GF_SG_LSR_NEW_SCENE); | |
| break; | |
| case GF_CODECID_DIMS: | |
| /* We do not create a new command, empty AU is enough in DIMS*/ | |
| default: | |
| com = NULL; | |
| break; | |
| } | |
| if (com) { | |
| com->node = ctx->scene_graph->RootNode; | |
| ctx->scene_graph->RootNode = NULL; | |
| gf_list_del(com->new_proto_list); | |
| com->new_proto_list = ctx->scene_graph->protos; | |
| ctx->scene_graph->protos = NULL; | |
| /*indicate the command is the aggregated scene graph, so that PROTOs and ROUTEs | |
| are taken from the scenegraph when encoding*/ | |
| com->aggregated = 1; | |
| gf_list_add(au->commands, com); | |
| } | |
| } | |
| /*update carousel flags of the AU*/ | |
| else if (carousel_commands) { | |
| /*if current stream caries its own carousel*/ | |
| if (!carousel_au) { | |
| carousel_au = gf_sm_stream_au_new(sc, 0, 0, 1); | |
| gf_list_del(carousel_au->commands); | |
| carousel_au->commands = carousel_commands; | |
| } | |
| carousel_au->flags |= GF_SM_AU_RAP | GF_SM_AU_CAROUSEL; | |
| if (has_modif) carousel_au->flags |= GF_SM_AU_MODIFIED; | |
| } | |
| } | |
| #endif | |
| } | |
| return e; | |
| } | |
| #ifndef GPAC_DISABLE_LOADER_BT | |
| GF_Err gf_sm_load_init_bt(GF_SceneLoader *load); | |
| #endif | |
| #ifndef GPAC_DISABLE_LOADER_XMT | |
| GF_Err gf_sm_load_init_xmt(GF_SceneLoader *load); | |
| #endif | |
| #ifndef GPAC_DISABLE_LOADER_ISOM | |
| GF_Err gf_sm_load_init_isom(GF_SceneLoader *load); | |
| #endif | |
| #ifndef GPAC_DISABLE_SVG | |
| GF_Err gf_sm_load_init_svg(GF_SceneLoader *load); | |
| #endif | |
| #ifndef GPAC_DISABLE_SWF_IMPORT | |
| GF_Err gf_sm_load_init_swf(GF_SceneLoader *load); | |
| #endif | |
| #ifndef GPAC_DISABLE_QTVR | |
| GF_Err gf_sm_load_init_qt(GF_SceneLoader *load); | |
| #endif | |
| GF_EXPORT | |
| GF_Err gf_sm_load_string(GF_SceneLoader *load, const char *str, Bool do_clean) | |
| { | |
| GF_Err e; | |
| if (!load->type) e = GF_BAD_PARAM; | |
| else if (load->parse_string) e = load->parse_string(load, str); | |
| else { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[Scene Manager] string parsing not supported by loader\n")); | |
| e = GF_NOT_SUPPORTED; | |
| } | |
| return e; | |
| } | |
| /*initializes the context loader*/ | |
| GF_EXPORT | |
| GF_Err gf_sm_load_init(GF_SceneLoader *load) | |
| { | |
| GF_Err e = GF_NOT_SUPPORTED; | |
| char *ext; | |
| /*we need at least a scene graph*/ | |
| if (!load || (!load->ctx && !load->scene_graph) | |
| #ifndef GPAC_DISABLE_ISOM | |
| || (!load->fileName && !load->isom && !(load->flags & GF_SM_LOAD_FOR_PLAYBACK) ) | |
| #endif | |
| ) return GF_BAD_PARAM; | |
| if (!load->type) { | |
| #ifndef GPAC_DISABLE_ISOM | |
| if (load->isom) { | |
| load->type = GF_SM_LOAD_MP4; | |
| } else | |
| #endif | |
| { | |
| char szExt[50]; | |
| ext = gf_file_ext_start(load->fileName); | |
| if (!ext) return GF_NOT_SUPPORTED; | |
| if (strlen(ext) < 2 || strlen(ext) > sizeof(szExt)) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[Scene Manager] invalid extension %s in file name %s\n", ext, load->fileName)); | |
| return GF_NOT_SUPPORTED; | |
| } | |
| gf_strcpy(szExt, &ext[1]); | |
| strlwr(szExt); | |
| if (strstr(szExt, "bt")) load->type = GF_SM_LOAD_BT; | |
| else if (strstr(szExt, "wrl")) load->type = GF_SM_LOAD_VRML; | |
| else if (strstr(szExt, "x3dv")) load->type = GF_SM_LOAD_X3DV; | |
| #ifndef GPAC_DISABLE_LOADER_XMT | |
| else if (strstr(szExt, "xmt") || strstr(szExt, "xmta")) load->type = GF_SM_LOAD_XMTA; | |
| else if (strstr(szExt, "x3d")) load->type = GF_SM_LOAD_X3D; | |
| #endif | |
| else if (strstr(szExt, "swf")) load->type = GF_SM_LOAD_SWF; | |
| else if (strstr(szExt, "mov")) load->type = GF_SM_LOAD_QT; | |
| else if (strstr(szExt, "svg")) load->type = GF_SM_LOAD_SVG; | |
| else if (strstr(szExt, "xsr")) load->type = GF_SM_LOAD_XSR; | |
| else if (strstr(szExt, "xml")) { | |
| char *rtype = gf_xml_get_root_type(load->fileName, &e); | |
| if (rtype) { | |
| if (!strcmp(rtype, "SAFSession")) load->type = GF_SM_LOAD_XSR; | |
| else if (!strcmp(rtype, "XMT-A")) load->type = GF_SM_LOAD_XMTA; | |
| else if (!strcmp(rtype, "X3D")) load->type = GF_SM_LOAD_X3D; | |
| gf_free(rtype); | |
| } | |
| } | |
| } | |
| } | |
| if (!load->type) return e; | |
| if (!load->scene_graph) load->scene_graph = load->ctx->scene_graph; | |
| switch (load->type) { | |
| #ifndef GPAC_DISABLE_LOADER_BT | |
| case GF_SM_LOAD_BT: | |
| case GF_SM_LOAD_VRML: | |
| case GF_SM_LOAD_X3DV: | |
| return gf_sm_load_init_bt(load); | |
| #endif | |
| #ifndef GPAC_DISABLE_LOADER_XMT | |
| case GF_SM_LOAD_XMTA: | |
| case GF_SM_LOAD_X3D: | |
| return gf_sm_load_init_xmt(load); | |
| #endif | |
| #ifndef GPAC_DISABLE_SVG | |
| case GF_SM_LOAD_SVG: | |
| case GF_SM_LOAD_XSR: | |
| case GF_SM_LOAD_DIMS: | |
| return gf_sm_load_init_svg(load); | |
| #endif | |
| #ifndef GPAC_DISABLE_SWF_IMPORT | |
| case GF_SM_LOAD_SWF: | |
| return gf_sm_load_init_swf(load); | |
| #endif | |
| #ifndef GPAC_DISABLE_LOADER_ISOM | |
| case GF_SM_LOAD_MP4: | |
| return gf_sm_load_init_isom(load); | |
| #endif | |
| #ifndef GPAC_DISABLE_QTVR | |
| case GF_SM_LOAD_QT: | |
| return gf_sm_load_init_qt(load); | |
| #endif | |
| default: | |
| return GF_NOT_SUPPORTED; | |
| } | |
| return GF_NOT_SUPPORTED; | |
| } | |
| GF_EXPORT | |
| void gf_sm_load_done(GF_SceneLoader *load) | |
| { | |
| if (load->done) load->done(load); | |
| } | |
| GF_EXPORT | |
| GF_Err gf_sm_load_run(GF_SceneLoader *load) | |
| { | |
| if (load->process) return load->process(load); | |
| return GF_OK; | |
| } | |
| #if 0 //unused | |
| GF_Err gf_sm_load_suspend(GF_SceneLoader *load, Bool suspend) | |
| { | |
| if (load->suspend) return load->suspend(load, suspend); | |
| return GF_OK; | |
| } | |
| #endif | |
| #if !defined(GPAC_DISABLE_LOADER_BT) || !defined(GPAC_DISABLE_LOADER_XMT) | |
| #include <gpac/base_coding.h> | |
| void gf_sm_update_bitwrapper_buffer(GF_Node *node, const char *fileName) | |
| { | |
| u32 data_size = 0; | |
| char *data = NULL; | |
| char *buffer; | |
| M_BitWrapper *bw = (M_BitWrapper *)node; | |
| if (!bw->buffer.buffer) return; | |
| buffer = bw->buffer.buffer; | |
| if (!strnicmp(buffer, "file:", 5) ) { | |
| char *url; | |
| if (!strnicmp(buffer, "file://", 7)) buffer += 7; | |
| else buffer += 5; | |
| url = gf_url_concatenate(fileName, buffer); | |
| if (url) { | |
| if (gf_file_load_data(url, (u8 **) &data, &data_size) != GF_OK) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[Scene Manager] error reading bitwrapper file %s\n", url)); | |
| } | |
| gf_free(url); | |
| } | |
| } else { | |
| Bool base_64 = 0; | |
| if (!strnicmp(buffer, "data:application/octet-string", 29)) { | |
| char *sep = strchr(bw->buffer.buffer, ','); | |
| base_64 = strstr(bw->buffer.buffer, ";base64") ? 1 : 0; | |
| if (sep) buffer = sep+1; | |
| } | |
| if (base_64) { | |
| data_size = 2 * (u32) strlen(buffer); | |
| data = (char*)gf_malloc(sizeof(char)*data_size); | |
| if (data) | |
| data_size = gf_base64_decode(buffer, (u32) strlen(buffer), data, data_size); | |
| } else { | |
| u32 i, c; | |
| if (!strnicmp(buffer, "0x", 2)) buffer += 2; | |
| data_size = (u32) strlen(buffer) / 2; | |
| data = (char*)gf_malloc(sizeof(char) * data_size); | |
| if (data) { | |
| char s[3]; | |
| s[2] = 0; | |
| for (i=0; i<data_size; i++) { | |
| s[0] = buffer[2*i]; | |
| s[1] = buffer[2*i+1]; | |
| sscanf(s, "%02X", &c); | |
| data[i] = (unsigned char) c; | |
| } | |
| } | |
| } | |
| } | |
| gf_free(bw->buffer.buffer); | |
| bw->buffer.buffer = NULL; | |
| bw->buffer_len = 0; | |
| if (data) { | |
| bw->buffer.buffer = data; | |
| bw->buffer_len = data_size; | |
| } | |
| } | |
| #endif //!defined(GPAC_DISABLE_LOADER_BT) || !defined(GPAC_DISABLE_LOADER_XMT) | |