|
1 /* -*- c-basic-offset: 2 -*- |
|
2 * |
|
3 * GStreamer |
|
4 * Copyright (C) 1999-2001 Erik Walthinsen <omega@cse.ogi.edu> |
|
5 * 2006 Dreamlab Technologies Ltd. <mathis.hofer@dreamlab.net> |
|
6 * 2007-2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> |
|
7 * |
|
8 * This library is free software; you can redistribute it and/or |
|
9 * modify it under the terms of the GNU Library General Public |
|
10 * License as published by the Free Software Foundation; either |
|
11 * version 2 of the License, or (at your option) any later version. |
|
12 * |
|
13 * This library is distributed in the hope that it will be useful, |
|
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
16 * Library General Public License for more details. |
|
17 * |
|
18 * You should have received a copy of the GNU Library General Public |
|
19 * License along with this library; if not, write to the |
|
20 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
|
21 * Boston, MA 02111-1307, USA. |
|
22 * |
|
23 * |
|
24 * this windowed sinc filter is taken from the freely downloadable DSP book, |
|
25 * "The Scientist and Engineer's Guide to Digital Signal Processing", |
|
26 * chapter 16 |
|
27 * available at http://www.dspguide.com/ |
|
28 * |
|
29 */ |
|
30 |
|
31 /** |
|
32 * SECTION:element-audiowsincband |
|
33 * |
|
34 * Attenuates all frequencies outside (bandpass) or inside (bandreject) of a frequency |
|
35 * band. The length parameter controls the rolloff, the window parameter |
|
36 * controls rolloff and stopband attenuation. The Hamming window provides a faster rolloff but a bit |
|
37 * worse stopband attenuation, the other way around for the Blackman window. |
|
38 * |
|
39 * This element has the advantage over the Chebyshev bandpass and bandreject filter that it has |
|
40 * a much better rolloff when using a larger kernel size and almost linear phase. The only |
|
41 * disadvantage is the much slower execution time with larger kernels. |
|
42 * |
|
43 * <refsect2> |
|
44 * <title>Example launch line</title> |
|
45 * |[ |
|
46 * gst-launch audiotestsrc freq=1500 ! audioconvert ! audiosincband mode=band-pass lower-frequency=3000 upper-frequency=10000 length=501 window=blackman ! audioconvert ! alsasink |
|
47 * gst-launch filesrc location="melo1.ogg" ! oggdemux ! vorbisdec ! audioconvert ! audiowsincband mode=band-reject lower-frequency=59 upper-frequency=61 length=10001 window=hamming ! audioconvert ! alsasink |
|
48 * gst-launch audiotestsrc wave=white-noise ! audioconvert ! audiowsincband mode=band-pass lower-frequency=1000 upper-frequency=2000 length=31 ! audioconvert ! alsasink |
|
49 * ]| |
|
50 * </refsect2> |
|
51 */ |
|
52 |
|
53 #ifdef HAVE_CONFIG_H |
|
54 #include "config.h" |
|
55 #endif |
|
56 |
|
57 #include <string.h> |
|
58 #include <math.h> |
|
59 #include <gst/gst.h> |
|
60 #include <gst/audio/gstaudiofilter.h> |
|
61 #include <gst/controller/gstcontroller.h> |
|
62 |
|
63 #include "audiowsincband.h" |
|
64 |
|
65 #define GST_CAT_DEFAULT gst_gst_audio_wsincband_debug |
|
66 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); |
|
67 |
|
68 enum |
|
69 { |
|
70 PROP_0, |
|
71 PROP_LENGTH, |
|
72 PROP_LOWER_FREQUENCY, |
|
73 PROP_UPPER_FREQUENCY, |
|
74 PROP_MODE, |
|
75 PROP_WINDOW |
|
76 }; |
|
77 |
|
78 enum |
|
79 { |
|
80 MODE_BAND_PASS = 0, |
|
81 MODE_BAND_REJECT |
|
82 }; |
|
83 |
|
84 #define GST_TYPE_AUDIO_WSINC_BAND_MODE (gst_gst_audio_wsincband_mode_get_type ()) |
|
85 static GType |
|
86 gst_gst_audio_wsincband_mode_get_type (void) |
|
87 { |
|
88 static GType gtype = 0; |
|
89 |
|
90 if (gtype == 0) { |
|
91 static const GEnumValue values[] = { |
|
92 {MODE_BAND_PASS, "Band pass (default)", |
|
93 "band-pass"}, |
|
94 {MODE_BAND_REJECT, "Band reject", |
|
95 "band-reject"}, |
|
96 {0, NULL, NULL} |
|
97 }; |
|
98 |
|
99 gtype = g_enum_register_static ("GstAudioWSincBandMode", values); |
|
100 } |
|
101 return gtype; |
|
102 } |
|
103 |
|
104 enum |
|
105 { |
|
106 WINDOW_HAMMING = 0, |
|
107 WINDOW_BLACKMAN |
|
108 }; |
|
109 |
|
110 #define GST_TYPE_AUDIO_WSINC_BAND_WINDOW (gst_gst_audio_wsincband_window_get_type ()) |
|
111 static GType |
|
112 gst_gst_audio_wsincband_window_get_type (void) |
|
113 { |
|
114 static GType gtype = 0; |
|
115 |
|
116 if (gtype == 0) { |
|
117 static const GEnumValue values[] = { |
|
118 {WINDOW_HAMMING, "Hamming window (default)", |
|
119 "hamming"}, |
|
120 {WINDOW_BLACKMAN, "Blackman window", |
|
121 "blackman"}, |
|
122 {0, NULL, NULL} |
|
123 }; |
|
124 |
|
125 gtype = g_enum_register_static ("GstAudioWSincBandWindow", values); |
|
126 } |
|
127 return gtype; |
|
128 } |
|
129 |
|
130 #define DEBUG_INIT(bla) \ |
|
131 GST_DEBUG_CATEGORY_INIT (gst_gst_audio_wsincband_debug, "audiowsincband", 0, \ |
|
132 "Band-pass and Band-reject Windowed sinc filter plugin"); |
|
133 |
|
134 GST_BOILERPLATE_FULL (GstAudioWSincBand, gst_audio_wsincband, GstAudioFilter, |
|
135 GST_TYPE_AUDIO_FX_BASE_FIR_FILTER, DEBUG_INIT); |
|
136 |
|
137 static void gst_audio_wsincband_set_property (GObject * object, guint prop_id, |
|
138 const GValue * value, GParamSpec * pspec); |
|
139 static void gst_audio_wsincband_get_property (GObject * object, guint prop_id, |
|
140 GValue * value, GParamSpec * pspec); |
|
141 static void gst_audio_wsincband_finalize (GObject * object); |
|
142 |
|
143 static gboolean gst_audio_wsincband_setup (GstAudioFilter * base, |
|
144 GstRingBufferSpec * format); |
|
145 |
|
146 /* Element class */ |
|
147 static void |
|
148 gst_audio_wsincband_base_init (gpointer g_class) |
|
149 { |
|
150 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); |
|
151 |
|
152 gst_element_class_set_details_simple (element_class, |
|
153 "Band pass & band reject filter", "Filter/Effect/Audio", |
|
154 "Band pass and band reject windowed sinc filter", |
|
155 "Thomas Vander Stichele <thomas at apestaart dot org>, " |
|
156 "Steven W. Smith, " |
|
157 "Dreamlab Technologies Ltd. <mathis.hofer@dreamlab.net>, " |
|
158 "Sebastian Dröge <sebastian.droege@collabora.co.uk>"); |
|
159 } |
|
160 |
|
161 static void |
|
162 gst_audio_wsincband_class_init (GstAudioWSincBandClass * klass) |
|
163 { |
|
164 GObjectClass *gobject_class = (GObjectClass *) klass; |
|
165 GstAudioFilterClass *filter_class = (GstAudioFilterClass *) klass; |
|
166 |
|
167 gobject_class->set_property = gst_audio_wsincband_set_property; |
|
168 gobject_class->get_property = gst_audio_wsincband_get_property; |
|
169 gobject_class->finalize = gst_audio_wsincband_finalize; |
|
170 |
|
171 /* FIXME: Don't use the complete possible range but restrict the upper boundary |
|
172 * so automatically generated UIs can use a slider */ |
|
173 g_object_class_install_property (gobject_class, PROP_LOWER_FREQUENCY, |
|
174 g_param_spec_float ("lower-frequency", "Lower Frequency", |
|
175 "Cut-off lower frequency (Hz)", 0.0, 100000.0, 0, |
|
176 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); |
|
177 g_object_class_install_property (gobject_class, PROP_UPPER_FREQUENCY, |
|
178 g_param_spec_float ("upper-frequency", "Upper Frequency", |
|
179 "Cut-off upper frequency (Hz)", 0.0, 100000.0, 0, |
|
180 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); |
|
181 g_object_class_install_property (gobject_class, PROP_LENGTH, |
|
182 g_param_spec_int ("length", "Length", |
|
183 "Filter kernel length, will be rounded to the next odd number", 3, |
|
184 50000, 101, |
|
185 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); |
|
186 |
|
187 g_object_class_install_property (gobject_class, PROP_MODE, |
|
188 g_param_spec_enum ("mode", "Mode", |
|
189 "Band pass or band reject mode", GST_TYPE_AUDIO_WSINC_BAND_MODE, |
|
190 MODE_BAND_PASS, |
|
191 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); |
|
192 |
|
193 g_object_class_install_property (gobject_class, PROP_WINDOW, |
|
194 g_param_spec_enum ("window", "Window", |
|
195 "Window function to use", GST_TYPE_AUDIO_WSINC_BAND_WINDOW, |
|
196 WINDOW_HAMMING, |
|
197 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); |
|
198 |
|
199 filter_class->setup = GST_DEBUG_FUNCPTR (gst_audio_wsincband_setup); |
|
200 } |
|
201 |
|
202 static void |
|
203 gst_audio_wsincband_init (GstAudioWSincBand * self, |
|
204 GstAudioWSincBandClass * g_class) |
|
205 { |
|
206 self->kernel_length = 101; |
|
207 self->lower_frequency = 0.0; |
|
208 self->upper_frequency = 0.0; |
|
209 self->mode = MODE_BAND_PASS; |
|
210 self->window = WINDOW_HAMMING; |
|
211 |
|
212 self->lock = g_mutex_new (); |
|
213 } |
|
214 |
|
215 static void |
|
216 gst_audio_wsincband_build_kernel (GstAudioWSincBand * self) |
|
217 { |
|
218 gint i = 0; |
|
219 gdouble sum = 0.0; |
|
220 gint len = 0; |
|
221 gdouble *kernel_lp, *kernel_hp; |
|
222 gdouble w; |
|
223 gdouble *kernel; |
|
224 |
|
225 len = self->kernel_length; |
|
226 |
|
227 if (GST_AUDIO_FILTER (self)->format.rate == 0) { |
|
228 GST_DEBUG ("rate not set yet"); |
|
229 return; |
|
230 } |
|
231 |
|
232 if (GST_AUDIO_FILTER (self)->format.channels == 0) { |
|
233 GST_DEBUG ("channels not set yet"); |
|
234 return; |
|
235 } |
|
236 |
|
237 /* Clamp frequencies */ |
|
238 self->lower_frequency = |
|
239 CLAMP (self->lower_frequency, 0.0, |
|
240 GST_AUDIO_FILTER (self)->format.rate / 2); |
|
241 self->upper_frequency = |
|
242 CLAMP (self->upper_frequency, 0.0, |
|
243 GST_AUDIO_FILTER (self)->format.rate / 2); |
|
244 if (self->lower_frequency > self->upper_frequency) { |
|
245 gint tmp = self->lower_frequency; |
|
246 |
|
247 self->lower_frequency = self->upper_frequency; |
|
248 self->upper_frequency = tmp; |
|
249 } |
|
250 |
|
251 GST_DEBUG ("gst_audio_wsincband: initializing filter kernel of length %d " |
|
252 "with lower frequency %.2lf Hz " |
|
253 ", upper frequency %.2lf Hz for mode %s", |
|
254 len, self->lower_frequency, self->upper_frequency, |
|
255 (self->mode == MODE_BAND_PASS) ? "band-pass" : "band-reject"); |
|
256 |
|
257 /* fill the lp kernel */ |
|
258 w = 2 * M_PI * (self->lower_frequency / GST_AUDIO_FILTER (self)->format.rate); |
|
259 kernel_lp = g_new (gdouble, len); |
|
260 for (i = 0; i < len; ++i) { |
|
261 if (i == len / 2) |
|
262 kernel_lp[i] = w; |
|
263 else |
|
264 kernel_lp[i] = sin (w * (i - len / 2)) |
|
265 / (i - len / 2); |
|
266 /* Windowing */ |
|
267 if (self->window == WINDOW_HAMMING) |
|
268 kernel_lp[i] *= (0.54 - 0.46 * cos (2 * M_PI * i / len)); |
|
269 else |
|
270 kernel_lp[i] *= |
|
271 (0.42 - 0.5 * cos (2 * M_PI * i / len) + |
|
272 0.08 * cos (4 * M_PI * i / len)); |
|
273 } |
|
274 |
|
275 /* normalize for unity gain at DC */ |
|
276 sum = 0.0; |
|
277 for (i = 0; i < len; ++i) |
|
278 sum += kernel_lp[i]; |
|
279 for (i = 0; i < len; ++i) |
|
280 kernel_lp[i] /= sum; |
|
281 |
|
282 /* fill the hp kernel */ |
|
283 w = 2 * M_PI * (self->upper_frequency / GST_AUDIO_FILTER (self)->format.rate); |
|
284 kernel_hp = g_new (gdouble, len); |
|
285 for (i = 0; i < len; ++i) { |
|
286 if (i == len / 2) |
|
287 kernel_hp[i] = w; |
|
288 else |
|
289 kernel_hp[i] = sin (w * (i - len / 2)) |
|
290 / (i - len / 2); |
|
291 /* Windowing */ |
|
292 if (self->window == WINDOW_HAMMING) |
|
293 kernel_hp[i] *= (0.54 - 0.46 * cos (2 * M_PI * i / len)); |
|
294 else |
|
295 kernel_hp[i] *= |
|
296 (0.42 - 0.5 * cos (2 * M_PI * i / len) + |
|
297 0.08 * cos (4 * M_PI * i / len)); |
|
298 } |
|
299 |
|
300 /* normalize for unity gain at DC */ |
|
301 sum = 0.0; |
|
302 for (i = 0; i < len; ++i) |
|
303 sum += kernel_hp[i]; |
|
304 for (i = 0; i < len; ++i) |
|
305 kernel_hp[i] /= sum; |
|
306 |
|
307 /* do spectral inversion to go from lowpass to highpass */ |
|
308 for (i = 0; i < len; ++i) |
|
309 kernel_hp[i] = -kernel_hp[i]; |
|
310 kernel_hp[len / 2] += 1; |
|
311 |
|
312 /* combine the two kernels */ |
|
313 kernel = g_new (gdouble, len); |
|
314 |
|
315 for (i = 0; i < len; ++i) |
|
316 kernel[i] = kernel_lp[i] + kernel_hp[i]; |
|
317 |
|
318 /* free the helper kernels */ |
|
319 g_free (kernel_lp); |
|
320 g_free (kernel_hp); |
|
321 |
|
322 /* do spectral inversion to go from bandreject to bandpass |
|
323 * if specified */ |
|
324 if (self->mode == MODE_BAND_PASS) { |
|
325 for (i = 0; i < len; ++i) |
|
326 kernel[i] = -kernel[i]; |
|
327 kernel[len / 2] += 1; |
|
328 } |
|
329 |
|
330 gst_audio_fx_base_fir_filter_set_kernel (GST_AUDIO_FX_BASE_FIR_FILTER (self), |
|
331 kernel, self->kernel_length, (len - 1) / 2); |
|
332 } |
|
333 |
|
334 /* GstAudioFilter vmethod implementations */ |
|
335 |
|
336 /* get notified of caps and plug in the correct process function */ |
|
337 static gboolean |
|
338 gst_audio_wsincband_setup (GstAudioFilter * base, GstRingBufferSpec * format) |
|
339 { |
|
340 GstAudioWSincBand *self = GST_AUDIO_WSINC_BAND (base); |
|
341 |
|
342 gst_audio_wsincband_build_kernel (self); |
|
343 |
|
344 return GST_AUDIO_FILTER_CLASS (parent_class)->setup (base, format); |
|
345 } |
|
346 |
|
347 static void |
|
348 gst_audio_wsincband_finalize (GObject * object) |
|
349 { |
|
350 GstAudioWSincBand *self = GST_AUDIO_WSINC_BAND (object); |
|
351 |
|
352 g_mutex_free (self->lock); |
|
353 self->lock = NULL; |
|
354 |
|
355 G_OBJECT_CLASS (parent_class)->finalize (object); |
|
356 } |
|
357 |
|
358 static void |
|
359 gst_audio_wsincband_set_property (GObject * object, guint prop_id, |
|
360 const GValue * value, GParamSpec * pspec) |
|
361 { |
|
362 GstAudioWSincBand *self = GST_AUDIO_WSINC_BAND (object); |
|
363 |
|
364 g_return_if_fail (GST_IS_AUDIO_WSINC_BAND (self)); |
|
365 |
|
366 switch (prop_id) { |
|
367 case PROP_LENGTH:{ |
|
368 gint val; |
|
369 |
|
370 g_mutex_lock (self->lock); |
|
371 val = g_value_get_int (value); |
|
372 if (val % 2 == 0) |
|
373 val++; |
|
374 |
|
375 if (val != self->kernel_length) { |
|
376 gst_audio_fx_base_fir_filter_push_residue (GST_AUDIO_FX_BASE_FIR_FILTER |
|
377 (self)); |
|
378 self->kernel_length = val; |
|
379 gst_audio_wsincband_build_kernel (self); |
|
380 } |
|
381 g_mutex_unlock (self->lock); |
|
382 break; |
|
383 } |
|
384 case PROP_LOWER_FREQUENCY: |
|
385 g_mutex_lock (self->lock); |
|
386 self->lower_frequency = g_value_get_float (value); |
|
387 gst_audio_wsincband_build_kernel (self); |
|
388 g_mutex_unlock (self->lock); |
|
389 break; |
|
390 case PROP_UPPER_FREQUENCY: |
|
391 g_mutex_lock (self->lock); |
|
392 self->upper_frequency = g_value_get_float (value); |
|
393 gst_audio_wsincband_build_kernel (self); |
|
394 g_mutex_unlock (self->lock); |
|
395 break; |
|
396 case PROP_MODE: |
|
397 g_mutex_lock (self->lock); |
|
398 self->mode = g_value_get_enum (value); |
|
399 gst_audio_wsincband_build_kernel (self); |
|
400 g_mutex_unlock (self->lock); |
|
401 break; |
|
402 case PROP_WINDOW: |
|
403 g_mutex_lock (self->lock); |
|
404 self->window = g_value_get_enum (value); |
|
405 gst_audio_wsincband_build_kernel (self); |
|
406 g_mutex_unlock (self->lock); |
|
407 break; |
|
408 default: |
|
409 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
410 break; |
|
411 } |
|
412 } |
|
413 |
|
414 static void |
|
415 gst_audio_wsincband_get_property (GObject * object, guint prop_id, |
|
416 GValue * value, GParamSpec * pspec) |
|
417 { |
|
418 GstAudioWSincBand *self = GST_AUDIO_WSINC_BAND (object); |
|
419 |
|
420 switch (prop_id) { |
|
421 case PROP_LENGTH: |
|
422 g_value_set_int (value, self->kernel_length); |
|
423 break; |
|
424 case PROP_LOWER_FREQUENCY: |
|
425 g_value_set_float (value, self->lower_frequency); |
|
426 break; |
|
427 case PROP_UPPER_FREQUENCY: |
|
428 g_value_set_float (value, self->upper_frequency); |
|
429 break; |
|
430 case PROP_MODE: |
|
431 g_value_set_enum (value, self->mode); |
|
432 break; |
|
433 case PROP_WINDOW: |
|
434 g_value_set_enum (value, self->window); |
|
435 break; |
|
436 default: |
|
437 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
438 break; |
|
439 } |
|
440 } |