From: Josep Torra Date: Thu, 11 Apr 2013 15:35:19 +0000 (+0200) Subject: examples: add an example aplication based OpenGL ES + EGL X-Git-Tag: 1.2.0~126 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c10031f371e5e26075b7ae79e9984c36d68c4231;p=platform%2Fupstream%2Fgst-omx.git examples: add an example aplication based OpenGL ES + EGL Application that shows how to integrate playbin with an OpenGL ES scene through EGL. Renders a video on the surfaces of an animated cube. The code is not ported to 1.x so it's not built by default. --- diff --git a/Makefile.am b/Makefile.am index 86710b3..e993476 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,9 @@ SUBDIRS = common omx tools config +# if BUILD_EXAMPLES +# SUBDIRS += examples +# endif + ACLOCAL_AMFLAGS = -I m4 -I common/m4 DISTCLEANFILES = _stdint.h diff --git a/configure.ac b/configure.ac index 21227c6..447e155 100644 --- a/configure.ac +++ b/configure.ac @@ -149,7 +149,9 @@ AG_GST_CHECK_GST_PLUGINS_BASE($GST_API_VERSION, [$GST_REQ], yes) AM_CONDITIONAL(HAVE_GST_CHECK, test "x$HAVE_GST_CHECK" = "xyes") PKG_CHECK_MODULES([GST_EGL], [gstreamer-egl-1.0], [ AC_DEFINE(HAVE_GST_EGL, 1, [Have gstreamer-egl]) + GST_EGL=yes ]) +AM_CONDITIONAL(HAVE_GST_EGL, test "x$GST_EGL" = "xyes") dnl Check for documentation xrefs GLIB_PREFIX="`$PKG_CONFIG --variable=prefix glib-2.0`" @@ -361,6 +363,8 @@ tools/Makefile config/Makefile config/bellagio/Makefile config/rpi/Makefile +examples/Makefile +examples/egl/Makefile ) AC_OUTPUT diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..8094d86 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,5 @@ +if HAVE_GST_EGL +SUBDIRS = egl +endif + +DIST_SUBDIRS = egl diff --git a/examples/egl/Makefile.am b/examples/egl/Makefile.am new file mode 100644 index 0000000..0aaa2e6 --- /dev/null +++ b/examples/egl/Makefile.am @@ -0,0 +1,19 @@ +noinst_PROGRAMS = testegl + +testegl_SOURCES = testegl.c + +noinst_HEADERS = cube_texture_and_coords.h + +testegl_LDADD = \ + $(GST_PLUGINS_BASE_LIBS) \ + -lgstapp-@GST_MAJORMINOR@ \ + $(GST_BASE_LIBS) \ + $(GST_LIBS) \ + $(GST_EGL_LIBS) + +testegl_CFLAGS = \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) \ + $(GST_CFLAGS) \ + $(GST_EGL_CFLAGS) + diff --git a/examples/egl/cube_texture_and_coords.h b/examples/egl/cube_texture_and_coords.h new file mode 100644 index 0000000..663e23b --- /dev/null +++ b/examples/egl/cube_texture_and_coords.h @@ -0,0 +1,100 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// Spatial coordinates for the cube + +static const GLbyte quadx[6*4*3] = { + /* FRONT */ + -10, -10, 10, + 10, -10, 10, + -10, 10, 10, + 10, 10, 10, + + /* BACK */ + -10, -10, -10, + -10, 10, -10, + 10, -10, -10, + 10, 10, -10, + + /* LEFT */ + -10, -10, 10, + -10, 10, 10, + -10, -10, -10, + -10, 10, -10, + + /* RIGHT */ + 10, -10, -10, + 10, 10, -10, + 10, -10, 10, + 10, 10, 10, + + /* TOP */ + -10, 10, 10, + 10, 10, 10, + -10, 10, -10, + 10, 10, -10, + + /* BOTTOM */ + -10, -10, 10, + -10, -10, -10, + 10, -10, 10, + 10, -10, -10, +}; + +/** Texture coordinates for the quad. */ +static const GLfloat texCoords[6 * 4 * 2] = { + 0.f, 0.f, + 1.f, 0.f, + 0.f, 1.f, + 1.f, 1.f, + + 0.f, 0.f, + 1.f, 0.f, + 0.f, 1.f, + 1.f, 1.f, + + 0.f, 0.f, + 1.f, 0.f, + 0.f, 1.f, + 1.f, 1.f, + + 0.f, 0.f, + 1.f, 0.f, + 0.f, 1.f, + 1.f, 1.f, + + 0.f, 0.f, + 1.f, 0.f, + 0.f, 1.f, + 1.f, 1.f, + + 0.f, 0.f, + 1.f, 0.f, + 0.f, 1.f, + 1.f, 1.f, +}; + diff --git a/examples/egl/testegl.c b/examples/egl/testegl.c new file mode 100644 index 0000000..e482329 --- /dev/null +++ b/examples/egl/testegl.c @@ -0,0 +1,1339 @@ +/* +Copyright (c) 2012, Broadcom Europe Ltd +Copyright (c) 2012, OtherCrashOverride +Copyright (C) 2013, Fluendo S.A. + @author: Josep Torra +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* A rotating cube rendered with OpenGL|ES and video played using GStreamer on + * the cube faces. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#if defined (USE_OMX_TARGET_RPI) && defined (HAVE_GST_EGL) && defined (__GNUC__) +#ifndef __VCCOREVER__ +#define __VCCOREVER__ 0x04000000 +#endif +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wredundant-decls" +#pragma GCC optimize ("gnu89-inline") +#endif + +#include "bcm_host.h" + +#if defined (USE_OMX_TARGET_RPI) && defined (HAVE_GST_EGL) && defined (__GNUC__) +#pragma GCC reset_options +#pragma GCC diagnostic pop +#endif + +#include +#include + +#if defined (USE_OMX_TARGET_RPI) && defined (HAVE_GST_EGL) && defined (__GNUC__) +#ifndef __VCCOREVER__ +#define __VCCOREVER__ 0x04000000 +#endif +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wredundant-decls" +#pragma GCC optimize ("gnu89-inline") +#endif + +#define EGL_EGLEXT_PROTOTYPES +#include + +#if defined (USE_OMX_TARGET_RPI) && defined (HAVE_GST_EGL) && defined (__GNUC__) +#pragma GCC reset_options +#pragma GCC diagnostic pop +#endif + +#include "cube_texture_and_coords.h" + +#ifndef M_PI +#define M_PI 3.141592654 +#endif + +#define TRACE_VC_MEMORY_ENABLED 0 + +#if TRACE_VC_MEMORY_ENABLED +#define TRACE_VC_MEMORY(str) \ + fprintf (stderr, "\n\n" str "\n"); \ + system ("vcdbg reloc >&2") + +#define TRACE_VC_MEMORY_DEFINE_ID(id) \ + static int id = 0 + +#define TRACE_VC_MEMORY_RESET_ID(id) \ + G_STMT_START { \ + id = 0; \ + } G_STMT_END + +#define TRACE_VC_MEMORY_ONCE_FOR_ID(str,id) \ + G_STMT_START { \ + if (id == 0) { \ + fprintf (stderr, "\n\n" str "\n"); \ + system ("vcdbg reloc >&2"); \ + id = 1; \ + } \ + } G_STMT_END + +#define TRACE_VC_MEMORY_ONCE(str,id) \ + G_STMT_START { \ + static int id = 0; \ + if (id == 0) { \ + fprintf (stderr, "\n\n" str "\n"); \ + system ("vcdbg reloc >&2"); \ + id = 1; \ + } \ + } G_STMT_END + +#else +#define TRACE_VC_MEMORY(str) while(0) +#define TRACE_VC_MEMORY_DEFINE_ID(id) +#define TRACE_VC_MEMORY_RESET_ID(id) while(0) +#define TRACE_VC_MEMORY_ONCE_FOR_ID(str,id) while(0) +#define TRACE_VC_MEMORY_ONCE(str,id) while(0) +#endif + + +typedef struct +{ + DISPMANX_DISPLAY_HANDLE_T dispman_display; + DISPMANX_ELEMENT_HANDLE_T dispman_element; + + uint32_t screen_width; + uint32_t screen_height; + gboolean animate; + gboolean sync_animation_with_video; + + /* OpenGL|ES objects */ + EGLDisplay display; + EGLSurface surface; + EGLContext context; + GLuint tex; + + /* model rotation vector and direction */ + GLfloat rot_angle_x_inc; + GLfloat rot_angle_y_inc; + GLfloat rot_angle_z_inc; + + /* current model rotation angles */ + GLfloat rot_angle_x; + GLfloat rot_angle_y; + GLfloat rot_angle_z; + + /* current distance from camera */ + GLfloat distance; + GLfloat distance_inc; + + /* GStreamer related resources */ + GstElement *pipeline; + GstEGLDisplay *gst_display; + + /* Interthread comunication */ + GAsyncQueue *queue; + GMutex *lock; + GCond *cond; + gboolean flushing; + GstMiniObject *popped_obj; + GstEGLImageMemory *current_mem; + + /* GLib mainloop */ + GMainLoop *main_loop; +} APP_STATE_T; + +static void init_ogl (APP_STATE_T * state); +static void init_model_proj (APP_STATE_T * state); +static void reset_model (APP_STATE_T * state); +static GLfloat inc_and_wrap_angle (GLfloat angle, GLfloat angle_inc); +static GLfloat inc_and_clip_distance (GLfloat distance, GLfloat distance_inc); +static void redraw_scene (APP_STATE_T * state); +static void update_model (APP_STATE_T * state); +static void init_textures (APP_STATE_T * state); +static APP_STATE_T _state, *state = &_state; + +TRACE_VC_MEMORY_DEFINE_ID (gid0); +TRACE_VC_MEMORY_DEFINE_ID (gid1); +TRACE_VC_MEMORY_DEFINE_ID (gid2); + +/*********************************************************** + * Name: init_ogl + * + * Arguments: + * APP_STATE_T *state - holds OGLES model info + * + * Description: Sets the display, OpenGL|ES context and screen stuff + * + * Returns: void + * + ***********************************************************/ +static void +init_ogl (APP_STATE_T * state) +{ + int32_t success = 0; + EGLBoolean result; + EGLint num_config; + + static EGL_DISPMANX_WINDOW_T nativewindow; + + DISPMANX_UPDATE_HANDLE_T dispman_update; + VC_RECT_T dst_rect; + VC_RECT_T src_rect; + + static const EGLint attribute_list[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 16, + //EGL_SAMPLES, 4, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_NONE + }; + + EGLConfig config; + + /* get an EGL display connection */ + state->display = eglGetDisplay (EGL_DEFAULT_DISPLAY); + assert (state->display != EGL_NO_DISPLAY); + + /* initialize the EGL display connection */ + result = eglInitialize (state->display, NULL, NULL); + assert (EGL_FALSE != result); + + /* get an appropriate EGL frame buffer configuration + * this uses a BRCM extension that gets the closest match, rather + * than standard which returns anything that matches. */ + result = + eglSaneChooseConfigBRCM (state->display, attribute_list, &config, 1, + &num_config); + assert (EGL_FALSE != result); + + /* create an EGL rendering context */ + state->context = + eglCreateContext (state->display, config, EGL_NO_CONTEXT, NULL); + assert (state->context != EGL_NO_CONTEXT); + + /* create an EGL window surface */ + success = graphics_get_display_size (0 /* LCD */ , &state->screen_width, + &state->screen_height); + assert (success >= 0); + + dst_rect.x = 0; + dst_rect.y = 0; + dst_rect.width = state->screen_width; + dst_rect.height = state->screen_height; + + src_rect.x = 0; + src_rect.y = 0; + src_rect.width = state->screen_width << 16; + src_rect.height = state->screen_height << 16; + + state->dispman_display = vc_dispmanx_display_open (0 /* LCD */ ); + dispman_update = vc_dispmanx_update_start (0); + + state->dispman_element = + vc_dispmanx_element_add (dispman_update, state->dispman_display, + 0 /*layer */ , &dst_rect, 0 /*src */ , + &src_rect, DISPMANX_PROTECTION_NONE, 0 /*alpha */ , 0 /*clamp */ , + 0 /*transform */ ); + + nativewindow.element = state->dispman_element; + nativewindow.width = state->screen_width; + nativewindow.height = state->screen_height; + vc_dispmanx_update_submit_sync (dispman_update); + + state->surface = + eglCreateWindowSurface (state->display, config, &nativewindow, NULL); + assert (state->surface != EGL_NO_SURFACE); + + /* connect the context to the surface */ + result = + eglMakeCurrent (state->display, state->surface, state->surface, + state->context); + assert (EGL_FALSE != result); + + /* Set background color and clear buffers */ + glClearColor (0.15f, 0.25f, 0.35f, 1.0f); + + /* Enable back face culling. */ + glEnable (GL_CULL_FACE); + + glMatrixMode (GL_MODELVIEW); +} + +/*********************************************************** + * Name: init_model_proj + * + * Arguments: + * APP_STATE_T *state - holds OGLES model info + * + * Description: Sets the OpenGL|ES model to default values + * + * Returns: void + * + ***********************************************************/ +static void +init_model_proj (APP_STATE_T * state) +{ + float nearp = 1.0f; + float farp = 500.0f; + float hht; + float hwd; + + glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + + glViewport (0, 0, (GLsizei) state->screen_width, + (GLsizei) state->screen_height); + + glMatrixMode (GL_PROJECTION); + glLoadIdentity (); + + hht = nearp * (float) tan (45.0 / 2.0 / 180.0 * M_PI); + hwd = hht * (float) state->screen_width / (float) state->screen_height; + + glFrustumf (-hwd, hwd, -hht, hht, nearp, farp); + + glEnableClientState (GL_VERTEX_ARRAY); + glVertexPointer (3, GL_BYTE, 0, quadx); + + reset_model (state); +} + +/*********************************************************** + * Name: reset_model + * + * Arguments: + * APP_STATE_T *state - holds OGLES model info + * + * Description: Resets the Model projection and rotation direction + * + * Returns: void + * + ***********************************************************/ +static void +reset_model (APP_STATE_T * state) +{ + /* reset model position */ + glMatrixMode (GL_MODELVIEW); + glLoadIdentity (); + glTranslatef (0.f, 0.f, -50.f); + + /* reset model rotation */ + state->rot_angle_x = 45.f; + state->rot_angle_y = 30.f; + state->rot_angle_z = 0.f; + state->rot_angle_x_inc = 0.5f; + state->rot_angle_y_inc = 0.5f; + state->rot_angle_z_inc = 0.f; + state->distance = 40.f; +} + +/*********************************************************** + * Name: update_model + * + * Arguments: + * APP_STATE_T *state - holds OGLES model info + * + * Description: Updates model projection to current position/rotation + * + * Returns: void + * + ***********************************************************/ +static void +update_model (APP_STATE_T * state) +{ + if (state->animate) { + /* update position */ + state->rot_angle_x = + inc_and_wrap_angle (state->rot_angle_x, state->rot_angle_x_inc); + state->rot_angle_y = + inc_and_wrap_angle (state->rot_angle_y, state->rot_angle_y_inc); + state->rot_angle_z = + inc_and_wrap_angle (state->rot_angle_z, state->rot_angle_z_inc); + state->distance = + inc_and_clip_distance (state->distance, state->distance_inc); + } + + glLoadIdentity (); + /* move camera back to see the cube */ + glTranslatef (0.f, 0.f, -state->distance); + + /* Rotate model to new position */ + glRotatef (state->rot_angle_x, 1.f, 0.f, 0.f); + glRotatef (state->rot_angle_y, 0.f, 1.f, 0.f); + glRotatef (state->rot_angle_z, 0.f, 0.f, 1.f); +} + +/*********************************************************** + * Name: inc_and_wrap_angle + * + * Arguments: + * GLfloat angle current angle + * GLfloat angle_inc angle increment + * + * Description: Increments or decrements angle by angle_inc degrees + * Wraps to 0 at 360 deg. + * + * Returns: new value of angle + * + ***********************************************************/ +static GLfloat +inc_and_wrap_angle (GLfloat angle, GLfloat angle_inc) +{ + angle += angle_inc; + + if (angle >= 360.0) + angle -= 360.f; + else if (angle <= 0) + angle += 360.f; + + return angle; +} + +/*********************************************************** + * Name: inc_and_clip_distance + * + * Arguments: + * GLfloat distance current distance + * GLfloat distance_inc distance increment + * + * Description: Increments or decrements distance by distance_inc units + * Clips to range + * + * Returns: new value of angle + * + ***********************************************************/ +static GLfloat +inc_and_clip_distance (GLfloat distance, GLfloat distance_inc) +{ + distance += distance_inc; + + if (distance >= 120.0f) + distance = 120.f; + else if (distance <= 40.0f) + distance = 40.0f; + + return distance; +} + +/*********************************************************** + * Name: redraw_scene + * + * Arguments: + * APP_STATE_T *state - holds OGLES model info + * + * Description: Draws the model and calls eglSwapBuffers + * to render to screen + * + * Returns: void + * + ***********************************************************/ +static void +redraw_scene (APP_STATE_T * state) +{ + /* Start with a clear screen */ + glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + /* Need to rotate textures - do this by rotating each cube face */ + glRotatef (270.f, 0.f, 0.f, 1.f); /* front face normal along z axis */ + + /* draw first 4 vertices */ + glDrawArrays (GL_TRIANGLE_STRIP, 0, 4); + + /* same pattern for other 5 faces - rotation chosen to make image orientation 'nice' */ + glRotatef (90.f, 0.f, 0.f, 1.f); /* back face normal along z axis */ + glDrawArrays (GL_TRIANGLE_STRIP, 4, 4); + + glRotatef (90.f, 1.f, 0.f, 0.f); /* left face normal along x axis */ + glDrawArrays (GL_TRIANGLE_STRIP, 8, 4); + + glRotatef (90.f, 1.f, 0.f, 0.f); /* right face normal along x axis */ + glDrawArrays (GL_TRIANGLE_STRIP, 12, 4); + + glRotatef (270.f, 0.f, 1.f, 0.f); /* top face normal along y axis */ + glDrawArrays (GL_TRIANGLE_STRIP, 16, 4); + + glRotatef (90.f, 0.f, 1.f, 0.f); /* bottom face normal along y axis */ + glDrawArrays (GL_TRIANGLE_STRIP, 20, 4); + + eglSwapBuffers (state->display, state->surface); +} + +/*********************************************************** + * Name: init_textures + * + * Arguments: + * APP_STATE_T *state - holds OGLES model info + * + * Description: Initialise OGL|ES texture surfaces to use image + * buffers + * + * Returns: void + * + ***********************************************************/ +static void +init_textures (APP_STATE_T * state) +{ + glGenTextures (1, &state->tex); + + glBindTexture (GL_TEXTURE_2D, state->tex); + +#if 0 + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); +#else + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +#endif + + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + /* setup overall texture environment */ + glTexCoordPointer (2, GL_FLOAT, 0, texCoords); + glEnableClientState (GL_TEXTURE_COORD_ARRAY); + + glEnable (GL_TEXTURE_2D); + + /* Bind texture surface to current vertices */ + glBindTexture (GL_TEXTURE_2D, state->tex); +} + +static void +destroy_pool_resources (GstEGLImageMemoryPool * pool, gpointer user_data) +{ + APP_STATE_T *state = (APP_STATE_T *) user_data; + gint i, size = gst_egl_image_memory_pool_get_size (pool); + EGLClientBuffer client_buffer; + EGLImageKHR image; + EGLint error; + + TRACE_VC_MEMORY ("before pool destruction"); + for (i = 0; i < size; i++) { + if (gst_egl_image_memory_pool_get_resources (pool, i, &client_buffer, + &image)) { + GLuint tid = (GLuint) client_buffer; + error = EGL_SUCCESS; + + if (image != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR (state->display, image); + if ((error = eglGetError ()) != EGL_SUCCESS) { + g_print ("eglDestroyImageKHR failed %x\n", error); + } + } + + if (tid) { + error = GL_NO_ERROR; + glDeleteTextures (1, &tid); + if ((error = glGetError ()) != GL_NO_ERROR) { + g_print ("glDeleteTextures failed %x\n", error); + } + } + g_print ("destroyed texture %x image %p\n", tid, image); + } + } + TRACE_VC_MEMORY ("after pool destruction"); +} + +static GstEGLImageMemoryPool * +create_pool (APP_STATE_T * state, gint size, gint width, gint height) +{ + GstEGLImageMemoryPool *pool; + gint i; + EGLint error; + + TRACE_VC_MEMORY ("before pool creation"); + pool = gst_egl_image_memory_pool_new (size, state->gst_display, state, + destroy_pool_resources); + + for (i = 0; i < size; i++) { + GLuint tid; + EGLImageKHR image; + + error = GL_NO_ERROR; + glGenTextures (1, &tid); + if ((error = glGetError ()) != GL_NO_ERROR) { + g_print ("glGenTextures failed %x\n", error); + goto failed; + } + + glBindTexture (GL_TEXTURE_2D, tid); + glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, + GL_UNSIGNED_BYTE, NULL); + if ((error = glGetError ()) != GL_NO_ERROR) { + g_print ("glTexImage2D failed %x\n", error); + goto failed; + } + + /* Create EGL Image */ + error = EGL_SUCCESS; + image = eglCreateImageKHR (state->display, + state->context, EGL_GL_TEXTURE_2D_KHR, (EGLClientBuffer) tid, 0); + + if (image == EGL_NO_IMAGE_KHR) { + if ((error = eglGetError ()) != EGL_SUCCESS) { + g_print ("eglCreateImageKHR failed %x\n", error); + } else { + g_print ("eglCreateImageKHR failed.\n"); + } + goto failed; + } + g_print ("created texture %x image %p\n", tid, image); + gst_egl_image_memory_pool_set_resources (pool, i, (EGLClientBuffer) tid, + image); + } + + TRACE_VC_MEMORY ("after pool creation"); + TRACE_VC_MEMORY_RESET_ID (gid0); + TRACE_VC_MEMORY_RESET_ID (gid1); + TRACE_VC_MEMORY_RESET_ID (gid2); + + return pool; + +failed: + gst_egl_image_memory_pool_unref (pool); + return NULL; +} + +static gboolean +render_scene (APP_STATE_T * state) +{ + update_model (state); + redraw_scene (state); + TRACE_VC_MEMORY_ONCE_FOR_ID ("after render_scene", gid2); + + return !state->sync_animation_with_video; +} + +static void +update_image (APP_STATE_T * state, GstBuffer * buffer) +{ + GstEGLImageMemory *mem = (GstEGLImageMemory *) GST_BUFFER_DATA (buffer); + + g_mutex_lock (state->lock); + if (state->current_mem) { + gst_egl_image_memory_unref (state->current_mem); + } + state->current_mem = gst_egl_image_memory_ref (mem); + g_mutex_unlock (state->lock); + + TRACE_VC_MEMORY_ONCE_FOR_ID ("before glEGLImageTargetTexture2DOES", gid0); + glBindTexture (GL_TEXTURE_2D, state->tex); + glEGLImageTargetTexture2DOES (GL_TEXTURE_2D, + gst_egl_image_memory_get_image (mem)); + TRACE_VC_MEMORY_ONCE_FOR_ID ("after glEGLImageTargetTexture2DOES", gid1); + + if (state->sync_animation_with_video) { + render_scene (state); + } +} + +static void +init_intercom (APP_STATE_T * state) +{ + state->queue = + g_async_queue_new_full ((GDestroyNotify) gst_mini_object_unref); + state->lock = g_mutex_new (); + state->cond = g_cond_new (); +} + +static void +flush_internal (APP_STATE_T * state) +{ + if (state->current_mem) { + gst_egl_image_memory_unref (state->current_mem); + } + state->current_mem = NULL; +} + +static void +flush_start (APP_STATE_T * state) +{ + GstMiniObject *object = NULL; + + g_mutex_lock (state->lock); + state->flushing = TRUE; + g_cond_broadcast (state->cond); + g_mutex_unlock (state->lock); + + while ((object = g_async_queue_try_pop (state->queue))) { + gst_mini_object_unref (object); + } + + flush_internal (state); +} + +static void +flush_stop (APP_STATE_T * state) +{ + g_mutex_lock (state->lock); + state->popped_obj = NULL; + state->flushing = FALSE; + g_mutex_unlock (state->lock); +} + +static void +pipeline_pause (APP_STATE_T * state) +{ + flush_start (state); + gst_element_set_state (state->pipeline, GST_STATE_PAUSED); + flush_stop (state); +} + +static gint64 +pipeline_get_position (APP_STATE_T * state) +{ + gint64 position = -1; + + if (state->pipeline) { + gst_element_query_position (state->pipeline, GST_FORMAT_TIME, &position); + } + + return position; +} + +static gint64 +pipeline_get_duration (APP_STATE_T * state) +{ + gint64 duration = -1; + + if (state->pipeline) { + gst_element_query_duration (state->pipeline, GST_FORMAT_TIME, &duration); + } + + return duration; +} + +static void +pipeline_seek (APP_STATE_T * state, gint64 position) +{ + if (state->pipeline) { + GstEvent *event; + event = gst_event_new_seek (1.0, + GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, + GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE); + if (!gst_element_send_event (state->pipeline, event)) { + g_print ("seek failed\n"); + } + } +} + +static gboolean +handle_queued_objects (APP_STATE_T * state) +{ + GstMiniObject *object = NULL; + + if (g_async_queue_length (state->queue) == 0) { + return FALSE; + } + + while ((object = g_async_queue_try_pop (state->queue))) { + + g_mutex_lock (state->lock); + if (state->flushing) { + state->popped_obj = object; + gst_mini_object_unref (object); + g_cond_broadcast (state->cond); + g_mutex_unlock (state->lock); + continue; + } + g_mutex_unlock (state->lock); + + if (GST_IS_BUFFER (object)) { + GstBuffer *buffer = GST_BUFFER_CAST (object); + update_image (state, buffer); + gst_buffer_unref (buffer); + } else if (GST_IS_MESSAGE (object)) { + GstMessage *message = GST_MESSAGE_CAST (object); + g_print ("\nmessage %p ", message); + if (gst_structure_has_name (message->structure, "need-egl-pool")) { + GstElement *element = GST_ELEMENT (GST_MESSAGE_SRC (message)); + gint size, width, height; + + gst_message_parse_need_egl_pool (message, &size, &width, &height); + + g_print ("need-egl-pool, size %d width %d height %d\n", size, width, + height); + + if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "pool")) { + GstEGLImageMemoryPool *pool = NULL; + + if ((pool = create_pool (state, size, width, height))) { + g_object_set (element, "pool", pool, NULL); + } + } + } + gst_message_unref (message); + } else if (GST_IS_EVENT (object)) { + GstEvent *event = GST_EVENT_CAST (object); + g_print ("\nevent %p %s\n", event, + gst_event_type_get_name (GST_EVENT_TYPE (event))); + + g_mutex_lock (state->lock); + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS: + flush_internal (state); + break; + default: + break; + } + g_mutex_unlock (state->lock); + gst_event_unref (event); + } + + g_mutex_lock (state->lock); + state->popped_obj = object; + g_cond_broadcast (state->cond); + g_mutex_unlock (state->lock); + } + + return FALSE; +} + +static gboolean +queue_object (APP_STATE_T * state, GstMiniObject * obj, gboolean synchronous) +{ + g_mutex_lock (state->lock); + if (state->flushing) { + g_mutex_unlock (state->lock); + gst_mini_object_unref (obj); + return FALSE; + } + + g_async_queue_push (state->queue, obj); + + + if (state->sync_animation_with_video) { + g_idle_add_full (G_PRIORITY_HIGH_IDLE, (GSourceFunc) handle_queued_objects, + state, NULL); + } + + if (synchronous) { + /* Waiting for object to be handled */ + do { + g_cond_wait (state->cond, state->lock); + } while (!state->flushing && state->popped_obj != obj); + } + g_mutex_unlock (state->lock); + + return TRUE; +} + +static gboolean +handle_msgs_and_render_scene (APP_STATE_T * state) +{ + handle_queued_objects (state); + return render_scene (state); +} + +static void +preroll_cb (GstElement * fakesink, GstBuffer * buffer, GstPad * pad, + gpointer user_data) +{ + APP_STATE_T *state = (APP_STATE_T *) user_data; + queue_object (state, GST_MINI_OBJECT_CAST (gst_buffer_ref (buffer)), FALSE); +} + +static void +buffers_cb (GstElement * fakesink, GstBuffer * buffer, GstPad * pad, + gpointer user_data) +{ + APP_STATE_T *state = (APP_STATE_T *) user_data; + queue_object (state, GST_MINI_OBJECT_CAST (gst_buffer_ref (buffer)), TRUE); +} + +static gboolean +events_cb (GstPad * pad, GstEvent * event, gpointer user_data) +{ + APP_STATE_T *state = (APP_STATE_T *) user_data; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + flush_start (state); + break; + case GST_EVENT_FLUSH_STOP: + flush_stop (state); + break; + case GST_EVENT_EOS: + queue_object (state, GST_MINI_OBJECT_CAST (gst_event_ref (event)), TRUE); + break; + default: + break; + } + + return TRUE; +} + +static gboolean +init_playbin_player (APP_STATE_T * state, const gchar * uri) +{ + GstElement *asink; + GstElement *vsink; + + vsink = gst_element_factory_make ("fakesink", "vsink"); + g_object_set (vsink, "sync", TRUE, "silent", TRUE, + "enable-last-buffer", FALSE, + "max-lateness", 20 * GST_MSECOND, "signal-handoffs", TRUE, NULL); + + g_signal_connect (vsink, "preroll-handoff", G_CALLBACK (preroll_cb), state); + g_signal_connect (vsink, "handoff", G_CALLBACK (buffers_cb), state); + + gst_pad_add_event_probe (gst_element_get_static_pad (vsink, "sink"), + G_CALLBACK (events_cb), state); + +#if 0 + asink = gst_element_factory_make ("fakesink", "asink"); + g_object_set (asink, "sync", TRUE, "silent", TRUE, NULL); +#else + asink = gst_element_factory_make ("alsasink", "asink"); +#endif + + /* Instantiate and configure playbin */ + state->pipeline = gst_element_factory_make ("playbin", "player"); + g_object_set (state->pipeline, "uri", uri, + "video-sink", vsink, "audio-sink", asink, NULL); + + return TRUE; +} + +static gboolean +init_parse_launch_player (APP_STATE_T * state, const gchar * spipeline) +{ + GstElement *vsink; + GError *error = NULL; + + state->pipeline = gst_parse_launch (spipeline, &error); + + if (!state->pipeline) { + g_printerr ("Unable to instatiate pipeline '%s': %s\n", + spipeline, error->message); + return FALSE; + } + + vsink = gst_bin_get_by_name (GST_BIN (state->pipeline), "vsink"); + + if (!vsink) { + g_printerr ("Unable to find a fakesink named 'vsink'"); + return FALSE; + } + + g_object_set (vsink, "sync", TRUE, "silent", TRUE, + "enable-last-buffer", FALSE, + "max-lateness", 20 * GST_MSECOND, "signal-handoffs", TRUE, NULL); + + g_signal_connect (vsink, "preroll-handoff", G_CALLBACK (preroll_cb), state); + g_signal_connect (vsink, "handoff", G_CALLBACK (buffers_cb), state); + + gst_pad_add_event_probe (gst_element_get_static_pad (vsink, "sink"), + G_CALLBACK (events_cb), state); + + return TRUE; +} + +//------------------------------------------------------------------------------ + +static void +report_position_duration (APP_STATE_T * state) +{ + gint64 position, duration; + + duration = pipeline_get_duration (state); + position = pipeline_get_position (state); + + if (position != -1) { + g_print ("\n position / duration: %" GST_TIME_FORMAT, + GST_TIME_ARGS (position)); + } else { + g_print ("\n position / duration: unknown"); + } + + if (duration != -1) { + g_print (" / %" GST_TIME_FORMAT, GST_TIME_ARGS (duration)); + } else { + g_print (" / unknown"); + } + g_print ("\n"); +} + +static void +seek_forward (APP_STATE_T * state) +{ + gint64 position, duration; + + duration = pipeline_get_duration (state); + position = pipeline_get_position (state); + + if (position != -1) { + position += 30 * GST_SECOND; + if (duration != -1) { + position = MIN (position, duration); + } + pipeline_seek (state, position); + } +} + +static void +seek_backward (APP_STATE_T * state) +{ + gint64 position; + + position = pipeline_get_position (state); + + if (position != -1) { + position -= 30 * GST_SECOND; + position = MAX (position, 0); + pipeline_seek (state, position); + } +} + +#define SKIP(t) \ + while (*t) { \ + if ((*t == ' ') || (*t == '\n') || (*t == '\t') || (*t == '\r')) \ + t++; \ + else \ + break; \ + } + +/* Process keyboard input */ +static gboolean +handle_keyboard (GIOChannel * source, GIOCondition cond, APP_STATE_T * state) +{ + gchar *str = NULL; + char op; + + if (g_io_channel_read_line (source, &str, NULL, NULL, + NULL) == G_IO_STATUS_NORMAL) { + + gchar *cmd = str; + SKIP (cmd) + op = *cmd; + cmd++; + switch (op) { + case 'a': + if (state->animate) { + state->animate = FALSE; + } else { + state->animate = TRUE; + } + break; + case 'p': + pipeline_pause (state); + break; + case 'r': + gst_element_set_state (state->pipeline, GST_STATE_PLAYING); + break; + case 'l': + report_position_duration (state); + break; + case 'f': + seek_forward (state); + break; + case 'b': + seek_backward (state); + break; + case 'q': + flush_start (state); + gst_element_set_state (state->pipeline, GST_STATE_READY); + break; + case 'S': + if (state->sync_animation_with_video) { + state->sync_animation_with_video = FALSE; + /* Add the rendering task */ + g_idle_add_full (G_PRIORITY_HIGH_IDLE, + (GSourceFunc) handle_msgs_and_render_scene, state, NULL); + g_print ("\nanimation is not synchoronized with video\n"); + } else { + state->sync_animation_with_video = TRUE; + g_print ("\nanimation is synchoronized with video\n"); + } + break; + } + } + g_free (str); + return TRUE; +} + +static GstBusSyncReply +bus_sync_handler (GstBus * bus, GstMessage * message, GstPipeline * data) +{ + if ((GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT) && + gst_structure_has_name (message->structure, "need-egl-pool")) { + queue_object (state, GST_MINI_OBJECT_CAST (gst_message_ref (message)), + TRUE); + } + return GST_BUS_PASS; +} + +/* on error print the error and quit the application */ +static void +error_cb (GstBus * bus, GstMessage * msg, APP_STATE_T * state) +{ + GError *err; + gchar *debug_info; + + gst_message_parse_error (msg, &err, &debug_info); + g_printerr ("Error received from element %s: %s\n", + GST_OBJECT_NAME (msg->src), err->message); + g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); + g_clear_error (&err); + g_free (debug_info); + flush_start (state); + gst_element_set_state (state->pipeline, GST_STATE_READY); +} + +/* buffering */ +static void +buffering_cb (GstBus * bus, GstMessage * msg, APP_STATE_T * state) +{ + gint percent; + + gst_message_parse_buffering (msg, &percent); + g_print ("Buffering %3d%%\r", percent); + if (percent < 100) + gst_element_set_state (state->pipeline, GST_STATE_PAUSED); + else { + g_print ("\n"); + gst_element_set_state (state->pipeline, GST_STATE_PLAYING); + } +} + +/* on EOS just quit the application */ +static void +eos_cb (GstBus * bus, GstMessage * msg, APP_STATE_T * state) +{ + if (GST_MESSAGE_SRC (msg) == GST_OBJECT (state->pipeline)) { + g_print ("End-Of-Stream reached.\n"); + flush_start (state); + gst_element_set_state (state->pipeline, GST_STATE_READY); + } +} + +static void +state_changed_cb (GstBus * bus, GstMessage * msg, APP_STATE_T * state) +{ + GstState old_state, new_state, pending_state; + if (GST_MESSAGE_SRC (msg) == GST_OBJECT (state->pipeline)) { + gst_message_parse_state_changed (msg, &old_state, &new_state, + &pending_state); + g_print ("State changed to %s\n", gst_element_state_get_name (new_state)); + if (old_state == GST_STATE_PAUSED && new_state == GST_STATE_READY) { + g_main_loop_quit (state->main_loop); + } + } +} + +//============================================================================== + +static void +close_ogl (void) +{ + DISPMANX_UPDATE_HANDLE_T dispman_update; + + /* clear screen */ + glClear (GL_COLOR_BUFFER_BIT); + eglSwapBuffers (state->display, state->surface); + + /* Release OpenGL resources */ + eglMakeCurrent (state->display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT); + eglDestroySurface (state->display, state->surface); + eglDestroyContext (state->display, state->context); + gst_egl_display_unref (state->gst_display); + + dispman_update = vc_dispmanx_update_start (0); + vc_dispmanx_element_remove (dispman_update, state->dispman_element); + vc_dispmanx_update_submit_sync (dispman_update); + vc_dispmanx_display_close (state->dispman_display); +} + +//============================================================================== + +int +main (int argc, char **argv) +{ + GstBus *bus; + GOptionContext *ctx; + GIOChannel *io_stdin; + GError *err = NULL; + gboolean res; + GOptionEntry options[] = { + {NULL} + }; + + /* Clear application state */ + memset (state, 0, sizeof (*state)); + state->animate = TRUE; + state->sync_animation_with_video = TRUE; + + /* must initialise the threading system before using any other GLib funtion */ + if (!g_thread_supported ()) + g_thread_init (NULL); + + ctx = g_option_context_new ("[ADDITIONAL ARGUMENTS]"); + g_option_context_add_main_entries (ctx, options, NULL); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + g_print ("Error initializing: %s\n", GST_STR_NULL (err->message)); + exit (1); + } + g_option_context_free (ctx); + + if (argc != 2) { + g_print ("Usage: %s or \n", argv[0]); + exit (1); + } + + /* Initialize GStreamer */ + gst_init (&argc, &argv); + + TRACE_VC_MEMORY ("state 0"); + +#if defined (USE_OMX_TARGET_RPI) && defined (HAVE_GST_EGL) + bcm_host_init (); + TRACE_VC_MEMORY ("after bcm_host_init"); +#endif + + + /* Start OpenGLES */ + init_ogl (state); + TRACE_VC_MEMORY ("after init_ogl"); + + /* Wrap the EGL display */ + state->gst_display = gst_egl_display_new (state->display); + + /* Setup the model world */ + init_model_proj (state); + TRACE_VC_MEMORY ("after init_model_proj"); + + /* initialize the OGLES texture(s) */ + init_textures (state); + TRACE_VC_MEMORY ("after init_textures"); + + /* initialize inter thread comunnication */ + init_intercom (state); + + /* Initialize player */ + if (gst_uri_is_valid (argv[1])) { + res = init_playbin_player (state, argv[1]); + } else { + res = init_parse_launch_player (state, argv[1]); + } + + if (!res) + goto done; + + /* Create a GLib Main Loop and set it to run */ + state->main_loop = g_main_loop_new (NULL, FALSE); + + /* Add a keyboard watch so we get notified of keystrokes */ + io_stdin = g_io_channel_unix_new (fileno (stdin)); + g_io_add_watch (io_stdin, G_IO_IN, (GIOFunc) handle_keyboard, state); + g_io_channel_unref (io_stdin); + + /* *INDENT-OFF* */ + g_print ("Available commands: \n" + " a - Toggle animation \n" + " p - Pause playback \n" + " r - Resume playback \n" + " l - Query position/duration\n" + " f - Seek 30 seconds forward \n" + " b - Seek 30 seconds backward \n" + " S - Toggle synchronization of video and animation \n" + " q - Quit \n"); + /* *INDENT-ON* */ + + if (!state->sync_animation_with_video) { + /* Add the rendering task */ + g_idle_add_full (G_PRIORITY_HIGH_IDLE, + (GSourceFunc) handle_msgs_and_render_scene, state, NULL); + } + + /* Connect the bus handlers */ + bus = gst_element_get_bus (state->pipeline); + + gst_bus_set_sync_handler (bus, (GstBusSyncHandler) bus_sync_handler, state); + + gst_bus_add_signal_watch_full (bus, G_PRIORITY_HIGH); + gst_bus_enable_sync_message_emission (bus); + + g_signal_connect (G_OBJECT (bus), "message::error", (GCallback) error_cb, + state); + g_signal_connect (G_OBJECT (bus), "message::buffering", + (GCallback) buffering_cb, state); + g_signal_connect (G_OBJECT (bus), "message::eos", (GCallback) eos_cb, state); + g_signal_connect (G_OBJECT (bus), "message::state-changed", + (GCallback) state_changed_cb, state); + gst_object_unref (bus); + + /* Make player start playing */ + gst_element_set_state (state->pipeline, GST_STATE_PLAYING); + + /* Start the mainloop */ + state->main_loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (state->main_loop); + +done: + /* Release pipeline */ + if (state->pipeline) { + gst_element_set_state (state->pipeline, GST_STATE_NULL); + gst_object_unref (state->pipeline); + } + + /* Release intercom */ + if (state->queue) { + g_async_queue_unref (state->queue); + } + + if (state->lock) { + g_mutex_free (state->lock); + } + + if (state->cond) { + g_cond_free (state->cond); + } + + /* Unref the mainloop */ + if (state->main_loop) { + g_main_loop_unref (state->main_loop); + } + + close_ogl (); + + TRACE_VC_MEMORY ("at exit"); + return 0; +}