diff options
author | Christoph Reiter <reiter.christoph@gmail.com> | 2015-01-10 21:41:12 +0100 |
---|---|---|
committer | Stefan Sauer <ensonic@users.sf.net> | 2015-01-15 19:59:03 +0100 |
commit | 75f8cca325065685bfd31d3907f4af051f373572 (patch) | |
tree | e83a0a7f4c34b015a6eed15bfdab7ab25497afab /ext/bs2b/gstbs2b.c | |
parent | efb74ca0df86954d2cbb51109e3bc214d814f874 (diff) | |
download | gstreamer-plugins-bad-75f8cca325065685bfd31d3907f4af051f373572.tar.gz |
bs2b: add new plugin (Effect/Audio, crossfeed)
https://bugzilla.gnome.org/show_bug.cgi?id=611689
Diffstat (limited to 'ext/bs2b/gstbs2b.c')
-rw-r--r-- | ext/bs2b/gstbs2b.c | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/ext/bs2b/gstbs2b.c b/ext/bs2b/gstbs2b.c new file mode 100644 index 000000000..cfa702d8b --- /dev/null +++ b/ext/bs2b/gstbs2b.c @@ -0,0 +1,415 @@ +/* GStreamer + * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu> + * Copyright (C) <2003> David Schleef <ds@schleef.org> + * Copyright (C) <2011,2014> Christoph Reiter <reiter.christoph@gmail.com> + * + * 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. + * + * <refsect2> + * <title>Example pipelines</title> + * |[ + * 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. + * </refsect2> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gst/gst.h> +#include <gst/audio/audio.h> +#include <gst/audio/gstaudiofilter.h> + +#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 <reiter.christoph@gmail.com>"); + + 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) |