diff --git a/configure.ac b/configure.ac index 71ca13c277..042b82e98c 100644 --- a/configure.ac +++ b/configure.ac @@ -1820,6 +1820,16 @@ AG_GST_CHECK_FEATURE(APEXSINK, [AirPort Express Wireless sink], apexsink, [ ]) ]) +dnl *** bs2b *** +translit(dnm, m, l) AM_CONDITIONAL(USE_BS2B, true) +AG_GST_CHECK_FEATURE(BS2B, [bs2b], bs2b, [ + PKG_CHECK_MODULES(BS2B, libbs2b >= 3.1.0, HAVE_BS2B="yes", [ + HAVE_BS2B="no" + ]) + AC_SUBST(BS2B_CFLAGS) + AC_SUBST(BS2B_LIBS) +]) + dnl *** BZ2 *** translit(dnm, m, l) AM_CONDITIONAL(USE_BZ2, true) AG_GST_CHECK_FEATURE(BZ2, [bz2 library], bz2, [ @@ -2998,6 +3008,7 @@ AM_CONDITIONAL(USE_ASSRENDER, false) AM_CONDITIONAL(USE_VOAMRWBENC, false) AM_CONDITIONAL(USE_VOAACENC, false) AM_CONDITIONAL(USE_APEXSINK, false) +AM_CONDITIONAL(USE_BS2B, false) AM_CONDITIONAL(USE_BZ2, false) AM_CONDITIONAL(USE_CHROMAPRINT, false) AM_CONDITIONAL(USE_CURL, false) @@ -3308,6 +3319,7 @@ ext/voamrwbenc/Makefile ext/voaacenc/Makefile ext/assrender/Makefile ext/apexsink/Makefile +ext/bs2b/Makefile ext/bz2/Makefile ext/chromaprint/Makefile ext/curl/Makefile diff --git a/ext/Makefile.am b/ext/Makefile.am index 3859cf7737..273758e7b4 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -22,6 +22,12 @@ endif AUDIOFILE_DIR= # endif +if USE_BS2B +BS2B_DIR=bs2b +else +BS2B_DIR= +endif + if USE_BZ2 BZ2_DIR=bz2 else @@ -418,6 +424,7 @@ SUBDIRS=\ $(VOAMRWBENC_DIR) \ $(APEXSINK_DIR) \ $(AUDIOFILE_DIR) \ + $(BS2B_DIR) \ $(BZ2_DIR) \ $(CHROMAPRINT_DIR) \ $(CURL_DIR) \ @@ -485,6 +492,7 @@ SUBDIRS=\ DIST_SUBDIRS = \ assrender \ apexsink \ + bs2b \ bz2 \ chromaprint \ curl \ diff --git a/ext/bs2b/Makefile.am b/ext/bs2b/Makefile.am new file mode 100644 index 0000000000..8516ec9dc9 --- /dev/null +++ b/ext/bs2b/Makefile.am @@ -0,0 +1,15 @@ +plugin_LTLIBRARIES = libgstbs2b.la + +libgstbs2b_la_SOURCES = gstbs2b.c gstbs2b.h + +libgstbs2b_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) \ + $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) \ + $(BS2B_CFLAGS) +libgstbs2b_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_API_VERSION) \ + $(GST_BASE_LIBS) $(GST_LIBS) \ + $(BS2B_LIBS) +libgstbs2b_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstbs2b_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +noinst_HEADERS = gstbs2b.h diff --git a/ext/bs2b/gstbs2b.c b/ext/bs2b/gstbs2b.c new file mode 100644 index 0000000000..cfa702d8b1 --- /dev/null +++ b/ext/bs2b/gstbs2b.c @@ -0,0 +1,415 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2003> David Schleef + * Copyright (C) <2011,2014> Christoph Reiter + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +/** + * SECTION:element-bs2b + * + * Improve headphone listening of stereo audio records using the bs2b library. + * It does so by mixing the left and right channel in a way that simulates + * a stereo speaker setup while using headphones. + * + * + * Example pipelines + * |[ + * 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. + * ]| Play two independent sine test sources and crossfeed them. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "gstbs2b.h" + +#define GST_BS2B_DP_LOCK(obj) g_mutex_lock (&obj->bs2b_lock) +#define GST_BS2B_DP_UNLOCK(obj) g_mutex_unlock (&obj->bs2b_lock) + +#define SUPPORTED_FORMAT \ + "(string) { S8, U8, S16LE, S16BE, U16LE, U16BE, S32LE, S32BE, U32LE, " \ + "U32BE, S24LE, S24BE, U24LE, U24BE, F32LE, F32BE, F64LE, F64BE }" + +#define SUPPORTED_RATE \ + "(int) [ " G_STRINGIFY (BS2B_MINSRATE) ", " G_STRINGIFY (BS2B_MAXSRATE) " ]" + +#define FRONT_L_FRONT_R "(bitmask) 0x3" + +#define PAD_CAPS \ + "audio/x-raw, " \ + "format = " SUPPORTED_FORMAT ", " \ + "rate = " SUPPORTED_RATE ", " \ + "channels = (int) 2, " \ + "channel-mask = " FRONT_L_FRONT_R ", " \ + "layout = (string) interleaved" \ + "; " \ + "audio/x-raw, " \ + "channels = (int) 1" \ + +enum +{ + PROP_FCUT = 1, + PROP_FEED, + PROP_PRESET, + PROP_LAST, +}; + +static GParamSpec *properties[PROP_LAST]; + +enum +{ + PRESET_DEFAULT, + PRESET_CMOY, + PRESET_JMEIER, + PRESET_NONE +}; + +G_DEFINE_TYPE (GstBs2b, gst_bs2b, GST_TYPE_AUDIO_FILTER); + +static GType gst_bs2b_preset_get_type (void); + +static void gst_bs2b_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_bs2b_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_bs2b_finalize (GObject * object); + +static GstFlowReturn gst_bs2b_transform_inplace (GstBaseTransform * + base_transform, GstBuffer * buffer); +static gboolean gst_bs2b_setup (GstAudioFilter * self, + const GstAudioInfo * audio_info); + + +static void +gst_bs2b_class_init (GstBs2bClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass); + GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS (klass); + GstCaps *caps; + + gobject_class->set_property = gst_bs2b_set_property; + gobject_class->get_property = gst_bs2b_get_property; + gobject_class->finalize = gst_bs2b_finalize; + + trans_class->transform_ip = gst_bs2b_transform_inplace; + trans_class->transform_ip_on_passthrough = FALSE; + + filter_class->setup = gst_bs2b_setup; + + properties[PROP_FCUT] = g_param_spec_int ("fcut", "Frequency cut", + "Low-pass filter cut frequency (Hz)", + BS2B_MINFCUT, BS2B_MAXFCUT, BS2B_DEFAULT_CLEVEL & 0xFFFF, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_FEED] = + g_param_spec_int ("feed", "Feed level", "Feed Level (dB/10)", + BS2B_MINFEED, BS2B_MAXFEED, BS2B_DEFAULT_CLEVEL >> 16, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_PRESET] = + g_param_spec_enum ("preset", "Preset", "Bs2b filter preset", + gst_bs2b_preset_get_type (), PRESET_DEFAULT, + G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, properties); + + gst_element_class_set_metadata (element_class, + "Crossfeed effect", + "Filter/Effect/Audio", + "Improve headphone listening of stereo audio records using the bs2b " + "library.", "Christoph Reiter "); + + caps = gst_caps_from_string (PAD_CAPS); + gst_audio_filter_class_add_pad_templates (filter_class, caps); + gst_caps_unref (caps); +} + +static void +gst_bs2b_init (GstBs2b * element) +{ + g_mutex_init (&element->bs2b_lock); + element->bs2bdp = bs2b_open (); +} + +static gboolean +gst_bs2b_setup (GstAudioFilter * filter, const GstAudioInfo * audio_info) +{ + GstBaseTransform *base_transform = GST_BASE_TRANSFORM (filter); + GstBs2b *element = GST_BS2B (filter); + gint channels = GST_AUDIO_INFO_CHANNELS (audio_info); + + element->func = NULL; + + if (channels == 1) { + gst_base_transform_set_passthrough (base_transform, TRUE); + return TRUE; + } + + g_assert (channels == 2); + gst_base_transform_set_passthrough (base_transform, FALSE); + + switch (GST_AUDIO_INFO_FORMAT (audio_info)) { + case GST_AUDIO_FORMAT_S8: + element->func = &bs2b_cross_feed_s8; + break; + case GST_AUDIO_FORMAT_U8: + element->func = &bs2b_cross_feed_u8; + break; + case GST_AUDIO_FORMAT_S16BE: + element->func = &bs2b_cross_feed_s16be; + break; + case GST_AUDIO_FORMAT_S16LE: + element->func = &bs2b_cross_feed_s16le; + break; + case GST_AUDIO_FORMAT_U16BE: + element->func = &bs2b_cross_feed_u16be; + break; + case GST_AUDIO_FORMAT_U16LE: + element->func = &bs2b_cross_feed_u16le; + break; + case GST_AUDIO_FORMAT_S24BE: + element->func = &bs2b_cross_feed_s24be; + break; + case GST_AUDIO_FORMAT_S24LE: + element->func = &bs2b_cross_feed_s24le; + break; + case GST_AUDIO_FORMAT_U24BE: + element->func = &bs2b_cross_feed_u24be; + break; + case GST_AUDIO_FORMAT_U24LE: + element->func = &bs2b_cross_feed_u24le; + break; + case GST_AUDIO_FORMAT_S32BE: + element->func = &bs2b_cross_feed_s32be; + break; + case GST_AUDIO_FORMAT_S32LE: + element->func = &bs2b_cross_feed_s32le; + break; + case GST_AUDIO_FORMAT_U32BE: + element->func = &bs2b_cross_feed_u32be; + break; + case GST_AUDIO_FORMAT_U32LE: + element->func = &bs2b_cross_feed_u32le; + break; + case GST_AUDIO_FORMAT_F32BE: + element->func = &bs2b_cross_feed_fbe; + break; + case GST_AUDIO_FORMAT_F32LE: + element->func = &bs2b_cross_feed_fle; + break; + case GST_AUDIO_FORMAT_F64BE: + element->func = &bs2b_cross_feed_dbe; + break; + case GST_AUDIO_FORMAT_F64LE: + element->func = &bs2b_cross_feed_dle; + break; + default: + return FALSE; + } + + g_assert (element->func); + element->bytes_per_sample = + (GST_AUDIO_INFO_WIDTH (audio_info) * channels) / 8; + + GST_BS2B_DP_LOCK (element); + bs2b_set_srate (element->bs2bdp, GST_AUDIO_INFO_RATE (audio_info)); + GST_BS2B_DP_UNLOCK (element); + + return TRUE; +} + +static void +gst_bs2b_finalize (GObject * object) +{ + GstBs2b *element = GST_BS2B (object); + + bs2b_close (element->bs2bdp); + element->bs2bdp = NULL; + + G_OBJECT_CLASS (gst_bs2b_parent_class)->finalize (object); +} + +static GstFlowReturn +gst_bs2b_transform_inplace (GstBaseTransform * base_transform, + GstBuffer * buffer) +{ + GstBs2b *element = GST_BS2B (base_transform); + GstMapInfo map_info; + + if (!gst_buffer_map (buffer, &map_info, GST_MAP_READ | GST_MAP_WRITE)) + return GST_FLOW_ERROR; + + GST_BS2B_DP_LOCK (element); + if (GST_BUFFER_IS_DISCONT (buffer)) + bs2b_clear (element->bs2bdp); + element->func (element->bs2bdp, map_info.data, + map_info.size / element->bytes_per_sample); + GST_BS2B_DP_UNLOCK (element); + + gst_buffer_unmap (buffer, &map_info); + + return GST_FLOW_OK; +} + +static GType +gst_bs2b_preset_get_type (void) +{ + static GType bs2b_preset_type = 0; + + if (!bs2b_preset_type) { + static GEnumValue types[] = { + { + PRESET_DEFAULT, + "Closest to virtual speaker placement (30°, 3 meter) [700Hz, 4.5dB]", + "default"}, + { + PRESET_CMOY, + "Close to Chu Moy's crossfeeder (popular) [700Hz, 6.0dB]", + "cmoy"}, + { + PRESET_JMEIER, + "Close to Jan Meier's CORDA amplifiers (little change) [650Hz, 9.0dB]", + "jmeier"}, + { + PRESET_NONE, + "No preset", + "none"}, + {0, NULL, NULL}, + }; + + bs2b_preset_type = g_enum_register_static ("GstBs2bPreset", types); + } + + return bs2b_preset_type; +} + +static void +gst_bs2b_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstBs2b *element = GST_BS2B (object); + + switch (prop_id) { + case PROP_FCUT: + GST_BS2B_DP_LOCK (element); + bs2b_set_level_fcut (element->bs2bdp, g_value_get_int (value)); + bs2b_clear (element->bs2bdp); + GST_BS2B_DP_UNLOCK (element); + g_object_notify_by_pspec (object, properties[PROP_PRESET]); + break; + case PROP_FEED: + GST_BS2B_DP_LOCK (element); + bs2b_set_level_feed (element->bs2bdp, g_value_get_int (value)); + bs2b_clear (element->bs2bdp); + GST_BS2B_DP_UNLOCK (element); + g_object_notify_by_pspec (object, properties[PROP_PRESET]); + break; + case PROP_PRESET: + switch (g_value_get_enum (value)) { + case PRESET_DEFAULT: + GST_BS2B_DP_LOCK (element); + bs2b_set_level (element->bs2bdp, BS2B_DEFAULT_CLEVEL); + bs2b_clear (element->bs2bdp); + GST_BS2B_DP_UNLOCK (element); + break; + case PRESET_CMOY: + GST_BS2B_DP_LOCK (element); + bs2b_set_level (element->bs2bdp, BS2B_CMOY_CLEVEL); + bs2b_clear (element->bs2bdp); + GST_BS2B_DP_UNLOCK (element); + break; + case PRESET_JMEIER: + GST_BS2B_DP_LOCK (element); + bs2b_set_level (element->bs2bdp, BS2B_JMEIER_CLEVEL); + bs2b_clear (element->bs2bdp); + GST_BS2B_DP_UNLOCK (element); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + return; + } + g_object_notify_by_pspec (object, properties[PROP_FCUT]); + g_object_notify_by_pspec (object, properties[PROP_FEED]); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_bs2b_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstBs2b *element = GST_BS2B (object); + + switch (prop_id) { + case PROP_FCUT: + GST_BS2B_DP_LOCK (element); + g_value_set_int (value, bs2b_get_level_fcut (element->bs2bdp)); + GST_BS2B_DP_UNLOCK (element); + break; + case PROP_FEED: + GST_BS2B_DP_LOCK (element); + g_value_set_int (value, bs2b_get_level_feed (element->bs2bdp)); + GST_BS2B_DP_UNLOCK (element); + break; + case PROP_PRESET: + GST_BS2B_DP_LOCK (element); + switch (bs2b_get_level (element->bs2bdp)) { + case BS2B_DEFAULT_CLEVEL: + g_value_set_enum (value, PRESET_DEFAULT); + break; + case BS2B_CMOY_CLEVEL: + g_value_set_enum (value, PRESET_CMOY); + break; + case BS2B_JMEIER_CLEVEL: + g_value_set_enum (value, PRESET_JMEIER); + break; + default: + g_value_set_enum (value, PRESET_NONE); + break; + } + GST_BS2B_DP_UNLOCK (element); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "bs2b", GST_RANK_NONE, GST_TYPE_BS2B); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + bs2b, + "Improve headphone listening of stereo audio records" + "using the bs2b library.", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/ext/bs2b/gstbs2b.h b/ext/bs2b/gstbs2b.h new file mode 100644 index 0000000000..8ec7f145c6 --- /dev/null +++ b/ext/bs2b/gstbs2b.h @@ -0,0 +1,63 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen + * Copyright (C) <2003> David Schleef + * Copyright (C) <2011,2014> Christoph Reiter + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __GST_BS2B_H__ +#define __GST_BS2B_H__ + +#include +#include +#include +#include + +G_BEGIN_DECLS +#define GST_TYPE_BS2B \ + (gst_bs2b_get_type()) +#define GST_BS2B(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_BS2B,GstBs2b)) +#define GST_BS2B_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_BS2B,GstBs2bClass)) +#define GST_IS_BS2B(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_BS2B)) +#define GST_IS_BS2B_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BS2B)) +typedef struct _GstBs2b GstBs2b; +typedef struct _GstBs2bClass GstBs2bClass; + + +struct _GstBs2b +{ + GstAudioFilter element; + + /*< private > */ + GMutex bs2b_lock; + t_bs2bdp bs2bdp; + void (*func) (); + guint bytes_per_sample; +}; + +struct _GstBs2bClass +{ + GstAudioFilterClass parent_class; +}; + +GType gst_bs2b_get_type (void); + +G_END_DECLS +#endif /* __GST_BS2B_H__ */