fixes #123901 update level plugin for the new millenium

Original commit message from CVS:
fixes #123901
update level plugin for the new millenium
parent 611dc2d6
common @ b7abb510
Subproject commit b4a839c99c0bf2d4903824426ef3cc0d4b0ad992
Subproject commit b7abb510aa14e8692df39ea8c2c758e37d8a8d8a
......@@ -2,11 +2,43 @@ plugindir = $(libdir)/gstreamer-@GST_MAJORMINOR@
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
if HAVE_GTK
noinst_PROGRAMS = demo plot
demo_SOURCES = demo.c
demo_CFLAGS = $(GTK_CFLAGS) $(GST_CFLAGS)
demo_LDFLAGS = $(GTK_LIBS) $(GST_LIBS)
plot_SOURCES = plot.c
plot_CFLAGS = $(GTK_CFLAGS) $(GST_CFLAGS)
plot_LDFLAGS = $(GTK_LIBS) $(GST_LIBS)
endif
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
level plugin by thomas <thomas@apestaart.org>
level plugin by thomas <thomas at apestaart dot org>
basic level indicator; prints out RMS values averaged over the buffer of
one iteration. Insert this into an audio/raw chain.
this plugin signals:
- running time since last EOS/start
- 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)
There are two demo apps, apps and plot. apps will create some GTK sliders
to display the volume. plot will output data readable by gnuplot.
Here is a sample plot script to plot output of the plot command that was
stored to plot.dat
set xlabel "Seconds"
set ylabel "dB"
set yrange [-60:0]
plot 'plot.dat' using 1:2 title 'L RMS' with lines, \
'plot.dat' using 1:3 title 'L peak' with lines, \
'plot.dat' using 1:4 title 'L decay' with lines
plot 'plot.dat' using 1:5 title 'R RMS' with lines, \
'plot.dat' using 1:6 title 'R peak' with lines, \
'plot.dat' using 1:7 title 'R decay' with lines
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
/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
*
* demo.c: sample application to display VU meter-like output of level
* Copyright (C) 2003
* Thomas Vander Stichele <thomas at apestaart dot org>
*
* 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
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <gst/gst.h>
#include <gtk/gtk.h>
/* global array for the scale widgets, we'll assume stereo */
GtkWidget *elapsed;
GtkWidget *scale[2][3];
static void
level_callback (GstElement *element, gdouble time, gint channel,
gdouble rms, gdouble peak, gdouble decay)
{
gchar *label;
label = g_strdup_printf ("%.3f", time);
gtk_label_set (GTK_LABEL (elapsed), label);
g_free (label);
gtk_range_set_value (GTK_RANGE (scale[channel][0]), rms);
gtk_range_set_value (GTK_RANGE (scale[channel][1]), peak);
gtk_range_set_value (GTK_RANGE (scale[channel][2]), decay);
}
static gboolean
idler (gpointer data)
{
GstElement *pipeline = GST_ELEMENT (data);
g_print ("+");
if (gst_bin_iterate (GST_BIN (pipeline)))
return TRUE;
gtk_main_quit ();
return FALSE;
}
static void
setup_gui ()
{
GtkWidget *window;
GtkWidget *vbox;
GtkWidget *label, *hbox;
int c;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (window, "destroy", gtk_main_quit, NULL);
vbox = gtk_vbox_new (TRUE, 0);
gtk_container_add (GTK_CONTAINER (window), vbox);
/* elapsed widget */
hbox = gtk_hbox_new (TRUE, 0);
label = gtk_label_new ("Elapsed");
elapsed = gtk_label_new ("0.000");
gtk_container_add (GTK_CONTAINER (hbox), label);
gtk_container_add (GTK_CONTAINER (hbox), elapsed);
gtk_container_add (GTK_CONTAINER (vbox), hbox);
for (c = 0; c < 2; ++c)
{
/* RMS */
hbox = gtk_hbox_new (TRUE, 0);
label = gtk_label_new ("RMS");
gtk_container_add (GTK_CONTAINER (hbox), label);
scale[c][0] = gtk_hscale_new_with_range (-90.0, 0.0, 0.2);
gtk_widget_set_size_request (scale[c][0], 100, -1);
gtk_container_add (GTK_CONTAINER (hbox), scale[c][0]);
gtk_container_add (GTK_CONTAINER (vbox), hbox);
/* peak */
hbox = gtk_hbox_new (TRUE, 0);
label = gtk_label_new ("peak");
gtk_container_add (GTK_CONTAINER (hbox), label);
scale[c][1] = gtk_hscale_new_with_range (-90.0, 0.0, 0.2);
gtk_widget_set_size_request (scale[c][1], 100, -1);
gtk_container_add (GTK_CONTAINER (hbox), scale[c][1]);
gtk_container_add (GTK_CONTAINER (vbox), hbox);
/* decay */
hbox = gtk_hbox_new (TRUE, 0);
label = gtk_label_new ("decaying peek");
gtk_container_add (GTK_CONTAINER (hbox), label);
scale[c][2] = gtk_hscale_new_with_range (-90.0, 0.0, 0.2);
gtk_widget_set_size_request (scale[c][2], 100, -1);
gtk_container_add (GTK_CONTAINER (hbox), scale[c][2]);
gtk_container_add (GTK_CONTAINER (vbox), hbox);
}
gtk_widget_show_all (GTK_WIDGET (window));
}
int main
(int argc, char *argv[])
{
GstElement *pipeline = NULL;
GError *error = NULL;
GstElement *level;
gst_init (&argc, &argv);
gtk_init (&argc, &argv);
pipeline = GST_ELEMENT (gst_parse_launchv ((const gchar **) &argv[1], &error));
if (error)
{
g_print ("pipeline could not be constructed: %s\n", error->message);
g_print ("Please give a complete pipeline with a 'level' element.\n");
g_print ("Example: sinesrc ! level ! osssink\n");
g_error_free (error);
return 1;
}
level = gst_bin_get_by_name (GST_BIN (pipeline), "level0");
if (level == NULL)
{
g_print ("Please give a pipeline with a 'level' element in it\n");
return 1;
}
g_object_set (level, "signal", TRUE, NULL);
g_signal_connect (level, "level", G_CALLBACK (level_callback), NULL);
/* setup GUI */
setup_gui ();
/* connect level signal */
/* go to main loop */
gst_element_set_state (pipeline, GST_STATE_PLAYING);
g_idle_add (idler, pipeline);
gtk_main ();
return 0;
}
/* 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);
}
VOID:DOUBLE,INT,DOUBLE,DOUBLE,DOUBLE
/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
*
* gstlevel.c: signals RMS, peak and decaying peak levels
* Copyright (C) 2000,2001,2002,2003
* Thomas Vander Stichele <thomas at apestaart dot org>
*
* 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
......@@ -26,65 +30,73 @@ 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 <thomas@apestaart.org>",
"(C) 2001",
"(C) 2001, 2003, 2003",
};
/* pad templates */
GST_PAD_TEMPLATE_FACTORY (sink_template_factory,
"level_sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_CAPS_NEW (
"level_sink",
"audio/raw",
"format", GST_PROPS_STRING ("int"),
"signed", GST_PROPS_BOOLEAN (TRUE),
"width", GST_PROPS_LIST (
GST_PROPS_INT (8),
GST_PROPS_INT (16)
),
"depth", GST_PROPS_LIST (
GST_PROPS_INT (8),
GST_PROPS_INT (16)
),
"rate", GST_PROPS_INT_RANGE (1, G_MAXINT),
"channels", GST_PROPS_INT_RANGE (1, 2)
)
)
GST_PAD_TEMPLATE_FACTORY (src_template_factory,
"level_src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_CAPS_NEW (
"level_src",
"audio/raw",
"format", GST_PROPS_STRING ("int"),
"signed", GST_PROPS_BOOLEAN (TRUE),
"width", GST_PROPS_LIST (
GST_PROPS_INT (8),
GST_PROPS_INT (16)
),
"depth", GST_PROPS_LIST (
GST_PROPS_INT (8),
GST_PROPS_INT (16)
),
"rate", GST_PROPS_INT_RANGE (1, G_MAXINT),
"channels", GST_PROPS_INT_RANGE (1, 2)
)
)
/* 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*
level_src_factory (void)
{
static GstPadTemplate *template = NULL;
if (!template) {
template = gst_pad_template_new (
"src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
gst_caps_new (
"test_src",
"audio/raw",
gst_props_new (
"channels", GST_PROPS_INT_RANGE (1, 2),
NULL)),
NULL);
}
return template;
}
static GstPadTemplate*
level_sink_factory (void)
{
static GstPadTemplate *template = NULL;
if (!template) {
template = gst_pad_template_new (
"sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
gst_caps_new (
"test_src",
"audio/raw",
gst_props_new (
"channels", GST_PROPS_INT_RANGE (1, 2),
NULL)),
NULL);
}
return template;
}
static void gst_level_class_init (GstLevelClass *klass);
static void gst_level_init (GstLevel *filter);
......@@ -92,13 +104,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)
......@@ -125,6 +133,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);
......@@ -133,73 +143,175 @@ 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, 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);
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)