gst-indent
[platform/upstream/gst-plugins-good.git] / gst / cutter / gstcutter.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 #include <gst/gst.h>
24 #include <gst/audio/audio.h>
25 #include "gstcutter.h"
26 #include "math.h"
27
28 /* elementfactory information */
29 static GstElementDetails cutter_details = {
30   "Cutter",
31   "Filter/Editor/Audio",
32   "Audio Cutter to split audio into non-silent bits",
33   "Thomas <thomas@apestaart.org>",
34 };
35
36
37 /* Filter signals and args */
38 enum
39 {
40   /* FILL ME */
41   CUT_START,
42   CUT_STOP,
43   LAST_SIGNAL
44 };
45
46 enum
47 {
48   ARG_0,
49   ARG_THRESHOLD,
50   ARG_THRESHOLD_DB,
51   ARG_RUN_LENGTH,
52   ARG_PRE_LENGTH,
53   ARG_LEAKY
54 };
55
56 static GstStaticPadTemplate cutter_src_factory = GST_STATIC_PAD_TEMPLATE ("src",
57     GST_PAD_SRC,
58     GST_PAD_ALWAYS,
59     GST_STATIC_CAPS (GST_AUDIO_INT_PAD_TEMPLATE_CAPS "; "
60         GST_AUDIO_FLOAT_PAD_TEMPLATE_CAPS)
61     );
62
63 static GstStaticPadTemplate cutter_sink_factory =
64     GST_STATIC_PAD_TEMPLATE ("sink",
65     GST_PAD_SINK,
66     GST_PAD_ALWAYS,
67     GST_STATIC_CAPS (GST_AUDIO_INT_PAD_TEMPLATE_CAPS "; "
68         GST_AUDIO_FLOAT_PAD_TEMPLATE_CAPS)
69     );
70
71 static void gst_cutter_base_init (gpointer g_class);
72 static void gst_cutter_class_init (GstCutterClass * klass);
73 static void gst_cutter_init (GstCutter * filter);
74
75 static void gst_cutter_set_property (GObject * object, guint prop_id,
76     const GValue * value, GParamSpec * pspec);
77 static void gst_cutter_get_property (GObject * object, guint prop_id,
78     GValue * value, GParamSpec * pspec);
79
80 static void gst_cutter_chain (GstPad * pad, GstData * _data);
81 static double inline gst_cutter_16bit_ms (gint16 * data, guint numsamples);
82 static double inline gst_cutter_8bit_ms (gint8 * data, guint numsamples);
83
84 void gst_cutter_get_caps (GstPad * pad, GstCutter * filter);
85
86 static GstElementClass *parent_class = NULL;
87 static guint gst_cutter_signals[LAST_SIGNAL] = { 0 };
88
89
90 GType
91 gst_cutter_get_type (void)
92 {
93   static GType cutter_type = 0;
94
95   if (!cutter_type) {
96     static const GTypeInfo cutter_info = {
97       sizeof (GstCutterClass),
98       gst_cutter_base_init,
99       NULL,
100       (GClassInitFunc) gst_cutter_class_init, NULL, NULL,
101       sizeof (GstCutter), 0,
102       (GInstanceInitFunc) gst_cutter_init,
103     };
104     cutter_type = g_type_register_static (GST_TYPE_ELEMENT, "GstCutter",
105         &cutter_info, 0);
106   }
107   return cutter_type;
108 }
109
110 static void
111 gst_cutter_base_init (gpointer g_class)
112 {
113   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
114
115   gst_element_class_add_pad_template (element_class,
116       gst_static_pad_template_get (&cutter_src_factory));
117   gst_element_class_add_pad_template (element_class,
118       gst_static_pad_template_get (&cutter_sink_factory));
119   gst_element_class_set_details (element_class, &cutter_details);
120 }
121
122 static GstPadLinkReturn
123 gst_cutter_link (GstPad * pad, const GstCaps * caps)
124 {
125   GstCutter *filter;
126   GstPad *otherpad;
127
128   filter = GST_CUTTER (gst_pad_get_parent (pad));
129   g_return_val_if_fail (filter != NULL, GST_PAD_LINK_REFUSED);
130   g_return_val_if_fail (GST_IS_CUTTER (filter), GST_PAD_LINK_REFUSED);
131   otherpad = (pad == filter->srcpad ? filter->sinkpad : filter->srcpad);
132
133   return gst_pad_try_set_caps (otherpad, caps);
134 }
135
136 static void
137 gst_cutter_class_init (GstCutterClass * klass)
138 {
139   GObjectClass *gobject_class;
140   GstElementClass *gstelement_class;
141
142   gobject_class = (GObjectClass *) klass;
143   gstelement_class = (GstElementClass *) klass;
144
145   parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
146
147   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_THRESHOLD,
148       g_param_spec_double ("threshold", "Threshold",
149           "Volume threshold before trigger",
150           -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE));
151   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_THRESHOLD_DB,
152       g_param_spec_double ("threshold_dB", "Threshold (dB)",
153           "Volume threshold before trigger (in dB)",
154           -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE));
155   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_RUN_LENGTH,
156       g_param_spec_double ("runlength", "Runlength",
157           "Length of drop below threshold before cut_stop (seconds)",
158           0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE));
159   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PRE_LENGTH,
160       g_param_spec_double ("prelength", "prelength",
161           "Length of pre-recording buffer (seconds)",
162           0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE));
163   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_LEAKY,
164       g_param_spec_boolean ("leaky", "Leaky",
165           "do we leak buffers when below threshold ?",
166           FALSE, G_PARAM_READWRITE));
167   gst_cutter_signals[CUT_START] =
168       g_signal_new ("cut-start", G_TYPE_FROM_CLASS (klass),
169       G_SIGNAL_RUN_FIRST,
170       G_STRUCT_OFFSET (GstCutterClass, cut_start), NULL, NULL,
171       g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
172   gst_cutter_signals[CUT_STOP] =
173       g_signal_new ("cut-stop", G_TYPE_FROM_CLASS (klass),
174       G_SIGNAL_RUN_FIRST,
175       G_STRUCT_OFFSET (GstCutterClass, cut_stop), NULL, NULL,
176       g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
177
178
179   gobject_class->set_property = gst_cutter_set_property;
180   gobject_class->get_property = gst_cutter_get_property;
181 }
182
183 static void
184 gst_cutter_init (GstCutter * filter)
185 {
186   filter->sinkpad =
187       gst_pad_new_from_template (gst_static_pad_template_get
188       (&cutter_sink_factory), "sink");
189   filter->srcpad =
190       gst_pad_new_from_template (gst_static_pad_template_get
191       (&cutter_src_factory), "src");
192
193   filter->threshold_level = 0.1;
194   filter->threshold_length = 0.5;
195   filter->silent_run_length = 0.0;
196   filter->silent = TRUE;
197
198   filter->pre_length = 0.2;
199   filter->pre_run_length = 0.0;
200   filter->pre_buffer = NULL;
201   filter->leaky = FALSE;
202
203   gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
204   gst_pad_set_chain_function (filter->sinkpad, gst_cutter_chain);
205   gst_pad_set_link_function (filter->sinkpad, gst_cutter_link);
206
207   gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
208   /*gst_pad_set_link_function (filter->srcpad, gst_cutter_link); */
209 }
210
211 static void
212 gst_cutter_chain (GstPad * pad, GstData * _data)
213 {
214   GstBuffer *buf = GST_BUFFER (_data);
215   GstCutter *filter;
216   gint16 *in_data;
217   double RMS = 0.0;             /* RMS of signal in buffer */
218   double ms = 0.0;              /* mean square value of buffer */
219   static gboolean silent_prev = FALSE;  /* previous value of silent */
220   GstBuffer *prebuf;            /* pointer to a prebuffer element */
221
222   g_return_if_fail (pad != NULL);
223   g_return_if_fail (GST_IS_PAD (pad));
224   g_return_if_fail (buf != NULL);
225
226   filter = GST_CUTTER (GST_OBJECT_PARENT (pad));
227   g_return_if_fail (filter != NULL);
228   g_return_if_fail (GST_IS_CUTTER (filter));
229
230   if (gst_audio_is_buffer_framed (pad, buf) == FALSE)
231     g_warning ("audio buffer is not framed !\n");
232
233   if (!filter->have_caps)
234     gst_cutter_get_caps (pad, filter);
235
236   in_data = (gint16 *) GST_BUFFER_DATA (buf);
237   GST_DEBUG ("length of prerec buffer: %.3f sec", filter->pre_run_length);
238
239   /* calculate mean square value on buffer */
240   switch (filter->width) {
241     case 16:
242       ms = gst_cutter_16bit_ms (in_data, GST_BUFFER_SIZE (buf) / 2);
243       break;
244     case 8:
245       ms = gst_cutter_8bit_ms ((gint8 *) in_data, GST_BUFFER_SIZE (buf));
246       break;
247     default:
248       /* this shouldn't happen */
249       g_print ("WARNING: no mean square function for width %d\n",
250           filter->width);
251       break;
252   }
253
254   silent_prev = filter->silent;
255
256   RMS = sqrt (ms) / (double) filter->max_sample;
257   /* if RMS below threshold, add buffer length to silent run length count
258    * if not, reset
259    */
260   GST_DEBUG ("buffer stats: ms %f, RMS %f, audio length %f",
261       ms, RMS, gst_audio_length (filter->srcpad, buf));
262   if (RMS < filter->threshold_level)
263     filter->silent_run_length += gst_audio_length (filter->srcpad, buf);
264   else {
265     filter->silent_run_length = 0.0;
266     filter->silent = FALSE;
267   }
268
269   if (filter->silent_run_length > filter->threshold_length)
270     /* it has been silent long enough, flag it */
271     filter->silent = TRUE;
272
273   /* has the silent status changed ? if so, send right signal
274    * and, if from silent -> not silent, flush pre_record buffer
275    */
276   if (filter->silent != silent_prev) {
277     if (filter->silent) {
278 /*      g_print ("DEBUG: cutter: cut to here, turning off out\n"); */
279       g_signal_emit (G_OBJECT (filter), gst_cutter_signals[CUT_STOP], 0);
280     } else {
281       gint count = 0;
282
283 /*      g_print ("DEBUG: cutter: start from here, turning on out\n"); */
284       /* first of all, flush current buffer */
285       g_signal_emit (G_OBJECT (filter), gst_cutter_signals[CUT_START], 0);
286       GST_DEBUG ("flushing buffer of length %.3f", filter->pre_run_length);
287       while (filter->pre_buffer) {
288         prebuf = (g_list_first (filter->pre_buffer))->data;
289         filter->pre_buffer = g_list_remove (filter->pre_buffer, prebuf);
290         gst_pad_push (filter->srcpad, GST_DATA (prebuf));
291         ++count;
292       }
293       GST_DEBUG ("flushed %d buffers", count);
294       filter->pre_run_length = 0.0;
295     }
296   }
297   /* now check if we have to send the new buffer to the internal buffer cache
298    * or to the srcpad */
299   if (filter->silent) {
300     /* we ref it before putting it in the pre_buffer */
301     /* FIXME: we shouldn't probably do this, because the buffer
302      * arrives reffed already; the plugin should just push it
303      * or unref it to make it disappear */
304     /*
305        gst_buffer_ref (buf);
306      */
307     filter->pre_buffer = g_list_append (filter->pre_buffer, buf);
308     filter->pre_run_length += gst_audio_length (filter->srcpad, buf);
309     while (filter->pre_run_length > filter->pre_length) {
310       prebuf = (g_list_first (filter->pre_buffer))->data;
311       g_assert (GST_IS_BUFFER (prebuf));
312       filter->pre_buffer = g_list_remove (filter->pre_buffer, prebuf);
313       filter->pre_run_length -= gst_audio_length (filter->srcpad, prebuf);
314       /* only pass buffers if we don't leak */
315       if (!filter->leaky)
316         gst_pad_push (filter->srcpad, GST_DATA (prebuf));
317       /* we unref it after getting it out of the pre_buffer */
318       gst_buffer_unref (prebuf);
319     }
320   } else
321     gst_pad_push (filter->srcpad, GST_DATA (buf));
322 }
323
324 static double inline
325 gst_cutter_16bit_ms (gint16 * data, guint num_samples)
326 #include "filter.func"
327      static double inline gst_cutter_8bit_ms (gint8 * data, guint num_samples)
328 #include "filter.func"
329      static void
330          gst_cutter_set_property (GObject * object, guint prop_id,
331     const GValue * value, GParamSpec * pspec)
332 {
333   GstCutter *filter;
334
335   g_return_if_fail (GST_IS_CUTTER (object));
336   filter = GST_CUTTER (object);
337
338   switch (prop_id) {
339     case ARG_THRESHOLD:
340       /* set the level */
341       filter->threshold_level = g_value_get_double (value);
342       GST_DEBUG ("DEBUG: set threshold level to %f", filter->threshold_level);
343       break;
344     case ARG_THRESHOLD_DB:
345       /* set the level given in dB
346        * value in dB = 20 * log (value)
347        * values in dB < 0 result in values between 0 and 1
348        */
349       filter->threshold_level = pow (10, g_value_get_double (value) / 20);
350       GST_DEBUG ("DEBUG: set threshold level to %f", filter->threshold_level);
351       break;
352     case ARG_RUN_LENGTH:
353       /* set the minimum length of the silent run required */
354       filter->threshold_length = g_value_get_double (value);
355       break;
356     case ARG_PRE_LENGTH:
357       /* set the length of the pre-record block */
358       filter->pre_length = g_value_get_double (value);
359       break;
360     case ARG_LEAKY:
361       /* set if the pre-record buffer is leaky or not */
362       filter->leaky = g_value_get_boolean (value);
363       break;
364     default:
365       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
366       break;
367   }
368 }
369
370 static void
371 gst_cutter_get_property (GObject * object, guint prop_id,
372     GValue * value, GParamSpec * pspec)
373 {
374   GstCutter *filter;
375
376   g_return_if_fail (GST_IS_CUTTER (object));
377   filter = GST_CUTTER (object);
378
379   switch (prop_id) {
380     case ARG_RUN_LENGTH:
381       g_value_set_double (value, filter->threshold_length);
382       break;
383     case ARG_THRESHOLD:
384       g_value_set_double (value, filter->threshold_level);
385       break;
386     case ARG_THRESHOLD_DB:
387       g_value_set_double (value, 20 * log (filter->threshold_level));
388       break;
389     case ARG_PRE_LENGTH:
390       g_value_set_double (value, filter->pre_length);
391       break;
392     case ARG_LEAKY:
393       g_value_set_boolean (value, filter->leaky);
394       break;
395     default:
396       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
397       break;
398   }
399 }
400
401 static gboolean
402 plugin_init (GstPlugin * plugin)
403 {
404   /* load audio support library */
405   if (!gst_library_load ("gstaudio"))
406     return FALSE;
407
408   if (!gst_element_register (plugin, "cutter", GST_RANK_NONE, GST_TYPE_CUTTER))
409     return FALSE;
410
411   return TRUE;
412 }
413
414 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
415     GST_VERSION_MINOR,
416     "cutter",
417     "Audio Cutter to split audio into non-silent bits",
418     plugin_init, VERSION, "LGPL", GST_PACKAGE, GST_ORIGIN)
419
420      void gst_cutter_get_caps (GstPad * pad, GstCutter * filter)
421 {
422   const GstCaps *caps = NULL;
423   GstStructure *structure;
424
425   caps = GST_PAD_CAPS (pad);
426   /* FIXME : Please change this to a better warning method ! */
427   g_assert (caps != NULL);
428   if (caps == NULL)
429     printf ("WARNING: get_caps: Could not get caps of pad !\n");
430   structure = gst_caps_get_structure (caps, 0);
431   gst_structure_get_int (structure, "width", &filter->width);
432   filter->max_sample = gst_audio_highest_sample_value (pad);
433   filter->have_caps = TRUE;
434 }