blob: 1ae996c3012f6370b11f384d49bb8148148dd4e4 [file] [edit]
/*
* 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 "texturing.h"
#include <gpac/network.h>
#include <gpac/nodes_mpeg4.h>
#include <gpac/nodes_x3d.h>
#if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR)
/*for cache texture decode and hash*/
#include <gpac/avparse.h>
#include <gpac/crypt.h>
#include "nodes_stacks.h"
typedef struct
{
GF_TextureHandler txh;
GF_TimeNode time_handle;
Bool fetch_first_frame, first_frame_fetched, is_x3d;
Double start_time;
} MovieTextureStack;
static void movietexture_destroy(GF_Node *node, void *rs, Bool is_destroy)
{
if (is_destroy) {
MovieTextureStack *st = (MovieTextureStack *) gf_node_get_private(node);
gf_sc_texture_destroy(&st->txh);
if (st->time_handle.is_registered) gf_sc_unregister_time_node(st->txh.compositor, &st->time_handle);
gf_free(st);
}
}
static Fixed movietexture_get_speed(MovieTextureStack *stack, M_MovieTexture *mt)
{
return gf_mo_get_speed(stack->txh.stream, mt->speed);
}
static Bool movietexture_get_loop(MovieTextureStack *stack, M_MovieTexture *mt)
{
return gf_mo_get_loop(stack->txh.stream, mt->loop);
}
static void movietexture_activate(MovieTextureStack *stack, M_MovieTexture *mt, Double scene_time)
{
mt->isActive = 1;
gf_node_event_out((GF_Node*)mt, 8/*"isActive"*/);
if (!stack->txh.is_open) {
scene_time -= mt->startTime;
gf_sc_texture_play_from_to(&stack->txh, &mt->url, scene_time, -1, gf_mo_get_loop(stack->txh.stream, mt->loop), 0);
} else if (stack->first_frame_fetched) {
gf_mo_resume(stack->txh.stream);
}
gf_mo_set_speed(stack->txh.stream, mt->speed);
}
static void movietexture_deactivate(MovieTextureStack *stack, M_MovieTexture *mt)
{
mt->isActive = 0;
gf_node_event_out((GF_Node*)mt, 8/*"isActive"*/);
stack->time_handle.needs_unregister = 1;
if (stack->txh.is_open) {
gf_sc_texture_stop_no_unregister(&stack->txh);
}
}
static void movietexture_update(GF_TextureHandler *txh)
{
M_MovieTexture *txnode = (M_MovieTexture *) txh->owner;
MovieTextureStack *st = (MovieTextureStack *) gf_node_get_private(txh->owner);
/*setup texture if needed*/
if (!txh->is_open) return;
if (!txnode->isActive && st->first_frame_fetched) return;
/*when fetching the first frame disable resync*/
gf_sc_texture_update_frame(txh, 0);
if (txh->stream_finished) {
if (movietexture_get_loop(st, txnode)) {
gf_sc_texture_restart(txh);
}
/*if active deactivate*/
else if (txnode->isActive && (txnode->startTime<txnode->stopTime) && gf_mo_should_deactivate(st->txh.stream) ) {
movietexture_deactivate(st, txnode);
}
}
/*first frame is fetched*/
if (!st->first_frame_fetched && (txh->needs_refresh) ) {
st->first_frame_fetched = 1;
txnode->duration_changed = gf_mo_get_duration(txh->stream);
gf_node_event_out(txh->owner, 7/*"duration_changed"*/);
/*stop stream if needed*/
if (!txnode->isActive && txh->is_open) {
gf_mo_pause(txh->stream);
/*make sure the refresh flag is not cleared*/
txh->needs_refresh = 1;
gf_sc_invalidate(txh->compositor, NULL);
}
}
if (txh->needs_refresh) {
/*mark all subtrees using this image as dirty*/
gf_node_dirty_parents(txh->owner);
}
}
static void movietexture_update_time(GF_TimeNode *st)
{
Double time;
M_MovieTexture *mt = (M_MovieTexture *)st->udta;
MovieTextureStack *stack = (MovieTextureStack *)gf_node_get_private(st->udta);
/*not active, store start time and speed*/
if ( ! mt->isActive) {
stack->start_time = mt->startTime;
}
time = gf_node_get_scene_time(st->udta);
if (time < stack->start_time ||
/*special case if we're getting active AFTER stoptime */
(!mt->isActive && (mt->stopTime > stack->start_time) && (time>=mt->stopTime))
// || (!stack->start_time && !stack->is_x3d && !mt->loop)
) {
/*opens stream only at first access to fetch first frame*/
if (stack->fetch_first_frame) {
stack->fetch_first_frame = 0;
if (!stack->txh.is_open)
gf_sc_texture_play(&stack->txh, &mt->url);
else
gf_mo_resume(stack->txh.stream);
}
return;
}
if (movietexture_get_speed(stack, mt) && mt->isActive) {
/*if stoptime is reached (>startTime) deactivate*/
if ((mt->stopTime > stack->start_time) && (time >= mt->stopTime) ) {
movietexture_deactivate(stack, mt);
return;
}
}
/*we're (about to be) active: VRML:
"A time-dependent node is inactive until its startTime is reached. When time now becomes greater than or
equal to startTime, an isActive TRUE event is generated and the time-dependent node becomes active */
if (! mt->isActive) movietexture_activate(stack, mt, time);
stack->txh.stream_finished = GF_FALSE;
if (!mt->url.count)
stack->txh.stream_finished = GF_TRUE;
}
void compositor_init_movietexture(GF_Compositor *compositor, GF_Node *node)
{
MovieTextureStack *st;
GF_SAFEALLOC(st, MovieTextureStack);
if (!st) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate movie texture stack\n"));
return;
}
gf_sc_texture_setup(&st->txh, compositor, node);
st->txh.update_texture_fcnt = movietexture_update;
st->time_handle.UpdateTimeNode = movietexture_update_time;
st->time_handle.udta = node;
st->fetch_first_frame = 1;
st->txh.flags = 0;
if (((M_MovieTexture*)node)->repeatS) st->txh.flags |= GF_SR_TEXTURE_REPEAT_S;
if (((M_MovieTexture*)node)->repeatT) st->txh.flags |= GF_SR_TEXTURE_REPEAT_T;
#ifndef GPAC_DISABLE_X3D
st->is_x3d = (gf_node_get_tag(node)==TAG_X3D_MovieTexture) ? 1 : 0;
#endif
gf_node_set_private(node, st);
gf_node_set_callback_function(node, movietexture_destroy);
gf_sc_register_time_node(compositor, &st->time_handle);
}
GF_TextureHandler *mt_get_texture(GF_Node *node)
{
MovieTextureStack *st = (MovieTextureStack *) gf_node_get_private(node);
return &st->txh;
}
void compositor_movietexture_modified(GF_Node *node)
{
M_MovieTexture *mt = (M_MovieTexture *)node;
MovieTextureStack *st = (MovieTextureStack *) gf_node_get_private(node);
if (!st) return;
/*if open and changed, stop and play*/
if (gf_sc_texture_check_url_change(&st->txh, &mt->url)) {
if (st->txh.is_open) gf_sc_texture_stop(&st->txh);
if (mt->isActive) gf_sc_texture_play(&st->txh, &mt->url);
}
/*update state if we're active*/
else if (mt->isActive) {
movietexture_update_time(&st->time_handle);
if (!mt->isActive) return;
}
/*reregister if needed*/
st->time_handle.needs_unregister = 0;
if (!st->time_handle.is_registered) gf_sc_register_time_node(st->txh.compositor, &st->time_handle);
}
static void imagetexture_destroy(GF_Node *node, void *rs, Bool is_destroy)
{
if (is_destroy) {
GF_TextureHandler *txh = (GF_TextureHandler *) gf_node_get_private(node);
/*cleanup cache if needed*/
if (gf_node_get_tag(node)==TAG_MPEG4_CacheTexture) {
char section[64];
const char *opt, *file;
Bool delete_file = GF_TRUE;
M_CacheTexture *ct = (M_CacheTexture*)node;
sprintf(section, "@cache=%p", ct);
file = gf_opts_get_key(section, "cacheFile");
opt = gf_opts_get_key(section, "expireAfterNTP");
if (opt) {
u32 sec, frac, exp;
sscanf(opt, "%u", &exp);
gf_net_get_ntp(&sec, &frac);
if (!exp || (exp>sec)) delete_file=GF_FALSE;
}
if (delete_file) {
if (file) gf_file_delete((char*)file);
gf_opts_del_section(section);
}
if (txh->data) gf_free(txh->data);
txh->data = NULL;
}
gf_sc_texture_destroy(txh);
gf_free(txh);
}
}
static void imagetexture_update(GF_TextureHandler *txh)
{
if (gf_node_get_tag(txh->owner)!=TAG_MPEG4_CacheTexture) {
MFURL url = ((M_ImageTexture *) txh->owner)->url;
/*setup texture if needed*/
if (!txh->is_open && url.count) {
gf_sc_texture_play(txh, &url);
}
gf_sc_texture_update_frame(txh, 0);
if (
/*URL is present but not opened - redraw till fetch*/
/* (txh->stream && !txh->tx_io) && */
/*image has been updated*/
txh->needs_refresh) {
/*mark all subtrees using this image as dirty*/
gf_node_dirty_parents(txh->owner);
gf_sc_invalidate(txh->compositor, NULL);
}
return;
}
/*cache texture case*/
else {
M_CacheTexture *ct = (M_CacheTexture *) txh->owner;
/*decode cacheTexture data */
if ((ct->data || ct->image.buffer) && !txh->data) {
#ifndef GPAC_DISABLE_AV_PARSERS
u32 out_size;
GF_Err e;
/*BT/XMT playback: load to memory*/
if (ct->image.buffer) {
char *par = (char *) gf_scene_get_service_url( gf_node_get_graph(txh->owner ) );
char *src_url = gf_url_concatenate(par, ct->image.buffer);
if (ct->data) gf_free(ct->data);
ct->data = NULL;
ct->data_len = 0;
e = gf_file_load_data(src_url ? src_url : ct->image.buffer, &ct->data, &ct->data_len);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to load CacheTexture data from file %s: %s\n", src_url ? src_url : ct->image.buffer, gf_error_to_string(e) ) );
}
if (ct->image.buffer) gf_free(ct->image.buffer);
ct->image.buffer = NULL;
if (src_url) gf_free(src_url);
}
/*BIFS decoded playback*/
switch (ct->objectTypeIndication) {
case GF_CODECID_JPEG:
out_size = 0;
e = gf_img_jpeg_dec(ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, NULL, &out_size, 3);
if (e==GF_BUFFER_TOO_SMALL) {
u32 BPP;
txh->data = gf_malloc(sizeof(char) * out_size);
if (txh->pixelformat==GF_PIXEL_GREYSCALE) BPP = 1;
else BPP = 3;
e = gf_img_jpeg_dec(ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, txh->data, &out_size, BPP);
if (e==GF_OK) {
gf_sc_texture_allocate(txh);
gf_sc_texture_set_data(txh);
txh->needs_refresh = 1;
txh->stride = out_size / txh->height;
}
}
break;
case GF_CODECID_PNG:
out_size = 0;
e = gf_img_png_dec(ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, NULL, &out_size);
if (e==GF_BUFFER_TOO_SMALL) {
txh->data = gf_malloc(sizeof(char) * out_size);
e = gf_img_png_dec(ct->data, ct->data_len, &txh->width, &txh->height, &txh->pixelformat, txh->data, &out_size);
if (e==GF_OK) {
gf_sc_texture_allocate(txh);
gf_sc_texture_set_data(txh);
txh->needs_refresh = 1;
txh->stride = out_size / txh->height;
}
}
break;
}
#endif // GPAC_DISABLE_AV_PARSERS
/*cacheURL is specified, store the image*/
if (ct->cacheURL.buffer) {
u32 i;
u8 hash[20];
FILE *cached_texture;
char szExtractName[GF_MAX_PATH], *opt, *src_url;
opt = (char *) gf_opts_get_key("core", "cache");
if (opt) {
gf_strcpy(szExtractName, opt);
} else {
gf_strcpy(szExtractName, gf_get_default_cache_directory());
}
gf_strcat(szExtractName, "/");
src_url = (char *) gf_scene_get_service_url( gf_node_get_graph(txh->owner ) );
gf_sha1_csum((u8 *)src_url, (u32) strlen(src_url), hash);
for (i=0; i<20; i++) {
char t[3];
t[2] = 0;
sprintf(t, "%02X", hash[i]);
gf_strcat(szExtractName, t);
}
gf_strcat(szExtractName, "_");
gf_strcat(szExtractName, ct->cacheURL.buffer);
cached_texture = gf_fopen(szExtractName, "wb");
if (cached_texture) {
gf_fwrite(ct->data, ct->data_len, cached_texture);
gf_fclose(cached_texture);
}
/*and write cache info*/
if (ct->expirationDate!=0) {
char section[64];
sprintf(section, "@cache=%p", ct);
gf_opts_set_key(section, "serviceURL", src_url);
gf_opts_set_key(section, "cacheFile", szExtractName);
gf_opts_set_key(section, "cacheName", ct->cacheURL.buffer);
if (ct->expirationDate>0) {
char exp[50];
u32 sec, frac;
gf_net_get_ntp(&sec, &frac);
sec += ct->expirationDate;
sprintf(exp, "%u", sec);
gf_opts_set_key(section, "expireAfterNTP", exp);
} else {
gf_opts_set_key(section, "expireAfterNTP", "0");
}
}
}
/*done with image, destroy buffer*/
if (ct->data) gf_free(ct->data);
ct->data = NULL;
ct->data_len = 0;
}
}
}
void compositor_init_imagetexture(GF_Compositor *compositor, GF_Node *node)
{
GF_TextureHandler *txh;
GF_SAFEALLOC(txh, GF_TextureHandler);
if (!txh) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate image texture stack\n"));
return;
}
gf_sc_texture_setup(txh, compositor, node);
txh->update_texture_fcnt = imagetexture_update;
gf_node_set_private(node, txh);
gf_node_set_callback_function(node, imagetexture_destroy);
txh->flags = 0;
if (gf_node_get_tag(txh->owner)!=TAG_MPEG4_CacheTexture) {
if (((M_ImageTexture*)node)->repeatS) txh->flags |= GF_SR_TEXTURE_REPEAT_S;
if (((M_ImageTexture*)node)->repeatT) txh->flags |= GF_SR_TEXTURE_REPEAT_T;
} else {
const char *url;
u32 i, count;
M_CacheTexture*ct = (M_CacheTexture*)node;
if (!ct->image.buffer) return;
if (ct->repeatS) txh->flags |= GF_SR_TEXTURE_REPEAT_S;
if (ct->repeatT) txh->flags |= GF_SR_TEXTURE_REPEAT_T;
/*locate existing cache*/
url = gf_scene_get_service_url( gf_node_get_graph(node) );
count = gf_opts_get_section_count();
for (i=0; i<count; i++) {
const char *opt;
const char *name = gf_opts_get_section_name(i);
if (strncmp(name, "@cache=", 7)) continue;
opt = gf_opts_get_key(name, "serviceURL");
if (!opt || stricmp(opt, url)) continue;
opt = gf_opts_get_key(name, "cacheName");
if (opt && ct->cacheURL.buffer && !stricmp(opt, ct->cacheURL.buffer)) {
opt = gf_opts_get_key(name, "cacheFile");
if (opt) gf_file_delete((char*)opt);
gf_opts_del_section(name);
break;
}
}
}
}
GF_TextureHandler *it_get_texture(GF_Node *node)
{
return gf_node_get_private(node);
}
void compositor_imagetexture_modified(GF_Node *node)
{
MFURL url;
SFURL sfurl;
GF_TextureHandler *txh = (GF_TextureHandler *) gf_node_get_private(node);
if (!txh) return;
if (gf_node_get_tag(node)!=TAG_MPEG4_CacheTexture) {
url = ((M_ImageTexture *) node)->url;
} else {
url.count = 1;
sfurl.OD_ID=GF_MEDIA_EXTERNAL_ID;
sfurl.url = ((M_CacheTexture *) node)->image.buffer;
url.vals = &sfurl;
}
/*if open and changed, stop and play*/
if (txh->is_open) {
if (! gf_sc_texture_check_url_change(txh, &url)) return;
gf_sc_texture_stop(txh);
gf_sc_texture_play(txh, &url);
return;
}
/*if not open and changed play*/
if (url.count)
gf_sc_texture_play(txh, &url);
}
typedef struct
{
GF_TextureHandler txh;
char *pixels;
} PixelTextureStack;
static void pixeltexture_destroy(GF_Node *node, void *rs, Bool is_destroy)
{
if (is_destroy) {
PixelTextureStack *st = (PixelTextureStack *) gf_node_get_private(node);
if (st->pixels) gf_free(st->pixels);
gf_sc_texture_destroy(&st->txh);
gf_free(st);
}
}
static void pixeltexture_update(GF_TextureHandler *txh)
{
u32 pix_format, stride, i;
M_PixelTexture *pt = (M_PixelTexture *) txh->owner;
PixelTextureStack *st = (PixelTextureStack *) gf_node_get_private(txh->owner);
if (!gf_node_dirty_get(txh->owner)) return;
gf_node_dirty_clear(txh->owner, 0);
/*pixel texture doesn not use any media object but has data in the content.
However we still use the same texture object, just be careful not to use media funtcions*/
txh->transparent = 0;
stride = pt->image.width;
/*num_components are as in VRML (1->4) not as in BIFS bitstream (0->3)*/
switch (pt->image.numComponents) {
case 1:
pix_format = GF_PIXEL_GREYSCALE;
break;
case 2:
pix_format = GF_PIXEL_ALPHAGREY;
txh->transparent = 1;
stride *= 2;
break;
case 3:
pix_format = GF_PIXEL_RGB;
txh->transparent = 0;
stride *= 3;
break;
case 4:
pix_format = GF_PIXEL_RGBA;
txh->transparent = 1;
stride *= 4;
break;
default:
return;
}
if (!txh->tx_io) {
gf_sc_texture_allocate(txh);
if (!txh->tx_io) return;
}
if (st->pixels) gf_free(st->pixels);
st->pixels = (char*)gf_malloc(sizeof(char) * stride * pt->image.height);
/*FIXME FOR OPENGL !!*/
#if 0
for (i=0; i<pt->image.height; i++) {
memcpy(st->pixels + i * stride, pt->image.pixels + i * stride, stride);
}
#else
/*revert pixel ordering...*/
for (i=0; i<pt->image.height; i++) {
memcpy(st->pixels + i * stride, pt->image.pixels + (pt->image.height - 1 - i) * stride, stride);
}
#endif
txh->width = pt->image.width;
txh->height = pt->image.height;
txh->stride = stride;
txh->pixelformat = pix_format;
txh->data = st->pixels;
gf_sc_texture_set_data(txh);
}
void compositor_init_pixeltexture(GF_Compositor *compositor, GF_Node *node)
{
PixelTextureStack *st;
GF_SAFEALLOC(st, PixelTextureStack);
if (!st) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate pixel texture stack\n"));
return;
}
gf_sc_texture_setup(&st->txh, compositor, node);
st->pixels = NULL;
st->txh.update_texture_fcnt = pixeltexture_update;
gf_node_set_private(node, st);
gf_node_set_callback_function(node, pixeltexture_destroy);
st->txh.flags = 0;
if (((M_PixelTexture*)node)->repeatS) st->txh.flags |= GF_SR_TEXTURE_REPEAT_S;
if (((M_PixelTexture*)node)->repeatT) st->txh.flags |= GF_SR_TEXTURE_REPEAT_T;
}
GF_TextureHandler *pt_get_texture(GF_Node *node)
{
PixelTextureStack *st = (PixelTextureStack *) gf_node_get_private(node);
return &st->txh;
}
static void matte_update(GF_TextureHandler *txh)
{
/*nothing to do*/
}
void compositor_init_mattetexture(GF_Compositor *compositor, GF_Node *node)
{
GF_TextureHandler *txh;
GF_SAFEALLOC(txh, GF_TextureHandler);
if (!txh) {
GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate matte texture stack\n"));
return;
}
gf_sc_texture_setup(txh, compositor, node);
txh->flags = GF_SR_TEXTURE_MATTE;
txh->update_texture_fcnt = matte_update;
gf_node_set_private(node, txh);
gf_node_set_callback_function(node, imagetexture_destroy);
}
void gf_sc_mo_destroyed(GF_Node *n)
{
void *st = gf_node_get_private(n);
if (!st) return;
switch (gf_node_get_tag(n)) {
case TAG_MPEG4_Background2D:
((Background2DStack *)st)->txh.stream = NULL;
break;
case TAG_MPEG4_Background:
#ifndef GPAC_DISABLE_X3D
case TAG_X3D_Background:
#endif
#ifndef GPAC_DISABLE_3D
((BackgroundStack *)st)->txh_back.stream = NULL;
((BackgroundStack *)st)->txh_front.stream = NULL;
((BackgroundStack *)st)->txh_left.stream = NULL;
((BackgroundStack *)st)->txh_right.stream = NULL;
((BackgroundStack *)st)->txh_top.stream = NULL;
((BackgroundStack *)st)->txh_bottom.stream = NULL;
#endif
break;
case TAG_MPEG4_ImageTexture:
#ifndef GPAC_DISABLE_X3D
case TAG_X3D_ImageTexture:
#endif
((GF_TextureHandler *)st)->stream = NULL;
break;
case TAG_MPEG4_MovieTexture:
#ifndef GPAC_DISABLE_X3D
case TAG_X3D_MovieTexture:
#endif
((MovieTextureStack *)st)->txh.stream = NULL;
break;
case TAG_MPEG4_MediaSensor:
((MediaSensorStack *)st)->stream = NULL;
break;
case TAG_MPEG4_MediaControl:
((MediaControlStack *)st)->stream = NULL;
break;
case TAG_MPEG4_AudioSource:
((AudioSourceStack *)st)->input.stream = NULL;
break;
case TAG_MPEG4_AudioClip:
#ifndef GPAC_DISABLE_X3D
case TAG_X3D_AudioClip:
#endif
((AudioClipStack *)st)->input.stream = NULL;
break;
#ifndef GPAC_DISABLE_SVG
case TAG_SVG_video:
case TAG_SVG_image:
((SVG_video_stack *)st)->txh.stream = NULL;
break;
case TAG_SVG_audio:
((SVG_audio_stack *)st)->input.stream = NULL;
break;
#endif
default:
break;
}
}
#endif //!defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR)