1 /*
2 * GPAC Multimedia Framework
3 *
4 * Authors: Jean Le Feuvre, Cyril Concolato
5 * Copyright (c) Telecom ParisTech 2000-2012
6 * All rights reserved
7 *
8 * This file is part of GPAC / ISO Media File Format sub-project
9 *
10 * GPAC is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
13 * any later version.
14 *
15 * GPAC is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; see the file COPYING. If not, write to
22 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 */
25
26 #include <gpac/scene_engine.h>
27
28 #ifndef GPAC_DISABLE_SENG
29 #include <gpac/scene_manager.h>
30
31 #ifndef GPAC_DISABLE_BIFS_ENC
32 #include <gpac/bifs.h>
33 #endif
34
35 #ifndef GPAC_DISABLE_VRML
36 #include <gpac/nodes_mpeg4.h>
37 #endif
38
39 #ifndef GPAC_DISABLE_LASER
40 #include <gpac/laser.h>
41 #endif
42
43 #include <gpac/constants.h>
44 #include <gpac/base_coding.h>
45
46
47 struct __tag_scene_engine
48 {
49 GF_SceneGraph *sg;
50 GF_SceneManager *ctx;
51 GF_SceneLoader loader;
52 void *calling_object;
53 Bool owns_context;
54 #ifndef GPAC_DISABLE_BIFS_ENC
55 GF_BifsEncoder *bifsenc;
56 #endif
57 #ifndef GPAC_DISABLE_LASER
58 GF_LASeRCodec *lsrenc;
59 #endif
60
61 u32 start_time;
62
63 char *dump_path;
64
65 Bool embed_resources;
66 Bool dump_rap;
67 Bool first_dims_sent;
68 };
69
70 #ifndef GPAC_DISABLE_BIFS_ENC
gf_sm_setup_bifsenc(GF_SceneEngine * seng,GF_StreamContext * sc,GF_ESD * esd)71 static GF_Err gf_sm_setup_bifsenc(GF_SceneEngine *seng, GF_StreamContext *sc, GF_ESD *esd)
72 {
73 u8 *data;
74 u32 data_len;
75 u32 nbb;
76 Bool encode_names, delete_bcfg;
77 GF_BIFSConfig *bcfg;
78
79 if (!esd->decoderConfig || (esd->decoderConfig->streamType != GF_STREAM_SCENE)) return GF_BAD_PARAM;
80
81 if (!seng->bifsenc)
82 seng->bifsenc = gf_bifs_encoder_new(seng->ctx->scene_graph);
83
84 delete_bcfg = 0;
85 /*inputctx is not properly setup, do it*/
86 if (!esd->decoderConfig->decoderSpecificInfo) {
87 bcfg = (GF_BIFSConfig*)gf_odf_desc_new(GF_ODF_BIFS_CFG_TAG);
88 bcfg->pixelMetrics = seng->ctx->is_pixel_metrics;
89 bcfg->pixelWidth = seng->ctx->scene_width;
90 bcfg->pixelHeight = seng->ctx->scene_height;
91 delete_bcfg = 1;
92 }
93 /*regular case*/
94 else if (esd->decoderConfig->decoderSpecificInfo->tag == GF_ODF_BIFS_CFG_TAG) {
95 bcfg = (GF_BIFSConfig *)esd->decoderConfig->decoderSpecificInfo;
96 }
97 /*happens when loading from MP4 in which case BIFSc is not decoded*/
98 else {
99 bcfg = gf_odf_get_bifs_config(esd->decoderConfig->decoderSpecificInfo, esd->decoderConfig->objectTypeIndication);
100 delete_bcfg = 1;
101 }
102
103 /*NO CHANGE TO BIFSC otherwise the generated update will not match the input context
104 The only case we modify the bifs config is when XXXBits is not specified*/
105 nbb = gf_get_bit_size(seng->ctx->max_node_id);
106 if (!bcfg->nodeIDbits) bcfg->nodeIDbits = nbb;
107 else if (bcfg->nodeIDbits<nbb) {
108 GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("[BIFS] BIFSConfig.NodeIDBits too small (%d bits vs %d nodes)\n", bcfg->nodeIDbits, seng->ctx->max_node_id));
109 }
110 nbb = gf_get_bit_size(seng->ctx->max_route_id);
111 if (!bcfg->routeIDbits) bcfg->routeIDbits = nbb;
112 else if (bcfg->routeIDbits<nbb) {
113 GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("[BIFS] BIFSConfig.RouteIDBits too small (%d bits vs %d routes)\n", bcfg->routeIDbits, seng->ctx->max_route_id));
114 }
115 nbb = gf_get_bit_size(seng->ctx->max_proto_id);
116 if (!bcfg->protoIDbits) bcfg->protoIDbits=nbb;
117 else if (bcfg->protoIDbits<nbb) {
118 GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("[BIFS] BIFSConfig.ProtoIDBits too small (%d bits vs %d protos)\n", bcfg->protoIDbits, seng->ctx->max_proto_id));
119 }
120
121 /*this is the real pb, not stored in cfg or file level, set at EACH replaceScene*/
122 encode_names = 0;
123
124 /* The BIFS Config that is passed here should be the BIFSConfig from the IOD */
125 gf_bifs_encoder_new_stream(seng->bifsenc, esd->ESID, bcfg, encode_names, 0);
126 if (delete_bcfg) gf_odf_desc_del((GF_Descriptor *)bcfg);
127
128 gf_bifs_encoder_get_config(seng->bifsenc, esd->ESID, &data, &data_len);
129
130 if (esd->decoderConfig->decoderSpecificInfo) gf_odf_desc_del((GF_Descriptor *) esd->decoderConfig->decoderSpecificInfo);
131 esd->decoderConfig->decoderSpecificInfo = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG);
132 esd->decoderConfig->decoderSpecificInfo->data = data;
133 esd->decoderConfig->decoderSpecificInfo->dataLength = data_len;
134
135 sc->dec_cfg = gf_malloc(sizeof(char)*data_len);
136 memcpy(sc->dec_cfg, data, data_len);
137 sc->dec_cfg_len = data_len;
138
139 esd->decoderConfig->objectTypeIndication = gf_bifs_encoder_get_version(seng->bifsenc, esd->ESID);
140
141 return GF_OK;
142 }
143 #endif /*GPAC_DISABLE_BIFS_ENC*/
144
145 #ifndef GPAC_DISABLE_LASER
gf_sm_setup_lsrenc(GF_SceneEngine * seng,GF_StreamContext * sc,GF_ESD * esd)146 static GF_Err gf_sm_setup_lsrenc(GF_SceneEngine *seng, GF_StreamContext *sc, GF_ESD *esd)
147 {
148 u8 *data;
149 u32 data_len;
150 GF_LASERConfig lsr_cfg;
151
152 if (!esd->decoderConfig || (esd->decoderConfig->streamType != GF_STREAM_SCENE)) return GF_BAD_PARAM;
153
154 seng->lsrenc = gf_laser_encoder_new(seng->ctx->scene_graph);
155
156 /*inputctx is not properly setup, do it*/
157 if (!esd->decoderConfig->decoderSpecificInfo) {
158 memset(&lsr_cfg, 0, sizeof(GF_LASERConfig));
159 }
160 /*regular case*/
161 else if (esd->decoderConfig->decoderSpecificInfo->tag == GF_ODF_LASER_CFG_TAG) {
162 memcpy(&lsr_cfg, (GF_LASERConfig *)esd->decoderConfig->decoderSpecificInfo, sizeof(GF_LASERConfig) );
163 }
164 /*happens when loading from MP4 in which case BIFSc is not decoded*/
165 else {
166 gf_odf_get_laser_config(esd->decoderConfig->decoderSpecificInfo, &lsr_cfg);
167 }
168
169 gf_laser_encoder_new_stream(seng->lsrenc, esd->ESID , &lsr_cfg);
170
171 gf_laser_encoder_get_config(seng->lsrenc, esd->ESID, &data, &data_len);
172
173 if (esd->decoderConfig->decoderSpecificInfo) gf_odf_desc_del((GF_Descriptor *) esd->decoderConfig->decoderSpecificInfo);
174 esd->decoderConfig->decoderSpecificInfo = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG);
175 esd->decoderConfig->decoderSpecificInfo->data = data;
176 esd->decoderConfig->decoderSpecificInfo->dataLength = data_len;
177
178 sc->dec_cfg = (char*)gf_malloc(sizeof(char)*data_len);
179 memcpy(sc->dec_cfg, data, data_len);
180 sc->dec_cfg_len = data_len;
181 return GF_OK;
182 }
183 #endif /*GPAC_DISABLE_LASER*/
184
gf_sm_live_setup(GF_SceneEngine * seng)185 static GF_Err gf_sm_live_setup(GF_SceneEngine *seng)
186 {
187 GF_Err e;
188 GF_StreamContext *sc;
189 GF_InitialObjectDescriptor *iod;
190 GF_ESD *esd;
191 u32 i, j;
192
193 e = GF_OK;
194
195 iod = (GF_InitialObjectDescriptor *) seng->ctx->root_od;
196
197 /*build an IOD*/
198 if (!iod) {
199 seng->ctx->root_od = (GF_ObjectDescriptor*) gf_odf_desc_new(GF_ODF_IOD_TAG);
200 iod = (GF_InitialObjectDescriptor *) seng->ctx->root_od;
201
202 i=0;
203 while ((sc = gf_list_enum(seng->ctx->streams, &i))) {
204 if (sc->streamType != GF_STREAM_SCENE) continue;
205
206 if (!sc->ESID) sc->ESID = 1;
207
208 esd = gf_odf_desc_esd_new(2);
209 gf_odf_desc_del((GF_Descriptor *) esd->decoderConfig->decoderSpecificInfo);
210 esd->decoderConfig->decoderSpecificInfo = NULL;
211 esd->ESID = sc->ESID;
212 esd->decoderConfig->streamType = GF_STREAM_SCENE;
213 esd->decoderConfig->objectTypeIndication = sc->codec_id;
214 gf_list_add(iod->ESDescriptors, esd);
215
216 if (!sc->timeScale) sc->timeScale = 1000;
217 esd->slConfig->timestampResolution = sc->timeScale;
218 }
219 }
220
221 i=0;
222 while ((sc = (GF_StreamContext*)gf_list_enum(seng->ctx->streams, &i))) {
223
224 j=0;
225 while ((esd = gf_list_enum(seng->ctx->root_od->ESDescriptors, &j))) {
226 if (sc->ESID==esd->ESID) {
227 break;
228 }
229 }
230 if (!esd) continue;
231
232 if (!esd->slConfig) esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG);
233 if (!esd->slConfig->timestampResolution) esd->slConfig->timestampResolution = 1000;
234 if (!sc->timeScale) sc->timeScale = esd->slConfig->timestampResolution;
235
236
237 if (sc->streamType == GF_STREAM_SCENE) {
238 switch (sc->codec_id) {
239 #ifndef GPAC_DISABLE_BIFS_ENC
240 case GF_CODECID_BIFS:
241 case GF_CODECID_BIFS_V2:
242 e = gf_sm_setup_bifsenc(seng, sc, esd);
243 break;
244 #endif
245
246 #ifndef GPAC_DISABLE_LASER
247 case GF_CODECID_LASER:
248 e = gf_sm_setup_lsrenc(seng, sc, esd);
249 break;
250 #endif
251 case GF_CODECID_DIMS:
252 /* Nothing to be done here */
253 break;
254 default:
255 e = GF_NOT_SUPPORTED;
256 break;
257 }
258 if (e) return e;
259 }
260 }
261 return e;
262 }
263
264
265 GF_EXPORT
gf_seng_enable_aggregation(GF_SceneEngine * seng,u16 ESID,u16 onESID)266 GF_Err gf_seng_enable_aggregation(GF_SceneEngine *seng, u16 ESID, u16 onESID)
267 {
268 GF_StreamContext *sc;
269
270 if (ESID) {
271 u32 i=0;
272 while (NULL != (sc = (GF_StreamContext*)gf_list_enum(seng->ctx->streams, &i))) {
273 if (0 != (sc->ESID==ESID)) break;
274 }
275 } else {
276 sc = (GF_StreamContext*)gf_list_get(seng->ctx->streams, 0);
277 }
278 if (!sc) return GF_STREAM_NOT_FOUND;
279
280 sc->aggregate_on_esid = onESID;
281 return GF_OK;
282 }
283
284 /* Set to 1 if you want every dump with a timed file name */
285 //#define DUMP_DIMS_LOG_WITH_TIME
286
gf_seng_encode_dims_au(GF_SceneEngine * seng,u16 ESID,GF_List * commands,u8 ** data,u32 * size,Bool compress_dims)287 static GF_Err gf_seng_encode_dims_au(GF_SceneEngine *seng, u16 ESID, GF_List *commands, u8 **data, u32 *size, Bool compress_dims)
288 {
289 #ifndef GPAC_DISABLE_SCENE_DUMP
290 GF_SceneDumper *dumper = NULL;
291 #endif
292 GF_Err e;
293 char rad_name[4096];
294 char file_name[4096+100];
295 u32 fsize;
296 u8 *buffer = NULL;
297 GF_BitStream *bs = NULL;
298 u8 dims_header;
299 #ifdef DUMP_DIMS_LOG_WITH_TIME
300 u32 do_dump_with_time = 1;
301 #endif
302 u32 buffer_len;
303 const char *cache_dir;
304 char *dump_name;
305
306 if (!data) return GF_BAD_PARAM;
307
308 if (!seng->dump_path) cache_dir = gf_get_default_cache_directory();
309 else cache_dir = seng->dump_path;
310
311 dump_name = "gpac_scene_engine_dump";
312
313 #ifdef DUMP_DIMS_LOG_WITH_TIME
314 start:
315 #endif
316
317 if (commands && gf_list_count(commands)) {
318 sprintf(rad_name, "%s%c%s%s", cache_dir, GF_PATH_SEPARATOR, dump_name, "_update");
319 } else {
320 #ifndef DUMP_DIMS_LOG_WITH_TIME
321 sprintf(rad_name, "%s%c%s%s", cache_dir, GF_PATH_SEPARATOR, "rap_", dump_name);
322 #else
323 char date_str[100], time_str[100];
324 time_t now;
325 struct tm *tm_tot;
326 now = time(NULL);
327 tm_tot = localtime(&now);
328 strftime(date_str, 100, "%Yy%mm%dd", tm_tot);
329 strftime(time_str, 100, "%Hh%Mm%Ss", tm_tot);
330 sprintf(rad_name, "%s%c%s-%s-%s%s", cache_dir, GF_PATH_SEPARATOR, date_str, time_str, "rap_", dump_name);
331 #endif
332 }
333
334 #ifndef GPAC_DISABLE_SCENE_DUMP
335 dumper = gf_sm_dumper_new(seng->ctx->scene_graph, rad_name, GF_FALSE, ' ', GF_SM_DUMP_SVG);
336 if (!dumper) {
337 GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[SceneEngine] Cannot create SVG dumper for %s.svg\n", rad_name));
338 e = GF_IO_ERR;
339 goto exit;
340 }
341
342 if (commands && gf_list_count(commands)) {
343 e = gf_sm_dump_command_list(dumper, commands, 0, 0);
344 }
345 else {
346 e = gf_sm_dump_graph(dumper, 0, 0);
347 }
348 gf_sm_dumper_del(dumper);
349
350 #if 0 //unused
351 if(seng->dump_rap) {
352 GF_SceneDumper *dumper = NULL;
353
354 sprintf(rad_name, "%s%c%s%s", cache_dir, GF_PATH_SEPARATOR, "rap_", dump_name);
355
356 dumper = gf_sm_dumper_new(seng->ctx->scene_graph, rad_name, GF_FALSE, ' ', GF_SM_DUMP_SVG);
357 if (!dumper) {
358 GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[SceneEngine] Cannot create SVG dumper for %s.svg\n", rad_name));
359 e = GF_IO_ERR;
360 goto exit;
361 }
362 e = gf_sm_dump_graph(dumper, 0, 0);
363 gf_sm_dumper_del(dumper);
364 }
365 #endif
366
367 if (e) {
368 GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[SceneEngine] Cannot dump DIMS Commands\n"));
369 goto exit;
370 }
371 #endif
372
373 #ifdef DUMP_DIMS_LOG_WITH_TIME
374 if (do_dump_with_time) {
375 do_dump_with_time = 0;
376 goto start;
377 }
378 #endif
379
380 sprintf(file_name, "%s.svg", rad_name);
381
382 e = gf_file_load_data(file_name, (u8 **) &buffer, &fsize);
383 if (e) {
384 GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[SceneEngine] Error loading SVG dump file %s\n", file_name));
385 goto exit;
386 }
387
388 /* Then, set DIMS unit header - TODO: notify redundant units*/
389 if (commands && gf_list_count(commands)) {
390 dims_header = GF_DIMS_UNIT_P; /* streamer->all_non_rap_critical ? 0 : GF_DIMS_UNIT_P;*/
391 } else {
392 /*redundant RAP with complete scene*/
393 dims_header = GF_DIMS_UNIT_M | GF_DIMS_UNIT_S | GF_DIMS_UNIT_I | GF_DIMS_UNIT_P;
394 }
395
396 /* Then, if compression is asked, we do it */
397 buffer_len = (u32)fsize;
398 assert(fsize < 0x80000000);
399 GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("[SceneEngine] Sending DIMS data - sizes: raw (%d)", buffer_len));
400 if (compress_dims) {
401 #ifndef GPAC_DISABLE_ZLIB
402 dims_header |= GF_DIMS_UNIT_C;
403 e = gf_gz_compress_payload(&buffer, buffer_len, &buffer_len);
404 GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("/ compressed (%d)", buffer_len));
405 if (e) goto exit;
406 #else
407 GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("Error: your version of GPAC was compiled with no libz support. Abort."));
408 e = GF_NOT_SUPPORTED;
409 goto exit;
410 #endif
411 }
412 GF_LOG(GF_LOG_DEBUG, GF_LOG_CONTAINER, ("\n"));
413
414 /* Then, prepare the DIMS data using a bitstream instead of direct manipulation for endianness
415 The new bitstream size should be:
416 the size of the (compressed) data
417 + 1 bytes for the header
418 + 2 bytes for the size
419 + 4 bytes if the size is greater than 65535
420 */
421 bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
422 if (buffer_len > 65535) {
423 GF_LOG(GF_LOG_WARNING, GF_LOG_CONTAINER, ("[SceneEngine] Warning: DIMS Unit size too big !!!\n"));
424 gf_bs_write_u16(bs, 0); /* internal GPAC hack to indicate that the size is larger than 65535 */
425 gf_bs_write_u32(bs, buffer_len+1);
426 } else {
427 gf_bs_write_u16(bs, buffer_len+1);
428 }
429 gf_bs_write_u8(bs, dims_header);
430 gf_bs_write_data(bs, buffer, buffer_len);
431
432 gf_free(buffer);
433 buffer = NULL;
434
435 gf_bs_get_content(bs, data, size);
436 gf_bs_del(bs);
437
438 exit:
439 if (buffer) gf_free(buffer);
440 return e;
441 }
442
gf_sm_check_for_modif(GF_SceneEngine * seng,GF_AUContext * au)443 static Bool gf_sm_check_for_modif(GF_SceneEngine *seng, GF_AUContext *au)
444 {
445 GF_Command *com;
446 Bool modified=0;
447 u32 i=0;
448 /*au is marked as modified - this happens when commands are concatenated into the au*/
449 if (au->flags & GF_SM_AU_MODIFIED) {
450 au->flags &= ~GF_SM_AU_MODIFIED;
451 modified=1;
452 }
453 /*check each command*/
454 while (NULL != (com = gf_list_enum(au->commands, &i))) {
455 u32 j=0;
456 GF_CommandField *field;
457 if (!com->node) continue;
458 /*check root node (for SCENE_REPLACE) */
459 if (gf_node_dirty_get(com->node)) {
460 modified=1;
461 gf_node_dirty_reset(com->node, 1);
462 }
463 /*check all command fields of type SFNODE or MFNODE*/
464 while (NULL != (field = gf_list_enum(com->command_fields, &j))) {
465 switch (field->fieldType) {
466 case GF_SG_VRML_SFNODE:
467 if (field->new_node) {
468 if (gf_node_dirty_get(field->new_node)) {
469 modified=1;
470 gf_node_dirty_reset(field->new_node, 1);
471 }
472 }
473 break;
474 case GF_SG_VRML_MFNODE:
475 if (field->field_ptr) {
476 GF_ChildNodeItem *child;
477 child = field->node_list;
478 while (child) {
479 if (gf_node_dirty_get(child->node)) {
480 modified=1;
481 gf_node_dirty_reset(child->node, 1);
482 }
483 child = child->next;
484 }
485 }
486 break;
487 }
488 }
489 }
490
491 if (!seng->first_dims_sent) {
492 if (au->owner->codec_id==GF_CODECID_DIMS) {
493 GF_Node *root = gf_sg_get_root_node(seng->ctx->scene_graph);
494 if (gf_node_dirty_get(root)) {
495 modified=1;
496 gf_node_dirty_reset(root, 1);
497 }
498 } else {
499 }
500 seng->first_dims_sent = 1;
501 }
502 return modified;
503 }
504
gf_sm_live_encode_scene_au(GF_SceneEngine * seng,gf_seng_callback callback,Bool from_start)505 static GF_Err gf_sm_live_encode_scene_au(GF_SceneEngine *seng, gf_seng_callback callback, Bool from_start)
506 {
507 GF_Err e;
508 u32 i, j, size, count, nb_streams;
509 u8 *data;
510 GF_AUContext *au;
511
512 if (!callback) return GF_BAD_PARAM;
513
514 e = GF_OK;
515
516 nb_streams = gf_list_count(seng->ctx->streams);
517 for (i=0; i<nb_streams; i++) {
518 GF_StreamContext *sc = gf_list_get(seng->ctx->streams, i);
519 if (sc->streamType != GF_STREAM_SCENE) continue;
520
521 count = gf_list_count(sc->AUs);
522 j = from_start ? 0 : sc->current_au_count;
523 for (; j<count; j++) {
524 au = (GF_AUContext *)gf_list_get(sc->AUs, j);
525 data = NULL;
526 size = 0;
527 /*in case using XMT*/
528 if (au->timing_sec) au->timing = (u64) (au->timing_sec * sc->timeScale);
529
530 if (from_start && !j && !gf_sm_check_for_modif(seng, au)) continue;
531
532 switch (sc->codec_id) {
533 #ifndef GPAC_DISABLE_BIFS_ENC
534 case GF_CODECID_BIFS:
535 case GF_CODECID_BIFS_V2:
536 e = gf_bifs_encode_au(seng->bifsenc, sc->ESID, au->commands, &data, &size);
537 break;
538 #endif
539
540 #ifndef GPAC_DISABLE_LASER
541 case GF_CODECID_LASER:
542 e = gf_laser_encode_au(seng->lsrenc, sc->ESID, au->commands, 0, &data, &size);
543 break;
544 #endif
545 case GF_CODECID_DIMS:
546 e = gf_seng_encode_dims_au(seng, sc->ESID, au->commands, &data, &size, GF_TRUE);
547 break;
548
549 default:
550 GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("Cannot encode AU for Scene OTI %x\n", sc->codec_id));
551 break;
552 }
553 callback(seng->calling_object, sc->ESID, data, size, au->timing);
554 gf_free(data);
555 data = NULL;
556 if (e) break;
557 }
558 }
559 return e;
560 }
561
562 GF_EXPORT
gf_seng_aggregate_context(GF_SceneEngine * seng,u16 ESID)563 GF_Err gf_seng_aggregate_context(GF_SceneEngine *seng, u16 ESID)
564 {
565 return gf_sm_aggregate(seng->ctx, ESID);
566 }
567
568 #if 0 //unused
569 GF_Err gf_seng_dump_rap_on(GF_SceneEngine *seng, Bool dump_rap)
570 {
571 seng->dump_rap = dump_rap;
572 return 0;
573 }
574 #endif
575
576 GF_EXPORT
gf_seng_save_context(GF_SceneEngine * seng,char * ctxFileName)577 GF_Err gf_seng_save_context(GF_SceneEngine *seng, char *ctxFileName)
578 {
579 #ifdef GPAC_DISABLE_SCENE_DUMP
580 return GF_NOT_SUPPORTED;
581 #else
582 u32 d_mode, do_enc;
583 char szF[GF_MAX_PATH], *ext;
584 GF_Err e;
585
586 /*check if we dump to BT, XMT or encode to MP4*/
587 ext = NULL;
588 if (ctxFileName) {
589 strcpy(szF, ctxFileName);
590 ext = gf_file_ext_start(szF);
591 }
592 d_mode = GF_SM_DUMP_BT;
593 do_enc = 0;
594 if (ext) {
595 if (!stricmp(ext, ".xmt") || !stricmp(ext, ".xmta")) d_mode = GF_SM_DUMP_XMTA;
596 else if (!stricmp(ext, ".mp4")) do_enc = 1;
597 ext[0] = 0;
598 }
599
600 if (do_enc) {
601 #ifndef GPAC_DISABLE_SCENE_ENCODER
602 GF_ISOFile *mp4;
603 strcat(szF, ".mp4");
604 mp4 = gf_isom_open(szF, GF_ISOM_OPEN_WRITE, NULL);
605 e = gf_sm_encode_to_file(seng->ctx, mp4, NULL);
606 if (e) gf_isom_delete(mp4);
607 else gf_isom_close(mp4);
608 #else
609 return GF_NOT_SUPPORTED;
610 #endif
611 } else {
612 e = gf_sm_dump(seng->ctx, ctxFileName ? szF : NULL, GF_FALSE, d_mode);
613 }
614 return e;
615 #endif
616 }
617
gf_seng_create_new_au(GF_StreamContext * sc,u32 time)618 static GF_AUContext *gf_seng_create_new_au(GF_StreamContext *sc, u32 time)
619 {
620 GF_AUContext *new_au, *last_au;
621 if (!sc) return NULL;
622 last_au = gf_list_last(sc->AUs);
623 if (last_au && last_au->timing == time) {
624 GF_LOG(GF_LOG_DEBUG, GF_LOG_SCENE, ("[SceneEngine] Forcing new AU\n"));
625 time++;
626 }
627 new_au = gf_sm_stream_au_new(sc, time, 0, 0);
628 return new_au;
629 }
630
631 GF_EXPORT
gf_seng_encode_from_string(GF_SceneEngine * seng,u16 ESID,Bool disable_aggregation,char * auString,gf_seng_callback callback)632 GF_Err gf_seng_encode_from_string(GF_SceneEngine *seng, u16 ESID, Bool disable_aggregation, char *auString, gf_seng_callback callback)
633 {
634 GF_StreamContext *sc;
635 u32 i;
636 GF_Err e;
637
638 i = 0;
639 while ((sc = (GF_StreamContext*)gf_list_enum(seng->ctx->streams, &i))) {
640 sc->current_au_count = gf_list_count(sc->AUs);
641 sc->disable_aggregation = disable_aggregation;
642 }
643 seng->loader.flags |= GF_SM_LOAD_CONTEXT_READY;
644 seng->loader.force_es_id = ESID;
645
646 /* We need to create an empty AU for the parser to correctly parse a LASeR Command without SceneUnit */
647 sc = gf_list_get(seng->ctx->streams, 0);
648 if (sc->codec_id == GF_CODECID_DIMS) {
649 gf_seng_create_new_au(sc, 0);
650 }
651
652 e = gf_sm_load_string(&seng->loader, auString, 0);
653 if (e) goto exit;
654
655 i = 0;
656 while ((sc = (GF_StreamContext*)gf_list_enum(seng->ctx->streams, &i))) {
657 sc->disable_aggregation = 0;
658 }
659 e = gf_sm_live_encode_scene_au(seng, callback, 0);
660 exit:
661 return e;
662 }
663
664
665 #if 0 //unused
666 GF_Err gf_seng_encode_from_commands(GF_SceneEngine *seng, u16 ESID, Bool disable_aggregation, u32 time, GF_List *commands, gf_seng_callback callback)
667 {
668 GF_Err e;
669 u32 size;
670 char *data;
671 GF_StreamContext *sc;
672 u32 i, nb_streams;
673 GF_AUContext *new_au;
674
675 if (!callback) return GF_BAD_PARAM;
676 if (!commands || !gf_list_count(commands)) return GF_BAD_PARAM;
677
678 e = GF_OK;
679
680 /* if the ESID is not provided we try to use the first scene stream */
681 sc = NULL;
682 nb_streams = gf_list_count(seng->ctx->streams);
683 for (i=0; i<nb_streams; i++) {
684 GF_StreamContext *tmp_sc = gf_list_get(seng->ctx->streams, i);
685 if (tmp_sc->streamType != GF_STREAM_SCENE) continue;
686 sc = tmp_sc;
687 if (!ESID) break;
688 else if (sc->ESID == ESID) break;
689 }
690 if (!sc) return GF_BAD_PARAM;
691 /* We need to create an empty AU for the parser to correctly parse a LASeR Command without SceneUnit */
692 new_au = gf_seng_create_new_au(sc, time);
693
694 if (disable_aggregation) new_au->flags = GF_SM_AU_NOT_AGGREGATED;
695
696
697
698 /* Removing the commands from the input list to avoid destruction
699 and setting the RAP flag */
700 while (gf_list_count(commands)) {
701 GF_Command *com = gf_list_get(commands, 0);
702 gf_list_rem(commands, 0);
703 switch (com->tag) {
704 #ifndef GPAC_DISABLE_VRML
705 case GF_SG_SCENE_REPLACE:
706 new_au->flags |= GF_SM_AU_RAP;
707 break;
708 #endif
709 case GF_SG_LSR_NEW_SCENE:
710 new_au->flags |= GF_SM_AU_RAP;
711 break;
712 }
713 gf_list_add(new_au->commands, com);
714 }
715
716 data = NULL;
717 size = 0;
718
719 switch(sc->codec_id) {
720 #ifndef GPAC_DISABLE_BIFS_ENC
721 case GF_CODECID_BIFS:
722 case GF_CODECID_BIFS_V2:
723 e = gf_bifs_encode_au(seng->bifsenc, ESID, new_au->commands, &data, &size);
724 break;
725 #endif
726
727 #ifndef GPAC_DISABLE_LASER
728 case GF_CODECID_LASER:
729 e = gf_laser_encode_au(seng->lsrenc, ESID, new_au->commands, 0, &data, &size);
730 break;
731 #endif
732 case GF_CODECID_DIMS:
733 e = gf_seng_encode_dims_au(seng, ESID, new_au->commands, &data, &size, GF_TRUE);
734 break;
735 default:
736 GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("Cannot encode commands for Scene OTI %x\n", sc->codec_id));
737 break;
738 }
739 callback(seng->calling_object, ESID, data, size, 0);
740 gf_free(data);
741 return e;
742 }
743 #endif
744 GF_EXPORT
gf_seng_encode_from_file(GF_SceneEngine * seng,u16 ESID,Bool disable_aggregation,char * auFile,gf_seng_callback callback)745 GF_Err gf_seng_encode_from_file(GF_SceneEngine *seng, u16 ESID, Bool disable_aggregation, char *auFile, gf_seng_callback callback)
746 {
747 GF_Err e;
748 GF_StreamContext *sc;
749 u32 i;
750 Bool dims = 0;
751
752 seng->loader.fileName = auFile;
753 seng->loader.ctx = seng->ctx;
754 seng->loader.force_es_id = ESID;
755
756 sc = NULL;
757 i=0;
758 while ((sc = (GF_StreamContext*)gf_list_enum(seng->ctx->streams, &i))) {
759 sc->current_au_count = gf_list_count(sc->AUs);
760 sc->disable_aggregation = disable_aggregation;
761 }
762 /* We need to create an empty AU for the parser to correctly parse a LASeR Command without SceneUnit */
763 sc = gf_list_get(seng->ctx->streams, 0);
764 if (sc->codec_id == GF_CODECID_DIMS) {
765 dims = 1;
766 gf_seng_create_new_au(sc, 0);
767 }
768 seng->loader.flags |= GF_SM_LOAD_CONTEXT_READY;
769
770 if (dims) {
771 seng->loader.type |= GF_SM_LOAD_DIMS;
772 } else {
773 seng->loader.flags |= GF_SM_LOAD_MPEG4_STRICT;
774 }
775 e = gf_sm_load_run(&seng->loader);
776
777 if (e<0) {
778 GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[SceneEngine] cannot load AU File %s (error %s)\n", auFile, gf_error_to_string(e)));
779 goto exit;
780 }
781
782 i = 0;
783 while ((sc = (GF_StreamContext*)gf_list_enum(seng->ctx->streams, &i))) {
784 sc->disable_aggregation = 0;
785 }
786
787 e = gf_sm_live_encode_scene_au(seng, callback, 0);
788 if (e) goto exit;
789 exit:
790 return e;
791 }
792
793 GF_EXPORT
gf_seng_encode_context(GF_SceneEngine * seng,gf_seng_callback callback)794 GF_Err gf_seng_encode_context(GF_SceneEngine *seng, gf_seng_callback callback)
795 {
796 if (!seng) {
797 GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[SceneEngine] Cannot encode context. No seng provided\n"));
798 return GF_BAD_PARAM;
799 }
800 return gf_sm_live_encode_scene_au(seng, callback, 1);
801 }
802
803 GF_EXPORT
gf_seng_terminate(GF_SceneEngine * seng)804 void gf_seng_terminate(GF_SceneEngine *seng)
805 {
806 #ifndef GPAC_DISABLE_BIFS_ENC
807 if (seng->bifsenc) gf_bifs_encoder_del(seng->bifsenc);
808 #endif
809
810 #ifndef GPAC_DISABLE_LASER
811 if (seng->lsrenc) gf_laser_encoder_del(seng->lsrenc);
812 #endif
813
814 gf_sm_load_done(&seng->loader);
815
816 if (seng->owns_context) {
817 if (seng->ctx) gf_sm_del(seng->ctx);
818 if (seng->sg) gf_sg_del(seng->sg);
819 }
820 gf_free(seng);
821 }
822
823 GF_EXPORT
gf_seng_get_stream_config(GF_SceneEngine * seng,u32 idx,u16 * ESID,const u8 ** config,u32 * config_len,u32 * streamType,u32 * codec_id,u32 * timeScale)824 GF_Err gf_seng_get_stream_config(GF_SceneEngine *seng, u32 idx, u16 *ESID, const u8 **config, u32 *config_len, u32 *streamType, u32 *codec_id, u32 *timeScale)
825 {
826 GF_StreamContext *sc = gf_list_get(seng->ctx->streams, idx);
827 if (!sc || !ESID || !config || !config_len) return GF_BAD_PARAM;
828 *ESID = sc->ESID;
829 *config = sc->dec_cfg;
830 *config_len = sc->dec_cfg_len;
831 if (streamType) *streamType = sc->streamType;
832 if (codec_id) *codec_id = sc->codec_id;
833 if (timeScale) *timeScale = sc->timeScale;
834 return GF_OK;
835 }
836
837
838 #ifndef GPAC_DISABLE_VRML
839
seng_exec_conditional(M_Conditional * c,GF_SceneGraph * scene)840 static void seng_exec_conditional(M_Conditional *c, GF_SceneGraph *scene)
841 {
842 GF_List *clist = c->buffer.commandList;
843 c->buffer.commandList = NULL;
844
845 gf_sg_command_apply_list(gf_node_get_graph((GF_Node*)c), clist, 0.0);
846
847 if (c->buffer.commandList != NULL) {
848 while (gf_list_count(clist)) {
849 GF_Command *sub_com = (GF_Command *)gf_list_get(clist, 0);
850 gf_sg_command_del(sub_com);
851 gf_list_rem(clist, 0);
852 }
853 gf_list_del(clist);
854 } else {
855 c->buffer.commandList = clist;
856 }
857 }
858
seng_conditional_activate(GF_Node * node,GF_Route * route)859 static void seng_conditional_activate(GF_Node *node, GF_Route *route)
860 {
861 if (node) {
862 GF_SceneEngine *seng = (GF_SceneEngine *) gf_node_get_private(node);
863 M_Conditional *c = (M_Conditional*)node;
864 if (c->activate) seng_exec_conditional(c, seng->sg);
865 }
866 }
867
seng_conditional_reverse_activate(GF_Node * node,GF_Route * route)868 static void seng_conditional_reverse_activate(GF_Node *node, GF_Route *route)
869 {
870 if (node) {
871 GF_SceneEngine *seng = (GF_SceneEngine *) gf_node_get_private(node);
872 M_Conditional*c = (M_Conditional*)node;
873 if (!c->reverseActivate) seng_exec_conditional(c, seng->sg);
874 }
875 }
876 #endif //GPAC_DISABLE_VRML
877
878
gf_seng_on_node_modified(void * _seng,GF_SGNodeCbkType type,GF_Node * node,void * ctxdata)879 static void gf_seng_on_node_modified(void *_seng, GF_SGNodeCbkType type, GF_Node *node, void *ctxdata)
880 {
881 switch (type) {
882 #ifndef GPAC_DISABLE_VRML
883 case GF_SG_CALLBACK_INIT:
884 if (gf_node_get_tag(node) == TAG_MPEG4_Conditional) {
885 M_Conditional*c = (M_Conditional*)node;
886 c->on_activate = seng_conditional_activate;
887 c->on_reverseActivate = seng_conditional_reverse_activate;
888 gf_node_set_private(node, _seng);
889 }
890 break;
891 #endif
892 case GF_SG_CALLBACK_MODIFIED:
893 gf_node_dirty_parents(node);
894 break;
895 default:
896 break;
897 }
898 }
899
900 GF_EXPORT
gf_seng_init(void * calling_object,char * inputContext,u32 load_type,char * dump_path,Bool embed_resources)901 GF_SceneEngine *gf_seng_init(void *calling_object, char * inputContext, u32 load_type, char *dump_path, Bool embed_resources)
902 {
903 GF_SceneEngine *seng;
904 GF_Err e = GF_OK;
905
906 if (!inputContext) return NULL;
907
908 GF_SAFEALLOC(seng, GF_SceneEngine)
909 if (!seng) return NULL;
910
911 seng->calling_object = calling_object;
912
913 /*Step 1: create context and load input*/
914 seng->sg = gf_sg_new();
915 gf_sg_set_node_callback(seng->sg, gf_seng_on_node_modified);
916 gf_sg_set_private(seng->sg, seng);
917 seng->dump_path = dump_path;
918 seng->ctx = gf_sm_new(seng->sg);
919 seng->owns_context = 1;
920 memset(&(seng->loader), 0, sizeof(GF_SceneLoader));
921 seng->loader.ctx = seng->ctx;
922 seng->loader.type = load_type;
923 /*since we're encoding in BIFS we must get MPEG-4 nodes only*/
924 seng->loader.flags = GF_SM_LOAD_MPEG4_STRICT;
925 if (embed_resources) seng->loader.flags |= GF_SM_LOAD_EMBEDS_RES;
926
927 #ifdef GPAC_ENABLE_COVERAGE
928 if (gf_sys_is_cov_mode()) {
929 seng_conditional_activate(NULL, NULL);
930 seng_conditional_reverse_activate(NULL, NULL);
931 gf_seng_create_new_au(NULL, 0);
932
933 }
934 #endif
935
936 seng->loader.fileName = inputContext;
937 e = gf_sm_load_init(&(seng->loader));
938 if (!e) e = gf_sm_load_run(&(seng->loader));
939
940 if (e<0) {
941 GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[SceneEngine] Cannot load context from %s (error %s)\n", inputContext, gf_error_to_string(e)));
942 goto exit;
943 }
944 e = gf_sm_live_setup(seng);
945 if (e!=GF_OK) {
946 GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[SceneEngine] cannot init scene encoder for context (error %s)\n", gf_error_to_string(e)));
947 goto exit;
948 }
949 return seng;
950
951 exit:
952 gf_seng_terminate(seng);
953 return NULL;
954 }
955
956 #if 0 //unused
957 /**
958 \param calling_object the calling object on which call back will be called
959 \param ctx an already loaded scene manager
960 \param dump_path the path where scenes are dumped
961 *
962 * must be called only one time (by process calling the DLL) before other calls
963 */
964 GF_SceneEngine *gf_seng_init_from_context(void *calling_object, GF_SceneManager *ctx, char *dump_path)
965 {
966 GF_SceneEngine *seng;
967 GF_Err e = GF_OK;
968
969 if (!ctx) return NULL;
970
971 GF_SAFEALLOC(seng, GF_SceneEngine)
972 if (!seng) return NULL;
973
974 seng->calling_object = calling_object;
975 seng->dump_path = dump_path;
976 /*Step 1: create context and load input*/
977 seng->sg = ctx->scene_graph;
978 seng->ctx = ctx;
979 seng->owns_context = 0;
980
981 e = gf_sm_live_setup(seng);
982 if (e!=GF_OK) {
983 GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[SceneEngine] cannot init scene encoder for context (error %s)\n", gf_error_to_string(e)));
984 goto exit;
985 }
986 return seng;
987
988 exit:
989 gf_seng_terminate(seng);
990 return NULL;
991 }
992 #endif
993
994 #if 0 //unused
995 /**
996 \param calling_object is the calling object on which call back will be called
997 \param inputContext is an UTF-8 scene description (with or without IOD) in BT or XMT-A format
998 \param load_type is the preferred loader type for the content (e.g. SVG vs DIMS)
999 \param width width of scene if no IOD is given in the context.
1000 \param height height of scene if no IOD is given in the context.
1001 \param usePixelMetrics metrics system used in the scene, if no IOD is given in the context.
1002 \param dump_path the path where scenes are dumped
1003 *
1004 * must be called only one time (by process calling the DLL) before other calls
1005 */
1006
1007 GF_SceneEngine *gf_seng_init_from_string(void *calling_object, char * inputContext, u32 load_type, u32 width, u32 height, Bool usePixelMetrics, char *dump_path)
1008 {
1009 GF_SceneEngine *seng;
1010 GF_Err e = GF_OK;
1011
1012 if (!inputContext) return NULL;
1013
1014 GF_SAFEALLOC(seng, GF_SceneEngine)
1015 if (!seng) return NULL;
1016
1017 seng->calling_object = calling_object;
1018 seng->dump_path = dump_path;
1019 /*Step 1: create context and load input*/
1020 seng->sg = gf_sg_new();
1021 seng->ctx = gf_sm_new(seng->sg);
1022 seng->owns_context = 1;
1023 memset(& seng->loader, 0, sizeof(GF_SceneLoader));
1024 seng->loader.ctx = seng->ctx;
1025 seng->loader.type = load_type;
1026 /*since we're encoding in BIFS we must get MPEG-4 nodes only*/
1027 seng->loader.flags = GF_SM_LOAD_MPEG4_STRICT;
1028
1029 /* assign a loader type only if it was not requested (e.g. DIMS should not be overriden by SVG) */
1030 if (!seng->loader.type) {
1031 if (inputContext[0] == '<') {
1032 if (strstr(inputContext, "<svg ")) seng->loader.type = GF_SM_LOAD_SVG;
1033 else if (strstr(inputContext, "<saf ")) seng->loader.type = GF_SM_LOAD_XSR;
1034 else if (strstr(inputContext, "XMT-A") || strstr(inputContext, "X3D")) seng->loader.type = GF_SM_LOAD_XMTA;
1035 } else {
1036 seng->loader.type = GF_SM_LOAD_BT;
1037 }
1038 }
1039 e = gf_sm_load_string(&seng->loader, inputContext, 0);
1040
1041 if (e) {
1042 GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[SceneEngine] cannot load context from %s (error %s)\n", inputContext, gf_error_to_string(e)));
1043 goto exit;
1044 }
1045 if (!seng->ctx->root_od) {
1046 seng->ctx->is_pixel_metrics = usePixelMetrics;
1047 seng->ctx->scene_width = width;
1048 seng->ctx->scene_height = height;
1049 }
1050
1051 e = gf_sm_live_setup(seng);
1052 if (e!=GF_OK) {
1053 GF_LOG(GF_LOG_ERROR, GF_LOG_SCENE, ("[SceneEngine] cannot init scene encoder for context (error %s)\n", gf_error_to_string(e)));
1054 goto exit;
1055 }
1056 return seng;
1057
1058 exit:
1059 gf_seng_terminate(seng);
1060 return NULL;
1061 }
1062 #endif
1063
1064
1065 GF_EXPORT
gf_seng_get_stream_count(GF_SceneEngine * seng)1066 u32 gf_seng_get_stream_count(GF_SceneEngine *seng)
1067 {
1068 return gf_list_count(seng->ctx->streams);
1069 }
1070
1071 GF_EXPORT
gf_seng_get_stream_carousel_info(GF_SceneEngine * seng,u16 ESID,u32 * carousel_period,u16 * aggregate_on_es_id)1072 GF_Err gf_seng_get_stream_carousel_info(GF_SceneEngine *seng, u16 ESID, u32 *carousel_period, u16 *aggregate_on_es_id)
1073 {
1074 u32 i=0;
1075 GF_StreamContext *sc;
1076
1077 if (carousel_period) *carousel_period = (u32) -1;
1078 if (aggregate_on_es_id) *aggregate_on_es_id = 0;
1079
1080 while (NULL != (sc = gf_list_enum(seng->ctx->streams, &i))) {
1081 if (sc->ESID==ESID) {
1082 if (carousel_period) *carousel_period = sc->carousel_period;
1083 if (aggregate_on_es_id) *aggregate_on_es_id = sc->aggregate_on_esid;
1084 return GF_OK;
1085 }
1086 }
1087 return GF_OK;
1088 }
1089
1090 GF_EXPORT
gf_seng_get_base64_iod(GF_SceneEngine * seng)1091 char *gf_seng_get_base64_iod(GF_SceneEngine *seng)
1092 {
1093 u32 size, size64;
1094 u8 *buffer, *buf64;
1095 u32 i=0;
1096 GF_StreamContext*sc = NULL;
1097
1098 if (!seng->ctx->root_od) return NULL;
1099
1100 while ((sc = (GF_StreamContext*)gf_list_enum(seng->ctx->streams, &i))) {
1101 if ((sc->streamType == GF_STREAM_SCENE) && (sc->codec_id != GF_CODECID_DIMS))
1102 break;
1103 }
1104 if (!sc) return NULL;
1105
1106 size = 0;
1107 gf_odf_desc_write((GF_Descriptor *) seng->ctx->root_od, &buffer, &size);
1108 buf64 = gf_malloc(size*2);
1109 size64 = gf_base64_encode( buffer, size, buf64, size*2);
1110 buf64[size64] = 0;
1111 gf_free(buffer);
1112 return buf64;
1113 }
1114
1115 GF_EXPORT
gf_seng_get_iod(GF_SceneEngine * seng)1116 GF_Descriptor *gf_seng_get_iod(GF_SceneEngine *seng)
1117 {
1118 u32 i=0;
1119 GF_Descriptor *out_iod = NULL;
1120 GF_StreamContext*sc = NULL;
1121
1122 if (!seng->ctx->root_od) return NULL;
1123 while ((sc = (GF_StreamContext*)gf_list_enum(seng->ctx->streams, &i))) {
1124 if ((sc->streamType == GF_STREAM_SCENE) && (sc->codec_id != GF_CODECID_DIMS))
1125 break;
1126 }
1127 if (!sc) return NULL;
1128 gf_odf_desc_copy((GF_Descriptor *)seng->ctx->root_od, &out_iod);
1129 return out_iod;
1130 }
1131
1132
1133 #endif /*GPAC_DISABLE_SENG*/
1134
1135