| /* | |
| * 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 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/constants.h> | |
| #if !defined(GPAC_DISABLE_COMPOSITOR) | |
| GF_EXPORT | |
| GF_ObjectManager *gf_odm_new() | |
| { | |
| GF_ObjectManager *tmp; | |
| GF_SAFEALLOC(tmp, GF_ObjectManager); | |
| if (!tmp) return NULL; | |
| #ifndef GPAC_DISABLE_VRML | |
| tmp->ms_stack = gf_list_new(); | |
| tmp->mc_stack = gf_list_new(); | |
| #endif | |
| return tmp; | |
| } | |
| void gf_odm_reset_media_control(GF_ObjectManager *odm, Bool signal_reset) | |
| { | |
| #ifndef GPAC_DISABLE_VRML | |
| MediaSensorStack *media_sens; | |
| MediaControlStack *media_ctrl; | |
| while ((media_sens = (MediaSensorStack *)gf_list_last(odm->ms_stack))) { | |
| MS_Stop(media_sens); | |
| /*and detach from stream object*/ | |
| media_sens->stream = NULL; | |
| gf_list_rem_last(odm->ms_stack); | |
| } | |
| while ((media_ctrl = (MediaControlStack *)gf_list_last(odm->mc_stack))) { | |
| if (signal_reset) | |
| gf_odm_remove_mediacontrol(odm, media_ctrl); | |
| media_ctrl->stream = NULL; | |
| media_ctrl->ck = NULL; | |
| gf_list_rem_last(odm->mc_stack); | |
| } | |
| #endif | |
| } | |
| void gf_odm_del(GF_ObjectManager *odm) | |
| { | |
| if (odm->has_pending_remove_task) { | |
| odm->flags |= GF_ODM_DESTROYED; | |
| return; | |
| } | |
| if (odm->addon && (odm->addon->root_od==odm)) { | |
| odm->addon->root_od = NULL; | |
| odm->addon->started = 0; | |
| } | |
| if (odm->upper_layer_odm) { | |
| odm->upper_layer_odm->lower_layer_odm = NULL; | |
| } | |
| if (odm->lower_layer_odm) { | |
| odm->lower_layer_odm->upper_layer_odm = NULL; | |
| } | |
| /*detach media object as referenced by the scene - this should ensures that any attempt to lock the ODM from the | |
| compositor will fail as the media object is no longer linked to object manager*/ | |
| if (odm->mo) odm->mo->odm = NULL; | |
| #ifndef GPAC_DISABLE_VRML | |
| gf_odm_reset_media_control(odm, 0); | |
| gf_list_del(odm->ms_stack); | |
| gf_list_del(odm->mc_stack); | |
| if (odm->type == GF_STREAM_INTERACT) | |
| gf_input_sensor_delete(odm); | |
| #endif | |
| if (odm->raw_frame_sema) gf_sema_del(odm->raw_frame_sema); | |
| if (odm->pid) gf_filter_pid_set_udta(odm->pid, NULL); | |
| if (odm->extra_pids) { | |
| while (gf_list_count(odm->extra_pids)) { | |
| GF_ODMExtraPid *xpid = gf_list_pop_back(odm->extra_pids); | |
| if (xpid->pid) gf_filter_pid_set_udta(xpid->pid, NULL); | |
| gf_free(xpid); | |
| } | |
| gf_list_del(odm->extra_pids); | |
| } | |
| if (odm->OD) | |
| gf_odf_desc_del((GF_Descriptor *)odm->OD); | |
| gf_free(odm); | |
| } | |
| void gf_odm_register_pid(GF_ObjectManager *odm, GF_FilterPid *pid, Bool register_only) | |
| { | |
| u32 es_id=0; | |
| const GF_PropertyValue *prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); | |
| if (!prop) prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); | |
| if (prop) es_id = prop->value.uint; | |
| if (! odm->pid) { | |
| odm->pid = pid; | |
| odm->pid_id = es_id; | |
| } else { | |
| GF_ODMExtraPid *xpid; | |
| if (!odm->extra_pids) odm->extra_pids = gf_list_new(); | |
| GF_SAFEALLOC(xpid, GF_ODMExtraPid); | |
| if (xpid) { | |
| xpid->pid = pid; | |
| xpid->pid_id = es_id; | |
| gf_list_add(odm->extra_pids, xpid); | |
| } | |
| } | |
| if (register_only) return; | |
| gf_odm_setup_object(odm, odm->subscene ? odm->scene_ns : odm->parentscene->root_od->scene_ns, pid); | |
| } | |
| //this function is not exposed and shall only be used for the reset scene event | |
| //it sends the event synchronously, as needed for gf_term_disconnect() | |
| void gf_filter_pid_exec_event(GF_FilterPid *pid, GF_FilterEvent *evt); | |
| GF_EXPORT | |
| void gf_odm_disconnect(GF_ObjectManager *odm, u32 do_remove) | |
| { | |
| if (odm->flags & GF_ODM_DESTROYED) return; | |
| GF_Compositor *compositor = odm->parentscene ? odm->parentscene->compositor : odm->subscene->compositor; | |
| if (odm->skip_disconnect_state) { | |
| if (do_remove) odm->skip_disconnect_state = 2; | |
| return; | |
| } | |
| gf_odm_stop(odm, GF_TRUE); | |
| /*disconnect sub-scene*/ | |
| if (odm->subscene) { | |
| //send a scene reset | |
| if (odm->pid) { | |
| GF_FilterEvent fevt; | |
| GF_FEVT_INIT(fevt, GF_FEVT_RESET_SCENE, odm->pid); | |
| fevt.attach_scene.object_manager = odm; | |
| gf_filter_pid_exec_event(odm->pid, &fevt); | |
| } | |
| gf_scene_disconnect(odm->subscene, do_remove ? GF_TRUE : GF_FALSE); | |
| } | |
| else if (odm->pid) { | |
| GF_FilterEvent fevt; | |
| switch (odm->type) { | |
| case GF_STREAM_SCENE: | |
| case GF_STREAM_OD: | |
| case GF_STREAM_PRIVATE_SCENE: | |
| case GF_STREAM_TEXT: | |
| GF_FEVT_INIT(fevt, GF_FEVT_RESET_SCENE, odm->pid); | |
| fevt.attach_scene.object_manager = odm; | |
| fevt.attach_scene.on_pid = odm->pid; | |
| gf_filter_pid_exec_event(odm->pid, &fevt); | |
| break; | |
| } | |
| } | |
| /*no destroy*/ | |
| if (!do_remove) return; | |
| /*unload the decoders before deleting the channels to prevent any access fault*/ | |
| if (odm->type==GF_STREAM_INTERACT) { | |
| u32 i, count; | |
| // Remove all Registered InputSensor nodes -> shut down the InputSensor threads -> prevent illegal access on deleted pointers | |
| GF_MediaObject *obj = odm->mo; | |
| count = gf_mo_event_target_count(obj); | |
| for (i=0; i<count; i++) { | |
| GF_Node *n = (GF_Node *)gf_event_target_get_node(gf_mo_event_target_get(obj, i)); | |
| switch (gf_node_get_tag(n)) { | |
| #ifndef GPAC_DISABLE_VRML | |
| case TAG_MPEG4_InputSensor: | |
| ((M_InputSensor*)n)->enabled = 0; | |
| InputSensorModified(n); | |
| break; | |
| #endif | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| /*detach from network service */ | |
| if (odm->scene_ns) { | |
| GF_Scene *scene = odm->parentscene; | |
| GF_SceneNamespace *ns = odm->scene_ns; | |
| if (ns->nb_odm_users) ns->nb_odm_users--; | |
| if (ns->owner == odm) { | |
| /*detach it!!*/ | |
| ns->owner = NULL; | |
| /*try to assign a new root in case this is not scene shutdown*/ | |
| if (ns->nb_odm_users && odm->parentscene) { | |
| GF_ObjectManager *new_root; | |
| u32 i = 0; | |
| while ((new_root = (GF_ObjectManager *)gf_list_enum(odm->parentscene->resources, &i)) ) { | |
| if (new_root == odm) continue; | |
| if (new_root->scene_ns != ns) continue; | |
| ns->owner = new_root; | |
| break; | |
| } | |
| } | |
| if (!ns->owner) ns->nb_odm_users=0; | |
| } | |
| scene = scene ? gf_scene_get_root_scene(scene) : NULL; | |
| odm->scene_ns = NULL; | |
| if (!ns->nb_odm_users && scene) gf_scene_ns_del(ns, scene); | |
| } | |
| /*delete from the parent scene.*/ | |
| if (odm->parentscene) { | |
| GF_Event evt; | |
| if ((odm->type == GF_STREAM_AUDIO) && odm->parentscene->compositor->audio_renderer->nb_audio_objects) | |
| odm->parentscene->compositor->audio_renderer->nb_audio_objects--; | |
| if (odm->addon) { | |
| gf_list_del_item(odm->parentscene->declared_addons, odm->addon); | |
| gf_scene_reset_addon(odm->addon, GF_FALSE); | |
| odm->addon = NULL; | |
| } | |
| evt.type = GF_EVENT_CONNECT; | |
| evt.connect.is_connected = GF_FALSE; | |
| gf_filter_forward_gf_event(odm->parentscene->compositor->filter, &evt, GF_FALSE, GF_TRUE); | |
| gf_scene_remove_object(odm->parentscene, odm, do_remove); | |
| if (odm->subscene) { | |
| gf_scene_del(odm->subscene); | |
| } | |
| gf_odm_del(odm); | |
| return; | |
| } | |
| /*this is the scene root OD (may be a remote OD ..) */ | |
| if (odm->subscene) { | |
| GF_Event evt; | |
| evt.type = GF_EVENT_CONNECT; | |
| evt.connect.is_connected = GF_FALSE; | |
| gf_sc_send_event(compositor, &evt); | |
| gf_scene_del(odm->subscene); | |
| } | |
| /*delete the ODMan*/ | |
| gf_odm_del(odm); | |
| } | |
| static Bool gf_odm_should_auto_select(GF_ObjectManager *odm) | |
| { | |
| u32 i, count; | |
| if (odm->type == GF_STREAM_SCENE) return GF_TRUE; | |
| //TODO- detect image media to start playback right away | |
| //if (odm->type == GF_STREAM_VISUAL) return GF_TRUE; | |
| if (odm->parentscene && !odm->parentscene->is_dynamic_scene) { | |
| return GF_FALSE; | |
| } | |
| if (odm->parentscene && odm->parentscene->root_od->addon) { | |
| if (odm->parentscene->root_od->addon->addon_type == GF_ADDON_TYPE_MAIN) | |
| return GF_FALSE; | |
| } | |
| count = gf_list_count(odm->parentscene->resources); | |
| for (i=0; i<count; i++) { | |
| GF_ObjectManager *an_odm = gf_list_get(odm->parentscene->resources, i); | |
| if (an_odm==odm) continue; | |
| if (an_odm->type != odm->type) continue; | |
| //same type - if the first one has been automatically activated, do not activate this one | |
| if (an_odm->state == GF_ODM_STATE_PLAY) return GF_FALSE; | |
| } | |
| return GF_TRUE; | |
| } | |
| void gf_odm_setup_remote_object(GF_ObjectManager *odm, GF_SceneNamespace *parent_ns, char *remote_url, Bool for_addon) | |
| { | |
| char *parent_url = NULL; | |
| if (!remote_url) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_COMPTIME, ("[ODM%d] No URL specified for remote object - ignoring object setup\n", odm->ID)); | |
| return; | |
| } | |
| if (!odm->scene_ns) { | |
| if (odm->flags & GF_ODM_DESTROYED) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_COMPTIME, ("[ODM%d] Object has been scheduled for destruction - ignoring object setup\n", odm->ID)); | |
| return; | |
| } | |
| odm->scene_ns = parent_ns ? parent_ns : odm->parentscene->root_od->scene_ns; | |
| if (odm->scene_ns) | |
| odm->scene_ns->nb_odm_users++; | |
| } | |
| /*store original OD ID */ | |
| if (!odm->media_current_time) | |
| odm->media_current_time = odm->ID; | |
| //detach it | |
| odm->scene_ns = NULL; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("[ODM%d] Object redirection to %s (MO %08x)\n", odm->ID, remote_url, odm->mo)); | |
| /*if object is a scene, create the inline before connecting the object. | |
| This is needed in order to register the nodes using the resource for event | |
| propagation (stored at the inline level) | |
| */ | |
| if (odm->mo && (odm->mo->type==GF_MEDIA_OBJECT_SCENE) && !odm->subscene) { | |
| odm->subscene = gf_scene_new(NULL, odm->parentscene); | |
| odm->subscene->root_od = odm; | |
| //scenes are by default dynamic | |
| odm->subscene->is_dynamic_scene = GF_TRUE; | |
| } | |
| parent_url = parent_ns ? parent_ns->url : NULL; | |
| if (parent_url && !strnicmp(parent_url, "views://", 8)) | |
| parent_url = NULL; | |
| //make sure we don't have an ID before attempting to connect | |
| if (odm->ID == GF_MEDIA_EXTERNAL_ID) { | |
| odm->ID = 0; | |
| } | |
| odm->flags |= GF_ODM_NOT_IN_OD_STREAM; | |
| odm->ServiceID = 0; | |
| gf_scene_ns_connect_object(odm->subscene ? odm->subscene : odm->parentscene, odm, remote_url, parent_url, for_addon ? parent_ns : NULL); | |
| } | |
| GF_EXPORT | |
| void gf_odm_setup_object(GF_ObjectManager *odm, GF_SceneNamespace *parent_ns, GF_FilterPid *for_pid) | |
| { | |
| GF_Err e; | |
| if (!odm->scene_ns) { | |
| if (odm->flags & GF_ODM_DESTROYED) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_COMPTIME, ("[ODM%d] Object has been scheduled for destruction - ignoring object setup\n", odm->ID)); | |
| return; | |
| } | |
| odm->scene_ns = parent_ns; | |
| odm->scene_ns->nb_odm_users++; | |
| } | |
| /*restore OD ID */ | |
| if (odm->media_current_time) { | |
| odm->ID = (u32) odm->media_current_time; | |
| odm->media_current_time = 0; | |
| odm->flags |= GF_ODM_REMOTE_OD; | |
| } | |
| if (odm->scene_ns->owner && (odm->scene_ns->owner->flags & GF_ODM_INHERIT_TIMELINE)) { | |
| odm->flags |= GF_ODM_INHERIT_TIMELINE; | |
| } | |
| /*empty object, use a dynamic scene*/ | |
| if (! odm->pid && odm->subscene) { | |
| gf_assert(odm->subscene->root_od==odm); | |
| odm->subscene->is_dynamic_scene = GF_TRUE; | |
| } else if (odm->pid) { | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("[Compositor] Setting up object streams\n")); | |
| e = gf_odm_setup_pid(odm, for_pid); | |
| if (e) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPTIME, ("Service %s PID %s Setup Failure: %s", odm->scene_ns->url, gf_filter_pid_get_name(for_pid ? for_pid : odm->pid), gf_error_to_string(e) )); | |
| } | |
| } | |
| if (odm->pid && !odm->buffer_playout_ms) { | |
| GF_FilterEvent evt; | |
| const GF_PropertyValue *prop; | |
| u32 tsdepth=0; | |
| GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; | |
| odm->buffer_playout_ms = scene->compositor->buffer; | |
| odm->buffer_min_ms = scene->compositor->rbuffer; | |
| odm->buffer_max_ms = scene->compositor->mbuffer; | |
| //check the same on the pid | |
| prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_PLAY_BUFFER); | |
| if (prop) odm->buffer_playout_ms = prop->value.uint; | |
| prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_RE_BUFFER); | |
| if (prop) odm->buffer_min_ms = prop->value.uint; | |
| prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_MAX_BUFFER); | |
| if (prop) odm->buffer_max_ms = prop->value.uint; | |
| prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_TIMESHIFT_DEPTH); | |
| if (prop && prop->value.frac.den) { | |
| tsdepth = (u32) ( ((u64)prop->value.frac.num) * 1000 / prop->value.frac.den); | |
| } | |
| gf_odm_set_timeshift_depth(odm, tsdepth); | |
| if (odm->buffer_playout_ms > odm->buffer_max_ms) odm->buffer_max_ms = odm->buffer_playout_ms; | |
| prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_FILE_CACHED); | |
| if (prop && prop->value.boolean) { | |
| odm->buffer_playout_ms = odm->buffer_max_ms = 1; //1 ms | |
| odm->buffer_min_ms = 0; | |
| } | |
| prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_RAWGRAB); | |
| if (prop && prop->value.uint) { | |
| odm->buffer_playout_ms = 0; | |
| odm->buffer_min_ms = 0; | |
| odm->buffer_max_ms = 0; | |
| } | |
| GF_FEVT_INIT(evt, GF_FEVT_BUFFER_REQ, for_pid ? for_pid : odm->pid); | |
| evt.buffer_req.max_buffer_us = odm->buffer_max_ms * 1000; | |
| evt.buffer_req.min_playout_us = odm->buffer_min_ms * 1000; | |
| evt.buffer_req.max_playout_us = odm->buffer_playout_ms * 1000; | |
| gf_filter_pid_send_event(NULL, &evt); | |
| if (odm->buffer_min_ms * 1000 > evt.buffer_req.max_playout_us) | |
| odm->buffer_min_ms = 0; | |
| if (gf_filter_pid_is_sparse(for_pid ? for_pid : odm->pid)) | |
| odm->buffer_min_ms = odm->buffer_max_ms = odm->buffer_playout_ms = 0; | |
| } | |
| /*setup mediaobject info except for top-level OD*/ | |
| if (odm->parentscene) { | |
| GF_Event evt; | |
| //this may result in an attempt to lock the compositor, so release the net MX before | |
| gf_scene_setup_object(odm->parentscene, odm); | |
| /*setup node decoder*/ | |
| if (odm->pid && (odm->type==GF_STREAM_SCENE)) { | |
| const GF_PropertyValue *p = gf_filter_pid_get_property(odm->pid, GF_PROP_PID_SCENE_NODE); | |
| if (p && p->value.boolean) { | |
| GF_FilterEvent fevt; | |
| void gf_filter_pid_exec_event(GF_FilterPid *pid, GF_FilterEvent *evt); | |
| GF_Node *n = gf_event_target_get_node(gf_mo_event_target_get(odm->mo, 0)); | |
| GF_FEVT_INIT(fevt, GF_FEVT_ATTACH_SCENE, odm->pid); | |
| fevt.attach_scene.object_manager = odm; | |
| fevt.attach_scene.node = n; | |
| gf_filter_pid_exec_event(odm->pid, &fevt); | |
| /*not clear in the spec how the streams attached to AFX are started - default to "right now"*/ | |
| gf_odm_start(odm); | |
| } | |
| } | |
| if (odm->pid && (odm->pid==for_pid)) { | |
| evt.type = GF_EVENT_CONNECT; | |
| evt.connect.is_connected = GF_TRUE; | |
| gf_filter_forward_gf_event(odm->parentscene->compositor->filter, &evt, GF_FALSE, GF_TRUE); | |
| } | |
| } else if (odm->pid==for_pid) { | |
| /*othewise send a connect ack for top level*/ | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("[ODM] Root object connected (%s) !\n", odm->scene_ns->url)); | |
| if (odm->subscene) { | |
| GF_Event evt; | |
| evt.type = GF_EVENT_CONNECT; | |
| evt.connect.is_connected = GF_TRUE; | |
| gf_sc_send_event(odm->subscene->compositor, &evt); | |
| } | |
| } | |
| /* start object*/ | |
| /*object is already started (new PID was inserted for this object)*/ | |
| if (odm->state==GF_ODM_STATE_PLAY) { | |
| if (odm->pid==for_pid) | |
| odm->state = GF_ODM_STATE_STOP; | |
| gf_odm_start(odm); | |
| } | |
| /*object is the root, always start*/ | |
| else if (!odm->parentscene) { | |
| gf_assert(odm->subscene && (odm->subscene->root_od==odm)); | |
| odm->flags &= ~GF_ODM_NOT_SETUP; | |
| gf_odm_start(odm); | |
| } | |
| /*object is a pure OCR object - connect*/ | |
| else if (odm->type==GF_STREAM_OCR) { | |
| odm->flags &= ~GF_ODM_NOT_SETUP; | |
| gf_odm_start(odm); | |
| } | |
| /*if the object is inserted from a broadcast, start it if not already done. This covers cases where the scene (BIFS, LASeR) and | |
| the media (images) are both carrouseled and the carrousels are interleaved. If we wait for the scene to trigger a PLAY, we will likely | |
| have to wait for an entire image carousel period to start filling the buffers, which is sub-optimal | |
| we also force a prefetch for object declared outside the OD stream to make sure we don't loose any data before object declaration and play | |
| as can be the case with MPEG2 TS (first video packet right after the PMT) - this should be refined*/ | |
| else if ( ((odm->flags & GF_ODM_NO_TIME_CTRL) || (odm->flags & GF_ODM_NOT_IN_OD_STREAM)) && (odm->parentscene->selected_service_id == odm->ServiceID)) { | |
| if (gf_odm_should_auto_select(odm)) { | |
| Bool force_play = GF_FALSE; | |
| if (odm->state==GF_ODM_STATE_STOP) { | |
| odm->flags |= GF_ODM_PREFETCH; | |
| force_play = GF_TRUE; | |
| } | |
| if (force_play) { | |
| odm->flags |= GF_ODM_INITIAL_BROADCAST_PLAY; | |
| odm->parentscene->selected_service_id = odm->ServiceID; | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPTIME, ("[ODM%d] Inserted from input service %s - forcing play\n", odm->ID, odm->scene_ns->url)); | |
| odm->flags &= ~GF_ODM_NOT_SETUP; | |
| gf_odm_start(odm); | |
| } | |
| } else { | |
| //otherwise issue play/stop to stop the stream | |
| //the play is needed because some filters (m2ts demux) start considering the pid is active (for reframers) | |
| gf_odm_start(odm); | |
| gf_odm_stop(odm, GF_FALSE); | |
| } | |
| } | |
| odm->flags &= ~GF_ODM_NOT_SETUP; | |
| /*for objects inserted by user (subs & co), auto select*/ | |
| if (odm->parentscene && odm->parentscene->is_dynamic_scene | |
| && (odm->ID==GF_MEDIA_EXTERNAL_ID) | |
| && (odm->flags & GF_ODM_REMOTE_OD) | |
| ) { | |
| GF_Event evt; | |
| if (odm->addon) { | |
| Bool role_set = 0; | |
| if (odm->addon->addon_type >= GF_ADDON_TYPE_MAIN) return; | |
| //check role - for now look into URL, we need to inspect DASH roles | |
| if (odm->mo && odm->mo->URLs.count && odm->mo->URLs.vals[0].url) { | |
| char *sep = strchr(odm->mo->URLs.vals[0].url, '?'); | |
| if (sep && strstr(sep, "role=main")) { | |
| odm->addon->addon_type = GF_ADDON_TYPE_MAIN; | |
| role_set = 1; | |
| } | |
| } | |
| if (!role_set) { | |
| const GF_PropertyValue *prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_ROLE); | |
| if (prop && prop->value.string_list.nb_items && !strcmp(prop->value.string_list.vals[0], "main")) { | |
| odm->addon->addon_type = GF_ADDON_TYPE_MAIN; | |
| } | |
| } | |
| if (odm->addon->addon_type == GF_ADDON_TYPE_ADDITIONAL) { | |
| gf_scene_select_object(odm->parentscene, odm); | |
| } | |
| return; | |
| } | |
| if (!odm->parentscene) { | |
| evt.type = GF_EVENT_STREAMLIST; | |
| gf_sc_send_event(odm->parentscene->compositor, &evt); | |
| return; | |
| } | |
| } else if (odm->parentscene) { | |
| const GF_PropertyValue *prop = gf_filter_pid_get_property(for_pid ? for_pid : odm->pid, GF_PROP_PID_ROLE); | |
| if (prop && prop->value.string_list.nb_items && !strncmp(prop->value.string_list.vals[0], "ambi", 4)) { | |
| odm->ambi_ch_id = atoi(prop->value.string_list.vals[0] + 4); | |
| if (odm->ambi_ch_id > odm->parentscene->ambisonic_type) | |
| odm->parentscene->ambisonic_type = odm->ambi_ch_id; | |
| } | |
| } | |
| } | |
| /*setup channel, clock and query caps*/ | |
| GF_EXPORT | |
| GF_Err gf_odm_setup_pid(GF_ObjectManager *odm, GF_FilterPid *pid) | |
| { | |
| GF_Clock *ck; | |
| GF_List *ck_namespace; | |
| s8 flag; | |
| u32 clockID; | |
| GF_Scene *scene; | |
| Bool clock_inherited = GF_TRUE; | |
| const GF_PropertyValue *prop; | |
| u32 OD_OCR_ID=0; | |
| u32 es_id=0; | |
| u32 ckid_use_odid = 0; | |
| /*find the clock for this new channel*/ | |
| ck = NULL; | |
| flag = (s8) -1; | |
| if (!pid) pid = odm->pid; | |
| #ifdef GPAC_ENABLE_COVERAGE | |
| if (gf_sys_is_cov_mode() && pid) { | |
| GF_FilterPidStatistics stats; | |
| gf_filter_pid_get_statistics(pid, &stats, GF_STATS_DECODER_SOURCE); | |
| gf_filter_pid_get_packet_count(pid); | |
| } | |
| #endif | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ESID); | |
| if (!prop) prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); | |
| if (prop) es_id = prop->value.uint; | |
| if (odm->ck) { | |
| ck = odm->ck; | |
| goto clock_setup; | |
| } | |
| /*sync reference*/ | |
| if (odm->sync_ref && odm->sync_ref->odm) { | |
| ck = odm->sync_ref->odm->ck; | |
| goto clock_setup; | |
| } | |
| /*timeline override*/ | |
| if (odm->flags & GF_ODM_INHERIT_TIMELINE) { | |
| ck = odm->parentscene->root_od->ck; | |
| /**/ | |
| if (!ck) { | |
| GF_ObjectManager *odm_par = odm->parentscene->root_od->parentscene->root_od; | |
| while (odm_par) { | |
| ck = odm_par->ck; | |
| if (ck) break; | |
| odm_par = (odm_par->parentscene && odm_par->parentscene->root_od->parentscene ) ? odm_par->parentscene->root_od->parentscene->root_od : NULL; | |
| } | |
| } | |
| if (ck) | |
| goto clock_setup; | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_COMPTIME, ("[ODM] Cannot inherit timeline from parent scene for scene %s - new clock will be created\n", odm->scene_ns->url)); | |
| } | |
| /*get clocks namespace (eg, parent scene)*/ | |
| scene = odm->subscene ? odm->subscene : odm->parentscene; | |
| if (!scene) return GF_BAD_PARAM; | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_CLOCK_ID); | |
| if (prop) OD_OCR_ID = prop->value.uint; | |
| ck_namespace = odm->scene_ns->clocks; | |
| odm->set_speed = odm->scene_ns->set_speed; | |
| /*little trick for non-OD addressing: if object is a remote one, and service owner already has clocks, | |
| override OCR. This will solve addressing like file.avi#audio and file.avi#video*/ | |
| if (!OD_OCR_ID && (odm->flags & GF_ODM_REMOTE_OD) && (gf_list_count(ck_namespace)==1) ) { | |
| ck = (GF_Clock*)gf_list_get(ck_namespace, 0); | |
| OD_OCR_ID = ck->clock_id; | |
| } | |
| /*for dynamic scene, force all streams to be sync on main OD stream (one timeline, no need to reload resources)*/ | |
| else if (odm->parentscene && odm->parentscene->is_dynamic_scene && !odm->subscene) { | |
| GF_ObjectManager *parent_od = odm->parentscene->root_od; | |
| if (parent_od->scene_ns && parent_od->scene_ns->clocks && (gf_list_count(parent_od->scene_ns->clocks)==1)) { | |
| ck = (GF_Clock*)gf_list_get(parent_od->scene_ns->clocks, 0); | |
| if (!odm->ServiceID || (odm->ServiceID==ck->service_id)) { | |
| goto clock_setup; | |
| } | |
| } | |
| } | |
| /*do we have an OCR specified*/ | |
| clockID = OD_OCR_ID; | |
| /*if OCR stream force self-synchro !!*/ | |
| if (odm->type == GF_STREAM_OCR) clockID = odm->ID; | |
| if (!clockID) { | |
| if (odm->ID == GF_MEDIA_EXTERNAL_ID) { | |
| clockID = (u32) (intptr_t) odm->scene_ns; | |
| ckid_use_odid = clockID; | |
| } else { | |
| clockID = odm->ID; | |
| ckid_use_odid = clockID; | |
| } | |
| } | |
| /*override clock dependencies if specified*/ | |
| if (scene->compositor->sclock) { | |
| GF_Scene *parent = gf_scene_get_root_scene(scene); | |
| clockID = scene->root_od->ck->clock_id; | |
| ck_namespace = parent->root_od->scene_ns->clocks; | |
| gf_fatal_assert(ck_namespace); | |
| } | |
| ck = gf_clock_attach(ck_namespace, scene, clockID, es_id, flag); | |
| if (!ck) return GF_OUT_OF_MEM; | |
| ck->service_id = odm->ServiceID; | |
| clock_inherited = GF_FALSE; | |
| if (scene->root_od->subscene && scene->root_od->subscene->is_dynamic_scene && !scene->root_od->ck) | |
| scene->root_od->ck = ck; | |
| clock_setup: | |
| gf_fatal_assert(ck); | |
| odm->ck = ck; | |
| odm->clock_inherited = clock_inherited; | |
| if (es_id==ck->clock_id) | |
| odm->owns_clock = GF_TRUE; | |
| else if (ckid_use_odid && (ckid_use_odid==ck->clock_id)) | |
| odm->owns_clock = GF_TRUE; | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_DELAY); | |
| odm->timestamp_offset = prop ? prop->value.longsint : 0; | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE); | |
| //if offset greater than 30sec, move it down to [0s,1s] range to avoid presenting nothing | |
| if ((odm->timestamp_offset > 0) && (prop && prop->value.uint) && (odm->timestamp_offset > 30 * prop->value.uint) | |
| ) { | |
| u64 to_rem = odm->timestamp_offset / prop->value.uint; | |
| odm->timestamp_offset -= to_rem * prop->value.uint; | |
| } | |
| gf_odm_update_duration(odm, pid); | |
| /*regular setup*/ | |
| return GF_OK; | |
| } | |
| void gf_odm_update_duration(GF_ObjectManager *odm, GF_FilterPid *pid) | |
| { | |
| u64 dur=0; | |
| GF_PropertyEntry *pe = NULL; | |
| const GF_PropertyValue *prop; | |
| prop = gf_filter_pid_get_info(pid, GF_PROP_PID_DURATION, &pe); | |
| if (prop && prop->value.lfrac.den) { | |
| dur = (prop->value.lfrac.num<0) ? -prop->value.lfrac.num : prop->value.lfrac.num; | |
| dur *= 1000; | |
| dur /= prop->value.lfrac.den; | |
| } | |
| gf_filter_release_property(pe); | |
| if ((u32) dur > odm->duration) { | |
| odm->duration = (u32) dur; | |
| /*update scene duration*/ | |
| gf_scene_set_duration(odm->subscene ? odm->subscene : odm->parentscene); | |
| } | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_HAS_TEMI); | |
| if (prop && prop->value.boolean) | |
| odm->flags |= GF_ODM_HAS_TEMI; | |
| else | |
| odm->flags &= ~GF_ODM_HAS_TEMI; | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_TIME_DISCONTINUITY); | |
| if (prop && (prop->value.uint != odm->last_ckdisc)) { | |
| odm->last_ckdisc = prop->value.uint; | |
| gf_clock_reset(odm->ck); | |
| } | |
| } | |
| /*this is the tricky part: make sure the net is locked before doing anything since an async service | |
| reply could destroy the object we're queuing for play*/ | |
| void gf_odm_start(GF_ObjectManager *odm) | |
| { | |
| /*object is not started - issue channel setup requests*/ | |
| if (!odm->state) { | |
| /*look for a given segment name to play*/ | |
| if (odm->subscene) { | |
| char *url, *frag=NULL; | |
| gf_assert(odm->subscene->root_od==odm); | |
| url = (odm->mo && odm->mo->URLs.count) ? odm->mo->URLs.vals[0].url : odm->scene_ns->url; | |
| frag = strrchr(url, '#'); | |
| if (frag) { | |
| GF_Segment *seg = gf_odm_find_segment(odm, frag+1); | |
| if (seg) { | |
| odm->media_start_time = (u64) ((s64) seg->startTime*1000); | |
| odm->media_stop_time = (u64) ((s64) (seg->startTime + seg->Duration)*1000); | |
| } | |
| } | |
| } | |
| } | |
| gf_odm_play(odm); | |
| } | |
| void gf_odm_play(GF_ObjectManager *odm) | |
| { | |
| u64 range_end; | |
| Bool media_control_paused = 0; | |
| Bool start_range_is_clock = 0; | |
| Double ck_time; | |
| GF_Clock *clock = odm->ck; | |
| GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; | |
| #ifndef GPAC_DISABLE_VRML | |
| MediaControlStack *ctrl; | |
| #endif | |
| GF_FilterEvent com; | |
| GF_Clock *parent_ck = NULL; | |
| if (!scene) return; | |
| if (odm->mo && odm->mo->pck && !(odm->flags & GF_ODM_PREFETCH)) { | |
| /*reset*/ | |
| gf_filter_pck_unref(odm->mo->pck); | |
| odm->mo->pck = NULL; | |
| //reset all textures pointing to this stream | |
| u32 i, count = gf_list_count(scene->compositor->textures); | |
| for (i=0; i<count; i++) { | |
| GF_TextureHandler *txh = (GF_TextureHandler *)gf_list_get(scene->compositor->textures, i); | |
| if (!txh || (txh->stream != odm->mo)) break; | |
| txh->data = NULL; | |
| txh->frame_ifce = NULL; | |
| } | |
| } | |
| if (odm->parentscene) { | |
| parent_ck = gf_odm_get_media_clock(odm->parentscene->root_od); | |
| if (!gf_odm_shares_clock(odm, parent_ck)) parent_ck = NULL; | |
| } | |
| //PID not yet attached, mark as state_play and wait for error or OK | |
| if (!odm->pid ) { | |
| odm->state = GF_ODM_STATE_PLAY; | |
| return; | |
| } | |
| if ( !(odm->flags & (GF_ODM_INHERIT_TIMELINE|GF_ODM_TILED_SHARED_CLOCK) ) && odm->owns_clock ) { | |
| gf_clock_reset(clock); | |
| } | |
| range_end = odm->media_stop_time; | |
| /*send play command*/ | |
| GF_FEVT_INIT(com, GF_FEVT_PLAY, odm->pid) | |
| if (odm->flags & GF_ODM_INITIAL_BROADCAST_PLAY) { | |
| odm->flags &= ~GF_ODM_INITIAL_BROADCAST_PLAY; | |
| com.play.initial_broadcast_play = 1; | |
| } | |
| /*play from requested time (seeking or non-mpeg4 media control)*/ | |
| if (odm->media_start_time && !clock->clock_init) { | |
| ck_time = (Double) (s64) odm->media_start_time; | |
| ck_time /= 1000; | |
| } | |
| else if (odm->parentscene && odm->parentscene->root_od->media_start_time && !clock->clock_init) { | |
| ck_time = (Double) (s64) odm->parentscene->root_od->media_start_time; | |
| ck_time /= 1000; | |
| } | |
| /*play from current time*/ | |
| else { | |
| ck_time = (Double) gf_clock_media_time(clock); | |
| ck_time /= 1000; | |
| start_range_is_clock = 1; | |
| } | |
| /*handle initial start - MPEG-4 is a bit annoying here, streams are not started through OD but through | |
| scene nodes. If the stream runs on the BIFS/OD clock, the clock is already started at this point and we're | |
| sure to get at least a one-frame delay in PLAY, so just remove it - note we're generous but this shouldn't hurt*/ | |
| if (ck_time<=0.5) ck_time = 0; | |
| /*adjust time for addons*/ | |
| if (odm->parentscene && odm->parentscene->root_od->addon) { | |
| com.play.initial_broadcast_play = 0; | |
| //addon timing is resolved against timestamps, not media time | |
| if (start_range_is_clock) { | |
| if (!gf_clock_is_started(clock)) { | |
| ck_time = (Double) odm->parentscene->root_od->addon->media_pts; | |
| ck_time /= 90000; | |
| } else { | |
| ck_time = (Double) gf_clock_time_absolute(clock); | |
| ck_time /= 1000; | |
| } | |
| } | |
| ck_time = gf_scene_adjust_time_for_addon(odm->parentscene->root_od->addon, ck_time, &com.play.timestamp_based); | |
| //we are having a play request for an addon without the main content being active - we no longer have timestamp info from the main content | |
| if (!clock->clock_init && com.play.timestamp_based) | |
| com.play.timestamp_based = 2; | |
| if (ck_time<0) | |
| ck_time=0; | |
| } | |
| com.play.start_range = ck_time; | |
| if (range_end) { | |
| com.play.end_range = (s64) range_end / 1000.0; | |
| } else { | |
| if (!odm->subscene && odm->parentscene && gf_odm_shares_clock(odm->parentscene->root_od, clock) | |
| && (odm->parentscene->root_od->media_stop_time != odm->parentscene->root_od->duration) | |
| ) { | |
| com.play.end_range = (s64) odm->parentscene->root_od->media_stop_time / 1000.0; | |
| } else { | |
| com.play.end_range = 0; | |
| } | |
| } | |
| com.play.speed = clock->speed; | |
| if (scene->compositor->max_vspeed && (ABS(com.play.speed) > ABS(scene->compositor->max_vspeed))) | |
| com.play.drop_non_ref = GF_TRUE; | |
| #ifndef GPAC_DISABLE_VRML | |
| ctrl = parent_ck ? parent_ck->mc : gf_odm_get_mediacontrol(odm); | |
| /*override range and speed with MC*/ | |
| if (ctrl && !odm->disable_buffer_at_next_play) { | |
| //for addon, use current clock settings (media control is ignored) | |
| if (!odm->parentscene || !odm->parentscene->root_od->addon) { | |
| //this is fake timeshift, eg we are playing a VoD as a timeshift service: stop and start times have already been adjusted | |
| if (ctrl->control->mediaStopTime<0 && !odm->timeshift_depth) { | |
| } else { | |
| MC_GetRange(ctrl, &com.play.start_range, &com.play.end_range); | |
| } | |
| } | |
| com.play.speed = FIX2FLT(ctrl->control->mediaSpeed); | |
| if (scene->compositor->max_vspeed && (ABS(com.play.speed) > ABS(scene->compositor->max_vspeed))) | |
| com.play.drop_non_ref = GF_TRUE; | |
| /*if the channel doesn't control the clock, jump to current time in the controled range, not just the beginning*/ | |
| if ((ctrl->stream != odm->mo) && (ck_time>com.play.start_range) && (com.play.end_range>com.play.start_range) | |
| && (ck_time<com.play.end_range)) { | |
| com.play.start_range = ck_time; | |
| } | |
| if (ctrl->paused) media_control_paused = 1; | |
| gf_clock_set_speed(clock, ctrl->control->mediaSpeed); | |
| if (odm->mo) odm->mo->speed = ctrl->control->mediaSpeed; | |
| #if 0 | |
| /*if requested seek time AND media control, adjust start range to current play time*/ | |
| if ((com.play.speed>=0) && odm->media_start_time) { | |
| if ((com.play.start_range>=0) && (com.play.end_range>com.play.start_range)) { | |
| if (ctrl->control->loop) { | |
| Double active_dur = com.play.end_range - com.play.start_range; | |
| while (ck_time>active_dur) ck_time -= active_dur; | |
| } else { | |
| ck_time = 0; | |
| //com.play.start_range = com.play.end_range; | |
| } | |
| } | |
| com.play.start_range += ck_time; | |
| } | |
| #endif | |
| } | |
| #endif //GPAC_DISABLE_VRML | |
| /*full object playback*/ | |
| if (com.play.end_range<=0) { | |
| if (com.play.speed<0) { | |
| odm->media_stop_time = 0; | |
| } else { | |
| odm->media_stop_time = odm->subscene ? 0 : odm->duration; | |
| } | |
| } else { | |
| odm->media_stop_time = (u64) -1; | |
| if (com.play.end_range < FLT_MAX) { | |
| /*segment playback - since our timing is in ms whereas segment ranges are double precision, | |
| make sure we have a LARGER range in ms, otherwise media sensors won't deactivate properly*/ | |
| odm->media_stop_time = (u64) ceil(1000 * com.play.end_range); | |
| } | |
| } | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPTIME, ("[ODM%d %s] PID %s: At OTB %u requesting PLAY from %g to %g (clock init %d) - speed %g\n", odm->ID, odm->scene_ns->url, odm->pid ? gf_filter_pid_get_name(odm->pid) : "ROOTSCENE", gf_clock_time(clock), com.play.start_range, com.play.end_range, clock->clock_init, com.play.speed)); | |
| if (odm->state != GF_ODM_STATE_PLAY) { | |
| odm->state = GF_ODM_STATE_PLAY; | |
| if (odm->buffer_max_ms) { | |
| odm->nb_buffering++; | |
| scene->nb_buffering++; | |
| //start buffering | |
| gf_clock_buffer_on(odm->ck); | |
| } | |
| odm->has_seen_eos = GF_FALSE; | |
| if ((odm->type!=GF_STREAM_VISUAL) && (odm->type!=GF_STREAM_AUDIO)) { | |
| if (gf_list_find(scene->compositor->systems_pids, odm->pid)<0) | |
| gf_list_add(scene->compositor->systems_pids, odm->pid); | |
| } | |
| gf_filter_pid_send_event(odm->pid, &com); | |
| } | |
| if (odm->extra_pids) { | |
| u32 k, count = gf_list_count(odm->extra_pids); | |
| for (k=0; k<count; k++) { | |
| GF_ODMExtraPid *xpid = gf_list_get(odm->extra_pids, k); | |
| if (xpid->state != GF_ODM_STATE_PLAY) { | |
| xpid->state = GF_ODM_STATE_PLAY; | |
| com.base.on_pid = xpid->pid; | |
| odm->has_seen_eos = GF_FALSE; | |
| odm->nb_buffering++; | |
| scene->nb_buffering++; | |
| //start buffering | |
| gf_clock_buffer_on(odm->ck); | |
| if ((odm->type!=GF_STREAM_VISUAL) && (odm->type!=GF_STREAM_AUDIO)) { | |
| if (gf_list_find(scene->compositor->systems_pids, xpid->pid)<0) | |
| gf_list_add(scene->compositor->systems_pids, xpid->pid); | |
| } | |
| gf_filter_pid_send_event(xpid->pid, &com); | |
| } | |
| } | |
| } | |
| if (odm->parentscene) { | |
| if (odm->parentscene->root_od->addon) { | |
| odm->parentscene->root_od->addon->started = 1; | |
| } | |
| if (odm->parentscene->first_frame_pause_type) { | |
| media_control_paused = GF_TRUE; | |
| } | |
| } else if (odm->subscene && odm->subscene->first_frame_pause_type) { | |
| media_control_paused = GF_TRUE; | |
| } | |
| gf_odm_service_media_event(odm, GF_EVENT_MEDIA_LOAD_START); | |
| gf_odm_service_media_event(odm, GF_EVENT_MEDIA_TIME_UPDATE); | |
| odm->disable_buffer_at_next_play = GF_FALSE; | |
| if (odm->flags & GF_ODM_PAUSE_QUEUED) { | |
| odm->flags &= ~GF_ODM_PAUSE_QUEUED; | |
| media_control_paused = 1; | |
| } | |
| if (media_control_paused) { | |
| gf_odm_pause(odm); | |
| } | |
| } | |
| void gf_odm_stop(GF_ObjectManager *odm, Bool force_close) | |
| { | |
| u32 i; | |
| GF_ODMExtraPid *xpid; | |
| #ifndef GPAC_DISABLE_VRML | |
| MediaControlStack *ctrl; | |
| MediaSensorStack *media_sens; | |
| #endif | |
| GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; | |
| GF_FilterEvent com; | |
| odm->flags &= ~GF_ODM_PREFETCH; | |
| //root ODs of dynamic scene may not have seen play/pause request | |
| if (!odm->state && (!odm->subscene || !odm->subscene->is_dynamic_scene) ) return; | |
| //PID not yet attached, mark as state_stop and wait for error or OK | |
| if (!odm->pid ) { | |
| odm->state = GF_ODM_STATE_STOP; | |
| return; | |
| } | |
| if (force_close && odm->mo) odm->mo->flags |= GF_MO_DISPLAY_REMOVE; | |
| /*stop codecs*/ | |
| if (odm->subscene) { | |
| GF_ObjectManager *sub_odm; | |
| /*stops all resources of the subscene as well*/ | |
| i=0; | |
| while ((sub_odm=(GF_ObjectManager *)gf_list_enum(odm->subscene->resources, &i))) { | |
| gf_odm_stop(sub_odm, force_close); | |
| } | |
| } | |
| /*send stop command*/ | |
| odm->has_seen_eos = GF_FALSE; | |
| odm->state = GF_ODM_STATE_STOP; | |
| GF_FEVT_INIT(com, GF_FEVT_STOP, odm->pid) | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPTIME, ("[ODM%d %s] PID %s At OTB %u requesting STOP\n", odm->ID, odm->scene_ns->url, odm->pid ? gf_filter_pid_get_name(odm->pid) : "ROOTSCENE", odm->ck ? gf_clock_time(odm->ck) : 0 )); | |
| gf_filter_pid_send_event(odm->pid, &com); | |
| gf_list_del_item(scene->compositor->systems_pids, odm->pid); | |
| i=0; | |
| while ((xpid = (GF_ODMExtraPid*)gf_list_enum(odm->extra_pids, &i)) ) { | |
| xpid->has_seen_eos = GF_FALSE; | |
| xpid->state = GF_ODM_STATE_STOP; | |
| com.base.on_pid = xpid->pid; | |
| gf_list_del_item(scene->compositor->systems_pids, xpid->pid); | |
| gf_filter_pid_send_event(xpid->pid, &com); | |
| } | |
| if (odm->parentscene && odm->parentscene->root_od->addon) { | |
| odm->parentscene->root_od->addon->started = 0; | |
| } | |
| if (odm->nb_buffering) { | |
| gf_assert(scene->nb_buffering>=odm->nb_buffering); | |
| scene->nb_buffering -= odm->nb_buffering; | |
| while (odm->nb_buffering && odm->ck) { | |
| gf_clock_buffer_off(odm->ck); | |
| odm->nb_buffering--; | |
| } | |
| if (!scene->nb_buffering) { | |
| gf_scene_buffering_info(scene, GF_TRUE); | |
| } | |
| } | |
| gf_odm_service_media_event(odm, GF_EVENT_ABORT); | |
| /*stops clock if this is a scene stop*/ | |
| if (!(odm->flags & GF_ODM_INHERIT_TIMELINE) && odm->subscene && odm->ck && odm->owns_clock) { | |
| gf_clock_reset(odm->ck); | |
| } | |
| odm->media_current_time = 0; | |
| #ifndef GPAC_DISABLE_VRML | |
| /*reset media sensor(s)*/ | |
| i = 0; | |
| while ((media_sens = (MediaSensorStack *)gf_list_enum(odm->ms_stack, &i))) { | |
| MS_Stop(media_sens); | |
| } | |
| /*reset media control state*/ | |
| ctrl = gf_odm_get_mediacontrol(odm); | |
| if (ctrl) ctrl->current_seg = 0; | |
| #endif | |
| } | |
| void gf_odm_on_eos(GF_ObjectManager *odm, GF_FilterPid *pid) | |
| { | |
| u32 i, count; | |
| Bool all_done = GF_TRUE; | |
| #ifndef GPAC_DISABLE_VRML | |
| if (gf_odm_check_segment_switch(odm)) return; | |
| #endif | |
| if (odm->pid==pid) { | |
| odm->has_seen_eos = GF_TRUE; | |
| } | |
| if (!odm->has_seen_eos) all_done = GF_FALSE; | |
| count = gf_list_count(odm->extra_pids); | |
| for (i=0; i<count; i++) { | |
| GF_ODMExtraPid *xpid = gf_list_get(odm->extra_pids, i); | |
| if (xpid->pid == pid) { | |
| xpid->has_seen_eos = GF_TRUE; | |
| } | |
| if (!xpid->has_seen_eos) all_done = GF_FALSE; | |
| } | |
| if (!all_done) return; | |
| if (odm->addon && odm->addon->is_splicing) | |
| odm->addon->is_over = 1; | |
| if (odm->parentscene && odm->parentscene->root_od->addon && odm->parentscene->root_od->addon->is_splicing) | |
| odm->parentscene->root_od->addon->is_over = 1; | |
| if (odm->ck->has_seen_eos) return; | |
| odm->ck->has_seen_eos = 1; | |
| gf_odm_check_buffering(odm, pid); | |
| #ifndef GPAC_DISABLE_VRML | |
| //check for scene restart upon end of stream | |
| if (odm->subscene) { | |
| gf_scene_mpeg4_inline_check_restart(odm->subscene); | |
| } | |
| #endif | |
| gf_odm_service_media_event(odm, GF_EVENT_MEDIA_LOAD_DONE); | |
| //a little optimization here: for scene with no associated resources (no audio, video, images), unload | |
| //the filter chain once the scene is loaded | |
| //TODO: further optimize to disconnect scenes with static resources (images, logo, ...) | |
| if (odm->subscene && !gf_list_count(odm->subscene->resources)) { | |
| Bool skip = GF_FALSE; | |
| GF_PropertyEntry *pe=NULL; | |
| const GF_PropertyValue *p = gf_filter_pid_get_info(odm->pid, GF_PROP_PID_KEEP_AFTER_EOS, &pe); | |
| if (p && p->value.boolean) skip = GF_TRUE; | |
| gf_filter_release_property(pe); | |
| //if PID disabled auto-remove, do not destroy filter chain | |
| if (!skip) { | |
| GF_FilterEvent fevt; | |
| GF_FEVT_INIT(fevt, GF_FEVT_RESET_SCENE, odm->pid); | |
| fevt.attach_scene.object_manager = odm; | |
| gf_filter_pid_exec_event(odm->pid, &fevt); | |
| gf_filter_pid_set_udta(odm->pid, NULL); | |
| odm->pid = NULL; | |
| for (i=0; i<count; i++) { | |
| GF_ODMExtraPid *xpid = gf_list_get(odm->extra_pids, i); | |
| gf_filter_pid_set_udta(xpid->pid, NULL); | |
| xpid->pid = NULL; | |
| } | |
| gf_filter_remove_src(odm->subscene->compositor->filter, odm->scene_ns->source_filter); | |
| odm->scene_ns->source_filter = NULL; | |
| } | |
| } | |
| gf_odm_signal_eos_reached(odm); | |
| } | |
| void gf_odm_signal_eos_reached(GF_ObjectManager *odm) | |
| { | |
| if (odm->parentscene && !gf_scene_is_root(odm->parentscene) ) { | |
| GF_ObjectManager *root = odm->parentscene->root_od; | |
| Bool is_over = 0; | |
| if (!gf_scene_check_clocks(root->scene_ns, root->subscene, 0)) return; | |
| if (root->subscene->is_dynamic_scene) | |
| is_over = 1; | |
| else | |
| is_over = gf_sc_is_over(odm->parentscene->compositor, root->subscene->graph); | |
| if (is_over) { | |
| gf_odm_service_media_event(root, GF_EVENT_MEDIA_ENDED); | |
| } | |
| } else { | |
| GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; | |
| if (scene && odm->parentscene && gf_sc_check_end_of_scene(scene->compositor, 0)) { | |
| GF_Event evt; | |
| evt.type = GF_EVENT_EOS; | |
| gf_sc_send_event(odm->parentscene->compositor, &evt); | |
| } | |
| } | |
| } | |
| void gf_odm_set_timeshift_depth(GF_ObjectManager *odm, u32 stream_timeshift) | |
| { | |
| GF_Scene *scene; | |
| if (odm->timeshift_depth == stream_timeshift) | |
| return; | |
| odm->timeshift_depth = stream_timeshift; | |
| scene = odm->subscene ? odm->subscene : odm->parentscene; | |
| /*update scene duration*/ | |
| gf_scene_set_timeshift_depth(scene); | |
| } | |
| GF_Clock *gf_odm_get_media_clock(GF_ObjectManager *odm) | |
| { | |
| while (odm->lower_layer_odm) { | |
| odm = odm->lower_layer_odm; | |
| } | |
| if (odm->ck) return odm->ck; | |
| return NULL; | |
| } | |
| Bool gf_odm_shares_clock(GF_ObjectManager *odm, GF_Clock *ck) | |
| { | |
| if (odm->ck == ck) return GF_TRUE; | |
| return GF_FALSE; | |
| } | |
| void gf_odm_pause(GF_ObjectManager *odm) | |
| { | |
| u32 i; | |
| #ifndef GPAC_DISABLE_VRML | |
| MediaSensorStack *media_sens; | |
| #endif | |
| GF_ODMExtraPid *xpid; | |
| GF_FilterEvent com; | |
| GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; | |
| //postpone until the PLAY request | |
| if (odm->state != GF_ODM_STATE_PLAY) { | |
| odm->flags |= GF_ODM_PAUSE_QUEUED; | |
| return; | |
| } | |
| if (odm->flags & GF_ODM_PAUSED) return; | |
| odm->flags |= GF_ODM_PAUSED; | |
| //cleanup - we need to enter in stop state for broadcast modes | |
| if (odm->flags & GF_ODM_NO_TIME_CTRL) { | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_COMPTIME, ("[ODM%d %s] PID %s: no time control available in source filter, will not pause\n", odm->ID, odm->scene_ns->url, odm->pid ? gf_filter_pid_get_name(odm->pid) : "ROOTSCENE")); | |
| return; | |
| } | |
| scene = gf_scene_get_root_scene(scene); | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPTIME, ("[ODM%d %s] PID %s: At OTB %u requesting PAUSE (clock init %d)\n", odm->ID, odm->scene_ns->url, odm->pid ? gf_filter_pid_get_name(odm->pid) : "ROOTSCENE", gf_clock_time(odm->ck), odm->ck->clock_init )); | |
| GF_FEVT_INIT(com, GF_FEVT_PAUSE, odm->pid); | |
| gf_clock_pause(odm->ck); | |
| if ((odm->state == GF_ODM_STATE_PLAY) && (scene->first_frame_pause_type!=2)) { | |
| gf_filter_pid_send_event(odm->pid, &com); | |
| } | |
| i=0; | |
| while ((xpid = (GF_ODMExtraPid*)gf_list_enum(odm->extra_pids, &i))) { | |
| gf_clock_pause(odm->ck); | |
| if (xpid->state != GF_ODM_STATE_PLAY) continue; | |
| //if we are in dump mode, the clocks are paused (step-by-step render), but we don't send the pause commands to | |
| //the network ! | |
| if (scene->first_frame_pause_type!=2) { | |
| com.base.on_pid = xpid->pid; | |
| gf_filter_pid_send_event(xpid->pid, &com); | |
| } | |
| } | |
| //if we are in dump mode, only the clocks are paused (step-by-step render), the media object is still in play state | |
| if (scene->first_frame_pause_type==2) { | |
| return; | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| /*mediaSensor shall generate isActive false when paused*/ | |
| i=0; | |
| while ((media_sens = (MediaSensorStack *)gf_list_enum(odm->ms_stack, &i)) ) { | |
| if (media_sens->sensor->isActive) { | |
| media_sens->sensor->isActive = 0; | |
| gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/); | |
| } | |
| } | |
| #endif | |
| } | |
| void gf_odm_resume(GF_ObjectManager *odm) | |
| { | |
| u32 i; | |
| #ifndef GPAC_DISABLE_VRML | |
| MediaSensorStack *media_sens; | |
| MediaControlStack *ctrl; | |
| #endif | |
| GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; | |
| GF_ODMExtraPid *xpid; | |
| GF_FilterEvent com; | |
| if (odm->flags & GF_ODM_PAUSE_QUEUED) { | |
| odm->flags &= ~GF_ODM_PAUSE_QUEUED; | |
| return; | |
| } | |
| if (!(odm->flags & GF_ODM_PAUSED)) return; | |
| odm->flags &= ~GF_ODM_PAUSED; | |
| if (odm->flags & GF_ODM_NO_TIME_CTRL) return; | |
| #ifndef GPAC_DISABLE_VRML | |
| ctrl = gf_odm_get_mediacontrol(odm); | |
| #endif | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPTIME, ("[ODM%d %s] PID %s: At OTB %u requesting RESUME (clock init %d)\n", odm->ID, odm->scene_ns->url, odm->pid ? gf_filter_pid_get_name(odm->pid) : "ROOTSCENE", gf_clock_time(odm->ck), odm->ck->clock_init )); | |
| GF_FEVT_INIT(com, GF_FEVT_RESUME, odm->pid); | |
| com.play.speed = odm->ck->speed; | |
| #ifndef GPAC_DISABLE_VRML | |
| if (ctrl) com.play.speed = ctrl->control->mediaSpeed; | |
| #endif | |
| if (scene->compositor->max_vspeed && (ABS(com.play.speed) > ABS(scene->compositor->max_vspeed))) | |
| com.play.drop_non_ref = GF_TRUE; | |
| gf_clock_resume(odm->ck); | |
| if (odm->state == GF_ODM_STATE_PLAY) | |
| gf_filter_pid_send_event(odm->pid, &com); | |
| i=0; | |
| while ((xpid = (GF_ODMExtraPid*)gf_list_enum(odm->extra_pids, &i)) ) { | |
| gf_clock_resume(odm->ck); | |
| if (odm->state != GF_ODM_STATE_PLAY) continue; | |
| com.base.on_pid = xpid->pid; | |
| gf_filter_pid_send_event(odm->pid, &com); | |
| } | |
| #ifndef GPAC_DISABLE_VRML | |
| /*override speed with MC*/ | |
| if (ctrl) { | |
| gf_clock_set_speed(odm->ck, ctrl->control->mediaSpeed); | |
| } | |
| /*mediaSensor shall generate isActive TRUE when resumed*/ | |
| i=0; | |
| while ((media_sens = (MediaSensorStack *)gf_list_enum(odm->ms_stack, &i)) ) { | |
| if (!media_sens->sensor->isActive) { | |
| media_sens->sensor->isActive = 1; | |
| gf_node_event_out((GF_Node *) media_sens->sensor, 4/*"isActive"*/); | |
| } | |
| } | |
| #endif | |
| } | |
| void gf_odm_set_speed(GF_ObjectManager *odm, Fixed speed, Bool adjust_clock_speed) | |
| { | |
| u32 i; | |
| GF_ODMExtraPid *xpid; | |
| GF_FilterEvent com; | |
| GF_Scene *scene = odm->subscene ? odm->subscene : odm->parentscene; | |
| if (odm->flags & GF_ODM_NO_TIME_CTRL) return; | |
| if (!odm->pid) return; | |
| if (adjust_clock_speed) | |
| gf_clock_set_speed(odm->ck, speed); | |
| GF_FEVT_INIT(com, GF_FEVT_SET_SPEED, odm->pid); | |
| odm->too_slow = GF_FALSE; | |
| com.play.speed = FIX2FLT(speed); | |
| if (scene->compositor->max_vspeed && (ABS(com.play.speed) > ABS(scene->compositor->max_vspeed))) | |
| com.play.drop_non_ref = GF_TRUE; | |
| gf_filter_pid_send_event(odm->pid, &com); | |
| i=0; | |
| while ((xpid = (GF_ODMExtraPid*)gf_list_enum(odm->extra_pids, &i)) ) { | |
| com.play.on_pid = xpid->pid; | |
| gf_filter_pid_send_event(xpid->pid, &com); | |
| } | |
| } | |
| GF_Segment *gf_odm_find_segment(GF_ObjectManager *odm, char *descName) | |
| { | |
| GF_Segment *desc; | |
| u32 i = 0; | |
| if (!odm->OD) return NULL; | |
| while ( (desc = (GF_Segment *)gf_list_enum(odm->OD->OCIDescriptors, &i)) ) { | |
| if (desc->tag != GF_ODF_SEGMENT_TAG) continue; | |
| if (!stricmp(desc->SegmentName, descName)) return desc; | |
| } | |
| return NULL; | |
| } | |
| static void gf_odm_insert_segment(GF_ObjectManager *odm, GF_Segment *seg, GF_List *list) | |
| { | |
| /*this reorders segments when inserting into list - I believe this is not compliant*/ | |
| GF_Segment *desc; | |
| u32 i = 0; | |
| while ((desc = gf_list_enum(list, &i))) { | |
| if (desc == seg) return; | |
| if (seg->startTime + seg->Duration <= desc->startTime) { | |
| gf_list_insert(list, seg, i); | |
| return; | |
| } | |
| } | |
| gf_list_add(list, seg); | |
| } | |
| /*add segment descriptor and sort them*/ | |
| void gf_odm_init_segments(GF_ObjectManager *odm, GF_List *list, MFURL *url) | |
| { | |
| char *str, *sep; | |
| char seg1[1024], seg2[1024], seg_url[4096]; | |
| GF_Segment *first_seg, *last_seg, *seg; | |
| u32 i, j; | |
| /*browse all URLs*/ | |
| for (i=0; i<url->count; i++) { | |
| if (!url->vals[i].url) continue; | |
| str = strstr(url->vals[i].url, "#"); | |
| if (!str) continue; | |
| str++; | |
| gf_strcpy(seg_url, str); | |
| /*segment closed range*/ | |
| if ((sep = strstr(seg_url, "-")) ) { | |
| gf_strcpy(seg2, sep+1); | |
| sep[0] = 0; | |
| gf_strcpy(seg1, seg_url); | |
| first_seg = gf_odm_find_segment(odm, seg1); | |
| if (!first_seg) continue; | |
| last_seg = gf_odm_find_segment(odm, seg2); | |
| } | |
| /*segment open range*/ | |
| else if ((sep = strstr(seg_url, "+")) ) { | |
| sep[0] = 0; | |
| gf_strcpy(seg1, seg_url); | |
| first_seg = gf_odm_find_segment(odm, seg_url); | |
| if (!first_seg) continue; | |
| last_seg = NULL; | |
| } | |
| /*single segment*/ | |
| else { | |
| first_seg = gf_odm_find_segment(odm, seg_url); | |
| if (!first_seg) continue; | |
| gf_odm_insert_segment(odm, first_seg, list); | |
| continue; | |
| } | |
| /*segment range process*/ | |
| gf_odm_insert_segment(odm, first_seg, list); | |
| j=0; | |
| while ( (seg = (GF_Segment *)gf_list_enum(odm->OD->OCIDescriptors, &j)) ) { | |
| if (seg->tag != GF_ODF_SEGMENT_TAG) continue; | |
| if (seg==first_seg) continue; | |
| if (seg->startTime + seg->Duration <= first_seg->startTime) continue; | |
| /*this also includes last_seg insertion !!*/ | |
| if (last_seg && (seg->startTime + seg->Duration > last_seg->startTime + last_seg->Duration) ) continue; | |
| gf_odm_insert_segment(odm, seg, list); | |
| } | |
| } | |
| } | |
| /*DO NOT use get_packet here, we don't want to trigger reconfig*/ | |
| static Bool odm_update_buffer(GF_Scene *scene, GF_ObjectManager *odm, GF_FilterPid *pid, Bool check_full_buffer, Bool *signal_eob) | |
| { | |
| u32 timescale; | |
| u64 buffer_duration = gf_filter_pid_query_buffer_duration(pid, check_full_buffer)/1000; | |
| if (odm->ck && ! odm->ck->clock_init) { | |
| u64 time; | |
| if (!gf_filter_pid_get_first_packet_cts(pid, &time)) | |
| return GF_TRUE; | |
| timescale = gf_filter_pid_get_timescale(pid); | |
| if (time==GF_FILTER_NO_TS) { | |
| //this usually happens with BT/XMT playback | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPTIME, ("No timestamp on first packet, using 0\n")); | |
| time = 0; | |
| } | |
| if (odm->timestamp_offset<0) { | |
| if (time < (u64) -odm->timestamp_offset) { | |
| gf_filter_pid_drop_packet(pid); | |
| return GF_TRUE; | |
| } | |
| time-= -odm->timestamp_offset; | |
| } | |
| gf_clock_set_time(odm->ck, time, timescale); | |
| odm->media_current_time = 0; | |
| if (odm->parentscene) { | |
| odm->parentscene->root_od->media_start_time = 0; | |
| odm->parentscene->root_od->media_current_time = 0; | |
| } | |
| if (gf_filter_pid_first_packet_is_blocking_ref(pid)) | |
| odm->blocking_media = GF_TRUE; | |
| } | |
| //TODO abort buffering when errors are found on the input chain !! | |
| if (odm->blocking_media || (buffer_duration >= odm->buffer_playout_ms)) { | |
| odm->nb_buffering--; | |
| if (scene->nb_buffering) | |
| scene->nb_buffering--; | |
| if (!scene->nb_buffering) { | |
| *signal_eob = GF_TRUE; | |
| } | |
| if (odm->ck) | |
| gf_clock_buffer_off(odm->ck); | |
| } else if (gf_filter_pid_has_seen_eos(pid) ) { | |
| odm->nb_buffering--; | |
| if (scene->nb_buffering) | |
| scene->nb_buffering--; | |
| //if eos while buffering, consider the last rebuffer an error | |
| //fixeme, we need a way to probe for eos being "close" but not yet detected | |
| if (odm->nb_rebuffer) | |
| odm->nb_rebuffer --; | |
| if (!scene->nb_buffering) { | |
| *signal_eob = GF_TRUE; | |
| if (scene->nb_rebuffer) { | |
| scene->nb_rebuffer--; | |
| } | |
| } | |
| if (odm->ck) | |
| gf_clock_buffer_off(odm->ck); | |
| } | |
| return GF_FALSE; | |
| } | |
| /*DO NOT use get_packet here, we don't want to trigger reconfig*/ | |
| Bool gf_odm_check_buffering(GF_ObjectManager *odm, GF_FilterPid *pid) | |
| { | |
| u32 timescale; | |
| Bool signal_eob = GF_FALSE; | |
| GF_Scene *scene; | |
| GF_FilterClockType ck_type; | |
| u64 clock_reference; | |
| u64 pck_time = 0; | |
| Bool has_pck = GF_FALSE; | |
| Bool check_disc = GF_FALSE; | |
| Bool check_full_buffer = GF_TRUE; | |
| if (!odm) return GF_FALSE; | |
| if (!pid) { | |
| check_full_buffer = GF_FALSE; | |
| pid = odm->pid; | |
| } else { | |
| //when a pid is given, this is a non-AV codec (bifs/od, svg, text), try to map media timeline | |
| //otherwise we cannot since we don't want to use get_packet here | |
| gf_odm_check_clock_mediatime(odm); | |
| } | |
| scene = odm->subscene ? odm->subscene : odm->parentscene; | |
| if (!scene) return GF_FALSE; | |
| if (!gf_filter_pid_first_packet_is_empty(pid)) { | |
| if (gf_filter_pid_get_first_packet_cts(pid, &pck_time)) | |
| has_pck = GF_TRUE; | |
| } | |
| ck_type = gf_filter_pid_get_clock_info(pid, &clock_reference, ×cale); | |
| if (!odm->ck->clock_init) { | |
| if (ck_type) { | |
| gf_clock_set_time(odm->ck, clock_reference, timescale); | |
| if (odm->parentscene) odm->parentscene->root_od->media_start_time = 0; | |
| } | |
| else if (has_pck && odm->owns_clock && !odm->ck->clock_init) { | |
| gf_clock_set_time(odm->ck, pck_time, gf_filter_pid_get_timescale(pid) ); | |
| if (gf_filter_pid_first_packet_is_blocking_ref(pid)) { | |
| odm->blocking_media = GF_TRUE; | |
| if (odm->nb_buffering) | |
| odm_update_buffer(scene, odm, pid, check_full_buffer, &signal_eob); | |
| } | |
| } | |
| } | |
| if (odm->nb_buffering) { | |
| GF_ODMExtraPid *xpid; | |
| u32 i=0; | |
| Bool ret = odm_update_buffer(scene, odm, pid, check_full_buffer, &signal_eob); | |
| if (ret) return GF_TRUE; | |
| while (odm->nb_buffering && (xpid = gf_list_enum(odm->extra_pids, &i))) { | |
| ret = odm_update_buffer(scene, odm, xpid->pid, GF_FALSE, &signal_eob); | |
| if (ret) return GF_TRUE; | |
| } | |
| } | |
| if (scene->nb_buffering) { | |
| GF_ObjectManager *an_odm; | |
| u32 i=0; | |
| while ((an_odm = gf_list_enum(scene->resources,&i))) { | |
| if (odm==an_odm) continue; | |
| if (!an_odm->pid) continue; | |
| if (an_odm->nb_buffering) | |
| odm_update_buffer(scene, an_odm, an_odm->pid, GF_FALSE, &signal_eob); | |
| } | |
| } else if (!odm->blocking_media && odm->buffer_min_ms && odm->pid && odm->ck && odm->ck->clock_init && !gf_filter_pid_has_seen_eos(odm->pid) ) { | |
| u64 buffer_duration = gf_filter_pid_query_buffer_duration(odm->pid, GF_FALSE)/1000; | |
| if (buffer_duration < odm->buffer_min_ms) { | |
| gf_clock_buffer_on(odm->ck); | |
| odm->nb_buffering++; | |
| odm->nb_rebuffer++; | |
| if (!scene->nb_buffering) scene->nb_rebuffer++; | |
| scene->nb_buffering++; | |
| } | |
| } | |
| if (scene->nb_buffering || signal_eob) | |
| gf_scene_buffering_info(scene, GF_FALSE); | |
| if (odm->parentscene && odm->parentscene->root_od->addon) | |
| check_disc = GF_FALSE; | |
| else if ((odm->type== GF_STREAM_VISUAL) || (odm->type== GF_STREAM_AUDIO)) | |
| check_disc = GF_TRUE; | |
| //handle both PCR discontinuities or TS looping when no PCR disc is present/signaled | |
| if (has_pck && check_disc) { | |
| s32 diff=0; | |
| u32 clock_time = gf_clock_time(odm->ck); | |
| s32 diff_to = 0; | |
| u64 ckref_orig = clock_reference; | |
| if (ck_type) { | |
| clock_reference = gf_timestamp_to_clocktime(clock_reference, timescale); | |
| diff = (s32) clock_time + odm->buffer_playout_ms; | |
| diff -= (s32) clock_reference; | |
| GF_LOG(GF_LOG_INFO, GF_LOG_COMPTIME, ("Clock %d (ODM %d) reference found "LLU" ms clock time %d ms - diff %d - type %d\n", odm->ck->clock_id, odm->ID, clock_reference, clock_time, diff, ck_type)); | |
| //if explicit clock discontinuity, mark clock | |
| if (ck_type==GF_FILTER_CLOCK_PCR_DISC) | |
| odm->ck->ocr_discontinuity_time = 1 + gf_timestamp_rescale(ckref_orig, timescale, 1000); | |
| } | |
| timescale = gf_filter_pid_get_timescale(pid); | |
| if (pck_time != GF_FILTER_NO_TS) { | |
| pck_time = gf_timestamp_to_clocktime(pck_time, timescale); | |
| pck_time += 1; | |
| diff = (u32) ((u64) clock_time - pck_time); | |
| diff_to = (s32) (odm->ck->ocr_discontinuity_time ? 500 : 8000) * ABS(FIX2INT(odm->ck->speed) ); | |
| } | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("Clock %d (ODM %d) pck time %d - clock ref "LLU" clock time %d - diff %d vs %d\n", odm->ck->clock_id, odm->ID, pck_time, clock_reference, clock_time, diff, diff_to)); | |
| //we have a valid TS for the packet, and the CTS diff to the current clock is larget than 8 sec, check for discontinuities | |
| //it may happen that video is sent up to 4 or 5 seconds ahead of the PCR in some systems, 8 sec should be enough | |
| if (diff_to && (ABS(diff) > diff_to) ) { | |
| s64 diff_pck_old_clock, diff_pck_new_clock; | |
| //compute diff to old clock and new clock | |
| diff_pck_new_clock = pck_time-1 - (s64) clock_reference; | |
| if (diff_pck_new_clock<0) diff_pck_new_clock = -diff_pck_new_clock; | |
| diff_pck_old_clock = pck_time-1 - (s64) clock_time; | |
| if (diff_pck_old_clock<0) diff_pck_old_clock = -diff_pck_old_clock; | |
| //if the packet time is closer to the new clock than the old, switch to new clock | |
| if (diff_pck_old_clock > diff_pck_new_clock) { | |
| u32 i, count; | |
| GF_Scene *in_scene = odm->subscene ? odm->subscene : odm->parentscene; | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_COMPTIME, ("Clock %d (ODM %d) discontinuity detected "LLU" clock time %d - diff %d - type %d - pck time "LLU"\n", odm->ck->clock_id, odm->ID, clock_reference, clock_time, diff, ck_type, pck_time-1)); | |
| count = gf_list_count(in_scene->resources); | |
| for (i=0; i<count; i++) { | |
| GF_ObjectManager *an_odm = gf_list_get(in_scene->resources, i); | |
| if (an_odm->ck != odm->ck) continue; | |
| an_odm->prev_clock_at_discontinuity_plus_one = 1 + clock_time; | |
| } | |
| odm->ck->clock_init = GF_FALSE; | |
| if (odm->ck->ocr_discontinuity_time) | |
| gf_clock_set_time(odm->ck, odm->ck->ocr_discontinuity_time - 1, 1000); | |
| else | |
| gf_clock_set_time(odm->ck, ckref_orig, timescale); | |
| odm->ck->ocr_discontinuity_time = 0; | |
| //if we had media time mapping, get media time in last clock value and reassign mapping | |
| if (odm->ck->has_media_time_shift) { | |
| u64 mtime = gf_clock_to_media_time(odm->ck, clock_time); | |
| odm->ck->media_time_orig = mtime; | |
| odm->ck->media_ts_orig = gf_timestamp_rescale(ckref_orig, timescale, 1000); | |
| } | |
| } | |
| } | |
| } else if (ck_type) { | |
| clock_reference = gf_timestamp_rescale(clock_reference, timescale, 1000); | |
| if (ck_type==GF_FILTER_CLOCK_PCR_DISC) | |
| odm->ck->ocr_discontinuity_time = 1 + clock_reference; | |
| GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPTIME, ("Clock %d (ODM %d) received "LLU" type %d clock time %d no pending packets\n", odm->ck->clock_id, odm->ID, clock_reference, ck_type, gf_clock_time(odm->ck))); | |
| } | |
| //only send event when playing | |
| if (!odm->ck->nb_buffering) { | |
| gf_odm_service_media_event(odm, GF_EVENT_MEDIA_PROGRESS); | |
| } | |
| return odm->ck->nb_buffering ? GF_TRUE : GF_FALSE; | |
| } | |
| #ifndef GPAC_DISABLE_SVG | |
| void gf_odm_collect_buffer_info(GF_SceneNamespace *scene_ns, GF_ObjectManager *odm, GF_DOMMediaEvent *media_event, u32 *min_time, u32 *min_buffer) | |
| { | |
| GF_ODMExtraPid *xpid; | |
| u32 i, val; | |
| u64 buf_val; | |
| if (!odm->pid) return; | |
| if (odm->scene_ns != scene_ns) return; | |
| if (! odm->buffer_playout_ms) { | |
| media_event->bufferValid = GF_FALSE; | |
| return; | |
| } | |
| if (odm->nb_buffering) | |
| media_event->bufferValid = GF_TRUE; | |
| buf_val = gf_filter_pid_query_buffer_duration(odm->pid, GF_FALSE)/1000; | |
| if (buf_val > odm->buffer_max_ms) buf_val = odm->buffer_max_ms; | |
| val = (u32) ((buf_val * 100) / odm->buffer_playout_ms); | |
| if (*min_buffer > val) (*min_buffer) = val; | |
| if (*min_time > (u32) buf_val) | |
| *min_time = (u32) buf_val; | |
| i=0; | |
| while ((xpid = gf_list_enum(odm->extra_pids, &i))) { | |
| buf_val = gf_filter_pid_query_buffer_duration(odm->pid, GF_FALSE)/1000; | |
| if (buf_val > odm->buffer_max_ms) buf_val = odm->buffer_max_ms; | |
| val = (u32) ((buf_val * 100) / odm->buffer_playout_ms); | |
| if (*min_buffer > val) (*min_buffer) = val; | |
| if (*min_time > (u32) buf_val) | |
| *min_time = (u32) buf_val; | |
| } | |
| } | |
| #endif | |
| void gf_odm_service_media_event_with_download(GF_ObjectManager *odm, GF_EventType event_type, u64 loaded_size, u64 total_size, u32 bytes_per_sec, u32 buffer_level_plus_one, u32 min_buffer_time) | |
| { | |
| #ifndef GPAC_DISABLE_SVG | |
| u32 i, count, min_buffer, min_time; | |
| GF_DOM_Event evt; | |
| GF_Scene *scene; | |
| if (!odm || !odm->scene_ns) return; | |
| if (odm->mo) { | |
| count = gf_mo_event_target_count(odm->mo); | |
| //for dynamic scenes, check if we have listeners on the root object of the scene containing this media | |
| if (odm->parentscene | |
| && odm->parentscene->is_dynamic_scene | |
| && odm->parentscene->root_od->mo | |
| && (odm->parentscene->root_od->scene_ns==odm->scene_ns) | |
| ) { | |
| odm = odm->parentscene->root_od; | |
| count = gf_mo_event_target_count(odm->mo); | |
| } | |
| if (!count) return; | |
| } else { | |
| count = 0; | |
| } | |
| memset(&evt, 0, sizeof(GF_DOM_Event)); | |
| evt.media_event.bufferValid = GF_FALSE; | |
| evt.media_event.session_name = odm->scene_ns->url; | |
| scene = odm->subscene ? odm->subscene : odm->parentscene; | |
| if (!scene) return; | |
| if (!buffer_level_plus_one) { | |
| GF_ObjectManager *an_od; | |
| min_time = min_buffer = (u32) -1; | |
| /*get buffering on root OD*/ | |
| if (!scene->is_dynamic_scene) | |
| gf_odm_collect_buffer_info(odm->scene_ns, scene->root_od, &evt.media_event, &min_time, &min_buffer); | |
| /*get buffering on all ODs*/ | |
| i=0; | |
| while ((an_od = (GF_ObjectManager*)gf_list_enum(scene->resources, &i))) { | |
| if (odm->scene_ns == an_od->scene_ns) | |
| gf_odm_collect_buffer_info(odm->scene_ns, an_od, &evt.media_event, &min_time, &min_buffer); | |
| } | |
| } else { | |
| min_buffer = buffer_level_plus_one - 1; | |
| min_time = min_buffer_time; | |
| evt.media_event.bufferValid = GF_TRUE; | |
| } | |
| if (min_buffer != (u32) -1) { | |
| evt.media_event.level = min_buffer; | |
| } | |
| if (min_time != (u32) -1) | |
| evt.media_event.remaining_time = INT2FIX(min_time) / 60; | |
| evt.media_event.status = 0; | |
| evt.media_event.loaded_size = loaded_size; | |
| evt.media_event.total_size = total_size; | |
| evt.type = event_type; | |
| evt.bubbles = 0; /*the spec says yes but we force it to NO*/ | |
| //these events may be triggered from any input or decoding threads. Sync processing cannot be | |
| //achieved in most cases, because we may run into deadlocks, especially if the event | |
| //was triggered by a service opened by JS | |
| for (i=0; i<count; i++) { | |
| GF_DOMEventTarget *target = (GF_DOMEventTarget *)gf_list_get(odm->mo->evt_targets, i); | |
| if (target) | |
| gf_sc_queue_dom_event_on_target(scene->compositor, &evt, target, scene->graph); | |
| } | |
| if (!count) { | |
| GF_Node *root = gf_sg_get_root_node(scene->graph); | |
| if (root) gf_sc_queue_dom_event(scene->compositor, root, &evt); | |
| } | |
| #endif | |
| } | |
| void gf_odm_service_media_event(GF_ObjectManager *odm, GF_EventType event_type) | |
| { | |
| gf_odm_service_media_event_with_download(odm, event_type, 0, 0, 0, 0, 0); | |
| } | |
| Bool gf_odm_stop_or_destroy(GF_ObjectManager *odm) | |
| { | |
| Bool destroy = GF_FALSE; | |
| if (odm->mo ) { | |
| if (odm->addon) odm->flags |= GF_ODM_REGENERATE_SCENE; | |
| else if (odm->mo->OD_ID==GF_MEDIA_EXTERNAL_ID) destroy = GF_TRUE; | |
| else if (odm->ID==GF_MEDIA_EXTERNAL_ID) destroy = GF_TRUE; | |
| } | |
| if (destroy) { | |
| gf_odm_disconnect(odm, 2); | |
| return GF_TRUE; | |
| } | |
| gf_odm_stop(odm, 0); | |
| return GF_FALSE; | |
| } | |
| static void get_codec_stats(GF_FilterPid *pid, GF_MediaInfo *info, Bool scalable_addon) | |
| { | |
| GF_FilterPidStatistics stats; | |
| gf_filter_pid_get_statistics(pid, &stats, scalable_addon ? GF_STATS_LOCAL : GF_STATS_LOCAL_INPUTS); | |
| info->avg_bitrate = stats.average_bitrate; | |
| info->max_bitrate = stats.max_bitrate; | |
| info->nb_dec_frames = stats.nb_processed; | |
| info->max_dec_time = stats.max_process_time; | |
| info->total_dec_time = stats.total_process_time; | |
| info->first_frame_time = (u32) stats.first_process_time/1000; | |
| info->last_frame_time = (u32) stats.last_process_time/1000; | |
| info->au_duration = (u32) stats.min_frame_dur/1000; | |
| info->nb_iraps = stats.nb_saps; | |
| info->irap_max_dec_time = stats.max_sap_process_time; | |
| info->irap_total_dec_time = stats.total_sap_process_time; | |
| info->avg_process_bitrate = stats.average_process_rate; | |
| info->max_process_bitrate = stats.max_process_rate; | |
| info->db_unit_count = stats.nb_buffer_units; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_odm_get_object_info(GF_ObjectManager *odm, GF_MediaInfo *info) | |
| { | |
| GF_FilterPid *gf_filter_pid_first_pid_for_source(GF_FilterPid *pid, GF_Filter *source); | |
| const GF_PropertyValue *prop; | |
| GF_ObjectManager *an_odm; | |
| GF_FilterPid *pid; | |
| GF_ObjectManager *scalable_addon_par = NULL; | |
| if (!odm || !info) return GF_BAD_PARAM; | |
| memset(info, 0, sizeof(GF_MediaInfo)); | |
| info->ODID = odm->ID; | |
| info->ServiceID = odm->ServiceID; | |
| info->pid_id = odm->pid_id; | |
| info->ocr_id = odm->ck ? odm->ck->clock_id : 0; | |
| info->od_type = odm->type; | |
| info->duration = (Double) (s64)odm->duration; | |
| info->duration /= 1000; | |
| pid = odm->pid; | |
| an_odm = odm; | |
| while (an_odm->lower_layer_odm) { | |
| an_odm = an_odm->lower_layer_odm; | |
| pid = an_odm->pid; | |
| } | |
| #ifdef GPAC_ENABLE_COVERAGE | |
| if (gf_sys_is_cov_mode() && pid) { | |
| GF_FilterPidStatistics stats; | |
| gf_filter_pid_get_statistics(odm->pid, &stats, GF_STATS_SINK); | |
| gf_filter_pid_set_rt_stats(pid, 0, 0, 0); | |
| gf_filter_pid_share_origin(pid, pid); | |
| if (odm->parentscene) | |
| gf_filter_pid_first_pid_for_source(pid, odm->parentscene->root_od->scene_ns->source_filter); | |
| } | |
| #endif | |
| if (odm->subscene && !odm->pid && odm->addon && !gf_list_count(odm->subscene->resources)) { | |
| u32 i; | |
| for (i=0; i<gf_list_count(odm->parentscene->resources); i++) { | |
| GF_ObjectManager *par_odm = gf_list_get(odm->parentscene->resources, i); | |
| if (!par_odm->pid) continue; | |
| pid = gf_filter_pid_first_pid_for_source(par_odm->pid, odm->addon->root_od->scene_ns->source_filter); | |
| if (!pid) continue; | |
| if (!odm->ID) { | |
| info->ServiceID = odm->ServiceID = par_odm->ServiceID; | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_ID); | |
| if (prop) info->pid_id = odm->pid_id = odm->ID = prop->value.uint; | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE); | |
| if (prop) info->od_type = odm->type = prop->value.uint; | |
| odm->buffer_max_ms = par_odm->buffer_max_ms; | |
| odm->buffer_min_ms = par_odm->buffer_min_ms; | |
| } | |
| scalable_addon_par = par_odm; | |
| break; | |
| } | |
| } | |
| if (pid) { | |
| /*since we don't remove ODs that failed setup, check for clock*/ | |
| if (odm->ck) { | |
| //info->current_time = (Double) odm->media_current_time; | |
| info->ntp_diff = odm->last_drawn_frame_ntp_diff; | |
| info->current_time = (Double) gf_clock_media_time(odm->ck); | |
| } | |
| info->current_time /= 1000; | |
| info->nb_dropped = odm->nb_dropped; | |
| } else if (odm->subscene) { | |
| if (odm->subscene->root_od && odm->subscene->root_od->ck) { | |
| info->current_time = (Double) gf_clock_media_time(odm->subscene->root_od->ck); | |
| info->current_time /= 1000; | |
| } | |
| info->duration = (Double) (s64)odm->subscene->duration; | |
| info->duration /= 1000; | |
| info->nb_dropped = odm->subscene->root_od ? odm->subscene->root_od->nb_dropped : 0; | |
| info->generated_scene = odm->subscene->is_dynamic_scene; | |
| } | |
| if (info->duration && info->current_time>info->duration) | |
| info->current_time = info->duration; | |
| info->buffer = -2; | |
| info->db_unit_count = 0; | |
| if (odm->state) { | |
| GF_Clock *ck; | |
| ck = gf_odm_get_media_clock(odm); | |
| if (scalable_addon_par) | |
| ck = scalable_addon_par->ck; | |
| /*no clock means setup failed*/ | |
| if (!ck) { | |
| info->status = 4; | |
| } else { | |
| info->status = gf_clock_is_started(ck) ? 1 : 2; | |
| info->clock_drift = ck->audio_delay; | |
| info->buffer = -1; | |
| info->min_buffer = -1; | |
| info->max_buffer = 0; | |
| if (pid) | |
| info->buffer = (u32) gf_filter_pid_query_buffer_duration(pid, GF_FALSE) / 1000; | |
| info->max_buffer = odm->buffer_max_ms; | |
| info->min_buffer = odm->buffer_min_ms; | |
| } | |
| if (odm->pid) { | |
| prop = gf_filter_pid_get_property(odm->pid, GF_PROP_PID_ORIG_CRYPT_SCHEME); | |
| if (prop) | |
| info->protection = prop->value.uint; | |
| } | |
| } | |
| if (odm->scene_ns) { | |
| info->service_handler = odm->scene_ns->source_filter ? gf_filter_get_name(odm->scene_ns->source_filter) : "unloaded"; | |
| info->service_url = odm->scene_ns->url; | |
| if (odm->scene_ns->owner == odm) info->owns_service = 1; | |
| } else if ((odm->subscene && odm->subscene->graph_attached) || (odm->ID)) { | |
| info->service_url = "No associated network Service"; | |
| } else { | |
| info->service_url = "Service not found or error"; | |
| } | |
| if (pid) { | |
| info->codec_name = gf_filter_pid_get_filter_name(scalable_addon_par ? scalable_addon_par->pid : pid); | |
| info->od_type = odm->type; | |
| gf_filter_pid_get_buffer_occupancy(pid, &info->cb_max_count, &info->cb_unit_count, NULL, NULL); | |
| get_codec_stats(pid, info, scalable_addon_par ? GF_TRUE : GF_FALSE); | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_LANGUAGE); | |
| if (prop) info->lang_code = prop->value.string; | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_VIEWS); | |
| if (prop && (prop->value.uint>1)) info->nb_views = prop->value.uint; | |
| } | |
| if (!scalable_addon_par) { | |
| if (odm->subscene) { | |
| gf_sg_get_scene_size_info(odm->subscene->graph, &info->width, &info->height); | |
| } else if (odm->mo) { | |
| switch (info->od_type) { | |
| case GF_STREAM_VISUAL: | |
| gf_mo_get_visual_info(odm->mo, &info->width, &info->height, NULL, &info->par, &info->pixelFormat, NULL); | |
| break; | |
| case GF_STREAM_AUDIO: | |
| gf_mo_get_audio_info(odm->mo, &info->sample_rate, &info->afmt, &info->num_channels, NULL, NULL); | |
| info->clock_drift = 0; | |
| break; | |
| case GF_STREAM_TEXT: | |
| gf_mo_get_visual_info(odm->mo, &info->width, &info->height, NULL, NULL, NULL, NULL); | |
| break; | |
| } | |
| } | |
| } else { | |
| switch (info->od_type) { | |
| case GF_STREAM_VISUAL: | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH); | |
| if (prop) info->width = prop->value.uint; | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT); | |
| if (prop) info->height = prop->value.uint; | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_SAR); | |
| if (prop && (prop->value.frac.num>0)) info->par = (prop->value.frac.num) << 16 | (prop->value.frac.den); | |
| break; | |
| case GF_STREAM_AUDIO: | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_SAMPLE_RATE); | |
| if (prop) info->sample_rate = prop->value.uint; | |
| prop = gf_filter_pid_get_property(pid, GF_PROP_PID_NUM_CHANNELS); | |
| if (prop) info->num_channels = prop->value.uint; | |
| info->clock_drift = 0; | |
| break; | |
| } | |
| } | |
| if (odm->mo && odm->mo->URLs.count) | |
| info->media_url = odm->mo->URLs.vals[0].url; | |
| return GF_OK; | |
| } | |
| //adjust media time info in case the timestamp found at init is not media time 0 | |
| void gf_odm_check_clock_mediatime(GF_ObjectManager *odm) | |
| { | |
| u64 timestamp; | |
| u32 timescale; | |
| u32 i; | |
| Double media_time; | |
| GF_Scene *scene; | |
| const GF_PropertyValue *p; | |
| if (!odm->owns_clock || odm->ck->has_media_time_shift) return; | |
| timescale = gf_filter_pid_get_timescale(odm->pid); | |
| if (!timescale) return; | |
| GF_FilterPacket *pck = gf_filter_pid_get_packet(odm->pid); | |
| if (!pck) return; | |
| timestamp = gf_filter_pck_get_cts(pck); | |
| if (timestamp == GF_FILTER_NO_TS) return; | |
| p = gf_filter_pck_get_property(pck, GF_PROP_PCK_MEDIA_TIME); | |
| if (!p) return; | |
| media_time = p->value.number; | |
| odm->ck->media_ts_orig = gf_timestamp_rescale(timestamp, timescale, 1000); | |
| odm->ck->media_time_orig = (u64) (media_time * 1000); | |
| odm->ck->has_media_time_shift = GF_TRUE; | |
| scene = odm->subscene ? odm->subscene : odm->parentscene; | |
| if (!scene) return; | |
| if (scene->root_od) | |
| scene->root_od->media_current_time = 0; | |
| for (i=0; i<gf_list_count(scene->resources); i++) { | |
| GF_ObjectManager *anodm = gf_list_get(scene->resources, i); | |
| anodm->media_current_time = 0; | |
| } | |
| } | |
| #endif //!defined(GPAC_DISABLE_COMPOSITOR) |