From 1bb14f4e4864696d32bdbb24168a6b8d7e561753 Mon Sep 17 00:00:00 2001 From: Thomas Vander Stichele Date: Sun, 21 Sep 2003 12:21:49 +0000 Subject: [PATCH] reworked level plugin. It now does RMS, peak, and decay peak signaling per interleaved channel. Original commit message from CVS: reworked level plugin. It now does RMS, peak, and decay peak signaling per interleaved channel. --- gst/level/Makefile.am | 24 +++- gst/level/README | 24 +++- gst/level/filter.func | 57 ++++---- gst/level/gstlevel-marshal.list | 1 + gst/level/gstlevel.c | 288 +++++++++++++++++++++++++++++++--------- gst/level/gstlevel.h | 32 ++++- 6 files changed, 317 insertions(+), 109 deletions(-) create mode 100644 gst/level/gstlevel-marshal.list diff --git a/gst/level/Makefile.am b/gst/level/Makefile.am index 507454d..702b2cd 100644 --- a/gst/level/Makefile.am +++ b/gst/level/Makefile.am @@ -1,11 +1,33 @@ plugin_LTLIBRARIES = libgstlevel.la -libgstlevel_la_SOURCES = gstlevel.c +libgstlevel_la_SOURCES = gstlevel.c gstlevel-marshal.c libgstlevel_la_CFLAGS = $(GST_CFLAGS) libgstlevel_la_LIBADD = libgstlevel_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) noinst_HEADERS = gstlevel.h filter.func +EXTRA_libgstlevel_la_SOURCES = gstlevel-marshal.list + +BUILT_SOURCES = \ + gstlevel-marshal.c \ + gstlevel-marshal.h + +gstlevel-marshal.h: gstlevel-marshal.list + glib-genmarshal --header --prefix=gstlevel_cclosure_marshal $(srcdir)/gstlevel-marshal.list > gstlevel-marshal.h.tmp + mv gstlevel-marshal.h.tmp gstlevel-marshal.h + +gstlevel-marshal.c: gstlevel-marshal.list + echo "#include \"glib.h\"" > gstlevel-marshal.c.tmp + echo "#include \"glib-object.h\"" >> gstlevel-marshal.c.tmp + echo "#include \"gstlevel-marshal.h\"" >> gstlevel-marshal.c.tmp + glib-genmarshal --body --prefix=gstlevel_cclosure_marshal $(srcdir)/gstlevel-marshal.list >> gstlevel-marshal.c.tmp + mv gstlevel-marshal.c.tmp gstlevel-marshal.c + +# Don't want the generated marshal files in the dist +dist-hook: + rm -f $(distdir)/gstlevel-marshal.c + rm -f $(distdir)/gstlevel-marshal.h + EXTRA_DIST = README diff --git a/gst/level/README b/gst/level/README index c7dbf4c..fffc69f 100644 --- a/gst/level/README +++ b/gst/level/README @@ -1,11 +1,21 @@ level plugin by thomas -basic level indicator; prints out RMS values averaged over the buffer of -one iteration. Insert this into an audio/raw chain. +this plugin signals: + - channel + - RMS level + - peak level + - decaying peak level +over the given interval. + +This is useful for a VU meter display and for plotting out the signal graph. +The VU meter can either display RMS, or display immediate peak level and +have the falloff decaying peak level displayed as a line. + +The interval for signal emission, ttl of decay peak, and falloff of decay peak +can all be set. + +The element only takes unsigned data in; it could be extended to signed as +well, if separate fast chain functions are made that displaces the incoming +data to its midpoint (ie, 0,65535 should be mapped to -32768, 32767) -You can plot the level envelope of the track using gnuplot, example : -tools/gstreamer-launch disksrc location=foo.wav ! parsewav ! level ! \ - fakesink silent=true > foo.level -graph -T gif foo.level > foo.gif -xview dark.gif diff --git a/gst/level/filter.func b/gst/level/filter.func index a530270..eacecc6 100644 --- a/gst/level/filter.func +++ b/gst/level/filter.func @@ -1,45 +1,34 @@ +/* process one (interleaved) channel of incoming samples + * calculate square sum of samples + * normalize and return normalized Cumulative Square + * caller must assure num is a multiple of channels + * this filter only accepts signed audio data, so mid level is always 0 + */ { - guint j; - double squaresum = 0.0; - double RMS = 0.0; - double RMS_dB = 0.0; - static int threshold_dB = -80; - static long int sample = 0; - double timepoint; + register int j; + double squaresum = 0.0; /* square sum of the integer samples */ + register double square = 0.0; /* Square */ + register double PSS = 0.0; /* Peak Square Sample */ + + *CS = 0.0; /* Cumulative Square for this block */ + + gdouble normalizer = (double) (1 << resolution); /* * process data here * input sample data enters in *in_data as 8 or 16 bit data * samples for left and right channel are interleaved + * returns the Mean Square of the samples as a double between 0 and 1 */ -/* - for(j = 0; j < num_samples; j++) { - out_data[j] = in_data[j]; - squaresum += in_data[j] * in_data[j]; - } - RMS = sqrt (squaresum / (float) num_samples); - printf ("RMS for this block : %f\n", RMS); - RMS_dB = 20 * log (RMS / 32767); - printf ("RMS in dB (for 16bit) : %f\n", RMS_dB); -*/ - for(j = 0; j < num_samples; j++) { - out_data[j] = in_data[j]; - squaresum += pow ((double) in_data[j] / 32767.0, 2); - } - RMS = sqrt (squaresum / (float) num_samples); - RMS_dB = 10 * log (RMS); - sample += num_samples; - timepoint = sample / (44100.0 * 2); - - if (RMS_dB > (double) threshold_dB) + for (j = 0; j < num; j += channels) { -/* printf ("Reached %d dB at %f sec (%f dB)\n", - threshold_dB, timepoint, RMS_dB); -*/ - threshold_dB += 1; + square = (double) (in[j] * in[j]); + if (square > PSS) PSS = square; + squaresum += square; } -/* printf ("RMS in dB (for 16bit) : %f\n", RMS_dB); */ - printf ("%f s %f dB\n", timepoint, RMS_dB); + *peak = PSS / ((double) normalizer * (double) normalizer); + + /* return normalized cumulative square */ + *CS = squaresum / ((double) normalizer * (double) normalizer); } - diff --git a/gst/level/gstlevel-marshal.list b/gst/level/gstlevel-marshal.list new file mode 100644 index 0000000..d2ae97e --- /dev/null +++ b/gst/level/gstlevel-marshal.list @@ -0,0 +1 @@ +VOID:INT,DOUBLE,DOUBLE,DOUBLE diff --git a/gst/level/gstlevel.c b/gst/level/gstlevel.c index ad4f1b1..4fdfb0c 100644 --- a/gst/level/gstlevel.c +++ b/gst/level/gstlevel.c @@ -1,6 +1,10 @@ /* GStreamer * Copyright (C) <1999> Erik Walthinsen * + * gstlevel.c: signals RMS, peak and decaying peak levels + * Copyright (C) 2000,2001,2002,2003 + * Thomas Vander Stichele + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either @@ -17,34 +21,35 @@ * Boston, MA 02111-1307, USA. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif #include #include "gstlevel.h" #include "math.h" -#include /* elementfactory information */ static GstElementDetails level_details = { "Level", "Filter/Audio/Analysis", "LGPL", - "RMS Level indicator for audio/raw", + "RMS/Peak/Decaying Peak Level signaller for audio/raw", VERSION, "Thomas ", - "(C) 2001", + "(C) 2001, 2003, 2003", }; /* Filter signals and args */ enum { /* FILL ME */ + SIGNAL_LEVEL, LAST_SIGNAL }; enum { - ARG_0 + ARG_0, + ARG_SIGNAL_LEVEL, + ARG_SIGNAL_INTERVAL, + ARG_PEAK_TTL, + ARG_PEAK_FALLOFF }; static GstPadTemplate* @@ -59,11 +64,12 @@ level_src_factory (void) GST_PAD_ALWAYS, gst_caps_new ( "test_src", - "audio/x-raw-int", - GST_AUDIO_INT_PAD_TEMPLATE_PROPS - ), - NULL - ); + "audio/raw", + gst_props_new ( + "channels", GST_PROPS_INT_RANGE (1, 2), + "signed", GST_PROPS_BOOLEAN (TRUE), + NULL)), + NULL); } return template; } @@ -80,11 +86,12 @@ level_sink_factory (void) GST_PAD_ALWAYS, gst_caps_new ( "test_src", - "audio/x-raw-int", - GST_AUDIO_INT_PAD_TEMPLATE_PROPS - ), - NULL - ); + "audio/raw", + gst_props_new ( + "channels", GST_PROPS_INT_RANGE (1, 2), + "signed", GST_PROPS_BOOLEAN (TRUE), + NULL)), + NULL); } return template; } @@ -96,13 +103,9 @@ static void gst_level_set_property (GObject *object, guint prop_id, const GVa static void gst_level_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gst_level_chain (GstPad *pad, GstBuffer *buf); -static void inline gst_level_fast_16bit_chain (gint16* data, gint16* out_data, - guint numsamples); -static void inline gst_level_fast_8bit_chain (gint8* data, gint8* out_data, - guint numsamples); static GstElementClass *parent_class = NULL; -/*static guint gst_filter_signals[LAST_SIGNAL] = { 0 }; */ +static guint gst_filter_signals[LAST_SIGNAL] = { 0 }; GType gst_level_get_type (void) @@ -129,6 +132,8 @@ gst_level_connect (GstPad *pad, GstCaps *caps) { GstLevel *filter; GstPad *otherpad; + GstPadLinkReturn res; + int i; filter = GST_LEVEL (gst_pad_get_parent (pad)); g_return_val_if_fail (filter != NULL, GST_PAD_LINK_REFUSED); @@ -137,73 +142,172 @@ gst_level_connect (GstPad *pad, GstCaps *caps) if (GST_CAPS_IS_FIXED (caps)) { - /*if ( !volume_parse_caps (filter, caps) || */ - return gst_pad_try_set_caps (otherpad, gst_caps_ref (caps)); + /* yep, got them */ + res = gst_pad_try_set_caps (otherpad, caps); + /* if ok, set filter */ + if (res == GST_PAD_LINK_OK) + { + filter->num_samples = 0; + /* FIXME: error handling */ + if (! gst_caps_get_int (caps, "rate", &(filter->rate))) + g_warning ("WARNING: level: Could not get rate from caps\n"); + if (!gst_caps_get_int (caps, "width", &(filter->width))) + g_warning ("WARNING: level: Could not get width from caps\n"); + if (!gst_caps_get_int (caps, "channels", &(filter->channels))) + g_warning ("WARNING: level: Could not get number of channels from caps\n"); + + /* allocate channel variable arrays */ + if (filter->CS) g_free (filter->CS); + if (filter->peak) g_free (filter->peak); + if (filter->last_peak) g_free (filter->last_peak); + if (filter->decay_peak) g_free (filter->decay_peak); + if (filter->decay_peak_age) g_free (filter->decay_peak_age); + if (filter->MS) g_free (filter->MS); + if (filter->RMS_dB) g_free (filter->RMS_dB); + filter->CS = g_new (double, filter->channels); + filter->peak = g_new (double, filter->channels); + filter->last_peak = g_new (double, filter->channels); + filter->decay_peak = g_new (double, filter->channels); + filter->decay_peak_age = g_new (double, filter->channels); + filter->MS = g_new (double, filter->channels); + filter->RMS_dB = g_new (double, filter->channels); + for (i = 0; i < filter->channels; ++i) + { + filter->CS[i] = filter->peak[i] = filter->last_peak[i] = + filter->decay_peak[i] = filter->decay_peak_age[i] = + filter->MS[i] = filter->RMS_dB[i] = 0.0; + } + } + return res; } return GST_PAD_LINK_DELAYED; } +static void inline +gst_level_fast_16bit_chain (gint16* in, guint num, gint channels, + gint resolution, double *CS, double *peak) +#include "filter.func" + +static void inline +gst_level_fast_8bit_chain (gint8* in, guint num, gint channels, + gint resolution, double *CS, double *peak) +#include "filter.func" + static void gst_level_chain (GstPad *pad, GstBuffer *buf) { GstLevel *filter; gint16 *in_data; - gint16 *out_data; - GstBuffer* outbuf; - gint width; - GstCaps *caps; + double CS = 0.0; + gint num_samples = 0; + gint i; g_return_if_fail (pad != NULL); g_return_if_fail (GST_IS_PAD (pad)); g_return_if_fail (buf != NULL); + + g_print ("\nDEBUG: chain start\n"); filter = GST_LEVEL (GST_OBJECT_PARENT (pad)); g_return_if_fail (filter != NULL); g_return_if_fail (GST_IS_LEVEL (filter)); - caps = NULL; - caps = GST_PAD_CAPS (pad); - if (caps == NULL) + for (i = 0; i < filter->channels; ++i) + filter->CS[i] = filter->peak[i] = filter->MS[i] = filter->RMS_dB[i] = 0.0; + + in_data = (gint16 *) GST_BUFFER_DATA(buf); + + num_samples = GST_BUFFER_SIZE (buf) / (filter->width / 8); + if (num_samples % filter->channels != 0) + g_warning ("WARNING: level: programming error, data not properly interleaved"); + + for (i = 0; i < filter->channels; ++i) { - /* FIXME : Please change this to a better warning method ! */ - g_error ("WARNING: level: Could not get pad caps - caps nego failed !\n"); + switch (filter->width) + { + case 16: + gst_level_fast_16bit_chain (in_data + i, num_samples, + filter->channels, filter->width - 1, + &CS, &filter->peak[i]); + break; + case 8: + gst_level_fast_8bit_chain (((gint8 *) in_data) + i, num_samples, + filter->channels, filter->width - 1, + &CS, &filter->peak[i]); + break; + } + g_print ("DEBUG: CS %f, peak %f\n", CS, filter->peak[i]); + filter->CS[i] += CS; + } + gst_pad_push (filter->srcpad, buf); - gst_caps_get_int (caps, "width", &width); + filter->num_samples += num_samples; - in_data = (gint16 *) GST_BUFFER_DATA(buf); - outbuf = gst_buffer_new(); - GST_BUFFER_DATA (outbuf) = (gchar *) g_new (gint16, - GST_BUFFER_SIZE (buf) / 2); - GST_BUFFER_SIZE (outbuf) = GST_BUFFER_SIZE (buf); + for (i = 0; i < filter->channels; ++i) + { + filter->decay_peak_age[i] += num_samples; + g_print ("filter peak info [%d]: peak %f, age %f\n", i, + filter->last_peak[i], filter->decay_peak_age[i]); + /* update running peak */ + if (filter->peak[i] > filter->last_peak[i]) + filter->last_peak[i] = filter->peak[i]; + + /* update decay peak */ + if (filter->peak[i] >= filter->decay_peak[i]) + { + g_print ("new peak, %f\n", filter->peak[i]); + filter->decay_peak[i] = filter->peak[i]; + filter->decay_peak_age[i] = 0; + } + else + { + /* make decay peak fall off if too old */ + if (filter->decay_peak_age[i] > filter->rate * filter->decay_peak_ttl) + { + double falloff_dB; + double falloff; + double length; /* length of buffer in seconds */ + + + length = (double) num_samples / (filter->channels * filter->rate); + falloff_dB = filter->decay_peak_falloff * length; + falloff = pow (10, falloff_dB / -20.0); + + g_print ("falloff: length %f, dB falloff %f, falloff factor %e\n", + length, falloff_dB, falloff); + filter->decay_peak[i] *= falloff; + g_print ("peak is %f samples old, decayed with factor %e to %f\n", + filter->decay_peak_age[i], falloff, filter->decay_peak[i]); + } + } + } - out_data = (gint16 *) GST_BUFFER_DATA (outbuf); + /* do we need to emit ? */ - g_print ("%s: ", gst_element_get_name (GST_ELEMENT (filter))); - switch (width) { - case 16: - gst_level_fast_16bit_chain (in_data, out_data, - GST_BUFFER_SIZE (buf) / 2); - break; - case 8: - gst_level_fast_8bit_chain ((gint8 *) in_data, - (gint8 *) out_data, GST_BUFFER_SIZE(buf)); - break; + if (filter->num_samples >= filter->interval * (gdouble) filter->rate) + { + if (filter->signal) + { + gdouble RMS, peak; + for (i = 0; i < filter->channels; ++i) + { + RMS = sqrt (filter->CS[i] / (filter->num_samples / filter->channels)); + peak = filter->last_peak[i]; + + g_signal_emit (G_OBJECT (filter), gst_filter_signals[SIGNAL_LEVEL], 0, + i, 20 * log10 (RMS), 20 * log10 (filter->last_peak[i]), + 20 * log10 (filter->decay_peak[i])); + /* we emitted, so reset cumulative and normal peak */ + filter->CS[i] = 0.0; + filter->last_peak[i] = 0.0; + } + } + filter->num_samples = 0; } - gst_buffer_unref (buf); - gst_pad_push (filter->srcpad,outbuf); } -static void inline -gst_level_fast_16bit_chain (gint16* in_data, gint16* out_data, - guint num_samples) -#include "filter.func" - -static void inline -gst_level_fast_8bit_chain (gint8* in_data, gint8* out_data, - guint num_samples) -#include "filter.func" static void gst_level_set_property (GObject *object, guint prop_id, @@ -216,6 +320,18 @@ gst_level_set_property (GObject *object, guint prop_id, filter = GST_LEVEL (object); switch (prop_id) { + case ARG_SIGNAL_LEVEL: + filter->signal = g_value_get_boolean (value); + break; + case ARG_SIGNAL_INTERVAL: + filter->interval = g_value_get_double (value); + break; + case ARG_PEAK_TTL: + filter->decay_peak_ttl = g_value_get_double (value); + break; + case ARG_PEAK_FALLOFF: + filter->decay_peak_falloff = g_value_get_double (value); + break; default: break; } @@ -232,7 +348,19 @@ gst_level_get_property (GObject *object, guint prop_id, filter = GST_LEVEL (object); switch (prop_id) { - default: + case ARG_SIGNAL_LEVEL: + g_value_set_boolean (value, filter->signal); + break; + case ARG_SIGNAL_INTERVAL: + g_value_set_double (value, filter->interval); + break; + case ARG_PEAK_TTL: + g_value_set_double (value, filter->decay_peak_ttl); + break; + case ARG_PEAK_FALLOFF: + g_value_set_double (value, filter->decay_peak_falloff); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } @@ -248,9 +376,32 @@ gst_level_class_init (GstLevelClass *klass) gstelement_class = (GstElementClass*) klass; parent_class = g_type_class_ref (GST_TYPE_ELEMENT); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SIGNAL_LEVEL, + g_param_spec_boolean ("signal", "Signal", + "Emit level signals for each interval", + TRUE, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SIGNAL_INTERVAL, + g_param_spec_double ("interval", "Interval", + "Interval between emissions (in seconds)", + 0.01, 100.0, 0.1, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PEAK_TTL, + g_param_spec_double ("peak_ttl", "Peak TTL", + "Time To Live of decay peak before it falls back", + 0, 100.0, 0.3, G_PARAM_READWRITE)); + g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PEAK_FALLOFF, + g_param_spec_double ("peak_falloff", "Peak Falloff", + "Decay rate of decay peak after TTL (in dB/sec)", + 0.0, G_MAXDOUBLE, 10.0, G_PARAM_READWRITE)); gobject_class->set_property = gst_level_set_property; gobject_class->get_property = gst_level_get_property; + + gst_filter_signals[SIGNAL_LEVEL] = + g_signal_new ("level", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GstLevelClass, level), NULL, NULL, + gstlevel_cclosure_marshal_VOID__INT_DOUBLE_DOUBLE_DOUBLE, + G_TYPE_NONE, 4, + G_TYPE_INT, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE); } static void @@ -265,6 +416,19 @@ gst_level_init (GstLevel *filter) gst_pad_set_chain_function (filter->sinkpad, gst_level_chain); filter->srcpad = gst_pad_new ("src", GST_PAD_SRC); gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad); + + filter->CS = NULL; + filter->peak = NULL; + filter->MS = NULL; + filter->RMS_dB = NULL; + + filter->rate = 0; + filter->width = 0; + filter->channels = 0; + + filter->interval = 0.1; + filter->decay_peak_ttl = 0.4; + filter->decay_peak_falloff = 10.0; /* dB falloff (/sec) */ } static gboolean diff --git a/gst/level/gstlevel.h b/gst/level/gstlevel.h index dd5edab..d5b8fd7 100644 --- a/gst/level/gstlevel.h +++ b/gst/level/gstlevel.h @@ -1,6 +1,10 @@ /* GStreamer * Copyright (C) <1999> Erik Walthinsen * + * gstlevel.c: signals RMS, peak and decaying peak levels + * Copyright (C) 2000,2001,2002,2003 + * Thomas Vander Stichele + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either @@ -24,8 +28,8 @@ #include #include -/* #include */ +#include "gstlevel-marshal.h" #ifdef __cplusplus extern "C" { @@ -49,14 +53,32 @@ typedef struct _GstLevelClass GstLevelClass; struct _GstLevel { GstElement element; - GstPad *sinkpad,*srcpad; - - /*MetaAudioRaw meta; */ - + GstPad *sinkpad, *srcpad; + gboolean signal; /* whether or not to emit signals */ + gdouble interval; /* how many seconds between emits */ + + gint rate; /* caps variables */ + gint width; + gint channels; + + gdouble decay_peak_ttl; /* time to live for peak in seconds */ + gdouble decay_peak_falloff; /* falloff in dB/sec */ + gdouble num_samples; /* cumulative sample count */ + + /* per-channel arrays for intermediate values */ + gdouble *CS; /* normalized Cumulative Square */ + gdouble *peak; /* normalized Peak value over buffer */ + gdouble *last_peak; /* last normalized Peak value over interval */ + gdouble *decay_peak; /* running decaying normalized Peak */ + gdouble *MS; /* normalized Mean Square of buffer */ + gdouble *RMS_dB; /* RMS in dB to emit */ + gdouble *decay_peak_age; /* age of last peak */ }; struct _GstLevelClass { GstElementClass parent_class; + void (*level) (GstElement *element, gint channel, + gdouble RMS_dB, gdouble peak_dB, gdouble decay_peak_dB); }; GType gst_level_get_type(void); -- 2.7.4