spectrum: update doc review stamp
[platform/upstream/gst-plugins-good.git] / gst / spectrum / gstspectrum.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  *               <2006> Stefan Kost <ensonic@users.sf.net>
4  *               <2007-2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
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  * SECTION:element-spectrum
23  *
24  * The Spectrum element analyzes the frequency spectrum of an audio signal.
25  * If the #GstSpectrum:post-messages property is #TRUE, it sends analysis results
26  * as application messages named
27  * <classname>&quot;spectrum&quot;</classname> after each interval of time given
28  * by the #GstSpectrum:interval property.
29  *
30  * The message's structure contains some combination of these fields:
31  * <itemizedlist>
32  * <listitem>
33  *   <para>
34  *   #GstClockTime
35  *   <classname>&quot;timestamp&quot;</classname>:
36  *   the timestamp of the buffer that triggered the message.
37  *   </para>
38  * </listitem>
39  * <listitem>
40  *   <para>
41  *   #GstClockTime
42  *   <classname>&quot;stream-time&quot;</classname>:
43  *   the stream time of the buffer.
44  *   </para>
45  * </listitem>
46  * <listitem>
47  *   <para>
48  *   #GstClockTime
49  *   <classname>&quot;running-time&quot;</classname>:
50  *   the running_time of the buffer.
51  *   </para>
52  * </listitem>
53  * <listitem>
54  *   <para>
55  *   #GstClockTime
56  *   <classname>&quot;duration&quot;</classname>:
57  *   the duration of the buffer.
58  *   </para>
59  * </listitem>
60  * <listitem>
61  *   <para>
62  *   #GstClockTime
63  *   <classname>&quot;endtime&quot;</classname>:
64  *   the end time of the buffer that triggered the message as stream time (this
65  *   is deprecated, as it can be calculated from stream-time + duration)
66  *   </para>
67  * </listitem>
68  * <listitem>
69  *   <para>
70  *   #GstValueList of #gfloat
71  *   <classname>&quot;magnitude&quot;</classname>:
72  *   the level for each frequency band in dB. All values below the value of the
73  *   #GstSpectrum:threshold property will be set to the threshold. Only present
74  *   if the #GstSpectrum:message-magnitude property is %TRUE.
75  *   </para>
76  * </listitem>
77  * <listitem>
78  *   <para>
79  *   #GstValueList of #gfloat
80  *   <classname>&quot;phase&quot;</classname>:
81  *   The phase for each frequency band. The value is between -pi and pi. Only
82  *   present if the #GstSpectrum:message-phase property is %TRUE.
83  *   </para>
84  * </listitem>
85  * </itemizedlist>
86  *
87  * If #GstSpectrum:multi-channel property is set to true. magnitude and phase
88  * fields will be each a nested #GstValueArray. The first dimension are the
89  * channels and the second dimension are the values.
90  *
91  * <refsect2>
92  * <title>Example application</title>
93  * |[
94  * <xi:include xmlns:xi="http://www.w3.org/2003/XInclude" parse="text" href="../../../../tests/examples/spectrum/spectrum-example.c" />
95  * ]|
96  * </refsect2>
97  *
98  * Last reviewed on 2011-03-10 (0.10.29)
99  */
100
101 #ifdef HAVE_CONFIG_H
102 #include "config.h"
103 #endif
104
105 #include <string.h>
106 #include <math.h>
107 #include "gstspectrum.h"
108
109 GST_DEBUG_CATEGORY_STATIC (gst_spectrum_debug);
110 #define GST_CAT_DEFAULT gst_spectrum_debug
111
112 /* elementfactory information */
113
114 #define ALLOWED_CAPS \
115     "audio/x-raw-int, "                                               \
116     " width = (int) 16, "                                             \
117     " depth = (int) [ 1, 16 ], "                                      \
118     " signed = (boolean) true, "                                      \
119     " endianness = (int) BYTE_ORDER, "                                \
120     " rate = (int) [ 1, MAX ], "                                      \
121     " channels = (int) [ 1, MAX ]; "                                  \
122     "audio/x-raw-int, "                                               \
123     " width = (int) 24, "                                             \
124     " depth = (int) [ 1, 24 ], "                                      \
125     " signed = (boolean) true, "                                      \
126     " endianness = (int) BYTE_ORDER, "                                \
127     " rate = (int) [ 1, MAX ], "                                      \
128     " channels = (int) [ 1, MAX ]; "                                  \
129     "audio/x-raw-int, "                                               \
130     " width = (int) 32, "                                             \
131     " depth = (int) [ 1, 32 ], "                                      \
132     " signed = (boolean) true, "                                      \
133     " endianness = (int) BYTE_ORDER, "                                \
134     " rate = (int) [ 1, MAX ], "                                      \
135     " channels = (int) [ 1, MAX ]; "                                  \
136     "audio/x-raw-float, "                                             \
137     " width = (int) { 32, 64 }, "                                     \
138     " endianness = (int) BYTE_ORDER, "                                \
139     " rate = (int) [ 1, MAX ], "                                      \
140     " channels = (int) [ 1, MAX ]"
141
142 /* Spectrum properties */
143 #define DEFAULT_MESSAGE                 TRUE
144 #define DEFAULT_POST_MESSAGES                   TRUE
145 #define DEFAULT_MESSAGE_MAGNITUDE       TRUE
146 #define DEFAULT_MESSAGE_PHASE           FALSE
147 #define DEFAULT_INTERVAL                (GST_SECOND / 10)
148 #define DEFAULT_BANDS                   128
149 #define DEFAULT_THRESHOLD               -60
150 #define DEFAULT_MULTI_CHANNEL           FALSE
151
152 enum
153 {
154   PROP_0,
155   PROP_MESSAGE,
156   PROP_POST_MESSAGES,
157   PROP_MESSAGE_MAGNITUDE,
158   PROP_MESSAGE_PHASE,
159   PROP_INTERVAL,
160   PROP_BANDS,
161   PROP_THRESHOLD,
162   PROP_MULTI_CHANNEL
163 };
164
165 GST_BOILERPLATE (GstSpectrum, gst_spectrum, GstAudioFilter,
166     GST_TYPE_AUDIO_FILTER);
167
168 static void gst_spectrum_finalize (GObject * object);
169 static void gst_spectrum_set_property (GObject * object, guint prop_id,
170     const GValue * value, GParamSpec * pspec);
171 static void gst_spectrum_get_property (GObject * object, guint prop_id,
172     GValue * value, GParamSpec * pspec);
173 static gboolean gst_spectrum_start (GstBaseTransform * trans);
174 static gboolean gst_spectrum_stop (GstBaseTransform * trans);
175 static GstFlowReturn gst_spectrum_transform_ip (GstBaseTransform * trans,
176     GstBuffer * in);
177 static gboolean gst_spectrum_setup (GstAudioFilter * base,
178     GstRingBufferSpec * format);
179
180 static void
181 gst_spectrum_base_init (gpointer g_class)
182 {
183   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
184   GstCaps *caps;
185
186   gst_element_class_set_details_simple (element_class, "Spectrum analyzer",
187       "Filter/Analyzer/Audio",
188       "Run an FFT on the audio signal, output spectrum data",
189       "Erik Walthinsen <omega@cse.ogi.edu>, "
190       "Stefan Kost <ensonic@users.sf.net>, "
191       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
192
193   caps = gst_caps_from_string (ALLOWED_CAPS);
194   gst_audio_filter_class_add_pad_templates (GST_AUDIO_FILTER_CLASS (g_class),
195       caps);
196   gst_caps_unref (caps);
197 }
198
199 static void
200 gst_spectrum_class_init (GstSpectrumClass * klass)
201 {
202   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
203   GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
204   GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS (klass);
205
206   gobject_class->set_property = gst_spectrum_set_property;
207   gobject_class->get_property = gst_spectrum_get_property;
208   gobject_class->finalize = gst_spectrum_finalize;
209
210   trans_class->start = GST_DEBUG_FUNCPTR (gst_spectrum_start);
211   trans_class->stop = GST_DEBUG_FUNCPTR (gst_spectrum_stop);
212   trans_class->transform_ip = GST_DEBUG_FUNCPTR (gst_spectrum_transform_ip);
213   trans_class->passthrough_on_same_caps = TRUE;
214
215   filter_class->setup = GST_DEBUG_FUNCPTR (gst_spectrum_setup);
216
217   /* FIXME 0.11, remove in favour of post-messages */
218   g_object_class_install_property (gobject_class, PROP_MESSAGE,
219       g_param_spec_boolean ("message", "Message",
220           "Whether to post a 'spectrum' element message on the bus for each "
221           "passed interval (deprecated, use post-messages)", DEFAULT_MESSAGE,
222           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
223   /**
224    * GstSpectrum:post-messages
225    *
226    * Post messages on the bus with spectrum information.
227    *
228    * Since: 0.10.17
229    */
230   g_object_class_install_property (gobject_class, PROP_POST_MESSAGES,
231       g_param_spec_boolean ("post-messages", "Post Messages",
232           "Whether to post a 'spectrum' element message on the bus for each "
233           "passed interval", DEFAULT_POST_MESSAGES,
234           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
235
236   g_object_class_install_property (gobject_class, PROP_MESSAGE_MAGNITUDE,
237       g_param_spec_boolean ("message-magnitude", "Magnitude",
238           "Whether to add a 'magnitude' field to the structure of any "
239           "'spectrum' element messages posted on the bus",
240           DEFAULT_MESSAGE_MAGNITUDE,
241           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
242
243   g_object_class_install_property (gobject_class, PROP_MESSAGE_PHASE,
244       g_param_spec_boolean ("message-phase", "Phase",
245           "Whether to add a 'phase' field to the structure of any "
246           "'spectrum' element messages posted on the bus",
247           DEFAULT_MESSAGE_PHASE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
248
249   g_object_class_install_property (gobject_class, PROP_INTERVAL,
250       g_param_spec_uint64 ("interval", "Interval",
251           "Interval of time between message posts (in nanoseconds)",
252           1, G_MAXUINT64, DEFAULT_INTERVAL,
253           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
254
255   g_object_class_install_property (gobject_class, PROP_BANDS,
256       g_param_spec_uint ("bands", "Bands", "Number of frequency bands",
257           0, G_MAXUINT, DEFAULT_BANDS,
258           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
259
260   g_object_class_install_property (gobject_class, PROP_THRESHOLD,
261       g_param_spec_int ("threshold", "Threshold",
262           "dB threshold for result. All lower values will be set to this",
263           G_MININT, 0, DEFAULT_THRESHOLD,
264           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
265
266   /**
267    * GstSpectrum:multi-channel
268    *
269    * Send separate results for each channel
270    *
271    * Since: 0.10.29
272    */
273   g_object_class_install_property (gobject_class, PROP_MULTI_CHANNEL,
274       g_param_spec_boolean ("multi-channel", "Multichannel results",
275           "Send separate results for each channel",
276           DEFAULT_MULTI_CHANNEL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
277
278   GST_DEBUG_CATEGORY_INIT (gst_spectrum_debug, "spectrum", 0,
279       "audio spectrum analyser element");
280 }
281
282 static void
283 gst_spectrum_init (GstSpectrum * spectrum, GstSpectrumClass * g_class)
284 {
285   spectrum->post_messages = DEFAULT_POST_MESSAGES;
286   spectrum->message_magnitude = DEFAULT_MESSAGE_MAGNITUDE;
287   spectrum->message_phase = DEFAULT_MESSAGE_PHASE;
288   spectrum->interval = DEFAULT_INTERVAL;
289   spectrum->bands = DEFAULT_BANDS;
290   spectrum->threshold = DEFAULT_THRESHOLD;
291 }
292
293 static void
294 gst_spectrum_alloc_channel_data (GstSpectrum * spectrum)
295 {
296   gint i;
297   GstSpectrumChannel *cd;
298   guint bands = spectrum->bands;
299   guint nfft = 2 * bands - 2;
300   guint channels = (spectrum->multi_channel) ?
301       GST_AUDIO_FILTER (spectrum)->format.channels : 1;
302
303   GST_DEBUG_OBJECT (spectrum, "allocating data for %d channels", channels);
304
305   spectrum->channel_data = g_new (GstSpectrumChannel, channels);
306   for (i = 0; i < channels; i++) {
307     cd = &spectrum->channel_data[i];
308     cd->fft_ctx = gst_fft_f32_new (nfft, FALSE);
309     cd->input = g_new0 (gfloat, nfft);
310     cd->input_tmp = g_new0 (gfloat, nfft);
311     cd->freqdata = g_new0 (GstFFTF32Complex, bands);
312     cd->spect_magnitude = g_new0 (gfloat, bands);
313     cd->spect_phase = g_new0 (gfloat, bands);
314   }
315 }
316
317 static void
318 gst_spectrum_free_channel_data (GstSpectrum * spectrum)
319 {
320   gint i;
321   GstSpectrumChannel *cd;
322   guint channels = (spectrum->multi_channel) ?
323       GST_AUDIO_FILTER (spectrum)->format.channels : 1;
324
325   GST_DEBUG_OBJECT (spectrum, "freeing data for %d channels", channels);
326
327   if (spectrum->channel_data) {
328     for (i = 0; i < channels; i++) {
329       cd = &spectrum->channel_data[i];
330       if (cd->fft_ctx)
331         gst_fft_f32_free (cd->fft_ctx);
332       g_free (cd->input);
333       g_free (cd->input_tmp);
334       g_free (cd->freqdata);
335       g_free (cd->spect_magnitude);
336       g_free (cd->spect_phase);
337     }
338     g_free (spectrum->channel_data);
339     spectrum->channel_data = NULL;
340   }
341 }
342
343 static void
344 gst_spectrum_flush (GstSpectrum * spectrum)
345 {
346   spectrum->num_frames = 0;
347   spectrum->num_fft = 0;
348
349   spectrum->accumulated_error = 0;
350 }
351
352 static void
353 gst_spectrum_reset_state (GstSpectrum * spectrum)
354 {
355   GST_DEBUG_OBJECT (spectrum, "resetting state");
356
357   gst_spectrum_free_channel_data (spectrum);
358   gst_spectrum_flush (spectrum);
359 }
360
361 static void
362 gst_spectrum_finalize (GObject * object)
363 {
364   GstSpectrum *spectrum = GST_SPECTRUM (object);
365
366   gst_spectrum_reset_state (spectrum);
367
368   G_OBJECT_CLASS (parent_class)->finalize (object);
369 }
370
371 static void
372 gst_spectrum_set_property (GObject * object, guint prop_id,
373     const GValue * value, GParamSpec * pspec)
374 {
375   GstSpectrum *filter = GST_SPECTRUM (object);
376
377   switch (prop_id) {
378     case PROP_MESSAGE:
379     case PROP_POST_MESSAGES:
380       filter->post_messages = g_value_get_boolean (value);
381       break;
382     case PROP_MESSAGE_MAGNITUDE:
383       filter->message_magnitude = g_value_get_boolean (value);
384       break;
385     case PROP_MESSAGE_PHASE:
386       filter->message_phase = g_value_get_boolean (value);
387       break;
388     case PROP_INTERVAL:{
389       guint64 interval = g_value_get_uint64 (value);
390       if (filter->interval != interval) {
391         GST_BASE_TRANSFORM_LOCK (filter);
392         filter->interval = interval;
393         gst_spectrum_reset_state (filter);
394         GST_BASE_TRANSFORM_UNLOCK (filter);
395       }
396     }
397       break;
398     case PROP_BANDS:{
399       guint bands = g_value_get_uint (value);
400       if (filter->bands != bands) {
401         GST_BASE_TRANSFORM_LOCK (filter);
402         filter->bands = bands;
403         gst_spectrum_reset_state (filter);
404         GST_BASE_TRANSFORM_UNLOCK (filter);
405       }
406     }
407       break;
408     case PROP_THRESHOLD:
409       filter->threshold = g_value_get_int (value);
410       break;
411     case PROP_MULTI_CHANNEL:{
412       gboolean multi_channel = g_value_get_boolean (value);
413       if (filter->multi_channel != multi_channel) {
414         GST_BASE_TRANSFORM_LOCK (filter);
415         filter->multi_channel = multi_channel;
416         gst_spectrum_reset_state (filter);
417         GST_BASE_TRANSFORM_UNLOCK (filter);
418       }
419     }
420       break;
421     default:
422       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
423       break;
424   }
425 }
426
427 static void
428 gst_spectrum_get_property (GObject * object, guint prop_id,
429     GValue * value, GParamSpec * pspec)
430 {
431   GstSpectrum *filter = GST_SPECTRUM (object);
432
433   switch (prop_id) {
434     case PROP_MESSAGE:
435     case PROP_POST_MESSAGES:
436       g_value_set_boolean (value, filter->post_messages);
437       break;
438     case PROP_MESSAGE_MAGNITUDE:
439       g_value_set_boolean (value, filter->message_magnitude);
440       break;
441     case PROP_MESSAGE_PHASE:
442       g_value_set_boolean (value, filter->message_phase);
443       break;
444     case PROP_INTERVAL:
445       g_value_set_uint64 (value, filter->interval);
446       break;
447     case PROP_BANDS:
448       g_value_set_uint (value, filter->bands);
449       break;
450     case PROP_THRESHOLD:
451       g_value_set_int (value, filter->threshold);
452       break;
453     case PROP_MULTI_CHANNEL:
454       g_value_set_boolean (value, filter->multi_channel);
455       break;
456     default:
457       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
458       break;
459   }
460 }
461
462 static gboolean
463 gst_spectrum_start (GstBaseTransform * trans)
464 {
465   GstSpectrum *spectrum = GST_SPECTRUM (trans);
466
467   gst_spectrum_reset_state (spectrum);
468
469   return TRUE;
470 }
471
472 static gboolean
473 gst_spectrum_stop (GstBaseTransform * trans)
474 {
475   GstSpectrum *spectrum = GST_SPECTRUM (trans);
476
477   gst_spectrum_reset_state (spectrum);
478
479   return TRUE;
480 }
481
482 /* mixing data readers */
483
484 static gfloat
485 input_data_mixed_float (const guint8 * data, guint channels, gfloat max_value)
486 {
487   guint i;
488   gfloat v = 0.0;
489   gfloat *in = (gfloat *) data;
490
491   for (i = 0; i < channels; i++)
492     v += in[i];
493
494   return v / channels;
495 }
496
497 static gfloat
498 input_data_mixed_double (const guint8 * data, guint channels, gfloat max_value)
499 {
500   guint i;
501   gfloat v = 0.0;
502   gdouble *in = (gdouble *) data;
503
504   for (i = 0; i < channels; i++)
505     v += in[i];
506
507   return v / channels;
508 }
509
510 static gfloat
511 input_data_mixed_int32 (const guint8 * data, guint channels, gfloat max_value)
512 {
513   guint i;
514   gfloat v = 0.0;
515   gint32 *in = (gint32 *) data;
516
517   for (i = 0; i < channels; i++)
518     v += in[i] * 2 + 1;
519
520   return v / channels;
521 }
522
523 static gfloat
524 input_data_mixed_int32_max (const guint8 * data, guint channels,
525     gfloat max_value)
526 {
527   guint i;
528   gfloat v = 0.0;
529   gint32 *in = (gint32 *) data;
530
531   for (i = 0; i < channels; i++)
532     v += in[i] / max_value;
533
534   return v / channels;
535 }
536
537 static gfloat
538 input_data_mixed_int24 (const guint8 * data, guint channels, gfloat max_value)
539 {
540   guint i;
541   gfloat v = 0.0;
542
543   for (i = 0; i < channels; i++) {
544 #if G_BYTE_ORDER == G_BIG_ENDIAN
545     gint32 value = GST_READ_UINT24_BE (data);
546 #else
547     gint32 value = GST_READ_UINT24_LE (data);
548 #endif
549     if (value & 0x00800000)
550       value |= 0xff000000;
551     v += value * 2 + 1;
552   }
553
554   return v / channels;
555 }
556
557 static gfloat
558 input_data_mixed_int24_max (const guint8 * data, guint channels,
559     gfloat max_value)
560 {
561   guint i;
562   gfloat v = 0.0;
563
564   for (i = 0; i < channels; i++) {
565 #if G_BYTE_ORDER == G_BIG_ENDIAN
566     gint32 value = GST_READ_UINT24_BE (data);
567 #else
568     gint32 value = GST_READ_UINT24_LE (data);
569 #endif
570     if (value & 0x00800000)
571       value |= 0xff000000;
572     v += value / max_value;
573   }
574
575   return v / channels;
576 }
577
578 static gfloat
579 input_data_mixed_int16 (const guint8 * data, guint channels, gfloat max_value)
580 {
581   guint i;
582   gfloat v = 0.0;
583   gint16 *in = (gint16 *) data;
584
585   for (i = 0; i < channels; i++)
586     v += in[i] * 2 + 1;
587
588   return v / channels;
589 }
590
591 static gfloat
592 input_data_mixed_int16_max (const guint8 * data, guint channels,
593     gfloat max_value)
594 {
595   guint i;
596   gfloat v = 0.0;
597   gint16 *in = (gint16 *) data;
598
599   for (i = 0; i < channels; i++)
600     v += in[i] / max_value;
601
602   return v / channels;
603 }
604
605 /* non mixing data readers */
606
607 static gfloat
608 input_data_float (const guint8 * data, gfloat max_value)
609 {
610   return ((gfloat *) data)[0];
611 }
612
613 static gfloat
614 input_data_double (const guint8 * data, gfloat max_value)
615 {
616   return (gfloat) ((gdouble *) data)[0];
617 }
618
619 static gfloat
620 input_data_int32 (const guint8 * data, gfloat max_value)
621 {
622   return ((gint32 *) data)[0] * 2 + 1;
623 }
624
625 static gfloat
626 input_data_int32_max (const guint8 * data, gfloat max_value)
627 {
628   return ((gint32 *) data)[0] / max_value;
629 }
630
631 static gfloat
632 input_data_int24 (const guint8 * data, gfloat max_value)
633 {
634 #if G_BYTE_ORDER == G_BIG_ENDIAN
635   gint32 in = GST_READ_UINT24_BE (data);
636 #else
637   gint32 in = GST_READ_UINT24_LE (data);
638 #endif
639   if (in & 0x00800000)
640     in |= 0xff000000;
641   return in * 2 + 1;
642 }
643
644 static gfloat
645 input_data_int24_max (const guint8 * data, gfloat max_value)
646 {
647 #if G_BYTE_ORDER == G_BIG_ENDIAN
648   gint32 in = GST_READ_UINT24_BE (data);
649 #else
650   gint32 in = GST_READ_UINT24_LE (data);
651 #endif
652   if (in & 0x00800000)
653     in |= 0xff000000;
654   return in / max_value;
655 }
656
657 static gfloat
658 input_data_int16 (const guint8 * data, gfloat max_value)
659 {
660   return ((gint16 *) data)[0] * 2 + 1;
661 }
662
663 static gfloat
664 input_data_int16_max (const guint8 * data, gfloat max_value)
665 {
666   return ((gint16 *) data)[0] / max_value;
667 }
668
669 static gboolean
670 gst_spectrum_setup (GstAudioFilter * base, GstRingBufferSpec * format)
671 {
672   GstSpectrum *spectrum = GST_SPECTRUM (base);
673   guint width = format->width / 8;
674   gboolean is_float = (format->type == GST_BUFTYPE_FLOAT);
675   /* max_value will be 0 when depth is 1,
676    * interpret -1 and 0 as -1 and +1 if that's the case. */
677   gfloat max_value = (1UL << (format->depth - 1)) - 1;
678
679   spectrum->input_data_mixed = NULL;
680   spectrum->input_data = NULL;
681
682   if (is_float) {
683     if (width == 4) {
684       spectrum->input_data_mixed = input_data_mixed_float;
685       spectrum->input_data = input_data_float;
686     } else if (width == 8) {
687       spectrum->input_data_mixed = input_data_mixed_double;
688       spectrum->input_data = input_data_double;
689     } else {
690       g_assert_not_reached ();
691     }
692   } else {
693     if (width == 4) {
694       if (max_value) {
695         spectrum->input_data_mixed = input_data_mixed_int32_max;
696         spectrum->input_data = input_data_int32_max;
697       } else {
698         spectrum->input_data_mixed = input_data_mixed_int32;
699         spectrum->input_data = input_data_int32;
700       }
701     } else if (width == 3) {
702       if (max_value) {
703         spectrum->input_data_mixed = input_data_mixed_int24_max;
704         spectrum->input_data = input_data_int24_max;
705       } else {
706         spectrum->input_data_mixed = input_data_mixed_int24;
707         spectrum->input_data = input_data_int24;
708       }
709     } else if (width == 2) {
710       if (max_value) {
711         spectrum->input_data_mixed = input_data_mixed_int16_max;
712         spectrum->input_data = input_data_int16_max;
713       } else {
714         spectrum->input_data_mixed = input_data_mixed_int16;
715         spectrum->input_data = input_data_int16;
716       }
717     } else {
718       g_assert_not_reached ();
719     }
720   }
721
722   gst_spectrum_reset_state (spectrum);
723   return TRUE;
724 }
725
726 static GValue *
727 gst_spectrum_message_add_container (GstStructure * s, GType type,
728     const gchar * name)
729 {
730   GValue v = { 0, };
731
732   g_value_init (&v, type);
733   /* will copy-by-value */
734   gst_structure_set_value (s, name, &v);
735   g_value_unset (&v);
736   return (GValue *) gst_structure_get_value (s, name);
737 }
738
739 static void
740 gst_spectrum_message_add_list (GValue * cv, gfloat * data, guint num_values)
741 {
742   GValue v = { 0, };
743   guint i;
744
745   g_value_init (&v, G_TYPE_FLOAT);
746   for (i = 0; i < num_values; i++) {
747     g_value_set_float (&v, data[i]);
748     gst_value_list_append_value (cv, &v);       /* copies by value */
749   }
750   g_value_unset (&v);
751 }
752
753 static void
754 gst_spectrum_message_add_array (GValue * cv, gfloat * data, guint num_values)
755 {
756   GValue v = { 0, };
757   GValue a = { 0, };
758   guint i;
759
760   g_value_init (&a, GST_TYPE_ARRAY);
761
762   g_value_init (&v, G_TYPE_FLOAT);
763   for (i = 0; i < num_values; i++) {
764     g_value_set_float (&v, data[i]);
765     gst_value_array_append_value (&a, &v);      /* copies by value */
766   }
767   g_value_unset (&v);
768
769   gst_value_array_append_value (cv, &a);        /* copies by value */
770   g_value_unset (&a);
771 }
772
773 static GstMessage *
774 gst_spectrum_message_new (GstSpectrum * spectrum, GstClockTime timestamp,
775     GstClockTime duration)
776 {
777   GstBaseTransform *trans = GST_BASE_TRANSFORM_CAST (spectrum);
778   GstSpectrumChannel *cd;
779   GstStructure *s;
780   GValue *mcv = NULL, *pcv = NULL;
781   GstClockTime endtime, running_time, stream_time;
782
783   GST_DEBUG_OBJECT (spectrum, "preparing message, bands =%d ", spectrum->bands);
784
785   running_time = gst_segment_to_running_time (&trans->segment, GST_FORMAT_TIME,
786       timestamp);
787   stream_time = gst_segment_to_stream_time (&trans->segment, GST_FORMAT_TIME,
788       timestamp);
789   /* endtime is for backwards compatibility */
790   endtime = stream_time + duration;
791
792   s = gst_structure_new ("spectrum",
793       "endtime", GST_TYPE_CLOCK_TIME, endtime,
794       "timestamp", G_TYPE_UINT64, timestamp,
795       "stream-time", G_TYPE_UINT64, stream_time,
796       "running-time", G_TYPE_UINT64, running_time,
797       "duration", G_TYPE_UINT64, duration, NULL);
798
799   if (!spectrum->multi_channel) {
800     cd = &spectrum->channel_data[0];
801
802     if (spectrum->message_magnitude) {
803       /* FIXME 0.11: this should be an array, not a list */
804       mcv = gst_spectrum_message_add_container (s, GST_TYPE_LIST, "magnitude");
805       gst_spectrum_message_add_list (mcv, cd->spect_magnitude, spectrum->bands);
806     }
807     if (spectrum->message_phase) {
808       /* FIXME 0.11: this should be an array, not a list */
809       pcv = gst_spectrum_message_add_container (s, GST_TYPE_LIST, "phase");
810       gst_spectrum_message_add_list (pcv, cd->spect_phase, spectrum->bands);
811     }
812   } else {
813     guint c;
814     guint channels = GST_AUDIO_FILTER (spectrum)->format.channels;
815
816     if (spectrum->message_magnitude) {
817       mcv = gst_spectrum_message_add_container (s, GST_TYPE_ARRAY, "magnitude");
818     }
819     if (spectrum->message_phase) {
820       pcv = gst_spectrum_message_add_container (s, GST_TYPE_ARRAY, "phase");
821     }
822
823     for (c = 0; c < channels; c++) {
824       cd = &spectrum->channel_data[c];
825
826       if (spectrum->message_magnitude) {
827         gst_spectrum_message_add_array (mcv, cd->spect_magnitude,
828             spectrum->bands);
829       }
830       if (spectrum->message_phase) {
831         gst_spectrum_message_add_array (pcv, cd->spect_magnitude,
832             spectrum->bands);
833       }
834     }
835   }
836   return gst_message_new_element (GST_OBJECT (spectrum), s);
837 }
838
839 static void
840 gst_spectrum_run_fft (GstSpectrum * spectrum, GstSpectrumChannel * cd,
841     guint input_pos)
842 {
843   guint i;
844   guint bands = spectrum->bands;
845   guint nfft = 2 * bands - 2;
846   gint threshold = spectrum->threshold;
847   gfloat *input = cd->input;
848   gfloat *input_tmp = cd->input_tmp;
849   gfloat *spect_magnitude = cd->spect_magnitude;
850   gfloat *spect_phase = cd->spect_phase;
851   GstFFTF32Complex *freqdata = cd->freqdata;
852   GstFFTF32 *fft_ctx = cd->fft_ctx;
853
854   for (i = 0; i < nfft; i++)
855     input_tmp[i] = input[(input_pos + i) % nfft];
856
857   gst_fft_f32_window (fft_ctx, input_tmp, GST_FFT_WINDOW_HAMMING);
858
859   gst_fft_f32_fft (fft_ctx, input_tmp, freqdata);
860
861   if (spectrum->message_magnitude) {
862     gdouble val;
863     /* Calculate magnitude in db */
864     for (i = 0; i < bands; i++) {
865       val = freqdata[i].r * freqdata[i].r;
866       val += freqdata[i].i * freqdata[i].i;
867       val /= nfft * nfft;
868       val = 10.0 * log10 (val);
869       if (val < threshold)
870         val = threshold;
871       spect_magnitude[i] += val;
872     }
873   }
874
875   if (spectrum->message_phase) {
876     /* Calculate phase */
877     for (i = 0; i < bands; i++)
878       spect_phase[i] += atan2 (freqdata[i].i, freqdata[i].r);
879   }
880 }
881
882 static void
883 gst_spectrum_prepare_message_data (GstSpectrum * spectrum,
884     GstSpectrumChannel * cd)
885 {
886   guint i;
887   guint bands = spectrum->bands;
888   guint num_fft = spectrum->num_fft;
889   gfloat *spect_magnitude = cd->spect_magnitude;
890   gfloat *spect_phase = cd->spect_phase;
891
892   /* Calculate average */
893   for (i = 0; i < bands; i++) {
894     spect_magnitude[i] /= num_fft;
895     spect_phase[i] /= num_fft;
896   }
897 }
898
899 static void
900 gst_spectrum_reset_message_data (GstSpectrum * spectrum,
901     GstSpectrumChannel * cd)
902 {
903   guint bands = spectrum->bands;
904   gfloat *spect_magnitude = cd->spect_magnitude;
905   gfloat *spect_phase = cd->spect_phase;
906
907   /* reset spectrum accumulators */
908   memset (spect_magnitude, 0, bands * sizeof (gfloat));
909   memset (spect_phase, 0, bands * sizeof (gfloat));
910 }
911
912 static GstFlowReturn
913 gst_spectrum_transform_ip (GstBaseTransform * trans, GstBuffer * buffer)
914 {
915   GstSpectrum *spectrum = GST_SPECTRUM (trans);
916   GstRingBufferSpec *format = &GST_AUDIO_FILTER (spectrum)->format;
917   guint rate = format->rate;
918   guint channels = format->channels;
919   guint width = format->width / 8;
920   gfloat max_value = (1UL << (format->depth - 1)) - 1;
921   guint bands = spectrum->bands;
922   guint nfft = 2 * bands - 2;
923   guint input_pos;
924   gfloat *input;
925   const guint8 *data = GST_BUFFER_DATA (buffer);
926   guint size = GST_BUFFER_SIZE (buffer);
927   gboolean have_full_interval;
928   GstSpectrumChannel *cd;
929
930   GST_LOG_OBJECT (spectrum, "input size: %d bytes", GST_BUFFER_SIZE (buffer));
931
932   if (GST_BUFFER_IS_DISCONT (buffer)) {
933     GST_DEBUG_OBJECT (spectrum, "Discontinuity detected -- flushing");
934     gst_spectrum_flush (spectrum);
935   }
936
937   /* If we don't have a FFT context yet (or it was reset due to parameter
938    * changes) get one and allocate memory for everything
939    */
940   if (spectrum->channel_data == NULL) {
941     GST_DEBUG_OBJECT (spectrum, "allocating for bands %u", bands);
942
943     gst_spectrum_alloc_channel_data (spectrum);
944
945     spectrum->frames_per_interval =
946         gst_util_uint64_scale (spectrum->interval, rate, GST_SECOND);
947     spectrum->error_per_interval = (spectrum->interval * rate) % GST_SECOND;
948     if (spectrum->frames_per_interval == 0)
949       spectrum->frames_per_interval = 1;
950
951     spectrum->input_pos = 0;
952
953     gst_spectrum_flush (spectrum);
954   }
955
956   if (spectrum->num_frames == 0)
957     spectrum->message_ts = GST_BUFFER_TIMESTAMP (buffer);
958
959   input_pos = spectrum->input_pos;
960
961   if (!spectrum->multi_channel) {
962     cd = &spectrum->channel_data[0];
963     input = cd->input;
964
965     while (size >= width * channels) {
966       /* Move the mixdown of current frame into our ringbuffer */
967       input[input_pos] = spectrum->input_data_mixed (data, channels, max_value);
968
969       data += width * channels;
970       size -= width * channels;
971       input_pos = (input_pos + 1) % nfft;
972       spectrum->num_frames++;
973
974       have_full_interval = (
975           (spectrum->accumulated_error < GST_SECOND
976               && spectrum->num_frames == spectrum->frames_per_interval) ||
977           (spectrum->accumulated_error >= GST_SECOND
978               && spectrum->num_frames - 1 == spectrum->frames_per_interval)
979           );
980
981       /* If we have enough frames for an FFT or we have all frames required for
982        * the interval run an FFT. In the last case we probably take the
983        * FFT of frames that we already handled.
984        */
985       if ((spectrum->num_frames % nfft == 0) || have_full_interval) {
986         gst_spectrum_run_fft (spectrum, cd, input_pos);
987         spectrum->num_fft++;
988       }
989
990       /* Do we have the FFTs for one interval? */
991       if (have_full_interval) {
992
993         GST_INFO ("nfft: %u num_frames: %" G_GUINT64_FORMAT " fpi: %"
994             G_GUINT64_FORMAT " error: %" GST_TIME_FORMAT, nfft,
995             spectrum->num_frames, spectrum->frames_per_interval,
996             GST_TIME_ARGS (spectrum->accumulated_error));
997
998         if (spectrum->accumulated_error >= GST_SECOND)
999           spectrum->accumulated_error -= GST_SECOND;
1000         else
1001           spectrum->accumulated_error += spectrum->error_per_interval;
1002
1003         if (spectrum->post_messages) {
1004           GstMessage *m;
1005
1006           gst_spectrum_prepare_message_data (spectrum, cd);
1007
1008           m = gst_spectrum_message_new (spectrum, spectrum->message_ts,
1009               spectrum->interval);
1010
1011           gst_element_post_message (GST_ELEMENT (spectrum), m);
1012         }
1013
1014         if (GST_CLOCK_TIME_IS_VALID (spectrum->message_ts))
1015           spectrum->message_ts +=
1016               gst_util_uint64_scale (spectrum->num_frames, GST_SECOND, rate);
1017
1018         gst_spectrum_reset_message_data (spectrum, cd);
1019         spectrum->num_frames = 0;
1020         spectrum->num_fft = 0;
1021       }
1022     }
1023   } else {
1024     guint c;
1025
1026     while (size >= width * channels) {
1027       for (c = 0; c < channels; c++) {
1028         cd = &spectrum->channel_data[c];
1029         input = cd->input;
1030         /* Move the current frames into our ringbuffers */
1031         input[input_pos] = spectrum->input_data (data, max_value);
1032         data += width;
1033       }
1034       size -= width * channels;
1035       input_pos = (input_pos + 1) % nfft;
1036       spectrum->num_frames++;
1037
1038       have_full_interval = (
1039           (spectrum->accumulated_error < GST_SECOND
1040               && spectrum->num_frames == spectrum->frames_per_interval) ||
1041           (spectrum->accumulated_error >= GST_SECOND
1042               && spectrum->num_frames - 1 == spectrum->frames_per_interval)
1043           );
1044
1045       /* If we have enough frames for an FFT or we have all frames required for
1046        * the interval run an FFT. In the last case we probably take the
1047        * FFT of frames that we already handled.
1048        */
1049       if ((spectrum->num_frames % nfft == 0) || have_full_interval) {
1050         for (c = 0; c < channels; c++) {
1051           cd = &spectrum->channel_data[c];
1052           gst_spectrum_run_fft (spectrum, cd, input_pos);
1053         }
1054         spectrum->num_fft++;
1055       }
1056
1057       /* Do we have the FFTs for one interval? */
1058       if (have_full_interval) {
1059
1060         GST_INFO ("nfft: %u num_frames: %" G_GUINT64_FORMAT " fpi: %"
1061             G_GUINT64_FORMAT " error: %" GST_TIME_FORMAT, nfft,
1062             spectrum->num_frames, spectrum->frames_per_interval,
1063             GST_TIME_ARGS (spectrum->accumulated_error));
1064
1065         if (spectrum->accumulated_error >= GST_SECOND)
1066           spectrum->accumulated_error -= GST_SECOND;
1067         else
1068           spectrum->accumulated_error += spectrum->error_per_interval;
1069
1070         if (spectrum->post_messages) {
1071           GstMessage *m;
1072
1073           for (c = 0; c < channels; c++) {
1074             cd = &spectrum->channel_data[c];
1075             gst_spectrum_prepare_message_data (spectrum, cd);
1076           }
1077
1078           m = gst_spectrum_message_new (spectrum, spectrum->message_ts,
1079               spectrum->interval);
1080
1081           gst_element_post_message (GST_ELEMENT (spectrum), m);
1082         }
1083
1084         if (GST_CLOCK_TIME_IS_VALID (spectrum->message_ts))
1085           spectrum->message_ts +=
1086               gst_util_uint64_scale (spectrum->num_frames, GST_SECOND, rate);
1087
1088         for (c = 0; c < channels; c++) {
1089           cd = &spectrum->channel_data[c];
1090           gst_spectrum_reset_message_data (spectrum, cd);
1091         }
1092         spectrum->num_frames = 0;
1093         spectrum->num_fft = 0;
1094       }
1095     }
1096   }
1097
1098   spectrum->input_pos = input_pos;
1099
1100   g_assert (size == 0);
1101
1102   return GST_FLOW_OK;
1103 }
1104
1105 static gboolean
1106 plugin_init (GstPlugin * plugin)
1107 {
1108   return gst_element_register (plugin, "spectrum", GST_RANK_NONE,
1109       GST_TYPE_SPECTRUM);
1110 }
1111
1112 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1113     GST_VERSION_MINOR,
1114     "spectrum",
1115     "Run an FFT on the audio signal, output spectrum data",
1116     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)