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>
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.
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.
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., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
22 * SECTION:element-cutter
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:
32 * <classname>"timestamp"</classname>:
33 * the timestamp of the buffer that triggered the message.
39 * <classname>"above"</classname>:
40 * %TRUE for begin of silence and %FALSE for end of silence.
46 * <title>Example launch line</title>
48 * gst-launch -m filesrc location=foo.ogg ! decodebin ! audioconvert ! cutter ! autoaudiosink
49 * ]| Show cut messages.
57 #include <gst/audio/audio.h>
58 #include "gstcutter.h"
61 GST_DEBUG_CATEGORY_STATIC (cutter_debug);
62 #define GST_CAT_DEFAULT cutter_debug
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)
68 static GstStaticPadTemplate cutter_src_factory = GST_STATIC_PAD_TEMPLATE ("src",
71 GST_STATIC_CAPS ("audio/x-raw-int, "
72 "rate = (int) [ 1, MAX ], "
73 "channels = (int) [ 1, MAX ], "
74 "endianness = (int) BYTE_ORDER, "
75 "width = (int) { 8, 16 }, "
76 "depth = (int) { 8, 16 }, " "signed = (boolean) true")
79 static GstStaticPadTemplate cutter_sink_factory =
80 GST_STATIC_PAD_TEMPLATE ("sink",
83 GST_STATIC_CAPS ("audio/x-raw-int, "
84 "rate = (int) [ 1, MAX ], "
85 "channels = (int) [ 1, MAX ], "
86 "endianness = (int) BYTE_ORDER, "
87 "width = (int) { 8, 16 }, "
88 "depth = (int) { 8, 16 }, " "signed = (boolean) true")
101 GST_BOILERPLATE (GstCutter, gst_cutter, GstElement, GST_TYPE_ELEMENT);
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);
108 static GstFlowReturn gst_cutter_chain (GstPad * pad, GstBuffer * buffer);
110 static gboolean gst_cutter_get_caps (GstPad * pad, GstCutter * filter);
113 gst_cutter_base_init (gpointer g_class)
115 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
117 gst_element_class_add_static_pad_template (element_class,
118 &cutter_src_factory);
119 gst_element_class_add_static_pad_template (element_class,
120 &cutter_sink_factory);
121 gst_element_class_set_details_simple (element_class, "Audio cutter",
122 "Filter/Editor/Audio",
123 "Audio Cutter to split audio into non-silent bits",
124 "Thomas Vander Stichele <thomas at apestaart dot org>");
128 gst_cutter_class_init (GstCutterClass * klass)
130 GObjectClass *gobject_class;
132 gobject_class = (GObjectClass *) klass;
134 gobject_class->set_property = gst_cutter_set_property;
135 gobject_class->get_property = gst_cutter_get_property;
137 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_THRESHOLD,
138 g_param_spec_double ("threshold", "Threshold",
139 "Volume threshold before trigger",
140 -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
141 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
142 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_THRESHOLD_DB,
143 g_param_spec_double ("threshold-dB", "Threshold (dB)",
144 "Volume threshold before trigger (in dB)",
145 -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
146 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
147 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RUN_LENGTH,
148 g_param_spec_uint64 ("run-length", "Run length",
149 "Length of drop below threshold before cut_stop (in nanoseconds)",
150 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
151 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PRE_LENGTH,
152 g_param_spec_uint64 ("pre-length", "Pre-recording buffer length",
153 "Length of pre-recording buffer (in nanoseconds)",
154 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
155 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LEAKY,
156 g_param_spec_boolean ("leaky", "Leaky",
157 "do we leak buffers when below threshold ?",
158 FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
160 GST_DEBUG_CATEGORY_INIT (cutter_debug, "cutter", 0, "Audio cutting");
164 gst_cutter_init (GstCutter * filter, GstCutterClass * g_class)
167 gst_pad_new_from_static_template (&cutter_sink_factory, "sink");
169 gst_pad_new_from_static_template (&cutter_src_factory, "src");
171 filter->threshold_level = CUTTER_DEFAULT_THRESHOLD_LEVEL;
172 filter->threshold_length = CUTTER_DEFAULT_THRESHOLD_LENGTH;
173 filter->silent_run_length = 0 * GST_SECOND;
174 filter->silent = TRUE;
175 filter->silent_prev = FALSE; /* previous value of silent */
177 filter->pre_length = CUTTER_DEFAULT_PRE_LENGTH;
178 filter->pre_run_length = 0 * GST_SECOND;
179 filter->pre_buffer = NULL;
180 filter->leaky = FALSE;
182 gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
183 gst_pad_set_chain_function (filter->sinkpad, gst_cutter_chain);
184 gst_pad_use_fixed_caps (filter->sinkpad);
186 gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
187 gst_pad_use_fixed_caps (filter->srcpad);
191 gst_cutter_message_new (GstCutter * c, gboolean above, GstClockTime timestamp)
196 g_value_init (&v, GST_TYPE_LIST);
198 s = gst_structure_new ("cutter",
199 "above", G_TYPE_BOOLEAN, above,
200 "timestamp", GST_TYPE_CLOCK_TIME, timestamp, NULL);
202 return gst_message_new_element (GST_OBJECT (c), s);
205 /* Calculate the Normalized Cumulative Square over a buffer of the given type
206 * and over all channels combined */
208 #define DEFINE_CUTTER_CALCULATOR(TYPE, RESOLUTION) \
210 gst_cutter_calculate_##TYPE (TYPE * in, guint num, \
214 double squaresum = 0.0; /* square sum of the integer samples */ \
215 register double square = 0.0; /* Square */ \
216 gdouble normalizer; /* divisor to get a [-1.0, 1.0] range */ \
218 *NCS = 0.0; /* Normalized Cumulative Square */ \
220 normalizer = (double) (1 << (RESOLUTION * 2)); \
222 for (j = 0; j < num; j++) \
224 square = ((double) in[j]) * in[j]; \
225 squaresum += square; \
229 *NCS = squaresum / normalizer; \
232 DEFINE_CUTTER_CALCULATOR (gint16, 15);
233 DEFINE_CUTTER_CALCULATOR (gint8, 7);
237 gst_cutter_chain (GstPad * pad, GstBuffer * buf)
242 gdouble NCS = 0.0; /* Normalized Cumulative Square of buffer */
243 gdouble RMS = 0.0; /* RMS of signal in buffer */
244 gdouble NMS = 0.0; /* Normalized Mean Square of buffer */
245 GstBuffer *prebuf; /* pointer to a prebuffer element */
247 g_return_val_if_fail (pad != NULL, GST_FLOW_ERROR);
248 g_return_val_if_fail (GST_IS_PAD (pad), GST_FLOW_ERROR);
249 g_return_val_if_fail (buf != NULL, GST_FLOW_ERROR);
251 filter = GST_CUTTER (GST_OBJECT_PARENT (pad));
252 g_return_val_if_fail (filter != NULL, GST_FLOW_ERROR);
253 g_return_val_if_fail (GST_IS_CUTTER (filter), GST_FLOW_ERROR);
255 if (!filter->have_caps) {
256 if (!(gst_cutter_get_caps (pad, filter)))
257 return GST_FLOW_NOT_NEGOTIATED;
260 in_data = (gint16 *) GST_BUFFER_DATA (buf);
261 GST_LOG_OBJECT (filter, "length of prerec buffer: %" GST_TIME_FORMAT,
262 GST_TIME_ARGS (filter->pre_run_length));
264 /* calculate mean square value on buffer */
265 switch (filter->width) {
267 num_samples = GST_BUFFER_SIZE (buf) / 2;
268 gst_cutter_calculate_gint16 (in_data, num_samples, &NCS);
269 NMS = NCS / num_samples;
272 num_samples = GST_BUFFER_SIZE (buf);
273 gst_cutter_calculate_gint8 ((gint8 *) in_data, num_samples, &NCS);
274 NMS = NCS / num_samples;
277 /* this shouldn't happen */
278 g_warning ("no mean square function for width %d\n", filter->width);
282 filter->silent_prev = filter->silent;
285 /* if RMS below threshold, add buffer length to silent run length count
288 GST_LOG_OBJECT (filter, "buffer stats: NMS %f, RMS %f, audio length %f", NMS,
290 gst_guint64_to_gdouble (gst_audio_duration_from_pad_buffer
291 (filter->sinkpad, buf)));
292 if (RMS < filter->threshold_level)
293 filter->silent_run_length +=
294 gst_guint64_to_gdouble (gst_audio_duration_from_pad_buffer
295 (filter->sinkpad, buf));
297 filter->silent_run_length = 0 * GST_SECOND;
298 filter->silent = FALSE;
301 if (filter->silent_run_length > filter->threshold_length)
302 /* it has been silent long enough, flag it */
303 filter->silent = TRUE;
305 /* has the silent status changed ? if so, send right signal
306 * and, if from silent -> not silent, flush pre_record buffer
308 if (filter->silent != filter->silent_prev) {
309 if (filter->silent) {
311 gst_cutter_message_new (filter, FALSE, GST_BUFFER_TIMESTAMP (buf));
312 GST_DEBUG_OBJECT (filter, "signaling CUT_STOP");
313 gst_element_post_message (GST_ELEMENT (filter), m);
317 gst_cutter_message_new (filter, TRUE, GST_BUFFER_TIMESTAMP (buf));
319 GST_DEBUG_OBJECT (filter, "signaling CUT_START");
320 gst_element_post_message (GST_ELEMENT (filter), m);
321 /* first of all, flush current buffer */
322 GST_DEBUG_OBJECT (filter, "flushing buffer of length %" GST_TIME_FORMAT,
323 GST_TIME_ARGS (filter->pre_run_length));
324 while (filter->pre_buffer) {
325 prebuf = (g_list_first (filter->pre_buffer))->data;
326 filter->pre_buffer = g_list_remove (filter->pre_buffer, prebuf);
327 gst_pad_push (filter->srcpad, prebuf);
330 GST_DEBUG_OBJECT (filter, "flushed %d buffers", count);
331 filter->pre_run_length = 0 * GST_SECOND;
334 /* now check if we have to send the new buffer to the internal buffer cache
335 * or to the srcpad */
336 if (filter->silent) {
337 filter->pre_buffer = g_list_append (filter->pre_buffer, buf);
338 filter->pre_run_length +=
339 gst_guint64_to_gdouble (gst_audio_duration_from_pad_buffer
340 (filter->sinkpad, buf));
341 while (filter->pre_run_length > filter->pre_length) {
342 prebuf = (g_list_first (filter->pre_buffer))->data;
343 g_assert (GST_IS_BUFFER (prebuf));
344 filter->pre_buffer = g_list_remove (filter->pre_buffer, prebuf);
345 filter->pre_run_length -=
346 gst_guint64_to_gdouble (gst_audio_duration_from_pad_buffer
347 (filter->sinkpad, prebuf));
348 /* only pass buffers if we don't leak */
350 gst_pad_push (filter->srcpad, prebuf);
352 gst_buffer_unref (prebuf);
355 gst_pad_push (filter->srcpad, buf);
362 gst_cutter_get_caps (GstPad * pad, GstCutter * filter)
365 GstStructure *structure;
367 caps = gst_pad_get_caps (pad);
369 GST_INFO ("no caps on pad %s:%s", GST_DEBUG_PAD_NAME (pad));
372 structure = gst_caps_get_structure (caps, 0);
373 gst_structure_get_int (structure, "width", &filter->width);
374 filter->max_sample = 1 << (filter->width - 1); /* signed */
375 filter->have_caps = TRUE;
377 gst_caps_unref (caps);
383 gst_cutter_set_property (GObject * object, guint prop_id,
384 const GValue * value, GParamSpec * pspec)
388 g_return_if_fail (GST_IS_CUTTER (object));
389 filter = GST_CUTTER (object);
393 filter->threshold_level = g_value_get_double (value);
394 GST_DEBUG ("DEBUG: set threshold level to %f", filter->threshold_level);
396 case PROP_THRESHOLD_DB:
397 /* set the level given in dB
398 * value in dB = 20 * log (value)
399 * values in dB < 0 result in values between 0 and 1
401 filter->threshold_level = pow (10, g_value_get_double (value) / 20);
402 GST_DEBUG_OBJECT (filter, "set threshold level to %f",
403 filter->threshold_level);
405 case PROP_RUN_LENGTH:
406 /* set the minimum length of the silent run required */
407 filter->threshold_length =
408 gst_guint64_to_gdouble (g_value_get_uint64 (value));
410 case PROP_PRE_LENGTH:
411 /* set the length of the pre-record block */
412 filter->pre_length = gst_guint64_to_gdouble (g_value_get_uint64 (value));
415 /* set if the pre-record buffer is leaky or not */
416 filter->leaky = g_value_get_boolean (value);
419 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
425 gst_cutter_get_property (GObject * object, guint prop_id,
426 GValue * value, GParamSpec * pspec)
430 g_return_if_fail (GST_IS_CUTTER (object));
431 filter = GST_CUTTER (object);
434 case PROP_RUN_LENGTH:
435 g_value_set_uint64 (value, filter->threshold_length);
438 g_value_set_double (value, filter->threshold_level);
440 case PROP_THRESHOLD_DB:
441 g_value_set_double (value, 20 * log (filter->threshold_level));
443 case PROP_PRE_LENGTH:
444 g_value_set_uint64 (value, filter->pre_length);
447 g_value_set_boolean (value, filter->leaky);
450 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
456 plugin_init (GstPlugin * plugin)
458 if (!gst_element_register (plugin, "cutter", GST_RANK_NONE, GST_TYPE_CUTTER))
464 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
467 "Audio Cutter to split audio into non-silent bits",
468 plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);