1 /* GStreamer
2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3 * Copyright (C) 2000,2001,2002,2003,2005
4 * Thomas Vander Stichele <thomas at apestaart dot org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 */
21
22 /**
23 * SECTION:element-level
24 *
25 * Level analyses incoming audio buffers and, if the #GstLevel:message property
26 * is %TRUE, generates an element message named
27 * <classname>"level"</classname>:
28 * after each interval of time given by the #GstLevel:interval property.
29 * The message's structure contains these fields:
30 * <itemizedlist>
31 * <listitem>
32 * <para>
33 * #GstClockTime
34 * <classname>"timestamp"</classname>:
35 * the timestamp of the buffer that triggered the message.
36 * </para>
37 * </listitem>
38 * <listitem>
39 * <para>
40 * #GstClockTime
41 * <classname>"stream-time"</classname>:
42 * the stream time of the buffer.
43 * </para>
44 * </listitem>
45 * <listitem>
46 * <para>
47 * #GstClockTime
48 * <classname>"running-time"</classname>:
49 * the running_time of the buffer.
50 * </para>
51 * </listitem>
52 * <listitem>
53 * <para>
54 * #GstClockTime
55 * <classname>"duration"</classname>:
56 * the duration of the buffer.
57 * </para>
58 * </listitem>
59 * <listitem>
60 * <para>
61 * #GstClockTime
62 * <classname>"endtime"</classname>:
63 * the end time of the buffer that triggered the message as stream time (this
64 * is deprecated, as it can be calculated from stream-time + duration)
65 * </para>
66 * </listitem>
67 * <listitem>
68 * <para>
69 * #GValueArray of #gdouble
70 * <classname>"peak"</classname>:
71 * the peak power level in dB for each channel
72 * </para>
73 * </listitem>
74 * <listitem>
75 * <para>
76 * #GValueArray of #gdouble
77 * <classname>"decay"</classname>:
78 * the decaying peak power level in dB for each channel
79 * The decaying peak level follows the peak level, but starts dropping if no
80 * new peak is reached after the time given by the #GstLevel:peak-ttl.
81 * When the decaying peak level drops, it does so at the decay rate as
82 * specified by the #GstLevel:peak-falloff.
83 * </para>
84 * </listitem>
85 * <listitem>
86 * <para>
87 * #GValueArray of #gdouble
88 * <classname>"rms"</classname>:
89 * the Root Mean Square (or average power) level in dB for each channel
90 * </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 * </refsect2>
100 */
101
102 #ifdef HAVE_CONFIG_H
103 #include "config.h"
104 #endif
105
106 /* FIXME 0.11: suppress warnings for deprecated API such as GValueArray
107 * with newer GLib versions (>= 2.31.0) */
108 #define GLIB_DISABLE_DEPRECATION_WARNINGS
109
110 #include <string.h>
111 #include <math.h>
112 #include <gst/gst.h>
113 #include <gst/audio/audio.h>
114
115 #include "gstlevel.h"
116
117 GST_DEBUG_CATEGORY_STATIC (level_debug);
118 #define GST_CAT_DEFAULT level_debug
119
120 #define EPSILON 1e-35f
121
122 static GstStaticPadTemplate sink_template_factory =
123 GST_STATIC_PAD_TEMPLATE ("sink",
124 GST_PAD_SINK,
125 GST_PAD_ALWAYS,
126 GST_STATIC_CAPS ("audio/x-raw, "
127 "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 ]")
131 );
132
133 static GstStaticPadTemplate src_template_factory =
134 GST_STATIC_PAD_TEMPLATE ("src",
135 GST_PAD_SRC,
136 GST_PAD_ALWAYS,
137 GST_STATIC_CAPS ("audio/x-raw, "
138 "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 ]")
142 );
143
144 enum
145 {
146 PROP_0,
147 PROP_POST_MESSAGES,
148 PROP_MESSAGE,
149 PROP_INTERVAL,
150 PROP_PEAK_TTL,
151 PROP_PEAK_FALLOFF
152 };
153
154 #define gst_level_parent_class parent_class
155 G_DEFINE_TYPE (GstLevel, gst_level, GST_TYPE_BASE_TRANSFORM);
156
157 static void gst_level_set_property (GObject * object, guint prop_id,
158 const GValue * value, GParamSpec * pspec);
159 static void gst_level_get_property (GObject * object, guint prop_id,
160 GValue * value, GParamSpec * pspec);
161 static void gst_level_finalize (GObject * obj);
162
163 static gboolean gst_level_set_caps (GstBaseTransform * trans, GstCaps * in,
164 GstCaps * out);
165 static gboolean gst_level_start (GstBaseTransform * trans);
166 static GstFlowReturn gst_level_transform_ip (GstBaseTransform * trans,
167 GstBuffer * in);
168 static void gst_level_post_message (GstLevel * filter);
169 static gboolean gst_level_sink_event (GstBaseTransform * trans,
170 GstEvent * event);
171 static void gst_level_recalc_interval_frames (GstLevel * level);
172
173 static void
gst_level_class_init(GstLevelClass * klass)174 gst_level_class_init (GstLevelClass * klass)
175 {
176 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
177 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
178 GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
179
180 gobject_class->set_property = gst_level_set_property;
181 gobject_class->get_property = gst_level_get_property;
182 gobject_class->finalize = gst_level_finalize;
183
184 /**
185 * GstLevel:post-messages
186 *
187 * Post messages on the bus with level information.
188 *
189 * Since: 1.1.0
190 */
191 g_object_class_install_property (gobject_class, PROP_POST_MESSAGES,
192 g_param_spec_boolean ("post-messages", "Post Messages",
193 "Whether to post a 'level' element message on the bus for each "
194 "passed interval", TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
195 /* FIXME(2.0): remove this property */
196 /**
197 * GstLevel:post-messages
198 *
199 * Post messages on the bus with level information.
200 *
201 * Deprecated: use the #GstLevel:post-messages property
202 */
203 #ifndef GST_REMOVE_DEPRECATED
204 g_object_class_install_property (gobject_class, PROP_MESSAGE,
205 g_param_spec_boolean ("message", "message",
206 "Post a 'level' message for each passed interval "
207 "(deprecated, use the post-messages property instead)", TRUE,
208 G_PARAM_READWRITE | G_PARAM_DEPRECATED | G_PARAM_STATIC_STRINGS));
209 #endif
210 g_object_class_install_property (gobject_class, PROP_INTERVAL,
211 g_param_spec_uint64 ("interval", "Interval",
212 "Interval of time between message posts (in nanoseconds)",
213 1, G_MAXUINT64, GST_SECOND / 10,
214 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 0, G_MAXUINT64, GST_SECOND / 10 * 3,
219 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 GST_DEBUG_CATEGORY_INIT (level_debug, "level", 0, "Level calculation");
226
227 gst_element_class_add_static_pad_template (element_class,
228 &sink_template_factory);
229 gst_element_class_add_static_pad_template (element_class,
230 &src_template_factory);
231 gst_element_class_set_static_metadata (element_class, "Level",
232 "Filter/Analyzer/Audio",
233 "RMS/Peak/Decaying Peak Level messager for audio/raw",
234 "Thomas Vander Stichele <thomas at apestaart dot org>");
235
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
gst_level_init(GstLevel * filter)244 gst_level_init (GstLevel * filter)
245 {
246 filter->CS = NULL;
247 filter->peak = NULL;
248 filter->last_peak = NULL;
249 filter->decay_peak = NULL;
250 filter->decay_peak_base = NULL;
251 filter->decay_peak_age = NULL;
252
253 gst_audio_info_init (&filter->info);
254
255 filter->interval = GST_SECOND / 10;
256 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
gst_level_finalize(GObject * obj)267 gst_level_finalize (GObject * obj)
268 {
269 GstLevel *filter = GST_LEVEL (obj);
270
271 g_free (filter->CS);
272 g_free (filter->peak);
273 g_free (filter->last_peak);
274 g_free (filter->decay_peak);
275 g_free (filter->decay_peak_base);
276 g_free (filter->decay_peak_age);
277
278 filter->CS = NULL;
279 filter->peak = NULL;
280 filter->last_peak = NULL;
281 filter->decay_peak = NULL;
282 filter->decay_peak_base = NULL;
283 filter->decay_peak_age = NULL;
284
285 G_OBJECT_CLASS (parent_class)->finalize (obj);
286 }
287
288 static void
gst_level_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)289 gst_level_set_property (GObject * object, guint prop_id,
290 const GValue * value, GParamSpec * pspec)
291 {
292 GstLevel *filter = GST_LEVEL (object);
293
294 switch (prop_id) {
295 case PROP_POST_MESSAGES:
296 /* fall-through */
297 case PROP_MESSAGE:
298 filter->post_messages = g_value_get_boolean (value);
299 break;
300 case PROP_INTERVAL:
301 filter->interval = g_value_get_uint64 (value);
302 /* Not exactly thread-safe, but property does not advertise that it
303 * can be changed at runtime anyway */
304 if (GST_AUDIO_INFO_RATE (&filter->info)) {
305 gst_level_recalc_interval_frames (filter);
306 }
307 break;
308 case PROP_PEAK_TTL:
309 filter->decay_peak_ttl =
310 gst_guint64_to_gdouble (g_value_get_uint64 (value));
311 break;
312 case PROP_PEAK_FALLOFF:
313 filter->decay_peak_falloff = g_value_get_double (value);
314 break;
315 default:
316 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
317 break;
318 }
319 }
320
321 static void
gst_level_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)322 gst_level_get_property (GObject * object, guint prop_id,
323 GValue * value, GParamSpec * pspec)
324 {
325 GstLevel *filter = GST_LEVEL (object);
326
327 switch (prop_id) {
328 case PROP_POST_MESSAGES:
329 /* fall-through */
330 case PROP_MESSAGE:
331 g_value_set_boolean (value, filter->post_messages);
332 break;
333 case PROP_INTERVAL:
334 g_value_set_uint64 (value, filter->interval);
335 break;
336 case PROP_PEAK_TTL:
337 g_value_set_uint64 (value, filter->decay_peak_ttl);
338 break;
339 case PROP_PEAK_FALLOFF:
340 g_value_set_double (value, filter->decay_peak_falloff);
341 break;
342 default:
343 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
344 break;
345 }
346 }
347
348
349 /* process one (interleaved) channel of incoming samples
350 * calculate square sum of samples
351 * normalize and average over number of samples
352 * returns a normalized cumulative square value, which can be averaged
353 * to return the average power as a double between 0 and 1
354 * also returns the normalized peak power (square of the highest amplitude)
355 *
356 * caller must assure num is a multiple of channels
357 * samples for multiple channels are interleaved
358 * input sample data enters in *in_data and is not modified
359 * this filter only accepts signed audio data, so mid level is always 0
360 *
361 * for integers, this code considers the non-existant positive max value to be
362 * 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 gdouble *NCS, gdouble *NPS) \
369 { \
370 TYPE * in = (TYPE *)data; \
371 register guint j; \
372 gdouble squaresum = 0.0; /* square sum of the input samples */ \
373 register gdouble square = 0.0; /* Square */ \
374 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 */ \
378 /* *NPS = 0.0; Normalized Peak Square */ \
379 \
380 for (j = 0; j < num; j += channels) { \
381 square = ((gdouble) in[j]) * in[j]; \
382 if (square > peaksquare) peaksquare = square; \
383 squaresum += square; \
384 } \
385 \
386 normalizer = (gdouble) (G_GINT64_CONSTANT(1) << (RESOLUTION * 2)); \
387 *NCS = squaresum / normalizer; \
388 *NPS = peaksquare / normalizer; \
389 }
390
391 DEFINE_INT_LEVEL_CALCULATOR (gint32, 31);
392 DEFINE_INT_LEVEL_CALCULATOR (gint16, 15);
393 DEFINE_INT_LEVEL_CALCULATOR (gint8, 7);
394
395 /* FIXME: use orc to calculate squaresums? */
396 #define DEFINE_FLOAT_LEVEL_CALCULATOR(TYPE) \
397 static void inline \
398 gst_level_calculate_##TYPE (gpointer data, guint num, guint channels, \
399 gdouble *NCS, gdouble *NPS) \
400 { \
401 TYPE * in = (TYPE *)data; \
402 register guint j; \
403 gdouble squaresum = 0.0; /* square sum of the input samples */ \
404 register gdouble square = 0.0; /* Square */ \
405 register gdouble peaksquare = 0.0; /* Peak Square Sample */ \
406 \
407 /* *NCS = 0.0; Normalized Cumulative Square */ \
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 square = ((gdouble) in[j]) * in[j]; \
413 if (square > peaksquare) peaksquare = square; \
414 squaresum += square; \
415 } \
416 \
417 *NCS = squaresum; \
418 *NPS = peaksquare; \
419 }
420
421 DEFINE_FLOAT_LEVEL_CALCULATOR (gfloat);
422 DEFINE_FLOAT_LEVEL_CALCULATOR (gdouble);
423
424 /* we would need stride to deinterleave also
425 static void inline
426 gst_level_calculate_gdouble (gpointer data, guint num, guint channels,
427 gdouble *NCS, gdouble *NPS)
428 {
429 orc_level_squaresum_f64(NCS,(gdouble *)data,num);
430 *NPS = 0.0;
431 }
432 */
433
434 static void
gst_level_recalc_interval_frames(GstLevel * level)435 gst_level_recalc_interval_frames (GstLevel * level)
436 {
437 GstClockTime interval = level->interval;
438 guint sample_rate = GST_AUDIO_INFO_RATE (&level->info);
439 guint interval_frames;
440
441 interval_frames = GST_CLOCK_TIME_TO_FRAMES (interval, sample_rate);
442
443 if (interval_frames == 0) {
444 GST_WARNING_OBJECT (level, "interval %" GST_TIME_FORMAT " is too small, "
445 "should be at least %" GST_TIME_FORMAT " for sample rate %u",
446 GST_TIME_ARGS (interval),
447 GST_TIME_ARGS (GST_FRAMES_TO_CLOCK_TIME (1, sample_rate)), sample_rate);
448 interval_frames = 1;
449 }
450
451 level->interval_frames = interval_frames;
452
453 GST_INFO_OBJECT (level, "interval_frames now %u for interval "
454 "%" GST_TIME_FORMAT " and sample rate %u", interval_frames,
455 GST_TIME_ARGS (interval), sample_rate);
456 }
457
458 static gboolean
gst_level_set_caps(GstBaseTransform * trans,GstCaps * in,GstCaps * out)459 gst_level_set_caps (GstBaseTransform * trans, GstCaps * in, GstCaps * out)
460 {
461 GstLevel *filter = GST_LEVEL (trans);
462 GstAudioInfo info;
463 gint i, channels;
464
465 if (!gst_audio_info_from_caps (&info, in))
466 return FALSE;
467
468 switch (GST_AUDIO_INFO_FORMAT (&info)) {
469 case GST_AUDIO_FORMAT_S8:
470 filter->process = gst_level_calculate_gint8;
471 break;
472 case GST_AUDIO_FORMAT_S16:
473 filter->process = gst_level_calculate_gint16;
474 break;
475 case GST_AUDIO_FORMAT_S32:
476 filter->process = gst_level_calculate_gint32;
477 break;
478 case GST_AUDIO_FORMAT_F32:
479 filter->process = gst_level_calculate_gfloat;
480 break;
481 case GST_AUDIO_FORMAT_F64:
482 filter->process = gst_level_calculate_gdouble;
483 break;
484 default:
485 filter->process = NULL;
486 break;
487 }
488
489 filter->info = info;
490
491 channels = GST_AUDIO_INFO_CHANNELS (&info);
492
493 /* allocate channel variable arrays */
494 g_free (filter->CS);
495 g_free (filter->peak);
496 g_free (filter->last_peak);
497 g_free (filter->decay_peak);
498 g_free (filter->decay_peak_base);
499 g_free (filter->decay_peak_age);
500 filter->CS = g_new (gdouble, channels);
501 filter->peak = g_new (gdouble, channels);
502 filter->last_peak = g_new (gdouble, channels);
503 filter->decay_peak = g_new (gdouble, channels);
504 filter->decay_peak_base = g_new (gdouble, channels);
505
506 filter->decay_peak_age = g_new (GstClockTime, channels);
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 return TRUE;
517 }
518
519 static gboolean
gst_level_start(GstBaseTransform * trans)520 gst_level_start (GstBaseTransform * trans)
521 {
522 GstLevel *filter = GST_LEVEL (trans);
523
524 filter->num_frames = 0;
525 filter->message_ts = GST_CLOCK_TIME_NONE;
526
527 return TRUE;
528 }
529
530 static GstMessage *
gst_level_message_new(GstLevel * level,GstClockTime timestamp,GstClockTime duration)531 gst_level_message_new (GstLevel * level, GstClockTime timestamp,
532 GstClockTime duration)
533 {
534 GstBaseTransform *trans = GST_BASE_TRANSFORM_CAST (level);
535 GstStructure *s;
536 GValue v = { 0, };
537 GstClockTime endtime, running_time, stream_time;
538
539 running_time = gst_segment_to_running_time (&trans->segment, GST_FORMAT_TIME,
540 timestamp);
541 stream_time = gst_segment_to_stream_time (&trans->segment, GST_FORMAT_TIME,
542 timestamp);
543 /* endtime is for backwards compatibility */
544 endtime = stream_time + duration;
545
546 s = gst_structure_new ("level",
547 "endtime", GST_TYPE_CLOCK_TIME, endtime,
548 "timestamp", G_TYPE_UINT64, timestamp,
549 "stream-time", G_TYPE_UINT64, stream_time,
550 "running-time", G_TYPE_UINT64, running_time,
551 "duration", G_TYPE_UINT64, duration, NULL);
552
553 g_value_init (&v, G_TYPE_VALUE_ARRAY);
554 g_value_take_boxed (&v, g_value_array_new (0));
555 gst_structure_take_value (s, "rms", &v);
556
557 g_value_init (&v, G_TYPE_VALUE_ARRAY);
558 g_value_take_boxed (&v, g_value_array_new (0));
559 gst_structure_take_value (s, "peak", &v);
560
561 g_value_init (&v, G_TYPE_VALUE_ARRAY);
562 g_value_take_boxed (&v, g_value_array_new (0));
563 gst_structure_take_value (s, "decay", &v);
564
565 return gst_message_new_element (GST_OBJECT (level), s);
566 }
567
568 static void
gst_level_message_append_channel(GstMessage * m,gdouble rms,gdouble peak,gdouble decay)569 gst_level_message_append_channel (GstMessage * m, gdouble rms, gdouble peak,
570 gdouble decay)
571 {
572 const GValue *array_val;
573 GstStructure *s;
574 GValueArray *arr;
575 GValue v = { 0, };
576
577 g_value_init (&v, G_TYPE_DOUBLE);
578
579 s = (GstStructure *) gst_message_get_structure (m);
580
581 array_val = gst_structure_get_value (s, "rms");
582 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 array_val = gst_structure_get_value (s, "peak");
587 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 array_val = gst_structure_get_value (s, "decay");
592 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
gst_level_transform_ip(GstBaseTransform * trans,GstBuffer * in)600 gst_level_transform_ip (GstBaseTransform * trans, GstBuffer * in)
601 {
602 GstLevel *filter;
603 GstMapInfo map;
604 guint8 *in_data;
605 gsize in_size;
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 guint block_size, block_int_size; /* we subdivide buffers to not skip message
612 * intervals */
613 GstClockTimeDiff falloff_time;
614 gint channels, rate, bps;
615
616 filter = GST_LEVEL (trans);
617
618 channels = GST_AUDIO_INFO_CHANNELS (&filter->info);
619 bps = GST_AUDIO_INFO_BPS (&filter->info);
620 rate = GST_AUDIO_INFO_RATE (&filter->info);
621
622 gst_buffer_map (in, &map, GST_MAP_READ);
623 in_data = map.data;
624 in_size = map.size;
625
626 num_int_samples = in_size / bps;
627
628 GST_LOG_OBJECT (filter, "analyzing %u sample frames at ts %" GST_TIME_FORMAT,
629 num_int_samples, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (in)));
630
631 g_return_val_if_fail (num_int_samples % channels == 0, GST_FLOW_ERROR);
632
633 if (GST_BUFFER_FLAG_IS_SET (in, GST_BUFFER_FLAG_DISCONT)) {
634 filter->message_ts = GST_BUFFER_TIMESTAMP (in);
635 filter->num_frames = 0;
636 }
637 if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (filter->message_ts))) {
638 filter->message_ts = GST_BUFFER_TIMESTAMP (in);
639 }
640
641 num_frames = num_int_samples / channels;
642 while (num_frames > 0) {
643 block_size = filter->interval_frames - filter->num_frames;
644 block_size = MIN (block_size, num_frames);
645 block_int_size = block_size * channels;
646
647 for (i = 0; i < channels; ++i) {
648 if (!GST_BUFFER_FLAG_IS_SET (in, GST_BUFFER_FLAG_GAP)) {
649 filter->process (in_data + (bps * i), block_int_size, channels, &CS,
650 &filter->peak[i]);
651 GST_LOG_OBJECT (filter,
652 "[%d]: cumulative squares %lf, over %d samples/%d channels",
653 i, CS, block_int_size, channels);
654 filter->CS[i] += CS;
655 } else {
656 filter->peak[i] = 0.0;
657 }
658
659 filter->decay_peak_age[i] += GST_FRAMES_TO_CLOCK_TIME (num_frames, rate);
660 GST_LOG_OBJECT (filter,
661 "[%d]: peak %f, last peak %f, decay peak %f, age %" GST_TIME_FORMAT,
662 i, filter->peak[i], filter->last_peak[i], filter->decay_peak[i],
663 GST_TIME_ARGS (filter->decay_peak_age[i]));
664
665 /* update running peak */
666 if (filter->peak[i] > filter->last_peak[i])
667 filter->last_peak[i] = filter->peak[i];
668
669 /* make decay peak fall off if too old */
670 falloff_time =
671 GST_CLOCK_DIFF (gst_gdouble_to_guint64 (filter->decay_peak_ttl),
672 filter->decay_peak_age[i]);
673 if (falloff_time > 0) {
674 gdouble falloff_dB;
675 gdouble falloff;
676 gdouble length; /* length of falloff time in seconds */
677
678 length = (gdouble) falloff_time / (gdouble) GST_SECOND;
679 falloff_dB = filter->decay_peak_falloff * length;
680 falloff = pow (10, falloff_dB / -20.0);
681
682 GST_LOG_OBJECT (filter,
683 "falloff: current %f, base %f, interval %" GST_TIME_FORMAT
684 ", dB falloff %f, factor %e",
685 filter->decay_peak[i], filter->decay_peak_base[i],
686 GST_TIME_ARGS (falloff_time), falloff_dB, falloff);
687 filter->decay_peak[i] = filter->decay_peak_base[i] * falloff;
688 GST_LOG_OBJECT (filter,
689 "peak is %" GST_TIME_FORMAT " old, decayed with factor %e to %f",
690 GST_TIME_ARGS (filter->decay_peak_age[i]), falloff,
691 filter->decay_peak[i]);
692 } else {
693 GST_LOG_OBJECT (filter, "peak not old enough, not decaying");
694 }
695
696 /* if the peak of this run is higher, the decay peak gets reset */
697 if (filter->peak[i] >= filter->decay_peak[i]) {
698 GST_LOG_OBJECT (filter, "new peak, %f", filter->peak[i]);
699 filter->decay_peak[i] = filter->peak[i];
700 filter->decay_peak_base[i] = filter->peak[i];
701 filter->decay_peak_age[i] = G_GINT64_CONSTANT (0);
702 }
703 }
704 in_data += block_size * bps * channels;
705
706 filter->num_frames += block_size;
707 num_frames -= block_size;
708
709 /* do we need to message ? */
710 if (filter->num_frames >= filter->interval_frames) {
711 gst_level_post_message (filter);
712 }
713 }
714
715 gst_buffer_unmap (in, &map);
716
717 return GST_FLOW_OK;
718 }
719
720 static void
gst_level_post_message(GstLevel * filter)721 gst_level_post_message (GstLevel * filter)
722 {
723 guint i;
724 gint channels, rate, frames = filter->num_frames;
725 GstClockTime duration;
726
727 channels = GST_AUDIO_INFO_CHANNELS (&filter->info);
728 rate = GST_AUDIO_INFO_RATE (&filter->info);
729 duration = GST_FRAMES_TO_CLOCK_TIME (frames, rate);
730
731 if (filter->post_messages) {
732 GstMessage *m =
733 gst_level_message_new (filter, filter->message_ts, duration);
734
735 GST_LOG_OBJECT (filter,
736 "message: ts %" GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT
737 ", num_frames %d", GST_TIME_ARGS (filter->message_ts),
738 GST_TIME_ARGS (duration), frames);
739
740 for (i = 0; i < channels; ++i) {
741 gdouble RMS;
742 gdouble RMSdB, peakdB, decaydB;
743
744 RMS = sqrt (filter->CS[i] / frames);
745 GST_LOG_OBJECT (filter,
746 "message: channel %d, CS %f, RMS %f", i, filter->CS[i], RMS);
747 GST_LOG_OBJECT (filter,
748 "message: last_peak: %f, decay_peak: %f",
749 filter->last_peak[i], filter->decay_peak[i]);
750 /* RMS values are calculated in amplitude, so 20 * log 10 */
751 RMSdB = 20 * log10 (RMS + EPSILON);
752 /* peak values are square sums, ie. power, so 10 * log 10 */
753 peakdB = 10 * log10 (filter->last_peak[i] + EPSILON);
754 decaydB = 10 * log10 (filter->decay_peak[i] + EPSILON);
755
756 if (filter->decay_peak[i] < filter->last_peak[i]) {
757 /* this can happen in certain cases, for example when
758 * the last peak is between decay_peak and decay_peak_base */
759 GST_DEBUG_OBJECT (filter,
760 "message: decay peak dB %f smaller than last peak dB %f, copying",
761 decaydB, peakdB);
762 filter->decay_peak[i] = filter->last_peak[i];
763 }
764 GST_LOG_OBJECT (filter,
765 "message: RMS %f dB, peak %f dB, decay %f dB",
766 RMSdB, peakdB, decaydB);
767
768 gst_level_message_append_channel (m, RMSdB, peakdB, decaydB);
769
770 /* reset cumulative and normal peak */
771 filter->CS[i] = 0.0;
772 filter->last_peak[i] = 0.0;
773 }
774
775 gst_element_post_message (GST_ELEMENT (filter), m);
776
777 }
778 filter->num_frames -= frames;
779 filter->message_ts += duration;
780 }
781
782
783 static gboolean
gst_level_sink_event(GstBaseTransform * trans,GstEvent * event)784 gst_level_sink_event (GstBaseTransform * trans, GstEvent * event)
785 {
786 if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) {
787 GstLevel *filter = GST_LEVEL (trans);
788
789 gst_level_post_message (filter);
790 }
791
792 return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (trans, event);
793 }
794
795 static gboolean
plugin_init(GstPlugin * plugin)796 plugin_init (GstPlugin * plugin)
797 {
798 return gst_element_register (plugin, "level", GST_RANK_NONE, GST_TYPE_LEVEL);
799 }
800
801 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
802 GST_VERSION_MINOR,
803 level,
804 "Audio level plugin",
805 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
806