| /* 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 |
| * @title: 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 <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_LAST, |
| }; |
| |
| static GParamSpec *properties[PROP_LAST]; |
| |
| typedef struct |
| { |
| const gchar *name; |
| const gchar *desc; |
| gint preset; |
| } GstBs2bPreset; |
| |
| static const GstBs2bPreset presets[3] = { |
| { |
| "default", |
| "Closest to virtual speaker placement (30°, 3 meter) [700Hz, 4.5dB]", |
| BS2B_DEFAULT_CLEVEL}, |
| { |
| "cmoy", |
| "Close to Chu Moy's crossfeeder (popular) [700Hz, 6.0dB]", |
| BS2B_CMOY_CLEVEL}, |
| { |
| "jmeier", |
| "Close to Jan Meier's CORDA amplifiers (little change) [650Hz, 9.0dB]", |
| BS2B_JMEIER_CLEVEL} |
| }; |
| |
| static void gst_preset_interface_init (gpointer g_iface, gpointer iface_data); |
| |
| G_DEFINE_TYPE_WITH_CODE (GstBs2b, gst_bs2b, GST_TYPE_AUDIO_FILTER, |
| G_IMPLEMENT_INTERFACE (GST_TYPE_PRESET, gst_preset_interface_init)); |
| |
| 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 gchar ** |
| gst_bs2b_get_preset_names (GstPreset * preset) |
| { |
| gchar **names; |
| gint i; |
| |
| names = g_new (gchar *, 1 + G_N_ELEMENTS (presets)); |
| for (i = 0; i < G_N_ELEMENTS (presets); i++) { |
| names[i] = g_strdup (presets[i].name); |
| } |
| names[i] = NULL; |
| return names; |
| } |
| |
| static gchar ** |
| gst_bs2b_get_property_names (GstPreset * preset) |
| { |
| gchar **names = g_new (gchar *, 3); |
| |
| names[0] = g_strdup ("fcut"); |
| names[1] = g_strdup ("feed"); |
| names[2] = NULL; |
| return names; |
| } |
| |
| static gboolean |
| gst_bs2b_load_preset (GstPreset * preset, const gchar * name) |
| { |
| GstBs2b *element = GST_BS2B (preset); |
| GObject *object = (GObject *) preset; |
| gint i; |
| |
| for (i = 0; i < G_N_ELEMENTS (presets); i++) { |
| if (!g_strcmp0 (presets[i].name, name)) { |
| bs2b_set_level (element->bs2bdp, presets[i].preset); |
| g_object_notify_by_pspec (object, properties[PROP_FCUT]); |
| g_object_notify_by_pspec (object, properties[PROP_FEED]); |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| static gboolean |
| gst_bs2b_get_meta (GstPreset * preset, const gchar * name, |
| const gchar * tag, gchar ** value) |
| { |
| if (!g_strcmp0 (tag, "comment")) { |
| gint i; |
| |
| for (i = 0; i < G_N_ELEMENTS (presets); i++) { |
| if (!g_strcmp0 (presets[i].name, name)) { |
| *value = g_strdup (presets[i].desc); |
| return TRUE; |
| } |
| } |
| } |
| *value = NULL; |
| return FALSE; |
| } |
| |
| static void |
| gst_preset_interface_init (gpointer g_iface, gpointer iface_data) |
| { |
| GstPresetInterface *iface = g_iface; |
| |
| iface->get_preset_names = gst_bs2b_get_preset_names; |
| iface->get_property_names = gst_bs2b_get_property_names; |
| |
| iface->load_preset = gst_bs2b_load_preset; |
| iface->save_preset = NULL; |
| iface->rename_preset = NULL; |
| iface->delete_preset = NULL; |
| |
| iface->get_meta = gst_bs2b_get_meta; |
| iface->set_meta = NULL; |
| } |
| |
| 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); |
| |
| 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 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); |
| 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); |
| 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; |
| 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) |