28cec4ee8341c28a622ddeab167bdefc93db6fbd
[platform/upstream/gst-plugins-good.git] / gst / goom / gstgoom.c
1 /* gstgoom.c: implementation of goom drawing element
2  * Copyright (C) <2001> Richard Boulton <richard@tartarus.org>
3  *           (C) <2006> Wim Taymans <wim at fluendo dot com>
4  *           (C) <2011> Wim Taymans <wim.taymans at gmail dot com>
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., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 /**
23  * SECTION:element-goom
24  * @see_also: synaesthesia
25  *
26  * Goom is an audio visualisation element. It creates warping structures
27  * based on the incoming audio signal.
28  *
29  * <refsect2>
30  * <title>Example launch line</title>
31  * |[
32  * gst-launch -v audiotestsrc ! goom ! ffmpegcolorspace ! xvimagesink
33  * ]|
34  * </refsect2>
35  */
36
37 #ifdef HAVE_CONFIG_H
38 #include "config.h"
39 #endif
40
41 #include <string.h>
42 #include <gst/gst.h>
43 #include "gstgoom.h"
44 #include <gst/video/video.h>
45 #include "goom.h"
46
47 #if HAVE_ORC
48 #include <orc/orc.h>
49 #endif
50
51 GST_DEBUG_CATEGORY (goom_debug);
52 #define GST_CAT_DEFAULT goom_debug
53
54 #define DEFAULT_WIDTH  320
55 #define DEFAULT_HEIGHT 240
56 #define DEFAULT_FPS_N  25
57 #define DEFAULT_FPS_D  1
58
59 /* signals and args */
60 enum
61 {
62   /* FILL ME */
63   LAST_SIGNAL
64 };
65
66 enum
67 {
68   ARG_0
69       /* FILL ME */
70 };
71
72 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
73     GST_PAD_SRC,
74     GST_PAD_ALWAYS,
75 #if G_BYTE_ORDER == G_BIG_ENDIAN
76     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("xRGB"))
77 #else
78     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("BGRx"))
79 #endif
80     );
81
82 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",    /* the name of the pads */
83     GST_PAD_SINK,               /* type of the pad */
84     GST_PAD_ALWAYS,             /* ALWAYS/SOMETIMES */
85     GST_STATIC_CAPS ("audio/x-raw-int, "
86         "endianness = (int) BYTE_ORDER, "
87         "signed = (boolean) TRUE, "
88         "width = (int) 16, "
89         "depth = (int) 16, "
90         "rate = (int) [ 8000, 96000 ], " "channels = (int) { 1, 2 }")
91     );
92
93
94 static void gst_goom_finalize (GObject * object);
95
96 static GstStateChangeReturn gst_goom_change_state (GstElement * element,
97     GstStateChange transition);
98
99 static GstFlowReturn gst_goom_chain (GstPad * pad, GstBuffer * buffer);
100 static gboolean gst_goom_src_event (GstPad * pad, GstEvent * event);
101 static gboolean gst_goom_sink_event (GstPad * pad, GstEvent * event);
102
103 static gboolean gst_goom_src_query (GstPad * pad, GstQuery * query);
104
105 #define gst_goom_parent_class parent_class
106 G_DEFINE_TYPE (GstGoom, gst_goom, GST_TYPE_ELEMENT);
107
108 static void
109 gst_goom_class_init (GstGoomClass * klass)
110 {
111   GObjectClass *gobject_class;
112   GstElementClass *gstelement_class;
113
114   gobject_class = (GObjectClass *) klass;
115   gstelement_class = (GstElementClass *) klass;
116
117   gobject_class->finalize = gst_goom_finalize;
118
119   gst_element_class_set_details_simple (gstelement_class, "GOOM: what a GOOM!",
120       "Visualization",
121       "Takes frames of data and outputs video frames using the GOOM filter",
122       "Wim Taymans <wim@fluendo.com>");
123   gst_element_class_add_pad_template (gstelement_class,
124       gst_static_pad_template_get (&sink_template));
125   gst_element_class_add_pad_template (gstelement_class,
126       gst_static_pad_template_get (&src_template));
127
128   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_goom_change_state);
129 }
130
131 static void
132 gst_goom_init (GstGoom * goom)
133 {
134   /* create the sink and src pads */
135   goom->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
136   gst_pad_set_chain_function (goom->sinkpad,
137       GST_DEBUG_FUNCPTR (gst_goom_chain));
138   gst_pad_set_event_function (goom->sinkpad,
139       GST_DEBUG_FUNCPTR (gst_goom_sink_event));
140   gst_element_add_pad (GST_ELEMENT (goom), goom->sinkpad);
141
142   goom->srcpad = gst_pad_new_from_static_template (&src_template, "src");
143   gst_pad_set_event_function (goom->srcpad,
144       GST_DEBUG_FUNCPTR (gst_goom_src_event));
145   gst_pad_set_query_function (goom->srcpad,
146       GST_DEBUG_FUNCPTR (gst_goom_src_query));
147   gst_element_add_pad (GST_ELEMENT (goom), goom->srcpad);
148
149   goom->adapter = gst_adapter_new ();
150
151   goom->width = DEFAULT_WIDTH;
152   goom->height = DEFAULT_HEIGHT;
153   goom->fps_n = DEFAULT_FPS_N;  /* desired frame rate */
154   goom->fps_d = DEFAULT_FPS_D;  /* desired frame rate */
155   goom->channels = 0;
156   goom->rate = 0;
157   goom->duration = 0;
158
159   goom->plugin = goom_init (goom->width, goom->height);
160 }
161
162 static void
163 gst_goom_finalize (GObject * object)
164 {
165   GstGoom *goom = GST_GOOM (object);
166
167   goom_close (goom->plugin);
168   goom->plugin = NULL;
169
170   g_object_unref (goom->adapter);
171   if (goom->pool)
172     gst_object_unref (goom->pool);
173
174   G_OBJECT_CLASS (parent_class)->finalize (object);
175 }
176
177 static void
178 gst_goom_reset (GstGoom * goom)
179 {
180   gst_adapter_clear (goom->adapter);
181   gst_segment_init (&goom->segment, GST_FORMAT_UNDEFINED);
182
183   GST_OBJECT_LOCK (goom);
184   goom->proportion = 1.0;
185   goom->earliest_time = -1;
186   GST_OBJECT_UNLOCK (goom);
187 }
188
189 static gboolean
190 gst_goom_sink_setcaps (GstGoom * goom, GstCaps * caps)
191 {
192   GstStructure *structure;
193
194   structure = gst_caps_get_structure (caps, 0);
195
196   gst_structure_get_int (structure, "channels", &goom->channels);
197   gst_structure_get_int (structure, "rate", &goom->rate);
198
199   goom->bps = goom->channels * sizeof (gint16);
200
201   return TRUE;
202 }
203
204 static gboolean
205 gst_goom_src_setcaps (GstGoom * goom, GstCaps * caps)
206 {
207   GstStructure *structure;
208   gboolean res;
209
210   structure = gst_caps_get_structure (caps, 0);
211   if (!gst_structure_get_int (structure, "width", &goom->width) ||
212       !gst_structure_get_int (structure, "height", &goom->height) ||
213       !gst_structure_get_fraction (structure, "framerate", &goom->fps_n,
214           &goom->fps_d))
215     goto error;
216
217   goom_set_resolution (goom->plugin, goom->width, goom->height);
218
219   /* size of the output buffer in bytes, depth is always 4 bytes */
220   goom->outsize = goom->width * goom->height * 4;
221   goom->duration =
222       gst_util_uint64_scale_int (GST_SECOND, goom->fps_d, goom->fps_n);
223   goom->spf = gst_util_uint64_scale_int (goom->rate, goom->fps_d, goom->fps_n);
224   goom->bpf = goom->spf * goom->bps;
225
226   GST_DEBUG_OBJECT (goom, "dimension %dx%d, framerate %d/%d, spf %d",
227       goom->width, goom->height, goom->fps_n, goom->fps_d, goom->spf);
228
229   res = gst_pad_push_event (goom->srcpad, gst_event_new_caps (caps));
230
231   return res;
232
233   /* ERRORS */
234 error:
235   {
236     GST_DEBUG_OBJECT (goom, "error parsing caps");
237     return FALSE;
238   }
239 }
240
241 static gboolean
242 gst_goom_src_negotiate (GstGoom * goom)
243 {
244   GstCaps *othercaps, *target;
245   GstStructure *structure;
246   GstCaps *templ;
247   GstQuery *query;
248   GstBufferPool *pool = NULL;
249   guint size, min, max, prefix, alignment;
250
251   templ = gst_pad_get_pad_template_caps (goom->srcpad);
252
253   GST_DEBUG_OBJECT (goom, "performing negotiation");
254
255   /* see what the peer can do */
256   othercaps = gst_pad_peer_get_caps (goom->srcpad, NULL);
257   if (othercaps) {
258     target = gst_caps_intersect (othercaps, templ);
259     gst_caps_unref (othercaps);
260     gst_caps_unref (templ);
261
262     if (gst_caps_is_empty (target))
263       goto no_format;
264
265     gst_caps_truncate (target);
266   } else {
267     target = templ;
268   }
269
270   structure = gst_caps_get_structure (target, 0);
271   gst_structure_fixate_field_nearest_int (structure, "width", DEFAULT_WIDTH);
272   gst_structure_fixate_field_nearest_int (structure, "height", DEFAULT_HEIGHT);
273   gst_structure_fixate_field_nearest_fraction (structure, "framerate",
274       DEFAULT_FPS_N, DEFAULT_FPS_D);
275
276   gst_goom_src_setcaps (goom, target);
277
278   /* try to get a bufferpool now */
279   /* find a pool for the negotiated caps now */
280   query = gst_query_new_allocation (target, TRUE);
281
282   if (gst_pad_peer_query (goom->srcpad, query)) {
283     /* we got configuration from our peer, parse them */
284     gst_query_parse_allocation_params (query, &size, &min, &max, &prefix,
285         &alignment, &pool);
286   } else {
287     size = goom->outsize;
288     min = max = 0;
289     prefix = 0;
290     alignment = 0;
291   }
292
293   if (pool == NULL) {
294     GstStructure *config;
295
296     /* we did not get a pool, make one ourselves then */
297     pool = gst_buffer_pool_new ();
298
299     config = gst_buffer_pool_get_config (pool);
300     gst_buffer_pool_config_set (config, target, size, min, max, prefix,
301         alignment);
302     gst_buffer_pool_set_config (pool, config);
303   }
304
305   if (goom->pool)
306     gst_object_unref (goom->pool);
307   goom->pool = pool;
308
309   /* and activate */
310   gst_buffer_pool_set_active (pool, TRUE);
311
312   gst_caps_unref (target);
313
314   return TRUE;
315
316 no_format:
317   {
318     gst_caps_unref (target);
319     return FALSE;
320   }
321 }
322
323 static gboolean
324 gst_goom_src_event (GstPad * pad, GstEvent * event)
325 {
326   gboolean res;
327   GstGoom *goom;
328
329   goom = GST_GOOM (gst_pad_get_parent (pad));
330
331   switch (GST_EVENT_TYPE (event)) {
332     case GST_EVENT_QOS:
333     {
334       gdouble proportion;
335       GstClockTimeDiff diff;
336       GstClockTime timestamp;
337
338       gst_event_parse_qos (event, NULL, &proportion, &diff, &timestamp);
339
340       /* save stuff for the _chain() function */
341       GST_OBJECT_LOCK (goom);
342       goom->proportion = proportion;
343       if (diff >= 0)
344         /* we're late, this is a good estimate for next displayable
345          * frame (see part-qos.txt) */
346         goom->earliest_time = timestamp + 2 * diff + goom->duration;
347       else
348         goom->earliest_time = timestamp + diff;
349       GST_OBJECT_UNLOCK (goom);
350
351       res = gst_pad_push_event (goom->sinkpad, event);
352       break;
353     }
354     default:
355       res = gst_pad_push_event (goom->sinkpad, event);
356       break;
357   }
358   gst_object_unref (goom);
359
360   return res;
361 }
362
363 static gboolean
364 gst_goom_sink_event (GstPad * pad, GstEvent * event)
365 {
366   gboolean res;
367   GstGoom *goom;
368
369   goom = GST_GOOM (gst_pad_get_parent (pad));
370
371   switch (GST_EVENT_TYPE (event)) {
372     case GST_EVENT_CAPS:
373     {
374       GstCaps *caps;
375
376       gst_event_parse_caps (event, &caps);
377       res = gst_goom_sink_setcaps (goom, caps);
378       break;
379     }
380     case GST_EVENT_FLUSH_START:
381       res = gst_pad_push_event (goom->srcpad, event);
382       break;
383     case GST_EVENT_FLUSH_STOP:
384       gst_goom_reset (goom);
385       res = gst_pad_push_event (goom->srcpad, event);
386       break;
387     case GST_EVENT_SEGMENT:
388     {
389       /* the newsegment values are used to clip the input samples
390        * and to convert the incomming timestamps to running time so
391        * we can do QoS */
392       gst_event_copy_segment (event, &goom->segment);
393
394       res = gst_pad_push_event (goom->srcpad, event);
395       break;
396     }
397     default:
398       res = gst_pad_push_event (goom->srcpad, event);
399       break;
400   }
401   gst_object_unref (goom);
402
403   return res;
404 }
405
406 static gboolean
407 gst_goom_src_query (GstPad * pad, GstQuery * query)
408 {
409   gboolean res = FALSE;
410   GstGoom *goom;
411
412   goom = GST_GOOM (gst_pad_get_parent (pad));
413
414   switch (GST_QUERY_TYPE (query)) {
415     case GST_QUERY_LATENCY:
416     {
417       /* We need to send the query upstream and add the returned latency to our
418        * own */
419       GstClockTime min_latency, max_latency;
420       gboolean us_live;
421       GstClockTime our_latency;
422       guint max_samples;
423
424       if (goom->rate == 0)
425         break;
426
427       if ((res = gst_pad_peer_query (goom->sinkpad, query))) {
428         gst_query_parse_latency (query, &us_live, &min_latency, &max_latency);
429
430         GST_DEBUG_OBJECT (goom, "Peer latency: min %"
431             GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
432             GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
433
434         /* the max samples we must buffer buffer */
435         max_samples = MAX (GOOM_SAMPLES, goom->spf);
436         our_latency =
437             gst_util_uint64_scale_int (max_samples, GST_SECOND, goom->rate);
438
439         GST_DEBUG_OBJECT (goom, "Our latency: %" GST_TIME_FORMAT,
440             GST_TIME_ARGS (our_latency));
441
442         /* we add some latency but only if we need to buffer more than what
443          * upstream gives us */
444         min_latency += our_latency;
445         if (max_latency != -1)
446           max_latency += our_latency;
447
448         GST_DEBUG_OBJECT (goom, "Calculated total latency : min %"
449             GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
450             GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
451
452         gst_query_set_latency (query, TRUE, min_latency, max_latency);
453       }
454       break;
455     }
456     default:
457       res = gst_pad_peer_query (goom->sinkpad, query);
458       break;
459   }
460
461   gst_object_unref (goom);
462
463   return res;
464 }
465
466 /* make sure we are negotiated */
467 static GstFlowReturn
468 ensure_negotiated (GstGoom * goom)
469 {
470   gboolean reconfigure;
471
472   GST_OBJECT_LOCK (goom->srcpad);
473   reconfigure = GST_PAD_NEEDS_RECONFIGURE (goom->srcpad);
474   GST_OBJECT_FLAG_UNSET (goom->srcpad, GST_PAD_NEED_RECONFIGURE);
475   GST_OBJECT_UNLOCK (goom->srcpad);
476
477   /* we don't know an output format yet, pick one */
478   if (reconfigure || !gst_pad_has_current_caps (goom->srcpad)) {
479     if (!gst_goom_src_negotiate (goom))
480       return GST_FLOW_NOT_NEGOTIATED;
481   }
482   return GST_FLOW_OK;
483 }
484
485
486 static GstFlowReturn
487 gst_goom_chain (GstPad * pad, GstBuffer * buffer)
488 {
489   GstGoom *goom;
490   GstFlowReturn ret;
491   GstBuffer *outbuf = NULL;
492
493   goom = GST_GOOM (gst_pad_get_parent (pad));
494   if (goom->bps == 0) {
495     ret = GST_FLOW_NOT_NEGOTIATED;
496     goto beach;
497   }
498
499   /* Make sure have an output format */
500   ret = ensure_negotiated (goom);
501   if (ret != GST_FLOW_OK) {
502     gst_buffer_unref (buffer);
503     goto beach;
504   }
505
506   /* don't try to combine samples from discont buffer */
507   if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) {
508     gst_adapter_clear (goom->adapter);
509   }
510
511   GST_DEBUG_OBJECT (goom,
512       "Input buffer has %d samples, time=%" G_GUINT64_FORMAT,
513       gst_buffer_get_size (buffer) / goom->bps, GST_BUFFER_TIMESTAMP (buffer));
514
515   /* Collect samples until we have enough for an output frame */
516   gst_adapter_push (goom->adapter, buffer);
517
518   ret = GST_FLOW_OK;
519
520   while (TRUE) {
521     const guint16 *data;
522     gboolean need_skip;
523     guchar *out_frame;
524     gint i;
525     guint avail, to_flush;
526     guint64 dist, timestamp;
527
528     avail = gst_adapter_available (goom->adapter);
529     GST_DEBUG_OBJECT (goom, "avail now %u", avail);
530
531     /* we need GOOM_SAMPLES to get a meaningful result from goom. */
532     if (avail < (GOOM_SAMPLES * goom->bps))
533       break;
534
535     /* we also need enough samples to produce one frame at least */
536     if (avail < goom->bpf)
537       break;
538
539     GST_DEBUG_OBJECT (goom, "processing buffer");
540
541     /* get timestamp of the current adapter byte */
542     timestamp = gst_adapter_prev_timestamp (goom->adapter, &dist);
543     if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
544       /* convert bytes to time */
545       dist /= goom->bps;
546       timestamp += gst_util_uint64_scale_int (dist, GST_SECOND, goom->rate);
547     }
548
549     if (timestamp != -1) {
550       gint64 qostime;
551
552       qostime = gst_segment_to_running_time (&goom->segment, GST_FORMAT_TIME,
553           timestamp);
554       qostime += goom->duration;
555
556       GST_OBJECT_LOCK (goom);
557       /* check for QoS, don't compute buffers that are known to be late */
558       need_skip = goom->earliest_time != -1 && qostime <= goom->earliest_time;
559       GST_OBJECT_UNLOCK (goom);
560
561       if (need_skip) {
562         GST_WARNING_OBJECT (goom,
563             "QoS: skip ts: %" GST_TIME_FORMAT ", earliest: %" GST_TIME_FORMAT,
564             GST_TIME_ARGS (qostime), GST_TIME_ARGS (goom->earliest_time));
565         goto skip;
566       }
567     }
568
569     /* get next GOOM_SAMPLES, we have at least this amount of samples */
570     data =
571         (const guint16 *) gst_adapter_map (goom->adapter,
572         GOOM_SAMPLES * goom->bps);
573
574     if (goom->channels == 2) {
575       for (i = 0; i < GOOM_SAMPLES; i++) {
576         goom->datain[0][i] = *data++;
577         goom->datain[1][i] = *data++;
578       }
579     } else {
580       for (i = 0; i < GOOM_SAMPLES; i++) {
581         goom->datain[0][i] = *data;
582         goom->datain[1][i] = *data++;
583       }
584     }
585
586     /* alloc a buffer if we don't have one yet, this happens
587      * when we pushed a buffer in this while loop before */
588     if (outbuf == NULL) {
589       GST_DEBUG_OBJECT (goom, "allocating output buffer");
590       ret = gst_buffer_pool_acquire_buffer (goom->pool, &outbuf, NULL);
591       if (ret != GST_FLOW_OK) {
592         gst_adapter_unmap (goom->adapter, 0);
593         goto beach;
594       }
595     }
596
597     GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
598     GST_BUFFER_DURATION (outbuf) = goom->duration;
599
600     out_frame = (guchar *) goom_update (goom->plugin, goom->datain, 0, 0);
601     gst_buffer_fill (outbuf, 0, out_frame, goom->outsize);
602
603     gst_adapter_unmap (goom->adapter, 0);
604
605     GST_DEBUG ("Pushing frame with time=%" GST_TIME_FORMAT ", duration=%"
606         GST_TIME_FORMAT, GST_TIME_ARGS (timestamp),
607         GST_TIME_ARGS (goom->duration));
608
609     ret = gst_pad_push (goom->srcpad, outbuf);
610     outbuf = NULL;
611
612   skip:
613     /* Now flush the samples we needed for this frame, which might be more than
614      * the samples we used (GOOM_SAMPLES). */
615     to_flush = goom->bpf;
616
617     GST_DEBUG_OBJECT (goom, "finished frame, flushing %u bytes from input",
618         to_flush);
619     gst_adapter_flush (goom->adapter, to_flush);
620
621     if (ret != GST_FLOW_OK)
622       break;
623   }
624
625   if (outbuf != NULL)
626     gst_buffer_unref (outbuf);
627
628 beach:
629   gst_object_unref (goom);
630
631   return ret;
632 }
633
634 static GstStateChangeReturn
635 gst_goom_change_state (GstElement * element, GstStateChange transition)
636 {
637   GstGoom *goom = GST_GOOM (element);
638   GstStateChangeReturn ret;
639
640   switch (transition) {
641     case GST_STATE_CHANGE_NULL_TO_READY:
642       break;
643     case GST_STATE_CHANGE_READY_TO_PAUSED:
644       gst_goom_reset (goom);
645       break;
646     default:
647       break;
648   }
649
650   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
651
652   switch (transition) {
653     case GST_STATE_CHANGE_PAUSED_TO_READY:
654       if (goom->pool) {
655         gst_buffer_pool_set_active (goom->pool, FALSE);
656         gst_object_unref (goom->pool);
657         goom->pool = NULL;
658       }
659       break;
660     case GST_STATE_CHANGE_READY_TO_NULL:
661       break;
662     default:
663       break;
664   }
665
666   return ret;
667 }
668
669 static gboolean
670 plugin_init (GstPlugin * plugin)
671 {
672   GST_DEBUG_CATEGORY_INIT (goom_debug, "goom", 0, "goom visualisation element");
673
674 #if HAVE_ORC
675   orc_init ();
676 #endif
677
678   return gst_element_register (plugin, "goom", GST_RANK_NONE, GST_TYPE_GOOM);
679 }
680
681 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
682     GST_VERSION_MINOR,
683     "goom",
684     "GOOM visualization filter",
685     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)