|
1 /* |
|
2 * GStreamer |
|
3 * Copyright (C) 2008 Wim Taymans <wim.taymans@gmail.com> |
|
4 * |
|
5 * This library is free software; you can redistribute it and/or |
|
6 * modify it under the terms of the GNU Library General Public |
|
7 * License as published by the Free Software Foundation; either |
|
8 * version 2 of the License, or (at your option) any later version. |
|
9 * |
|
10 * This library is distributed in the hope that it will be useful, |
|
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
13 * Library General Public License for more details. |
|
14 * |
|
15 * You should have received a copy of the GNU Library General Public |
|
16 * License along with this library; if not, write to the |
|
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
|
18 * Boston, MA 02111-1307, USA. |
|
19 */ |
|
20 |
|
21 /** |
|
22 * SECTION:element-audiokaraoke |
|
23 * |
|
24 * Remove the voice from audio by filtering the center channel. |
|
25 * This plugin is useful for karaoke applications. |
|
26 * |
|
27 * <refsect2> |
|
28 * <title>Example launch line</title> |
|
29 * |[ |
|
30 * gst-launch filesrc location=song.ogg ! oggdemux ! vorbisdec ! audiokaraoke ! audioconvert ! alsasink |
|
31 * ]| |
|
32 * </refsect2> |
|
33 */ |
|
34 |
|
35 #ifdef HAVE_CONFIG_H |
|
36 #include "config.h" |
|
37 #endif |
|
38 |
|
39 #include <math.h> |
|
40 |
|
41 #include <gst/gst.h> |
|
42 #include <gst/base/gstbasetransform.h> |
|
43 #include <gst/audio/audio.h> |
|
44 #include <gst/audio/gstaudiofilter.h> |
|
45 #include <gst/controller/gstcontroller.h> |
|
46 |
|
47 #include "audiokaraoke.h" |
|
48 |
|
49 #define GST_CAT_DEFAULT gst_audio_karaoke_debug |
|
50 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); |
|
51 |
|
52 static const GstElementDetails element_details = |
|
53 GST_ELEMENT_DETAILS ("AudioKaraoke", |
|
54 "Filter/Effect/Audio", |
|
55 "Removes voice from sound", |
|
56 "Wim Taymans <wim.taymans@gmail.com>"); |
|
57 |
|
58 /* Filter signals and args */ |
|
59 enum |
|
60 { |
|
61 /* FILL ME */ |
|
62 LAST_SIGNAL |
|
63 }; |
|
64 |
|
65 #define DEFAULT_LEVEL 1.0 |
|
66 #define DEFAULT_MONO_LEVEL 1.0 |
|
67 #define DEFAULT_FILTER_BAND 220.0 |
|
68 #define DEFAULT_FILTER_WIDTH 100.0 |
|
69 |
|
70 enum |
|
71 { |
|
72 PROP_0, |
|
73 PROP_LEVEL, |
|
74 PROP_MONO_LEVEL, |
|
75 PROP_FILTER_BAND, |
|
76 PROP_FILTER_WIDTH, |
|
77 PROP_LAST |
|
78 }; |
|
79 |
|
80 #define ALLOWED_CAPS \ |
|
81 "audio/x-raw-int," \ |
|
82 " depth=(int)16," \ |
|
83 " width=(int)16," \ |
|
84 " endianness=(int)BYTE_ORDER," \ |
|
85 " signed=(bool)TRUE," \ |
|
86 " rate=(int)[1,MAX]," \ |
|
87 " channels=(int)[1,MAX]; " \ |
|
88 "audio/x-raw-float," \ |
|
89 " width=(int)32," \ |
|
90 " endianness=(int)BYTE_ORDER," \ |
|
91 " rate=(int)[1,MAX]," \ |
|
92 " channels=(int)[1,MAX]" |
|
93 |
|
94 #define DEBUG_INIT(bla) \ |
|
95 GST_DEBUG_CATEGORY_INIT (gst_audio_karaoke_debug, "audiokaraoke", 0, "audiokaraoke element"); |
|
96 |
|
97 GST_BOILERPLATE_FULL (GstAudioKaraoke, gst_audio_karaoke, GstAudioFilter, |
|
98 GST_TYPE_AUDIO_FILTER, DEBUG_INIT); |
|
99 |
|
100 static void gst_audio_karaoke_set_property (GObject * object, guint prop_id, |
|
101 const GValue * value, GParamSpec * pspec); |
|
102 static void gst_audio_karaoke_get_property (GObject * object, guint prop_id, |
|
103 GValue * value, GParamSpec * pspec); |
|
104 |
|
105 static gboolean gst_audio_karaoke_setup (GstAudioFilter * filter, |
|
106 GstRingBufferSpec * format); |
|
107 static GstFlowReturn gst_audio_karaoke_transform_ip (GstBaseTransform * base, |
|
108 GstBuffer * buf); |
|
109 |
|
110 static void gst_audio_karaoke_transform_int (GstAudioKaraoke * filter, |
|
111 gint16 * data, guint num_samples); |
|
112 static void gst_audio_karaoke_transform_float (GstAudioKaraoke * filter, |
|
113 gfloat * data, guint num_samples); |
|
114 |
|
115 /* GObject vmethod implementations */ |
|
116 |
|
117 static void |
|
118 gst_audio_karaoke_base_init (gpointer klass) |
|
119 { |
|
120 GstElementClass *element_class = GST_ELEMENT_CLASS (klass); |
|
121 GstCaps *caps; |
|
122 |
|
123 gst_element_class_set_details (element_class, &element_details); |
|
124 |
|
125 caps = gst_caps_from_string (ALLOWED_CAPS); |
|
126 gst_audio_filter_class_add_pad_templates (GST_AUDIO_FILTER_CLASS (klass), |
|
127 caps); |
|
128 gst_caps_unref (caps); |
|
129 } |
|
130 |
|
131 static void |
|
132 gst_audio_karaoke_class_init (GstAudioKaraokeClass * klass) |
|
133 { |
|
134 GObjectClass *gobject_class; |
|
135 |
|
136 gobject_class = (GObjectClass *) klass; |
|
137 gobject_class->set_property = gst_audio_karaoke_set_property; |
|
138 gobject_class->get_property = gst_audio_karaoke_get_property; |
|
139 |
|
140 g_object_class_install_property (gobject_class, PROP_LEVEL, |
|
141 g_param_spec_float ("level", "Level", |
|
142 "Level of the effect (1.0 = full)", 0.0, 1.0, DEFAULT_LEVEL, |
|
143 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE)); |
|
144 |
|
145 g_object_class_install_property (gobject_class, PROP_MONO_LEVEL, |
|
146 g_param_spec_float ("mono-level", "Mono Level", |
|
147 "Level of the mono channel (1.0 = full)", 0.0, 1.0, DEFAULT_LEVEL, |
|
148 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE)); |
|
149 |
|
150 g_object_class_install_property (gobject_class, PROP_FILTER_BAND, |
|
151 g_param_spec_float ("filter-band", "Filter Band", |
|
152 "The Frequency band of the filter", 0.0, 441.0, DEFAULT_FILTER_BAND, |
|
153 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE)); |
|
154 |
|
155 g_object_class_install_property (gobject_class, PROP_FILTER_WIDTH, |
|
156 g_param_spec_float ("filter-width", "Filter Width", |
|
157 "The Frequency width of the filter", 0.0, 100.0, DEFAULT_FILTER_WIDTH, |
|
158 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE)); |
|
159 |
|
160 GST_AUDIO_FILTER_CLASS (klass)->setup = |
|
161 GST_DEBUG_FUNCPTR (gst_audio_karaoke_setup); |
|
162 GST_BASE_TRANSFORM_CLASS (klass)->transform_ip = |
|
163 GST_DEBUG_FUNCPTR (gst_audio_karaoke_transform_ip); |
|
164 } |
|
165 |
|
166 static void |
|
167 gst_audio_karaoke_init (GstAudioKaraoke * filter, GstAudioKaraokeClass * klass) |
|
168 { |
|
169 gst_base_transform_set_in_place (GST_BASE_TRANSFORM (filter), TRUE); |
|
170 gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (filter), TRUE); |
|
171 |
|
172 filter->level = DEFAULT_LEVEL; |
|
173 filter->mono_level = DEFAULT_MONO_LEVEL; |
|
174 filter->filter_band = DEFAULT_FILTER_BAND; |
|
175 filter->filter_width = DEFAULT_FILTER_WIDTH; |
|
176 } |
|
177 |
|
178 static void |
|
179 update_filter (GstAudioKaraoke * filter, gint rate) |
|
180 { |
|
181 gfloat A, B, C; |
|
182 |
|
183 if (rate == 0) |
|
184 return; |
|
185 |
|
186 C = exp (-2 * M_PI * filter->filter_width / rate); |
|
187 B = -4 * C / (1 + C) * cos (2 * M_PI * filter->filter_band / rate); |
|
188 A = sqrt (1 - B * B / (4 * C)) * (1 - C); |
|
189 |
|
190 filter->A = A; |
|
191 filter->B = B; |
|
192 filter->C = C; |
|
193 filter->y1 = 0.0; |
|
194 filter->y2 = 0.0; |
|
195 } |
|
196 |
|
197 static void |
|
198 gst_audio_karaoke_set_property (GObject * object, guint prop_id, |
|
199 const GValue * value, GParamSpec * pspec) |
|
200 { |
|
201 GstAudioKaraoke *filter; |
|
202 |
|
203 filter = GST_AUDIO_KARAOKE (object); |
|
204 |
|
205 switch (prop_id) { |
|
206 case PROP_LEVEL: |
|
207 filter->level = g_value_get_float (value); |
|
208 break; |
|
209 case PROP_MONO_LEVEL: |
|
210 filter->mono_level = g_value_get_float (value); |
|
211 break; |
|
212 case PROP_FILTER_BAND: |
|
213 filter->filter_band = g_value_get_float (value); |
|
214 update_filter (filter, filter->rate); |
|
215 break; |
|
216 case PROP_FILTER_WIDTH: |
|
217 filter->filter_width = g_value_get_float (value); |
|
218 update_filter (filter, filter->rate); |
|
219 break; |
|
220 default: |
|
221 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
222 break; |
|
223 } |
|
224 } |
|
225 |
|
226 static void |
|
227 gst_audio_karaoke_get_property (GObject * object, guint prop_id, |
|
228 GValue * value, GParamSpec * pspec) |
|
229 { |
|
230 GstAudioKaraoke *filter; |
|
231 |
|
232 filter = GST_AUDIO_KARAOKE (object); |
|
233 |
|
234 switch (prop_id) { |
|
235 case PROP_LEVEL: |
|
236 g_value_set_float (value, filter->level); |
|
237 break; |
|
238 case PROP_MONO_LEVEL: |
|
239 g_value_set_float (value, filter->mono_level); |
|
240 break; |
|
241 case PROP_FILTER_BAND: |
|
242 g_value_set_float (value, filter->filter_band); |
|
243 break; |
|
244 case PROP_FILTER_WIDTH: |
|
245 g_value_set_float (value, filter->filter_width); |
|
246 break; |
|
247 default: |
|
248 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
249 break; |
|
250 } |
|
251 } |
|
252 |
|
253 /* GstAudioFilter vmethod implementations */ |
|
254 |
|
255 static gboolean |
|
256 gst_audio_karaoke_setup (GstAudioFilter * base, GstRingBufferSpec * format) |
|
257 { |
|
258 GstAudioKaraoke *filter = GST_AUDIO_KARAOKE (base); |
|
259 gboolean ret = TRUE; |
|
260 |
|
261 filter->channels = format->channels; |
|
262 filter->rate = format->rate; |
|
263 |
|
264 if (format->type == GST_BUFTYPE_FLOAT && format->width == 32) |
|
265 filter->process = (GstAudioKaraokeProcessFunc) |
|
266 gst_audio_karaoke_transform_float; |
|
267 else if (format->type == GST_BUFTYPE_LINEAR && format->width == 16) |
|
268 filter->process = (GstAudioKaraokeProcessFunc) |
|
269 gst_audio_karaoke_transform_int; |
|
270 else |
|
271 ret = FALSE; |
|
272 |
|
273 update_filter (filter, format->rate); |
|
274 |
|
275 return ret; |
|
276 } |
|
277 |
|
278 static void |
|
279 gst_audio_karaoke_transform_int (GstAudioKaraoke * filter, |
|
280 gint16 * data, guint num_samples) |
|
281 { |
|
282 gint i, l, r, o, x; |
|
283 gint channels; |
|
284 gdouble y; |
|
285 gint level; |
|
286 |
|
287 channels = filter->channels; |
|
288 level = filter->level * 256; |
|
289 |
|
290 for (i = 0; i < num_samples; i += channels) { |
|
291 /* get left and right inputs */ |
|
292 l = data[i]; |
|
293 r = data[i + 1]; |
|
294 /* do filtering */ |
|
295 x = (l + r) / 2; |
|
296 y = (filter->A * x - filter->B * filter->y1) - filter->C * filter->y2; |
|
297 filter->y2 = filter->y1; |
|
298 filter->y1 = y; |
|
299 /* filter mono signal */ |
|
300 o = (int) (y * filter->mono_level); |
|
301 o = CLAMP (o, G_MININT16, G_MAXINT16); |
|
302 o = (o * level) >> 8; |
|
303 /* now cut the center */ |
|
304 x = l - ((r * level) >> 8) + o; |
|
305 r = r - ((l * level) >> 8) + o; |
|
306 data[i] = CLAMP (x, G_MININT16, G_MAXINT16); |
|
307 data[i + 1] = CLAMP (r, G_MININT16, G_MAXINT16); |
|
308 } |
|
309 } |
|
310 |
|
311 static void |
|
312 gst_audio_karaoke_transform_float (GstAudioKaraoke * filter, |
|
313 gfloat * data, guint num_samples) |
|
314 { |
|
315 gint i; |
|
316 gint channels; |
|
317 gdouble l, r, o; |
|
318 gdouble y; |
|
319 |
|
320 channels = filter->channels; |
|
321 |
|
322 for (i = 0; i < num_samples; i += channels) { |
|
323 /* get left and right inputs */ |
|
324 l = data[i]; |
|
325 r = data[i + 1]; |
|
326 /* do filtering */ |
|
327 y = (filter->A * ((l + r) / 2.0) - filter->B * filter->y1) - |
|
328 filter->C * filter->y2; |
|
329 filter->y2 = filter->y1; |
|
330 filter->y1 = y; |
|
331 /* filter mono signal */ |
|
332 o = y * filter->mono_level * filter->level; |
|
333 /* now cut the center */ |
|
334 data[i] = l - (r * filter->level) + o; |
|
335 data[i + 1] = r - (l * filter->level) + o; |
|
336 } |
|
337 } |
|
338 |
|
339 /* GstBaseTransform vmethod implementations */ |
|
340 static GstFlowReturn |
|
341 gst_audio_karaoke_transform_ip (GstBaseTransform * base, GstBuffer * buf) |
|
342 { |
|
343 GstAudioKaraoke *filter = GST_AUDIO_KARAOKE (base); |
|
344 guint num_samples = |
|
345 GST_BUFFER_SIZE (buf) / (GST_AUDIO_FILTER (filter)->format.width / 8); |
|
346 |
|
347 if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buf))) |
|
348 gst_object_sync_values (G_OBJECT (filter), GST_BUFFER_TIMESTAMP (buf)); |
|
349 |
|
350 if (gst_base_transform_is_passthrough (base) || |
|
351 G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_GAP))) |
|
352 return GST_FLOW_OK; |
|
353 |
|
354 filter->process (filter, GST_BUFFER_DATA (buf), num_samples); |
|
355 |
|
356 return GST_FLOW_OK; |
|
357 } |