• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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