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