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