gstlevel.c 27.7 KB
Newer Older
Andy Wingo's avatar
Andy Wingo committed
1
/* GStreamer
Andy Wingo's avatar
Andy Wingo committed
2
 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3
 * Copyright (C) 2000,2001,2002,2003,2005
4 5
 *           Thomas Vander Stichele <thomas at apestaart dot org>
 *
Andy Wingo's avatar
Andy Wingo committed
6 7 8 9 10 11 12 13 14 15 16 17
 * 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
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
18 19
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
Andy Wingo's avatar
Andy Wingo committed
20 21
 */

22 23 24
/**
 * SECTION:element-level
 *
25 26
 * Level analyses incoming audio buffers and, if the #GstLevel:message property
 * is #TRUE, generates an element message named
27
 * <classname>&quot;level&quot;</classname>:
28
 * after each interval of time given by the #GstLevel:interval property.
29
 * The message's structure contains these fields:
30 31 32
 * <itemizedlist>
 * <listitem>
 *   <para>
33
 *   #GstClockTime
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
 *   <classname>&quot;timestamp&quot;</classname>:
 *   the timestamp of the buffer that triggered the message.
 *   </para>
 * </listitem>
 * <listitem>
 *   <para>
 *   #GstClockTime
 *   <classname>&quot;stream-time&quot;</classname>:
 *   the stream time of the buffer.
 *   </para>
 * </listitem>
 * <listitem>
 *   <para>
 *   #GstClockTime
 *   <classname>&quot;running-time&quot;</classname>:
 *   the running_time of the buffer.
 *   </para>
 * </listitem>
 * <listitem>
 *   <para>
 *   #GstClockTime
 *   <classname>&quot;duration&quot;</classname>:
 *   the duration of the buffer.
 *   </para>
 * </listitem>
 * <listitem>
 *   <para>
 *   #GstClockTime
62
 *   <classname>&quot;endtime&quot;</classname>:
63 64
 *   the end time of the buffer that triggered the message as stream time (this
 *   is deprecated, as it can be calculated from stream-time + duration)
65
 *   </para>
66 67 68
 * </listitem>
 * <listitem>
 *   <para>
69
 *   #GValueArray of #gdouble
70 71 72 73 74 75
 *   <classname>&quot;peak&quot;</classname>:
 *   the peak power level in dB for each channel
 *   </para>
 * </listitem>
 * <listitem>
 *   <para>
76
 *   #GValueArray of #gdouble
77 78
 *   <classname>&quot;decay&quot;</classname>:
 *   the decaying peak power level in dB for each channel
79 80 81 82
 *   The decaying peak level follows the peak level, but starts dropping if no
 *   new peak is reached after the time given by the #GstLevel:peak-ttl.
 *   When the decaying peak level drops, it does so at the decay rate as
 *   specified by the #GstLevel:peak-falloff.
83 84 85 86
 *   </para>
 * </listitem>
 * <listitem>
 *   <para>
87
 *   #GValueArray of #gdouble
88 89 90
 *   <classname>&quot;rms&quot;</classname>:
 *   the Root Mean Square (or average power) level in dB for each channel
 *   </para>
91
 * </listitem>
92
 * </itemizedlist>
93 94
 *
 * <refsect2>
95
 * <title>Example application</title>
96
 * <informalexample><programlisting language="C">
97
 * <xi:include xmlns:xi="http://www.w3.org/2003/XInclude" parse="text" href="../../../../tests/examples/level/level-example.c" />
98
 * </programlisting></informalexample>
99 100 101
 * </refsect2>
 */

102 103 104
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
105

106 107 108 109
/* FIXME 0.11: suppress warnings for deprecated API such as GValueArray
 * with newer GLib versions (>= 2.31.0) */
#define GLIB_DISABLE_DEPRECATION_WARNINGS

110
#include <string.h>
111
#include <math.h>
Andy Wingo's avatar
Andy Wingo committed
112
#include <gst/gst.h>
113
#include <gst/audio/audio.h>
114

Andy Wingo's avatar
Andy Wingo committed
115 116
#include "gstlevel.h"

117
GST_DEBUG_CATEGORY_STATIC (level_debug);
118 119
#define GST_CAT_DEFAULT level_debug

120 121
#define EPSILON 1e-35f

David Schleef's avatar
David Schleef committed
122
static GstStaticPadTemplate sink_template_factory =
123
GST_STATIC_PAD_TEMPLATE ("sink",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
124 125
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
126 127
    GST_STATIC_CAPS ("audio/x-raw, "
        "format = (string) { S8, " GST_AUDIO_NE (S16) ", " GST_AUDIO_NE (S32)
128
        ", " GST_AUDIO_NE (F32) "," GST_AUDIO_NE (F64) " },"
129
        "layout = (string) interleaved, "
130
        "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]")
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
131
    );
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
132

David Schleef's avatar
David Schleef committed
133
static GstStaticPadTemplate src_template_factory =
134
GST_STATIC_PAD_TEMPLATE ("src",
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
135 136
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
137 138
    GST_STATIC_CAPS ("audio/x-raw, "
        "format = (string) { S8, " GST_AUDIO_NE (S16) ", " GST_AUDIO_NE (S32)
139
        ", " GST_AUDIO_NE (F32) "," GST_AUDIO_NE (F64) " },"
140
        "layout = (string) interleaved, "
141
        "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]")
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
142
    );
Andy Wingo's avatar
Andy Wingo committed
143

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
144 145
enum
{
146
  PROP_0,
147 148 149
  PROP_POST_MESSAGES,
  PROP_MESSAGE,
  PROP_INTERVAL,
150 151
  PROP_PEAK_TTL,
  PROP_PEAK_FALLOFF
Andy Wingo's avatar
Andy Wingo committed
152 153
};

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
154 155
#define gst_level_parent_class parent_class
G_DEFINE_TYPE (GstLevel, gst_level, GST_TYPE_BASE_TRANSFORM);
156

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
157 158 159 160
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);
161
static void gst_level_finalize (GObject * obj);
Andy Wingo's avatar
Andy Wingo committed
162

163 164
static gboolean gst_level_set_caps (GstBaseTransform * trans, GstCaps * in,
    GstCaps * out);
165
static gboolean gst_level_start (GstBaseTransform * trans);
166 167
static GstFlowReturn gst_level_transform_ip (GstBaseTransform * trans,
    GstBuffer * in);
168 169 170
static void gst_level_post_message (GstLevel * filter);
static gboolean gst_level_sink_event (GstBaseTransform * trans,
    GstEvent * event);
171
static void gst_level_recalc_interval_frames (GstLevel * level);
172 173 174 175

static void
gst_level_class_init (GstLevelClass * klass)
{
176
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
177
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
178
  GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
179 180 181

  gobject_class->set_property = gst_level_set_property;
  gobject_class->get_property = gst_level_get_property;
182
  gobject_class->finalize = gst_level_finalize;
183

184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
  /**
   * GstLevel:post-messages
   *
   * Post messages on the bus with level information.
   *
   * Since: 1.1.0
   */
  g_object_class_install_property (gobject_class, PROP_POST_MESSAGES,
      g_param_spec_boolean ("post-messages", "Post Messages",
          "Whether to post a 'level' element message on the bus for each "
          "passed interval", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
  /* FIXME(2.0): remove this property */
  /**
   * GstLevel:post-messages
   *
   * Post messages on the bus with level information.
   *
   * Deprecated: use the #GstLevel:post-messages property
   */
203
#ifndef GST_REMOVE_DEPRECATED
204
  g_object_class_install_property (gobject_class, PROP_MESSAGE,
205
      g_param_spec_boolean ("message", "message",
206 207 208 209
          "Post a 'level' message for each passed interval "
          "(deprecated, use the post-messages property instead)", TRUE,
          G_PARAM_READWRITE | G_PARAM_DEPRECATED | G_PARAM_STATIC_STRINGS));
#endif
210
  g_object_class_install_property (gobject_class, PROP_INTERVAL,
211 212
      g_param_spec_uint64 ("interval", "Interval",
          "Interval of time between message posts (in nanoseconds)",
213 214
          1, G_MAXUINT64, GST_SECOND / 10,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
215
  g_object_class_install_property (gobject_class, PROP_PEAK_TTL,
216
      g_param_spec_uint64 ("peak-ttl", "Peak TTL",
217
          "Time To Live of decay peak before it falls back (in nanoseconds)",
218 219
          0, G_MAXUINT64, GST_SECOND / 10 * 3,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
220
  g_object_class_install_property (gobject_class, PROP_PEAK_FALLOFF,
221
      g_param_spec_double ("peak-falloff", "Peak Falloff",
222
          "Decay rate of decay peak after TTL (in dB/sec)",
223
          0.0, G_MAXDOUBLE, 10.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
224 225 226

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

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
227 228 229 230
  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));
231
  gst_element_class_set_static_metadata (element_class, "Level",
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
232 233 234 235
      "Filter/Analyzer/Audio",
      "RMS/Peak/Decaying Peak Level messager for audio/raw",
      "Thomas Vander Stichele <thomas at apestaart dot org>");

236
  trans_class->set_caps = GST_DEBUG_FUNCPTR (gst_level_set_caps);
237
  trans_class->start = GST_DEBUG_FUNCPTR (gst_level_start);
238
  trans_class->transform_ip = GST_DEBUG_FUNCPTR (gst_level_transform_ip);
239
  trans_class->sink_event = GST_DEBUG_FUNCPTR (gst_level_sink_event);
240
  trans_class->passthrough_on_same_caps = TRUE;
241 242 243
}

static void
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
244
gst_level_init (GstLevel * filter)
245 246 247
{
  filter->CS = NULL;
  filter->peak = NULL;
248 249 250 251
  filter->last_peak = NULL;
  filter->decay_peak = NULL;
  filter->decay_peak_base = NULL;
  filter->decay_peak_age = NULL;
252

253
  gst_audio_info_init (&filter->info);
254

255 256
  filter->interval = GST_SECOND / 10;
  filter->decay_peak_ttl = GST_SECOND / 10 * 3;
257
  filter->decay_peak_falloff = 10.0;    /* dB falloff (/sec) */
258

259
  filter->post_messages = TRUE;
260 261

  filter->process = NULL;
262 263

  gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (filter), TRUE);
264 265
}

266
static void
267
gst_level_finalize (GObject * obj)
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
{
  GstLevel *filter = GST_LEVEL (obj);

  g_free (filter->CS);
  g_free (filter->peak);
  g_free (filter->last_peak);
  g_free (filter->decay_peak);
  g_free (filter->decay_peak_base);
  g_free (filter->decay_peak_age);

  filter->CS = NULL;
  filter->peak = NULL;
  filter->last_peak = NULL;
  filter->decay_peak = NULL;
  filter->decay_peak_base = NULL;
  filter->decay_peak_age = NULL;

285
  G_OBJECT_CLASS (parent_class)->finalize (obj);
286 287
}

288 289 290 291 292
static void
gst_level_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstLevel *filter = GST_LEVEL (object);
Andy Wingo's avatar
Andy Wingo committed
293

294
  switch (prop_id) {
295 296 297 298
    case PROP_POST_MESSAGES:
      /* fall-through */
    case PROP_MESSAGE:
      filter->post_messages = g_value_get_boolean (value);
299
      break;
300
    case PROP_INTERVAL:
301
      filter->interval = g_value_get_uint64 (value);
302 303
      /* Not exactly thread-safe, but property does not advertise that it
       * can be changed at runtime anyway */
304
      if (GST_AUDIO_INFO_RATE (&filter->info)) {
305
        gst_level_recalc_interval_frames (filter);
306
      }
307 308
      break;
    case PROP_PEAK_TTL:
309 310
      filter->decay_peak_ttl =
          gst_guint64_to_gdouble (g_value_get_uint64 (value));
311 312 313 314 315
      break;
    case PROP_PEAK_FALLOFF:
      filter->decay_peak_falloff = g_value_get_double (value);
      break;
    default:
316
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
317 318 319
      break;
  }
}
Andy Wingo's avatar
Andy Wingo committed
320

321 322 323
static void
gst_level_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
324
{
325 326 327
  GstLevel *filter = GST_LEVEL (object);

  switch (prop_id) {
328 329 330 331
    case PROP_POST_MESSAGES:
      /* fall-through */
    case PROP_MESSAGE:
      g_value_set_boolean (value, filter->post_messages);
332
      break;
333
    case PROP_INTERVAL:
334
      g_value_set_uint64 (value, filter->interval);
335 336
      break;
    case PROP_PEAK_TTL:
337
      g_value_set_uint64 (value, filter->decay_peak_ttl);
338 339 340 341 342 343 344
      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's avatar
Andy Wingo committed
345 346 347
  }
}

348 349 350 351 352 353 354 355 356 357

/* process one (interleaved) channel of incoming samples
 * calculate square sum of samples
 * normalize and average over number of samples
 * returns a normalized cumulative square value, which can be averaged
 * to return the average power as a double between 0 and 1
 * also returns the normalized peak power (square of the highest amplitude)
 *
 * caller must assure num is a multiple of channels
 * samples for multiple channels are interleaved
Stefan Sauer's avatar
Stefan Sauer committed
358
 * input sample data enters in *in_data and is not modified
359 360
 * this filter only accepts signed audio data, so mid level is always 0
 *
Stefan Sauer's avatar
Stefan Sauer committed
361 362
 * for integers, this code considers the non-existant positive max value to be
 * full-scale; so max-1 will not map to 1.0
363 364 365
 */

#define DEFINE_INT_LEVEL_CALCULATOR(TYPE, RESOLUTION)                         \
366
static void inline                                                            \
367
gst_level_calculate_##TYPE (gpointer data, guint num, guint channels,         \
368 369 370 371
                            gdouble *NCS, gdouble *NPS)                       \
{                                                                             \
  TYPE * in = (TYPE *)data;                                                   \
  register guint j;                                                           \
Stefan Sauer's avatar
Stefan Sauer committed
372
  gdouble squaresum = 0.0;           /* square sum of the input samples */    \
373 374
  register gdouble square = 0.0;     /* Square */                             \
  register gdouble peaksquare = 0.0; /* Peak Square Sample */                 \
375
  gdouble normalizer;                /* divisor to get a [-1.0, 1.0] range */ \
376
                                                                              \
377
  /* *NCS = 0.0; Normalized Cumulative Square */                              \
Stefan Sauer's avatar
Stefan Sauer committed
378
  /* *NPS = 0.0; Normalized Peak Square */                                    \
379
                                                                              \
380
  for (j = 0; j < num; j += channels) {                                       \
381 382 383 384 385
    square = ((gdouble) in[j]) * in[j];                                       \
    if (square > peaksquare) peaksquare = square;                             \
    squaresum += square;                                                      \
  }                                                                           \
                                                                              \
386
  normalizer = (gdouble) (G_GINT64_CONSTANT(1) << (RESOLUTION * 2));          \
387 388 389 390
  *NCS = squaresum / normalizer;                                              \
  *NPS = peaksquare / normalizer;                                             \
}

391
DEFINE_INT_LEVEL_CALCULATOR (gint32, 31);
392 393 394
DEFINE_INT_LEVEL_CALCULATOR (gint16, 15);
DEFINE_INT_LEVEL_CALCULATOR (gint8, 7);

395
/* FIXME: use orc to calculate squaresums? */
396 397
#define DEFINE_FLOAT_LEVEL_CALCULATOR(TYPE)                                   \
static void inline                                                            \
398
gst_level_calculate_##TYPE (gpointer data, guint num, guint channels,         \
399 400 401 402
                            gdouble *NCS, gdouble *NPS)                       \
{                                                                             \
  TYPE * in = (TYPE *)data;                                                   \
  register guint j;                                                           \
Stefan Sauer's avatar
Stefan Sauer committed
403
  gdouble squaresum = 0.0;           /* square sum of the input samples */    \
404 405 406
  register gdouble square = 0.0;     /* Square */                             \
  register gdouble peaksquare = 0.0; /* Peak Square Sample */                 \
                                                                              \
407
  /* *NCS = 0.0; Normalized Cumulative Square */                              \
Stefan Sauer's avatar
Stefan Sauer committed
408
  /* *NPS = 0.0; Normalized Peak Square */                                    \
409
                                                                              \
410
  /* orc_level_squaresum_f64(&squaresum,in,num); */                           \
411
  for (j = 0; j < num; j += channels) {                                       \
412 413 414 415 416 417 418 419 420 421 422 423
    square = ((gdouble) in[j]) * in[j];                                       \
    if (square > peaksquare) peaksquare = square;                             \
    squaresum += square;                                                      \
  }                                                                           \
                                                                              \
  *NCS = squaresum;                                                           \
  *NPS = peaksquare;                                                          \
}

DEFINE_FLOAT_LEVEL_CALCULATOR (gfloat);
DEFINE_FLOAT_LEVEL_CALCULATOR (gdouble);

424 425 426 427 428
/* we would need stride to deinterleave also
static void inline
gst_level_calculate_gdouble (gpointer data, guint num, guint channels,
                            gdouble *NCS, gdouble *NPS)
{
429
  orc_level_squaresum_f64(NCS,(gdouble *)data,num);
430 431 432 433
  *NPS = 0.0;
}
*/

434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
static void
gst_level_recalc_interval_frames (GstLevel * level)
{
  GstClockTime interval = level->interval;
  guint sample_rate = GST_AUDIO_INFO_RATE (&level->info);
  guint interval_frames;

  interval_frames = GST_CLOCK_TIME_TO_FRAMES (interval, sample_rate);

  if (interval_frames == 0) {
    GST_WARNING_OBJECT (level, "interval %" GST_TIME_FORMAT " is too small, "
        "should be at least %" GST_TIME_FORMAT " for sample rate %u",
        GST_TIME_ARGS (interval),
        GST_TIME_ARGS (GST_FRAMES_TO_CLOCK_TIME (1, sample_rate)), sample_rate);
    interval_frames = 1;
  }

  level->interval_frames = interval_frames;

  GST_INFO_OBJECT (level, "interval_frames now %u for interval "
      "%" GST_TIME_FORMAT " and sample rate %u", interval_frames,
      GST_TIME_ARGS (interval), sample_rate);
}
457

458 459
static gboolean
gst_level_set_caps (GstBaseTransform * trans, GstCaps * in, GstCaps * out)
Andy Wingo's avatar
Andy Wingo committed
460
{
461
  GstLevel *filter = GST_LEVEL (trans);
462
  GstAudioInfo info;
463
  gint i, channels;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
464

465 466
  if (!gst_audio_info_from_caps (&info, in))
    return FALSE;
467

468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
  switch (GST_AUDIO_INFO_FORMAT (&info)) {
    case GST_AUDIO_FORMAT_S8:
      filter->process = gst_level_calculate_gint8;
      break;
    case GST_AUDIO_FORMAT_S16:
      filter->process = gst_level_calculate_gint16;
      break;
    case GST_AUDIO_FORMAT_S32:
      filter->process = gst_level_calculate_gint32;
      break;
    case GST_AUDIO_FORMAT_F32:
      filter->process = gst_level_calculate_gfloat;
      break;
    case GST_AUDIO_FORMAT_F64:
      filter->process = gst_level_calculate_gdouble;
      break;
    default:
      filter->process = NULL;
      break;
487
  }
488

489 490 491 492
  filter->info = info;

  channels = GST_AUDIO_INFO_CHANNELS (&info);

493
  /* allocate channel variable arrays */
494 495 496 497
  g_free (filter->CS);
  g_free (filter->peak);
  g_free (filter->last_peak);
  g_free (filter->decay_peak);
498
  g_free (filter->decay_peak_base);
499
  g_free (filter->decay_peak_age);
500 501 502 503 504
  filter->CS = g_new (gdouble, channels);
  filter->peak = g_new (gdouble, channels);
  filter->last_peak = g_new (gdouble, channels);
  filter->decay_peak = g_new (gdouble, channels);
  filter->decay_peak_base = g_new (gdouble, channels);
505

506
  filter->decay_peak_age = g_new (GstClockTime, channels);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
507

508
  for (i = 0; i < channels; ++i) {
509
    filter->CS[i] = filter->peak[i] = filter->last_peak[i] =
510
        filter->decay_peak[i] = filter->decay_peak_base[i] = 0.0;
511
    filter->decay_peak_age[i] = G_GUINT64_CONSTANT (0);
512 513
  }

514
  gst_level_recalc_interval_frames (filter);
515

516 517 518
  return TRUE;
}

519 520 521 522 523 524
static gboolean
gst_level_start (GstBaseTransform * trans)
{
  GstLevel *filter = GST_LEVEL (trans);

  filter->num_frames = 0;
525
  filter->message_ts = GST_CLOCK_TIME_NONE;
526 527 528 529

  return TRUE;
}

530
static GstMessage *
531
gst_level_message_new (GstLevel * level, GstClockTime timestamp,
532
    GstClockTime duration)
Andy Wingo's avatar
Andy Wingo committed
533
{
534
  GstBaseTransform *trans = GST_BASE_TRANSFORM_CAST (level);
535 536
  GstStructure *s;
  GValue v = { 0, };
537
  GstClockTime endtime, running_time, stream_time;
538

539 540 541 542 543 544 545 546 547 548 549 550 551
  running_time = gst_segment_to_running_time (&trans->segment, GST_FORMAT_TIME,
      timestamp);
  stream_time = gst_segment_to_stream_time (&trans->segment, GST_FORMAT_TIME,
      timestamp);
  /* endtime is for backwards compatibility */
  endtime = stream_time + duration;

  s = gst_structure_new ("level",
      "endtime", GST_TYPE_CLOCK_TIME, endtime,
      "timestamp", G_TYPE_UINT64, timestamp,
      "stream-time", G_TYPE_UINT64, stream_time,
      "running-time", G_TYPE_UINT64, running_time,
      "duration", G_TYPE_UINT64, duration, NULL);
552

553 554 555 556 557 558 559 560 561 562 563
  g_value_init (&v, G_TYPE_VALUE_ARRAY);
  g_value_take_boxed (&v, g_value_array_new (0));
  gst_structure_take_value (s, "rms", &v);

  g_value_init (&v, G_TYPE_VALUE_ARRAY);
  g_value_take_boxed (&v, g_value_array_new (0));
  gst_structure_take_value (s, "peak", &v);

  g_value_init (&v, G_TYPE_VALUE_ARRAY);
  g_value_take_boxed (&v, g_value_array_new (0));
  gst_structure_take_value (s, "decay", &v);
564

565
  return gst_message_new_element (GST_OBJECT (level), s);
566
}
Andy Wingo's avatar
Andy Wingo committed
567

568 569 570 571
static void
gst_level_message_append_channel (GstMessage * m, gdouble rms, gdouble peak,
    gdouble decay)
{
572
  const GValue *array_val;
573
  GstStructure *s;
574
  GValueArray *arr;
575 576 577 578 579 580
  GValue v = { 0, };

  g_value_init (&v, G_TYPE_DOUBLE);

  s = (GstStructure *) gst_message_get_structure (m);

581 582
  array_val = gst_structure_get_value (s, "rms");
  arr = (GValueArray *) g_value_get_boxed (array_val);
583
  g_value_set_double (&v, rms);
584
  g_value_array_append (arr, &v);       /* copies by value */
585

586 587
  array_val = gst_structure_get_value (s, "peak");
  arr = (GValueArray *) g_value_get_boxed (array_val);
588
  g_value_set_double (&v, peak);
589
  g_value_array_append (arr, &v);       /* copies by value */
590

591 592
  array_val = gst_structure_get_value (s, "decay");
  arr = (GValueArray *) g_value_get_boxed (array_val);
593
  g_value_set_double (&v, decay);
594
  g_value_array_append (arr, &v);       /* copies by value */
595 596

  g_value_unset (&v);
597 598 599
}

static GstFlowReturn
600
gst_level_transform_ip (GstBaseTransform * trans, GstBuffer * in)
601 602
{
  GstLevel *filter;
Wim Taymans's avatar
Wim Taymans committed
603 604
  GstMapInfo map;
  guint8 *in_data;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
605
  gsize in_size;
Stefan Kost's avatar
Stefan Kost committed
606
  gdouble CS;
607
  guint i;
608
  guint num_frames;
609
  guint num_int_samples = 0;    /* number of interleaved samples
610
                                 * ie. total count for all channels combined */
611 612
  guint block_size, block_int_size;     /* we subdivide buffers to not skip message
                                         * intervals */
Stefan Kost's avatar
Stefan Kost committed
613
  GstClockTimeDiff falloff_time;
614
  gint channels, rate, bps;
Andy Wingo's avatar
Andy Wingo committed
615

616
  filter = GST_LEVEL (trans);
617

618 619 620 621
  channels = GST_AUDIO_INFO_CHANNELS (&filter->info);
  bps = GST_AUDIO_INFO_BPS (&filter->info);
  rate = GST_AUDIO_INFO_RATE (&filter->info);

Wim Taymans's avatar
Wim Taymans committed
622 623 624 625
  gst_buffer_map (in, &map, GST_MAP_READ);
  in_data = map.data;
  in_size = map.size;

626
  num_int_samples = in_size / bps;
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
627

628 629 630
  GST_LOG_OBJECT (filter, "analyzing %u sample frames at ts %" GST_TIME_FORMAT,
      num_int_samples, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (in)));

631
  g_return_val_if_fail (num_int_samples % channels == 0, GST_FLOW_ERROR);
632

Stefan Sauer's avatar
Stefan Sauer committed
633 634 635 636
  if (GST_BUFFER_FLAG_IS_SET (in, GST_BUFFER_FLAG_DISCONT)) {
    filter->message_ts = GST_BUFFER_TIMESTAMP (in);
    filter->num_frames = 0;
  }
637 638 639 640
  if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (filter->message_ts))) {
    filter->message_ts = GST_BUFFER_TIMESTAMP (in);
  }

641
  num_frames = num_int_samples / channels;
642 643 644 645
  while (num_frames > 0) {
    block_size = filter->interval_frames - filter->num_frames;
    block_size = MIN (block_size, num_frames);
    block_int_size = block_size * channels;
646

647 648
    for (i = 0; i < channels; ++i) {
      if (!GST_BUFFER_FLAG_IS_SET (in, GST_BUFFER_FLAG_GAP)) {
649
        filter->process (in_data + (bps * i), block_int_size, channels, &CS,
650 651
            &filter->peak[i]);
        GST_LOG_OBJECT (filter,
Stefan Sauer's avatar
Stefan Sauer committed
652 653
            "[%d]: cumulative squares %lf, over %d samples/%d channels",
            i, CS, block_int_size, channels);
654 655 656 657 658 659
        filter->CS[i] += CS;
      } else {
        filter->peak[i] = 0.0;
      }

      filter->decay_peak_age[i] += GST_FRAMES_TO_CLOCK_TIME (num_frames, rate);
660
      GST_LOG_OBJECT (filter,
661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
          "[%d]: peak %f, last peak %f, decay peak %f, age %" GST_TIME_FORMAT,
          i, filter->peak[i], filter->last_peak[i], filter->decay_peak[i],
          GST_TIME_ARGS (filter->decay_peak_age[i]));

      /* update running peak */
      if (filter->peak[i] > filter->last_peak[i])
        filter->last_peak[i] = filter->peak[i];

      /* make decay peak fall off if too old */
      falloff_time =
          GST_CLOCK_DIFF (gst_gdouble_to_guint64 (filter->decay_peak_ttl),
          filter->decay_peak_age[i]);
      if (falloff_time > 0) {
        gdouble falloff_dB;
        gdouble falloff;
        gdouble length;         /* length of falloff time in seconds */

        length = (gdouble) falloff_time / (gdouble) GST_SECOND;
        falloff_dB = filter->decay_peak_falloff * length;
        falloff = pow (10, falloff_dB / -20.0);

        GST_LOG_OBJECT (filter,
            "falloff: current %f, base %f, interval %" GST_TIME_FORMAT
            ", dB falloff %f, factor %e",
            filter->decay_peak[i], filter->decay_peak_base[i],
            GST_TIME_ARGS (falloff_time), falloff_dB, falloff);
        filter->decay_peak[i] = filter->decay_peak_base[i] * falloff;
        GST_LOG_OBJECT (filter,
            "peak is %" GST_TIME_FORMAT " old, decayed with factor %e to %f",
            GST_TIME_ARGS (filter->decay_peak_age[i]), falloff,
            filter->decay_peak[i]);
      } else {
        GST_LOG_OBJECT (filter, "peak not old enough, not decaying");
      }
695

696 697 698 699 700 701 702
      /* if the peak of this run is higher, the decay peak gets reset */
      if (filter->peak[i] >= filter->decay_peak[i]) {
        GST_LOG_OBJECT (filter, "new peak, %f", filter->peak[i]);
        filter->decay_peak[i] = filter->peak[i];
        filter->decay_peak_base[i] = filter->peak[i];
        filter->decay_peak_age[i] = G_GINT64_CONSTANT (0);
      }
703
    }
704
    in_data += block_size * bps * channels;
Andy Wingo's avatar
Andy Wingo committed
705

706 707
    filter->num_frames += block_size;
    num_frames -= block_size;
708

709 710 711 712
    /* do we need to message ? */
    if (filter->num_frames >= filter->interval_frames) {
      gst_level_post_message (filter);
    }
713 714 715 716 717 718 719 720 721 722 723
  }

  gst_buffer_unmap (in, &map);

  return GST_FLOW_OK;
}

static void
gst_level_post_message (GstLevel * filter)
{
  guint i;
Stefan Sauer's avatar
Stefan Sauer committed
724
  gint channels, rate, frames = filter->num_frames;
725
  GstClockTime duration;
726 727 728

  channels = GST_AUDIO_INFO_CHANNELS (&filter->info);
  rate = GST_AUDIO_INFO_RATE (&filter->info);
Stefan Sauer's avatar
Stefan Sauer committed
729
  duration = GST_FRAMES_TO_CLOCK_TIME (frames, rate);
730

731
  if (filter->post_messages) {
732 733
    GstMessage *m =
        gst_level_message_new (filter, filter->message_ts, duration);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
734

735
    GST_LOG_OBJECT (filter,
Stefan Sauer's avatar
Stefan Sauer committed
736 737
        "message: ts %" GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT
        ", num_frames %d", GST_TIME_ARGS (filter->message_ts),
Stefan Sauer's avatar
Stefan Sauer committed
738
        GST_TIME_ARGS (duration), frames);
739 740 741

    for (i = 0; i < channels; ++i) {
      gdouble RMS;
Stefan Sauer's avatar
Stefan Sauer committed
742
      gdouble RMSdB, peakdB, decaydB;
743

Stefan Sauer's avatar
Stefan Sauer committed
744
      RMS = sqrt (filter->CS[i] / frames);
745
      GST_LOG_OBJECT (filter,
Stefan Sauer's avatar
Stefan Sauer committed
746
          "message: channel %d, CS %f, RMS %f", i, filter->CS[i], RMS);
747 748 749 750 751 752
      GST_LOG_OBJECT (filter,
          "message: last_peak: %f, decay_peak: %f",
          filter->last_peak[i], filter->decay_peak[i]);
      /* RMS values are calculated in amplitude, so 20 * log 10 */
      RMSdB = 20 * log10 (RMS + EPSILON);
      /* peak values are square sums, ie. power, so 10 * log 10 */
Stefan Sauer's avatar
Stefan Sauer committed
753
      peakdB = 10 * log10 (filter->last_peak[i] + EPSILON);
754 755 756 757 758 759 760
      decaydB = 10 * log10 (filter->decay_peak[i] + EPSILON);

      if (filter->decay_peak[i] < filter->last_peak[i]) {
        /* this can happen in certain cases, for example when
         * the last peak is between decay_peak and decay_peak_base */
        GST_DEBUG_OBJECT (filter,
            "message: decay peak dB %f smaller than last peak dB %f, copying",
Stefan Sauer's avatar
Stefan Sauer committed
761
            decaydB, peakdB);
762
        filter->decay_peak[i] = filter->last_peak[i];
763
      }
764 765
      GST_LOG_OBJECT (filter,
          "message: RMS %f dB, peak %f dB, decay %f dB",
Stefan Sauer's avatar
Stefan Sauer committed
766
          RMSdB, peakdB, decaydB);
767

Stefan Sauer's avatar
Stefan Sauer committed
768
      gst_level_message_append_channel (m, RMSdB, peakdB, decaydB);
769 770 771 772

      /* reset cumulative and normal peak */
      filter->CS[i] = 0.0;
      filter->last_peak[i] = 0.0;
773
    }
774

775
    gst_element_post_message (GST_ELEMENT (filter), m);
Stefan Sauer's avatar
Stefan Sauer committed
776

Andy Wingo's avatar
Andy Wingo committed
777
  }
Stefan Sauer's avatar
Stefan Sauer committed
778
  filter->num_frames -= frames;
779
  filter->message_ts += duration;
780
}
Andy Wingo's avatar
Andy Wingo committed
781

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
782

783 784 785 786 787 788 789 790 791 792
static gboolean
gst_level_sink_event (GstBaseTransform * trans, GstEvent * event)
{
  if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
    GstLevel *filter = GST_LEVEL (trans);

    gst_level_post_message (filter);
  }

  return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
793 794
}

Andy Wingo's avatar
Andy Wingo committed
795
static gboolean
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
796
plugin_init (GstPlugin * plugin)
Andy Wingo's avatar
Andy Wingo committed
797
{
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
798
  return gst_element_register (plugin, "level", GST_RANK_NONE, GST_TYPE_LEVEL);
Andy Wingo's avatar
Andy Wingo committed
799 800
}

Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
801 802
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
803
    level,
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
804
    "Audio level plugin",
805
    plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);