diff --git a/CMakeLists.txt b/CMakeLists.txt index e9aed26..0a214d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,12 +20,12 @@ add_library(gstprojectm SHARED src/debug.c src/config.h src/enums.h - src/gl.h - src/gl.c src/plugin.h src/plugin.c src/projectm.h src/projectm.c + src/gstglbaseaudiovisualizer.h + src/gstglbaseaudiovisualizer.c ) target_include_directories(gstprojectm @@ -50,6 +50,14 @@ message(STATUS "GLIB2_INCLUDE_DIR: ${GLIB2_INCLUDE_DIR}") message(STATUS "GLIB2_LIBRARIES: ${GLIB2_LIBRARIES}") message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}") +# GLEW needs to be initialized if libprojectM depends on it. +if(TARGET GLEW::glew OR TARGET GLEW::glew_s) + target_compile_definitions(gstprojectm + PRIVATE + USE_GLEW + ) +endif() + target_link_libraries(gstprojectm PRIVATE libprojectM::projectM diff --git a/src/caps.c b/src/caps.c index b1be6e0..8001ead 100644 --- a/src/caps.c +++ b/src/caps.c @@ -42,7 +42,7 @@ const gchar *get_video_src_cap(unsigned int type) switch (type) { case 0: - format = GST_VIDEO_CAPS_MAKE("video/x-raw, format = (string) { RGBA, BGRA }, framerate=(fraction)[0/1,MAX]"); + format = GST_VIDEO_CAPS_MAKE("video/x-raw, format = (string) { ABGR }, framerate=(fraction)[0/1,MAX]"); break; default: format = NULL; diff --git a/src/gl.c b/src/gl.c deleted file mode 100644 index 9b0b8cf..0000000 --- a/src/gl.c +++ /dev/null @@ -1,157 +0,0 @@ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include - -#include "gl.h" -#include "debug.h" -#include "plugin.h" - -GST_DEBUG_CATEGORY_STATIC(gstprojectm_gl_debug); -#define GST_CAT_DEFAULT gstprojectm_gl_debug - -void gl_init(GstProjectM *plugin) -{ - GST_DEBUG_CATEGORY_INIT(gstprojectm_gl_debug, "projectm", - 0, "OpenGL"); - - // Error object - GError *error = NULL; - - // Check if there is another GL context active - GstGLContext *other = gst_gl_context_get_current(); - if (other) - return; - - // Create GL display - GstGLDisplay *display = gst_gl_display_new_with_type(GST_GL_DISPLAY_TYPE_ANY); - - // Check if display was created - if (!display) - { - GST_ERROR_OBJECT(plugin, "Failed to create OpenGL display"); - return; - } - - // Create GL context - GstGLContext *context = gst_gl_context_new(display); - gboolean created_context = gst_gl_context_create(context, NULL, &error); - - // Check if context was created - if (!created_context) - { - GST_ERROR_OBJECT(plugin, "Failed to create OpenGL context"); - gl_error_handler(context, plugin); - return; - } - - // Get GL window - GstGLWindow *window; - window = gst_gl_context_get_window(context); - - // Get GL Info - GstGLAPI api = gst_gl_context_get_gl_api(context); - gint major, minor; - gst_gl_context_get_gl_version(context, &major, &minor); - GstGLSLVersion glsl_version = gst_gl_version_to_glsl_version(api, major, minor); - const gchar *glsl_version_string = gst_glsl_version_to_string(glsl_version); - GstGLSLProfile profile = gst_glsl_profile_from_string(glsl_version_string); - - GST_DEBUG_OBJECT(plugin, "GL API: %d", api); - GST_DEBUG_OBJECT(plugin, "GL version: %d.%d", major, minor); - GST_DEBUG_OBJECT(plugin, "GLSL version: %d", glsl_version); - GST_DEBUG_OBJECT(plugin, "GLSL profile: %d", profile); - - // Check whether context supports the requested version - if (!gst_gl_context_check_gl_version(context, api, major, minor)) - { - GST_ERROR_OBJECT(plugin, "OpenGL context does not support the requested version"); - gl_error_handler(context, plugin); - } - else - { - GST_DEBUG_OBJECT(plugin, "OpenGL context supports the requested version: %d.%d", major, minor); - } - - // Check whether context supports the combination of version with profile - if (!gst_gl_context_supports_glsl_profile_version(context, glsl_version, profile)) - { - GST_ERROR_OBJECT(plugin, "OpenGL context does not support the combination of version with profile"); - gl_error_handler(context, plugin); - } - else - { - GST_DEBUG_OBJECT(plugin, "OpenGL context supports the combination of version with profile"); - } - - // Check whether context supports the 'precision' specifier in GLSL shaders - - if (!gst_gl_context_supports_precision(context, glsl_version, profile)) - { - GST_WARNING_OBJECT(plugin, "OpenGL context does not support the 'precision' specifier in GLSL shaders"); - gl_error_handler(context, plugin); - } - else - { - GST_DEBUG_OBJECT(plugin, "OpenGL context supports the 'precision' specifier in GLSL shaders"); - } - - // Check whether context supports the 'precision highp' specifier in GLSL shaders - if (!gst_gl_context_supports_precision_highp(context, glsl_version, profile)) - { - GST_WARNING_OBJECT(plugin, "OpenGL context does not support the 'precision highp' specifier in GLSL shaders"); - gl_error_handler(context, plugin); - } - else - { - GST_DEBUG_OBJECT(plugin, "OpenGL context supports the 'precision highp' specifier in GLSL shaders"); - } - - // Get information from the audio visualizer - GstAudioVisualizer *scope = GST_AUDIO_VISUALIZER(plugin); - plugin->fps = scope->vinfo.fps_n; - plugin->window_width = scope->vinfo.width; - plugin->window_height = scope->vinfo.height; - - // TODO: Needs to be research to resize to the window, currently doesnt work right. - // Set preferred size and render rectangle - gst_gl_window_set_preferred_size(window, plugin->window_width, plugin->window_height); - gst_gl_window_set_render_rectangle(window, 0, 0, plugin->window_width, plugin->window_height); - gst_gl_window_resize(window, plugin->window_width, plugin->window_height); - - // Activate GL context - if (!gst_gl_context_activate(context, true)) - { - GST_ERROR_OBJECT(plugin, "Failed to create OpenGL context"); - gl_error_handler(context, plugin); - return; - } - - // Create GL framebuffer - GstGLFramebuffer *framebuffer = gst_gl_framebuffer_new(context); - - // Check if framebuffer was created - if (!gst_gl_context_check_framebuffer_status(context, GL_FRAMEBUFFER)) - { - GST_ERROR_OBJECT(plugin, "Failed to create OpenGL framebuffer"); - gl_error_handler(context, plugin); - return; - } - - // Initialize GL memory - gst_gl_memory_init_once(); - - // Set GL window for the context - gst_gl_context_set_window(context, window); - - // Get current GL context - guint curr_con = gst_gl_context_get_current_gl_context(GST_GL_PLATFORM_ANY); - GST_DEBUG_OBJECT(plugin, "OpenGL Current Context: %d\n", curr_con); - - // Set display, context, and window on the plugin object - plugin->display = display; - plugin->context = context; - plugin->window = window; -} \ No newline at end of file diff --git a/src/gl.h b/src/gl.h deleted file mode 100644 index f846cc2..0000000 --- a/src/gl.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef __GST_PROJECTM_GL_H__ -#define __GST_PROJECTM_GL_H__ - -#include -#include - -#include "plugin.h" - -G_BEGIN_DECLS - -/** - * @brief Initialize OpenGL - */ -void gl_init(GstProjectM *plugin); - -G_END_DECLS - -#endif /* __GST_PROJECTM_GL_H__ */ \ No newline at end of file diff --git a/src/gstglbaseaudiovisualizer.c b/src/gstglbaseaudiovisualizer.c new file mode 100644 index 0000000..e88acc8 --- /dev/null +++ b/src/gstglbaseaudiovisualizer.c @@ -0,0 +1,577 @@ +/* + * GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) 2002,2007 David A. Schleef + * Copyright (C) 2008 Julien Isorce + * Copyright (C) 2015 Matthew Waters + * Copyright (C) 2019 Philippe Normand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* + * The code in this file is based on code from + * GStreamer / gst-plugins-base / 1.19.2: gst-libs/gst/gl/gstglbasesrc.c + * Git Repository: https://github.com/GStreamer/gst-plugins-base/blob/master/gst-libs/gst/gl/gstglbasesrc.c + * Original copyright notice has been retained at the top of this file. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gstglbaseaudiovisualizer.h" + +/** + * SECTION:GstGLBaseAudioVisualizer + * @short_description: #GstAudioVisualizer subclass for injecting OpenGL resources in a pipeline + * @title: GstGLBaseAudioVisualizer + * @see_also: #GstAudioVisualizer + * + * Wrapper for GstAudioVisualizer for handling OpenGL contexts. + * + * #GstGLBaseAudioVisualizer handles the nitty gritty details of retrieving an OpenGL + * context. It also provides `gl_start()` and `gl_stop()` virtual methods + * that ensure an OpenGL context is available and current in the calling thread for initializing and cleaning up + * OpenGL dependent resources. + * The `gl_render` virtual method is used to perform OpenGL rendering. + */ + +#define GST_CAT_DEFAULT gst_gl_base_audio_visualizer_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _GstGLBaseAudioVisualizerPrivate +{ + GstGLContext *other_context; + + gint64 n_frames; /* total frames sent */ + gboolean gl_result; + gboolean gl_started; + + GRecMutex context_lock; +}; + +/* Properties */ +enum +{ + PROP_0 +}; + +#define gst_gl_base_audio_visualizer_parent_class parent_class +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstGLBaseAudioVisualizer, gst_gl_base_audio_visualizer, + GST_TYPE_AUDIO_VISUALIZER, G_ADD_PRIVATE (GstGLBaseAudioVisualizer) + GST_DEBUG_CATEGORY_INIT (gst_gl_base_audio_visualizer_debug, + "glbaseaudiovisualizer", 0, "glbaseaudiovisualizer element"); +); + +static void gst_gl_base_audio_visualizer_finalize (GObject * object); +static void gst_gl_base_audio_visualizer_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_gl_base_audio_visualizer_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static void gst_gl_base_audio_visualizer_set_context (GstElement * element, + GstContext * context); +static GstStateChangeReturn gst_gl_base_audio_visualizer_change_state (GstElement * element, + GstStateChange transition); + +static gboolean gst_gl_base_audio_visualizer_render (GstAudioVisualizer *bscope, GstBuffer *audio, GstVideoFrame *video); +static void gst_gl_base_audio_visualizer_start (GstGLBaseAudioVisualizer * glav); +static void gst_gl_base_audio_visualizer_stop (GstGLBaseAudioVisualizer * glav); +static gboolean gst_gl_base_audio_visualizer_decide_allocation (GstAudioVisualizer * gstav, + GstQuery * query); + +static gboolean gst_gl_base_audio_visualizer_default_setup (GstGLBaseAudioVisualizer * glav); +static gboolean gst_gl_base_audio_visualizer_default_gl_start (GstGLBaseAudioVisualizer * glav); +static void gst_gl_base_audio_visualizer_default_gl_stop (GstGLBaseAudioVisualizer * glav); +static gboolean gst_gl_base_audio_visualizer_default_gl_render (GstGLBaseAudioVisualizer * glav, + GstBuffer* audio, GstVideoFrame *video); + +static gboolean gst_gl_base_audio_visualizer_find_gl_context_unlocked (GstGLBaseAudioVisualizer * glav); + +static gboolean gst_gl_base_audio_visualizer_setup(GstAudioVisualizer * gstav); + +static void +gst_gl_base_audio_visualizer_class_init (GstGLBaseAudioVisualizerClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstAudioVisualizerClass *gstav_class = GST_AUDIO_VISUALIZER_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + gobject_class->finalize = gst_gl_base_audio_visualizer_finalize; + gobject_class->set_property = gst_gl_base_audio_visualizer_set_property; + gobject_class->get_property = gst_gl_base_audio_visualizer_get_property; + + element_class->set_context = GST_DEBUG_FUNCPTR (gst_gl_base_audio_visualizer_set_context); + + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_gl_base_audio_visualizer_change_state); + + gstav_class->decide_allocation = GST_DEBUG_FUNCPTR (gst_gl_base_audio_visualizer_decide_allocation); + gstav_class->setup = GST_DEBUG_FUNCPTR (gst_gl_base_audio_visualizer_setup); + + gstav_class->render = GST_DEBUG_FUNCPTR (gst_gl_base_audio_visualizer_render); + + klass->supported_gl_api = GST_GL_API_ANY; + klass->gl_start = GST_DEBUG_FUNCPTR (gst_gl_base_audio_visualizer_default_gl_start); + klass->gl_stop = GST_DEBUG_FUNCPTR (gst_gl_base_audio_visualizer_default_gl_stop); + klass->gl_render = GST_DEBUG_FUNCPTR (gst_gl_base_audio_visualizer_default_gl_render); + klass->setup = GST_DEBUG_FUNCPTR (gst_gl_base_audio_visualizer_default_setup); +} + +static void +gst_gl_base_audio_visualizer_init (GstGLBaseAudioVisualizer * glav) +{ + glav->priv = gst_gl_base_audio_visualizer_get_instance_private (glav); + glav->priv->gl_started = FALSE; + glav->priv->gl_result = TRUE; + glav->context = NULL; + g_rec_mutex_init (&glav->priv->context_lock); + gst_gl_base_audio_visualizer_start(glav); +} + +static void +gst_gl_base_audio_visualizer_finalize (GObject * object) +{ + GstGLBaseAudioVisualizer *glav = GST_GL_BASE_AUDIO_VISUALIZER (object); + gst_gl_base_audio_visualizer_stop(glav); + + g_rec_mutex_clear (&glav->priv->context_lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_gl_base_audio_visualizer_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstGLBaseAudioVisualizer *glav = GST_GL_BASE_AUDIO_VISUALIZER (object); + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_gl_base_audio_visualizer_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstGLBaseAudioVisualizer *glav = GST_GL_BASE_AUDIO_VISUALIZER (object); + + switch (prop_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static void +gst_gl_base_audio_visualizer_set_context (GstElement * element, GstContext * context) +{ + GstGLBaseAudioVisualizer *glav = GST_GL_BASE_AUDIO_VISUALIZER (element); + GstGLBaseAudioVisualizerClass *klass = GST_GL_BASE_AUDIO_VISUALIZER_GET_CLASS (glav); + GstGLDisplay *old_display, *new_display; + + g_rec_mutex_lock (&glav->priv->context_lock); + old_display = glav->display ? gst_object_ref (glav->display) : NULL; + gst_gl_handle_set_context (element, context, &glav->display, + &glav->priv->other_context); + if (glav->display) + gst_gl_display_filter_gl_api (glav->display, klass->supported_gl_api); + new_display = glav->display ? gst_object_ref (glav->display) : NULL; + + if (old_display && new_display) { + if (old_display != new_display) { + gst_clear_object (&glav->context); + if (gst_gl_base_audio_visualizer_find_gl_context_unlocked (glav)) { + // TODO does this need to be handled ? + //gst_pad_mark_reconfigure (GST_BASE_SRC_PAD (glav)); + } + } + } + gst_clear_object (&old_display); + gst_clear_object (&new_display); + g_rec_mutex_unlock (&glav->priv->context_lock); + + GST_ELEMENT_CLASS (parent_class)->set_context (element, context); +} + +static gboolean +gst_gl_base_audio_visualizer_default_gl_start (GstGLBaseAudioVisualizer * glav) +{ + return TRUE; +} + +static gboolean +gst_gl_base_audio_visualizer_default_setup (GstGLBaseAudioVisualizer * glav) +{ + return TRUE; +} + +static void +gst_gl_base_audio_visualizer_gl_start (GstGLContext * context, gpointer data) +{ + GstGLBaseAudioVisualizer *glav = GST_GL_BASE_AUDIO_VISUALIZER (data); + GstGLBaseAudioVisualizerClass *glav_class = GST_GL_BASE_AUDIO_VISUALIZER_GET_CLASS (glav); + + GST_INFO_OBJECT (glav, "starting"); + gst_gl_insert_debug_marker (glav->context, + "starting element %s", GST_OBJECT_NAME (glav)); + + glav->priv->gl_started = glav_class->gl_start (glav); +} + +static void +gst_gl_base_audio_visualizer_default_gl_stop (GstGLBaseAudioVisualizer * glav) +{ +} + +static void +gst_gl_base_audio_visualizer_gl_stop (GstGLContext * context, gpointer data) +{ + GstGLBaseAudioVisualizer *glav = GST_GL_BASE_AUDIO_VISUALIZER (data); + GstGLBaseAudioVisualizerClass *glav_class = GST_GL_BASE_AUDIO_VISUALIZER_GET_CLASS (glav); + + GST_INFO_OBJECT (glav, "stopping"); + gst_gl_insert_debug_marker (glav->context, + "stopping element %s", GST_OBJECT_NAME (glav)); + + if (glav->priv->gl_started) + glav_class->gl_stop (glav); + + glav->priv->gl_started = FALSE; +} + +static gboolean gst_gl_base_audio_visualizer_setup(GstAudioVisualizer * gstav) { + GstGLBaseAudioVisualizer *glav = GST_GL_BASE_AUDIO_VISUALIZER (gstav); + GstGLBaseAudioVisualizerClass *glav_class = GST_GL_BASE_AUDIO_VISUALIZER_GET_CLASS(gstav); + + // cascade setup to the derived plugin after gl initialization has been completed + return glav_class->setup(glav); +} + +static gboolean +gst_gl_base_audio_visualizer_default_gl_render (GstGLBaseAudioVisualizer * glav, GstBuffer* audio, GstVideoFrame *video) +{ + return TRUE; +} + +typedef struct { + GstGLBaseAudioVisualizer *glav; + GstBuffer *in_audio; + GstVideoFrame *out_video; +} GstGLRenderCallbackParams; + +static void +gst_gl_base_audio_visualizer_gl_thread_render_callback (gpointer params) +{ + GstGLRenderCallbackParams* cb_params = (GstGLRenderCallbackParams*)params; + GstGLBaseAudioVisualizerClass *klass = GST_GL_BASE_AUDIO_VISUALIZER_GET_CLASS (cb_params->glav); + + // inside gl thread: call virtual render function with audio and video + cb_params->glav->priv->gl_result = klass->gl_render (cb_params->glav, cb_params->in_audio, cb_params->out_video); +} + +static gboolean +gst_gl_base_audio_visualizer_render (GstAudioVisualizer *bscope, GstBuffer *audio, GstVideoFrame *video) +{ + GstGLBaseAudioVisualizer *glav = GST_GL_BASE_AUDIO_VISUALIZER (bscope); + GstGLRenderCallbackParams cb_params; + GstGLWindow *window; + + g_rec_mutex_lock (&glav->priv->context_lock); + + // wrap params into cb_params struct to pass them to the GL window/thread via userdata pointer + cb_params.glav = glav; + cb_params.in_audio = audio; + cb_params.out_video = video; + + window = gst_gl_context_get_window (glav->context); + + // dispatch render call through the gl thread + // call is blocking, accessing audio and video params from gl thread *should* be safe + gst_gl_window_send_message(window, GST_GL_WINDOW_CB(gst_gl_base_audio_visualizer_gl_thread_render_callback), + &cb_params); + + gst_object_unref(window); + + g_rec_mutex_unlock (&glav->priv->context_lock); + + if (glav->priv->gl_result) { + glav->priv->n_frames++; + } else { + // gl error + GST_ELEMENT_ERROR (glav, RESOURCE, NOT_FOUND, (("failed to render audio visualizer")), + (("A GL error occurred"))); + } + + return glav->priv->gl_result; +} + +static void +gst_gl_base_audio_visualizer_start (GstGLBaseAudioVisualizer * glav) +{ + glav->priv->n_frames = 0; +} + +static void +gst_gl_base_audio_visualizer_stop (GstGLBaseAudioVisualizer * glav) +{ + g_rec_mutex_lock (&glav->priv->context_lock); + + if (glav->context) { + if (glav->priv->gl_started) + gst_gl_context_thread_add (glav->context, gst_gl_base_audio_visualizer_gl_stop, glav); + + gst_object_unref (glav->context); + } + + glav->context = NULL; + g_rec_mutex_unlock (&glav->priv->context_lock); +} + +static gboolean +_find_local_gl_context_unlocked (GstGLBaseAudioVisualizer * glav) +{ + GstGLContext *context, *prev_context; + gboolean ret; + + if (glav->context && glav->context->display == glav->display) + return TRUE; + + context = prev_context = glav->context; + g_rec_mutex_unlock (&glav->priv->context_lock); + /* we need to drop the lock to query as another element may also be + * performing a context query on us which would also attempt to take the + * context_lock. Our query could block on the same lock in the other element. + */ + ret = + gst_gl_query_local_gl_context (GST_ELEMENT (glav), GST_PAD_SRC, &context); + g_rec_mutex_lock (&glav->priv->context_lock); + if (ret) { + if (glav->context != prev_context) { + /* we need to recheck everything since we dropped the lock and the + * context has changed */ + if (glav->context && glav->context->display == glav->display) { + if (context != glav->context) + gst_clear_object (&context); + return TRUE; + } + } + + if (context->display == glav->display) { + glav->context = context; + return TRUE; + } + if (context != glav->context) + gst_clear_object (&context); + } + return FALSE; +} + +static gboolean +gst_gl_base_audio_visualizer_find_gl_context_unlocked (GstGLBaseAudioVisualizer * glav) +{ + GstGLBaseAudioVisualizerClass *klass = GST_GL_BASE_AUDIO_VISUALIZER_GET_CLASS (glav); + GError *error = NULL; + gboolean new_context = FALSE; + + GST_DEBUG_OBJECT (glav, "attempting to find an OpenGL context, existing %" + GST_PTR_FORMAT, glav->context); + + if (!glav->context) + new_context = TRUE; + + if (!gst_gl_ensure_element_data (glav, &glav->display, + &glav->priv->other_context)) + return FALSE; + + gst_gl_display_filter_gl_api (glav->display, klass->supported_gl_api); + + _find_local_gl_context_unlocked (glav); + + if (!glav->context) { + GST_OBJECT_LOCK (glav->display); + do { + if (glav->context) { + gst_object_unref (glav->context); + glav->context = NULL; + } + /* just get a GL context. we don't care */ + glav->context = + gst_gl_display_get_gl_context_for_thread (glav->display, NULL); + if (!glav->context) { + if (!gst_gl_display_create_context (glav->display, + glav->priv->other_context, &glav->context, &error)) { + GST_OBJECT_UNLOCK (glav->display); + goto context_error; + } + } + } while (!gst_gl_display_add_context (glav->display, glav->context)); + GST_OBJECT_UNLOCK (glav->display); + } + GST_INFO_OBJECT (glav, "found OpenGL context %" GST_PTR_FORMAT, glav->context); + + if (new_context || !glav->priv->gl_started) { + if (glav->priv->gl_started) + gst_gl_context_thread_add (glav->context, gst_gl_base_audio_visualizer_gl_stop, glav); + + { + if ((gst_gl_context_get_gl_api (glav-> + context) & klass->supported_gl_api) == 0) + goto unsupported_gl_api; + } + + gst_gl_context_thread_add (glav->context, gst_gl_base_audio_visualizer_gl_start, glav); + + if (!glav->priv->gl_started) + goto error; + } + + return TRUE; + + unsupported_gl_api: + { + GstGLAPI gl_api = gst_gl_context_get_gl_api (glav->context); + gchar *gl_api_str = gst_gl_api_to_string (gl_api); + gchar *supported_gl_api_str = + gst_gl_api_to_string (klass->supported_gl_api); + GST_ELEMENT_ERROR (glav, RESOURCE, BUSY, + ("GL API's not compatible context: %s supported: %s", gl_api_str, + supported_gl_api_str), (NULL)); + + g_free (supported_gl_api_str); + g_free (gl_api_str); + return FALSE; + } + context_error: + { + if (error) { + GST_ELEMENT_ERROR (glav, RESOURCE, NOT_FOUND, ("%s", error->message), + (NULL)); + g_clear_error (&error); + } else { + GST_ELEMENT_ERROR (glav, RESOURCE, NOT_FOUND, (NULL), (NULL)); + } + if (glav->context) + gst_object_unref (glav->context); + glav->context = NULL; + return FALSE; + } + error: + { + GST_ELEMENT_ERROR (glav, LIBRARY, INIT, + ("Subclass failed to initialize."), (NULL)); + return FALSE; + } +} + +static gboolean +gst_gl_base_audio_visualizer_decide_allocation (GstAudioVisualizer * gstav, GstQuery * query) +{ + GstGLBaseAudioVisualizer *glav = GST_GL_BASE_AUDIO_VISUALIZER (gstav); + GstGLContext *context; + GstBufferPool *pool = NULL; + GstStructure *config; + GstCaps *caps; + guint min, max, size; + gboolean update_pool; + + g_rec_mutex_lock (&glav->priv->context_lock); + if (!gst_gl_base_audio_visualizer_find_gl_context_unlocked (glav)) { + g_rec_mutex_unlock (&glav->priv->context_lock); + return FALSE; + } + context = gst_object_ref (glav->context); + g_rec_mutex_unlock (&glav->priv->context_lock); + + gst_query_parse_allocation (query, &caps, NULL); + + if (gst_query_get_n_allocation_pools (query) > 0) { + gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); + + update_pool = TRUE; + } else { + GstVideoInfo vinfo; + + gst_video_info_init (&vinfo); + gst_video_info_from_caps (&vinfo, caps); + size = vinfo.size; + min = max = 0; + update_pool = FALSE; + } + + if (!pool || !GST_IS_GL_BUFFER_POOL (pool)) { + /* can't use this pool */ + if (pool) + gst_object_unref (pool); + pool = gst_gl_buffer_pool_new (context); + } + config = gst_buffer_pool_get_config (pool); + + gst_buffer_pool_config_set_params (config, caps, size, min, max); + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); + if (gst_query_find_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, NULL)) + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_GL_SYNC_META); + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_GL_TEXTURE_UPLOAD_META); + + gst_buffer_pool_set_config (pool, config); + + if (update_pool) + gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max); + else + gst_query_add_allocation_pool (query, pool, size, min, max); + + gst_object_unref (pool); + gst_object_unref (context); + + return TRUE; +} + +static GstStateChangeReturn +gst_gl_base_audio_visualizer_change_state (GstElement * element, GstStateChange transition) +{ + GstGLBaseAudioVisualizer *glav = GST_GL_BASE_AUDIO_VISUALIZER (element); + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + + GST_DEBUG_OBJECT (glav, "changing state: %s => %s", + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_NULL: + g_rec_mutex_lock (&glav->priv->context_lock); + gst_clear_object (&glav->priv->other_context); + gst_clear_object (&glav->display); + g_rec_mutex_unlock (&glav->priv->context_lock); + break; + default: + break; + } + + return ret; +} + diff --git a/src/gstglbaseaudiovisualizer.h b/src/gstglbaseaudiovisualizer.h new file mode 100644 index 0000000..89df80a --- /dev/null +++ b/src/gstglbaseaudiovisualizer.h @@ -0,0 +1,104 @@ +/* + * GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) 2002,2007 David A. Schleef + * Copyright (C) 2008 Julien Isorce + * Copyright (C) 2019 Philippe Normand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/* + * The code in this file is based on code from + * GStreamer / gst-plugins-base / 1.19.2: gst-libs/gst/gl/gstglbasesrc.h + * Git Repository: https://github.com/GStreamer/gst-plugins-base/blob/master/gst-libs/gst/gl/gstglbasesrc.h + * Original copyright notice has been retained at the top of this file. + */ + +#ifndef __GST_GL_BASE_AUDIO_VISUALIZER_H__ +#define __GST_GL_BASE_AUDIO_VISUALIZER_H__ + +#include +#include +#include +#include + +typedef struct _GstGLBaseAudioVisualizer GstGLBaseAudioVisualizer; +typedef struct _GstGLBaseAudioVisualizerClass GstGLBaseAudioVisualizerClass; +typedef struct _GstGLBaseAudioVisualizerPrivate GstGLBaseAudioVisualizerPrivate; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstGLBaseAudioVisualizer , gst_object_unref) + +G_BEGIN_DECLS + +GST_GL_API +GType gst_gl_base_audio_visualizer_get_type(void); + + +#define GST_TYPE_GL_BASE_AUDIO_VISUALIZER (gst_gl_base_audio_visualizer_get_type()) +#define GST_GL_BASE_AUDIO_VISUALIZER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_GL_BASE_AUDIO_VISUALIZER,GstGLBaseAudioVisualizer)) +#define GST_GL_BASE_AUDIO_VISUALIZER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_GL_BASE_AUDIO_VISUALIZER,GstGLBaseAudioVisualizerClass)) +#define GST_IS_GL_BASE_AUDIO_VISUALIZER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_GL_BASE_AUDIO_VISUALIZER)) +#define GST_IS_GL_BASE_AUDIO_VISUALIZER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_GL_BASE_AUDIO_VISUALIZER)) +#define GST_GL_BASE_AUDIO_VISUALIZER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj) ,GST_TYPE_GL_BASE_AUDIO_VISUALIZER,GstGLBaseAudioVisualizerClass)) + +/** + * GstGLBaseAudioVisualizer: + * @display: the currently configured #GstGLDisplay + * @context: the currently configured #GstGLContext + * + * The parent instance type of a base GL Audio Visualizer. + */ +struct _GstGLBaseAudioVisualizer { + GstAudioVisualizer parent; + + /*< public >*/ + GstGLDisplay *display; + GstGLContext *context; + + /*< private >*/ + gpointer _padding[GST_PADDING]; + + GstGLBaseAudioVisualizerPrivate *priv; +}; + +/** + * GstGLBaseAudioVisualizerClass: + * @supported_gl_api: the logical-OR of #GstGLAPI's supported by this element + * @gl_start: called in the GL thread to setup the element GL state. + * @gl_stop: called in the GL thread to clean up the element GL state. + * @gl_render: called in the GL thread to fill the current video texture. + * @setup: called when the format changes (delegate from GstAudioVisualizer.setup) + * + * The base class for OpenGL based audio visualizers. + * + */ +struct _GstGLBaseAudioVisualizerClass { + GstAudioVisualizerClass parent_class; + + /*< public >*/ + GstGLAPI supported_gl_api; + gboolean (*gl_start) (GstGLBaseAudioVisualizer *glav); + void (*gl_stop) (GstGLBaseAudioVisualizer *glav); + gboolean (*gl_render) (GstGLBaseAudioVisualizer *glav, GstBuffer* audio, GstVideoFrame *video); + gboolean (*setup) (GstGLBaseAudioVisualizer *glav); + /*< private >*/ + gpointer _padding[GST_PADDING]; +}; + +G_END_DECLS + +#endif /* __GST_GL_BASE_AUDIO_VISUALIZER_H__ */ diff --git a/src/plugin.c b/src/plugin.c index 7eb7386..4615f54 100644 --- a/src/plugin.c +++ b/src/plugin.c @@ -2,8 +2,10 @@ #include "config.h" #endif +#ifdef USE_GLEW +#include +#endif #include -#include #include #include @@ -14,13 +16,19 @@ #include "config.h" #include "debug.h" #include "enums.h" -#include "gl.h" #include "projectm.h" +#include "gstglbaseaudiovisualizer.h" GST_DEBUG_CATEGORY_STATIC(gst_projectm_debug); #define GST_CAT_DEFAULT gst_projectm_debug -G_DEFINE_TYPE_WITH_CODE(GstProjectM, gst_projectm, GST_TYPE_AUDIO_VISUALIZER, +struct _GstProjectMPrivate +{ + GLenum gl_format; + projectm_handle handle; +}; + +G_DEFINE_TYPE_WITH_CODE(GstProjectM, gst_projectm, GST_TYPE_GL_BASE_AUDIO_VISUALIZER, G_ADD_PRIVATE (GstProjectM) GST_DEBUG_CATEGORY_INIT(gst_projectm_debug, "gstprojectm", 0, "Plugin Root")); @@ -151,6 +159,8 @@ void gst_projectm_get_property(GObject *object, guint property_id, static void gst_projectm_init(GstProjectM *plugin) { + plugin->priv = gst_projectm_get_instance_private (plugin); + // Set default values for properties plugin->preset_path = DEFAULT_PRESET_PATH; plugin->texture_dir_path = DEFAULT_TEXTURE_DIR_PATH; @@ -180,142 +190,139 @@ static void gst_projectm_init(GstProjectM *plugin) plugin->aspect_correction = DEFAULT_ASPECT_CORRECTION; plugin->easter_egg = DEFAULT_EASTER_EGG; plugin->preset_locked = DEFAULT_PRESET_LOCKED; + plugin->priv->handle = NULL; } static void gst_projectm_finalize(GObject *object) { GstProjectM *plugin = GST_PROJECTM(object); + g_free(plugin->preset_path); + g_free(plugin->texture_dir_path); + G_OBJECT_CLASS(gst_projectm_parent_class)->finalize(object); +} - if (plugin->framebuffer) +static void gst_projectm_gl_stop(GstGLBaseAudioVisualizer *src) { + GstProjectM *plugin = GST_PROJECTM(src); + if (plugin->priv->handle) { - GST_DEBUG_OBJECT(plugin, "Freeing framebuffer"); - free(plugin->framebuffer); + GST_DEBUG_OBJECT(plugin, "Destroying ProjectM instance"); + projectm_destroy(plugin->priv->handle); + plugin->priv->handle = NULL; } - if (plugin->handle) - { - GST_DEBUG_OBJECT(plugin, "Destroying ProjectM instance"); - projectm_destroy(plugin->handle); - } - - G_OBJECT_CLASS(gst_projectm_parent_class)->finalize(object); } -static gboolean gst_projectm_setup(GstAudioVisualizer *bscope) +static gboolean gst_projectm_gl_start(GstGLBaseAudioVisualizer *glav) { // Cast the audio visualizer to the ProjectM plugin - GstProjectM *plugin = GST_PROJECTM(bscope); - - // Check if GL context, window, and display exist, and create if not - if (!plugin->display || !plugin->context || !plugin->window) - { - gl_init(plugin); + GstProjectM *plugin = GST_PROJECTM(glav); + +#ifdef USE_GLEW + GST_DEBUG_OBJECT(plugin, "Initializing GLEW"); + GLenum err = glewInit(); + if (GLEW_OK != err) { + GST_ERROR_OBJECT(plugin, "GLEW initialization failed"); + return FALSE; } +#endif // Check if ProjectM instance exists, and create if not - if (!plugin->handle) + if (!plugin->priv->handle) { // Create ProjectM instance - projectm_init(plugin); + plugin->priv->handle = projectm_init(plugin); + if (!plugin->priv->handle) { + GST_ERROR_OBJECT(plugin, "ProjectM could not be initialized"); + return FALSE; + } + gl_error_handler(glav->context, plugin); + } - // Calculate depth based on pixel stride and bits - gint depth = bscope->vinfo.finfo->pixel_stride[0] * ((bscope->vinfo.finfo->bits >= 8) ? 8 : 1); + return TRUE; +} - // Calculate required samples per frame - bscope->req_spf = (bscope->ainfo.channels * bscope->ainfo.rate * 2) / bscope->vinfo.fps_n; +static gboolean gst_projectm_setup(GstGLBaseAudioVisualizer *glav) { + GstAudioVisualizer *bscope = GST_AUDIO_VISUALIZER(glav); + GstProjectM *plugin = GST_PROJECTM(glav); - // Allocate memory for the framebuffer - plugin->framebuffer = (uint8_t *)malloc(GST_VIDEO_INFO_WIDTH(&bscope->vinfo) * GST_VIDEO_INFO_HEIGHT(&bscope->vinfo) * 4); + // Calculate depth based on pixel stride and bits + gint depth = bscope->vinfo.finfo->pixel_stride[0] * ((bscope->vinfo.finfo->bits >= 8) ? 8 : 1); + + // Calculate required samples per frame + bscope->req_spf = (bscope->ainfo.channels * bscope->ainfo.rate * 2) / bscope->vinfo.fps_n; - if (plugin->framebuffer == NULL) - { - GST_ERROR_OBJECT(plugin, "Failed to allocate memory for framebuffer"); - return FALSE; - } - // Log audio info - GST_DEBUG_OBJECT(plugin, - "Audio Information ", - bscope->ainfo.channels, bscope->ainfo.rate, bscope->ainfo.finfo->description); - - // Log video info - GST_DEBUG_OBJECT(plugin, - "Video Information ", - GST_VIDEO_INFO_WIDTH(&bscope->vinfo), - GST_VIDEO_INFO_HEIGHT(&bscope->vinfo), - bscope->vinfo.fps_n, bscope->vinfo.fps_d, - depth, bscope->req_spf); + // get GStreamer video format and map it to the corresponding OpenGL pixel format + const GstVideoFormat video_format = GST_VIDEO_INFO_FORMAT(&bscope->vinfo); + + // TODO: why is the reversed byte order needed when copying pixel data from OpenGL ? + switch (video_format) { + case GST_VIDEO_FORMAT_ABGR: + plugin->priv->gl_format = GL_RGBA; + break; + + case GST_VIDEO_FORMAT_RGBA: + // GL_ABGR_EXT does not seem to be well-supported, does not work on Windows + plugin->priv->gl_format = GL_ABGR_EXT; + break; + + default: + GST_ERROR_OBJECT(plugin, "Unsupported video format: %d", video_format); + return FALSE; } + // Log audio info + GST_DEBUG_OBJECT(glav, + "Audio Information ", + bscope->ainfo.channels, bscope->ainfo.rate, bscope->ainfo.finfo->description); + + // Log video info + GST_DEBUG_OBJECT(glav, + "Video Information ", + GST_VIDEO_INFO_WIDTH(&bscope->vinfo), + GST_VIDEO_INFO_HEIGHT(&bscope->vinfo), + bscope->vinfo.fps_n, bscope->vinfo.fps_d, + depth, bscope->req_spf); + return TRUE; } + // TODO: CLEANUP & ADD DEBUGGING -static gboolean gst_projectm_render(GstAudioVisualizer *bscope, GstBuffer *audio, - GstVideoFrame *video) +static gboolean gst_projectm_render(GstGLBaseAudioVisualizer *glav, GstBuffer *audio, GstVideoFrame *video) { - GstProjectM *plugin = GST_PROJECTM(bscope); + GstProjectM *plugin = GST_PROJECTM(glav); + GstMapInfo audioMap; - gint16 *audioData; - gint channels; gboolean result = TRUE; - guint32 audioSampleRate; - guint numSamples; // AUDIO - channels = GST_AUDIO_INFO_CHANNELS(&bscope->ainfo); - numSamples = audioMap.size / (channels * sizeof(gint16)); - - GstMemory *audioMemory = gst_buffer_get_all_memory(audio); - // GST_DEBUG_OBJECT(plugin, "Audio Memory Size: %lu", audioMemory->size); - gst_buffer_map(audio, &audioMap, GST_MAP_READ); // GST_DEBUG_OBJECT(plugin, "Audio Samples: %u, Offset: %lu, Offset End: %lu, Sample Rate: %d, FPS: %d, Required Samples Per Frame: %d", // audioMap.size / 8, audio->offset, audio->offset_end, bscope->ainfo.rate, bscope->vinfo.fps_n, bscope->req_spf); - projectm_pcm_add_int16(plugin->handle, (gint16 *)audioMap.data, audioMap.size / 4, PROJECTM_STEREO); + projectm_pcm_add_int16(plugin->priv->handle, (gint16 *)audioMap.data, audioMap.size / 4, PROJECTM_STEREO); // GST_DEBUG_OBJECT(plugin, "Audio Data: %d %d %d %d", ((gint16 *)audioMap.data)[100], ((gint16 *)audioMap.data)[101], ((gint16 *)audioMap.data)[102], ((gint16 *)audioMap.data)[103]); // VIDEO - gst_video_frame_map(video, &video->info, video->buffer, GST_MAP_READWRITE); - - const GstGLFuncs *glFunctions = plugin->context->gl_vtable; + const GstGLFuncs *glFunctions = glav->context->gl_vtable; size_t windowWidth, windowHeight; - projectm_get_window_size(plugin->handle, &windowWidth, &windowHeight); - glFunctions->Viewport(0, 0, windowWidth, windowHeight); + projectm_get_window_size(plugin->priv->handle, &windowWidth, &windowHeight); - projectm_opengl_render_frame(plugin->handle); - gl_error_handler(plugin->context, plugin); + projectm_opengl_render_frame(plugin->priv->handle); + gl_error_handler(glav->context, plugin); - uint8_t *framebufferData = plugin->framebuffer; + glFunctions->ReadPixels(0, 0, windowWidth, windowHeight, plugin->priv->gl_format, GL_UNSIGNED_INT_8_8_8_8, (guint8 *)GST_VIDEO_FRAME_PLANE_DATA(video, 0)); - glFunctions->ReadPixels(0, 0, windowWidth, windowHeight, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, framebufferData); - // GST_DEBUG_OBJECT(plugin, "Framebuffer Data: %d %d %d %d", framebufferData[0], framebufferData[1], framebufferData[2], framebufferData[3]); - - // Swap buffers - gst_gl_context_swap_buffers(plugin->context); - - uint8_t *videoData = ((uint8_t *)(video->data[0])); - - // Convert RGBA to BGRA - for (int r = 0; r < windowWidth * windowHeight * 4; r += 4) - { - videoData[r + 3] = framebufferData[r]; - videoData[r + 2] = framebufferData[r + 2]; - videoData[r + 1] = framebufferData[r + 1]; - videoData[r] = framebufferData[r + 3]; - } + gst_buffer_unmap(audio, &audioMap); // GST_DEBUG_OBJECT(plugin, "Video Data: %d %d\n", GST_VIDEO_FRAME_N_PLANES(video), ((uint8_t *)(GST_VIDEO_FRAME_PLANE_DATA(video, 0)))[0]); // GST_DEBUG_OBJECT(plugin, "Rendered one frame"); -done: - gst_buffer_unmap(audio, &audioMap); - return result; } @@ -323,7 +330,7 @@ static void gst_projectm_class_init(GstProjectMClass *klass) { GObjectClass *gobject_class = (GObjectClass *)klass; GstElementClass *element_class = (GstElementClass *)klass; - GstAudioVisualizerClass *scope_class = (GstAudioVisualizerClass *)klass; + GstGLBaseAudioVisualizerClass *scope_class = GST_GL_BASE_AUDIO_VISUALIZER_CLASS(klass); // Setup audio and video caps const gchar *audio_sink_caps = get_audio_sink_cap(0); @@ -418,8 +425,11 @@ static void gst_projectm_class_init(GstProjectMClass *klass) gobject_class->finalize = gst_projectm_finalize; + scope_class->supported_gl_api = GST_GL_API_OPENGL3 | GST_GL_API_GLES2; + scope_class->gl_start = GST_DEBUG_FUNCPTR(gst_projectm_gl_start); + scope_class->gl_stop = GST_DEBUG_FUNCPTR(gst_projectm_gl_stop); + scope_class->gl_render = GST_DEBUG_FUNCPTR(gst_projectm_render); scope_class->setup = GST_DEBUG_FUNCPTR(gst_projectm_setup); - scope_class->render = GST_DEBUG_FUNCPTR(gst_projectm_render); } static gboolean plugin_init(GstPlugin *plugin) @@ -430,6 +440,8 @@ static gboolean plugin_init(GstPlugin *plugin) return gst_element_register(plugin, "projectm", GST_RANK_NONE, GST_TYPE_PROJECTM); } + + GST_PLUGIN_DEFINE(GST_VERSION_MAJOR, GST_VERSION_MINOR, projectm, "plugin to visualize audio using the ProjectM library", plugin_init, PACKAGE_VERSION, PACKAGE_LICENSE, PACKAGE_NAME, PACKAGE_ORIGIN) \ No newline at end of file diff --git a/src/plugin.h b/src/plugin.h index cd7e7ac..2f2377b 100644 --- a/src/plugin.h +++ b/src/plugin.h @@ -2,28 +2,19 @@ #define __GST_PROJECTM_H__ #include -#include -#include +#include "gstglbaseaudiovisualizer.h" -#include +typedef struct _GstProjectMPrivate GstProjectMPrivate; G_BEGIN_DECLS #define GST_TYPE_PROJECTM (gst_projectm_get_type()) G_DECLARE_FINAL_TYPE(GstProjectM, gst_projectm, GST, - PROJECTM, GstAudioVisualizer) + PROJECTM, GstGLBaseAudioVisualizer) struct _GstProjectM { - GstAudioVisualizer element; - - GstGLDisplay *display; - GstGLWindow *window; - GstGLContext *context; - - uint8_t *framebuffer; - - projectm_handle handle; + GstGLBaseAudioVisualizer element; gchar *preset_path; gchar *texture_dir_path; @@ -36,12 +27,11 @@ struct _GstProjectM gdouble preset_duration; gulong mesh_width; gulong mesh_height; - gint32 fps; gboolean aspect_correction; gfloat easter_egg; gboolean preset_locked; - gulong window_width; - gulong window_height; + + GstProjectMPrivate *priv; }; struct _GstProjectMClass @@ -60,14 +50,18 @@ static void gst_projectm_init(GstProjectM *plugin); static void gst_projectm_finalize(GObject *object); -static gboolean gst_projectm_setup(GstAudioVisualizer *bscope); +static gboolean gst_projectm_gl_start(GstGLBaseAudioVisualizer *glav); + +static void gst_projectm_gl_stop(GstGLBaseAudioVisualizer *glav); -static gboolean gst_projectm_render(GstAudioVisualizer *bscope, GstBuffer *audio, GstVideoFrame *video); +static gboolean gst_projectm_render(GstGLBaseAudioVisualizer *glav, GstBuffer *audio, GstVideoFrame *video); static void gst_projectm_class_init(GstProjectMClass *klass); static gboolean plugin_init(GstPlugin *plugin); +static gboolean gst_projectm_setup(GstGLBaseAudioVisualizer *glav); + G_END_DECLS #endif /* __GST_PROJECTM_H__ */ \ No newline at end of file diff --git a/src/projectm.c b/src/projectm.c index 20e68fd..b86a7fe 100644 --- a/src/projectm.c +++ b/src/projectm.c @@ -12,21 +12,26 @@ GST_DEBUG_CATEGORY_STATIC(projectm_debug); #define GST_CAT_DEFAULT projectm_debug -void projectm_init(GstProjectM *plugin) +projectm_handle projectm_init(GstProjectM *plugin) { + projectm_handle handle = NULL; GST_DEBUG_CATEGORY_INIT(projectm_debug, "projectm", 0, "ProjectM"); + GstAudioVisualizer *bscope = GST_AUDIO_VISUALIZER(plugin); + // Create ProjectM instance - plugin->handle = projectm_create(); + GST_DEBUG_OBJECT(plugin, "Creating projectM instance.."); + handle = projectm_create(); - if (!plugin->handle) + if (!handle) { - GST_INFO("Could not create instance"); + GST_DEBUG_OBJECT(plugin, "project_create() returned NULL, projectM instance was not created!"); + return NULL; } else { - GST_INFO("Created instance!"); + GST_DEBUG_OBJECT(plugin, "Created projectM instance!"); } // Log properties @@ -59,39 +64,41 @@ void projectm_init(GstProjectM *plugin) // Load preset file if path is provided if (plugin->preset_path != NULL) - projectm_load_preset_file(plugin->handle, plugin->preset_path, false); + projectm_load_preset_file(handle, plugin->preset_path, false); // Set texture search path if directory path is provided if (plugin->texture_dir_path != NULL) { const gchar *texturePaths[1] = {plugin->texture_dir_path}; - projectm_set_texture_search_paths(plugin->handle, texturePaths, 1); + projectm_set_texture_search_paths(handle, texturePaths, 1); } // Set properties - projectm_set_beat_sensitivity(plugin->handle, plugin->beat_sensitivity); - projectm_set_hard_cut_duration(plugin->handle, plugin->hard_cut_duration); - projectm_set_hard_cut_enabled(plugin->handle, plugin->hard_cut_enabled); - projectm_set_hard_cut_sensitivity(plugin->handle, plugin->hard_cut_sensitivity); - projectm_set_soft_cut_duration(plugin->handle, plugin->soft_cut_duration); + projectm_set_beat_sensitivity(handle, plugin->beat_sensitivity); + projectm_set_hard_cut_duration(handle, plugin->hard_cut_duration); + projectm_set_hard_cut_enabled(handle, plugin->hard_cut_enabled); + projectm_set_hard_cut_sensitivity(handle, plugin->hard_cut_sensitivity); + projectm_set_soft_cut_duration(handle, plugin->soft_cut_duration); // Set preset duration, or set to in infinite duration if zero if (plugin->preset_duration > 0.0) { - projectm_set_preset_duration(plugin->handle, plugin->preset_duration); + projectm_set_preset_duration(handle, plugin->preset_duration); } else { - projectm_set_preset_duration(plugin->handle, 999999.0); + projectm_set_preset_duration(handle, 999999.0); } - projectm_set_mesh_size(plugin->handle, plugin->mesh_width, plugin->mesh_height); - projectm_set_aspect_correction(plugin->handle, plugin->aspect_correction); - projectm_set_easter_egg(plugin->handle, plugin->easter_egg); - projectm_set_preset_locked(plugin->handle, plugin->preset_locked); + projectm_set_mesh_size(handle, plugin->mesh_width, plugin->mesh_height); + projectm_set_aspect_correction(handle, plugin->aspect_correction); + projectm_set_easter_egg(handle, plugin->easter_egg); + projectm_set_preset_locked(handle, plugin->preset_locked); + + projectm_set_fps(handle, GST_VIDEO_INFO_FPS_N(&bscope->vinfo)); + projectm_set_window_size(handle, GST_VIDEO_INFO_WIDTH(&bscope->vinfo), GST_VIDEO_INFO_HEIGHT(&bscope->vinfo)); - projectm_set_fps(plugin->handle, plugin->fps); - projectm_set_window_size(plugin->handle, plugin->window_width, plugin->window_height); + return handle; } // void projectm_render(GstProjectM *plugin, gint16 *samples, gint sample_count) diff --git a/src/projectm.h b/src/projectm.h index 0d1dd76..e3311cc 100644 --- a/src/projectm.h +++ b/src/projectm.h @@ -4,13 +4,14 @@ #include #include "plugin.h" +#include G_BEGIN_DECLS /** * @brief Initialize ProjectM */ -void projectm_init(GstProjectM *plugin); +projectm_handle projectm_init(GstProjectM *plugin); /** * @brief Render ProjectM diff --git a/vcpkg.json b/vcpkg.json index 23ebb82..d730486 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -2,6 +2,10 @@ "dependencies": [ "glew", "glib", - "gstreamer" + "gstreamer", + { + "name": "pkgconf", + "platform": "windows" + } ] }