| /* | |
| * 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 "nodes_stacks.h" | |
| #if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR) | |
| static void audioclip_activate(AudioClipStack *st, M_AudioClip *ac) | |
| { | |
| if (gf_sc_audio_open(&st->input, &ac->url, 0, -1, GF_FALSE) != GF_OK) { | |
| st->failure = GF_TRUE; | |
| return; | |
| } | |
| ac->isActive = 1; | |
| gf_node_event_out((GF_Node *)ac, 7/*"isActive"*/); | |
| gf_mo_set_speed(st->input.stream, st->input.speed); | |
| /*traverse all graph to get parent audio group*/ | |
| gf_sc_invalidate(st->input.compositor, NULL); | |
| } | |
| static void audioclip_deactivate(AudioClipStack *st, M_AudioClip *ac) | |
| { | |
| gf_sc_audio_stop(&st->input); | |
| gf_sc_audio_unregister(&st->input); | |
| ac->isActive = 0; | |
| gf_node_event_out((GF_Node *)ac, 7/*"isActive"*/); | |
| st->time_handle.needs_unregister = GF_TRUE; | |
| } | |
| static void audioclip_traverse(GF_Node *node, void *rs, Bool is_destroy) | |
| { | |
| GF_TraverseState *tr_state = (GF_TraverseState *)rs; | |
| M_AudioClip *ac = (M_AudioClip *)node; | |
| AudioClipStack *st = (AudioClipStack *)gf_node_get_private(node); | |
| if (is_destroy) { | |
| gf_sc_audio_predestroy(&st->input); | |
| if (st->time_handle.is_registered) { | |
| gf_sc_unregister_time_node(st->input.compositor, &st->time_handle); | |
| } | |
| gf_free(st); | |
| return; | |
| } | |
| if (st->failure) return; | |
| /*check end of stream*/ | |
| if (st->input.stream && st->input.stream_finished) { | |
| if (gf_mo_get_loop(st->input.stream, ac->loop)) { | |
| gf_sc_audio_restart(&st->input); | |
| } else if (ac->isActive && (ac->startTime<ac->stopTime) && gf_mo_should_deactivate(st->input.stream)) { | |
| /*deactivate*/ | |
| audioclip_deactivate(st, ac); | |
| } | |
| } | |
| if (ac->isActive) { | |
| gf_sc_audio_register(&st->input, (GF_TraverseState*)rs); | |
| } | |
| if (st->set_duration && st->input.stream && st->input.stream->odm) { | |
| ac->duration_changed = gf_mo_get_duration(st->input.stream); | |
| gf_node_event_out(node, 6/*"duration_changed"*/); | |
| st->set_duration = GF_FALSE; | |
| } | |
| /*store mute flag*/ | |
| st->input.is_muted = tr_state->switched_off; | |
| } | |
| static void audioclip_update_time(GF_TimeNode *tn) | |
| { | |
| Double time; | |
| M_AudioClip *ac = (M_AudioClip *)tn->udta; | |
| AudioClipStack *st = (AudioClipStack *)gf_node_get_private((GF_Node*)tn->udta); | |
| if (st->failure) { | |
| st->time_handle.needs_unregister = GF_TRUE; | |
| return; | |
| } | |
| if (! ac->isActive) { | |
| st->start_time = ac->startTime; | |
| st->input.speed = ac->pitch; | |
| } | |
| time = gf_node_get_scene_time((GF_Node*)tn->udta); | |
| if ((time<st->start_time) || (st->start_time<0)) return; | |
| if (ac->isActive) { | |
| if ( (ac->stopTime > st->start_time) && (time>=ac->stopTime)) { | |
| audioclip_deactivate(st, ac); | |
| return; | |
| } | |
| } | |
| if (!ac->isActive) audioclip_activate(st, ac); | |
| } | |
| void compositor_init_audioclip(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| AudioClipStack *st; | |
| GF_SAFEALLOC(st, AudioClipStack); | |
| if (!st) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate style group stack\n")); | |
| return; | |
| } | |
| gf_sc_audio_setup(&st->input, compositor, node); | |
| st->time_handle.UpdateTimeNode = audioclip_update_time; | |
| st->time_handle.udta = node; | |
| st->set_duration = GF_TRUE; | |
| gf_node_set_private(node, st); | |
| gf_node_set_callback_function(node, audioclip_traverse); | |
| gf_sc_register_time_node(compositor, &st->time_handle); | |
| } | |
| void compositor_audioclip_modified(GF_Node *node) | |
| { | |
| M_AudioClip *ac = (M_AudioClip *)node; | |
| AudioClipStack *st = (AudioClipStack *) gf_node_get_private(node); | |
| if (!st) return; | |
| st->failure = GF_FALSE; | |
| /*MPEG4 spec is not clear about that , so this is not forbidden*/ | |
| if (st->input.is_open) { | |
| if (gf_sc_audio_check_url(&st->input, &ac->url)) { | |
| gf_sc_audio_stop(&st->input); | |
| gf_sc_audio_open(&st->input, &ac->url, 0, -1, GF_FALSE); | |
| /*force unregister to resetup audio cfg*/ | |
| gf_sc_audio_unregister(&st->input); | |
| gf_sc_invalidate(st->input.compositor, NULL); | |
| } | |
| } | |
| //update state if we're active | |
| if (ac->isActive) { | |
| audioclip_update_time(&st->time_handle); | |
| /*we're no longer active fon't check for reactivation*/ | |
| if (!ac->isActive) return; | |
| } | |
| /*make sure we are still registered*/ | |
| if (!st->time_handle.is_registered && !st->time_handle.needs_unregister) | |
| gf_sc_register_time_node(st->input.compositor, &st->time_handle); | |
| else | |
| st->time_handle.needs_unregister = GF_FALSE; | |
| } | |
| static void audiosource_activate(AudioSourceStack *st, M_AudioSource *as) | |
| { | |
| if (gf_sc_audio_open(&st->input, &as->url, 0, -1, GF_FALSE) != GF_OK) | |
| return; | |
| st->is_active = GF_TRUE; | |
| gf_mo_set_speed(st->input.stream, st->input.speed); | |
| /*traverse all graph to get parent audio group*/ | |
| gf_sc_invalidate(st->input.compositor, NULL); | |
| } | |
| static void audiosource_deactivate(AudioSourceStack *st, M_AudioSource *as) | |
| { | |
| gf_sc_audio_stop(&st->input); | |
| st->is_active = GF_FALSE; | |
| st->time_handle.needs_unregister = GF_TRUE; | |
| } | |
| static void audiosource_traverse(GF_Node *node, void *rs, Bool is_destroy) | |
| { | |
| GF_TraverseState*tr_state = (GF_TraverseState*)rs; | |
| M_AudioSource *as = (M_AudioSource *)node; | |
| AudioSourceStack *st = (AudioSourceStack *)gf_node_get_private(node); | |
| if (is_destroy) { | |
| gf_sc_audio_predestroy(&st->input); | |
| if (st->time_handle.is_registered) { | |
| gf_sc_unregister_time_node(st->input.compositor, &st->time_handle); | |
| } | |
| gf_free(st); | |
| return; | |
| } | |
| /*check end of stream*/ | |
| if (st->input.stream && st->input.stream_finished) { | |
| if (gf_mo_get_loop(st->input.stream, GF_FALSE)) { | |
| gf_sc_audio_restart(&st->input); | |
| } else if (st->is_active && gf_mo_should_deactivate(st->input.stream)) { | |
| /*deactivate*/ | |
| audiosource_deactivate(st, as); | |
| } | |
| } | |
| if (st->is_active) { | |
| gf_sc_audio_register(&st->input, (GF_TraverseState*)rs); | |
| } | |
| /*store mute flag*/ | |
| st->input.is_muted = tr_state->switched_off; | |
| } | |
| static void audiosource_update_time(GF_TimeNode *tn) | |
| { | |
| Double time; | |
| M_AudioSource *as = (M_AudioSource *)tn->udta; | |
| AudioSourceStack *st = (AudioSourceStack *)gf_node_get_private((GF_Node*)tn->udta); | |
| if (! st->is_active) { | |
| st->start_time = as->startTime; | |
| st->input.speed = as->speed; | |
| } | |
| time = gf_node_get_scene_time((GF_Node*)tn->udta); | |
| if ((time<st->start_time) || (st->start_time<0)) return; | |
| if (st->input.input_ifce.GetSpeed(st->input.input_ifce.callback) && st->is_active) { | |
| if ( (as->stopTime > st->start_time) && (time>=as->stopTime)) { | |
| audiosource_deactivate(st, as); | |
| return; | |
| } | |
| } | |
| if (!st->is_active) audiosource_activate(st, as); | |
| } | |
| void compositor_init_audiosource(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| AudioSourceStack *st; | |
| GF_SAFEALLOC(st, AudioSourceStack); | |
| if (!st) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate style group stack\n")); | |
| return; | |
| } | |
| gf_sc_audio_setup(&st->input, compositor, node); | |
| st->time_handle.UpdateTimeNode = audiosource_update_time; | |
| st->time_handle.udta = node; | |
| gf_node_set_private(node, st); | |
| gf_node_set_callback_function(node, audiosource_traverse); | |
| gf_sc_register_time_node(compositor, &st->time_handle); | |
| } | |
| void compositor_audiosource_modified(GF_Node *node) | |
| { | |
| M_AudioSource *as = (M_AudioSource *)node; | |
| AudioSourceStack *st = (AudioSourceStack *) gf_node_get_private(node); | |
| if (!st) return; | |
| /*MPEG4 spec is not clear about that , so this is not forbidden*/ | |
| if (gf_sc_audio_check_url(&st->input, &as->url)) { | |
| if (st->input.is_open) gf_sc_audio_stop(&st->input); | |
| /*force unregister to resetup audio cfg*/ | |
| gf_sc_audio_unregister(&st->input); | |
| gf_sc_invalidate(st->input.compositor, NULL); | |
| if (st->is_active) gf_sc_audio_open(&st->input, &as->url, 0, -1, GF_FALSE); | |
| } | |
| //update state if we're active | |
| if (st->is_active) { | |
| audiosource_update_time(&st->time_handle); | |
| if (!st->is_active) return; | |
| } | |
| /*make sure we are still registered*/ | |
| if (!st->time_handle.is_registered && !st->time_handle.needs_unregister) | |
| gf_sc_register_time_node(st->input.compositor, &st->time_handle); | |
| else | |
| st->time_handle.needs_unregister = GF_FALSE; | |
| } | |
| typedef struct | |
| { | |
| AUDIO_GROUP_NODE | |
| GF_TimeNode time_handle; | |
| Double start_time; | |
| Bool set_duration; | |
| /*AudioBuffer mixes its children*/ | |
| GF_AudioMixer *am; | |
| Bool is_init, is_muted; | |
| /*buffer audio data*/ | |
| char *buffer; | |
| u32 buffer_size; | |
| Bool done; | |
| /*read/write position in buffer and associated read time (CTS)*/ | |
| u32 read_pos, write_pos, cur_cts; | |
| /*list of audio children after a traverse*/ | |
| GF_List *new_inputs; | |
| } AudioBufferStack; | |
| /*we have no choice but always browsing the children, since a src can be replaced by a new one | |
| without the parent being modified. We just collect the src and check against the current mixer inputs | |
| to reset the mixer or not - the spec is not clear about that btw, shall rebuffering happen if a source is modified or not ...*/ | |
| static void audiobuffer_traverse(GF_Node *node, void *rs, Bool is_destroy) | |
| { | |
| u32 j; | |
| Bool update_mixer; | |
| GF_ChildNodeItem *l; | |
| GF_AudioGroup *parent; | |
| AudioBufferStack *st = (AudioBufferStack *)gf_node_get_private(node); | |
| M_AudioBuffer *ab = (M_AudioBuffer *)node; | |
| GF_TraverseState*tr_state = (GF_TraverseState*) rs; | |
| if (is_destroy) { | |
| gf_sc_audio_unregister(&st->output); | |
| if (st->time_handle.is_registered) | |
| gf_sc_unregister_time_node(st->output.compositor, &st->time_handle); | |
| gf_mixer_del(st->am); | |
| if (st->buffer) gf_free(st->buffer); | |
| gf_list_del(st->new_inputs); | |
| gf_free(st); | |
| return; | |
| } | |
| parent = tr_state->audio_parent; | |
| tr_state->audio_parent = (GF_AudioGroup *) st; | |
| l = ab->children; | |
| while (l) { | |
| gf_node_traverse(l->node, tr_state); | |
| l = l->next; | |
| } | |
| gf_mixer_lock(st->am, GF_TRUE); | |
| /*if no new inputs don't change mixer config*/ | |
| update_mixer = gf_list_count(st->new_inputs) ? GF_TRUE : GF_FALSE; | |
| if (gf_mixer_get_src_count(st->am) == gf_list_count(st->new_inputs)) { | |
| u32 count = gf_list_count(st->new_inputs); | |
| update_mixer = GF_FALSE; | |
| for (j=0; j<count; j++) { | |
| GF_AudioInput *cur = (GF_AudioInput *)gf_list_get(st->new_inputs, j); | |
| if (!gf_mixer_is_src_present(st->am, &cur->input_ifce)) { | |
| update_mixer = GF_TRUE; | |
| break; | |
| } | |
| } | |
| } | |
| if (update_mixer) { | |
| gf_mixer_remove_all(st->am); | |
| gf_mixer_force_channel_out(st->am, ab->numChan); | |
| } | |
| while (gf_list_count(st->new_inputs)) { | |
| GF_AudioInput *src = (GF_AudioInput *)gf_list_get(st->new_inputs, 0); | |
| gf_list_rem(st->new_inputs, 0); | |
| if (update_mixer) gf_mixer_add_input(st->am, &src->input_ifce); | |
| } | |
| gf_mixer_lock(st->am, GF_FALSE); | |
| tr_state->audio_parent = parent; | |
| /*Note the audio buffer is ALWAYS registered until destroyed since buffer filling shall happen even when inactive*/ | |
| if (!st->output.register_with_parent || !st->output.register_with_renderer) | |
| gf_sc_audio_register(&st->output, tr_state); | |
| /*store mute flag*/ | |
| st->is_muted = tr_state->switched_off; | |
| } | |
| static void audiobuffer_activate(AudioBufferStack *st, M_AudioBuffer *ab) | |
| { | |
| ab->isActive = 1; | |
| gf_node_event_out((GF_Node *)ab, 17/*"isActive"*/); | |
| /*rerender all graph to get parent audio group*/ | |
| gf_sc_invalidate(st->output.compositor, NULL); | |
| st->done = GF_FALSE; | |
| st->read_pos = 0; | |
| } | |
| static void audiobuffer_deactivate(AudioBufferStack *st, M_AudioBuffer *ab) | |
| { | |
| ab->isActive = 0; | |
| gf_node_event_out((GF_Node *)ab, 17/*"isActive"*/); | |
| st->time_handle.needs_unregister = GF_TRUE; | |
| } | |
| static void audiobuffer_update_time(GF_TimeNode *tn) | |
| { | |
| Double time; | |
| M_AudioBuffer *ab = (M_AudioBuffer *)tn->udta; | |
| AudioBufferStack *st = (AudioBufferStack *)gf_node_get_private((GF_Node*)tn->udta); | |
| if (! ab->isActive) { | |
| st->start_time = ab->startTime; | |
| } | |
| time = gf_node_get_scene_time((GF_Node*)tn->udta); | |
| if ((time<st->start_time) || (st->start_time<0)) return; | |
| if (ab->isActive) { | |
| if ( (ab->stopTime > st->start_time) && (time>=ab->stopTime)) { | |
| audiobuffer_deactivate(st, ab); | |
| return; | |
| } | |
| /*THIS IS NOT NORMATIVE*/ | |
| if ( !ab->loop && st->done) { | |
| audiobuffer_deactivate(st, ab); | |
| return; | |
| } | |
| } | |
| if (!ab->isActive) audiobuffer_activate(st, ab); | |
| } | |
| static u8 *audiobuffer_fetch_frame(void *callback, u32 *size, u32 *planar_stride, u32 audio_delay_ms) | |
| { | |
| u32 blockAlign; | |
| AudioBufferStack *st = (AudioBufferStack *) gf_node_get_private( ((GF_AudioInput *) callback)->owner); | |
| M_AudioBuffer *ab = (M_AudioBuffer*)st->output.owner; | |
| if (!st->is_init) return NULL; | |
| if (!st->buffer) { | |
| st->done = GF_FALSE; | |
| st->buffer_size = (u32) ceil(FIX2FLT(ab->length) * gf_audio_fmt_bit_depth(st->output.input_ifce.afmt) * st->output.input_ifce.samplerate*st->output.input_ifce.chan/8); | |
| blockAlign = gf_mixer_get_block_align(st->am); | |
| /*BLOCK ALIGN*/ | |
| while (st->buffer_size%blockAlign) st->buffer_size++; | |
| st->buffer = (char*)gf_malloc(sizeof(char) * st->buffer_size); | |
| memset(st->buffer, 0, sizeof(char) * st->buffer_size); | |
| st->read_pos = st->write_pos = 0; | |
| } | |
| if (st->done) return NULL; | |
| /*even if not active, fill the buffer*/ | |
| if (st->write_pos < st->buffer_size) { | |
| u32 written; | |
| while (1) { | |
| /*just try to completely fill it*/ | |
| written = gf_mixer_get_output(st->am, st->buffer + st->write_pos, st->buffer_size - st->write_pos, 0); | |
| if (!written) break; | |
| st->write_pos += written; | |
| gf_fatal_assert(st->write_pos<=st->buffer_size); | |
| } | |
| } | |
| /*not playing*/ | |
| if (! ab->isActive) return NULL; | |
| *size = st->write_pos - st->read_pos; | |
| return st->buffer + st->read_pos; | |
| } | |
| static void audiobuffer_release_frame(void *callback, u32 nb_bytes) | |
| { | |
| AudioBufferStack *st = (AudioBufferStack *) gf_node_get_private( ((GF_AudioInput *) callback)->owner); | |
| st->read_pos += nb_bytes; | |
| gf_fatal_assert(st->read_pos<=st->write_pos); | |
| if (st->read_pos==st->write_pos) { | |
| if (st->write_pos<st->buffer_size) { | |
| /*reading faster than buffering - let's still attempt to fill the buffer*/ | |
| #if 0 | |
| st->write_pos = st->buffer_size; | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[AudioBuffer] done playing before buffer filling done\n")); | |
| #endif | |
| } else if ( ((M_AudioBuffer*)st->output.owner)->loop) { | |
| st->read_pos = 0; | |
| } else { | |
| st->done = GF_TRUE; | |
| } | |
| } | |
| } | |
| static Fixed audiobuffer_get_speed(void *callback) | |
| { | |
| M_AudioBuffer *ab = (M_AudioBuffer *) ((GF_AudioInput *) callback)->owner; | |
| return ab->pitch; | |
| } | |
| static Bool audiobuffer_get_volume(void *callback, Fixed *vol) | |
| { | |
| GF_AudioInput *ai = (GF_AudioInput *) callback; | |
| if (ai->snd->GetChannelVolume) { | |
| return ai->snd->GetChannelVolume(ai->snd->owner, vol); | |
| } else { | |
| // u32 i; | |
| // for (i=0; i<GF_AUDIO_MIXER_MAX_CHANNELS; i++) vol[i] = FIX_ONE; | |
| return GF_FALSE; | |
| } | |
| } | |
| static Bool audiobuffer_is_muted(void *callback) | |
| { | |
| AudioBufferStack *st = (AudioBufferStack *) gf_node_get_private( ((GF_AudioInput *) callback)->owner); | |
| return st->is_muted; | |
| } | |
| static Bool audiobuffer_get_config(GF_AudioInterface *aifc, Bool for_reconf) | |
| { | |
| AudioBufferStack *st = (AudioBufferStack *) gf_node_get_private( ((GF_AudioInput *) aifc->callback)->owner); | |
| if (gf_mixer_must_reconfig(st->am)) { | |
| Bool force_config = GF_TRUE; | |
| if (gf_mixer_reconfig(st->am)) { | |
| if (st->buffer) gf_free(st->buffer); | |
| st->buffer = NULL; | |
| st->buffer_size = 0; | |
| } | |
| gf_mixer_get_config(st->am, &aifc->samplerate, &aifc->chan, &aifc->afmt, &aifc->ch_layout); | |
| //we only work with packed formats | |
| switch (aifc->afmt) { | |
| case GF_AUDIO_FMT_U8P: | |
| aifc->afmt = GF_AUDIO_FMT_U8; | |
| break; | |
| case GF_AUDIO_FMT_S16P: | |
| case GF_AUDIO_FMT_S16_BE: | |
| aifc->afmt = GF_AUDIO_FMT_S16; | |
| break; | |
| case GF_AUDIO_FMT_S24P: | |
| case GF_AUDIO_FMT_S24_BE: | |
| aifc->afmt = GF_AUDIO_FMT_S24; | |
| break; | |
| case GF_AUDIO_FMT_S32P: | |
| case GF_AUDIO_FMT_S32_BE: | |
| aifc->afmt = GF_AUDIO_FMT_S32; | |
| break; | |
| case GF_AUDIO_FMT_FLTP: | |
| aifc->afmt = GF_AUDIO_FMT_FLT; | |
| break; | |
| case GF_AUDIO_FMT_DBLP: | |
| aifc->afmt = GF_AUDIO_FMT_DBL; | |
| break; | |
| default: | |
| force_config = GF_FALSE; | |
| break; | |
| } | |
| if (force_config) { | |
| gf_mixer_set_config(st->am, aifc->samplerate, aifc->chan, aifc->afmt, aifc->ch_layout); | |
| } | |
| st->is_init = (aifc->samplerate && aifc->chan && aifc->afmt) ? GF_TRUE : GF_FALSE; | |
| gf_assert(st->is_init); | |
| if (!st->is_init) { | |
| aifc->samplerate = aifc->chan = aifc->afmt = 0; | |
| aifc->ch_layout = 0; | |
| } | |
| /*this will force invalidation*/ | |
| return (for_reconf && st->is_init) ? GF_TRUE : GF_FALSE; | |
| } | |
| return st->is_init; | |
| } | |
| void audiobuffer_add_source(GF_AudioGroup *_this, GF_AudioInput *src) | |
| { | |
| AudioBufferStack *st = (AudioBufferStack *)_this; | |
| if (!src) return; | |
| /*just collect the input, reconfig is done once all children are rendered*/ | |
| gf_list_add(st->new_inputs, src); | |
| } | |
| void setup_audiobuffer(GF_AudioInput *ai, GF_Compositor *compositor, GF_Node *node) | |
| { | |
| memset(ai, 0, sizeof(GF_AudioInput)); | |
| ai->owner = node; | |
| ai->compositor = compositor; | |
| /*NEVER used for audio buffer*/ | |
| ai->stream = NULL; | |
| /*setup io interface*/ | |
| ai->input_ifce.FetchFrame = audiobuffer_fetch_frame; | |
| ai->input_ifce.ReleaseFrame = audiobuffer_release_frame; | |
| ai->input_ifce.GetConfig = audiobuffer_get_config; | |
| ai->input_ifce.GetChannelVolume = audiobuffer_get_volume; | |
| ai->input_ifce.GetSpeed = audiobuffer_get_speed; | |
| ai->input_ifce.IsMuted = audiobuffer_is_muted; | |
| ai->input_ifce.callback = ai; | |
| ai->speed = FIX_ONE; | |
| } | |
| void compositor_init_audiobuffer(GF_Compositor *compositor, GF_Node *node) | |
| { | |
| AudioBufferStack *st; | |
| GF_SAFEALLOC(st, AudioBufferStack); | |
| if (!st) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate audiobuffer stack\n")); | |
| return; | |
| } | |
| /*use our private input*/ | |
| setup_audiobuffer(&st->output, compositor, node); | |
| st->add_source = audiobuffer_add_source; | |
| st->time_handle.UpdateTimeNode = audiobuffer_update_time; | |
| st->time_handle.udta = node; | |
| st->set_duration = GF_TRUE; | |
| st->am = gf_mixer_new(NULL); | |
| st->new_inputs = gf_list_new(); | |
| gf_node_set_private(node, st); | |
| gf_node_set_callback_function(node, audiobuffer_traverse); | |
| gf_sc_register_time_node(compositor, &st->time_handle); | |
| } | |
| void compositor_audiobuffer_modified(GF_Node *node) | |
| { | |
| M_AudioBuffer *ab = (M_AudioBuffer *)node; | |
| AudioBufferStack *st = (AudioBufferStack *) gf_node_get_private(node); | |
| if (!st) return; | |
| //update state if we're active | |
| if (ab->isActive) | |
| audiobuffer_update_time(&st->time_handle); | |
| /*make sure we are still registered*/ | |
| if (!st->time_handle.is_registered && !st->time_handle.needs_unregister) | |
| gf_sc_register_time_node(st->output.compositor, &st->time_handle); | |
| else | |
| st->time_handle.needs_unregister = GF_FALSE; | |
| } | |
| #endif //!defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR) | |