gstlevel.c 15.4 KB
Newer Older
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
1
/* GStreamer
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
2
3
 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
 *
4
5
6
7
 * gstlevel.c: signals RMS, peak and decaying peak levels
 * Copyright (C) 2000,2001,2002,2003
 *           Thomas Vander Stichele <thomas at apestaart dot org>
 *
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 * 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.
 */

24
25
26
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
27
28
29
30
#include <gst/gst.h>
#include "gstlevel.h"
#include "math.h"

31
32
33
GST_DEBUG_CATEGORY (level_debug);
#define GST_CAT_DEFAULT level_debug

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
34
35
static GstElementDetails level_details = {
  "Level",
36
  "Filter/Analyzer/Audio",
37
  "RMS/Peak/Decaying Peak Level signaller for audio/raw",
Ronald S. Bultje's avatar
*sigh*    
Ronald S. Bultje committed
38
  "Thomas <thomas@apestaart.org>"
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
39
40
};

David Schleef's avatar
David Schleef committed
41
static GstStaticPadTemplate sink_template_factory =
42
GST_STATIC_PAD_TEMPLATE ("sink",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
43
44
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
45
46
    GST_STATIC_CAPS ("audio/x-raw-int, "
        "rate = (int) [ 1, MAX ], "
Iain Holmes's avatar
Iain Holmes committed
47
        "channels = (int) [ 1, 2 ], "
48
49
50
        "endianness = (int) BYTE_ORDER, "
        "width = (int) { 8, 16 }, "
        "depth = (int) { 8, 16 }, " "signed = (boolean) true")
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
51
    );
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
52

David Schleef's avatar
David Schleef committed
53
static GstStaticPadTemplate src_template_factory =
54
GST_STATIC_PAD_TEMPLATE ("src",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
55
56
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
57
58
    GST_STATIC_CAPS ("audio/x-raw-int, "
        "rate = (int) [ 1, MAX ], "
Iain Holmes's avatar
Iain Holmes committed
59
        "channels = (int) [ 1, 2 ], "
60
61
62
        "endianness = (int) BYTE_ORDER, "
        "width = (int) { 8, 16 }, "
        "depth = (int) { 8, 16 }, " "signed = (boolean) true")
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
63
    );
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
64
65


Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
66
67
enum
{
68
69
70
71
72
  PROP_0,
  PROP_SIGNAL_LEVEL,
  PROP_SIGNAL_INTERVAL,
  PROP_PEAK_TTL,
  PROP_PEAK_FALLOFF
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
73
74
};

75
76
77
78

GST_BOILERPLATE (GstLevel, gst_level, GstBaseTransform,
    GST_TYPE_BASE_TRANSFORM);

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
79

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
80
81
82
83
static void gst_level_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_level_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
84

85
86
87
static gboolean gst_level_set_caps (GstBaseTransform * trans, GstCaps * in,
    GstCaps * out);
static GstFlowReturn gst_level_transform (GstBaseTransform * trans,
Wim Taymans's avatar
Wim Taymans committed
88
    GstBuffer * in, GstBuffer * out);
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157


static void
gst_level_base_init (gpointer g_class)
{
  GstElementClass *element_class = g_class;

  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&sink_template_factory));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&src_template_factory));
  gst_element_class_set_details (element_class, &level_details);
}

static void
gst_level_class_init (GstLevelClass * klass)
{
  GObjectClass *gobject_class;
  GstBaseTransformClass *trans_class;

  gobject_class = (GObjectClass *) klass;
  trans_class = (GstBaseTransformClass *) klass;

  gobject_class->set_property = gst_level_set_property;
  gobject_class->get_property = gst_level_get_property;

  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_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), PROP_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), PROP_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), PROP_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));

  GST_DEBUG_CATEGORY_INIT (level_debug, "level", 0, "Level calculation");

  trans_class->set_caps = gst_level_set_caps;
  trans_class->transform = gst_level_transform;
}

static void
gst_level_init (GstLevel * filter)
{
  filter->CS = NULL;
  filter->peak = 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 void
gst_level_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstLevel *filter = GST_LEVEL (object);
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
158

159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
  switch (prop_id) {
    case PROP_SIGNAL_LEVEL:
      filter->signal = g_value_get_boolean (value);
      break;
    case PROP_SIGNAL_INTERVAL:
      filter->interval = g_value_get_double (value);
      break;
    case PROP_PEAK_TTL:
      filter->decay_peak_ttl = g_value_get_double (value);
      break;
    case PROP_PEAK_FALLOFF:
      filter->decay_peak_falloff = g_value_get_double (value);
      break;
    default:
      break;
  }
}
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
176

177
178
179
static void
gst_level_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
180
{
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
  GstLevel *filter = GST_LEVEL (object);

  switch (prop_id) {
    case PROP_SIGNAL_LEVEL:
      g_value_set_boolean (value, filter->signal);
      break;
    case PROP_SIGNAL_INTERVAL:
      g_value_set_double (value, filter->interval);
      break;
    case PROP_PEAK_TTL:
      g_value_set_double (value, filter->decay_peak_ttl);
      break;
    case PROP_PEAK_FALLOFF:
      g_value_set_double (value, filter->decay_peak_falloff);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
199
200
201
  }
}

202
203
204
205
206
207
208
209
210
211
212
213
214
static gint
structure_get_int (GstStructure * structure, const gchar * field)
{
  gint ret;

  if (!gst_structure_get_int (structure, field, &ret))
    g_assert_not_reached ();

  return ret;
}

static gboolean
gst_level_set_caps (GstBaseTransform * trans, GstCaps * in, GstCaps * out)
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
215
{
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
216
  GstLevel *filter;
David Schleef's avatar
David Schleef committed
217
  GstStructure *structure;
218
  int i;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
219

220
  filter = GST_LEVEL (trans);
221
222

  filter->num_samples = 0;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
223

224
225
226
227
  structure = gst_caps_get_structure (in, 0);
  filter->rate = structure_get_int (structure, "rate");
  filter->width = structure_get_int (structure, "width");
  filter->channels = structure_get_int (structure, "channels");
228
229

  /* allocate channel variable arrays */
230
231
232
233
234
235
  g_free (filter->CS);
  g_free (filter->peak);
  g_free (filter->last_peak);
  g_free (filter->decay_peak);
  g_free (filter->decay_peak_age);
  g_free (filter->RMS_dB);
236
237
238
239
240
241
  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->RMS_dB = g_new (double, filter->channels);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
242

243
244
  for (i = 0; i < filter->channels; ++i) {
    filter->CS[i] = filter->peak[i] = filter->last_peak[i] =
245
        filter->decay_peak[i] = filter->decay_peak_age[i] =
246
        filter->RMS_dB[i] = 0.0;
247
248
  }

249
250
251
252
253
  return TRUE;
}

/* process one (interleaved) channel of incoming samples
 * calculate square sum of samples
254
255
256
257
 * normalize and average over number of samples
 * returns a normalized average power value as CS, as a double between 0 and 1
 * also returns the normalized peak power (square of the highest amplitude)
 *
258
 * caller must assure num is a multiple of channels
259
260
 * samples for multiple channels are interleaved
 * input sample data enters in *in_data as 8 or 16 bit data
261
262
 * this filter only accepts signed audio data, so mid level is always 0
 */
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287

#define DEFINE_LEVEL_CALCULATOR(TYPE)                                         \
static void inline                                                            \
gst_level_calculate_##TYPE (TYPE * in, guint num, gint channels,              \
                            gint resolution, double *CS, double *peak)        \
{                                                                             \
  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 */                     \
  gdouble normalizer;            /* divisor to get a [-1, - 1] range */       \
                                                                              \
  *CS = 0.0;                     /* Cumulative Square for this block */       \
                                                                              \
  normalizer = (double) (1 << resolution);                                    \
                                                                              \
  for (j = 0; j < num; j += channels)                                         \
  {                                                                           \
    square = ((double) in[j]) * in[j];                                        \
    if (square > PSS) PSS = square;                                           \
    squaresum += square;                                                      \
  }                                                                           \
                                                                              \
  *CS = squaresum / (normalizer * normalizer);                                \
  *peak = PSS / (normalizer * normalizer);                                    \
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
288
289
}

290
291
292
293
294
DEFINE_LEVEL_CALCULATOR (gint16);
DEFINE_LEVEL_CALCULATOR (gint8);

static GstMessage *
gst_level_message_new (GstLevel * l, gdouble endtime)
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
295
{
296
297
298
299
300
301
302
303
304
305
306
  GstStructure *s;
  GValue v = { 0, };

  g_value_init (&v, GST_TYPE_LIST);

  s = gst_structure_new ("level", "endtime", G_TYPE_DOUBLE, endtime, NULL);
  /* will copy-by-value */
  gst_structure_set_value (s, "rms", &v);
  gst_structure_set_value (s, "peak", &v);
  gst_structure_set_value (s, "decay", &v);

307
  return gst_message_new_application (GST_OBJECT (l), s);
308
}
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
309

310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
static void
gst_level_message_append_channel (GstMessage * m, gdouble rms, gdouble peak,
    gdouble decay)
{
  GstStructure *s;
  GValue v = { 0, };
  GValue *l;

  g_value_init (&v, G_TYPE_DOUBLE);

  s = (GstStructure *) gst_message_get_structure (m);

  l = (GValue *) gst_structure_get_value (s, "rms");
  g_value_set_double (&v, rms);
  gst_value_list_append_value (l, &v);  /* copies by value */

  l = (GValue *) gst_structure_get_value (s, "peak");
  g_value_set_double (&v, peak);
  gst_value_list_append_value (l, &v);  /* copies by value */

  l = (GValue *) gst_structure_get_value (s, "decay");
  g_value_set_double (&v, decay);
  gst_value_list_append_value (l, &v);  /* copies by value */
}

static GstFlowReturn
Wim Taymans's avatar
Wim Taymans committed
336
gst_level_transform (GstBaseTransform * trans, GstBuffer * in, GstBuffer * out)
337
338
339
{
  GstLevel *filter;
  gpointer in_data;
340
  double CS = 0.0;
341
  gint num_int_samples = 0;     /* number of samples for all channels combined */
342
  gint i;
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
343

344
  filter = GST_LEVEL (trans);
345

346
  for (i = 0; i < filter->channels; ++i)
347
    filter->peak[i] = filter->RMS_dB[i] = 0.0;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
348

349
  in_data = GST_BUFFER_DATA (in);
350
  num_int_samples = GST_BUFFER_SIZE (in) / (filter->width / 8);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
351

352
353
  g_return_val_if_fail (num_int_samples % filter->channels == 0,
      GST_FLOW_ERROR);
354

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
355
  for (i = 0; i < filter->channels; ++i) {
356
    CS = 0.0;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
357
    switch (filter->width) {
358
      case 16:
359
        gst_level_calculate_gint16 (in_data + i, num_int_samples,
360
361
            filter->channels, filter->width - 1, &CS, &filter->peak[i]);
        break;
362
      case 8:
363
        gst_level_calculate_gint8 (((gint8 *) in_data) + i, num_int_samples,
364
365
            filter->channels, filter->width - 1, &CS, &filter->peak[i]);
        break;
366
    }
367
368
369
    GST_LOG_OBJECT (filter,
        "channel %d, cumulative sum %f, peak %f, over %d channels/%d samples",
        i, CS, filter->peak[i], num_int_samples, filter->channels);
370
    filter->CS[i] += CS;
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
371
372
  }

373
  filter->num_samples += num_int_samples / filter->channels;
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
374

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
375
  for (i = 0; i < filter->channels; ++i) {
376
377
    filter->decay_peak_age[i] += num_int_samples / filter->channels;
    GST_LOG_OBJECT (filter, "filter peak info [%d]: peak %f, age %f\n", i,
378
        filter->last_peak[i], filter->decay_peak_age[i]);
379

380
381
    /* update running peak */
    if (filter->peak[i] > filter->last_peak[i])
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
382
      filter->last_peak[i] = filter->peak[i];
383
384

    /* update decay peak */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
385
    if (filter->peak[i] >= filter->decay_peak[i]) {
386
      GST_LOG_OBJECT (filter, "new peak, %f\n", filter->peak[i]);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
387
388
389
      filter->decay_peak[i] = filter->peak[i];
      filter->decay_peak_age[i] = 0;
    } else {
390
      /* make decay peak fall off if too old */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
391
      if (filter->decay_peak_age[i] > filter->rate * filter->decay_peak_ttl) {
392
393
394
        double falloff_dB;
        double falloff;
        double length;          /* length of buffer in seconds */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
395
396


397
        length = (double) num_int_samples / (filter->channels * filter->rate);
398
399
        falloff_dB = filter->decay_peak_falloff * length;
        falloff = pow (10, falloff_dB / -20.0);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
400

401
402
        GST_LOG_OBJECT (filter,
            "falloff: length %f, dB falloff %f, falloff factor %e\n",
403
            length, falloff_dB, falloff);
404
        filter->decay_peak[i] *= falloff;
405
406
        GST_LOG_OBJECT (filter,
            "peak is %f samples old, decayed with factor %e to %f\n",
407
            filter->decay_peak_age[i], falloff, filter->decay_peak[i]);
408
409
      } else {
        GST_LOG_OBJECT (filter, "peak not old enough, not decaying");
410
411
412
      }
    }
  }
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
413

414
  /* do we need to emit ? */
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
415
416
417

  if (filter->num_samples >= filter->interval * (gdouble) filter->rate) {
    if (filter->signal) {
418
419
      GstMessage *m;
      double endtime, RMS;
420
      double RMSdB, lastdB, decaydB;
421

422
      /* FIXME: convert to a GstClockTime instead */
423
      endtime = (double) GST_BUFFER_TIMESTAMP (in) / GST_SECOND
424
          + (double) num_int_samples / (filter->rate * filter->channels);
425
426

      m = gst_level_message_new (filter, endtime);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
427
428

      for (i = 0; i < filter->channels; ++i) {
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
        RMS = sqrt (filter->CS[i] / filter->num_samples);
        GST_LOG_OBJECT (filter,
            "CS: %f, num_samples %f, channel %d, RMS %f",
            filter->CS[i], filter->num_samples, i, RMS);
        /* RMS values are calculated in amplitude, so 20 * log 10 */
        RMSdB = 20 * log10 (RMS);
        /* peak values are square sums, ie. power, so 10 * log 10 */
        lastdB = 10 * log10 (filter->last_peak[i]);
        decaydB = 10 * log10 (filter->decay_peak[i]);

        GST_LOG_OBJECT (filter,
            "time %f, channel %d, RMS %f dB, peak %f dB, decay %f dB",
            endtime, i, RMSdB, lastdB, decaydB);

        gst_level_message_append_channel (m, RMSdB, lastdB, decaydB);
444
445

        /* reset cumulative and normal peak */
446
447
        filter->CS[i] = 0.0;
        filter->last_peak[i] = 0.0;
448
      }
449
450

      gst_element_post_message (GST_ELEMENT (filter), m);
451
452
    }
    filter->num_samples = 0;
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
453
454
  }

455
  return GST_FLOW_OK;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
456
457
}

Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
458
static gboolean
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
459
plugin_init (GstPlugin * plugin)
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
460
{
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
461
  return gst_element_register (plugin, "level", GST_RANK_NONE, GST_TYPE_LEVEL);
Andy Wingo Wingo's avatar
Andy Wingo Wingo committed
462
463
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
464
465
466
467
468
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
    "level",
    "Audio level plugin",
    plugin_init, VERSION, GST_LICENSE, GST_PACKAGE, GST_ORIGIN)