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