1 /* GStreamer
2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3 * Copyright (C) 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 * SECTION:element-cutter
23 * @title: cutter
24 *
25 * Analyses the audio signal for periods of silence. The start and end of
26 * silence is signalled by bus messages named
27 * `cutter`.
28 *
29 * The message's structure contains two fields:
30 *
31 * * #GstClockTime `timestamp`: the timestamp of the buffer that triggered the message.
32 * * gboolean `above`: %TRUE for begin of silence and %FALSE for end of silence.
33 *
34 * ## Example launch line
35 * |[
36 * gst-launch-1.0 -m filesrc location=foo.ogg ! decodebin ! audioconvert ! cutter ! autoaudiosink
37 * ]| Show cut messages.
38 *
39 */
40
41 #ifdef HAVE_CONFIG_H
42 #include "config.h"
43 #endif
44 #include <gst/gst.h>
45 #include <gst/audio/audio.h>
46 #include "gstcutter.h"
47 #include "math.h"
48
49 GST_DEBUG_CATEGORY_STATIC (cutter_debug);
50 #define GST_CAT_DEFAULT cutter_debug
51
52 #define CUTTER_DEFAULT_THRESHOLD_LEVEL 0.1
53 #define CUTTER_DEFAULT_THRESHOLD_LENGTH (500 * GST_MSECOND)
54 #define CUTTER_DEFAULT_PRE_LENGTH (200 * GST_MSECOND)
55
56 static GstStaticPadTemplate cutter_src_factory = GST_STATIC_PAD_TEMPLATE ("src",
57 GST_PAD_SRC,
58 GST_PAD_ALWAYS,
59 GST_STATIC_CAPS ("audio/x-raw, "
60 "format = (string) { S8," GST_AUDIO_NE (S16) " }, "
61 "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ], "
62 "layout = (string) interleaved")
63 );
64
65 static GstStaticPadTemplate cutter_sink_factory =
66 GST_STATIC_PAD_TEMPLATE ("sink",
67 GST_PAD_SINK,
68 GST_PAD_ALWAYS,
69 GST_STATIC_CAPS ("audio/x-raw, "
70 "format = (string) { S8," GST_AUDIO_NE (S16) " }, "
71 "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ], "
72 "layout = (string) interleaved")
73 );
74
75 enum
76 {
77 PROP_0,
78 PROP_THRESHOLD,
79 PROP_THRESHOLD_DB,
80 PROP_RUN_LENGTH,
81 PROP_PRE_LENGTH,
82 PROP_LEAKY
83 };
84
85 #define gst_cutter_parent_class parent_class
86 G_DEFINE_TYPE (GstCutter, gst_cutter, GST_TYPE_ELEMENT);
87 GST_ELEMENT_REGISTER_DEFINE (cutter, "cutter", GST_RANK_NONE, GST_TYPE_CUTTER);
88
89 static GstStateChangeReturn
90 gst_cutter_change_state (GstElement * element, GstStateChange transition);
91
92 static void gst_cutter_set_property (GObject * object, guint prop_id,
93 const GValue * value, GParamSpec * pspec);
94 static void gst_cutter_get_property (GObject * object, guint prop_id,
95 GValue * value, GParamSpec * pspec);
96
97 static gboolean gst_cutter_event (GstPad * pad, GstObject * parent,
98 GstEvent * event);
99 static GstFlowReturn gst_cutter_chain (GstPad * pad, GstObject * parent,
100 GstBuffer * buffer);
101
102 static void
gst_cutter_class_init(GstCutterClass * klass)103 gst_cutter_class_init (GstCutterClass * klass)
104 {
105 GObjectClass *gobject_class;
106 GstElementClass *element_class;
107
108 gobject_class = (GObjectClass *) klass;
109 element_class = (GstElementClass *) klass;
110
111 gobject_class->set_property = gst_cutter_set_property;
112 gobject_class->get_property = gst_cutter_get_property;
113
114 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_THRESHOLD,
115 g_param_spec_double ("threshold", "Threshold",
116 "Volume threshold before trigger",
117 -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
118 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
119 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_THRESHOLD_DB,
120 g_param_spec_double ("threshold-dB", "Threshold (dB)",
121 "Volume threshold before trigger (in dB)",
122 -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
123 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
124 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RUN_LENGTH,
125 g_param_spec_uint64 ("run-length", "Run length",
126 "Length of drop below threshold before cut_stop (in nanoseconds)",
127 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
128 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PRE_LENGTH,
129 g_param_spec_uint64 ("pre-length", "Pre-recording buffer length",
130 "Length of pre-recording buffer (in nanoseconds)",
131 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
132 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LEAKY,
133 g_param_spec_boolean ("leaky", "Leaky",
134 "do we leak buffers when below threshold ?",
135 FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
136
137 GST_DEBUG_CATEGORY_INIT (cutter_debug, "cutter", 0, "Audio cutting");
138
139 gst_element_class_add_static_pad_template (element_class,
140 &cutter_src_factory);
141 gst_element_class_add_static_pad_template (element_class,
142 &cutter_sink_factory);
143 gst_element_class_set_static_metadata (element_class, "Audio cutter",
144 "Filter/Editor/Audio", "Audio Cutter to split audio into non-silent bits",
145 "Thomas Vander Stichele <thomas at apestaart dot org>");
146 element_class->change_state = gst_cutter_change_state;
147 }
148
149 static void
gst_cutter_init(GstCutter * filter)150 gst_cutter_init (GstCutter * filter)
151 {
152 filter->sinkpad =
153 gst_pad_new_from_static_template (&cutter_sink_factory, "sink");
154 gst_pad_set_chain_function (filter->sinkpad, gst_cutter_chain);
155 gst_pad_set_event_function (filter->sinkpad, gst_cutter_event);
156 gst_pad_use_fixed_caps (filter->sinkpad);
157 gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
158
159 filter->srcpad =
160 gst_pad_new_from_static_template (&cutter_src_factory, "src");
161 gst_pad_use_fixed_caps (filter->srcpad);
162 gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
163
164 filter->threshold_level = CUTTER_DEFAULT_THRESHOLD_LEVEL;
165 filter->threshold_length = CUTTER_DEFAULT_THRESHOLD_LENGTH;
166 filter->silent_run_length = 0 * GST_SECOND;
167 filter->silent = TRUE;
168 filter->silent_prev = FALSE; /* previous value of silent */
169
170 filter->pre_length = CUTTER_DEFAULT_PRE_LENGTH;
171 filter->pre_run_length = 0 * GST_SECOND;
172 filter->pre_buffer = NULL;
173 filter->leaky = FALSE;
174 }
175
176 static GstMessage *
gst_cutter_message_new(GstCutter * c,gboolean above,GstClockTime timestamp)177 gst_cutter_message_new (GstCutter * c, gboolean above, GstClockTime timestamp)
178 {
179 GstStructure *s;
180
181 s = gst_structure_new ("cutter",
182 "above", G_TYPE_BOOLEAN, above,
183 "timestamp", GST_TYPE_CLOCK_TIME, timestamp, NULL);
184
185 return gst_message_new_element (GST_OBJECT (c), s);
186 }
187
188 /* Calculate the Normalized Cumulative Square over a buffer of the given type
189 * and over all channels combined */
190
191 #define DEFINE_CUTTER_CALCULATOR(TYPE, RESOLUTION) \
192 static void inline \
193 gst_cutter_calculate_##TYPE (TYPE * in, guint num, \
194 double *NCS) \
195 { \
196 register int j; \
197 double squaresum = 0.0; /* square sum of the integer samples */ \
198 register double square = 0.0; /* Square */ \
199 gdouble normalizer; /* divisor to get a [-1.0, 1.0] range */ \
200 \
201 *NCS = 0.0; /* Normalized Cumulative Square */ \
202 \
203 normalizer = (double) (1 << (RESOLUTION * 2)); \
204 \
205 for (j = 0; j < num; j++) \
206 { \
207 square = ((double) in[j]) * in[j]; \
208 squaresum += square; \
209 } \
210 \
211 \
212 *NCS = squaresum / normalizer; \
213 }
214
215 DEFINE_CUTTER_CALCULATOR (gint16, 15);
216 DEFINE_CUTTER_CALCULATOR (gint8, 7);
217
218 static gboolean
gst_cutter_setcaps(GstCutter * filter,GstCaps * caps)219 gst_cutter_setcaps (GstCutter * filter, GstCaps * caps)
220 {
221 GstAudioInfo info;
222
223 if (!gst_audio_info_from_caps (&info, caps))
224 return FALSE;
225
226 filter->info = info;
227
228 return gst_pad_set_caps (filter->srcpad, caps);
229 }
230
231 static GstStateChangeReturn
gst_cutter_change_state(GstElement * element,GstStateChange transition)232 gst_cutter_change_state (GstElement * element, GstStateChange transition)
233 {
234 GstStateChangeReturn ret;
235 GstCutter *filter = GST_CUTTER (element);
236
237 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
238
239 switch (transition) {
240 case GST_STATE_CHANGE_PAUSED_TO_READY:
241 g_list_free_full (filter->pre_buffer, (GDestroyNotify) gst_buffer_unref);
242 filter->pre_buffer = NULL;
243 break;
244 default:
245 break;
246 }
247 return ret;
248 }
249
250 static gboolean
gst_cutter_event(GstPad * pad,GstObject * parent,GstEvent * event)251 gst_cutter_event (GstPad * pad, GstObject * parent, GstEvent * event)
252 {
253 gboolean ret;
254 GstCutter *filter;
255
256 filter = GST_CUTTER (parent);
257
258 switch (GST_EVENT_TYPE (event)) {
259 case GST_EVENT_CAPS:
260 {
261 GstCaps *caps;
262
263 gst_event_parse_caps (event, &caps);
264 ret = gst_cutter_setcaps (filter, caps);
265 gst_event_unref (event);
266 break;
267 }
268 default:
269 ret = gst_pad_event_default (pad, parent, event);
270 break;
271 }
272 return ret;
273 }
274
275 static GstFlowReturn
gst_cutter_chain(GstPad * pad,GstObject * parent,GstBuffer * buf)276 gst_cutter_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
277 {
278 GstFlowReturn ret = GST_FLOW_OK;
279 GstCutter *filter;
280 GstMapInfo map;
281 gint16 *in_data;
282 gint bpf, rate;
283 gsize in_size;
284 guint num_samples;
285 gdouble NCS = 0.0; /* Normalized Cumulative Square of buffer */
286 gdouble RMS = 0.0; /* RMS of signal in buffer */
287 gdouble NMS = 0.0; /* Normalized Mean Square of buffer */
288 GstBuffer *prebuf; /* pointer to a prebuffer element */
289 GstClockTime duration;
290
291 filter = GST_CUTTER (parent);
292
293 if (GST_AUDIO_INFO_FORMAT (&filter->info) == GST_AUDIO_FORMAT_UNKNOWN)
294 goto not_negotiated;
295
296 bpf = GST_AUDIO_INFO_BPF (&filter->info);
297 rate = GST_AUDIO_INFO_RATE (&filter->info);
298
299 gst_buffer_map (buf, &map, GST_MAP_READ);
300 in_data = (gint16 *) map.data;
301 in_size = map.size;
302
303 GST_LOG_OBJECT (filter, "length of prerec buffer: %" GST_TIME_FORMAT,
304 GST_TIME_ARGS (filter->pre_run_length));
305
306 /* calculate mean square value on buffer */
307 switch (GST_AUDIO_INFO_FORMAT (&filter->info)) {
308 case GST_AUDIO_FORMAT_S16:
309 num_samples = in_size / 2;
310 gst_cutter_calculate_gint16 (in_data, num_samples, &NCS);
311 NMS = NCS / num_samples;
312 break;
313 case GST_AUDIO_FORMAT_S8:
314 num_samples = in_size;
315 gst_cutter_calculate_gint8 ((gint8 *) in_data, num_samples, &NCS);
316 NMS = NCS / num_samples;
317 break;
318 default:
319 /* this shouldn't happen */
320 g_warning ("no mean square function for format");
321 break;
322 }
323
324 gst_buffer_unmap (buf, &map);
325
326 filter->silent_prev = filter->silent;
327
328 duration = gst_util_uint64_scale (in_size / bpf, GST_SECOND, rate);
329
330 RMS = sqrt (NMS);
331 /* if RMS below threshold, add buffer length to silent run length count
332 * if not, reset
333 */
334 GST_LOG_OBJECT (filter, "buffer stats: NMS %f, RMS %f, audio length %f", NMS,
335 RMS, gst_guint64_to_gdouble (duration));
336
337 if (RMS < filter->threshold_level)
338 filter->silent_run_length += gst_guint64_to_gdouble (duration);
339 else {
340 filter->silent_run_length = 0 * GST_SECOND;
341 filter->silent = FALSE;
342 }
343
344 if (filter->silent_run_length > filter->threshold_length)
345 /* it has been silent long enough, flag it */
346 filter->silent = TRUE;
347
348 /* has the silent status changed ? if so, send right signal
349 * and, if from silent -> not silent, flush pre_record buffer
350 */
351 if (filter->silent != filter->silent_prev) {
352 if (filter->silent) {
353 GstMessage *m =
354 gst_cutter_message_new (filter, FALSE, GST_BUFFER_TIMESTAMP (buf));
355 GST_DEBUG_OBJECT (filter, "signaling CUT_STOP");
356 gst_element_post_message (GST_ELEMENT (filter), m);
357 } else {
358 gint count = 0;
359 GstMessage *m =
360 gst_cutter_message_new (filter, TRUE, GST_BUFFER_TIMESTAMP (buf));
361
362 GST_DEBUG_OBJECT (filter, "signaling CUT_START");
363 gst_element_post_message (GST_ELEMENT (filter), m);
364 /* first of all, flush current buffer */
365 GST_DEBUG_OBJECT (filter, "flushing buffer of length %" GST_TIME_FORMAT,
366 GST_TIME_ARGS (filter->pre_run_length));
367
368 while (filter->pre_buffer) {
369 prebuf = (g_list_first (filter->pre_buffer))->data;
370 filter->pre_buffer = g_list_remove (filter->pre_buffer, prebuf);
371 gst_pad_push (filter->srcpad, prebuf);
372 ++count;
373 }
374 GST_DEBUG_OBJECT (filter, "flushed %d buffers", count);
375 filter->pre_run_length = 0 * GST_SECOND;
376 }
377 }
378 /* now check if we have to send the new buffer to the internal buffer cache
379 * or to the srcpad */
380 if (filter->silent) {
381 filter->pre_buffer = g_list_append (filter->pre_buffer, buf);
382 filter->pre_run_length += gst_guint64_to_gdouble (duration);
383
384 while (filter->pre_run_length > filter->pre_length) {
385 GstClockTime pduration;
386 gsize psize;
387
388 prebuf = (g_list_first (filter->pre_buffer))->data;
389 g_assert (GST_IS_BUFFER (prebuf));
390
391 psize = gst_buffer_get_size (prebuf);
392 pduration = gst_util_uint64_scale (psize / bpf, GST_SECOND, rate);
393
394 filter->pre_buffer = g_list_remove (filter->pre_buffer, prebuf);
395 filter->pre_run_length -= gst_guint64_to_gdouble (pduration);
396
397 /* only pass buffers if we don't leak */
398 if (!filter->leaky)
399 ret = gst_pad_push (filter->srcpad, prebuf);
400 else
401 gst_buffer_unref (prebuf);
402 }
403 } else
404 ret = gst_pad_push (filter->srcpad, buf);
405
406 return ret;
407
408 /* ERRORS */
409 not_negotiated:
410 {
411 return GST_FLOW_NOT_NEGOTIATED;
412 }
413 }
414
415 static void
gst_cutter_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)416 gst_cutter_set_property (GObject * object, guint prop_id,
417 const GValue * value, GParamSpec * pspec)
418 {
419 GstCutter *filter;
420
421 g_return_if_fail (GST_IS_CUTTER (object));
422 filter = GST_CUTTER (object);
423
424 switch (prop_id) {
425 case PROP_THRESHOLD:
426 filter->threshold_level = g_value_get_double (value);
427 GST_DEBUG ("DEBUG: set threshold level to %f", filter->threshold_level);
428 break;
429 case PROP_THRESHOLD_DB:
430 /* set the level given in dB
431 * value in dB = 20 * log (value)
432 * values in dB < 0 result in values between 0 and 1
433 */
434 filter->threshold_level = pow (10, g_value_get_double (value) / 20);
435 GST_DEBUG_OBJECT (filter, "set threshold level to %f",
436 filter->threshold_level);
437 break;
438 case PROP_RUN_LENGTH:
439 /* set the minimum length of the silent run required */
440 filter->threshold_length =
441 gst_guint64_to_gdouble (g_value_get_uint64 (value));
442 break;
443 case PROP_PRE_LENGTH:
444 /* set the length of the pre-record block */
445 filter->pre_length = gst_guint64_to_gdouble (g_value_get_uint64 (value));
446 break;
447 case PROP_LEAKY:
448 /* set if the pre-record buffer is leaky or not */
449 filter->leaky = g_value_get_boolean (value);
450 break;
451 default:
452 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
453 break;
454 }
455 }
456
457 static void
gst_cutter_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)458 gst_cutter_get_property (GObject * object, guint prop_id,
459 GValue * value, GParamSpec * pspec)
460 {
461 GstCutter *filter;
462
463 g_return_if_fail (GST_IS_CUTTER (object));
464 filter = GST_CUTTER (object);
465
466 switch (prop_id) {
467 case PROP_RUN_LENGTH:
468 g_value_set_uint64 (value, filter->threshold_length);
469 break;
470 case PROP_THRESHOLD:
471 g_value_set_double (value, filter->threshold_level);
472 break;
473 case PROP_THRESHOLD_DB:
474 g_value_set_double (value, 20 * log (filter->threshold_level));
475 break;
476 case PROP_PRE_LENGTH:
477 g_value_set_uint64 (value, filter->pre_length);
478 break;
479 case PROP_LEAKY:
480 g_value_set_boolean (value, filter->leaky);
481 break;
482 default:
483 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
484 break;
485 }
486 }
487
488 static gboolean
plugin_init(GstPlugin * plugin)489 plugin_init (GstPlugin * plugin)
490 {
491 return GST_ELEMENT_REGISTER (cutter, plugin);
492 }
493
494 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
495 GST_VERSION_MINOR,
496 cutter,
497 "Audio Cutter to split audio into non-silent bits",
498 plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);
499