|
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-audiowsinclimit |
|
33 * |
|
34 * Attenuates all frequencies above the cutoff frequency (low-pass) or all frequencies below the |
|
35 * cutoff frequency (high-pass). 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 lowpass and highpass 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 ! audiowsinclimit mode=low-pass frequency=1000 length=501 ! audioconvert ! alsasink |
|
47 * gst-launch filesrc location="melo1.ogg" ! oggdemux ! vorbisdec ! audioconvert ! audiowsinclimit mode=high-pass frequency=15000 length=501 ! audioconvert ! alsasink |
|
48 * gst-launch audiotestsrc wave=white-noise ! audioconvert ! audiowsinclimit mode=low-pass frequency=1000 length=10001 window=blackman ! 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 "audiowsinclimit.h" |
|
64 |
|
65 #define GST_CAT_DEFAULT gst_audio_wsinclimit_debug |
|
66 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); |
|
67 |
|
68 enum |
|
69 { |
|
70 PROP_0, |
|
71 PROP_LENGTH, |
|
72 PROP_FREQUENCY, |
|
73 PROP_MODE, |
|
74 PROP_WINDOW |
|
75 }; |
|
76 |
|
77 enum |
|
78 { |
|
79 MODE_LOW_PASS = 0, |
|
80 MODE_HIGH_PASS |
|
81 }; |
|
82 |
|
83 #define GST_TYPE_AUDIO_WSINC_LIMIT_MODE (gst_audio_wsinclimit_mode_get_type ()) |
|
84 static GType |
|
85 gst_audio_wsinclimit_mode_get_type (void) |
|
86 { |
|
87 static GType gtype = 0; |
|
88 |
|
89 if (gtype == 0) { |
|
90 static const GEnumValue values[] = { |
|
91 {MODE_LOW_PASS, "Low pass (default)", |
|
92 "low-pass"}, |
|
93 {MODE_HIGH_PASS, "High pass", |
|
94 "high-pass"}, |
|
95 {0, NULL, NULL} |
|
96 }; |
|
97 |
|
98 gtype = g_enum_register_static ("GstAudioWSincLimitMode", values); |
|
99 } |
|
100 return gtype; |
|
101 } |
|
102 |
|
103 enum |
|
104 { |
|
105 WINDOW_HAMMING = 0, |
|
106 WINDOW_BLACKMAN |
|
107 }; |
|
108 |
|
109 #define GST_TYPE_AUDIO_WSINC_LIMIT_WINDOW (gst_audio_wsinclimit_window_get_type ()) |
|
110 static GType |
|
111 gst_audio_wsinclimit_window_get_type (void) |
|
112 { |
|
113 static GType gtype = 0; |
|
114 |
|
115 if (gtype == 0) { |
|
116 static const GEnumValue values[] = { |
|
117 {WINDOW_HAMMING, "Hamming window (default)", |
|
118 "hamming"}, |
|
119 {WINDOW_BLACKMAN, "Blackman window", |
|
120 "blackman"}, |
|
121 {0, NULL, NULL} |
|
122 }; |
|
123 |
|
124 gtype = g_enum_register_static ("GstAudioWSincLimitWindow", values); |
|
125 } |
|
126 return gtype; |
|
127 } |
|
128 |
|
129 #define DEBUG_INIT(bla) \ |
|
130 GST_DEBUG_CATEGORY_INIT (gst_audio_wsinclimit_debug, "audiowsinclimit", 0, \ |
|
131 "Low-pass and High-pass Windowed sinc filter plugin"); |
|
132 |
|
133 GST_BOILERPLATE_FULL (GstAudioWSincLimit, gst_audio_wsinclimit, GstAudioFilter, |
|
134 GST_TYPE_AUDIO_FX_BASE_FIR_FILTER, DEBUG_INIT); |
|
135 |
|
136 static void gst_audio_wsinclimit_set_property (GObject * object, guint prop_id, |
|
137 const GValue * value, GParamSpec * pspec); |
|
138 static void gst_audio_wsinclimit_get_property (GObject * object, guint prop_id, |
|
139 GValue * value, GParamSpec * pspec); |
|
140 static void gst_audio_wsinclimit_finalize (GObject * object); |
|
141 |
|
142 static gboolean gst_audio_wsinclimit_setup (GstAudioFilter * base, |
|
143 GstRingBufferSpec * format); |
|
144 |
|
145 /* Element class */ |
|
146 |
|
147 static void |
|
148 gst_audio_wsinclimit_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 "Low pass & high pass filter", "Filter/Effect/Audio", |
|
154 "Low pass and high pass 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_wsinclimit_class_init (GstAudioWSincLimitClass * klass) |
|
163 { |
|
164 GObjectClass *gobject_class = (GObjectClass *) klass; |
|
165 GstAudioFilterClass *filter_class = (GstAudioFilterClass *) klass; |
|
166 |
|
167 gobject_class->set_property = gst_audio_wsinclimit_set_property; |
|
168 gobject_class->get_property = gst_audio_wsinclimit_get_property; |
|
169 gobject_class->finalize = gst_audio_wsinclimit_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_FREQUENCY, |
|
174 g_param_spec_float ("cutoff", "Cutoff", |
|
175 "Cut-off Frequency (Hz)", 0.0, 100000.0, 0.0, |
|
176 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); |
|
177 g_object_class_install_property (gobject_class, PROP_LENGTH, |
|
178 g_param_spec_int ("length", "Length", |
|
179 "Filter kernel length, will be rounded to the next odd number", |
|
180 3, 50000, 101, |
|
181 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); |
|
182 |
|
183 g_object_class_install_property (gobject_class, PROP_MODE, |
|
184 g_param_spec_enum ("mode", "Mode", |
|
185 "Low pass or high pass mode", GST_TYPE_AUDIO_WSINC_LIMIT_MODE, |
|
186 MODE_LOW_PASS, |
|
187 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); |
|
188 |
|
189 g_object_class_install_property (gobject_class, PROP_WINDOW, |
|
190 g_param_spec_enum ("window", "Window", |
|
191 "Window function to use", GST_TYPE_AUDIO_WSINC_LIMIT_WINDOW, |
|
192 WINDOW_HAMMING, |
|
193 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS)); |
|
194 |
|
195 filter_class->setup = GST_DEBUG_FUNCPTR (gst_audio_wsinclimit_setup); |
|
196 } |
|
197 |
|
198 static void |
|
199 gst_audio_wsinclimit_init (GstAudioWSincLimit * self, |
|
200 GstAudioWSincLimitClass * g_class) |
|
201 { |
|
202 self->mode = MODE_LOW_PASS; |
|
203 self->window = WINDOW_HAMMING; |
|
204 self->kernel_length = 101; |
|
205 self->cutoff = 0.0; |
|
206 |
|
207 self->lock = g_mutex_new (); |
|
208 } |
|
209 |
|
210 static void |
|
211 gst_audio_wsinclimit_build_kernel (GstAudioWSincLimit * self) |
|
212 { |
|
213 gint i = 0; |
|
214 gdouble sum = 0.0; |
|
215 gint len = 0; |
|
216 gdouble w; |
|
217 gdouble *kernel = NULL; |
|
218 |
|
219 len = self->kernel_length; |
|
220 |
|
221 if (GST_AUDIO_FILTER (self)->format.rate == 0) { |
|
222 GST_DEBUG ("rate not set yet"); |
|
223 return; |
|
224 } |
|
225 |
|
226 if (GST_AUDIO_FILTER (self)->format.channels == 0) { |
|
227 GST_DEBUG ("channels not set yet"); |
|
228 return; |
|
229 } |
|
230 |
|
231 /* Clamp cutoff frequency between 0 and the nyquist frequency */ |
|
232 self->cutoff = |
|
233 CLAMP (self->cutoff, 0.0, GST_AUDIO_FILTER (self)->format.rate / 2); |
|
234 |
|
235 GST_DEBUG ("gst_audio_wsinclimit_: initializing filter kernel of length %d " |
|
236 "with cutoff %.2lf Hz " |
|
237 "for mode %s", |
|
238 len, self->cutoff, |
|
239 (self->mode == MODE_LOW_PASS) ? "low-pass" : "high-pass"); |
|
240 |
|
241 /* fill the kernel */ |
|
242 w = 2 * M_PI * (self->cutoff / GST_AUDIO_FILTER (self)->format.rate); |
|
243 |
|
244 kernel = g_new (gdouble, len); |
|
245 |
|
246 for (i = 0; i < len; ++i) { |
|
247 if (i == len / 2) |
|
248 kernel[i] = w; |
|
249 else |
|
250 kernel[i] = sin (w * (i - len / 2)) / (i - len / 2); |
|
251 /* windowing */ |
|
252 if (self->window == WINDOW_HAMMING) |
|
253 kernel[i] *= (0.54 - 0.46 * cos (2 * M_PI * i / len)); |
|
254 else |
|
255 kernel[i] *= (0.42 - 0.5 * cos (2 * M_PI * i / len) + |
|
256 0.08 * cos (4 * M_PI * i / len)); |
|
257 } |
|
258 |
|
259 /* normalize for unity gain at DC */ |
|
260 for (i = 0; i < len; ++i) |
|
261 sum += kernel[i]; |
|
262 for (i = 0; i < len; ++i) |
|
263 kernel[i] /= sum; |
|
264 |
|
265 /* convert to highpass if specified */ |
|
266 if (self->mode == MODE_HIGH_PASS) { |
|
267 for (i = 0; i < len; ++i) |
|
268 kernel[i] = -kernel[i]; |
|
269 kernel[len / 2] += 1.0; |
|
270 } |
|
271 |
|
272 gst_audio_fx_base_fir_filter_set_kernel (GST_AUDIO_FX_BASE_FIR_FILTER (self), |
|
273 kernel, self->kernel_length, (len - 1) / 2); |
|
274 } |
|
275 |
|
276 /* GstAudioFilter vmethod implementations */ |
|
277 |
|
278 /* get notified of caps and plug in the correct process function */ |
|
279 static gboolean |
|
280 gst_audio_wsinclimit_setup (GstAudioFilter * base, GstRingBufferSpec * format) |
|
281 { |
|
282 GstAudioWSincLimit *self = GST_AUDIO_WSINC_LIMIT (base); |
|
283 |
|
284 gst_audio_wsinclimit_build_kernel (self); |
|
285 |
|
286 return GST_AUDIO_FILTER_CLASS (parent_class)->setup (base, format); |
|
287 } |
|
288 |
|
289 static void |
|
290 gst_audio_wsinclimit_finalize (GObject * object) |
|
291 { |
|
292 GstAudioWSincLimit *self = GST_AUDIO_WSINC_LIMIT (object); |
|
293 |
|
294 g_mutex_free (self->lock); |
|
295 self->lock = NULL; |
|
296 |
|
297 G_OBJECT_CLASS (parent_class)->finalize (object); |
|
298 } |
|
299 |
|
300 static void |
|
301 gst_audio_wsinclimit_set_property (GObject * object, guint prop_id, |
|
302 const GValue * value, GParamSpec * pspec) |
|
303 { |
|
304 GstAudioWSincLimit *self = GST_AUDIO_WSINC_LIMIT (object); |
|
305 |
|
306 g_return_if_fail (GST_IS_AUDIO_WSINC_LIMIT (self)); |
|
307 |
|
308 switch (prop_id) { |
|
309 case PROP_LENGTH:{ |
|
310 gint val; |
|
311 |
|
312 g_mutex_lock (self->lock); |
|
313 val = g_value_get_int (value); |
|
314 if (val % 2 == 0) |
|
315 val++; |
|
316 |
|
317 if (val != self->kernel_length) { |
|
318 gst_audio_fx_base_fir_filter_push_residue (GST_AUDIO_FX_BASE_FIR_FILTER |
|
319 (self)); |
|
320 self->kernel_length = val; |
|
321 gst_audio_wsinclimit_build_kernel (self); |
|
322 } |
|
323 g_mutex_unlock (self->lock); |
|
324 break; |
|
325 } |
|
326 case PROP_FREQUENCY: |
|
327 g_mutex_lock (self->lock); |
|
328 self->cutoff = g_value_get_float (value); |
|
329 gst_audio_wsinclimit_build_kernel (self); |
|
330 g_mutex_unlock (self->lock); |
|
331 break; |
|
332 case PROP_MODE: |
|
333 g_mutex_lock (self->lock); |
|
334 self->mode = g_value_get_enum (value); |
|
335 gst_audio_wsinclimit_build_kernel (self); |
|
336 g_mutex_unlock (self->lock); |
|
337 break; |
|
338 case PROP_WINDOW: |
|
339 g_mutex_lock (self->lock); |
|
340 self->window = g_value_get_enum (value); |
|
341 gst_audio_wsinclimit_build_kernel (self); |
|
342 g_mutex_unlock (self->lock); |
|
343 break; |
|
344 default: |
|
345 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
346 break; |
|
347 } |
|
348 } |
|
349 |
|
350 static void |
|
351 gst_audio_wsinclimit_get_property (GObject * object, guint prop_id, |
|
352 GValue * value, GParamSpec * pspec) |
|
353 { |
|
354 GstAudioWSincLimit *self = GST_AUDIO_WSINC_LIMIT (object); |
|
355 |
|
356 switch (prop_id) { |
|
357 case PROP_LENGTH: |
|
358 g_value_set_int (value, self->kernel_length); |
|
359 break; |
|
360 case PROP_FREQUENCY: |
|
361 g_value_set_float (value, self->cutoff); |
|
362 break; |
|
363 case PROP_MODE: |
|
364 g_value_set_enum (value, self->mode); |
|
365 break; |
|
366 case PROP_WINDOW: |
|
367 g_value_set_enum (value, self->window); |
|
368 break; |
|
369 default: |
|
370 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
|
371 break; |
|
372 } |
|
373 } |