7000346b6bc9226e71c0a5a7db7c500d1b7cbe97
[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   /* FILL ME */
40   CUT_START,
41   CUT_STOP,
42   LAST_SIGNAL
43 };
44
45 enum {
46   ARG_0,
47   ARG_THRESHOLD,
48   ARG_THRESHOLD_DB,
49   ARG_RUN_LENGTH,
50   ARG_PRE_LENGTH,
51   ARG_LEAKY
52 };
53
54 GST_PAD_TEMPLATE_FACTORY (cutter_src_factory,
55   "src",
56   GST_PAD_SRC,
57   GST_PAD_ALWAYS,
58   gst_caps_new (
59     "cutter_src_int",
60     "audio/x-raw-int",
61       GST_AUDIO_INT_PAD_TEMPLATE_PROPS
62   ),
63   gst_caps_new (
64     "cutter_src_float",
65     "audio/x-raw-float",
66       GST_AUDIO_INT_PAD_TEMPLATE_PROPS
67   )
68 );
69
70 GST_PAD_TEMPLATE_FACTORY (cutter_sink_factory,
71   "sink",
72   GST_PAD_SINK,
73   GST_PAD_ALWAYS,
74   gst_caps_new (
75     "cutter_sink_int",
76     "audio/x-raw-int",
77       GST_AUDIO_INT_PAD_TEMPLATE_PROPS
78   ),
79   gst_caps_new (
80     "cutter_sink_float",
81     "audio/x-raw-float",
82       GST_AUDIO_INT_PAD_TEMPLATE_PROPS
83   )
84 );
85
86 static void     gst_cutter_base_init    (gpointer g_class);
87 static void     gst_cutter_class_init   (GstCutterClass *klass);
88 static void     gst_cutter_init         (GstCutter *filter);
89
90 static void     gst_cutter_set_property (GObject *object, guint prop_id,
91                                          const GValue *value,
92                                          GParamSpec *pspec);
93 static void     gst_cutter_get_property (GObject *object, guint prop_id,
94                                          GValue *value, GParamSpec *pspec);
95
96 static void     gst_cutter_chain        (GstPad *pad, GstData *_data);
97 static double
98 inline          gst_cutter_16bit_ms     (gint16* data, guint numsamples);
99 static double
100 inline          gst_cutter_8bit_ms      (gint8* data, guint numsamples);
101
102 void            gst_cutter_get_caps     (GstPad *pad, GstCutter* filter);
103
104 static GstElementClass *parent_class = NULL;
105 static guint gst_cutter_signals[LAST_SIGNAL] = { 0 };
106
107
108 GType
109 gst_cutter_get_type (void) {
110   static GType cutter_type = 0;
111
112   if (!cutter_type) {
113     static const GTypeInfo cutter_info = {
114       sizeof (GstCutterClass),
115       gst_cutter_base_init,
116       NULL,
117       (GClassInitFunc) gst_cutter_class_init, NULL, NULL,
118       sizeof (GstCutter), 0,
119       (GInstanceInitFunc) gst_cutter_init,
120     };
121     cutter_type = g_type_register_static (GST_TYPE_ELEMENT, "GstCutter",
122                                           &cutter_info, 0);
123   }
124   return cutter_type;
125 }
126
127 static void
128 gst_cutter_base_init (gpointer g_class)
129 {
130   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
131
132   gst_element_class_add_pad_template (element_class, GST_PAD_TEMPLATE_GET (cutter_src_factory));
133   gst_element_class_add_pad_template (element_class, GST_PAD_TEMPLATE_GET (cutter_sink_factory));
134   gst_element_class_set_details (element_class, &cutter_details);
135 }
136
137 static GstPadLinkReturn
138 gst_cutter_link (GstPad *pad, GstCaps *caps)
139 {
140   GstCutter *filter;
141   GstPad *otherpad;
142
143   filter = GST_CUTTER (gst_pad_get_parent (pad));
144   g_return_val_if_fail (filter != NULL, GST_PAD_LINK_REFUSED);
145   g_return_val_if_fail (GST_IS_CUTTER (filter), GST_PAD_LINK_REFUSED);
146   otherpad = (pad == filter->srcpad ? filter->sinkpad : filter->srcpad);
147
148   if (GST_CAPS_IS_FIXED (caps))
149     return gst_pad_try_set_caps (otherpad, gst_caps_ref (caps));
150   return GST_PAD_LINK_DELAYED;
151 }
152
153 static void
154 gst_cutter_class_init (GstCutterClass *klass)
155 {
156   GObjectClass *gobject_class;
157   GstElementClass *gstelement_class;
158
159   gobject_class = (GObjectClass*) klass;
160   gstelement_class = (GstElementClass*) klass;
161
162   parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
163
164   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_THRESHOLD,
165     g_param_spec_double ("threshold", "Threshold",
166                          "Volume threshold before trigger",
167                          -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE));
168   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_THRESHOLD_DB,
169     g_param_spec_double ("threshold_dB", "Threshold (dB)",
170                          "Volume threshold before trigger (in dB)",
171                          -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE));
172   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_RUN_LENGTH,
173     g_param_spec_double ("runlength", "Runlength",
174                          "Length of drop below threshold before cut_stop (seconds)",
175                         0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE));
176   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_PRE_LENGTH,
177     g_param_spec_double ("prelength", "prelength",
178                          "Length of pre-recording buffer (seconds)",
179                         0.0, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE));
180   g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_LEAKY,
181     g_param_spec_boolean ("leaky", "Leaky",
182                           "do we leak buffers when below threshold ?",
183                           FALSE, G_PARAM_READWRITE));
184   gst_cutter_signals[CUT_START] =
185         g_signal_new ("cut_start", G_TYPE_FROM_CLASS (klass),
186                       G_SIGNAL_RUN_FIRST,
187                       G_STRUCT_OFFSET (GstCutterClass, cut_start), NULL, NULL,
188                       g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
189   gst_cutter_signals[CUT_STOP] =
190         g_signal_new ("cut_stop", G_TYPE_FROM_CLASS (klass),
191                       G_SIGNAL_RUN_FIRST,
192                       G_STRUCT_OFFSET (GstCutterClass, cut_stop), NULL, NULL,
193                       g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
194
195
196   gobject_class->set_property = gst_cutter_set_property;
197   gobject_class->get_property = gst_cutter_get_property;
198 }
199
200 static void
201 gst_cutter_init (GstCutter *filter)
202 {
203   filter->sinkpad = gst_pad_new_from_template (cutter_sink_factory (),"sink");
204   filter->srcpad = gst_pad_new_from_template (cutter_src_factory (),"src");
205
206   filter->threshold_level = 0.1;
207   filter->threshold_length = 0.5;
208   filter->silent_run_length = 0.0;
209   filter->silent = TRUE;
210
211   filter->pre_length = 0.2;
212   filter->pre_run_length = 0.0;
213   filter->pre_buffer = NULL;
214   filter->leaky = FALSE;
215
216   gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);
217   gst_pad_set_chain_function (filter->sinkpad, gst_cutter_chain);
218   gst_pad_set_link_function (filter->sinkpad, gst_cutter_link);
219   filter->srcpad = gst_pad_new ("src", GST_PAD_SRC);
220   gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);
221   /*gst_pad_set_link_function (filter->srcpad, gst_cutter_link);*/
222 }
223
224 static void
225 gst_cutter_chain (GstPad *pad, GstData *_data)
226 {
227   GstBuffer *buf = GST_BUFFER (_data);
228   GstCutter *filter;
229   gint16 *in_data;
230   double RMS = 0.0;                     /* RMS of signal in buffer */
231   double ms = 0.0;                      /* mean square value of buffer */
232   static gboolean silent_prev = FALSE;  /* previous value of silent */
233   GstBuffer *prebuf;                    /* pointer to a prebuffer element */
234
235   g_return_if_fail (pad != NULL);
236   g_return_if_fail (GST_IS_PAD (pad));
237   g_return_if_fail (buf != NULL);
238
239   filter = GST_CUTTER (GST_OBJECT_PARENT (pad));
240   g_return_if_fail (filter != NULL);
241   g_return_if_fail (GST_IS_CUTTER (filter));
242
243   if (gst_audio_is_buffer_framed (pad, buf) == FALSE)
244     g_warning ("audio buffer is not framed !\n");
245
246   if (!filter->have_caps) gst_cutter_get_caps (pad, filter);
247
248   in_data = (gint16 *) GST_BUFFER_DATA (buf);
249   GST_DEBUG (
250              "length of prerec buffer: %.3f sec",
251              filter->pre_run_length);
252
253   /* calculate mean square value on buffer */
254   switch (filter->width)
255   {
256     case 16:
257       ms = gst_cutter_16bit_ms (in_data, GST_BUFFER_SIZE (buf) / 2);
258       break;
259     case 8:
260       ms = gst_cutter_8bit_ms ((gint8 *) in_data, GST_BUFFER_SIZE (buf));
261       break;
262     default:
263       /* this shouldn't happen */
264       g_print ("WARNING: no mean square function for width %d\n",
265                 filter->width);
266       break;
267   }
268
269   silent_prev = filter->silent;
270
271   RMS = sqrt (ms) / (double) filter->max_sample;
272   /* if RMS below threshold, add buffer length to silent run length count
273    * if not, reset
274    */
275   GST_DEBUG (
276              "buffer stats: ms %f, RMS %f, audio length %f",
277              ms, RMS, gst_audio_length (filter->srcpad, buf));
278   if (RMS < filter->threshold_level)
279     filter->silent_run_length += gst_audio_length (filter->srcpad, buf);
280   else
281   {
282     filter->silent_run_length = 0.0;
283     filter->silent = FALSE;
284   }
285
286   if (filter->silent_run_length > filter->threshold_length)
287     /* it has been silent long enough, flag it */
288     filter->silent = TRUE;
289
290   /* has the silent status changed ? if so, send right signal
291    * and, if from silent -> not silent, flush pre_record buffer
292    */
293   if (filter->silent != silent_prev)
294   {
295     if (filter->silent)
296     {
297 /*      g_print ("DEBUG: cutter: cut to here, turning off out\n"); */
298       g_signal_emit (G_OBJECT (filter), gst_cutter_signals[CUT_STOP], 0);
299     }
300     else
301     {
302       gint count = 0;
303 /*      g_print ("DEBUG: cutter: start from here, turning on out\n"); */
304       /* first of all, flush current buffer */
305       g_signal_emit (G_OBJECT (filter), gst_cutter_signals[CUT_START], 0);
306       GST_DEBUG (
307                  "flushing buffer of length %.3f",
308                  filter->pre_run_length);
309       while (filter->pre_buffer)
310       {
311         prebuf = (g_list_first (filter->pre_buffer))->data;
312         filter->pre_buffer = g_list_remove (filter->pre_buffer, prebuf);
313         gst_pad_push (filter->srcpad, GST_DATA (prebuf));
314         ++count;
315       }
316       GST_DEBUG ("flushed %d buffers", count);
317       filter->pre_run_length = 0.0;
318     }
319   }
320   /* now check if we have to send the new buffer to the internal buffer cache
321    * or to the srcpad */
322   if (filter->silent)
323   {
324       /* we ref it before putting it in the pre_buffer */
325           /* FIXME: we shouldn't probably do this, because the buffer
326            * arrives reffed already; the plugin should just push it
327            * or unref it to make it disappear */
328           /*
329       gst_buffer_ref (buf);
330       */
331       filter->pre_buffer = g_list_append (filter->pre_buffer, buf);
332       filter->pre_run_length += gst_audio_length (filter->srcpad, buf);
333       while (filter->pre_run_length > filter->pre_length)
334       {
335         prebuf = (g_list_first (filter->pre_buffer))->data;
336         g_assert (GST_IS_BUFFER (prebuf));
337         filter->pre_buffer = g_list_remove (filter->pre_buffer, prebuf);
338         filter->pre_run_length -= gst_audio_length (filter->srcpad, prebuf);
339         /* only pass buffers if we don't leak */
340         if (!filter->leaky)
341           gst_pad_push (filter->srcpad, GST_DATA (prebuf));
342         /* we unref it after getting it out of the pre_buffer */
343         gst_buffer_unref (prebuf);
344       }
345   }
346   else
347     gst_pad_push (filter->srcpad, GST_DATA (buf));
348 }
349
350 static double inline
351 gst_cutter_16bit_ms (gint16* data, guint num_samples)
352 #include "filter.func"
353
354 static double inline
355 gst_cutter_8bit_ms (gint8* data, guint num_samples)
356 #include "filter.func"
357
358 static void
359 gst_cutter_set_property (GObject *object, guint prop_id,
360                          const GValue *value, GParamSpec *pspec)
361 {
362   GstCutter *filter;
363
364   g_return_if_fail (GST_IS_CUTTER (object));
365   filter = GST_CUTTER (object);
366
367   switch (prop_id)
368   {
369     case ARG_THRESHOLD:
370         /* set the level */
371       filter->threshold_level = g_value_get_double (value);
372       GST_DEBUG (
373                  "DEBUG: set threshold level to %f",
374                  filter->threshold_level);
375       break;
376     case ARG_THRESHOLD_DB:
377       /* set the level given in dB
378        * value in dB = 20 * log (value)
379        * values in dB < 0 result in values between 0 and 1
380        */
381       filter->threshold_level = pow (10, g_value_get_double (value) / 20);
382       GST_DEBUG (
383                  "DEBUG: set threshold level to %f",
384                  filter->threshold_level);
385       break;
386     case ARG_RUN_LENGTH:
387       /* set the minimum length of the silent run required */
388       filter->threshold_length = g_value_get_double (value);
389       break;
390     case ARG_PRE_LENGTH:
391       /* set the length of the pre-record block */
392       filter->pre_length = g_value_get_double (value);
393       break;
394     case ARG_LEAKY:
395       /* set if the pre-record buffer is leaky or not */
396       filter->leaky = g_value_get_boolean (value);
397       break;
398     default:
399       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
400       break;
401   }
402 }
403
404 static void
405 gst_cutter_get_property (GObject *object, guint prop_id,
406                          GValue *value, GParamSpec *pspec)
407 {
408   GstCutter *filter;
409
410   g_return_if_fail (GST_IS_CUTTER (object));
411   filter = GST_CUTTER (object);
412
413   switch (prop_id)
414   {
415     case ARG_RUN_LENGTH:
416      g_value_set_double (value, filter->threshold_length);
417      break;
418     case ARG_THRESHOLD:
419      g_value_set_double (value, filter->threshold_level);
420      break;
421     case ARG_THRESHOLD_DB:
422      g_value_set_double (value, 20 * log (filter->threshold_level));
423      break;
424     case ARG_PRE_LENGTH:
425       g_value_set_double (value, filter->pre_length);
426       break;
427     case ARG_LEAKY:
428       g_value_set_boolean (value, filter->leaky);
429       break;
430     default:
431       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
432       break;
433   }
434 }
435
436 static gboolean
437 plugin_init (GstPlugin *plugin)
438 {
439     /* load audio support library */
440   if (!gst_library_load ("gstaudio"))
441     return FALSE;
442
443   if (!gst_element_register (plugin, "cutter", GST_RANK_NONE, GST_TYPE_CUTTER))
444     return FALSE;
445
446   return TRUE;
447 }
448
449 GST_PLUGIN_DEFINE (
450   GST_VERSION_MAJOR,
451   GST_VERSION_MINOR,
452   "cutter",
453   "Audio Cutter to split audio into non-silent bits",
454   plugin_init,
455   VERSION,
456   "LGPL",
457   GST_COPYRIGHT,
458   GST_PACKAGE,
459   GST_ORIGIN)
460
461 void
462 gst_cutter_get_caps (GstPad *pad, GstCutter* filter)
463 {
464   GstCaps *caps = NULL;
465
466   caps = GST_PAD_CAPS (pad);
467     /* FIXME : Please change this to a better warning method ! */
468   g_assert (caps != NULL);
469   if (caps == NULL)
470     printf ("WARNING: get_caps: Could not get caps of pad !\n");
471   gst_caps_get_int (caps, "width", &filter->width);
472   filter->max_sample = gst_audio_highest_sample_value (pad);
473   filter->have_caps = TRUE;
474 }