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>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
22 * SECTION:element-goom2k1
23 * @see_also: goom, synaesthesia
25 * Goom2k1 is an audio visualisation element. It creates warping structures
26 * based on the incomming audio signal. Goom2k1 is the older version of the
27 * visualisation. Also available is goom2k4, with a different look.
30 * <title>Example launch line</title>
32 * gst-launch-1.0 -v audiotestsrc ! goom2k1 ! videoconvert ! xvimagesink
44 #include <gst/video/video.h>
45 #include <gst/audio/audio.h>
46 #include "goom_core.h"
48 GST_DEBUG_CATEGORY_STATIC (goom_debug);
49 #define GST_CAT_DEFAULT goom_debug
51 #define DEFAULT_WIDTH 320
52 #define DEFAULT_HEIGHT 240
53 #define DEFAULT_FPS_N 25
54 #define DEFAULT_FPS_D 1
56 /* signals and args */
69 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
72 #if G_BYTE_ORDER == G_BIG_ENDIAN
73 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("xRGB"))
75 GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("BGRx"))
79 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", /* the name of the pads */
80 GST_PAD_SINK, /* type of the pad */
81 GST_PAD_ALWAYS, /* ALWAYS/SOMETIMES */
82 GST_STATIC_CAPS ("audio/x-raw, "
83 "format = (string) " GST_AUDIO_NE (S16) ", "
84 "rate = (int) [ 8000, 96000 ], "
85 "channels = (int) 1, "
86 "layout = (string) interleaved; "
88 "format = (string) " GST_AUDIO_NE (S16) ", "
89 "rate = (int) [ 8000, 96000 ], "
90 "channels = (int) 2, "
91 "channel-mask = (bitmask) 0x3, " "layout = (string) interleaved")
94 static void gst_goom_finalize (GObject * object);
96 static GstStateChangeReturn gst_goom_change_state (GstElement * element,
97 GstStateChange transition);
99 static GstFlowReturn gst_goom_chain (GstPad * pad, GstObject * parent,
101 static gboolean gst_goom_src_event (GstPad * pad, GstObject * parent,
103 static gboolean gst_goom_sink_event (GstPad * pad, GstObject * parent,
106 static gboolean gst_goom_src_query (GstPad * pad, GstObject * parent,
109 static gboolean gst_goom_src_negotiate (GstGoom * goom);
111 #define gst_goom_parent_class parent_class
112 typedef GstGoom GstGoom2k1;
113 typedef GstGoomClass GstGoom2k1Class;
114 G_DEFINE_TYPE (GstGoom2k1, gst_goom, GST_TYPE_ELEMENT);
117 gst_goom_class_init (GstGoomClass * klass)
119 GObjectClass *gobject_class;
120 GstElementClass *gstelement_class;
122 gobject_class = (GObjectClass *) klass;
123 gstelement_class = (GstElementClass *) klass;
125 parent_class = g_type_class_peek_parent (klass);
127 gobject_class->finalize = gst_goom_finalize;
129 gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_goom_change_state);
131 gst_element_class_set_static_metadata (gstelement_class,
132 "GOOM: what a GOOM! 2k1 edition", "Visualization",
133 "Takes frames of data and outputs video frames using the GOOM 2k1 filter",
134 "Wim Taymans <wim@fluendo.com>");
135 gst_element_class_add_pad_template (gstelement_class,
136 gst_static_pad_template_get (&sink_template));
137 gst_element_class_add_pad_template (gstelement_class,
138 gst_static_pad_template_get (&src_template));
140 GST_DEBUG_CATEGORY_INIT (goom_debug, "goom", 0, "goom visualisation element");
144 gst_goom_init (GstGoom * goom)
146 /* create the sink and src pads */
147 goom->sinkpad = gst_pad_new_from_static_template (&sink_template, "sink");
148 gst_pad_set_chain_function (goom->sinkpad,
149 GST_DEBUG_FUNCPTR (gst_goom_chain));
150 gst_pad_set_event_function (goom->sinkpad,
151 GST_DEBUG_FUNCPTR (gst_goom_sink_event));
152 gst_element_add_pad (GST_ELEMENT (goom), goom->sinkpad);
154 goom->srcpad = gst_pad_new_from_static_template (&src_template, "src");
155 gst_pad_set_event_function (goom->srcpad,
156 GST_DEBUG_FUNCPTR (gst_goom_src_event));
157 gst_pad_set_query_function (goom->srcpad,
158 GST_DEBUG_FUNCPTR (gst_goom_src_query));
159 gst_element_add_pad (GST_ELEMENT (goom), goom->srcpad);
161 goom->adapter = gst_adapter_new ();
163 goom->width = DEFAULT_WIDTH;
164 goom->height = DEFAULT_HEIGHT;
165 goom->fps_n = DEFAULT_FPS_N; /* desired frame rate */
166 goom->fps_d = DEFAULT_FPS_D; /* desired frame rate */
171 goom_init (&(goom->goomdata), goom->width, goom->height);
175 gst_goom_finalize (GObject * object)
177 GstGoom *goom = GST_GOOM (object);
179 goom_close (&(goom->goomdata));
181 g_object_unref (goom->adapter);
183 G_OBJECT_CLASS (parent_class)->finalize (object);
187 gst_goom_reset (GstGoom * goom)
189 gst_adapter_clear (goom->adapter);
190 gst_segment_init (&goom->segment, GST_FORMAT_UNDEFINED);
192 GST_OBJECT_LOCK (goom);
193 goom->proportion = 1.0;
194 goom->earliest_time = GST_CLOCK_TIME_NONE;
195 GST_OBJECT_UNLOCK (goom);
202 gst_goom_sink_setcaps (GstGoom * goom, GstCaps * caps)
204 GstStructure *structure;
207 structure = gst_caps_get_structure (caps, 0);
209 res = gst_structure_get_int (structure, "channels", &goom->channels);
210 res &= gst_structure_get_int (structure, "rate", &goom->rate);
214 goom->bps = goom->channels * sizeof (gint16);
216 return gst_goom_src_negotiate (goom);
220 gst_goom_src_setcaps (GstGoom * goom, GstCaps * caps)
222 GstStructure *structure;
224 structure = gst_caps_get_structure (caps, 0);
226 if (!gst_structure_get_int (structure, "width", &goom->width) ||
227 !gst_structure_get_int (structure, "height", &goom->height) ||
228 !gst_structure_get_fraction (structure, "framerate", &goom->fps_n,
232 goom_set_resolution (&(goom->goomdata), goom->width, goom->height);
234 /* size of the output buffer in bytes, depth is always 4 bytes */
235 goom->outsize = goom->width * goom->height * 4;
237 gst_util_uint64_scale_int (GST_SECOND, goom->fps_d, goom->fps_n);
238 goom->spf = gst_util_uint64_scale_int (goom->rate, goom->fps_d, goom->fps_n);
239 goom->bpf = goom->spf * goom->bps;
241 GST_DEBUG_OBJECT (goom, "dimension %dx%d, framerate %d/%d, spf %d",
242 goom->width, goom->height, goom->fps_n, goom->fps_d, goom->spf);
244 return gst_pad_set_caps (goom->srcpad, caps);
249 GST_DEBUG_OBJECT (goom, "error parsing caps");
255 gst_goom_src_negotiate (GstGoom * goom)
257 GstCaps *othercaps, *target;
258 GstStructure *structure;
261 GstBufferPool *pool = NULL;
262 GstStructure *config;
263 guint size, min, max;
265 templ = gst_pad_get_pad_template_caps (goom->srcpad);
267 GST_DEBUG_OBJECT (goom, "performing negotiation");
269 /* see what the peer can do */
270 othercaps = gst_pad_peer_query_caps (goom->srcpad, NULL);
272 target = gst_caps_intersect (othercaps, templ);
273 gst_caps_unref (templ);
274 gst_caps_unref (othercaps);
276 if (gst_caps_is_empty (target))
279 target = gst_caps_truncate (target);
281 target = gst_caps_copy (templ);
284 structure = gst_caps_get_structure (target, 0);
285 gst_structure_fixate_field_nearest_int (structure, "width", DEFAULT_WIDTH);
286 gst_structure_fixate_field_nearest_int (structure, "height", DEFAULT_HEIGHT);
287 gst_structure_fixate_field_nearest_fraction (structure, "framerate",
288 DEFAULT_FPS_N, DEFAULT_FPS_D);
290 gst_goom_src_setcaps (goom, target);
292 /* find a pool for the negotiated caps now */
293 query = gst_query_new_allocation (target, TRUE);
295 if (!gst_pad_peer_query (goom->srcpad, query)) {
296 /* no problem, we use the query defaults */
297 GST_DEBUG_OBJECT (goom, "ALLOCATION query failed");
300 if (gst_query_get_n_allocation_pools (query) > 0) {
301 /* we got configuration from our peer, parse them */
302 gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
305 size = goom->outsize;
310 /* we did not get a pool, make one ourselves then */
311 pool = gst_buffer_pool_new ();
314 config = gst_buffer_pool_get_config (pool);
315 gst_buffer_pool_config_set_params (config, target, size, min, max);
316 gst_buffer_pool_set_config (pool, config);
319 gst_buffer_pool_set_active (goom->pool, FALSE);
320 gst_object_unref (goom->pool);
325 gst_buffer_pool_set_active (pool, TRUE);
327 gst_caps_unref (target);
333 gst_caps_unref (target);
339 gst_goom_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
344 goom = GST_GOOM (parent);
346 switch (GST_EVENT_TYPE (event)) {
350 GstClockTimeDiff diff;
351 GstClockTime timestamp;
353 gst_event_parse_qos (event, NULL, &proportion, &diff, ×tamp);
355 /* save stuff for the _chain() function */
356 GST_OBJECT_LOCK (goom);
357 goom->proportion = proportion;
359 /* we're late, this is a good estimate for next displayable
360 * frame (see part-qos.txt) */
361 goom->earliest_time = timestamp + 2 * diff + goom->duration;
363 goom->earliest_time = timestamp + diff;
364 GST_OBJECT_UNLOCK (goom);
366 res = gst_pad_event_default (pad, parent, event);
370 res = gst_pad_event_default (pad, parent, event);
378 gst_goom_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
383 goom = GST_GOOM (parent);
385 switch (GST_EVENT_TYPE (event)) {
390 gst_event_parse_caps (event, &caps);
391 res = gst_goom_sink_setcaps (goom, caps);
392 gst_event_unref (event);
395 case GST_EVENT_FLUSH_STOP:
396 gst_goom_reset (goom);
397 res = gst_pad_event_default (pad, parent, event);
399 case GST_EVENT_SEGMENT:
401 /* the newsegment values are used to clip the input samples
402 * and to convert the incomming timestamps to running time so
404 gst_event_copy_segment (event, &goom->segment);
406 res = gst_pad_event_default (pad, parent, event);
410 res = gst_pad_event_default (pad, parent, event);
418 gst_goom_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
423 goom = GST_GOOM (parent);
425 switch (GST_QUERY_TYPE (query)) {
426 case GST_QUERY_LATENCY:
428 /* We need to send the query upstream and add the returned latency to our
430 GstClockTime min_latency, max_latency;
432 GstClockTime our_latency;
435 if ((res = gst_pad_peer_query (goom->sinkpad, query))) {
436 gst_query_parse_latency (query, &us_live, &min_latency, &max_latency);
438 GST_DEBUG_OBJECT (goom, "Peer latency: min %"
439 GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
440 GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
442 /* the max samples we must buffer buffer */
443 max_samples = MAX (GOOM_SAMPLES, goom->spf);
445 gst_util_uint64_scale_int (max_samples, GST_SECOND, goom->rate);
447 GST_DEBUG_OBJECT (goom, "Our latency: %" GST_TIME_FORMAT,
448 GST_TIME_ARGS (our_latency));
450 /* we add some latency but only if we need to buffer more than what
451 * upstream gives us */
452 min_latency += our_latency;
453 if (max_latency != -1)
454 max_latency += our_latency;
456 GST_DEBUG_OBJECT (goom, "Calculated total latency : min %"
457 GST_TIME_FORMAT " max %" GST_TIME_FORMAT,
458 GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
460 gst_query_set_latency (query, TRUE, min_latency, max_latency);
465 res = gst_pad_query_default (pad, parent, query);
472 /* make sure we are negotiated */
474 ensure_negotiated (GstGoom * goom)
476 if (gst_pad_check_reconfigure (goom->srcpad)) {
477 if (!gst_goom_src_negotiate (goom))
478 return GST_FLOW_NOT_NEGOTIATED;
484 gst_goom_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
488 GstBuffer *outbuf = NULL;
490 goom = GST_GOOM (parent);
492 /* If we don't have an output format yet, preallocate a buffer to try and
494 if (goom->bps == 0) {
495 gst_buffer_unref (buffer);
496 ret = GST_FLOW_NOT_NEGOTIATED;
500 /* Make sure have an output format */
501 ret = ensure_negotiated (goom);
502 if (ret != GST_FLOW_OK) {
503 gst_buffer_unref (buffer);
507 /* don't try to combine samples from discont buffer */
508 if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)) {
509 gst_adapter_clear (goom->adapter);
512 GST_DEBUG_OBJECT (goom,
513 "Input buffer has %" G_GSIZE_FORMAT " samples, time=%" G_GUINT64_FORMAT,
514 gst_buffer_get_size (buffer) / goom->bps, GST_BUFFER_TIMESTAMP (buffer));
516 /* Collect samples until we have enough for an output frame */
517 gst_adapter_push (goom->adapter, buffer);
525 guint avail, to_flush;
526 guint64 dist, timestamp;
528 avail = gst_adapter_available (goom->adapter);
529 GST_DEBUG_OBJECT (goom, "avail now %u", avail);
531 /* we need GOOM_SAMPLES to get a meaningful result from goom. */
532 if (avail < (GOOM_SAMPLES * goom->bps))
535 /* we also need enough samples to produce one frame at least */
536 if (avail < goom->bpf)
539 GST_DEBUG_OBJECT (goom, "processing buffer");
541 /* get timestamp of the current adapter byte */
542 timestamp = gst_adapter_prev_pts (goom->adapter, &dist);
543 if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
544 /* convert bytes to time */
546 timestamp += gst_util_uint64_scale_int (dist, GST_SECOND, goom->rate);
549 /* check for QoS, don't compute buffers that are known to be late */
550 if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
551 GstClockTime earliest_time;
555 qostime = gst_segment_to_running_time (&goom->segment, GST_FORMAT_TIME,
557 qostime += goom->duration;
559 GST_OBJECT_LOCK (goom);
560 earliest_time = goom->earliest_time;
561 proportion = goom->proportion;
562 GST_OBJECT_UNLOCK (goom);
564 if (GST_CLOCK_TIME_IS_VALID (earliest_time) && qostime <= earliest_time) {
565 GstClockTime stream_time, jitter;
568 GST_DEBUG_OBJECT (goom,
569 "QoS: skip ts: %" GST_TIME_FORMAT ", earliest: %" GST_TIME_FORMAT,
570 GST_TIME_ARGS (qostime), GST_TIME_ARGS (earliest_time));
573 stream_time = gst_segment_to_stream_time (&goom->segment,
574 GST_FORMAT_TIME, timestamp);
575 jitter = GST_CLOCK_DIFF (qostime, earliest_time);
576 qos_msg = gst_message_new_qos (GST_OBJECT (goom), FALSE, qostime,
577 stream_time, timestamp, GST_BUFFER_DURATION (buffer));
578 gst_message_set_qos_values (qos_msg, jitter, proportion, 1000000);
579 gst_message_set_qos_stats (qos_msg, GST_FORMAT_BUFFERS,
580 goom->processed, goom->dropped);
581 gst_element_post_message (GST_ELEMENT (goom), qos_msg);
588 /* get next GOOM_SAMPLES, we have at least this amount of samples */
590 (const guint16 *) gst_adapter_map (goom->adapter,
591 GOOM_SAMPLES * goom->bps);
593 if (goom->channels == 2) {
594 for (i = 0; i < GOOM_SAMPLES; i++) {
595 goom->datain[0][i] = *data++;
596 goom->datain[1][i] = *data++;
599 for (i = 0; i < GOOM_SAMPLES; i++) {
600 goom->datain[0][i] = *data;
601 goom->datain[1][i] = *data++;
605 /* alloc a buffer if we don't have one yet, this happens
606 * when we pushed a buffer in this while loop before */
607 if (outbuf == NULL) {
608 GST_DEBUG_OBJECT (goom, "allocating output buffer");
609 ret = gst_buffer_pool_acquire_buffer (goom->pool, &outbuf, NULL);
610 if (ret != GST_FLOW_OK) {
611 gst_adapter_unmap (goom->adapter);
616 GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
617 GST_BUFFER_DURATION (outbuf) = goom->duration;
619 out_frame = (guchar *) goom_update (&(goom->goomdata), goom->datain);
620 gst_buffer_fill (outbuf, 0, out_frame, goom->outsize);
622 gst_adapter_unmap (goom->adapter);
624 GST_DEBUG ("Pushing frame with time=%" GST_TIME_FORMAT ", duration=%"
625 GST_TIME_FORMAT, GST_TIME_ARGS (timestamp),
626 GST_TIME_ARGS (goom->duration));
628 ret = gst_pad_push (goom->srcpad, outbuf);
632 /* Now flush the samples we needed for this frame, which might be more than
633 * the samples we used (GOOM_SAMPLES). */
634 to_flush = goom->bpf;
636 GST_DEBUG_OBJECT (goom, "finished frame, flushing %u bytes from input",
638 gst_adapter_flush (goom->adapter, to_flush);
640 if (ret != GST_FLOW_OK)
645 gst_buffer_unref (outbuf);
652 static GstStateChangeReturn
653 gst_goom_change_state (GstElement * element, GstStateChange transition)
655 GstGoom *goom = GST_GOOM (element);
656 GstStateChangeReturn ret;
658 switch (transition) {
659 case GST_STATE_CHANGE_NULL_TO_READY:
661 case GST_STATE_CHANGE_READY_TO_PAUSED:
662 gst_goom_reset (goom);
668 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
670 switch (transition) {
671 case GST_STATE_CHANGE_PAUSED_TO_READY:
673 gst_buffer_pool_set_active (goom->pool, FALSE);
674 gst_object_replace ((GstObject **) & goom->pool, NULL);
677 case GST_STATE_CHANGE_READY_TO_NULL:
687 plugin_init (GstPlugin * plugin)
689 return gst_element_register (plugin, "goom2k1", GST_RANK_NONE, GST_TYPE_GOOM);
692 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
695 "GOOM 2k1 visualization filter",
696 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)