blob: d96e8e52ea7e6ef404867a3dc12b2ecb16fe332b [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2000-2023
* All rights reserved
*
* This file is part of GPAC / Scene Compositor 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/compositor_dev.h>
#include <gpac/network.h>
#if !defined(GPAC_DISABLE_COMPOSITOR)
Bool scene_ns_on_setup_error(GF_Filter *failed_filter, void *udta, GF_Err err)
{
GF_SceneNamespace *scene_ns;
GF_Scene *scene;
GF_ObjectManager *root = (GF_ObjectManager *)udta;
if (!root) return GF_FALSE;;
scene_ns = root->scene_ns;
gf_fatal_assert(scene_ns);
scene = root->subscene ? root->subscene : root->parentscene;
gf_fatal_assert(scene);
GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("[ODM] Service connection failure received from %s - %s\n", scene_ns->url, gf_error_to_string(err) ));
if (root->scene_ns->owner != root) {
gf_scene_message(scene, scene_ns->url, "Incompatible module type", GF_SERVICE_ERROR);
return GF_FALSE;
}
/*this is service connection*/
gf_odm_service_media_event(root, GF_EVENT_MEDIA_SETUP_DONE);
scene_ns->connect_ack = GF_TRUE;
if (err) {
char msg[5000];
snprintf(msg, sizeof(msg), "Cannot open %s", scene_ns->url);
gf_scene_message(scene, scene_ns->url, msg, err);
if (root->mo) root->mo->connect_state = MO_CONNECT_FAILED;
gf_odm_service_media_event(root, GF_EVENT_ERROR);
/*destroy service only if attached*/
if (root) {
GF_Scene *top_scene;
//notify before disconnecting
if (root->subscene) gf_scene_notify_event(root->subscene, GF_EVENT_SCENE_ATTACHED, NULL, NULL, err, GF_FALSE);
//detach clocks before destroying namspace
root->ck = NULL;
if (root->subscene) {
u32 i, count = gf_list_count(root->subscene->resources);
for (i=0; i<count; i++) {
GF_ObjectManager *anodm = gf_list_get(root->subscene->resources, i);
anodm->ck = NULL;
if (anodm->scene_ns==root->scene_ns)
anodm->scene_ns = NULL;
}
}
root->scene_ns = NULL;
if (scene_ns->owner && scene_ns->nb_odm_users) scene_ns->nb_odm_users--;
scene_ns->owner = NULL;
top_scene = gf_scene_get_root_scene(scene);
//source filter no longer exists
scene_ns->source_filter = NULL;
gf_scene_ns_del(scene_ns, top_scene);
if (!root->parentscene) {
GF_Event evt;
evt.type = GF_EVENT_CONNECT;
evt.connect.is_connected = 0;
gf_filter_send_gf_event(scene->compositor->filter, &evt);
} else {
/*try to reinsert OD for VRML/X3D with multiple URLs:
1- first remove from parent scene without destroying object, this will trigger a re-setup
if other URLs are present
2- then destroy object*/
gf_scene_remove_object(root->parentscene, root, 0);
gf_odm_disconnect(root, 1);
}
return GF_FALSE;
}
}
return GF_FALSE;
}
void gf_scene_insert_pid(GF_Scene *scene, GF_SceneNamespace *sns, GF_FilterPid *pid, Bool is_in_iod)
{
u32 i, min_od_id;
GF_MediaObject *the_mo;
GF_ObjectManager *odm, *root;
const GF_PropertyValue *v;
u32 mtype=0;
u32 pid_odid=0;
u32 pid_id=0;
u32 ServiceID=0;
root = sns->owner;
if (!root) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPTIME, ("[Service %s] has no root, aborting !\n", sns->url));
return;
}
if (root->flags & GF_ODM_DESTROYED) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPTIME, ("[Service %s] root has been scheduled for destruction - aborting !\n", sns->url));
return;
}
scene = root->subscene ? root->subscene : root->parentscene;
if (scene->root_od->addon && (scene->root_od->addon->addon_type == GF_ADDON_TYPE_MAIN)) {
scene->root_od->flags |= GF_ODM_REGENERATE_SCENE;
}
if (scene->is_dynamic_scene) {
if (!root->ID)
root->ID = 1;
}
v = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE);
if (!v) return;
mtype = v->value.uint;
v = gf_filter_pid_get_property(pid, GF_PROP_PID_SERVICE_ID);
if (v) ServiceID = v->value.uint;
v = gf_filter_pid_get_property(pid, GF_PROP_PID_ID);
if (v) pid_id = v->value.uint;
if (scene->compositor->autofps && (mtype==GF_STREAM_VISUAL) ) {
v = gf_filter_pid_get_property(pid, GF_PROP_PID_FPS);
if (v && v->value.frac.den && v->value.frac.num && (v->value.frac.den!=v->value.frac.num)) {
scene->compositor->autofps = GF_FALSE;
gf_sc_set_fps(scene->compositor, v->value.frac);
}
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("[Service %s] Adding new media\n", sns->url));
/*object declared this way are not part of an OD stream and are considered as dynamic*/
/* od->objectDescriptorID = GF_MEDIA_EXTERNAL_ID; */
/*check if we have a mediaObject in the scene not attached and matching this object*/
the_mo = NULL;
odm = NULL;
min_od_id = 0;
for (i=0; i<gf_list_count(scene->scene_objects); i++) {
char *frag = NULL;
char *ext, *url;
u32 match_esid = 0;
Bool type_matched = GF_FALSE;
GF_MediaObject *mo = gf_list_get(scene->scene_objects, i);
if ((mo->OD_ID != GF_MEDIA_EXTERNAL_ID) && (min_od_id<mo->OD_ID))
min_od_id = mo->OD_ID;
//got the service, check the type
/*match type*/
switch (mtype) {
case GF_STREAM_VISUAL:
if (mo->type == GF_MEDIA_OBJECT_VIDEO) type_matched = GF_TRUE;
break;
case GF_STREAM_AUDIO:
if (mo->type == GF_MEDIA_OBJECT_AUDIO) type_matched = GF_TRUE;
break;
case GF_STREAM_SCENE:
if (mo->type == GF_MEDIA_OBJECT_UPDATES) type_matched = GF_TRUE;
break;
default:
break;
}
if (!mo->odm) {
u32 j;
//if not external OD, look for ODM with same ID
if (mo->OD_ID != GF_MEDIA_EXTERNAL_ID) {
for (j=0; j<gf_list_count(scene->resources); j++) {
GF_ObjectManager *an_odm = gf_list_get(scene->resources, j);
if (an_odm->ID == mo->OD_ID) {
mo->odm = an_odm;
break;
}
}
}
if (!mo->odm) {
continue;
}
}
/*if object is attached to a service, don't bother looking in a different one*/
if (mo->odm->scene_ns && (mo->odm->scene_ns != sns)) {
Bool mine = 0;
if (mo->odm->scene_ns->source_filter) {
mine = gf_filter_pid_is_filter_in_parents(pid, mo->odm->scene_ns->source_filter);
}
//passthrough ODM not yet assigned, consider it ours
else if (!mo->odm->pid && (mo->odm->flags & GF_ODM_PASSTHROUGH)) {
mine = GF_TRUE;
}
if (!mine) continue;
if (type_matched) {
the_mo = mo;
odm = mo->odm;
pid_odid = odm->ID = mo->OD_ID;
break;
}
}
/*already assigned object*/
if (mo->odm->ID) continue;
/*we cannot match yet the object, we need the OD command for that*/
if (mo->OD_ID != GF_MEDIA_EXTERNAL_ID) {
continue;
}
if (!mo->URLs.count || !mo->URLs.vals[0].url) continue;
ext = strrchr(mo->URLs.vals[0].url, '#');
if (ext) {
frag = strchr(ext, '=');
ext[0] = 0;
}
url = mo->URLs.vals[0].url;
if (!strnicmp(url, "file://localhost", 16)) url += 16;
else if (!strnicmp(url, "file://", 7)) url += 7;
else if (!strnicmp(url, "gpac://", 7)) url += 7;
else if (!strnicmp(url, "pid://", 6)) match_esid = atoi(url+6);
if (!match_esid && mo->odm->scene_ns && !strstr(mo->odm->scene_ns->url, url)) {
if (ext) ext[0] = '#';
continue;
}
if (ext) ext[0] = '#';
if (match_esid && (pid_id != match_esid))
continue;
/*match type*/
switch (mtype) {
case GF_STREAM_VISUAL:
if (mo->type != GF_MEDIA_OBJECT_VIDEO) continue;
break;
case GF_STREAM_AUDIO:
if (mo->type != GF_MEDIA_OBJECT_AUDIO) continue;
break;
case GF_STREAM_SCENE:
if (mo->type != GF_MEDIA_OBJECT_UPDATES) continue;
break;
default:
continue;
}
if (frag) {
u32 frag_id = 0;
u32 ID = mo->OD_ID;
if (ID==GF_MEDIA_EXTERNAL_ID) ID = pid_id;
frag++;
frag_id = atoi(frag);
if (ID!=frag_id) continue;
}
the_mo = mo;
odm = mo->odm;
pid_odid = odm->ID = mo->OD_ID;
if (mo->OD_ID == GF_MEDIA_EXTERNAL_ID)
odm->flags |= GF_ODM_NOT_IN_OD_STREAM;
break;
}
/*add a pass on scene->resource to check for min_od_id,
otherwise we may have another modules declaring an object with ID 0 from
another thread, which will assert (only one object with a givne OD ID)*/
for (i=0; i<gf_list_count(scene->resources); i++) {
GF_ObjectManager *an_odm = gf_list_get(scene->resources, i);
if (an_odm->ID == GF_MEDIA_EXTERNAL_ID) continue;
if (min_od_id < an_odm->ID)
min_od_id = an_odm->ID;
if (odm) continue;
//check for OD replacement, look for ODMs with no PID assigned and a source filter origin for this pid
if (an_odm->pid) continue;
if (!an_odm->scene_ns->source_filter) continue;
if (! gf_filter_pid_is_filter_in_parents(pid, an_odm->scene_ns->source_filter))
continue;
odm = an_odm;
pid_odid = an_odm->ID;
break;
}
if (is_in_iod) {
odm = scene->root_od;
//for inline cases, the media object may already be setup
the_mo = odm->mo;
if (the_mo) pid_odid = the_mo->OD_ID;
}
if (!odm) {
odm = gf_odm_new();
odm->parentscene = scene;
gf_list_add(scene->resources, odm);
}
odm->flags |= GF_ODM_NOT_SETUP;
if (!pid_odid) {
pid_odid = min_od_id + 1;
}
odm->ID = pid_odid;
odm->mo = the_mo;
odm->ServiceID = ServiceID;
if (!odm->pid) {
odm->type = mtype;
if (mtype == GF_STREAM_AUDIO) {
scene->compositor->audio_renderer->nb_audio_objects++;
scene->compositor->audio_renderer->scene_ready = GF_FALSE;
}
}
if (gf_filter_pid_is_sparse(pid)) {
odm->flags |= GF_ODM_IS_SPARSE;
}
//register PID with ODM, but don't call setup object
gf_odm_register_pid(odm, pid, GF_TRUE);
gf_filter_pid_set_udta(pid, odm);
v = gf_filter_pid_get_property(pid, GF_PROP_PID_PLAYBACK_MODE);
if (!v || !v->value.uint) odm->flags |= GF_ODM_NO_TIME_CTRL;
if (the_mo) the_mo->OD_ID = odm->ID;
if (!scene->selected_service_id)
scene->selected_service_id = ServiceID;
if (odm->parentscene && odm->parentscene->is_dynamic_scene)
odm->flags |= GF_ODM_NOT_IN_OD_STREAM;
//we insert right away the PID as a new object if the scene is dynamic
//if the scene is not dynamic, we wait for the corresponding OD update
//otherwise if subscene, this is an IOD
if (odm->subscene || (odm->flags & GF_ODM_NOT_IN_OD_STREAM) ) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("[ODM%d] setup object - MO %08x\n", odm->ID, odm->mo));
gf_odm_setup_object(odm, sns, pid);
} else {
//cannot setup until we get the associated OD_Update
odm->ID = 0;
}
if (scene->is_dynamic_scene && !root->scene_ns->connect_ack) {
/*othewise send a connect ack for top level*/
GF_Event evt;
root->scene_ns->connect_ack = GF_TRUE;
GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("[ODM] Root object connected (%s) !\n", root->scene_ns->url));
evt.type = GF_EVENT_CONNECT;
evt.connect.is_connected = GF_TRUE;
gf_sc_send_event(scene->compositor, &evt);
}
}
GF_SceneNamespace *gf_scene_ns_new(GF_Scene *scene, GF_ObjectManager *owner, const char *url, const char *parent_url)
{
char *frag;
GF_SceneNamespace *sns;
GF_SAFEALLOC(sns, GF_SceneNamespace);
if (!sns) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPTIME, ("[Compose] Failed to allocate namespace\n"));
return NULL;
}
sns->owner = owner;
sns->url = gf_url_concatenate(parent_url, url);
sns->clocks = gf_list_new();
frag = strchr(sns->url, '#');
if (frag) {
sns->url_frag = gf_strdup(frag+1);
frag[0] = 0;
}
//move to top scene
scene = gf_scene_get_root_scene(scene);
gf_list_add(scene->namespaces, sns);
return sns;
}
void gf_scene_ns_del(GF_SceneNamespace *sns, GF_Scene *root_scene)
{
gf_list_del_item(root_scene->namespaces, sns);
if (sns->source_filter) {
gf_filter_remove_src(root_scene->compositor->filter, sns->source_filter);
}
if (sns->clocks) {
while (gf_list_count(sns->clocks)) {
GF_Clock *ck = gf_list_pop_back(sns->clocks);
gf_clock_del(ck);
}
gf_list_del(sns->clocks);
}
if (sns->url) gf_free(sns->url);
if (sns->url_frag) gf_free(sns->url_frag);
gf_free(sns);
}
Bool scene_ns_remove_object(GF_Filter *filter, void *callback, u32 *reschedule_ms)
{
GF_ObjectManager *odm = (GF_ObjectManager *)callback;
odm->has_pending_remove_task = GF_FALSE;
if (odm->flags & GF_ODM_DESTROYED) {
gf_odm_del(odm);
return GF_FALSE;
}
gf_odm_disconnect(odm, 2);
return GF_FALSE;
}
/*connects given OD manager to its URL*/
void gf_scene_ns_connect_object(GF_Scene *scene, GF_ObjectManager *odm, char *serviceURL, char *parent_url, GF_SceneNamespace *addon_parent_ns)
{
GF_Err e;
char *frag;
char *url_with_args = NULL;
Bool reloc_result=GF_FALSE;
if (addon_parent_ns) {
parent_url = NULL;
}
/*check cache*/
if (parent_url) {
u32 i, count;
count = gf_opts_get_section_count();
for (i=0; i<count; i++) {
u32 exp, sec, frac;
const char *opt, *service_cache;
const char *name = gf_opts_get_section_name(i);
if (strncmp(name, "@cache=", 7)) continue;
opt = gf_opts_get_key(name, "serviceURL");
if (!opt || stricmp(opt, parent_url)) continue;
opt = gf_opts_get_key(name, "cacheName");
if (!opt || stricmp(opt, serviceURL)) continue;
service_cache = (char*)gf_opts_get_key(name, "cacheFile");
opt = gf_opts_get_key(name, "expireAfterNTP");
if (opt) {
sscanf(opt, "%u", &exp);
gf_net_get_ntp(&sec, &frac);
if (exp && (exp<sec)) {
opt = gf_opts_get_key(name, "cacheFile");
if (opt) gf_file_delete((char*) opt);
gf_opts_del_section(name);
service_cache = NULL;
}
}
if (service_cache) serviceURL = (char *)service_cache;
break;
}
}
frag = strchr(serviceURL, '#');
if (frag) {
frag[0] = 0;
u32 i, count = gf_list_count(scene->compositor->root_scene->namespaces);
for (i=0; i<count; i++) {
GF_SceneNamespace *sns = gf_list_get(scene->compositor->root_scene->namespaces, i);
if (sns->url && !strcmp(sns->url, serviceURL)) {
frag[0] = '#';
odm->scene_ns = sns;
sns->nb_odm_users++;
return;
}
}
}
odm->scene_ns = gf_scene_ns_new(scene->compositor->root_scene, odm, serviceURL, (odm->addon || reloc_result) ? NULL : parent_url);
if (!odm->scene_ns) {
gf_scene_message(scene, serviceURL, "Cannot create scene service", GF_OUT_OF_MEM);
gf_odm_disconnect(odm, 1);
if (frag) frag[0] = '#';
return;
}
odm->scene_ns->nb_odm_users++;
gf_assert(odm->scene_ns->owner == odm);
if (frag && !odm->scene_ns->url_frag) {
odm->scene_ns->url_frag = gf_strdup(frag+1);
}
if (!strncmp(serviceURL, "gpid://", 7)) {
gf_odm_service_media_event(odm, GF_EVENT_MEDIA_SETUP_BEGIN);
gf_odm_service_media_event(odm, GF_EVENT_MEDIA_SETUP_DONE);
odm->scene_ns->connect_ack = GF_TRUE;
odm->flags |= GF_ODM_NOT_IN_OD_STREAM | GF_ODM_PASSTHROUGH;
if (frag) frag[0] = '#';
return;
}
if (!addon_parent_ns && !parent_url && odm->parentscene && odm->parentscene->root_od->scene_ns)
parent_url = odm->parentscene->root_od->scene_ns->url;
//we setup an addon, use the same filter chain (so copy source id) as orginial content
//so that any scalable streams in the addon can connect to decoders
if (addon_parent_ns) {
const char *fid = gf_filter_get_id(addon_parent_ns->source_filter);
if (fid) {
char szExt[100];
char sep_a = gf_filter_get_sep(scene->compositor->filter, GF_FS_SEP_ARGS);
char sep_v = gf_filter_get_sep(scene->compositor->filter, GF_FS_SEP_NAME);
gf_dynstrcat(&url_with_args, serviceURL, NULL);
sprintf(szExt, "%cgpac%cFID%c", sep_a, sep_a, sep_v);
gf_dynstrcat(&url_with_args, szExt, NULL);
gf_dynstrcat(&url_with_args, fid, NULL);
}
}
gf_filter_lock_all(scene->compositor->filter, GF_TRUE);
odm->scene_ns->source_filter = gf_filter_connect_source(scene->compositor->filter, url_with_args ? url_with_args : serviceURL, parent_url, GF_FALSE, &e);
if (url_with_args) gf_free(url_with_args);
if (frag) frag[0] = '#';
if (!odm->scene_ns->source_filter) {
Bool remove_scene=GF_FALSE;
Bool remove_filter=GF_FALSE;
GF_Compositor *compositor = scene->compositor;
GF_Scene *target_scene = odm->subscene ? NULL : odm->parentscene;
gf_filter_lock_all(scene->compositor->filter, GF_FALSE);
if (!odm->mo && target_scene) {
u32 i;
for (i=0; i<gf_list_count(target_scene->scene_objects); i++) {
GF_MediaObject *mo = gf_list_get(target_scene->scene_objects, i);
if (mo->odm) continue;
if ((mo->OD_ID != GF_MEDIA_EXTERNAL_ID) && (mo->OD_ID==odm->ID)) {
odm->mo = mo;
mo->odm = odm;
break;
}
}
}
if (compositor->player && !odm->parentscene) remove_filter = GF_TRUE;
if (odm->mo) odm->mo->connect_state = MO_CONNECT_FAILED;
odm->skip_disconnect_state = 1;
//prevent scene from being disconnected - this can happen if a script catches the event and triggers a disonnection of the parent scene
if (target_scene) target_scene->root_od->skip_disconnect_state = 1;
gf_scene_notify_event(scene, GF_EVENT_SCENE_ATTACHED, NULL, NULL, e, GF_TRUE);
gf_scene_message(scene, serviceURL, "Cannot find filter for service", e);
odm->skip_disconnect_state = 0;
if (target_scene) {
if (target_scene->root_od->skip_disconnect_state==2) remove_scene = GF_TRUE;
target_scene->root_od->skip_disconnect_state = 0;
}
if (remove_scene) {
target_scene->root_od->has_pending_remove_task = GF_TRUE;
gf_filter_post_task(scene->compositor->filter, scene_ns_remove_object, target_scene->root_od, "remove_odm");
} else {
odm->has_pending_remove_task = GF_TRUE;
gf_filter_post_task(scene->compositor->filter, scene_ns_remove_object, odm, "remove_odm");
}
if (remove_filter) {
gf_filter_remove(compositor->filter);
compositor->check_eos_state = 3;
}
return;
}
//make sure we only connect this filter to ourselves
gf_filter_set_source_restricted(scene->compositor->filter, odm->scene_ns->source_filter, NULL);
gf_filter_set_setup_failure_callback(scene->compositor->filter, odm->scene_ns->source_filter, scene_ns_on_setup_error, odm);
gf_filter_lock_all(scene->compositor->filter, GF_FALSE);
/*OK connect*/
gf_odm_service_media_event(odm, GF_EVENT_MEDIA_SETUP_BEGIN);
}
#endif //!defined(GPAC_DISABLE_COMPOSITOR)