1 /* GStreamer
2  * Copyright (C) <2011> Stefan Kost <ensonic@users.sf.net>
3  *
4  * gstspacescope.c: simple stereo visualizer
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 /**
22  * SECTION:element-spacescope
23  * @title: spacescope
24  * @see_also: goom
25  *
26  * Spacescope is a simple audio visualisation element. It maps the left and
27  * right channel to x and y coordinates.
28  *
29  * ## Example launch line
30  * |[
31  * gst-launch-1.0 audiotestsrc ! audioconvert ! spacescope ! ximagesink
32  * ]|
33  *
34  */
35 #ifdef HAVE_CONFIG_H
36 #include "config.h"
37 #endif
38 
39 #include <stdlib.h>
40 #include "gstspacescope.h"
41 
42 #if G_BYTE_ORDER == G_BIG_ENDIAN
43 #define RGB_ORDER "xRGB"
44 #else
45 #define RGB_ORDER "BGRx"
46 #endif
47 
48 static GstStaticPadTemplate gst_space_scope_src_template =
49 GST_STATIC_PAD_TEMPLATE ("src",
50     GST_PAD_SRC,
51     GST_PAD_ALWAYS,
52     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (RGB_ORDER))
53     );
54 
55 static GstStaticPadTemplate gst_space_scope_sink_template =
56 GST_STATIC_PAD_TEMPLATE ("sink",
57     GST_PAD_SINK,
58     GST_PAD_ALWAYS,
59     GST_STATIC_CAPS ("audio/x-raw, "
60         "format = (string) " GST_AUDIO_NE (S16) ", "
61         "layout = (string) interleaved, "
62         "rate = (int) [ 8000, 96000 ], "
63         "channels = (int) 2, " "channel-mask = (bitmask) 0x3")
64     );
65 
66 
67 GST_DEBUG_CATEGORY_STATIC (space_scope_debug);
68 #define GST_CAT_DEFAULT space_scope_debug
69 
70 enum
71 {
72   PROP_0,
73   PROP_STYLE
74 };
75 
76 enum
77 {
78   STYLE_DOTS = 0,
79   STYLE_LINES,
80   STYLE_COLOR_DOTS,
81   STYLE_COLOR_LINES,
82   NUM_STYLES
83 };
84 
85 #define GST_TYPE_SPACE_SCOPE_STYLE (gst_space_scope_style_get_type ())
86 static GType
gst_space_scope_style_get_type(void)87 gst_space_scope_style_get_type (void)
88 {
89   static GType gtype = 0;
90 
91   if (gtype == 0) {
92     static const GEnumValue values[] = {
93       {STYLE_DOTS, "draw dots (default)", "dots"},
94       {STYLE_LINES, "draw lines", "lines"},
95       {STYLE_COLOR_DOTS, "draw color dots", "color-dots"},
96       {STYLE_COLOR_LINES, "draw color lines", "color-lines"},
97       {0, NULL, NULL}
98     };
99 
100     gtype = g_enum_register_static ("GstSpaceScopeStyle", values);
101   }
102   return gtype;
103 }
104 
105 static void gst_space_scope_set_property (GObject * object, guint prop_id,
106     const GValue * value, GParamSpec * pspec);
107 static void gst_space_scope_get_property (GObject * object, guint prop_id,
108     GValue * value, GParamSpec * pspec);
109 
110 static void render_dots (GstAudioVisualizer * base, guint32 * vdata,
111     gint16 * adata, guint num_samples);
112 static void render_lines (GstAudioVisualizer * base, guint32 * vdata,
113     gint16 * adata, guint num_samples);
114 static void render_color_dots (GstAudioVisualizer * base, guint32 * vdata,
115     gint16 * adata, guint num_samples);
116 static void render_color_lines (GstAudioVisualizer * base, guint32 * vdata,
117     gint16 * adata, guint num_samples);
118 
119 static gboolean gst_space_scope_render (GstAudioVisualizer * scope,
120     GstBuffer * audio, GstVideoFrame * video);
121 
122 
123 G_DEFINE_TYPE (GstSpaceScope, gst_space_scope, GST_TYPE_AUDIO_VISUALIZER);
124 
125 static void
gst_space_scope_class_init(GstSpaceScopeClass * g_class)126 gst_space_scope_class_init (GstSpaceScopeClass * g_class)
127 {
128   GObjectClass *gobject_class = (GObjectClass *) g_class;
129   GstElementClass *element_class = (GstElementClass *) g_class;
130   GstAudioVisualizerClass *scope_class = (GstAudioVisualizerClass *) g_class;
131 
132   gst_element_class_set_static_metadata (element_class, "Stereo visualizer",
133       "Visualization",
134       "Simple stereo visualizer", "Stefan Kost <ensonic@users.sf.net>");
135 
136   gst_element_class_add_static_pad_template (element_class,
137       &gst_space_scope_src_template);
138   gst_element_class_add_static_pad_template (element_class,
139       &gst_space_scope_sink_template);
140 
141   gobject_class->set_property = gst_space_scope_set_property;
142   gobject_class->get_property = gst_space_scope_get_property;
143 
144   scope_class->render = GST_DEBUG_FUNCPTR (gst_space_scope_render);
145 
146   g_object_class_install_property (gobject_class, PROP_STYLE,
147       g_param_spec_enum ("style", "drawing style",
148           "Drawing styles for the space scope display.",
149           GST_TYPE_SPACE_SCOPE_STYLE, STYLE_DOTS,
150           G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
151 }
152 
153 static void
gst_space_scope_init(GstSpaceScope * scope)154 gst_space_scope_init (GstSpaceScope * scope)
155 {
156   /* do nothing */
157 }
158 
159 static void
gst_space_scope_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)160 gst_space_scope_set_property (GObject * object, guint prop_id,
161     const GValue * value, GParamSpec * pspec)
162 {
163   GstSpaceScope *scope = GST_SPACE_SCOPE (object);
164 
165   switch (prop_id) {
166     case PROP_STYLE:
167       scope->style = g_value_get_enum (value);
168       switch (scope->style) {
169         case STYLE_DOTS:
170           scope->process = render_dots;
171           break;
172         case STYLE_LINES:
173           scope->process = render_lines;
174           break;
175         case STYLE_COLOR_DOTS:
176           scope->process = render_color_dots;
177           break;
178         case STYLE_COLOR_LINES:
179           scope->process = render_color_lines;
180           break;
181       }
182       break;
183     default:
184       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
185       break;
186   }
187 }
188 
189 static void
gst_space_scope_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)190 gst_space_scope_get_property (GObject * object, guint prop_id,
191     GValue * value, GParamSpec * pspec)
192 {
193   GstSpaceScope *scope = GST_SPACE_SCOPE (object);
194 
195   switch (prop_id) {
196     case PROP_STYLE:
197       g_value_set_enum (value, scope->style);
198       break;
199     default:
200       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
201       break;
202   }
203 }
204 
205 #include "gstdrawhelpers.h"
206 
207 static void
render_dots(GstAudioVisualizer * base,guint32 * vdata,gint16 * adata,guint num_samples)208 render_dots (GstAudioVisualizer * base, guint32 * vdata, gint16 * adata,
209     guint num_samples)
210 {
211   guint i, s, x, y, ox, oy;
212   gfloat dx, dy;
213   guint w = GST_VIDEO_INFO_WIDTH (&base->vinfo);
214   guint h = GST_VIDEO_INFO_HEIGHT (&base->vinfo);
215 
216   /* draw dots 1st channel x, 2nd channel y */
217   dx = w / 65536.0;
218   ox = w / 2;
219   dy = h / 65536.0;
220   oy = h / 2;
221   s = 0;
222   for (i = 0; i < num_samples; i++) {
223     x = (guint) (ox + (gfloat) adata[s++] * dx);
224     y = (guint) (oy + (gfloat) adata[s++] * dy);
225     draw_dot (vdata, x, y, w, 0x00FFFFFF);
226   }
227 }
228 
229 static void
render_lines(GstAudioVisualizer * base,guint32 * vdata,gint16 * adata,guint num_samples)230 render_lines (GstAudioVisualizer * base, guint32 * vdata, gint16 * adata,
231     guint num_samples)
232 {
233   guint i, s, x, y, ox, oy;
234   gfloat dx, dy;
235   guint w = GST_VIDEO_INFO_WIDTH (&base->vinfo);
236   guint h = GST_VIDEO_INFO_HEIGHT (&base->vinfo);
237   gint x2, y2;
238 
239   /* draw lines 1st channel x, 2nd channel y */
240   dx = (w - 1) / 65536.0;
241   ox = (w - 1) / 2;
242   dy = (h - 1) / 65536.0;
243   oy = (h - 1) / 2;
244   s = 0;
245   x2 = (guint) (ox + (gfloat) adata[s++] * dx);
246   y2 = (guint) (oy + (gfloat) adata[s++] * dy);
247   for (i = 1; i < num_samples; i++) {
248     x = (guint) (ox + (gfloat) adata[s++] * dx);
249     y = (guint) (oy + (gfloat) adata[s++] * dy);
250     draw_line_aa (vdata, x2, x, y2, y, w, 0x00FFFFFF);
251     x2 = x;
252     y2 = y;
253   }
254 }
255 
256 #define CUTOFF_1 0.15
257 #define CUTOFF_2 0.45
258 #define RESONANCE (1.0/0.5)
259 
260 #define filter(il, ir) G_STMT_START {                                          \
261   f1l_h = il - (f1l_m * RESONANCE) - f1l_l;                                    \
262   f1l_m += (f1l_h * CUTOFF_1);                                                 \
263   f1l_l += (f1l_m * CUTOFF_1);                                                 \
264                                                                                \
265   f2l_h = (f1l_m + f1l_h) - (f2l_m * RESONANCE) - f2l_l;                       \
266   f2l_m += (f2l_h * CUTOFF_2);                                                 \
267   f2l_l += (f2l_m * CUTOFF_2);                                                 \
268                                                                                \
269   f1r_h = ir - (f1r_m * RESONANCE) - f1r_l;                                    \
270   f1r_m += (f1r_h * CUTOFF_1);                                                 \
271   f1r_l += (f1r_m * CUTOFF_1);                                                 \
272                                                                                \
273   f2r_h = (f1r_m + f1r_h) - (f2r_m * RESONANCE) - f2r_l;                       \
274   f2r_m += (f2r_h * CUTOFF_2);                                                 \
275   f2r_l += (f2r_m * CUTOFF_2);                                                 \
276 } G_STMT_END
277 
278 static void
render_color_dots(GstAudioVisualizer * base,guint32 * vdata,gint16 * adata,guint num_samples)279 render_color_dots (GstAudioVisualizer * base, guint32 * vdata,
280     gint16 * adata, guint num_samples)
281 {
282   GstSpaceScope *scope = (GstSpaceScope *) base;
283   guint i, s;
284   gint x, y, ox, oy;
285   gfloat dx, dy;
286   gint w = GST_VIDEO_INFO_WIDTH (&base->vinfo), w1 = w - 2;
287   gint h = GST_VIDEO_INFO_HEIGHT (&base->vinfo), h1 = h - 2;
288   gdouble il, ir;
289   gdouble f1l_l = scope->f1l_l, f1l_m = scope->f1l_m, f1l_h = scope->f1l_h;
290   gdouble f1r_l = scope->f1r_l, f1r_m = scope->f1r_m, f1r_h = scope->f1r_h;
291   gdouble f2l_l = scope->f2l_l, f2l_m = scope->f2l_m, f2l_h = scope->f2l_h;
292   gdouble f2r_l = scope->f2r_l, f2r_m = scope->f2r_m, f2r_h = scope->f2r_h;
293 
294   /* draw dots 1st channel x, 2nd channel y */
295   ox = w / 2;
296   oy = h / 2;
297   dx = w / 65536.0;
298   dy = h / 65536.0;
299   s = 0;
300   for (i = 0; i < num_samples; i++) {
301     il = (gdouble) adata[s++];
302     ir = (gdouble) adata[s++];
303 
304     filter (il, ir);
305 
306     x = (gint) (ox + f1l_l * dx);
307     y = (gint) (oy + f1r_l * dy);
308     x = CLAMP (x, 0, w1);
309     y = CLAMP (y, 0, h1);
310     draw_dot_c (vdata, x, y, w, 0x00FF0000);
311 
312     x = (gint) (ox + f2l_l * dx);
313     y = (gint) (oy + f2r_l * dy);
314     x = CLAMP (x, 0, w1);
315     y = CLAMP (y, 0, h1);
316     draw_dot_c (vdata, x, y, w, 0x0000FF00);
317 
318     x = (gint) (ox + (f2l_m + f2l_h) * dx);
319     y = (gint) (oy + (f2r_m + f2r_h) * dy);
320     x = CLAMP (x, 0, w1);
321     y = CLAMP (y, 0, h1);
322     draw_dot_c (vdata, x, y, w, 0x000000FF);
323   }
324 
325   scope->f1l_l = f1l_l;
326   scope->f1l_m = f1l_m;
327   scope->f1l_h = f1l_h;
328   scope->f1r_l = f1r_l;
329   scope->f1r_m = f1r_m;
330   scope->f1r_h = f1r_h;
331   scope->f2l_l = f2l_l;
332   scope->f2l_m = f2l_m;
333   scope->f2l_h = f2l_h;
334   scope->f2r_l = f2r_l;
335   scope->f2r_m = f2r_m;
336   scope->f2r_h = f2r_h;
337 }
338 
339 static void
render_color_lines(GstAudioVisualizer * base,guint32 * vdata,gint16 * adata,guint num_samples)340 render_color_lines (GstAudioVisualizer * base, guint32 * vdata,
341     gint16 * adata, guint num_samples)
342 {
343   GstSpaceScope *scope = (GstSpaceScope *) base;
344   guint i, s;
345   gint x, y, ox, oy;
346   gfloat dx, dy;
347   gint w = GST_VIDEO_INFO_WIDTH (&base->vinfo), w1 = w - 2;
348   gint h = GST_VIDEO_INFO_HEIGHT (&base->vinfo), h1 = h - 2;
349   gdouble il, ir;
350   gdouble f1l_l = scope->f1l_l, f1l_m = scope->f1l_m, f1l_h = scope->f1l_h;
351   gdouble f1r_l = scope->f1r_l, f1r_m = scope->f1r_m, f1r_h = scope->f1r_h;
352   gdouble f2l_l = scope->f2l_l, f2l_m = scope->f2l_m, f2l_h = scope->f2l_h;
353   gdouble f2r_l = scope->f2r_l, f2r_m = scope->f2r_m, f2r_h = scope->f2r_h;
354   gint x2, y2, x3, y3, x4, y4;
355 
356   /* draw lines 1st channel x, 2nd channel y */
357   ox = w / 2;
358   oy = h / 2;
359   dx = w / 65536.0;
360   dy = h / 65536.0;
361   s = 0;
362 
363   /* do first pixels */
364   il = (gdouble) adata[s++];
365   ir = (gdouble) adata[s++];
366 
367   filter (il, ir);
368 
369   x = (gint) (ox + f1l_l * dx);
370   y = (gint) (oy + f1r_l * dy);
371   x2 = CLAMP (x, 0, w1);
372   y2 = CLAMP (y, 0, h1);
373 
374   x = (gint) (ox + f2l_l * dx);
375   y = (gint) (oy + f2r_l * dy);
376   x3 = CLAMP (x, 0, w1);
377   y3 = CLAMP (y, 0, h1);
378 
379   x = (gint) (ox + (f2l_m + f2l_h) * dx);
380   y = (gint) (oy + (f2r_m + f2r_h) * dy);
381   x4 = CLAMP (x, 0, w1);
382   y4 = CLAMP (y, 0, h1);
383 
384   for (i = 1; i < num_samples; i++) {
385     il = (gdouble) adata[s++];
386     ir = (gdouble) adata[s++];
387 
388     filter (il, ir);
389 
390     x = (gint) (ox + f1l_l * dx);
391     y = (gint) (oy + f1r_l * dy);
392     x = CLAMP (x, 0, w1);
393     y = CLAMP (y, 0, h1);
394     draw_line_aa (vdata, x2, x, y2, y, w, 0x00FF0000);
395     x2 = x;
396     y2 = y;
397 
398     x = (gint) (ox + f2l_l * dx);
399     y = (gint) (oy + f2r_l * dy);
400     x = CLAMP (x, 0, w1);
401     y = CLAMP (y, 0, h1);
402     draw_line_aa (vdata, x3, x, y3, y, w, 0x0000FF00);
403     x3 = x;
404     y3 = y;
405 
406     x = (gint) (ox + (f2l_m + f2l_h) * dx);
407     y = (gint) (oy + (f2r_m + f2r_h) * dy);
408     x = CLAMP (x, 0, w1);
409     y = CLAMP (y, 0, h1);
410     draw_line_aa (vdata, x4, x, y4, y, w, 0x000000FF);
411     x4 = x;
412     y4 = y;
413   }
414 
415   scope->f1l_l = f1l_l;
416   scope->f1l_m = f1l_m;
417   scope->f1l_h = f1l_h;
418   scope->f1r_l = f1r_l;
419   scope->f1r_m = f1r_m;
420   scope->f1r_h = f1r_h;
421   scope->f2l_l = f2l_l;
422   scope->f2l_m = f2l_m;
423   scope->f2l_h = f2l_h;
424   scope->f2r_l = f2r_l;
425   scope->f2r_m = f2r_m;
426   scope->f2r_h = f2r_h;
427 }
428 
429 static gboolean
gst_space_scope_render(GstAudioVisualizer * base,GstBuffer * audio,GstVideoFrame * video)430 gst_space_scope_render (GstAudioVisualizer * base, GstBuffer * audio,
431     GstVideoFrame * video)
432 {
433   GstSpaceScope *scope = GST_SPACE_SCOPE (base);
434   GstMapInfo amap;
435   guint num_samples;
436 
437   gst_buffer_map (audio, &amap, GST_MAP_READ);
438 
439   num_samples =
440       amap.size / (GST_AUDIO_INFO_CHANNELS (&base->ainfo) * sizeof (gint16));
441   scope->process (base, (guint32 *) GST_VIDEO_FRAME_PLANE_DATA (video, 0),
442       (gint16 *) amap.data, num_samples);
443   gst_buffer_unmap (audio, &amap);
444   return TRUE;
445 }
446 
447 gboolean
gst_space_scope_plugin_init(GstPlugin * plugin)448 gst_space_scope_plugin_init (GstPlugin * plugin)
449 {
450   GST_DEBUG_CATEGORY_INIT (space_scope_debug, "spacescope", 0, "spacescope");
451 
452   return gst_element_register (plugin, "spacescope", GST_RANK_NONE,
453       GST_TYPE_SPACE_SCOPE);
454 }
455