From 89903bf66ae0eec6f5613288123d263e1d80a5c9 Mon Sep 17 00:00:00 2001 From: Luis de Bethencourt Date: Tue, 2 Jun 2015 17:29:36 +0100 Subject: [PATCH] goom: rebase to use the audiovisualizer class --- gst/goom/Makefile.am | 3 +- gst/goom/gstaudiovisualizer.c | 1436 +++++++++++++++++++++++++++++++++ gst/goom/gstaudiovisualizer.h | 105 +++ gst/goom/gstgoom.c | 571 +------------ gst/goom/gstgoom.h | 40 +- 5 files changed, 1586 insertions(+), 569 deletions(-) create mode 100644 gst/goom/gstaudiovisualizer.c create mode 100644 gst/goom/gstaudiovisualizer.h diff --git a/gst/goom/Makefile.am b/gst/goom/Makefile.am index a0e6424d45..1ffa734014 100644 --- a/gst/goom/Makefile.am +++ b/gst/goom/Makefile.am @@ -35,10 +35,11 @@ libgstgoom_la_SOURCES = \ goom.h goom_typedefs.h goom_graphic.h \ goom_config_param.h goom_visual_fx.h goom_filters.h \ goom_tools.h goom_tools.h goom_config.h \ + gstaudiovisualizer.c gstaudiovisualizer.h \ $(ARCH_FILES) libgstgoom_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) $(GOOM_FILTER_CFLAGS) $(ARCH_CFLAGS) $(ORC_CFLAGS) -libgstgoom_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) $(LIBM) $(ORC_LIBS) +libgstgoom_la_LIBADD = $(GST_BASE_LIBS) $(GST_LIBS) -lgstvideo-$(GST_API_VERSION) -lgstaudio-$(GST_API_VERSION) $(LIBM) $(ORC_LIBS) libgstgoom_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgstgoom_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) diff --git a/gst/goom/gstaudiovisualizer.c b/gst/goom/gstaudiovisualizer.c new file mode 100644 index 0000000000..1abccbec08 --- /dev/null +++ b/gst/goom/gstaudiovisualizer.c @@ -0,0 +1,1436 @@ +/* GStreamer + * Copyright (C) <2011> Stefan Kost + * Copyright (C) <2015> Luis de Bethencourt + * + * gstaudiovisualizer.h: class for audio visualisation elements + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ +/** + * SECTION:gstaudiovisualizer + * + * A class for scopes (visualizers). It takes care of re-fitting the audio-rate + * to video-rate and handles renegotiation (downstream video size changes). + * + * It also provides several background shading effects. These effects are + * applied to a previous picture before the render() implementation can draw a + * new frame. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include + +#include "gstaudiovisualizer.h" + +GST_DEBUG_CATEGORY_STATIC (audio_visualizer_debug); +#define GST_CAT_DEFAULT (audio_visualizer_debug) + +#define DEFAULT_SHADER GST_AUDIO_VISUALIZER_SHADER_FADE +#define DEFAULT_SHADE_AMOUNT 0x000a0a0a + +enum +{ + PROP_0, + PROP_SHADER, + PROP_SHADE_AMOUNT +}; + +static GstBaseTransformClass *parent_class = NULL; + +static void gst_audio_visualizer_class_init (GstAudioVisualizerClass * klass); +static void gst_audio_visualizer_init (GstAudioVisualizer * scope, + GstAudioVisualizerClass * g_class); +static void gst_audio_visualizer_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_audio_visualizer_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); +static void gst_audio_visualizer_dispose (GObject * object); + +static gboolean gst_audio_visualizer_src_negotiate (GstAudioVisualizer * scope); +static gboolean gst_audio_visualizer_src_setcaps (GstAudioVisualizer * + scope, GstCaps * caps); +static gboolean gst_audio_visualizer_sink_setcaps (GstAudioVisualizer * + scope, GstCaps * caps); + +static GstFlowReturn gst_audio_visualizer_chain (GstPad * pad, + GstObject * parent, GstBuffer * buffer); + +static gboolean gst_audio_visualizer_src_event (GstPad * pad, + GstObject * parent, GstEvent * event); +static gboolean gst_audio_visualizer_sink_event (GstPad * pad, + GstObject * parent, GstEvent * event); + +static gboolean gst_audio_visualizer_src_query (GstPad * pad, + GstObject * parent, GstQuery * query); + +static GstStateChangeReturn gst_audio_visualizer_change_state (GstElement * + element, GstStateChange transition); + +static gboolean gst_audio_visualizer_do_bufferpool (GstAudioVisualizer * scope, + GstCaps * outcaps); + +static gboolean +default_decide_allocation (GstAudioVisualizer * scope, GstQuery * query); + +#define GST_AUDIO_VISUALIZER_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_AUDIO_VISUALIZER, GstAudioVisualizerPrivate)) + +struct _GstAudioVisualizerPrivate +{ + gboolean negotiated; + + GstBufferPool *pool; + gboolean pool_active; + GstAllocator *allocator; + GstAllocationParams params; + GstQuery *query; + + /* pads */ + GstPad *srcpad, *sinkpad; + + GstAudioVisualizerShader shader_type; + GstAudioVisualizerShaderFunc shader; + guint32 shade_amount; + + GstAdapter *adapter; + + GstBuffer *inbuf; + GstBuffer *tempbuf; + GstVideoFrame tempframe; + + guint spf; /* samples per video frame */ + guint64 frame_duration; + + /* QoS stuff *//* with LOCK */ + gdouble proportion; + GstClockTime earliest_time; + + guint dropped; /* frames dropped / not dropped */ + guint processed; + + /* configuration mutex */ + GMutex config_lock; + + GstSegment segment; +}; + +/* shading functions */ + +#define GST_TYPE_AUDIO_VISUALIZER_SHADER (gst_audio_visualizer_shader_get_type()) +static GType +gst_audio_visualizer_shader_get_type (void) +{ + static GType shader_type = 0; + static const GEnumValue shaders[] = { + {GST_AUDIO_VISUALIZER_SHADER_NONE, "None", "none"}, + {GST_AUDIO_VISUALIZER_SHADER_FADE, "Fade", "fade"}, + {GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_UP, "Fade and move up", + "fade-and-move-up"}, + {GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_DOWN, "Fade and move down", + "fade-and-move-down"}, + {GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_LEFT, "Fade and move left", + "fade-and-move-left"}, + {GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_RIGHT, + "Fade and move right", + "fade-and-move-right"}, + {GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_OUT, + "Fade and move horizontally out", "fade-and-move-horiz-out"}, + {GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_IN, + "Fade and move horizontally in", "fade-and-move-horiz-in"}, + {GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_OUT, + "Fade and move vertically out", "fade-and-move-vert-out"}, + {GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_IN, + "Fade and move vertically in", "fade-and-move-vert-in"}, + {0, NULL, NULL}, + }; + + if (G_UNLIKELY (shader_type == 0)) { + /* TODO: rename when exporting it as a library */ + shader_type = + g_enum_register_static ("GstAudioVisualizerShader-ExtGoom", shaders); + } + return shader_type; +} + +/* we're only supporting GST_VIDEO_FORMAT_xRGB right now) */ +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + +#define SHADE(_d, _s, _i, _r, _g, _b) \ +G_STMT_START { \ + _d[_i * 4 + 0] = (_s[_i * 4 + 0] > _b) ? _s[_i * 4 + 0] - _b : 0; \ + _d[_i * 4 + 1] = (_s[_i * 4 + 1] > _g) ? _s[_i * 4 + 1] - _g : 0; \ + _d[_i * 4 + 2] = (_s[_i * 4 + 2] > _r) ? _s[_i * 4 + 2] - _r : 0; \ + _d[_i * 4 + 3] = 0; \ +} G_STMT_END + +#else /* G_BYTE_ORDER == G_LITTLE_ENDIAN */ + +#define SHADE(_d, _s, _i, _r, _g, _b) \ +G_STMT_START { \ + _d[_i * 4 + 0] = 0; \ + _d[_i * 4 + 1] = (_s[_i * 4 + 1] > _r) ? _s[_i * 4 + 1] - _r : 0; \ + _d[_i * 4 + 2] = (_s[_i * 4 + 2] > _g) ? _s[_i * 4 + 2] - _g : 0; \ + _d[_i * 4 + 3] = (_s[_i * 4 + 3] > _b) ? _s[_i * 4 + 3] - _b : 0; \ +} G_STMT_END + +#endif + +static void +shader_fade (GstAudioVisualizer * scope, const GstVideoFrame * sframe, + GstVideoFrame * dframe) +{ + guint i, j; + guint r = (scope->priv->shade_amount >> 16) & 0xff; + guint g = (scope->priv->shade_amount >> 8) & 0xff; + guint b = (scope->priv->shade_amount >> 0) & 0xff; + guint8 *s, *d; + gint ss, ds, width, height; + + s = GST_VIDEO_FRAME_PLANE_DATA (sframe, 0); + ss = GST_VIDEO_FRAME_PLANE_STRIDE (sframe, 0); + d = GST_VIDEO_FRAME_PLANE_DATA (dframe, 0); + ds = GST_VIDEO_FRAME_PLANE_STRIDE (dframe, 0); + + width = GST_VIDEO_FRAME_WIDTH (sframe); + height = GST_VIDEO_FRAME_HEIGHT (sframe); + + for (j = 0; j < height; j++) { + for (i = 0; i < width; i++) { + SHADE (d, s, i, r, g, b); + } + s += ss; + d += ds; + } +} + +static void +shader_fade_and_move_up (GstAudioVisualizer * scope, + const GstVideoFrame * sframe, GstVideoFrame * dframe) +{ + guint i, j; + guint r = (scope->priv->shade_amount >> 16) & 0xff; + guint g = (scope->priv->shade_amount >> 8) & 0xff; + guint b = (scope->priv->shade_amount >> 0) & 0xff; + guint8 *s, *d; + gint ss, ds, width, height; + + s = GST_VIDEO_FRAME_PLANE_DATA (sframe, 0); + ss = GST_VIDEO_FRAME_PLANE_STRIDE (sframe, 0); + d = GST_VIDEO_FRAME_PLANE_DATA (dframe, 0); + ds = GST_VIDEO_FRAME_PLANE_STRIDE (dframe, 0); + + width = GST_VIDEO_FRAME_WIDTH (sframe); + height = GST_VIDEO_FRAME_HEIGHT (sframe); + + for (j = 1; j < height; j++) { + s += ss; + for (i = 0; i < width; i++) { + SHADE (d, s, i, r, g, b); + } + d += ds; + } +} + +static void +shader_fade_and_move_down (GstAudioVisualizer * scope, + const GstVideoFrame * sframe, GstVideoFrame * dframe) +{ + guint i, j; + guint r = (scope->priv->shade_amount >> 16) & 0xff; + guint g = (scope->priv->shade_amount >> 8) & 0xff; + guint b = (scope->priv->shade_amount >> 0) & 0xff; + guint8 *s, *d; + gint ss, ds, width, height; + + s = GST_VIDEO_FRAME_PLANE_DATA (sframe, 0); + ss = GST_VIDEO_FRAME_PLANE_STRIDE (sframe, 0); + d = GST_VIDEO_FRAME_PLANE_DATA (dframe, 0); + ds = GST_VIDEO_FRAME_PLANE_STRIDE (dframe, 0); + + width = GST_VIDEO_FRAME_WIDTH (sframe); + height = GST_VIDEO_FRAME_HEIGHT (sframe); + + for (j = 1; j < height; j++) { + d += ds; + for (i = 0; i < width; i++) { + SHADE (d, s, i, r, g, b); + } + s += ss; + } +} + +static void +shader_fade_and_move_left (GstAudioVisualizer * scope, + const GstVideoFrame * sframe, GstVideoFrame * dframe) +{ + guint i, j; + guint r = (scope->priv->shade_amount >> 16) & 0xff; + guint g = (scope->priv->shade_amount >> 8) & 0xff; + guint b = (scope->priv->shade_amount >> 0) & 0xff; + guint8 *s, *d; + gint ss, ds, width, height; + + s = GST_VIDEO_FRAME_PLANE_DATA (sframe, 0); + ss = GST_VIDEO_FRAME_PLANE_STRIDE (sframe, 0); + d = GST_VIDEO_FRAME_PLANE_DATA (dframe, 0); + ds = GST_VIDEO_FRAME_PLANE_STRIDE (dframe, 0); + + width = GST_VIDEO_FRAME_WIDTH (sframe); + height = GST_VIDEO_FRAME_HEIGHT (sframe); + + width -= 1; + s += 4; + + /* move to the left */ + for (j = 0; j < height; j++) { + for (i = 0; i < width; i++) { + SHADE (d, s, i, r, g, b); + } + d += ds; + s += ss; + } +} + +static void +shader_fade_and_move_right (GstAudioVisualizer * scope, + const GstVideoFrame * sframe, GstVideoFrame * dframe) +{ + guint i, j; + guint r = (scope->priv->shade_amount >> 16) & 0xff; + guint g = (scope->priv->shade_amount >> 8) & 0xff; + guint b = (scope->priv->shade_amount >> 0) & 0xff; + guint8 *s, *d; + gint ss, ds, width, height; + + s = GST_VIDEO_FRAME_PLANE_DATA (sframe, 0); + ss = GST_VIDEO_FRAME_PLANE_STRIDE (sframe, 0); + d = GST_VIDEO_FRAME_PLANE_DATA (dframe, 0); + ds = GST_VIDEO_FRAME_PLANE_STRIDE (dframe, 0); + + width = GST_VIDEO_FRAME_WIDTH (sframe); + height = GST_VIDEO_FRAME_HEIGHT (sframe); + + width -= 1; + d += 4; + + /* move to the right */ + for (j = 0; j < height; j++) { + for (i = 0; i < width; i++) { + SHADE (d, s, i, r, g, b); + } + d += ds; + s += ss; + } +} + +static void +shader_fade_and_move_horiz_out (GstAudioVisualizer * scope, + const GstVideoFrame * sframe, GstVideoFrame * dframe) +{ + guint i, j; + guint r = (scope->priv->shade_amount >> 16) & 0xff; + guint g = (scope->priv->shade_amount >> 8) & 0xff; + guint b = (scope->priv->shade_amount >> 0) & 0xff; + guint8 *s, *d; + gint ss, ds, width, height; + + s = GST_VIDEO_FRAME_PLANE_DATA (sframe, 0); + ss = GST_VIDEO_FRAME_PLANE_STRIDE (sframe, 0); + d = GST_VIDEO_FRAME_PLANE_DATA (dframe, 0); + ds = GST_VIDEO_FRAME_PLANE_STRIDE (dframe, 0); + + width = GST_VIDEO_FRAME_WIDTH (sframe); + height = GST_VIDEO_FRAME_HEIGHT (sframe); + + /* move upper half up */ + for (j = 0; j < height / 2; j++) { + s += ss; + for (i = 0; i < width; i++) { + SHADE (d, s, i, r, g, b); + } + d += ds; + } + /* move lower half down */ + for (j = 0; j < height / 2; j++) { + d += ds; + for (i = 0; i < width; i++) { + SHADE (d, s, i, r, g, b); + } + s += ss; + } +} + +static void +shader_fade_and_move_horiz_in (GstAudioVisualizer * scope, + const GstVideoFrame * sframe, GstVideoFrame * dframe) +{ + guint i, j; + guint r = (scope->priv->shade_amount >> 16) & 0xff; + guint g = (scope->priv->shade_amount >> 8) & 0xff; + guint b = (scope->priv->shade_amount >> 0) & 0xff; + guint8 *s, *d; + gint ss, ds, width, height; + + s = GST_VIDEO_FRAME_PLANE_DATA (sframe, 0); + ss = GST_VIDEO_FRAME_PLANE_STRIDE (sframe, 0); + d = GST_VIDEO_FRAME_PLANE_DATA (dframe, 0); + ds = GST_VIDEO_FRAME_PLANE_STRIDE (dframe, 0); + + width = GST_VIDEO_FRAME_WIDTH (sframe); + height = GST_VIDEO_FRAME_HEIGHT (sframe); + + /* move upper half down */ + for (j = 0; j < height / 2; j++) { + d += ds; + for (i = 0; i < width; i++) { + SHADE (d, s, i, r, g, b); + } + s += ss; + } + /* move lower half up */ + for (j = 0; j < height / 2; j++) { + s += ss; + for (i = 0; i < width; i++) { + SHADE (d, s, i, r, g, b); + } + d += ds; + } +} + +static void +shader_fade_and_move_vert_out (GstAudioVisualizer * scope, + const GstVideoFrame * sframe, GstVideoFrame * dframe) +{ + guint i, j; + guint r = (scope->priv->shade_amount >> 16) & 0xff; + guint g = (scope->priv->shade_amount >> 8) & 0xff; + guint b = (scope->priv->shade_amount >> 0) & 0xff; + guint8 *s, *s1, *d, *d1; + gint ss, ds, width, height; + + s = GST_VIDEO_FRAME_PLANE_DATA (sframe, 0); + ss = GST_VIDEO_FRAME_PLANE_STRIDE (sframe, 0); + d = GST_VIDEO_FRAME_PLANE_DATA (dframe, 0); + ds = GST_VIDEO_FRAME_PLANE_STRIDE (dframe, 0); + + width = GST_VIDEO_FRAME_WIDTH (sframe); + height = GST_VIDEO_FRAME_HEIGHT (sframe); + + for (j = 0; j < height; j++) { + /* move left half to the left */ + s1 = s + 1; + for (i = 0; i < width / 2; i++) { + SHADE (d, s1, i, r, g, b); + } + /* move right half to the right */ + d1 = d + 1; + for (; i < width - 1; i++) { + SHADE (d1, s, i, r, g, b); + } + s += ss; + d += ds; + } +} + +static void +shader_fade_and_move_vert_in (GstAudioVisualizer * scope, + const GstVideoFrame * sframe, GstVideoFrame * dframe) +{ + guint i, j; + guint r = (scope->priv->shade_amount >> 16) & 0xff; + guint g = (scope->priv->shade_amount >> 8) & 0xff; + guint b = (scope->priv->shade_amount >> 0) & 0xff; + guint8 *s, *s1, *d, *d1; + gint ss, ds, width, height; + + s = GST_VIDEO_FRAME_PLANE_DATA (sframe, 0); + ss = GST_VIDEO_FRAME_PLANE_STRIDE (sframe, 0); + d = GST_VIDEO_FRAME_PLANE_DATA (dframe, 0); + ds = GST_VIDEO_FRAME_PLANE_STRIDE (dframe, 0); + + width = GST_VIDEO_FRAME_WIDTH (sframe); + height = GST_VIDEO_FRAME_HEIGHT (sframe); + + for (j = 0; j < height; j++) { + /* move left half to the right */ + d1 = d + 1; + for (i = 0; i < width / 2; i++) { + SHADE (d1, s, i, r, g, b); + } + /* move right half to the left */ + s1 = s + 1; + for (; i < width - 1; i++) { + SHADE (d, s1, i, r, g, b); + } + s += ss; + d += ds; + } +} + +static void +gst_audio_visualizer_change_shader (GstAudioVisualizer * scope) +{ + switch (scope->priv->shader_type) { + case GST_AUDIO_VISUALIZER_SHADER_NONE: + scope->priv->shader = NULL; + break; + case GST_AUDIO_VISUALIZER_SHADER_FADE: + scope->priv->shader = shader_fade; + break; + case GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_UP: + scope->priv->shader = shader_fade_and_move_up; + break; + case GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_DOWN: + scope->priv->shader = shader_fade_and_move_down; + break; + case GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_LEFT: + scope->priv->shader = shader_fade_and_move_left; + break; + case GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_RIGHT: + scope->priv->shader = shader_fade_and_move_right; + break; + case GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_OUT: + scope->priv->shader = shader_fade_and_move_horiz_out; + break; + case GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_IN: + scope->priv->shader = shader_fade_and_move_horiz_in; + break; + case GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_OUT: + scope->priv->shader = shader_fade_and_move_vert_out; + break; + case GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_IN: + scope->priv->shader = shader_fade_and_move_vert_in; + break; + default: + GST_ERROR ("invalid shader function"); + scope->priv->shader = NULL; + break; + } +} + +/* class */ + +GType +gst_audio_visualizer_get_type (void) +{ + static volatile gsize audio_visualizer_type = 0; + + if (g_once_init_enter (&audio_visualizer_type)) { + static const GTypeInfo audio_visualizer_info = { + sizeof (GstAudioVisualizerClass), + NULL, + NULL, + (GClassInitFunc) gst_audio_visualizer_class_init, + NULL, + NULL, + sizeof (GstAudioVisualizer), + 0, + (GInstanceInitFunc) gst_audio_visualizer_init, + }; + GType _type; + + /* TODO: rename when exporting it as a library */ + _type = g_type_register_static (GST_TYPE_ELEMENT, + "GstAudioVisualizer-ExtGom", &audio_visualizer_info, + G_TYPE_FLAG_ABSTRACT); + g_once_init_leave (&audio_visualizer_type, _type); + } + return (GType) audio_visualizer_type; +} + +static void +gst_audio_visualizer_class_init (GstAudioVisualizerClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *element_class = (GstElementClass *) klass; + + g_type_class_add_private (klass, sizeof (GstAudioVisualizerPrivate)); + + parent_class = g_type_class_peek_parent (klass); + + GST_DEBUG_CATEGORY_INIT (audio_visualizer_debug, + "audiovisualizer-goom", 0, "audio visualisation class"); + + gobject_class->set_property = gst_audio_visualizer_set_property; + gobject_class->get_property = gst_audio_visualizer_get_property; + gobject_class->dispose = gst_audio_visualizer_dispose; + + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_audio_visualizer_change_state); + + klass->decide_allocation = GST_DEBUG_FUNCPTR (default_decide_allocation); + + g_object_class_install_property (gobject_class, PROP_SHADER, + g_param_spec_enum ("shader", "shader type", + "Shader function to apply on each frame", + GST_TYPE_AUDIO_VISUALIZER_SHADER, DEFAULT_SHADER, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_SHADE_AMOUNT, + g_param_spec_uint ("shade-amount", "shade amount", + "Shading color to use (big-endian ARGB)", 0, G_MAXUINT32, + DEFAULT_SHADE_AMOUNT, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_audio_visualizer_init (GstAudioVisualizer * scope, + GstAudioVisualizerClass * g_class) +{ + GstPadTemplate *pad_template; + + scope->priv = GST_AUDIO_VISUALIZER_GET_PRIVATE (scope); + + /* create the sink and src pads */ + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "sink"); + g_return_if_fail (pad_template != NULL); + scope->priv->sinkpad = gst_pad_new_from_template (pad_template, "sink"); + gst_pad_set_chain_function (scope->priv->sinkpad, + GST_DEBUG_FUNCPTR (gst_audio_visualizer_chain)); + gst_pad_set_event_function (scope->priv->sinkpad, + GST_DEBUG_FUNCPTR (gst_audio_visualizer_sink_event)); + gst_element_add_pad (GST_ELEMENT (scope), scope->priv->sinkpad); + + pad_template = + gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src"); + g_return_if_fail (pad_template != NULL); + scope->priv->srcpad = gst_pad_new_from_template (pad_template, "src"); + gst_pad_set_event_function (scope->priv->srcpad, + GST_DEBUG_FUNCPTR (gst_audio_visualizer_src_event)); + gst_pad_set_query_function (scope->priv->srcpad, + GST_DEBUG_FUNCPTR (gst_audio_visualizer_src_query)); + gst_element_add_pad (GST_ELEMENT (scope), scope->priv->srcpad); + + scope->priv->adapter = gst_adapter_new (); + scope->priv->inbuf = gst_buffer_new (); + + /* properties */ + scope->priv->shader_type = DEFAULT_SHADER; + gst_audio_visualizer_change_shader (scope); + scope->priv->shade_amount = DEFAULT_SHADE_AMOUNT; + + /* reset the initial video state */ + gst_video_info_init (&scope->vinfo); + scope->priv->frame_duration = GST_CLOCK_TIME_NONE; + + /* reset the initial state */ + gst_audio_info_init (&scope->ainfo); + gst_video_info_init (&scope->vinfo); + + g_mutex_init (&scope->priv->config_lock); +} + +static void +gst_audio_visualizer_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstAudioVisualizer *scope = GST_AUDIO_VISUALIZER (object); + + switch (prop_id) { + case PROP_SHADER: + scope->priv->shader_type = g_value_get_enum (value); + gst_audio_visualizer_change_shader (scope); + break; + case PROP_SHADE_AMOUNT: + scope->priv->shade_amount = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_audio_visualizer_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstAudioVisualizer *scope = GST_AUDIO_VISUALIZER (object); + + switch (prop_id) { + case PROP_SHADER: + g_value_set_enum (value, scope->priv->shader_type); + break; + case PROP_SHADE_AMOUNT: + g_value_set_uint (value, scope->priv->shade_amount); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_audio_visualizer_dispose (GObject * object) +{ + GstAudioVisualizer *scope = GST_AUDIO_VISUALIZER (object); + + if (scope->priv->adapter) { + g_object_unref (scope->priv->adapter); + scope->priv->adapter = NULL; + } + if (scope->priv->inbuf) { + gst_buffer_unref (scope->priv->inbuf); + scope->priv->inbuf = NULL; + } + if (scope->priv->tempbuf) { + gst_video_frame_unmap (&scope->priv->tempframe); + gst_buffer_unref (scope->priv->tempbuf); + scope->priv->tempbuf = NULL; + } + if (scope->priv->config_lock.p) { + g_mutex_clear (&scope->priv->config_lock); + scope->priv->config_lock.p = NULL; + } + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_audio_visualizer_reset (GstAudioVisualizer * scope) +{ + gst_adapter_clear (scope->priv->adapter); + gst_segment_init (&scope->priv->segment, GST_FORMAT_UNDEFINED); + + GST_OBJECT_LOCK (scope); + scope->priv->proportion = 1.0; + scope->priv->earliest_time = -1; + scope->priv->dropped = 0; + scope->priv->processed = 0; + GST_OBJECT_UNLOCK (scope); +} + +static gboolean +gst_audio_visualizer_sink_setcaps (GstAudioVisualizer * scope, GstCaps * caps) +{ + GstAudioInfo info; + + if (!gst_audio_info_from_caps (&info, caps)) + goto wrong_caps; + + scope->ainfo = info; + + GST_DEBUG_OBJECT (scope, "audio: channels %d, rate %d", + GST_AUDIO_INFO_CHANNELS (&info), GST_AUDIO_INFO_RATE (&info)); + + if (!gst_audio_visualizer_src_negotiate (scope)) { + goto not_negotiated; + } + + return TRUE; + + /* Errors */ +wrong_caps: + { + GST_WARNING_OBJECT (scope, "could not parse caps"); + return FALSE; + } +not_negotiated: + { + GST_WARNING_OBJECT (scope, "failed to negotiate"); + return FALSE; + } +} + +static gboolean +gst_audio_visualizer_src_setcaps (GstAudioVisualizer * scope, GstCaps * caps) +{ + GstVideoInfo info; + GstAudioVisualizerClass *klass; + gboolean res; + + if (!gst_video_info_from_caps (&info, caps)) + goto wrong_caps; + + klass = GST_AUDIO_VISUALIZER_CLASS (G_OBJECT_GET_CLASS (scope)); + + scope->vinfo = info; + + scope->priv->frame_duration = gst_util_uint64_scale_int (GST_SECOND, + GST_VIDEO_INFO_FPS_D (&info), GST_VIDEO_INFO_FPS_N (&info)); + scope->priv->spf = + gst_util_uint64_scale_int (GST_AUDIO_INFO_RATE (&scope->ainfo), + GST_VIDEO_INFO_FPS_D (&info), GST_VIDEO_INFO_FPS_N (&info)); + scope->req_spf = scope->priv->spf; + + if (scope->priv->tempbuf) { + gst_video_frame_unmap (&scope->priv->tempframe); + gst_buffer_unref (scope->priv->tempbuf); + } + scope->priv->tempbuf = gst_buffer_new_wrapped (g_malloc0 (scope->vinfo.size), + scope->vinfo.size); + gst_video_frame_map (&scope->priv->tempframe, &scope->vinfo, + scope->priv->tempbuf, GST_MAP_READWRITE); + + if (klass->setup && !klass->setup (scope)) + goto setup_failed; + + GST_DEBUG_OBJECT (scope, "video: dimension %dx%d, framerate %d/%d", + GST_VIDEO_INFO_WIDTH (&info), GST_VIDEO_INFO_HEIGHT (&info), + GST_VIDEO_INFO_FPS_N (&info), GST_VIDEO_INFO_FPS_D (&info)); + GST_DEBUG_OBJECT (scope, "blocks: spf %u, req_spf %u", + scope->priv->spf, scope->req_spf); + + gst_pad_set_caps (scope->priv->srcpad, caps); + + /* find a pool for the negotiated caps now */ + res = gst_audio_visualizer_do_bufferpool (scope, caps); + gst_caps_unref (caps); + + return res; + + /* ERRORS */ +wrong_caps: + { + gst_caps_unref (caps); + GST_DEBUG_OBJECT (scope, "error parsing caps"); + return FALSE; + } + +setup_failed: + { + GST_WARNING_OBJECT (scope, "failed to set up"); + return FALSE; + } +} + +static gboolean +gst_audio_visualizer_src_negotiate (GstAudioVisualizer * scope) +{ + GstCaps *othercaps, *target; + GstStructure *structure; + GstCaps *templ; + gboolean ret; + + templ = gst_pad_get_pad_template_caps (scope->priv->srcpad); + + GST_DEBUG_OBJECT (scope, "performing negotiation"); + + /* see what the peer can do */ + othercaps = gst_pad_peer_query_caps (scope->priv->srcpad, NULL); + if (othercaps) { + target = gst_caps_intersect (othercaps, templ); + gst_caps_unref (othercaps); + gst_caps_unref (templ); + + if (gst_caps_is_empty (target)) + goto no_format; + + target = gst_caps_truncate (target); + } else { + target = templ; + } + + target = gst_caps_make_writable (target); + structure = gst_caps_get_structure (target, 0); + gst_structure_fixate_field_nearest_int (structure, "width", 320); + gst_structure_fixate_field_nearest_int (structure, "height", 200); + gst_structure_fixate_field_nearest_fraction (structure, "framerate", 25, 1); + + target = gst_caps_fixate (target); + + GST_DEBUG_OBJECT (scope, "final caps are %" GST_PTR_FORMAT, target); + + ret = gst_audio_visualizer_src_setcaps (scope, target); + + return ret; + +no_format: + { + gst_caps_unref (target); + return FALSE; + } +} + +/* takes ownership of the pool, allocator and query */ +static gboolean +gst_audio_visualizer_set_allocation (GstAudioVisualizer * scope, + GstBufferPool * pool, GstAllocator * allocator, + GstAllocationParams * params, GstQuery * query) +{ + GstAllocator *oldalloc; + GstBufferPool *oldpool; + GstQuery *oldquery; + GstAudioVisualizerPrivate *priv = scope->priv; + + GST_OBJECT_LOCK (scope); + oldpool = priv->pool; + priv->pool = pool; + priv->pool_active = FALSE; + + oldalloc = priv->allocator; + priv->allocator = allocator; + + oldquery = priv->query; + priv->query = query; + + if (params) + priv->params = *params; + else + gst_allocation_params_init (&priv->params); + GST_OBJECT_UNLOCK (scope); + + if (oldpool) { + GST_DEBUG_OBJECT (scope, "deactivating old pool %p", oldpool); + gst_buffer_pool_set_active (oldpool, FALSE); + gst_object_unref (oldpool); + } + if (oldalloc) { + gst_object_unref (oldalloc); + } + if (oldquery) { + gst_query_unref (oldquery); + } + return TRUE; +} + +static gboolean +gst_audio_visualizer_do_bufferpool (GstAudioVisualizer * scope, + GstCaps * outcaps) +{ + GstQuery *query; + gboolean result = TRUE; + GstBufferPool *pool = NULL; + GstAudioVisualizerClass *klass; + GstAllocator *allocator; + GstAllocationParams params; + + /* not passthrough, we need to allocate */ + /* find a pool for the negotiated caps now */ + GST_DEBUG_OBJECT (scope, "doing allocation query"); + query = gst_query_new_allocation (outcaps, TRUE); + + if (!gst_pad_peer_query (scope->priv->srcpad, query)) { + /* not a problem, we use the query defaults */ + GST_DEBUG_OBJECT (scope, "allocation query failed"); + } + + klass = GST_AUDIO_VISUALIZER_GET_CLASS (scope); + + GST_DEBUG_OBJECT (scope, "calling decide_allocation"); + g_assert (klass->decide_allocation != NULL); + result = klass->decide_allocation (scope, query); + + GST_DEBUG_OBJECT (scope, "ALLOCATION (%d) params: %" GST_PTR_FORMAT, result, + query); + + if (!result) + goto no_decide_allocation; + + /* we got configuration from our peer or the decide_allocation method, + * parse them */ + if (gst_query_get_n_allocation_params (query) > 0) { + gst_query_parse_nth_allocation_param (query, 0, &allocator, ¶ms); + } else { + allocator = NULL; + gst_allocation_params_init (¶ms); + } + + if (gst_query_get_n_allocation_pools (query) > 0) + gst_query_parse_nth_allocation_pool (query, 0, &pool, NULL, NULL, NULL); + + /* now store */ + result = + gst_audio_visualizer_set_allocation (scope, pool, allocator, ¶ms, + query); + + return result; + + /* Errors */ +no_decide_allocation: + { + GST_WARNING_OBJECT (scope, "Subclass failed to decide allocation"); + gst_query_unref (query); + + return result; + } +} + +static gboolean +default_decide_allocation (GstAudioVisualizer * scope, GstQuery * query) +{ + GstCaps *outcaps; + GstBufferPool *pool; + guint size, min, max; + GstAllocator *allocator; + GstAllocationParams params; + GstStructure *config; + gboolean update_allocator; + gboolean update_pool; + + gst_query_parse_allocation (query, &outcaps, NULL); + + /* we got configuration from our peer or the decide_allocation method, + * parse them */ + if (gst_query_get_n_allocation_params (query) > 0) { + /* try the allocator */ + gst_query_parse_nth_allocation_param (query, 0, &allocator, ¶ms); + update_allocator = TRUE; + } else { + allocator = NULL; + gst_allocation_params_init (¶ms); + update_allocator = FALSE; + } + + 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 { + pool = NULL; + size = GST_VIDEO_INFO_SIZE (&scope->vinfo); + min = max = 0; + update_pool = FALSE; + } + + if (pool == NULL) { + /* we did not get a pool, make one ourselves then */ + pool = gst_video_buffer_pool_new (); + } + + config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_set_params (config, outcaps, size, min, max); + gst_buffer_pool_config_set_allocator (config, allocator, ¶ms); + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); + gst_buffer_pool_set_config (pool, config); + + if (update_allocator) + gst_query_set_nth_allocation_param (query, 0, allocator, ¶ms); + else + gst_query_add_allocation_param (query, allocator, ¶ms); + + if (allocator) + gst_object_unref (allocator); + + 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); + + if (pool) + gst_object_unref (pool); + + return TRUE; +} + +static GstFlowReturn +default_prepare_output_buffer (GstAudioVisualizer * scope, GstBuffer ** outbuf) +{ + GstAudioVisualizerPrivate *priv; + + priv = scope->priv; + + g_assert (priv->pool != NULL); + + /* we can't reuse the input buffer */ + if (!priv->pool_active) { + GST_DEBUG_OBJECT (scope, "setting pool %p active", priv->pool); + if (!gst_buffer_pool_set_active (priv->pool, TRUE)) + goto activate_failed; + priv->pool_active = TRUE; + } + GST_DEBUG_OBJECT (scope, "using pool alloc"); + + return gst_buffer_pool_acquire_buffer (priv->pool, outbuf, NULL); + + /* ERRORS */ +activate_failed: + { + GST_ELEMENT_ERROR (scope, RESOURCE, SETTINGS, + ("failed to activate bufferpool"), ("failed to activate bufferpool")); + return GST_FLOW_ERROR; + } +} + +static GstFlowReturn +gst_audio_visualizer_chain (GstPad * pad, GstObject * parent, + GstBuffer * buffer) +{ + GstFlowReturn ret = GST_FLOW_OK; + GstAudioVisualizer *scope; + GstAudioVisualizerClass *klass; + GstBuffer *inbuf; + guint64 dist, ts; + guint avail, sbpf; + gpointer adata; + gint bps, channels, rate; + + scope = GST_AUDIO_VISUALIZER (parent); + klass = GST_AUDIO_VISUALIZER_CLASS (G_OBJECT_GET_CLASS (scope)); + + GST_LOG_OBJECT (scope, "chainfunc called"); + + /* resync on DISCONT */ + if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) { + gst_adapter_clear (scope->priv->adapter); + } + + /* Make sure have an output format */ + if (gst_pad_check_reconfigure (scope->priv->srcpad)) { + if (!gst_audio_visualizer_src_negotiate (scope)) { + gst_pad_mark_reconfigure (scope->priv->srcpad); + goto not_negotiated; + } + } + + channels = GST_AUDIO_INFO_CHANNELS (&scope->ainfo); + rate = GST_AUDIO_INFO_RATE (&scope->ainfo); + bps = GST_AUDIO_INFO_BPS (&scope->ainfo); + + if (bps == 0) { + ret = GST_FLOW_NOT_NEGOTIATED; + goto beach; + } + + gst_adapter_push (scope->priv->adapter, buffer); + + g_mutex_lock (&scope->priv->config_lock); + + /* this is what we want */ + sbpf = scope->req_spf * channels * sizeof (gint16); + + inbuf = scope->priv->inbuf; + /* FIXME: the timestamp in the adapter would be different */ + gst_buffer_copy_into (inbuf, buffer, GST_BUFFER_COPY_METADATA, 0, -1); + + /* this is what we have */ + avail = gst_adapter_available (scope->priv->adapter); + GST_LOG_OBJECT (scope, "avail: %u, bpf: %u", avail, sbpf); + while (avail >= sbpf) { + GstBuffer *outbuf; + GstVideoFrame outframe; + + /* get timestamp of the current adapter content */ + ts = gst_adapter_prev_pts (scope->priv->adapter, &dist); + if (GST_CLOCK_TIME_IS_VALID (ts)) { + /* convert bytes to time */ + dist /= bps; + ts += gst_util_uint64_scale_int (dist, GST_SECOND, rate); + } + + /* check for QoS, don't compute buffers that are known to be late */ + if (GST_CLOCK_TIME_IS_VALID (ts)) { + GstClockTime earliest_time; + gdouble proportion; + gint64 qostime; + + qostime = + gst_segment_to_running_time (&scope->priv->segment, + GST_FORMAT_TIME, ts) + scope->priv->frame_duration; + + GST_OBJECT_LOCK (scope); + earliest_time = scope->priv->earliest_time; + proportion = scope->priv->proportion; + GST_OBJECT_UNLOCK (scope); + + if (GST_CLOCK_TIME_IS_VALID (earliest_time) && qostime <= earliest_time) { + GstClockTime stream_time, jitter; + GstMessage *qos_msg; + + GST_DEBUG_OBJECT (scope, + "QoS: skip ts: %" GST_TIME_FORMAT ", earliest: %" GST_TIME_FORMAT, + GST_TIME_ARGS (qostime), GST_TIME_ARGS (earliest_time)); + + ++scope->priv->dropped; + stream_time = gst_segment_to_stream_time (&scope->priv->segment, + GST_FORMAT_TIME, ts); + jitter = GST_CLOCK_DIFF (qostime, earliest_time); + qos_msg = gst_message_new_qos (GST_OBJECT (scope), FALSE, qostime, + stream_time, ts, GST_BUFFER_DURATION (buffer)); + gst_message_set_qos_values (qos_msg, jitter, proportion, 1000000); + gst_message_set_qos_stats (qos_msg, GST_FORMAT_BUFFERS, + scope->priv->processed, scope->priv->dropped); + gst_element_post_message (GST_ELEMENT (scope), qos_msg); + + goto skip; + } + } + + ++scope->priv->processed; + + g_mutex_unlock (&scope->priv->config_lock); + ret = default_prepare_output_buffer (scope, &outbuf); + g_mutex_lock (&scope->priv->config_lock); + /* recheck as the value could have changed */ + sbpf = scope->req_spf * channels * sizeof (gint16); + + /* no buffer allocated, we don't care why. */ + if (ret != GST_FLOW_OK) + break; + + /* sync controlled properties */ + if (GST_CLOCK_TIME_IS_VALID (ts)) + gst_object_sync_values (GST_OBJECT (scope), ts); + + GST_BUFFER_TIMESTAMP (outbuf) = ts; + GST_BUFFER_DURATION (outbuf) = scope->priv->frame_duration; + + /* this can fail as the data size we need could have changed */ + if (!(adata = (gpointer) gst_adapter_map (scope->priv->adapter, sbpf))) + break; + + gst_video_frame_map (&outframe, &scope->vinfo, outbuf, GST_MAP_READWRITE); + + if (scope->priv->shader) { + gst_video_frame_copy (&outframe, &scope->priv->tempframe); + } else { + /* gst_video_frame_clear() or is output frame already cleared */ + gint i; + + for (i = 0; i < scope->vinfo.finfo->n_planes; i++) { + memset (outframe.data[i], 0, outframe.map[i].size); + } + } + + gst_buffer_replace_all_memory (inbuf, + gst_memory_new_wrapped (GST_MEMORY_FLAG_READONLY, adata, sbpf, 0, + sbpf, NULL, NULL)); + + /* call class->render() vmethod */ + if (klass->render) { + if (!klass->render (scope, inbuf, &outframe)) { + ret = GST_FLOW_ERROR; + gst_video_frame_unmap (&outframe); + goto beach; + } else { + /* run various post processing (shading and geometric transformation) */ + /* FIXME: SHADER assumes 32bpp */ + if (scope->priv->shader && + GST_VIDEO_INFO_COMP_PSTRIDE (&scope->vinfo, 0) == 4) { + scope->priv->shader (scope, &outframe, &scope->priv->tempframe); + } + } + } + gst_video_frame_unmap (&outframe); + + g_mutex_unlock (&scope->priv->config_lock); + ret = gst_pad_push (scope->priv->srcpad, outbuf); + outbuf = NULL; + g_mutex_lock (&scope->priv->config_lock); + + skip: + /* recheck as the value could have changed */ + sbpf = scope->req_spf * channels * sizeof (gint16); + GST_LOG_OBJECT (scope, "avail: %u, bpf: %u", avail, sbpf); + /* we want to take less or more, depending on spf : req_spf */ + if (avail - sbpf >= sbpf) { + gst_adapter_flush (scope->priv->adapter, sbpf); + gst_adapter_unmap (scope->priv->adapter); + } else if (avail >= sbpf) { + /* just flush a bit and stop */ + gst_adapter_flush (scope->priv->adapter, (avail - sbpf)); + gst_adapter_unmap (scope->priv->adapter); + break; + } + avail = gst_adapter_available (scope->priv->adapter); + + if (ret != GST_FLOW_OK) + break; + } + + g_mutex_unlock (&scope->priv->config_lock); + +beach: + return ret; + + /* ERRORS */ +not_negotiated: + { + GST_DEBUG_OBJECT (scope, "Failed to renegotiate"); + return GST_FLOW_NOT_NEGOTIATED; + } +} + +static gboolean +gst_audio_visualizer_src_event (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + gboolean res; + GstAudioVisualizer *scope; + + scope = GST_AUDIO_VISUALIZER (parent); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_QOS: + { + gdouble proportion; + GstClockTimeDiff diff; + GstClockTime timestamp; + + gst_event_parse_qos (event, NULL, &proportion, &diff, ×tamp); + + /* save stuff for the _chain() function */ + GST_OBJECT_LOCK (scope); + scope->priv->proportion = proportion; + if (diff >= 0) + /* we're late, this is a good estimate for next displayable + * frame (see part-qos.txt) */ + scope->priv->earliest_time = timestamp + 2 * diff + + scope->priv->frame_duration; + else + scope->priv->earliest_time = timestamp + diff; + GST_OBJECT_UNLOCK (scope); + + res = gst_pad_push_event (scope->priv->sinkpad, event); + break; + } + case GST_EVENT_RECONFIGURE: + /* dont't forward */ + gst_event_unref (event); + res = TRUE; + break; + default: + res = gst_pad_event_default (pad, parent, event); + break; + } + + return res; +} + +static gboolean +gst_audio_visualizer_sink_event (GstPad * pad, GstObject * parent, + GstEvent * event) +{ + gboolean res; + GstAudioVisualizer *scope; + + scope = GST_AUDIO_VISUALIZER (parent); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_CAPS: + { + GstCaps *caps; + + gst_event_parse_caps (event, &caps); + res = gst_audio_visualizer_sink_setcaps (scope, caps); + gst_event_unref (event); + break; + } + case GST_EVENT_FLUSH_STOP: + gst_audio_visualizer_reset (scope); + res = gst_pad_push_event (scope->priv->srcpad, event); + break; + case GST_EVENT_SEGMENT: + { + /* the newsegment values are used to clip the input samples + * and to convert the incomming timestamps to running time so + * we can do QoS */ + gst_event_copy_segment (event, &scope->priv->segment); + + res = gst_pad_push_event (scope->priv->srcpad, event); + break; + } + default: + res = gst_pad_event_default (pad, parent, event); + break; + } + + return res; +} + +static gboolean +gst_audio_visualizer_src_query (GstPad * pad, GstObject * parent, + GstQuery * query) +{ + gboolean res = FALSE; + GstAudioVisualizer *scope; + + scope = GST_AUDIO_VISUALIZER (parent); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY: + { + /* We need to send the query upstream and add the returned latency to our + * own */ + GstClockTime min_latency, max_latency; + gboolean us_live; + GstClockTime our_latency; + guint max_samples; + gint rate = GST_AUDIO_INFO_RATE (&scope->ainfo); + + if (rate == 0) + break; + + if ((res = gst_pad_peer_query (scope->priv->sinkpad, query))) { + gst_query_parse_latency (query, &us_live, &min_latency, &max_latency); + + GST_DEBUG_OBJECT (scope, "Peer latency: min %" + GST_TIME_FORMAT " max %" GST_TIME_FORMAT, + GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); + + /* the max samples we must buffer buffer */ + max_samples = MAX (scope->req_spf, scope->priv->spf); + our_latency = gst_util_uint64_scale_int (max_samples, GST_SECOND, rate); + + GST_DEBUG_OBJECT (scope, "Our latency: %" GST_TIME_FORMAT, + GST_TIME_ARGS (our_latency)); + + /* we add some latency but only if we need to buffer more than what + * upstream gives us */ + min_latency += our_latency; + if (max_latency != -1) + max_latency += our_latency; + + GST_DEBUG_OBJECT (scope, "Calculated total latency : min %" + GST_TIME_FORMAT " max %" GST_TIME_FORMAT, + GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); + + gst_query_set_latency (query, TRUE, min_latency, max_latency); + } + break; + } + default: + res = gst_pad_query_default (pad, parent, query); + break; + } + + return res; +} + +static GstStateChangeReturn +gst_audio_visualizer_change_state (GstElement * element, + GstStateChange transition) +{ + GstStateChangeReturn ret; + GstAudioVisualizer *scope; + + scope = GST_AUDIO_VISUALIZER (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_audio_visualizer_reset (scope); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_audio_visualizer_set_allocation (scope, NULL, NULL, NULL, NULL); + break; + case GST_STATE_CHANGE_READY_TO_NULL: + break; + default: + break; + } + + return ret; +} diff --git a/gst/goom/gstaudiovisualizer.h b/gst/goom/gstaudiovisualizer.h new file mode 100644 index 0000000000..0fdb1fa3db --- /dev/null +++ b/gst/goom/gstaudiovisualizer.h @@ -0,0 +1,105 @@ +/* GStreamer + * Copyright (C) <2011> Stefan Kost + * Copyright (C) <2015> Luis de Bethencourt + * + * gstaudiovisualizer.c: base class for audio visualisation elements + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_AUDIO_VISUALIZER_H__ +#define __GST_AUDIO_VISUALIZER_H__ + +#include +#include + +#include +#include +#include + +G_BEGIN_DECLS +#define GST_TYPE_AUDIO_VISUALIZER (gst_audio_visualizer_get_type()) +#define GST_AUDIO_VISUALIZER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AUDIO_VISUALIZER,GstAudioVisualizer)) +#define GST_AUDIO_VISUALIZER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AUDIO_VISUALIZER,GstAudioVisualizerClass)) +#define GST_AUDIO_VISUALIZER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_AUDIO_VISUALIZER,GstAudioVisualizerClass)) +#define GST_IS_SYNAESTHESIA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AUDIO_VISUALIZER)) +#define GST_IS_SYNAESTHESIA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AUDIO_VISUALIZER)) +typedef struct _GstAudioVisualizer GstAudioVisualizer; +typedef struct _GstAudioVisualizerClass GstAudioVisualizerClass; +typedef struct _GstAudioVisualizerPrivate GstAudioVisualizerPrivate; + +typedef void (*GstAudioVisualizerShaderFunc)(GstAudioVisualizer *scope, const GstVideoFrame *s, GstVideoFrame *d); + +/** + * GstAudioVisualizerShader: + * @GST_AUDIO_VISUALIZER_SHADER_NONE: no shading + * @GST_AUDIO_VISUALIZER_SHADER_FADE: plain fading + * @GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_UP: fade and move up + * @GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_DOWN: fade and move down + * @GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_LEFT: fade and move left + * @GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_RIGHT: fade and move right + * @GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_OUT: fade and move horizontally out + * @GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_IN: fade and move horizontally in + * @GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_OUT: fade and move vertically out + * @GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_IN: fade and move vertically in + * + * Different types of supported background shading functions. + */ +typedef enum { + GST_AUDIO_VISUALIZER_SHADER_NONE, + GST_AUDIO_VISUALIZER_SHADER_FADE, + GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_UP, + GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_DOWN, + GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_LEFT, + GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_RIGHT, + GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_OUT, + GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_HORIZ_IN, + GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_OUT, + GST_AUDIO_VISUALIZER_SHADER_FADE_AND_MOVE_VERT_IN +} GstAudioVisualizerShader; + +struct _GstAudioVisualizer +{ + GstElement parent; + + guint req_spf; /* min samples per frame wanted by the subclass */ + + /* video state */ + GstVideoInfo vinfo; + + /* audio state */ + GstAudioInfo ainfo; + + /* */ + GstAudioVisualizerPrivate *priv; +}; + +struct _GstAudioVisualizerClass +{ + GstElementClass parent_class; + + /* virtual function, called whenever the format changes */ + gboolean (*setup) (GstAudioVisualizer * scope); + + /* virtual function for rendering a frame */ + gboolean (*render) (GstAudioVisualizer * scope, GstBuffer * audio, GstVideoFrame * video); + + gboolean (*decide_allocation) (GstAudioVisualizer * scope, GstQuery *query); +}; + +GType gst_audio_visualizer_get_type (void); + +G_END_DECLS +#endif /* __GST_AUDIO_VISUALIZER_H__ */ diff --git a/gst/goom/gstgoom.c b/gst/goom/gstgoom.c index 02715f9d7d..6700f75c9d 100644 --- a/gst/goom/gstgoom.c +++ b/gst/goom/gstgoom.c @@ -2,6 +2,7 @@ * Copyright (C) <2001> Richard Boulton * (C) <2006> Wim Taymans * (C) <2011> Wim Taymans + * (C) <2015> Luis de Bethencourt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -39,10 +40,7 @@ #endif #include -#include #include "gstgoom.h" -#include -#include #include "goom.h" #if HAVE_ORC @@ -54,8 +52,6 @@ GST_DEBUG_CATEGORY (goom_debug); #define DEFAULT_WIDTH 320 #define DEFAULT_HEIGHT 240 -#define DEFAULT_FPS_N 25 -#define DEFAULT_FPS_D 1 /* signals and args */ enum @@ -66,7 +62,7 @@ enum enum { - PROP_0 + ARG_0 /* FILL ME */ }; @@ -97,34 +93,23 @@ static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", static void gst_goom_finalize (GObject * object); -static void gst_goom_reset (GstGoom * goom); -static GstStateChangeReturn gst_goom_change_state (GstElement * element, - GstStateChange transition); +static gboolean gst_goom_setup (GstAudioVisualizer * base); +static gboolean gst_goom_render (GstAudioVisualizer * base, GstBuffer * audio, + GstVideoFrame * video); -static GstFlowReturn gst_goom_chain (GstPad * pad, GstObject * parent, - GstBuffer * buffer); -static gboolean gst_goom_src_event (GstPad * pad, GstObject * parent, - GstEvent * event); -static gboolean gst_goom_sink_event (GstPad * pad, GstObject * parent, - GstEvent * event); - -static gboolean gst_goom_src_query (GstPad * pad, GstObject * parent, - GstQuery * query); - -static gboolean gst_goom_src_negotiate (GstGoom * goom); - -#define gst_goom_parent_class parent_class -G_DEFINE_TYPE (GstGoom, gst_goom, GST_TYPE_ELEMENT); +G_DEFINE_TYPE (GstGoom, gst_goom, GST_TYPE_AUDIO_VISUALIZER); static void gst_goom_class_init (GstGoomClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; + GstAudioVisualizerClass *visualizer_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; + visualizer_class = (GstAudioVisualizerClass *) klass; gobject_class->finalize = gst_goom_finalize; @@ -137,40 +122,18 @@ gst_goom_class_init (GstGoomClass * klass) gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&src_template)); - gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_goom_change_state); + visualizer_class->setup = GST_DEBUG_FUNCPTR (gst_goom_setup); + visualizer_class->render = GST_DEBUG_FUNCPTR (gst_goom_render); } static void gst_goom_init (GstGoom * goom) { - /* create the sink and src pads */ - goom->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink"); - gst_pad_set_chain_function (goom->sinkpad, - GST_DEBUG_FUNCPTR (gst_goom_chain)); - gst_pad_set_event_function (goom->sinkpad, - GST_DEBUG_FUNCPTR (gst_goom_sink_event)); - gst_element_add_pad (GST_ELEMENT (goom), goom->sinkpad); - - goom->srcpad = gst_pad_new_from_static_template (&src_template, "src"); - gst_pad_set_event_function (goom->srcpad, - GST_DEBUG_FUNCPTR (gst_goom_src_event)); - gst_pad_set_query_function (goom->srcpad, - GST_DEBUG_FUNCPTR (gst_goom_src_query)); - gst_element_add_pad (GST_ELEMENT (goom), goom->srcpad); - - goom->adapter = gst_adapter_new (); - goom->width = DEFAULT_WIDTH; goom->height = DEFAULT_HEIGHT; - goom->fps_n = DEFAULT_FPS_N; /* desired frame rate */ - goom->fps_d = DEFAULT_FPS_D; /* desired frame rate */ goom->channels = 0; - goom->rate = 0; - goom->duration = 0; goom->plugin = goom_init (goom->width, goom->height); - - gst_goom_reset (goom); } static void @@ -181,512 +144,50 @@ gst_goom_finalize (GObject * object) goom_close (goom->plugin); goom->plugin = NULL; - g_object_unref (goom->adapter); - if (goom->pool) - gst_object_unref (goom->pool); - - G_OBJECT_CLASS (parent_class)->finalize (object); -} - -static void -gst_goom_reset (GstGoom * goom) -{ - gst_adapter_clear (goom->adapter); - gst_segment_init (&goom->segment, GST_FORMAT_UNDEFINED); - - GST_OBJECT_LOCK (goom); - goom->proportion = 1.0; - goom->earliest_time = GST_CLOCK_TIME_NONE; - GST_OBJECT_UNLOCK (goom); - - goom->dropped = 0; - goom->processed = 0; + G_OBJECT_CLASS (gst_goom_parent_class)->finalize (object); } static gboolean -gst_goom_sink_setcaps (GstGoom * goom, GstCaps * caps) +gst_goom_setup (GstAudioVisualizer * base) { - GstStructure *structure; + GstGoom *goom = GST_GOOM (base); - structure = gst_caps_get_structure (caps, 0); - - gst_structure_get_int (structure, "channels", &goom->channels); - gst_structure_get_int (structure, "rate", &goom->rate); - - goom->bps = goom->channels * sizeof (gint16); - - return gst_goom_src_negotiate (goom); -} - -static gboolean -gst_goom_src_setcaps (GstGoom * goom, GstCaps * caps) -{ - GstStructure *structure; - gboolean res; - - structure = gst_caps_get_structure (caps, 0); - if (!gst_structure_get_int (structure, "width", &goom->width) || - !gst_structure_get_int (structure, "height", &goom->height) || - !gst_structure_get_fraction (structure, "framerate", &goom->fps_n, - &goom->fps_d)) - goto error; - - goom_set_resolution (goom->plugin, goom->width, goom->height); - - /* size of the output buffer in bytes, depth is always 4 bytes */ - goom->outsize = goom->width * goom->height * 4; - goom->duration = - gst_util_uint64_scale_int (GST_SECOND, goom->fps_d, goom->fps_n); - goom->spf = gst_util_uint64_scale_int (goom->rate, goom->fps_d, goom->fps_n); - goom->bpf = goom->spf * goom->bps; - - GST_DEBUG_OBJECT (goom, "dimension %dx%d, framerate %d/%d, spf %d", - goom->width, goom->height, goom->fps_n, goom->fps_d, goom->spf); - - res = gst_pad_set_caps (goom->srcpad, caps); - - return res; - - /* ERRORS */ -error: - { - GST_DEBUG_OBJECT (goom, "error parsing caps"); - return FALSE; - } -} - -static gboolean -gst_goom_src_negotiate (GstGoom * goom) -{ - GstCaps *othercaps, *target; - GstStructure *structure; - GstCaps *templ; - GstQuery *query; - GstBufferPool *pool; - GstStructure *config; - guint size, min, max; - - templ = gst_pad_get_pad_template_caps (goom->srcpad); - - GST_DEBUG_OBJECT (goom, "performing negotiation"); - - /* see what the peer can do */ - othercaps = gst_pad_peer_query_caps (goom->srcpad, NULL); - if (othercaps) { - target = gst_caps_intersect (othercaps, templ); - gst_caps_unref (othercaps); - gst_caps_unref (templ); - - if (gst_caps_is_empty (target)) - goto no_format; - - target = gst_caps_truncate (target); - } else { - target = templ; - } - - target = gst_caps_make_writable (target); - structure = gst_caps_get_structure (target, 0); - gst_structure_fixate_field_nearest_int (structure, "width", DEFAULT_WIDTH); - gst_structure_fixate_field_nearest_int (structure, "height", DEFAULT_HEIGHT); - gst_structure_fixate_field_nearest_fraction (structure, "framerate", - DEFAULT_FPS_N, DEFAULT_FPS_D); - - gst_goom_src_setcaps (goom, target); - - /* try to get a bufferpool now */ - /* find a pool for the negotiated caps now */ - query = gst_query_new_allocation (target, TRUE); - - if (!gst_pad_peer_query (goom->srcpad, query)) { - /* no problem, we use the query defaults */ - GST_DEBUG_OBJECT (goom, "ALLOCATION query failed"); - } - - if (gst_query_get_n_allocation_pools (query) > 0) { - /* we got configuration from our peer, parse them */ - gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); - } else { - pool = NULL; - size = goom->outsize; - min = max = 0; - } - - if (pool == NULL) { - /* we did not get a pool, make one ourselves then */ - pool = gst_buffer_pool_new (); - } - - config = gst_buffer_pool_get_config (pool); - gst_buffer_pool_config_set_params (config, target, size, min, max); - gst_buffer_pool_set_config (pool, config); - - if (goom->pool) { - gst_buffer_pool_set_active (goom->pool, FALSE); - gst_object_unref (goom->pool); - } - goom->pool = pool; - - /* and activate */ - gst_buffer_pool_set_active (pool, TRUE); - - gst_caps_unref (target); + goom->width = GST_VIDEO_INFO_WIDTH (&base->vinfo); + goom->height = GST_VIDEO_INFO_HEIGHT (&base->vinfo); return TRUE; - -no_format: - { - gst_caps_unref (target); - return FALSE; - } } static gboolean -gst_goom_src_event (GstPad * pad, GstObject * parent, GstEvent * event) +gst_goom_render (GstAudioVisualizer * base, GstBuffer * audio, + GstVideoFrame * video) { - gboolean res; - GstGoom *goom; + GstGoom *goom = GST_GOOM (base); + GstMapInfo amap; + gint16 datain[2][GOOM_SAMPLES]; + gint16 *adata; + gint i; - goom = GST_GOOM (parent); + /* get next GOOM_SAMPLES, we have at least this amount of samples */ + gst_buffer_map (audio, &amap, GST_MAP_READ); + adata = (gint16 *) amap.data; - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_QOS: - { - gdouble proportion; - GstClockTimeDiff diff; - GstClockTime timestamp; - - gst_event_parse_qos (event, NULL, &proportion, &diff, ×tamp); - - /* save stuff for the _chain() function */ - GST_OBJECT_LOCK (goom); - goom->proportion = proportion; - if (diff >= 0) - /* we're late, this is a good estimate for next displayable - * frame (see part-qos.txt) */ - goom->earliest_time = timestamp + 2 * diff + goom->duration; - else - goom->earliest_time = timestamp + diff; - GST_OBJECT_UNLOCK (goom); - - res = gst_pad_event_default (pad, parent, event); - break; + if (goom->channels == 2) { + for (i = 0; i < GOOM_SAMPLES; i++) { + datain[0][i] = *adata++; + datain[1][i] = *adata++; } - default: - res = gst_pad_event_default (pad, parent, event); - break; - } - - return res; -} - -static gboolean -gst_goom_sink_event (GstPad * pad, GstObject * parent, GstEvent * event) -{ - gboolean res; - GstGoom *goom; - - goom = GST_GOOM (parent); - - switch (GST_EVENT_TYPE (event)) { - case GST_EVENT_CAPS: - { - GstCaps *caps; - - gst_event_parse_caps (event, &caps); - res = gst_goom_sink_setcaps (goom, caps); - gst_event_unref (event); - break; + } else { + for (i = 0; i < GOOM_SAMPLES; i++) { + datain[0][i] = *adata; + datain[1][i] = *adata++; } - case GST_EVENT_FLUSH_STOP: - gst_goom_reset (goom); - res = gst_pad_event_default (pad, parent, event); - break; - case GST_EVENT_SEGMENT: - { - /* the newsegment values are used to clip the input samples - * and to convert the incomming timestamps to running time so - * we can do QoS */ - gst_event_copy_segment (event, &goom->segment); - - res = gst_pad_event_default (pad, parent, event); - break; - } - default: - res = gst_pad_event_default (pad, parent, event); - break; } - return res; -} + video->data[0] = goom_update (goom->plugin, datain, 0, 0); + gst_buffer_unmap (audio, &amap); -static gboolean -gst_goom_src_query (GstPad * pad, GstObject * parent, GstQuery * query) -{ - gboolean res = FALSE; - GstGoom *goom; - - goom = GST_GOOM (parent); - - switch (GST_QUERY_TYPE (query)) { - case GST_QUERY_LATENCY: - { - /* We need to send the query upstream and add the returned latency to our - * own */ - GstClockTime min_latency, max_latency; - gboolean us_live; - GstClockTime our_latency; - guint max_samples; - - if (goom->rate == 0) - break; - - if ((res = gst_pad_peer_query (goom->sinkpad, query))) { - gst_query_parse_latency (query, &us_live, &min_latency, &max_latency); - - GST_DEBUG_OBJECT (goom, "Peer latency: min %" - GST_TIME_FORMAT " max %" GST_TIME_FORMAT, - GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); - - /* the max samples we must buffer buffer */ - max_samples = MAX (GOOM_SAMPLES, goom->spf); - our_latency = - gst_util_uint64_scale_int (max_samples, GST_SECOND, goom->rate); - - GST_DEBUG_OBJECT (goom, "Our latency: %" GST_TIME_FORMAT, - GST_TIME_ARGS (our_latency)); - - /* we add some latency but only if we need to buffer more than what - * upstream gives us */ - min_latency += our_latency; - if (max_latency != -1) - max_latency += our_latency; - - GST_DEBUG_OBJECT (goom, "Calculated total latency : min %" - GST_TIME_FORMAT " max %" GST_TIME_FORMAT, - GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency)); - - gst_query_set_latency (query, TRUE, min_latency, max_latency); - } - break; - } - default: - res = gst_pad_query_default (pad, parent, query); - break; - } - - return res; -} - -/* make sure we are negotiated */ -static GstFlowReturn -ensure_negotiated (GstGoom * goom) -{ - if (gst_pad_check_reconfigure (goom->srcpad)) { - if (!gst_goom_src_negotiate (goom)) - return GST_FLOW_NOT_NEGOTIATED; - } - return GST_FLOW_OK; -} - - -static GstFlowReturn -gst_goom_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer) -{ - GstGoom *goom; - GstFlowReturn ret; - GstBuffer *outbuf = NULL; - - goom = GST_GOOM (parent); - if (goom->bps == 0) { - gst_buffer_unref (buffer); - ret = GST_FLOW_NOT_NEGOTIATED; - goto beach; - } - - /* Make sure have an output format */ - ret = ensure_negotiated (goom); - if (ret != GST_FLOW_OK) { - gst_buffer_unref (buffer); - goto beach; - } - - /* don't try to combine samples from discont buffer */ - if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) { - gst_adapter_clear (goom->adapter); - } - - GST_DEBUG_OBJECT (goom, - "Input buffer has %" G_GSIZE_FORMAT " samples, time=%" G_GUINT64_FORMAT, - gst_buffer_get_size (buffer) / goom->bps, GST_BUFFER_TIMESTAMP (buffer)); - - /* Collect samples until we have enough for an output frame */ - gst_adapter_push (goom->adapter, buffer); - - ret = GST_FLOW_OK; - - while (TRUE) { - const guint16 *data; - guchar *out_frame; - gint i; - guint avail, to_flush; - guint64 dist, timestamp; - - avail = gst_adapter_available (goom->adapter); - GST_DEBUG_OBJECT (goom, "avail now %u", avail); - - /* we need GOOM_SAMPLES to get a meaningful result from goom. */ - if (avail < (GOOM_SAMPLES * goom->bps)) - break; - - /* we also need enough samples to produce one frame at least */ - if (avail < goom->bpf) - break; - - GST_DEBUG_OBJECT (goom, "processing buffer"); - - /* get timestamp of the current adapter byte */ - timestamp = gst_adapter_prev_pts (goom->adapter, &dist); - if (GST_CLOCK_TIME_IS_VALID (timestamp)) { - /* convert bytes to time */ - dist /= goom->bps; - timestamp += gst_util_uint64_scale_int (dist, GST_SECOND, goom->rate); - } - - /* check for QoS, don't compute buffers that are known to be late */ - if (GST_CLOCK_TIME_IS_VALID (timestamp)) { - GstClockTime earliest_time; - gdouble proportion; - gint64 qostime; - - qostime = gst_segment_to_running_time (&goom->segment, GST_FORMAT_TIME, - timestamp) + goom->duration; - - GST_OBJECT_LOCK (goom); - earliest_time = goom->earliest_time; - proportion = goom->proportion; - GST_OBJECT_UNLOCK (goom); - - if (GST_CLOCK_TIME_IS_VALID (earliest_time) && qostime <= earliest_time) { - GstClockTime stream_time, jitter; - GstMessage *qos_msg; - - GST_WARNING_OBJECT (goom, - "QoS: skip ts: %" GST_TIME_FORMAT ", earliest: %" GST_TIME_FORMAT, - GST_TIME_ARGS (qostime), GST_TIME_ARGS (earliest_time)); - - goom->dropped++; - stream_time = gst_segment_to_stream_time (&goom->segment, - GST_FORMAT_TIME, timestamp); - jitter = GST_CLOCK_DIFF (qostime, earliest_time); - qos_msg = gst_message_new_qos (GST_OBJECT (goom), FALSE, qostime, - stream_time, timestamp, GST_BUFFER_DURATION (buffer)); - gst_message_set_qos_values (qos_msg, jitter, proportion, 1000000); - gst_message_set_qos_stats (qos_msg, GST_FORMAT_BUFFERS, - goom->processed, goom->dropped); - gst_element_post_message (GST_ELEMENT (goom), qos_msg); - goto skip; - } - } - - goom->processed++; - - /* get next GOOM_SAMPLES, we have at least this amount of samples */ - data = - (const guint16 *) gst_adapter_map (goom->adapter, - GOOM_SAMPLES * goom->bps); - - if (goom->channels == 2) { - for (i = 0; i < GOOM_SAMPLES; i++) { - goom->datain[0][i] = *data++; - goom->datain[1][i] = *data++; - } - } else { - for (i = 0; i < GOOM_SAMPLES; i++) { - goom->datain[0][i] = *data; - goom->datain[1][i] = *data++; - } - } - - /* alloc a buffer if we don't have one yet, this happens - * when we pushed a buffer in this while loop before */ - if (outbuf == NULL) { - GST_DEBUG_OBJECT (goom, "allocating output buffer"); - ret = gst_buffer_pool_acquire_buffer (goom->pool, &outbuf, NULL); - if (ret != GST_FLOW_OK) { - gst_adapter_unmap (goom->adapter); - goto beach; - } - } - - GST_BUFFER_TIMESTAMP (outbuf) = timestamp; - GST_BUFFER_DURATION (outbuf) = goom->duration; - - out_frame = (guchar *) goom_update (goom->plugin, goom->datain, 0, 0); - gst_buffer_fill (outbuf, 0, out_frame, goom->outsize); - - gst_adapter_unmap (goom->adapter); - - GST_DEBUG ("Pushing frame with time=%" GST_TIME_FORMAT ", duration=%" - GST_TIME_FORMAT, GST_TIME_ARGS (timestamp), - GST_TIME_ARGS (goom->duration)); - - ret = gst_pad_push (goom->srcpad, outbuf); - outbuf = NULL; - - skip: - /* Now flush the samples we needed for this frame, which might be more than - * the samples we used (GOOM_SAMPLES). */ - to_flush = goom->bpf; - - GST_DEBUG_OBJECT (goom, "finished frame, flushing %u bytes from input", - to_flush); - gst_adapter_flush (goom->adapter, to_flush); - - if (ret != GST_FLOW_OK) - break; - } - - if (outbuf != NULL) - gst_buffer_unref (outbuf); - -beach: - - return ret; -} - -static GstStateChangeReturn -gst_goom_change_state (GstElement * element, GstStateChange transition) -{ - GstGoom *goom = GST_GOOM (element); - GstStateChangeReturn ret; - - switch (transition) { - case GST_STATE_CHANGE_NULL_TO_READY: - break; - case GST_STATE_CHANGE_READY_TO_PAUSED: - gst_goom_reset (goom); - break; - default: - break; - } - - ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); - - switch (transition) { - case GST_STATE_CHANGE_PAUSED_TO_READY: - if (goom->pool) { - gst_buffer_pool_set_active (goom->pool, FALSE); - gst_object_replace ((GstObject **) & goom->pool, NULL); - } - break; - case GST_STATE_CHANGE_READY_TO_NULL: - break; - default: - break; - } - - return ret; + return TRUE; } static gboolean diff --git a/gst/goom/gstgoom.h b/gst/goom/gstgoom.h index 5d1b82afb2..f8cb434d17 100644 --- a/gst/goom/gstgoom.h +++ b/gst/goom/gstgoom.h @@ -1,5 +1,6 @@ /* gstgoom.c: implementation of goom drawing element * Copyright (C) <2001> Richard Boulton + * Copyright (C) <2015> Luis de Bethencourt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -20,12 +21,11 @@ #ifndef __GST_GOOM_H__ #define __GST_GOOM_H__ -G_BEGIN_DECLS - -#include -#include +#include "gstaudiovisualizer.h" #include "goom.h" +G_BEGIN_DECLS + #define GOOM_SAMPLES 512 #define GST_TYPE_GOOM (gst_goom_get_type()) @@ -39,52 +39,26 @@ typedef struct _GstGoomClass GstGoomClass; struct _GstGoom { - GstElement element; - - /* pads */ - GstPad *sinkpad, *srcpad; - GstAdapter *adapter; + GstAudioVisualizer parent; /* input tracking */ - gint rate; gint channels; - guint bps; /* video state */ - gint fps_n; - gint fps_d; gint width; gint height; - GstClockTime duration; - guint outsize; - GstBufferPool *pool; - - guint dropped; /* frames dropped / not dropped */ - guint processed; - - /* samples per frame */ - guint spf; - /* bytes per frame */ - guint bpf; /* goom stuff */ - gint16 datain[2][GOOM_SAMPLES]; PluginInfo *plugin; - - /* segment state */ - GstSegment segment; - - /* QoS stuff *//* with LOCK */ - gdouble proportion; - GstClockTime earliest_time; }; struct _GstGoomClass { - GstElementClass parent_class; + GstAudioVisualizerClass parent_class; }; GType gst_goom_get_type (void); +gboolean gst_goom_plugin_init (GstPlugin * plugin); G_END_DECLS