1 /* GStreamer
2 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3 * Copyright (C) <2003> David Schleef <ds@schleef.org>
4 * Copyright (C) <2011,2014> Christoph Reiter <reiter.christoph@gmail.com>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 /**
22 * SECTION:element-bs2b
23 * @title: bs2b
24 *
25 * Improve headphone listening of stereo audio records using the bs2b library.
26 * It does so by mixing the left and right channel in a way that simulates
27 * a stereo speaker setup while using headphones.
28 *
29 * ## Example pipelines
30 * |[
31 * gst-launch-1.0 audiotestsrc ! "audio/x-raw,channel-mask=(bitmask)0x1" ! interleave name=i ! bs2b ! autoaudiosink audiotestsrc freq=330 ! "audio/x-raw,channel-mask=(bitmask)0x2" ! i.
32 * ]| Play two independent sine test sources and crossfeed them.
33 *
34 */
35
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
39
40 #include <gst/gst.h>
41 #include <gst/audio/audio.h>
42 #include <gst/audio/gstaudiofilter.h>
43
44 #include "gstbs2b.h"
45
46 #define GST_BS2B_DP_LOCK(obj) g_mutex_lock (&obj->bs2b_lock)
47 #define GST_BS2B_DP_UNLOCK(obj) g_mutex_unlock (&obj->bs2b_lock)
48
49 #define SUPPORTED_FORMAT \
50 "(string) { S8, U8, S16LE, S16BE, U16LE, U16BE, S32LE, S32BE, U32LE, " \
51 "U32BE, S24LE, S24BE, U24LE, U24BE, F32LE, F32BE, F64LE, F64BE }"
52
53 #define SUPPORTED_RATE \
54 "(int) [ " G_STRINGIFY (BS2B_MINSRATE) ", " G_STRINGIFY (BS2B_MAXSRATE) " ]"
55
56 #define FRONT_L_FRONT_R "(bitmask) 0x3"
57
58 #define PAD_CAPS \
59 "audio/x-raw, " \
60 "format = " SUPPORTED_FORMAT ", " \
61 "rate = " SUPPORTED_RATE ", " \
62 "channels = (int) 2, " \
63 "channel-mask = " FRONT_L_FRONT_R ", " \
64 "layout = (string) interleaved" \
65 "; " \
66 "audio/x-raw, " \
67 "channels = (int) 1" \
68
69 enum
70 {
71 PROP_FCUT = 1,
72 PROP_FEED,
73 PROP_LAST,
74 };
75
76 static GParamSpec *properties[PROP_LAST];
77
78 typedef struct
79 {
80 const gchar *name;
81 const gchar *desc;
82 gint preset;
83 } GstBs2bPreset;
84
85 static const GstBs2bPreset presets[3] = {
86 {
87 "default",
88 "Closest to virtual speaker placement (30°, 3 meter) [700Hz, 4.5dB]",
89 BS2B_DEFAULT_CLEVEL},
90 {
91 "cmoy",
92 "Close to Chu Moy's crossfeeder (popular) [700Hz, 6.0dB]",
93 BS2B_CMOY_CLEVEL},
94 {
95 "jmeier",
96 "Close to Jan Meier's CORDA amplifiers (little change) [650Hz, 9.0dB]",
97 BS2B_JMEIER_CLEVEL}
98 };
99
100 static void gst_preset_interface_init (gpointer g_iface, gpointer iface_data);
101
102 G_DEFINE_TYPE_WITH_CODE (GstBs2b, gst_bs2b, GST_TYPE_AUDIO_FILTER,
103 G_IMPLEMENT_INTERFACE (GST_TYPE_PRESET, gst_preset_interface_init));
104
105 static void gst_bs2b_set_property (GObject * object, guint prop_id,
106 const GValue * value, GParamSpec * pspec);
107 static void gst_bs2b_get_property (GObject * object, guint prop_id,
108 GValue * value, GParamSpec * pspec);
109 static void gst_bs2b_finalize (GObject * object);
110
111 static GstFlowReturn gst_bs2b_transform_inplace (GstBaseTransform *
112 base_transform, GstBuffer * buffer);
113 static gboolean gst_bs2b_setup (GstAudioFilter * self,
114 const GstAudioInfo * audio_info);
115
116 static gchar **
gst_bs2b_get_preset_names(GstPreset * preset)117 gst_bs2b_get_preset_names (GstPreset * preset)
118 {
119 gchar **names;
120 gint i;
121
122 names = g_new (gchar *, 1 + G_N_ELEMENTS (presets));
123 for (i = 0; i < G_N_ELEMENTS (presets); i++) {
124 names[i] = g_strdup (presets[i].name);
125 }
126 names[i] = NULL;
127 return names;
128 }
129
130 static gchar **
gst_bs2b_get_property_names(GstPreset * preset)131 gst_bs2b_get_property_names (GstPreset * preset)
132 {
133 gchar **names = g_new (gchar *, 3);
134
135 names[0] = g_strdup ("fcut");
136 names[1] = g_strdup ("feed");
137 names[2] = NULL;
138 return names;
139 }
140
141 static gboolean
gst_bs2b_load_preset(GstPreset * preset,const gchar * name)142 gst_bs2b_load_preset (GstPreset * preset, const gchar * name)
143 {
144 GstBs2b *element = GST_BS2B (preset);
145 GObject *object = (GObject *) preset;
146 gint i;
147
148 for (i = 0; i < G_N_ELEMENTS (presets); i++) {
149 if (!g_strcmp0 (presets[i].name, name)) {
150 bs2b_set_level (element->bs2bdp, presets[i].preset);
151 g_object_notify_by_pspec (object, properties[PROP_FCUT]);
152 g_object_notify_by_pspec (object, properties[PROP_FEED]);
153 return TRUE;
154 }
155 }
156 return FALSE;
157 }
158
159 static gboolean
gst_bs2b_get_meta(GstPreset * preset,const gchar * name,const gchar * tag,gchar ** value)160 gst_bs2b_get_meta (GstPreset * preset, const gchar * name,
161 const gchar * tag, gchar ** value)
162 {
163 if (!g_strcmp0 (tag, "comment")) {
164 gint i;
165
166 for (i = 0; i < G_N_ELEMENTS (presets); i++) {
167 if (!g_strcmp0 (presets[i].name, name)) {
168 *value = g_strdup (presets[i].desc);
169 return TRUE;
170 }
171 }
172 }
173 *value = NULL;
174 return FALSE;
175 }
176
177 static void
gst_preset_interface_init(gpointer g_iface,gpointer iface_data)178 gst_preset_interface_init (gpointer g_iface, gpointer iface_data)
179 {
180 GstPresetInterface *iface = g_iface;
181
182 iface->get_preset_names = gst_bs2b_get_preset_names;
183 iface->get_property_names = gst_bs2b_get_property_names;
184
185 iface->load_preset = gst_bs2b_load_preset;
186 iface->save_preset = NULL;
187 iface->rename_preset = NULL;
188 iface->delete_preset = NULL;
189
190 iface->get_meta = gst_bs2b_get_meta;
191 iface->set_meta = NULL;
192 }
193
194 static void
gst_bs2b_class_init(GstBs2bClass * klass)195 gst_bs2b_class_init (GstBs2bClass * klass)
196 {
197 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
198 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
199 GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
200 GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS (klass);
201 GstCaps *caps;
202
203 gobject_class->set_property = gst_bs2b_set_property;
204 gobject_class->get_property = gst_bs2b_get_property;
205 gobject_class->finalize = gst_bs2b_finalize;
206
207 trans_class->transform_ip = gst_bs2b_transform_inplace;
208 trans_class->transform_ip_on_passthrough = FALSE;
209
210 filter_class->setup = gst_bs2b_setup;
211
212 properties[PROP_FCUT] = g_param_spec_int ("fcut", "Frequency cut",
213 "Low-pass filter cut frequency (Hz)",
214 BS2B_MINFCUT, BS2B_MAXFCUT, BS2B_DEFAULT_CLEVEL & 0xFFFF,
215 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS);
216
217 properties[PROP_FEED] =
218 g_param_spec_int ("feed", "Feed level", "Feed Level (dB/10)",
219 BS2B_MINFEED, BS2B_MAXFEED, BS2B_DEFAULT_CLEVEL >> 16,
220 G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS);
221
222 g_object_class_install_properties (gobject_class, PROP_LAST, properties);
223
224 gst_element_class_set_metadata (element_class,
225 "Crossfeed effect",
226 "Filter/Effect/Audio",
227 "Improve headphone listening of stereo audio records using the bs2b "
228 "library.", "Christoph Reiter <reiter.christoph@gmail.com>");
229
230 caps = gst_caps_from_string (PAD_CAPS);
231 gst_audio_filter_class_add_pad_templates (filter_class, caps);
232 gst_caps_unref (caps);
233 }
234
235 static void
gst_bs2b_init(GstBs2b * element)236 gst_bs2b_init (GstBs2b * element)
237 {
238 g_mutex_init (&element->bs2b_lock);
239 element->bs2bdp = bs2b_open ();
240 }
241
242 static gboolean
gst_bs2b_setup(GstAudioFilter * filter,const GstAudioInfo * audio_info)243 gst_bs2b_setup (GstAudioFilter * filter, const GstAudioInfo * audio_info)
244 {
245 GstBaseTransform *base_transform = GST_BASE_TRANSFORM (filter);
246 GstBs2b *element = GST_BS2B (filter);
247 gint channels = GST_AUDIO_INFO_CHANNELS (audio_info);
248
249 element->func = NULL;
250
251 if (channels == 1) {
252 gst_base_transform_set_passthrough (base_transform, TRUE);
253 return TRUE;
254 }
255
256 g_assert (channels == 2);
257 gst_base_transform_set_passthrough (base_transform, FALSE);
258
259 switch (GST_AUDIO_INFO_FORMAT (audio_info)) {
260 case GST_AUDIO_FORMAT_S8:
261 element->func = &bs2b_cross_feed_s8;
262 break;
263 case GST_AUDIO_FORMAT_U8:
264 element->func = &bs2b_cross_feed_u8;
265 break;
266 case GST_AUDIO_FORMAT_S16BE:
267 element->func = &bs2b_cross_feed_s16be;
268 break;
269 case GST_AUDIO_FORMAT_S16LE:
270 element->func = &bs2b_cross_feed_s16le;
271 break;
272 case GST_AUDIO_FORMAT_U16BE:
273 element->func = &bs2b_cross_feed_u16be;
274 break;
275 case GST_AUDIO_FORMAT_U16LE:
276 element->func = &bs2b_cross_feed_u16le;
277 break;
278 case GST_AUDIO_FORMAT_S24BE:
279 element->func = &bs2b_cross_feed_s24be;
280 break;
281 case GST_AUDIO_FORMAT_S24LE:
282 element->func = &bs2b_cross_feed_s24le;
283 break;
284 case GST_AUDIO_FORMAT_U24BE:
285 element->func = &bs2b_cross_feed_u24be;
286 break;
287 case GST_AUDIO_FORMAT_U24LE:
288 element->func = &bs2b_cross_feed_u24le;
289 break;
290 case GST_AUDIO_FORMAT_S32BE:
291 element->func = &bs2b_cross_feed_s32be;
292 break;
293 case GST_AUDIO_FORMAT_S32LE:
294 element->func = &bs2b_cross_feed_s32le;
295 break;
296 case GST_AUDIO_FORMAT_U32BE:
297 element->func = &bs2b_cross_feed_u32be;
298 break;
299 case GST_AUDIO_FORMAT_U32LE:
300 element->func = &bs2b_cross_feed_u32le;
301 break;
302 case GST_AUDIO_FORMAT_F32BE:
303 element->func = &bs2b_cross_feed_fbe;
304 break;
305 case GST_AUDIO_FORMAT_F32LE:
306 element->func = &bs2b_cross_feed_fle;
307 break;
308 case GST_AUDIO_FORMAT_F64BE:
309 element->func = &bs2b_cross_feed_dbe;
310 break;
311 case GST_AUDIO_FORMAT_F64LE:
312 element->func = &bs2b_cross_feed_dle;
313 break;
314 default:
315 return FALSE;
316 }
317
318 g_assert (element->func);
319 element->bytes_per_sample =
320 (GST_AUDIO_INFO_WIDTH (audio_info) * channels) / 8;
321
322 GST_BS2B_DP_LOCK (element);
323 bs2b_set_srate (element->bs2bdp, GST_AUDIO_INFO_RATE (audio_info));
324 GST_BS2B_DP_UNLOCK (element);
325
326 return TRUE;
327 }
328
329 static void
gst_bs2b_finalize(GObject * object)330 gst_bs2b_finalize (GObject * object)
331 {
332 GstBs2b *element = GST_BS2B (object);
333
334 bs2b_close (element->bs2bdp);
335 element->bs2bdp = NULL;
336
337 G_OBJECT_CLASS (gst_bs2b_parent_class)->finalize (object);
338 }
339
340 static GstFlowReturn
gst_bs2b_transform_inplace(GstBaseTransform * base_transform,GstBuffer * buffer)341 gst_bs2b_transform_inplace (GstBaseTransform * base_transform,
342 GstBuffer * buffer)
343 {
344 GstBs2b *element = GST_BS2B (base_transform);
345 GstMapInfo map_info;
346
347 if (!gst_buffer_map (buffer, &map_info, GST_MAP_READ | GST_MAP_WRITE))
348 return GST_FLOW_ERROR;
349
350 GST_BS2B_DP_LOCK (element);
351 if (GST_BUFFER_IS_DISCONT (buffer))
352 bs2b_clear (element->bs2bdp);
353 element->func (element->bs2bdp, map_info.data,
354 map_info.size / element->bytes_per_sample);
355 GST_BS2B_DP_UNLOCK (element);
356
357 gst_buffer_unmap (buffer, &map_info);
358
359 return GST_FLOW_OK;
360 }
361
362 static void
gst_bs2b_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)363 gst_bs2b_set_property (GObject * object, guint prop_id,
364 const GValue * value, GParamSpec * pspec)
365 {
366 GstBs2b *element = GST_BS2B (object);
367
368 switch (prop_id) {
369 case PROP_FCUT:
370 GST_BS2B_DP_LOCK (element);
371 bs2b_set_level_fcut (element->bs2bdp, g_value_get_int (value));
372 bs2b_clear (element->bs2bdp);
373 GST_BS2B_DP_UNLOCK (element);
374 break;
375 case PROP_FEED:
376 GST_BS2B_DP_LOCK (element);
377 bs2b_set_level_feed (element->bs2bdp, g_value_get_int (value));
378 bs2b_clear (element->bs2bdp);
379 GST_BS2B_DP_UNLOCK (element);
380 break;
381 default:
382 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
383 break;
384 }
385 }
386
387 static void
gst_bs2b_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)388 gst_bs2b_get_property (GObject * object, guint prop_id, GValue * value,
389 GParamSpec * pspec)
390 {
391 GstBs2b *element = GST_BS2B (object);
392
393 switch (prop_id) {
394 case PROP_FCUT:
395 GST_BS2B_DP_LOCK (element);
396 g_value_set_int (value, bs2b_get_level_fcut (element->bs2bdp));
397 GST_BS2B_DP_UNLOCK (element);
398 break;
399 case PROP_FEED:
400 GST_BS2B_DP_LOCK (element);
401 g_value_set_int (value, bs2b_get_level_feed (element->bs2bdp));
402 GST_BS2B_DP_UNLOCK (element);
403 break;
404 default:
405 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
406 break;
407 }
408 }
409
410 static gboolean
plugin_init(GstPlugin * plugin)411 plugin_init (GstPlugin * plugin)
412 {
413 return gst_element_register (plugin, "bs2b", GST_RANK_NONE, GST_TYPE_BS2B);
414 }
415
416 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
417 GST_VERSION_MINOR,
418 bs2b,
419 "Improve headphone listening of stereo audio records"
420 "using the bs2b library.",
421 plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
422