diff options
Diffstat (limited to 'sys/oss4/oss4-mixer.c')
-rw-r--r-- | sys/oss4/oss4-mixer.c | 1774 |
1 files changed, 1774 insertions, 0 deletions
diff --git a/sys/oss4/oss4-mixer.c b/sys/oss4/oss4-mixer.c new file mode 100644 index 00000000..62e271e5 --- /dev/null +++ b/sys/oss4/oss4-mixer.c @@ -0,0 +1,1774 @@ +/* GStreamer OSS4 mixer implementation + * Copyright (C) 2007-2008 Tim-Philipp Müller <tim centricular net> + * + * 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 mixer library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:element-oss4mixer + * @short_description: element to control sound input and output levels with OSS4 + * + * <refsect2> + * <para> + * This element lets you adjust sound input and output levels with the + * Open Sound System (OSS) version 4. It supports the GstMixer interface, which + * can be used to obtain a list of available mixer tracks. Set the mixer + * element to READY state before using the GstMixer interface on it. + * </para> + * <title>Example pipelines</title> + * <para> + * oss4mixer can't be used in a sensible way in gst-launch. + * </para> + * </refsect2> + * + * Since: 0.10.7 + */ + +/* Note: ioctl calls on the same open mixer device are serialised via + * the object lock to make sure we don't do concurrent ioctls from two + * different threads (e.g. app thread and mixer watch thread), since that + * will probably confuse OSS. */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/ioctl.h> + +#include <gst/interfaces/mixer.h> +#include <gst/gst-i18n-plugin.h> + +#include <glib/gprintf.h> + +#define NO_LEGACY_MIXER + +#include "oss4-audio.h" +#include "oss4-mixer.h" +#include "oss4-mixer-enum.h" +#include "oss4-mixer-slider.h" +#include "oss4-mixer-switch.h" +#include "oss4-property-probe.h" +#include "oss4-soundcard.h" + +#define GST_OSS4_MIXER_WATCH_INTERVAL 500 /* in millisecs, ie. 0.5s */ + +GST_DEBUG_CATEGORY_EXTERN (oss4mixer_debug); +#define GST_CAT_DEFAULT oss4mixer_debug + +#define DEFAULT_DEVICE NULL +#define DEFAULT_DEVICE_NAME NULL + +enum +{ + PROP_0, + PROP_DEVICE, + PROP_DEVICE_NAME +}; + +static void gst_oss4_mixer_init_interfaces (GType type); + +GST_BOILERPLATE_FULL (GstOss4Mixer, gst_oss4_mixer, GstElement, + GST_TYPE_ELEMENT, gst_oss4_mixer_init_interfaces); + +static GstStateChangeReturn gst_oss4_mixer_change_state (GstElement * + element, GstStateChange transition); + +static void gst_oss4_mixer_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_oss4_mixer_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_oss4_mixer_finalize (GObject * object); + +static gboolean gst_oss4_mixer_open (GstOss4Mixer * mixer, + gboolean silent_errors); +static void gst_oss4_mixer_close (GstOss4Mixer * mixer); + +static gboolean gst_oss4_mixer_enum_control_update_enum_list (GstOss4Mixer * m, + GstOss4MixerControl * mc); + +#ifndef GST_DISABLE_DEBUG +static const gchar *mixer_ext_type_get_name (gint type); +static const gchar *mixer_ext_flags_get_string (gint flags); +#endif + +static void +gst_oss4_mixer_base_init (gpointer klass) +{ + gst_element_class_set_details_simple (GST_ELEMENT_CLASS (klass), + "OSS v4 Audio Mixer", "Generic/Audio", + "Control sound input and output levels with OSS4", + "Tim-Philipp Müller <tim centricular net>"); +} + +static void +gst_oss4_mixer_class_init (GstOss4MixerClass * klass) +{ + GstElementClass *element_class; + GObjectClass *gobject_class; + + element_class = (GstElementClass *) klass; + gobject_class = (GObjectClass *) klass; + + gobject_class->finalize = gst_oss4_mixer_finalize; + gobject_class->set_property = gst_oss4_mixer_set_property; + gobject_class->get_property = gst_oss4_mixer_get_property; + + /** + * GstOss4Mixer:device + * + * OSS4 mixer device (e.g. /dev/oss/hdaudio0/mix0 or /dev/mixerN) + * + **/ + g_object_class_install_property (gobject_class, PROP_DEVICE, + g_param_spec_string ("device", "Device", + "OSS mixer device (e.g. /dev/oss/hdaudio0/mix0 or /dev/mixerN) " + "(NULL = use first mixer device found)", DEFAULT_DEVICE, + G_PARAM_READWRITE)); + + /** + * GstOss4Mixer:device-name + * + * Human-readable name of the sound device. May be NULL if the device is + * not open (ie. when the mixer is in NULL state) + * + **/ + g_object_class_install_property (gobject_class, PROP_DEVICE_NAME, + g_param_spec_string ("device-name", "Device name", + "Human-readable name of the sound device", DEFAULT_DEVICE_NAME, + G_PARAM_READABLE)); + + element_class->change_state = GST_DEBUG_FUNCPTR (gst_oss4_mixer_change_state); +} + +static void +gst_oss4_mixer_finalize (GObject * obj) +{ + GstOss4Mixer *mixer = GST_OSS4_MIXER (obj); + + g_free (mixer->device); + + G_OBJECT_CLASS (parent_class)->finalize (obj); +} + +static void +gst_oss4_mixer_reset (GstOss4Mixer * mixer) +{ + mixer->fd = -1; + mixer->need_update = TRUE; + memset (&mixer->last_mixext, 0, sizeof (oss_mixext)); +} + +static void +gst_oss4_mixer_init (GstOss4Mixer * mixer, GstOss4MixerClass * g_class) +{ + mixer->device = g_strdup (DEFAULT_DEVICE); + mixer->device_name = NULL; + + gst_oss4_mixer_reset (mixer); +} + +static void +gst_oss4_mixer_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstOss4Mixer *mixer = GST_OSS4_MIXER (object); + + switch (prop_id) { + case PROP_DEVICE: + GST_OBJECT_LOCK (mixer); + if (!GST_OSS4_MIXER_IS_OPEN (mixer)) { + g_free (mixer->device); + mixer->device = g_value_dup_string (value); + /* unset any cached device-name */ + g_free (mixer->device_name); + mixer->device_name = NULL; + } else { + g_warning ("%s: can't change \"device\" property while mixer is open", + GST_OBJECT_NAME (mixer)); + } + GST_OBJECT_UNLOCK (mixer); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_oss4_mixer_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstOss4Mixer *mixer = GST_OSS4_MIXER (object); + + switch (prop_id) { + case PROP_DEVICE: + GST_OBJECT_LOCK (mixer); + g_value_set_string (value, mixer->device); + GST_OBJECT_UNLOCK (mixer); + break; + case PROP_DEVICE_NAME: + GST_OBJECT_LOCK (mixer); + /* If device is set, try to retrieve the name even if we're not open */ + if (mixer->fd == -1 && mixer->device != NULL) { + if (gst_oss4_mixer_open (mixer, TRUE)) { + g_value_set_string (value, mixer->device_name); + gst_oss4_mixer_close (mixer); + } else { + g_value_set_string (value, mixer->device_name); + } + } else { + g_value_set_string (value, mixer->device_name); + } + GST_OBJECT_UNLOCK (mixer); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_oss4_mixer_open (GstOss4Mixer * mixer, gboolean silent_errors) +{ + struct oss_mixerinfo mi = { 0, }; + gchar *device; + + g_return_val_if_fail (!GST_OSS4_MIXER_IS_OPEN (mixer), FALSE); + + if (mixer->device) + device = g_strdup (mixer->device); + else + device = gst_oss4_audio_find_device (GST_OBJECT_CAST (mixer)); + + /* desperate times, desperate measures */ + if (device == NULL) + device = g_strdup ("/dev/mixer"); + + GST_INFO_OBJECT (mixer, "Trying to open OSS4 mixer device '%s'", device); + + mixer->fd = open (device, O_RDWR, 0); + if (mixer->fd < 0) + goto open_failed; + + /* Make sure it's OSS4. If it's old OSS, let the old ossmixer handle it */ + if (!gst_oss4_audio_check_version (GST_OBJECT (mixer), mixer->fd)) + goto legacy_oss; + + GST_INFO_OBJECT (mixer, "Opened mixer device '%s', which is mixer %d", + device, mi.dev); + + /* Get device name for currently open fd */ + mi.dev = -1; + if (ioctl (mixer->fd, SNDCTL_MIXERINFO, &mi) == 0) { + mixer->modify_counter = mi.modify_counter; + if (mi.name[0] != '\0') { + mixer->device_name = g_strdup (mi.name); + } + } else { + mixer->modify_counter = 0; + } + + if (mixer->device_name == NULL) { + mixer->device_name = g_strdup ("Unknown"); + } + GST_INFO_OBJECT (mixer, "device name = '%s'", mixer->device_name); + + mixer->open_device = device; + + return TRUE; + + /* ERRORS */ +open_failed: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (mixer, RESOURCE, OPEN_READ_WRITE, + (_("Could not open audio device for mixer control handling.")), + GST_ERROR_SYSTEM); + } + g_free (device); + return FALSE; + } +legacy_oss: + { + gst_oss4_mixer_close (mixer); + if (!silent_errors) { + GST_ELEMENT_ERROR (mixer, RESOURCE, OPEN_READ_WRITE, + (_("Could not open audio device for mixer control handling." + "This version of the Open Sound System is not supported by this " + "element.")), ("Try the 'ossmixer' element instead")); + } + g_free (device); + return FALSE; + } +} + +static void +gst_oss4_mixer_control_free (GstOss4MixerControl * mc) +{ + g_list_free (mc->children); + g_list_free (mc->mute_group); + g_free (mc->enum_vals); + memset (mc, 0, sizeof (GstOss4MixerControl)); + g_free (mc); +} + +static void +gst_oss4_mixer_free_tracks (GstOss4Mixer * mixer) +{ + g_list_foreach (mixer->tracks, (GFunc) g_object_unref, NULL); + g_list_free (mixer->tracks); + mixer->tracks = NULL; + + g_list_foreach (mixer->controls, (GFunc) gst_oss4_mixer_control_free, NULL); + g_list_free (mixer->controls); + mixer->controls = NULL; +} + +static void +gst_oss4_mixer_close (GstOss4Mixer * mixer) +{ + g_free (mixer->device_name); + mixer->device_name = NULL; + + g_free (mixer->open_device); + mixer->open_device = NULL; + + gst_oss4_mixer_free_tracks (mixer); + + if (mixer->fd != -1) { + close (mixer->fd); + mixer->fd = -1; + } + + gst_oss4_mixer_reset (mixer); +} + +static void +gst_oss4_mixer_watch_process_changes (GstOss4Mixer * mixer) +{ + GList *c, *t, *tracks = NULL; + + GST_INFO_OBJECT (mixer, "mixer interface or control changed"); + + /* this is all with the mixer object lock held */ + + /* we go through the list backwards so we can bail out faster when the entire + * interface needs to be rebuilt */ + for (c = g_list_last (mixer->controls); c != NULL; c = c->prev) { + GstOss4MixerControl *mc = c->data; + oss_mixer_value ossval = { 0, }; + + mc->changed = FALSE; + mc->list_changed = FALSE; + + /* not interested in controls we don't expose in the mixer interface */ + if (!mc->used) + continue; + + /* don't try to read a value from controls that don't have one */ + if (mc->mixext.type == MIXT_DEVROOT || mc->mixext.type == MIXT_GROUP) + continue; + + /* is this an enum control whose list may change? */ + if (mc->mixext.type == MIXT_ENUM && mc->enum_version != 0) { + if (gst_oss4_mixer_enum_control_update_enum_list (mixer, mc)) + mc->list_changed = TRUE; + } + + ossval.dev = mc->mixext.dev; + ossval.ctrl = mc->mixext.ctrl; + ossval.timestamp = mc->mixext.timestamp; + + if (ioctl (mixer->fd, SNDCTL_MIX_READ, &ossval) == -1) { + if (errno == EIDRM || errno == EFAULT) { + GST_DEBUG ("%s has disappeared", mc->mixext.extname); + goto mixer_changed; + } + GST_WARNING_OBJECT (mixer, "MIX_READ failed: %s", g_strerror (errno)); + /* just ignore, move on to next one */ + continue; + } + + if (ossval.value == mc->last_val) { /* no change */ + /* GST_LOG_OBJECT (mixer, "%s hasn't changed", mc->mixext.extname); */ + continue; + } + + mc->last_val = ossval.value; + GST_LOG_OBJECT (mixer, "%s changed value to %u 0x%08x", + mc->mixext.extname, ossval.value, ossval.value); + mc->changed = TRUE; + } + + /* copy list and take track refs, so we can safely drop the object lock, + * which we need to do to be able to post messages on the bus */ + tracks = g_list_copy (mixer->tracks); + g_list_foreach (tracks, (GFunc) g_object_ref, NULL); + + GST_OBJECT_UNLOCK (mixer); + + /* since we don't know (or want to know exactly) which controls belong to + * which track, we just go through the tracks one-by-one now and make them + * check themselves if any of their controls have changed and which messages + * to post on the bus as a result */ + for (t = tracks; t != NULL; t = t->next) { + GstMixerTrack *track = t->data; + + if (GST_IS_OSS4_MIXER_SLIDER (track)) { + gst_oss4_mixer_slider_process_change_unlocked (track); + } else if (GST_IS_OSS4_MIXER_SWITCH (track)) { + gst_oss4_mixer_switch_process_change_unlocked (track); + } else if (GST_IS_OSS4_MIXER_ENUM (track)) { + gst_oss4_mixer_enum_process_change_unlocked (track); + } + + g_object_unref (track); + } + g_list_free (tracks); + + GST_OBJECT_LOCK (mixer); + return; + +mixer_changed: + { + GST_OBJECT_UNLOCK (mixer); + gst_mixer_mixer_changed (GST_MIXER (mixer)); + GST_OBJECT_LOCK (mixer); + return; + } +} + +/* This thread watches the mixer for changes in a somewhat inefficient way + * (running an ioctl every half second or so). This is still better and + * cheaper than apps polling all tracks for changes a few times a second + * though. Needs more thought. There's probably (hopefully) a way to get + * notifications via the fd directly somehow. */ +static gpointer +gst_oss4_mixer_watch_thread (gpointer thread_data) +{ + GstOss4Mixer *mixer = GST_OSS4_MIXER_CAST (thread_data); + + GST_DEBUG_OBJECT (mixer, "watch thread running"); + + GST_OBJECT_LOCK (mixer); + while (!mixer->watch_shutdown) { + oss_mixerinfo mi = { 0, }; + GTimeVal tv; + + mi.dev = -1; + if (ioctl (mixer->fd, SNDCTL_MIXERINFO, &mi) == 0) { + if (mixer->modify_counter != mi.modify_counter) { + /* GST_LOG ("processing changes"); */ + gst_oss4_mixer_watch_process_changes (mixer); + mixer->modify_counter = mi.modify_counter; + } else { + /* GST_LOG ("no changes"); */ + } + } else { + GST_WARNING_OBJECT (mixer, "MIXERINFO failed: %s", g_strerror (errno)); + } + + /* we could move the _get_current_time out of the loop and just do the + * add in ever iteration, which would be less exact, but who cares */ + g_get_current_time (&tv); + g_time_val_add (&tv, GST_OSS4_MIXER_WATCH_INTERVAL * 1000); + g_cond_timed_wait (mixer->watch_cond, GST_OBJECT_GET_LOCK (mixer), &tv); + } + GST_OBJECT_UNLOCK (mixer); + + GST_DEBUG_OBJECT (mixer, "watch thread done"); + gst_object_unref (mixer); + return NULL; +} + +/* call with object lock held */ +static void +gst_oss4_mixer_wake_up_watch_task (GstOss4Mixer * mixer) +{ + GST_LOG_OBJECT (mixer, "signalling watch thread to wake up"); + g_cond_signal (mixer->watch_cond); +} + +static void +gst_oss4_mixer_stop_watch_task (GstOss4Mixer * mixer) +{ + if (mixer->watch_thread) { + GST_OBJECT_LOCK (mixer); + mixer->watch_shutdown = TRUE; + GST_LOG_OBJECT (mixer, "signalling watch thread to stop"); + g_cond_signal (mixer->watch_cond); + GST_OBJECT_UNLOCK (mixer); + GST_LOG_OBJECT (mixer, "waiting for watch thread to join"); + g_thread_join (mixer->watch_thread); + GST_DEBUG_OBJECT (mixer, "watch thread stopped"); + mixer->watch_thread = NULL; + } + + if (mixer->watch_cond) { + g_cond_free (mixer->watch_cond); + mixer->watch_cond = NULL; + } +} + +static void +gst_oss4_mixer_start_watch_task (GstOss4Mixer * mixer) +{ + GError *err = NULL; + + mixer->watch_cond = g_cond_new (); + mixer->watch_shutdown = FALSE; + + mixer->watch_thread = g_thread_create (gst_oss4_mixer_watch_thread, + gst_object_ref (mixer), TRUE, &err); + + if (mixer->watch_thread == NULL) { + GST_ERROR_OBJECT (mixer, "Could not create watch thread: %s", err->message); + g_cond_free (mixer->watch_cond); + mixer->watch_cond = NULL; + g_error_free (err); + } +} + +static GstStateChangeReturn +gst_oss4_mixer_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + GstOss4Mixer *mixer = GST_OSS4_MIXER (element); + + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + if (!gst_oss4_mixer_open (mixer, FALSE)) + return GST_STATE_CHANGE_FAILURE; + gst_oss4_mixer_start_watch_task (mixer); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret == GST_STATE_CHANGE_FAILURE) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_NULL: + gst_oss4_mixer_stop_watch_task (mixer); + gst_oss4_mixer_close (mixer); + break; + default: + break; + } + + return ret; +} + +/* === GstMixer interface === */ + +static inline gboolean +gst_oss4_mixer_contains_track (GstMixer * mixer, GstMixerTrack * track) +{ + return (g_list_find (GST_OSS4_MIXER (mixer)->tracks, track) != NULL); +} + +static inline gboolean +gst_oss4_mixer_contains_options (GstMixer * mixer, GstMixerOptions * options) +{ + return (g_list_find (GST_OSS4_MIXER (mixer)->tracks, options) != NULL); +} + +static void +gst_oss4_mixer_post_mixer_changed_msg (GstOss4Mixer * mixer) +{ + /* only post mixer-changed message once */ + if (!mixer->need_update) { + gst_mixer_mixer_changed (GST_MIXER (mixer)); + mixer->need_update = TRUE; + } +} + +/* call with mixer object lock held to serialise ioctl */ +gboolean +gst_oss4_mixer_get_control_val (GstOss4Mixer * mixer, GstOss4MixerControl * mc, + int *val) +{ + oss_mixer_value ossval = { 0, }; + + if (GST_OBJECT_TRYLOCK (mixer)) { + GST_ERROR ("must be called with mixer lock held"); + GST_OBJECT_UNLOCK (mixer); + } + + ossval.dev = mc->mixext.dev; + ossval.ctrl = mc->mixext.ctrl; + ossval.timestamp = mc->mixext.timestamp; + + if (ioctl (mixer->fd, SNDCTL_MIX_READ, &ossval) == -1) { + if (errno == EIDRM) { + GST_DEBUG_OBJECT (mixer, "MIX_READ failed: mixer interface has changed"); + gst_oss4_mixer_post_mixer_changed_msg (mixer); + } else { + GST_WARNING_OBJECT (mixer, "MIX_READ failed: %s", g_strerror (errno)); + } + *val = 0; + mc->last_val = 0; + return FALSE; + } + + *val = ossval.value; + mc->last_val = ossval.value; + GST_LOG_OBJECT (mixer, "got value 0x%08x from %s)", *val, mc->mixext.extname); + return TRUE; +} + +/* call with mixer object lock held to serialise ioctl */ +gboolean +gst_oss4_mixer_set_control_val (GstOss4Mixer * mixer, GstOss4MixerControl * mc, + int val) +{ + oss_mixer_value ossval = { 0, }; + + ossval.dev = mc->mixext.dev; + ossval.ctrl = mc->mixext.ctrl; + ossval.timestamp = mc->mixext.timestamp; + ossval.value = val; + + if (GST_OBJECT_TRYLOCK (mixer)) { + GST_ERROR ("must be called with mixer lock held"); + GST_OBJECT_UNLOCK (mixer); + } + + if (ioctl (mixer->fd, SNDCTL_MIX_WRITE, &ossval) == -1) { + if (errno == EIDRM) { + GST_LOG_OBJECT (mixer, "MIX_WRITE failed: mixer interface has changed"); + gst_oss4_mixer_post_mixer_changed_msg (mixer); + } else { + GST_WARNING_OBJECT (mixer, "MIX_WRITE failed: %s", g_strerror (errno)); + } + return FALSE; + } + + mc->last_val = val; + GST_LOG_OBJECT (mixer, "set value 0x%08x on %s", val, mc->mixext.extname); + return TRUE; +} + +static gchar * +gst_oss4_mixer_control_get_pretty_name (GstOss4MixerControl * mc) +{ + gchar *name; + +#if 0 + const gchar *name, *u; + + /* "The id field is the original name given by the driver when it called + * mixer_ext_create_control. This name can be used by fully featured GUI + * mixers. However this name should be downshifted and cut before the last + * underscore ("_") to get the proper name. For example mixer control name + * created as "MYDRV_MAINVOL" will become just "mainvol" after this + * transformation." */ + name = mc->mixext.extname; + u = MAX (strrchr (name, '_'), strrchr (name, '.')); + if (u != NULL) + name = u + 1; + + /* maybe capitalize the first letter? */ + return g_ascii_strdown (name, -1); +#endif + /* the .id thing doesn't really seem to work right, ie. for some sliders + * it's just '-' so you have to use the name of the parent control etc. + * let's not use it for now, much too painful. */ + if (g_str_has_prefix (mc->mixext.extname, "misc.")) + name = g_strdup (mc->mixext.extname + 5); + else + name = g_strdup (mc->mixext.extname); + /* chop off trailing digit (only one for now) */ + if (strlen (name) > 0 && g_ascii_isdigit (name[strlen (name) - 1])) + name[strlen (name) - 1] = '\0'; + g_strdelimit (name, ".", ' '); + return name; +} + +#if 0 +/* FIXME: translations for common option strings */ +static struct +{ + const gchar oss_name[32]; + const gchar *label; +} option_labels[] = { + { + "Fast", N_("Fast")}, { + "Low", N_("Low")}, { + "Medium", N_("Medium")}, { + "High", N_("High")}, { + "High+", N_("Very high")}, { + "Production", N_("Production")}, { + "OFF", N_("Off")}, { + "ON", N_("On")}, { + "Stereo", N_("Stereo")}, { + "Multich", N_("Surround sound")}, { + "input-mix", N_("Input mix")}, { + "front", N_("Front")}, { + "rear", N_("Rear")}, { + "side", N_("Side")}, { + "center/LFE", N_("Center / LFE")}, { + "mic", N_("Microphone")}, { + "fp-mic", N_("Front panel microphone")}, { + "input", N_("Input")}, { + "linein", N_("Line-in")}, { + "pcm1", N_("PCM 1")}, { + "pcm2", N_("PCM 2")}, { + "pcm3", N_("PCM 3")}, { +"pcm4", N_("PCM 4")},}; +#endif + +/* these translations are a bit ad-hoc and horribly incomplete; it's not + * really going to work this way with all the different chipsets and drivers */ +static struct +{ + const gchar oss_name[32]; + const gchar *label; +} labels[] = { + /* connectors (e.g. hdaudio) */ + { + "jack.green", N_("Green connector")}, { + "jack.fp-green", N_("Green front panel connector")}, { + "jack.pink", N_("Pink connector")}, { + "jack.fp-pink", N_("Pink front panel connector")}, { + "jack.blue", N_("Blue connector")}, { + "jack.fp-blue", N_("Blue front panel connector")}, { + "jack.orange", N_("Orange connector")}, { + "jack.fp-orange", N_("Orange front panel connector")}, { + "jack.black", N_("Black connector")}, { + "jack.fp-black", N_("Black front panel connector")}, { + "jack.gray", N_("Gray connector")}, { + "jack.fp-gray", N_("Gray front panel connector")}, { + "jack.white", N_("White connector")}, { + "jack.fp-white", N_("White front panel connector")}, { + "jack.red", N_("Red connector")}, { + "jack.fp-red", N_("Red front panel connector")}, { + "jack.yellow", N_("Yellow connector")}, { + "jack.fp-yellow", N_("Yellow front panel connector")}, + /* connector functions (e.g. hdaudio) */ + { + "jack.green.mode", N_("Green connector function")}, { + "jack.fp-green.mode", N_("Green front panel connector function")}, { + "jack.pink.mode", N_("Pink connector function")}, { + "jack.fp-pink.mode", N_("Pink front panel connector function")}, { + "jack.blue.mode", N_("Blue connector function")}, { + "jack.fp-blue.mode", N_("Blue front panel connector function")}, { + "jack.orange.mode", N_("Orange connector function")}, { + "jack.fp-orange.mode", N_("Orange front panel connector function")}, { + "jack.black.mode", N_("Black connector function")}, { + "jack.fp-black.mode", N_("Black front panel connector function")}, { + "jack.gray.mode", N_("Gray connector function")}, { + "jack.fp-gray.mode", N_("Gray front panel connector function")}, { + "jack.white.mode", N_("White connector function")}, { + "jack.fp-white.mode", N_("White front panel connector function")}, { + "jack.red.mode", N_("Red connector function")}, { + "jack.fp-red.mode", N_("Red front panel connector function")}, { + "jack.yellow.mode", N_("Yellow connector function")}, { + "jack.fp-yellow.mode", N_("Yellow front panel connector function")}, + /* other */ + { + "misc.mic", N_("Microphone")}, { + "misc.fp-mic", N_("Front panel microphone")}, { + "misc.linein", N_("Line-in")}, { + "misc.fp-linein", N_("Front panel line-in")}, { + "misc.headphone", N_("Headphones")}, { + "misc.fp-headphone", N_("Front panel headphones")}, { + "misc.front", N_("Front")}, { + "misc.rear", N_("Rear")}, { + "misc.side", N_("Side")}, { + "misc.center/lfe", N_("Center / LFE")}, { + "misc.pcm", N_("PCM")}, { + "misc.input-mix", N_("Input mix")} + /* FIXME translate Audigy NX USB labels) */ +/* + { "rec.src", N_("Record Source") }, + { "output.mute", N_("Mute output") } + headph (Controller group) + headph.src (Enumeration control) + headph.mute (On/Off switch) + digital2 (Controller group) + digital2.src (Enumeration control) + digital2.mute (On/Off switch) + digital (Controller group) + digital.mute1 (On/Off switch) + digital.vol (Controller group) + digital.vol.front (Stereo slider (0-255)) + digital.vol.surr (Stereo slider (0-255)) + digital.vol.c/l (Stereo slider (0-255)) + digital.vol.center (Stereo slider (0-255)) + digital.mute2 (On/Off switch) + digital.vol (Stereo slider (0-255)) + line (Controller group) + line.mute (On/Off switch) + line.vol (Stereo slider (0-255)) + play-altset (Enumeration control) + rec-altset (Enumeration control) +*/ +}; + +/* Decent i18n is pretty much impossible with OSS's way of providing us with + * mixer labels (and the fact that they are pretty much random), but that + * doesn't mean we shouldn't at least try. */ +const gchar * +gst_oss4_mixer_control_get_translated_name (GstOss4MixerControl * mc) +{ + gchar name[33] = { 0, }; + char vmix_str[32] = { '\0', }; + int dummy, i; + + /* main virtual mixer controls (we hide the stream volumes) */ + if (sscanf (mc->mixext.extname, "vmix%d-%32c", &dummy, vmix_str) == 2) { + if (strcmp (vmix_str, "src") == 0) + return _("Virtual mixer input"); + else if (strcmp (vmix_str, "vol") == 0) + return _("Virtual mixer output"); + else if (strcmp (vmix_str, "channels") == 0) + return _("Virtual mixer channel configuration"); + } + + /* munge connector.foo => jack.foo (change from 4.0 -> 4.1) */ + if (g_str_has_prefix (mc->mixext.extname, "connector.")) { + g_snprintf (name, sizeof (name), "jack.%s", mc->mixext.extname + 10); + } else { + g_strlcpy (name, mc->mixext.extname, sizeof (name)); + } + + /* munge foo.function => foo.mode (change from 4.0 -> 4.1) */ + if (g_str_has_suffix (name, ".function")) + memcpy (name + strlen (name) - strlen (".function"), ".mode", 5 + 1); + + /* chop off trailing numbers */ + while (strlen (name) > 0 && g_ascii_isdigit (name[strlen (name) - 1])) + name[strlen (name) - 1] = '\0'; + + for (i = 0; i < G_N_ELEMENTS (labels); ++i) { + if (strcmp (name, labels[i].oss_name) == 0) + return _(labels[i].label); + } + + g_strdelimit (name, ".", ' '); + return g_quark_to_string (g_quark_from_string (name)); /* eek */ +} + +#ifndef GST_DISABLE_DEBUG +static const gchar * +mixer_ext_type_get_name (gint type) +{ + switch (type) { + case MIXT_DEVROOT: + return "Device root entry"; + case MIXT_GROUP: + return "Controller group"; + case MIXT_ONOFF: + return "On/Off switch"; + case MIXT_ENUM: + return "Enumeration control"; + case MIXT_MONOSLIDER: + return "Mono slider (0-255)"; + case MIXT_STEREOSLIDER: + return "Stereo slider (0-255)"; + case MIXT_MESSAGE: + return "Textual message"; /* whatever this is */ + case MIXT_MONOVU: + return "Mono VU meter value"; + case MIXT_STEREOVU: + return "Stereo VU meter value"; + case MIXT_MONOPEAK: + return "Mono VU meter peak value"; + case MIXT_STEREOPEAK: + return "Stereo VU meter peak value"; + case MIXT_RADIOGROUP: + return "Radio button group"; + case MIXT_MARKER: /* Separator between normal and extension entries */ + return "Separator"; + case MIXT_VALUE: + return "Decimal value entry"; + case MIXT_HEXVALUE: + return "Hex value entry"; + case MIXT_SLIDER: + return "Mono slider (31-bit value range)"; + case MIXT_3D: + return "3D"; /* what's this? */ + case MIXT_MONOSLIDER16: + return "Mono slider (0-32767)"; + case MIXT_STEREOSLIDER16: + return "Stereo slider (0-32767)"; + case MIXT_MUTE: + return "Mute switch"; + default: + break; + } + return "unknown"; +} +#endif /* GST_DISABLE_DEBUG */ + +#ifndef GST_DISABLE_DEBUG +static const gchar * +mixer_ext_flags_get_string (gint flags) +{ + struct + { + gint flag; + gchar nick[16]; + } all_flags[] = { + /* first the important ones */ + { + MIXF_MAINVOL, "MAINVOL"}, { + MIXF_PCMVOL, "PCMVOL"}, { + MIXF_RECVOL, "RECVOL"}, { + MIXF_MONVOL, "MONVOL"}, { + MIXF_DESCR, "DESCR"}, + /* now the rest in the right order */ + { + MIXF_READABLE, "READABLE"}, { + MIXF_WRITEABLE, "WRITABLE"}, { + MIXF_POLL, "POLL"}, { + MIXF_HZ, "HZ"}, { + MIXF_STRING, "STRING"}, { + MIXF_DYNAMIC, "DYNAMIC"}, { + MIXF_OKFAIL, "OKFAIL"}, { + MIXF_FLAT, "FLAT"}, { + MIXF_LEGACY, "LEGACY"}, { + MIXF_CENTIBEL, "CENTIBEL"}, { + MIXF_DECIBEL, "DECIBEL"}, { + MIXF_WIDE, "WIDE"} + }; + GString *s; + GQuark q; + gint i; + + if (flags == 0) + return "None"; + + s = g_string_new (""); + for (i = 0; i < G_N_ELEMENTS (all_flags); ++i) { + if ((flags & all_flags[i].flag)) { + if (s->len > 0) + g_string_append (s, " | "); + g_string_append (s, all_flags[i].nick); + flags &= ~all_flags[i].flag; /* unset */ + } + } + + /* unknown flags? */ + if (flags != 0) { + if (s->len > 0) + g_string_append (s, " | "); + g_string_append (s, "???"); + } + + /* we'll just put it into the global quark table (cheeky, eh?) */ + q = g_quark_from_string (s->str); + g_string_free (s, TRUE); + + return g_quark_to_string (q); +} +#endif /* GST_DISABLE_DEBUG */ + +#ifndef GST_DISABLE_DEBUG +static void +gst_oss4_mixer_control_dump_tree (GstOss4MixerControl * mc, gint depth) +{ + GList *c; + gchar spaces[64]; + gint i; + + depth = MIN (sizeof (spaces) - 1, depth); + for (i = 0; i < depth; ++i) + spaces[i] = ' '; + spaces[i] = '\0'; + + GST_LOG ("%s%s (%s)", spaces, mc->mixext.extname, + mixer_ext_type_get_name (mc->mixext.type)); + for (c = mc->children; c != NULL; c = c->next) { + GstOss4MixerControl *child_mc = (GstOss4MixerControl *) c->data; + + gst_oss4_mixer_control_dump_tree (child_mc, depth + 2); + } +} +#endif /* GST_DISABLE_DEBUG */ + +static GList * +gst_oss4_mixer_get_controls (GstOss4Mixer * mixer) +{ + GstOss4MixerControl *root_mc = NULL; + oss_mixerinfo mi = { 0, }; + GList *controls = NULL; + GList *l; + guint i; + + /* Get info for currently open fd */ + mi.dev = -1; + if (ioctl (mixer->fd, SNDCTL_MIXERINFO, &mi) == -1) + goto no_mixerinfo; + + if (mi.nrext <= 0) { + GST_DEBUG ("mixer %s has no controls", mi.id); + return NULL; + } + + GST_INFO ("Reading controls for mixer %s", mi.id); + + for (i = 0; i < mi.nrext; ++i) { + GstOss4MixerControl *mc; + oss_mixext mix_ext = { 0, }; + + mix_ext.dev = mi.dev; + mix_ext.ctrl = i; + + if (ioctl (mixer->fd, SNDCTL_MIX_EXTINFO, &mix_ext) == -1) { + GST_DEBUG ("SNDCTL_MIX_EXTINFO failed on mixer %s, control %d: %s", + mi.id, i, g_strerror (errno)); + continue; + } + + /* if this is the last one, save it for is-interface-up-to-date checking */ + if (i == mi.nrext) + mixer->last_mixext = mix_ext; + + mc = g_new0 (GstOss4MixerControl, 1); + mc->mixext = mix_ext; + + /* both control_no and desc fields are pretty useless, ie. not always set, + * if ever, so not listed here */ + GST_INFO ("Control %d", mix_ext.ctrl); + GST_INFO (" name : %s", mix_ext.extname); + GST_INFO (" type : %s (%d)", mixer_ext_type_get_name (mix_ext.type), + mix_ext.type); + GST_INFO (" flags : %s (0x%04x)", + mixer_ext_flags_get_string (mix_ext.flags), mix_ext.flags); + GST_INFO (" parent : %d", mix_ext.parent); + + /* get tooltip (just for informational purposes for now) */ + if (MIXEXT_HAS_DESCRIPTION (mix_ext)) { + oss_mixer_enuminfo desc = { 0, }; + + desc.dev = mix_ext.dev; + desc.ctrl = mix_ext.ctrl; + if (ioctl (mixer->fd, SNDCTL_MIX_DESCRIPTION, &desc) >= 0) { + /* "The string may contain multiple lines. The first line is the + * 'tooltip'. Optional subsequent lines may contain more detailed + * help text. Lines are separated by a linefeed character." */ + g_strdelimit (&desc.strings[desc.strindex[0]], "\n\r", '\0'); + GST_INFO (" tooltip: %s", &desc.strings[desc.strindex[0]]); + } + } + + if (!MIXEXT_IS_ROOT (mix_ext)) { + /* find parent (we assume it comes in the list before the child) */ + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *parent_mc = (GstOss4MixerControl *) l->data; + + if (parent_mc->mixext.ctrl == mix_ext.parent) { + mc->parent = parent_mc; + parent_mc->children = g_list_append (parent_mc->children, mc); + break; + } + } + if (mc->parent == NULL) { + GST_ERROR_OBJECT (mixer, "couldn't find parent for control %d", i); + g_free (mc); + continue; + } + } else if (root_mc == NULL) { + root_mc = mc; + } else { + GST_WARNING_OBJECT (mixer, "two root controls?!"); + } + + controls = g_list_prepend (controls, mc); + } + +#ifndef GST_DISABLE_DEBUG + gst_oss4_mixer_control_dump_tree (root_mc, 0); +#endif + + return g_list_reverse (controls); + +/* ERRORS */ +no_mixerinfo: + { + GST_WARNING ("SNDCTL_MIXERINFO failed on mixer device %s: %s", mi.id, + g_strerror (errno)); + return NULL; + } +} + +static void +gst_oss4_mixer_controls_guess_master (GstOss4Mixer * mixer, + const GList * controls) +{ + GstOss4MixerControl *firstpcm_mc = NULL; + GstOss4MixerControl *master_mc = NULL; + const GList *l; + + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data; + + if (((mc->mixext.flags & MIXF_MAINVOL)) && master_mc == NULL) { + GST_INFO_OBJECT (mixer, "Master control: %s", mc->mixext.extname); + master_mc = mc; + break; + } + /* do we need to check if it's a slider type here? */ + if (((mc->mixext.flags & MIXF_PCMVOL)) && firstpcm_mc == NULL) { + GST_INFO_OBJECT (mixer, "First PCM control: %s", mc->mixext.extname); + firstpcm_mc = mc; + } + } + + /* if no control with MIXF_MAINVOL found, use first one with PCMVOL */ + if (master_mc == NULL && firstpcm_mc != NULL) { + GST_INFO_OBJECT (mixer, "Marking first PCM control as master: %s", + firstpcm_mc->mixext.extname); + master_mc = firstpcm_mc; + } + + if (master_mc != NULL) + master_mc->is_master = TRUE; +} + +/* type: -1 for all types, otherwise just return siblings with requested type */ +static GList * +gst_oss4_mixer_control_get_siblings (GstOss4MixerControl * mc, gint type) +{ + GList *s, *siblings = NULL; + + if (mc->parent == NULL) + return NULL; + + for (s = mc->parent->children; s != NULL; s = s->next) { + GstOss4MixerControl *sibling = (GstOss4MixerControl *) s->data; + + if (mc != sibling && (type < 0 || sibling->mixext.type == type)) + siblings = g_list_append (siblings, sibling); + } + + return siblings; +} + +static void +gst_oss4_mixer_controls_find_sliders (GstOss4Mixer * mixer, + const GList * controls) +{ + const GList *l; + + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data; + GList *s, *siblings; + + if (!MIXEXT_IS_SLIDER (mc->mixext) || mc->parent == NULL || mc->used) + continue; + + mc->is_slider = TRUE; + mc->used = TRUE; + + siblings = gst_oss4_mixer_control_get_siblings (mc, -1); + + /* Note: the names can be misleading and may not reflect the actual + * hierarchy of the controls, e.g. it's possible that a slider is called + * connector.green and the mute control then connector.green.mute, but + * both controls are in fact siblings and both children of the group + * 'green' instead of mute being a child of connector.green as the naming + * would seem to suggest */ + GST_LOG ("Slider: %s, parent=%s, %d siblings", mc->mixext.extname, + mc->parent->mixext.extname, g_list_length (siblings)); + + for (s = siblings; s != NULL; s = s->next) { + GstOss4MixerControl *sibling = (GstOss4MixerControl *) s->data; + + GST_LOG (" %s (%s)", sibling->mixext.extname, + mixer_ext_type_get_name (sibling->mixext.type)); + + if (sibling->mixext.type == MIXT_MUTE || + g_str_has_suffix (sibling->mixext.extname, ".mute")) { + /* simple case: slider with single mute sibling. We assume the .mute + * suffix in the name won't change - can't really do much else anyway */ + if (sibling->mixext.type == MIXT_ONOFF || + sibling->mixext.type == MIXT_MUTE) { + GST_LOG (" mute control for %s is %s", mc->mixext.extname, + sibling->mixext.extname); + mc->mute = sibling; + sibling->used = TRUE; + } + /* a group of .mute controls. We assume they are all switches here */ + if (sibling->mixext.type == MIXT_GROUP) { + GList *m; + + for (m = sibling->children; m != NULL; m = m->next) { + GstOss4MixerControl *grouped_sibling = m->data; + + if (grouped_sibling->mixext.type == MIXT_ONOFF || + grouped_sibling->mixext.type == MIXT_MUTE) { + GST_LOG (" %s is grouped mute control for %s", + grouped_sibling->mixext.extname, mc->mixext.extname); + mc->mute_group = g_list_append (mc->mute_group, grouped_sibling); + } + } + + GST_LOG (" %s has a group of %d mute controls", + mc->mixext.extname, g_list_length (mc->mute_group)); + + /* we don't mark the individual mute controls as used, only the + * group control, as we still want individual switches for the + * individual controls */ + sibling->used = TRUE; + } + } + } + g_list_free (siblings); + } +} + +/* should be called with the mixer object lock held because of the ioctl; + * returns TRUE if the list was read the first time or modified */ +static gboolean +gst_oss4_mixer_enum_control_update_enum_list (GstOss4Mixer * mixer, + GstOss4MixerControl * mc) +{ + oss_mixer_enuminfo ei = { 0, }; + guint num_existing = 0; + int i; + + /* count and existing values */ + while (mc->enum_vals != NULL && mc->enum_vals[num_existing] != 0) + ++num_existing; + + ei.dev = mc->mixext.dev; + ei.ctrl = mc->mixext.ctrl; + + /* if we have create a generic list with numeric IDs already and the + * number of values hasn't changed, then there's not much to do here */ + if (mc->no_list && mc->enum_vals != NULL && + num_existing == mc->mixext.maxvalue) { + return FALSE; + } + + /* if we have a list and it doesn't change, there's nothing to do either */ + if (mc->enum_vals != NULL && mc->enum_version == 0) + return FALSE; + + if (ioctl (mixer->fd, SNDCTL_MIX_ENUMINFO, &ei) == -1) { + g_free (mc->enum_vals); + mc->enum_vals = g_new0 (GQuark, mc->mixext.maxvalue + 1); + + GST_DEBUG ("no enum info available, creating numeric values from 0-%d", + mc->mixext.maxvalue - 1); + + /* "It is possible that some enum controls don't have any name list + * available. In this case the application should automatically generate + * list of numbers (0 to N-1)" */ + for (i = 0; i < mc->mixext.maxvalue; ++i) { + gchar num_str[8]; + + g_snprintf (num_str, sizeof (num_str), "%d", i); + mc->enum_vals[i] = g_quark_from_string (num_str); + } + mc->enum_version = 0; /* the only way to change is via maxvalue */ + } else { + /* old list same as current list? */ + if (mc->enum_vals != NULL && mc->enum_version == ei.version) + return FALSE; + + /* no list yet, or the list has changed */ + GST_LOG ("%s", (mc->enum_vals) ? "enum list has changed" : "reading list"); + if (ei.nvalues != mc->mixext.maxvalue) { + GST_WARNING_OBJECT (mixer, "Enum: %s, nvalues %d != maxvalue %d", + mc->mixext.extname, ei.nvalues, mc->mixext.maxvalue); + mc->mixext.maxvalue = MIN (ei.nvalues, mc->mixext.maxvalue); + } + + mc->mixext.maxvalue = MIN (mc->mixext.maxvalue, OSS_ENUM_MAXVALUE); + + g_free (mc->enum_vals); + mc->enum_vals = g_new0 (GQuark, mc->mixext.maxvalue + 1); + for (i = 0; i < mc->mixext.maxvalue; ++i) { + GST_LOG (" %s", ei.strings + ei.strindex[i]); + mc->enum_vals[i] = g_quark_from_string (ei.strings + ei.strindex[i]); + } + } + + return TRUE; +} + +static void +gst_oss4_mixer_controls_find_enums (GstOss4Mixer * mixer, + const GList * controls) +{ + const GList *l; + + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data; + + if (mc->mixext.type != MIXT_ENUM || mc->used) + continue; + + mc->is_enum = TRUE; + mc->used = TRUE; + + /* Note: enums are special: for most controls, the maxvalue is inclusive, + * but for enum controls it's actually exclusive (boggle), so that + * mixext.maxvalue = num_values */ + + GST_LOG ("Enum: %s, parent=%s, num_enums=%d", mc->mixext.extname, + mc->parent->mixext.extname, mc->mixext.maxvalue); + + gst_oss4_mixer_enum_control_update_enum_list (mixer, mc); + } +} + +static void +gst_oss4_mixer_controls_find_switches (GstOss4Mixer * mixer, + const GList * controls) +{ + const GList *l; + + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data; + + if (mc->used) + continue; + + if (mc->mixext.type != MIXT_ONOFF && mc->mixext.type != MIXT_MUTE) + continue; + + mc->is_switch = TRUE; + mc->used = TRUE; + + GST_LOG ("Switch: %s, parent=%s", mc->mixext.extname, + mc->parent->mixext.extname); + } +} + +static void +gst_oss4_mixer_controls_find_virtual (GstOss4Mixer * mixer, + const GList * controls) +{ + const GList *l; + + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data; + + /* or sscanf (mc->mixext.extname, "vmix%d-out.", &n) == 1 ? */ + /* for now we just flag all virtual controls with managed labels, those + * are really more appropriate for a pavucontrol-type control thing than + * the (more hardware-oriented) mixer interface */ + if (mc->mixext.id[0] == '@') { + mc->is_virtual = TRUE; + GST_LOG ("%s is virtual control with managed label", mc->mixext.extname); + } + } +} + +static void +gst_oss4_mixer_controls_dump_unused (GstOss4Mixer * mixer, + const GList * controls) +{ + const GList *l; + + for (l = controls; l != NULL; l = l->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data; + + if (mc->used) + continue; + + switch (mc->mixext.type) { + case MIXT_DEVROOT: + case MIXT_GROUP: + case MIXT_MESSAGE: + case MIXT_MONOVU: + case MIXT_STEREOVU: + case MIXT_MONOPEAK: + case MIXT_STEREOPEAK: + case MIXT_MARKER: + continue; /* not interested in these types of controls */ + case MIXT_MONODB: + case MIXT_STEREODB: + GST_DEBUG ("obsolete control type %d", mc->mixext.type); + continue; + case MIXT_MONOSLIDER: + case MIXT_STEREOSLIDER: + case MIXT_SLIDER: + case MIXT_MONOSLIDER16: + case MIXT_STEREOSLIDER16: + /* this shouldn't happen */ + GST_ERROR ("unused slider control?!"); + continue; + case MIXT_VALUE: + case MIXT_HEXVALUE: + /* value entry, not sure what to do with that, skip for now */ + continue; + case MIXT_ONOFF: + case MIXT_MUTE: + case MIXT_ENUM: + case MIXT_3D: + case MIXT_RADIOGROUP: + GST_DEBUG ("FIXME: handle %s %s", + mixer_ext_type_get_name (mc->mixext.type), mc->mixext.extname); + break; + default: + GST_WARNING ("unknown control type %d", mc->mixext.type); + continue; + } + } +} + +static GList * +gst_oss4_mixer_create_tracks (GstOss4Mixer * mixer, const GList * controls) +{ + const GList *c; + GList *tracks = NULL; + + for (c = controls; c != NULL; c = c->next) { + GstOss4MixerControl *mc = (GstOss4MixerControl *) c->data; + GstMixerTrack *track = NULL; + + if (mc->is_virtual) + continue; + + if (mc->is_slider) { + track = gst_oss4_mixer_slider_new (mixer, mc); + } else if (mc->is_enum) { + track = gst_oss4_mixer_enum_new (mixer, mc); + } else if (mc->is_switch) { + track = gst_oss4_mixer_switch_new (mixer, mc); + } + + if (track == NULL) + continue; + + track->label = gst_oss4_mixer_control_get_pretty_name (mc); + track->flags = 0; + + GST_LOG ("translated label: %s [%s] = %s", track->label, mc->mixext.id, + gst_oss4_mixer_control_get_translated_name (mc)); + + /* This whole 'a track is either INPUT or OUTPUT' model is just flawed, + * esp. if a slider's role can be changed on the fly, like when you change + * function of a connector. What should we do in that case? Change the flag + * and make the app rebuild the interface? Ignore it? */ + if (g_str_has_prefix (mc->mixext.extname, "record.")) { + mc->is_output = FALSE; + mc->is_input = TRUE; + } + + /* FIXME: determine is_input and is_output */ + /* must be either INPUT or OUTPUT (but not both and not neither) for now, + * or gnome-volume-control aborts */ + if (mc->is_input) + track->flags |= GST_MIXER_TRACK_INPUT; + else if (mc->is_output) + track->flags |= GST_MIXER_TRACK_OUTPUT; + else + track->flags |= GST_MIXER_TRACK_OUTPUT; + + if (mc->is_master) + track->flags |= GST_MIXER_TRACK_MASTER; + + tracks = g_list_append (tracks, track); + } + + return tracks; +} + +static void +gst_oss4_mixer_update_tracks (GstOss4Mixer * mixer) +{ + GList *controls, *tracks; + + /* read and process controls */ + controls = gst_oss4_mixer_get_controls (mixer); + + gst_oss4_mixer_controls_guess_master (mixer, controls); + + gst_oss4_mixer_controls_find_sliders (mixer, controls); + + gst_oss4_mixer_controls_find_enums (mixer, controls); + + gst_oss4_mixer_controls_find_switches (mixer, controls); + + gst_oss4_mixer_controls_find_virtual (mixer, controls); + + gst_oss4_mixer_controls_dump_unused (mixer, controls); + + tracks = gst_oss4_mixer_create_tracks (mixer, controls); + + /* free old tracks and controls */ + gst_oss4_mixer_free_tracks (mixer); + + /* replace old with new */ + mixer->tracks = tracks; + mixer->controls = controls; +} + +static const GList * +gst_oss4_mixer_list_tracks (GstMixer * mixer_iface) +{ + GstOss4Mixer *mixer = GST_OSS4_MIXER (mixer_iface); + + g_return_val_if_fail (mixer != NULL, NULL); + g_return_val_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer), NULL); + + GST_OBJECT_LOCK (mixer); + + /* Do a read on the last control to check if the interface has changed */ + if (!mixer->need_update && mixer->last_mixext.ctrl > 0) { + GstOss4MixerControl mc = { {0,} + , + }; + int val; + + mc.mixext = mixer->last_mixext; + gst_oss4_mixer_get_control_val (mixer, &mc, &val); + } + + if (mixer->need_update || mixer->tracks == NULL) { + gst_oss4_mixer_update_tracks (mixer); + mixer->need_update = FALSE; + } + + GST_OBJECT_UNLOCK (mixer); + + return (const GList *) mixer->tracks; +} + +static void +gst_oss4_mixer_set_volume (GstMixer * mixer, GstMixerTrack * track, + gint * volumes) +{ + GstOss4Mixer *oss; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (GST_IS_OSS4_MIXER (mixer)); + g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer)); + g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track)); + g_return_if_fail (volumes != NULL); + + oss = GST_OSS4_MIXER (mixer); + + GST_OBJECT_LOCK (oss); + + if (GST_IS_OSS4_MIXER_SLIDER (track)) { + gst_oss4_mixer_slider_set_volume (GST_OSS4_MIXER_SLIDER (track), volumes); + } + + GST_OBJECT_UNLOCK (oss); +} + +static void +gst_oss4_mixer_get_volume (GstMixer * mixer, GstMixerTrack * track, + gint * volumes) +{ + GstOss4Mixer *oss; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (GST_IS_OSS4_MIXER (mixer)); + g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer)); + g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track)); + g_return_if_fail (volumes != NULL); + + oss = GST_OSS4_MIXER (mixer); + + GST_OBJECT_LOCK (oss); + + memset (volumes, 0, track->num_channels * sizeof (gint)); + + if (GST_IS_OSS4_MIXER_SLIDER (track)) { + gst_oss4_mixer_slider_get_volume (GST_OSS4_MIXER_SLIDER (track), volumes); + } + + GST_OBJECT_UNLOCK (oss); +} + +static void +gst_oss4_mixer_set_record (GstMixer * mixer, GstMixerTrack * track, + gboolean record) +{ + GstOss4Mixer *oss; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (GST_IS_OSS4_MIXER (mixer)); + g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer)); + g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track)); + + oss = GST_OSS4_MIXER (mixer); + + GST_OBJECT_LOCK (oss); + + if (GST_IS_OSS4_MIXER_SLIDER (track)) { + gst_oss4_mixer_slider_set_record (GST_OSS4_MIXER_SLIDER (track), record); + } else if (GST_IS_OSS4_MIXER_SWITCH (track)) { + if ((track->flags & GST_MIXER_TRACK_INPUT)) { + gst_oss4_mixer_switch_set (GST_OSS4_MIXER_SWITCH (track), record); + } else { + GST_WARNING_OBJECT (track, "set_record called on non-INPUT track"); + } + } + + GST_OBJECT_UNLOCK (oss); +} + +static void +gst_oss4_mixer_set_mute (GstMixer * mixer, GstMixerTrack * track, gboolean mute) +{ + GstOss4Mixer *oss; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (GST_IS_OSS4_MIXER (mixer)); + g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer)); + g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track)); + + oss = GST_OSS4_MIXER (mixer); + + GST_OBJECT_LOCK (oss); + + if (GST_IS_OSS4_MIXER_SLIDER (track)) { + gst_oss4_mixer_slider_set_mute (GST_OSS4_MIXER_SLIDER (track), mute); + } else if (GST_IS_OSS4_MIXER_SWITCH (track)) { + if ((track->flags & GST_MIXER_TRACK_OUTPUT)) { + gst_oss4_mixer_switch_set (GST_OSS4_MIXER_SWITCH (track), mute); + } else { + GST_WARNING_OBJECT (track, "set_mute called on non-OUTPUT track"); + } + } + + GST_OBJECT_UNLOCK (oss); +} + +static void +gst_oss4_mixer_set_option (GstMixer * mixer, GstMixerOptions * options, + gchar * value) +{ + GstOss4Mixer *oss; + + g_return_if_fail (mixer != NULL); + g_return_if_fail (value != NULL); + g_return_if_fail (GST_IS_OSS4_MIXER (mixer)); + g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer)); + g_return_if_fail (GST_IS_OSS4_MIXER_ENUM (options)); + g_return_if_fail (gst_oss4_mixer_contains_options (mixer, options)); + + oss = GST_OSS4_MIXER (mixer); + + GST_OBJECT_LOCK (oss); + + if (!gst_oss4_mixer_enum_set_option (GST_OSS4_MIXER_ENUM (options), value)) { + /* not much we can do here but wake up the watch thread early, so it + * can do its thing and post messages if anything has changed */ + gst_oss4_mixer_wake_up_watch_task (oss); + } + + GST_OBJECT_UNLOCK (oss); +} + +static const gchar * +gst_oss4_mixer_get_option (GstMixer * mixer, GstMixerOptions * options) +{ + GstOss4Mixer *oss; + const gchar *current_val; + + g_return_val_if_fail (mixer != NULL, NULL); + g_return_val_if_fail (GST_IS_OSS4_MIXER (mixer), NULL); + g_return_val_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer), NULL); + g_return_val_if_fail (GST_IS_OSS4_MIXER_ENUM (options), NULL); + g_return_val_if_fail (gst_oss4_mixer_contains_options (mixer, options), NULL); + + oss = GST_OSS4_MIXER (mixer); + + GST_OBJECT_LOCK (oss); + + current_val = gst_oss4_mixer_enum_get_option (GST_OSS4_MIXER_ENUM (options)); + + if (current_val == NULL) { + /* not much we can do here but wake up the watch thread early, so it + * can do its thing and post messages if anything has changed */ + gst_oss4_mixer_wake_up_watch_task (oss); + } + + GST_OBJECT_UNLOCK (oss); + + return current_val; +} + +static GstMixerFlags +gst_oss4_mixer_get_mixer_flags (GstMixer * mixer) +{ + return GST_MIXER_FLAG_AUTO_NOTIFICATIONS; +} + +static void +gst_oss4_mixer_interface_init (GstMixerClass * klass) +{ + GST_MIXER_TYPE (klass) = GST_MIXER_HARDWARE; + + klass->list_tracks = gst_oss4_mixer_list_tracks; + klass->set_volume = gst_oss4_mixer_set_volume; + klass->get_volume = gst_oss4_mixer_get_volume; + klass->set_mute = gst_oss4_mixer_set_mute; + klass->set_record = gst_oss4_mixer_set_record; + klass->set_option = gst_oss4_mixer_set_option; + klass->get_option = gst_oss4_mixer_get_option; + klass->get_mixer_flags = gst_oss4_mixer_get_mixer_flags; +} + +/* Implement the horror that is GstImplementsInterface */ + +static gboolean +gst_oss4_mixer_supported (GstImplementsInterface * iface, GType iface_type) +{ + GstOss4Mixer *mixer; + + g_return_val_if_fail (iface_type == GST_TYPE_MIXER, FALSE); + + mixer = GST_OSS4_MIXER (iface); + + return GST_OSS4_MIXER_IS_OPEN (mixer); +} + +static void +gst_oss4_mixer_implements_interface_init (GstImplementsInterfaceClass * klass) +{ + klass->supported = gst_oss4_mixer_supported; +} + +static void +gst_oss4_mixer_init_interfaces (GType type) +{ + static const GInterfaceInfo implements_iface_info = { + (GInterfaceInitFunc) gst_oss4_mixer_implements_interface_init, + NULL, + NULL, + }; + static const GInterfaceInfo mixer_iface_info = { + (GInterfaceInitFunc) gst_oss4_mixer_interface_init, + NULL, + NULL, + }; + + g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE, + &implements_iface_info); + g_type_add_interface_static (type, GST_TYPE_MIXER, &mixer_iface_info); + + gst_oss4_add_property_probe_interface (type); +} |