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>&quot;cutter&quot;</classname>.
27  * The message's structure contains two fields:
28  * <itemizedlist>
29  * <listitem>
30  *   <para>
31  *   #GstClockTime
32  *   <classname>&quot;timestamp&quot;</classname>:
33  *   the timestamp of the buffer that triggered the message.
34  *   </para>
35  * </listitem>
36  * <listitem>
37  *   <para>
38  *   gboolean
39  *   <classname>&quot;above&quot;</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