2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3 * <2006> Stefan Kost <ensonic@users.sf.net>
4 * <2007> Sebastian Dröge <slomo@circular-chaos.org>
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.
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.
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.
22 * SECTION:element-spectrum
25 * The Spectrum element analyzes the frequency spectrum of an audio signal.
26 * If <link linkend="GstSpectrum--message">message property</link> is #TRUE it
27 * sends analysis results as application message named
28 * <classname>"spectrum"</classname> after each interval of time given
29 * by the <link linkend="GstSpectrum--interval">interval property</link>.
30 * The message's structure contains two fields:
35 * <classname>"endtime"</classname>:
36 * the end time of the buffer that triggered the message
41 * #GstValueList of #gfloat
42 * <classname>"magnitude"</classname>:
43 * the level for each frequency band. A value of 0 maps to the
44 * db value given by the
45 * <link linkend="GstSpectrum--threshold">threshold property.</link>.
50 * #GstValueList of #gfloat
51 * <classname>"phase"</classname>:
52 * the phase for each frequency band. The value is between -pi and pi.
57 * This element cannot be used with the gst-launch command in a sensible way.
58 * The included demo shows how to use it in an application.
60 * Last reviewed on 2007-08-18 (0.10.5)
69 #include <gst/audio/audio.h>
70 #include <gst/audio/gstaudiofilter.h>
72 #include "gstspectrum.h"
74 #include <gst/fft/gstfft.h>
75 #include <gst/fft/gstffts16.h>
76 #include <gst/fft/gstffts32.h>
77 #include <gst/fft/gstfftf32.h>
78 #include <gst/fft/gstfftf64.h>
80 GST_DEBUG_CATEGORY_STATIC (gst_spectrum_debug);
81 #define GST_CAT_DEFAULT gst_spectrum_debug
83 /* elementfactory information */
84 static const GstElementDetails gst_spectrum_details =
85 GST_ELEMENT_DETAILS ("Spectrum analyzer",
86 "Filter/Analyzer/Audio",
87 "Run an FFT on the audio signal, output spectrum data",
88 "Erik Walthinsen <omega@cse.ogi.edu>, "
89 "Stefan Kost <ensonic@users.sf.net>, "
90 "Sebastian Dröge <slomo@circular-chaos.org>");
92 #define ALLOWED_CAPS \
94 " width = (int) 16, " \
95 " depth = (int) 16, " \
96 " signed = (boolean) true, " \
97 " endianness = (int) BYTE_ORDER, " \
98 " rate = (int) [ 1, MAX ], " \
99 " channels = (int) [ 1, MAX ]; " \
100 "audio/x-raw-int, " \
101 " width = (int) 32, " \
102 " depth = (int) 32, " \
103 " signed = (boolean) true, " \
104 " endianness = (int) BYTE_ORDER, " \
105 " rate = (int) [ 1, MAX ], " \
106 " channels = (int) [ 1, MAX ]; " \
107 "audio/x-raw-float, " \
108 " width = (int) { 32, 64 }, " \
109 " endianness = (int) BYTE_ORDER, " \
110 " rate = (int) [ 1, MAX ], " \
111 " channels = (int) [ 1, MAX ]"
113 /* Spectrum properties */
114 #define DEFAULT_SIGNAL_SPECTRUM TRUE
115 #define DEFAULT_SIGNAL_MAGNITUDE TRUE
116 #define DEFAULT_SIGNAL_PHASE FALSE
117 #define DEFAULT_SIGNAL_INTERVAL (GST_SECOND / 10)
118 #define DEFAULT_BANDS 128
119 #define DEFAULT_THRESHOLD -60
121 #define SPECTRUM_WINDOW_BASE 9
122 #define SPECTRUM_WINDOW_LEN (1 << (SPECTRUM_WINDOW_BASE+1))
127 PROP_SIGNAL_SPECTRUM,
128 PROP_SIGNAL_MAGNITUDE,
130 PROP_SIGNAL_INTERVAL,
135 GST_BOILERPLATE (GstSpectrum, gst_spectrum, GstAudioFilter,
136 GST_TYPE_AUDIO_FILTER);
138 static void gst_spectrum_dispose (GObject * object);
139 static void gst_spectrum_set_property (GObject * object, guint prop_id,
140 const GValue * value, GParamSpec * pspec);
141 static void gst_spectrum_get_property (GObject * object, guint prop_id,
142 GValue * value, GParamSpec * pspec);
143 static gboolean gst_spectrum_start (GstBaseTransform * trans);
144 static gboolean gst_spectrum_stop (GstBaseTransform * trans);
145 static gboolean gst_spectrum_event (GstBaseTransform * trans, GstEvent * event);
146 static GstFlowReturn gst_spectrum_transform_ip (GstBaseTransform * trans,
148 static gboolean gst_spectrum_setup (GstAudioFilter * base,
149 GstRingBufferSpec * format);
151 static void process_s16 (GstSpectrum * spectrum, const gint16 * samples);
152 static void process_s32 (GstSpectrum * spectrum, const gint32 * samples);
153 static void process_f32 (GstSpectrum * spectrum, const gfloat * samples);
154 static void process_f64 (GstSpectrum * spectrum, const gdouble * samples);
157 gst_spectrum_base_init (gpointer g_class)
159 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
162 gst_element_class_set_details (element_class, &gst_spectrum_details);
164 caps = gst_caps_from_string (ALLOWED_CAPS);
165 gst_audio_filter_class_add_pad_templates (GST_AUDIO_FILTER_CLASS (g_class),
167 gst_caps_unref (caps);
171 gst_spectrum_class_init (GstSpectrumClass * klass)
173 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
174 GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
175 GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS (klass);
177 gobject_class->set_property = gst_spectrum_set_property;
178 gobject_class->get_property = gst_spectrum_get_property;
179 gobject_class->dispose = gst_spectrum_dispose;
181 trans_class->start = GST_DEBUG_FUNCPTR (gst_spectrum_start);
182 trans_class->stop = GST_DEBUG_FUNCPTR (gst_spectrum_stop);
183 trans_class->event = GST_DEBUG_FUNCPTR (gst_spectrum_event);
184 trans_class->transform_ip = GST_DEBUG_FUNCPTR (gst_spectrum_transform_ip);
185 trans_class->passthrough_on_same_caps = TRUE;
187 filter_class->setup = GST_DEBUG_FUNCPTR (gst_spectrum_setup);
189 g_object_class_install_property (gobject_class, PROP_SIGNAL_SPECTRUM,
190 g_param_spec_boolean ("message", "Message",
191 "Post a level message for each passed interval",
192 DEFAULT_SIGNAL_SPECTRUM, G_PARAM_READWRITE));
194 g_object_class_install_property (gobject_class, PROP_SIGNAL_MAGNITUDE,
195 g_param_spec_boolean ("message-magnitude", "Magnitude",
196 "Post the magnitude of the spectrum",
197 DEFAULT_SIGNAL_MAGNITUDE, G_PARAM_READWRITE));
199 g_object_class_install_property (gobject_class, PROP_SIGNAL_PHASE,
200 g_param_spec_boolean ("message-phase", "Phase",
201 "Post the phase of the spectrum",
202 DEFAULT_SIGNAL_PHASE, G_PARAM_READWRITE));
204 g_object_class_install_property (gobject_class, PROP_SIGNAL_INTERVAL,
205 g_param_spec_uint64 ("interval", "Interval",
206 "Interval of time between message posts (in nanoseconds)",
207 1, G_MAXUINT64, DEFAULT_SIGNAL_INTERVAL, G_PARAM_READWRITE));
209 g_object_class_install_property (gobject_class, PROP_BANDS,
210 g_param_spec_uint ("bands", "Bands", "number of frequency bands",
211 0, G_MAXUINT, DEFAULT_BANDS, G_PARAM_READWRITE));
213 g_object_class_install_property (gobject_class, PROP_THRESHOLD,
214 g_param_spec_int ("threshold", "Threshold",
215 "db threshold for result, maps to 0", G_MININT, 0, DEFAULT_THRESHOLD,
218 GST_DEBUG_CATEGORY_INIT (gst_spectrum_debug, "spectrum", 0,
219 "audio spectrum analyser element");
223 gst_spectrum_init (GstSpectrum * spectrum, GstSpectrumClass * g_class)
225 spectrum->adapter = gst_adapter_new ();
227 spectrum->message = DEFAULT_SIGNAL_SPECTRUM;
228 spectrum->message_magnitude = DEFAULT_SIGNAL_MAGNITUDE;
229 spectrum->message_phase = DEFAULT_SIGNAL_PHASE;
230 spectrum->interval = DEFAULT_SIGNAL_INTERVAL;
231 spectrum->bands = DEFAULT_BANDS;
232 spectrum->threshold = DEFAULT_THRESHOLD;
234 spectrum->spect_magnitude = g_new0 (gfloat, spectrum->bands);
235 spectrum->spect_phase = g_new0 (gfloat, spectrum->bands);
239 gst_spectrum_dispose (GObject * object)
241 GstSpectrum *spectrum = GST_SPECTRUM (object);
243 if (spectrum->adapter) {
244 g_object_unref (spectrum->adapter);
245 spectrum->adapter = NULL;
248 g_free (spectrum->in);
249 if (spectrum->fft_free_func) {
250 spectrum->fft_free_func (spectrum->fft_ctx);
251 spectrum->fft_ctx = NULL;
252 spectrum->fft_free_func = NULL;
254 g_free (spectrum->freqdata);
255 g_free (spectrum->spect_magnitude);
256 g_free (spectrum->spect_phase);
259 spectrum->spect_magnitude = NULL;
260 spectrum->spect_phase = NULL;
261 spectrum->freqdata = NULL;
263 G_OBJECT_CLASS (parent_class)->dispose (object);
267 gst_spectrum_set_property (GObject * object, guint prop_id,
268 const GValue * value, GParamSpec * pspec)
270 GstSpectrum *filter = GST_SPECTRUM (object);
273 case PROP_SIGNAL_SPECTRUM:
274 filter->message = g_value_get_boolean (value);
276 case PROP_SIGNAL_MAGNITUDE:
277 filter->message_magnitude = g_value_get_boolean (value);
279 case PROP_SIGNAL_PHASE:
280 filter->message_phase = g_value_get_boolean (value);
282 case PROP_SIGNAL_INTERVAL:
283 filter->interval = g_value_get_uint64 (value);
286 GST_BASE_TRANSFORM_LOCK (filter);
288 filter->bands = g_value_get_uint (value);
289 g_free (filter->spect_magnitude);
290 g_free (filter->spect_phase);
292 g_free (filter->freqdata);
294 if (filter->fft_free_func) {
295 filter->fft_free_func (filter->fft_ctx);
296 filter->fft_ctx = NULL;
297 filter->fft_free_func = NULL;
301 filter->freqdata = NULL;
302 filter->spect_magnitude = g_new0 (gfloat, filter->bands);
303 filter->spect_phase = g_new0 (gfloat, filter->bands);
304 filter->num_frames = 0;
306 GST_BASE_TRANSFORM_UNLOCK (filter);
307 GST_DEBUG_OBJECT (filter, "reallocation, spect = %p, bands =%d ",
308 filter->spect_magnitude, filter->bands);
311 filter->threshold = g_value_get_int (value);
314 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
320 gst_spectrum_get_property (GObject * object, guint prop_id,
321 GValue * value, GParamSpec * pspec)
323 GstSpectrum *filter = GST_SPECTRUM (object);
326 case PROP_SIGNAL_SPECTRUM:
327 g_value_set_boolean (value, filter->message);
329 case PROP_SIGNAL_MAGNITUDE:
330 g_value_set_boolean (value, filter->message_magnitude);
332 case PROP_SIGNAL_PHASE:
333 g_value_set_boolean (value, filter->message_phase);
335 case PROP_SIGNAL_INTERVAL:
336 g_value_set_uint64 (value, filter->interval);
339 g_value_set_uint (value, filter->bands);
342 g_value_set_int (value, filter->threshold);
345 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
351 gst_spectrum_start (GstBaseTransform * trans)
353 GstSpectrum *filter = GST_SPECTRUM (trans);
355 filter->num_frames = 0;
357 if (filter->spect_magnitude)
358 memset (filter->spect_magnitude, 0, filter->bands * sizeof (gfloat));
359 if (filter->spect_phase)
360 memset (filter->spect_phase, 0, filter->bands * sizeof (gfloat));
366 gst_spectrum_stop (GstBaseTransform * trans)
368 GstSpectrum *filter = GST_SPECTRUM (trans);
370 gst_adapter_clear (filter->adapter);
376 gst_spectrum_event (GstBaseTransform * trans, GstEvent * event)
378 GstSpectrum *filter = GST_SPECTRUM (trans);
380 switch (GST_EVENT_TYPE (event)) {
381 case GST_EVENT_FLUSH_STOP:
383 gst_adapter_clear (filter->adapter);
393 gst_spectrum_setup (GstAudioFilter * base, GstRingBufferSpec * format)
395 GstSpectrum *filter = GST_SPECTRUM (base);
402 if (filter->fft_free_func) {
403 filter->fft_free_func (filter->fft_ctx);
404 filter->fft_ctx = NULL;
405 filter->fft_free_func = NULL;
408 if (filter->freqdata) {
409 g_free (filter->freqdata);
410 filter->freqdata = NULL;
413 if (format->type == GST_BUFTYPE_LINEAR && format->width == 32)
414 filter->process = (GstSpectrumProcessFunc) process_s32;
415 else if (format->type == GST_BUFTYPE_LINEAR && format->width == 16)
416 filter->process = (GstSpectrumProcessFunc) process_s16;
417 else if (format->type == GST_BUFTYPE_FLOAT && format->width == 64)
418 filter->process = (GstSpectrumProcessFunc) process_f64;
419 else if (format->type == GST_BUFTYPE_FLOAT && format->width == 32)
420 filter->process = (GstSpectrumProcessFunc) process_f32;
422 g_assert_not_reached ();
428 gst_spectrum_message_new (GstSpectrum * spectrum, GstClockTime endtime)
434 gfloat *spect_magnitude = spectrum->spect_magnitude;
435 gfloat *spect_phase = spectrum->spect_phase;
437 GST_DEBUG_OBJECT (spectrum, "preparing message, spect = %p, bands =%d ",
438 spect_magnitude, spectrum->bands);
440 s = gst_structure_new ("spectrum", "endtime", GST_TYPE_CLOCK_TIME,
443 if (spectrum->message_magnitude) {
444 g_value_init (&v, GST_TYPE_LIST);
445 /* will copy-by-value */
446 gst_structure_set_value (s, "magnitude", &v);
449 g_value_init (&v, G_TYPE_FLOAT);
450 l = (GValue *) gst_structure_get_value (s, "magnitude");
451 for (i = 0; i < spectrum->bands; i++) {
452 g_value_set_float (&v, spect_magnitude[i]);
453 gst_value_list_append_value (l, &v); /* copies by value */
458 if (spectrum->message_phase) {
459 g_value_init (&v, GST_TYPE_LIST);
460 /* will copy-by-value */
461 gst_structure_set_value (s, "phase", &v);
464 g_value_init (&v, G_TYPE_FLOAT);
465 l = (GValue *) gst_structure_get_value (s, "phase");
466 for (i = 0; i < spectrum->bands; i++) {
467 g_value_set_float (&v, spect_phase[i]);
468 gst_value_list_append_value (l, &v); /* copies by value */
473 return gst_message_new_element (GST_OBJECT (spectrum), s);
476 #define DEFINE_PROCESS_FUNC_INT(width,next_width,max) \
478 process_s##width (GstSpectrum *spectrum, const gint##width *samples) \
480 gfloat *spect_magnitude = spectrum->spect_magnitude; \
481 gfloat *spect_phase = spectrum->spect_phase; \
482 gint channels = GST_AUDIO_FILTER (spectrum)->format.channels; \
484 gint##next_width acc; \
485 GstFFTS##width##Complex *freqdata; \
486 GstFFTS##width *ctx; \
488 gint nfft = 2 * spectrum->bands - 2; \
491 spectrum->in = (guint8 *) g_new (gint##width, nfft); \
493 in = (gint##width *) spectrum->in; \
495 for (i = 0, j = 0; i < nfft; i++) { \
496 /* convert to mono */ \
497 for (k = 0, acc = 0; k < channels; k++) \
498 acc += samples[j++]; \
499 in[i] = (gint##width) (acc / channels); \
502 if (!spectrum->fft_ctx) { \
503 spectrum->fft_ctx = gst_fft_s##width##_new (nfft, FALSE); \
504 spectrum->fft_free_func = (GstSpectrumFFTFreeFunc) gst_fft_s##width##_free; \
506 ctx = spectrum->fft_ctx; \
508 gst_fft_s##width##_window (ctx, in, GST_FFT_WINDOW_HAMMING); \
510 if (!spectrum->freqdata) \
511 spectrum->freqdata = g_new (GstFFTS##width##Complex, spectrum->bands); \
513 freqdata = (GstFFTS##width##Complex *) spectrum->freqdata; \
515 gst_fft_s##width##_fft (ctx, in, freqdata); \
516 spectrum->num_fft++; \
518 /* Calculate magnitude in db */ \
519 for (i = 0; i < spectrum->bands; i++) { \
520 gdouble val = (gdouble) freqdata[i].r * (gdouble) freqdata[i].r \
521 + (gdouble) freqdata[i].i * (gdouble) freqdata[i].i; \
523 val = 20.0 * log10 (val / max); \
524 if (val > spectrum->threshold) \
525 val -= spectrum->threshold; \
528 spect_magnitude[i] += val; \
531 /* Calculate phase */ \
532 for (i = 0; i < spectrum->bands; i++) \
533 spect_phase[i] += atan2 (freqdata[i].i, freqdata[i].r); \
537 DEFINE_PROCESS_FUNC_INT (16, 32, 32767.0);
538 DEFINE_PROCESS_FUNC_INT (32, 64, 2147483647.0);
540 #define DEFINE_PROCESS_FUNC_FLOAT(width,type) \
542 process_f##width (GstSpectrum *spectrum, const g##type *samples) \
544 gfloat *spect_magnitude = spectrum->spect_magnitude; \
545 gfloat *spect_phase = spectrum->spect_phase; \
546 gint channels = GST_AUDIO_FILTER (spectrum)->format.channels; \
549 GstFFTF##width##Complex *freqdata; \
550 GstFFTF##width *ctx; \
552 gint nfft = 2 * spectrum->bands - 2; \
555 spectrum->in = (guint8 *) g_new (g##type, nfft); \
557 in = (g##type *) spectrum->in; \
559 for (i = 0, j = 0; i < nfft; i++) { \
560 /* convert to mono */ \
561 for (k = 0, acc = 0; k < channels; k++) \
562 acc += samples[j++]; \
563 in[i] = (g##type) (acc / channels); \
564 if (abs (in[i]) > 1.0) \
565 g_assert_not_reached(); \
568 if (!spectrum->fft_ctx) { \
569 spectrum->fft_ctx = gst_fft_f##width##_new (nfft, FALSE); \
570 spectrum->fft_free_func = (GstSpectrumFFTFreeFunc) gst_fft_f##width##_free; \
572 ctx = spectrum->fft_ctx; \
574 gst_fft_f##width##_window (ctx, in, GST_FFT_WINDOW_HAMMING); \
576 if (!spectrum->freqdata) \
577 spectrum->freqdata = g_new (GstFFTF##width##Complex, spectrum->bands); \
579 freqdata = (GstFFTF##width##Complex *) spectrum->freqdata; \
581 gst_fft_f##width##_fft (ctx, in, freqdata); \
582 spectrum->num_fft++; \
584 /* Calculate magnitude in db */ \
585 for (i = 0; i < spectrum->bands; i++) { \
586 gdouble val = freqdata[i].r * freqdata[i].r + freqdata[i].i * freqdata[i].i; \
588 val = 20.0 * log10 (val / nfft); \
589 if (val > spectrum->threshold) \
590 val -= spectrum->threshold; \
593 spect_magnitude[i] += val; \
596 /* Calculate phase */ \
597 for (i = 0; i < spectrum->bands; i++) \
598 spect_phase[i] += atan2 (freqdata[i].i, freqdata[i].r); \
602 DEFINE_PROCESS_FUNC_FLOAT (32, float);
603 DEFINE_PROCESS_FUNC_FLOAT (64, double);
606 gst_spectrum_transform_ip (GstBaseTransform * trans, GstBuffer * in)
608 GstSpectrum *spectrum = GST_SPECTRUM (trans);
611 gfloat *spect_magnitude = spectrum->spect_magnitude;
612 gfloat *spect_phase = spectrum->spect_phase;
613 gint rate = GST_AUDIO_FILTER (spectrum)->format.rate;
614 gint channels = GST_AUDIO_FILTER (spectrum)->format.channels;
615 gint width = GST_AUDIO_FILTER (spectrum)->format.width / 8;
616 gint nfft = 2 * spectrum->bands - 2;
618 GstClockTime endtime =
619 gst_segment_to_running_time (&trans->segment, GST_FORMAT_TIME,
620 GST_BUFFER_TIMESTAMP (in));
621 GstClockTime blktime = GST_FRAMES_TO_CLOCK_TIME (nfft, rate);
623 GST_LOG_OBJECT (spectrum, "input size: %d bytes", GST_BUFFER_SIZE (in));
625 /* can we do this nicer? */
626 gst_adapter_push (spectrum->adapter, gst_buffer_copy (in));
627 /* required number of bytes */
628 wanted = channels * nfft * width;
630 while (gst_adapter_available (spectrum->adapter) >= wanted) {
631 const guint8 *samples;
633 samples = gst_adapter_peek (spectrum->adapter, wanted);
635 spectrum->process (spectrum, samples);
637 spectrum->num_frames += nfft;
639 /* do we need to message ? */
640 if (spectrum->num_frames >=
641 GST_CLOCK_TIME_TO_FRAMES (spectrum->interval, rate)) {
642 if (spectrum->message) {
645 /* Calculate average */
646 for (i = 0; i < spectrum->bands; i++) {
647 spect_magnitude[i] /= spectrum->num_fft;
648 spect_phase[i] /= spectrum->num_fft;
651 m = gst_spectrum_message_new (spectrum, endtime);
653 gst_element_post_message (GST_ELEMENT (spectrum), m);
655 memset (spect_magnitude, 0, spectrum->bands * sizeof (gfloat));
656 memset (spect_phase, 0, spectrum->bands * sizeof (gfloat));
657 spectrum->num_frames = 0;
658 spectrum->num_fft = 0;
661 gst_adapter_flush (spectrum->adapter, wanted);
668 plugin_init (GstPlugin * plugin)
670 return gst_element_register (plugin, "spectrum", GST_RANK_NONE,
674 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
677 "Run an FFT on the audio signal, output spectrum data",
678 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)