• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 GST_ELEMENT_REGISTER_DEFINE (bs2b, "bs2b", GST_RANK_NONE, GST_TYPE_BS2B);
105 
106 static void gst_bs2b_set_property (GObject * object, guint prop_id,
107     const GValue * value, GParamSpec * pspec);
108 static void gst_bs2b_get_property (GObject * object, guint prop_id,
109     GValue * value, GParamSpec * pspec);
110 static void gst_bs2b_finalize (GObject * object);
111 
112 static GstFlowReturn gst_bs2b_transform_inplace (GstBaseTransform *
113     base_transform, GstBuffer * buffer);
114 static gboolean gst_bs2b_setup (GstAudioFilter * self,
115     const GstAudioInfo * audio_info);
116 
117 static gchar **
gst_bs2b_get_preset_names(GstPreset * preset)118 gst_bs2b_get_preset_names (GstPreset * preset)
119 {
120   gchar **names;
121   gint i;
122 
123   names = g_new (gchar *, 1 + G_N_ELEMENTS (presets));
124   for (i = 0; i < G_N_ELEMENTS (presets); i++) {
125     names[i] = g_strdup (presets[i].name);
126   }
127   names[i] = NULL;
128   return names;
129 }
130 
131 static gchar **
gst_bs2b_get_property_names(GstPreset * preset)132 gst_bs2b_get_property_names (GstPreset * preset)
133 {
134   gchar **names = g_new (gchar *, 3);
135 
136   names[0] = g_strdup ("fcut");
137   names[1] = g_strdup ("feed");
138   names[2] = NULL;
139   return names;
140 }
141 
142 static gboolean
gst_bs2b_load_preset(GstPreset * preset,const gchar * name)143 gst_bs2b_load_preset (GstPreset * preset, const gchar * name)
144 {
145   GstBs2b *element = GST_BS2B (preset);
146   GObject *object = (GObject *) preset;
147   gint i;
148 
149   for (i = 0; i < G_N_ELEMENTS (presets); i++) {
150     if (!g_strcmp0 (presets[i].name, name)) {
151       bs2b_set_level (element->bs2bdp, presets[i].preset);
152       g_object_notify_by_pspec (object, properties[PROP_FCUT]);
153       g_object_notify_by_pspec (object, properties[PROP_FEED]);
154       return TRUE;
155     }
156   }
157   return FALSE;
158 }
159 
160 static gboolean
gst_bs2b_get_meta(GstPreset * preset,const gchar * name,const gchar * tag,gchar ** value)161 gst_bs2b_get_meta (GstPreset * preset, const gchar * name,
162     const gchar * tag, gchar ** value)
163 {
164   if (!g_strcmp0 (tag, "comment")) {
165     gint i;
166 
167     for (i = 0; i < G_N_ELEMENTS (presets); i++) {
168       if (!g_strcmp0 (presets[i].name, name)) {
169         *value = g_strdup (presets[i].desc);
170         return TRUE;
171       }
172     }
173   }
174   *value = NULL;
175   return FALSE;
176 }
177 
178 static void
gst_preset_interface_init(gpointer g_iface,gpointer iface_data)179 gst_preset_interface_init (gpointer g_iface, gpointer iface_data)
180 {
181   GstPresetInterface *iface = g_iface;
182 
183   iface->get_preset_names = gst_bs2b_get_preset_names;
184   iface->get_property_names = gst_bs2b_get_property_names;
185 
186   iface->load_preset = gst_bs2b_load_preset;
187   iface->save_preset = NULL;
188   iface->rename_preset = NULL;
189   iface->delete_preset = NULL;
190 
191   iface->get_meta = gst_bs2b_get_meta;
192   iface->set_meta = NULL;
193 }
194 
195 static void
gst_bs2b_class_init(GstBs2bClass * klass)196 gst_bs2b_class_init (GstBs2bClass * klass)
197 {
198   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
199   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
200   GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
201   GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS (klass);
202   GstCaps *caps;
203 
204   gobject_class->set_property = gst_bs2b_set_property;
205   gobject_class->get_property = gst_bs2b_get_property;
206   gobject_class->finalize = gst_bs2b_finalize;
207 
208   trans_class->transform_ip = gst_bs2b_transform_inplace;
209   trans_class->transform_ip_on_passthrough = FALSE;
210 
211   filter_class->setup = gst_bs2b_setup;
212 
213   properties[PROP_FCUT] = g_param_spec_int ("fcut", "Frequency cut",
214       "Low-pass filter cut frequency (Hz)",
215       BS2B_MINFCUT, BS2B_MAXFCUT, BS2B_DEFAULT_CLEVEL & 0xFFFF,
216       G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS);
217 
218   properties[PROP_FEED] =
219       g_param_spec_int ("feed", "Feed level", "Feed Level (dB/10)",
220       BS2B_MINFEED, BS2B_MAXFEED, BS2B_DEFAULT_CLEVEL >> 16,
221       G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS);
222 
223   g_object_class_install_properties (gobject_class, PROP_LAST, properties);
224 
225   gst_element_class_set_metadata (element_class,
226       "Crossfeed effect",
227       "Filter/Effect/Audio",
228       "Improve headphone listening of stereo audio records using the bs2b "
229       "library.", "Christoph Reiter <reiter.christoph@gmail.com>");
230 
231   caps = gst_caps_from_string (PAD_CAPS);
232   gst_audio_filter_class_add_pad_templates (filter_class, caps);
233   gst_caps_unref (caps);
234 }
235 
236 static void
gst_bs2b_init(GstBs2b * element)237 gst_bs2b_init (GstBs2b * element)
238 {
239   g_mutex_init (&element->bs2b_lock);
240   element->bs2bdp = bs2b_open ();
241 }
242 
243 static gboolean
gst_bs2b_setup(GstAudioFilter * filter,const GstAudioInfo * audio_info)244 gst_bs2b_setup (GstAudioFilter * filter, const GstAudioInfo * audio_info)
245 {
246   GstBaseTransform *base_transform = GST_BASE_TRANSFORM (filter);
247   GstBs2b *element = GST_BS2B (filter);
248   gint channels = GST_AUDIO_INFO_CHANNELS (audio_info);
249 
250   element->func = NULL;
251 
252   if (channels == 1) {
253     gst_base_transform_set_passthrough (base_transform, TRUE);
254     return TRUE;
255   }
256 
257   g_assert (channels == 2);
258   gst_base_transform_set_passthrough (base_transform, FALSE);
259 
260   switch (GST_AUDIO_INFO_FORMAT (audio_info)) {
261     case GST_AUDIO_FORMAT_S8:
262       element->func = &bs2b_cross_feed_s8;
263       break;
264     case GST_AUDIO_FORMAT_U8:
265       element->func = &bs2b_cross_feed_u8;
266       break;
267     case GST_AUDIO_FORMAT_S16BE:
268       element->func = &bs2b_cross_feed_s16be;
269       break;
270     case GST_AUDIO_FORMAT_S16LE:
271       element->func = &bs2b_cross_feed_s16le;
272       break;
273     case GST_AUDIO_FORMAT_U16BE:
274       element->func = &bs2b_cross_feed_u16be;
275       break;
276     case GST_AUDIO_FORMAT_U16LE:
277       element->func = &bs2b_cross_feed_u16le;
278       break;
279     case GST_AUDIO_FORMAT_S24BE:
280       element->func = &bs2b_cross_feed_s24be;
281       break;
282     case GST_AUDIO_FORMAT_S24LE:
283       element->func = &bs2b_cross_feed_s24le;
284       break;
285     case GST_AUDIO_FORMAT_U24BE:
286       element->func = &bs2b_cross_feed_u24be;
287       break;
288     case GST_AUDIO_FORMAT_U24LE:
289       element->func = &bs2b_cross_feed_u24le;
290       break;
291     case GST_AUDIO_FORMAT_S32BE:
292       element->func = &bs2b_cross_feed_s32be;
293       break;
294     case GST_AUDIO_FORMAT_S32LE:
295       element->func = &bs2b_cross_feed_s32le;
296       break;
297     case GST_AUDIO_FORMAT_U32BE:
298       element->func = &bs2b_cross_feed_u32be;
299       break;
300     case GST_AUDIO_FORMAT_U32LE:
301       element->func = &bs2b_cross_feed_u32le;
302       break;
303     case GST_AUDIO_FORMAT_F32BE:
304       element->func = &bs2b_cross_feed_fbe;
305       break;
306     case GST_AUDIO_FORMAT_F32LE:
307       element->func = &bs2b_cross_feed_fle;
308       break;
309     case GST_AUDIO_FORMAT_F64BE:
310       element->func = &bs2b_cross_feed_dbe;
311       break;
312     case GST_AUDIO_FORMAT_F64LE:
313       element->func = &bs2b_cross_feed_dle;
314       break;
315     default:
316       return FALSE;
317   }
318 
319   g_assert (element->func);
320   element->bytes_per_sample =
321       (GST_AUDIO_INFO_WIDTH (audio_info) * channels) / 8;
322 
323   GST_BS2B_DP_LOCK (element);
324   bs2b_set_srate (element->bs2bdp, GST_AUDIO_INFO_RATE (audio_info));
325   GST_BS2B_DP_UNLOCK (element);
326 
327   return TRUE;
328 }
329 
330 static void
gst_bs2b_finalize(GObject * object)331 gst_bs2b_finalize (GObject * object)
332 {
333   GstBs2b *element = GST_BS2B (object);
334 
335   bs2b_close (element->bs2bdp);
336   element->bs2bdp = NULL;
337 
338   G_OBJECT_CLASS (gst_bs2b_parent_class)->finalize (object);
339 }
340 
341 static GstFlowReturn
gst_bs2b_transform_inplace(GstBaseTransform * base_transform,GstBuffer * buffer)342 gst_bs2b_transform_inplace (GstBaseTransform * base_transform,
343     GstBuffer * buffer)
344 {
345   GstBs2b *element = GST_BS2B (base_transform);
346   GstMapInfo map_info;
347 
348   if (!gst_buffer_map (buffer, &map_info, GST_MAP_READ | GST_MAP_WRITE))
349     return GST_FLOW_ERROR;
350 
351   GST_BS2B_DP_LOCK (element);
352   if (GST_BUFFER_IS_DISCONT (buffer))
353     bs2b_clear (element->bs2bdp);
354   element->func (element->bs2bdp, map_info.data,
355       map_info.size / element->bytes_per_sample);
356   GST_BS2B_DP_UNLOCK (element);
357 
358   gst_buffer_unmap (buffer, &map_info);
359 
360   return GST_FLOW_OK;
361 }
362 
363 static void
gst_bs2b_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)364 gst_bs2b_set_property (GObject * object, guint prop_id,
365     const GValue * value, GParamSpec * pspec)
366 {
367   GstBs2b *element = GST_BS2B (object);
368 
369   switch (prop_id) {
370     case PROP_FCUT:
371       GST_BS2B_DP_LOCK (element);
372       bs2b_set_level_fcut (element->bs2bdp, g_value_get_int (value));
373       bs2b_clear (element->bs2bdp);
374       GST_BS2B_DP_UNLOCK (element);
375       break;
376     case PROP_FEED:
377       GST_BS2B_DP_LOCK (element);
378       bs2b_set_level_feed (element->bs2bdp, g_value_get_int (value));
379       bs2b_clear (element->bs2bdp);
380       GST_BS2B_DP_UNLOCK (element);
381       break;
382     default:
383       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
384       break;
385   }
386 }
387 
388 static void
gst_bs2b_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)389 gst_bs2b_get_property (GObject * object, guint prop_id, GValue * value,
390     GParamSpec * pspec)
391 {
392   GstBs2b *element = GST_BS2B (object);
393 
394   switch (prop_id) {
395     case PROP_FCUT:
396       GST_BS2B_DP_LOCK (element);
397       g_value_set_int (value, bs2b_get_level_fcut (element->bs2bdp));
398       GST_BS2B_DP_UNLOCK (element);
399       break;
400     case PROP_FEED:
401       GST_BS2B_DP_LOCK (element);
402       g_value_set_int (value, bs2b_get_level_feed (element->bs2bdp));
403       GST_BS2B_DP_UNLOCK (element);
404       break;
405     default:
406       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
407       break;
408   }
409 }
410 
411 static gboolean
plugin_init(GstPlugin * plugin)412 plugin_init (GstPlugin * plugin)
413 {
414   return GST_ELEMENT_REGISTER (bs2b, plugin);
415 }
416 
417 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
418     GST_VERSION_MINOR,
419     bs2b,
420     "Improve headphone listening of stereo audio records"
421     "using the bs2b library.",
422     plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
423