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