diff options
Diffstat (limited to 'backends/oss')
-rw-r--r-- | backends/oss/Makefile.am | 34 | ||||
-rw-r--r-- | backends/oss/oss-backend.c | 587 | ||||
-rw-r--r-- | backends/oss/oss-backend.h | 63 | ||||
-rw-r--r-- | backends/oss/oss-common.h | 39 | ||||
-rw-r--r-- | backends/oss/oss-device.c | 404 | ||||
-rw-r--r-- | backends/oss/oss-device.h | 77 | ||||
-rw-r--r-- | backends/oss/oss-stream-control.c | 392 | ||||
-rw-r--r-- | backends/oss/oss-stream-control.h | 70 | ||||
-rw-r--r-- | backends/oss/oss-stream.c | 183 | ||||
-rw-r--r-- | backends/oss/oss-stream.h | 74 |
10 files changed, 1923 insertions, 0 deletions
diff --git a/backends/oss/Makefile.am b/backends/oss/Makefile.am new file mode 100644 index 0000000..44caeb8 --- /dev/null +++ b/backends/oss/Makefile.am @@ -0,0 +1,34 @@ +backenddir = $(libdir)/libmatemixer + +backend_LTLIBRARIES = libmatemixer-oss.la + +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -DG_LOG_DOMAIN=\"libmatemixer-oss\" + +libmatemixer_oss_la_CFLAGS = \ + $(GLIB_CFLAGS) \ + $(OSS_CFLAGS) + +libmatemixer_oss_la_SOURCES = \ + oss-common.h \ + oss-backend.c \ + oss-backend.h \ + oss-device.c \ + oss-device.h \ + oss-stream.c \ + oss-stream.h \ + oss-stream-control.c \ + oss-stream-control.h + +libmatemixer_oss_la_LIBADD = \ + $(GLIB_LIBS) \ + $(OSS_LIBS) + +libmatemixer_oss_la_LDFLAGS = \ + -avoid-version \ + -no-undefined \ + -export-dynamic \ + -module + +-include $(top_srcdir)/git.mk diff --git a/backends/oss/oss-backend.c b/backends/oss/oss-backend.c new file mode 100644 index 0000000..2b5eca7 --- /dev/null +++ b/backends/oss/oss-backend.c @@ -0,0 +1,587 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <glib.h> +#include <glib/gstdio.h> +#include <glib/gi18n.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "oss-backend.h" +#include "oss-common.h" +#include "oss-device.h" +#include "oss-stream.h" + +#define BACKEND_NAME "OSS" +#define BACKEND_PRIORITY 9 + +#if !defined(__linux__) && !defined(__NetBSD__) && !defined(__OpenBSD__) + /* At least on systems based on FreeBSD we will need to read device names + * from the sndstat file, but avoid even trying that on systems where this + * is not needed and the file is not present */ +#define OSS_PATH_SNDSTAT "/dev/sndstat" +#endif + +#define OSS_MAX_DEVICES 32 + +struct _OssBackendPrivate +{ + gchar *default_device; + GSource *timeout_source; + GHashTable *devices; +}; + +static void oss_backend_class_init (OssBackendClass *klass); +static void oss_backend_class_finalize (OssBackendClass *klass); +static void oss_backend_init (OssBackend *oss); +static void oss_backend_dispose (GObject *object); +static void oss_backend_finalize (GObject *object); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" +G_DEFINE_DYNAMIC_TYPE (OssBackend, oss_backend, MATE_MIXER_TYPE_BACKEND) +#pragma clang diagnostic pop + +static gboolean oss_backend_open (MateMixerBackend *backend); +static void oss_backend_close (MateMixerBackend *backend); +static GList * oss_backend_list_devices (MateMixerBackend *backend); +static GList * oss_backend_list_streams (MateMixerBackend *backend); + +static gboolean read_devices (OssBackend *oss); + +static gboolean read_device (OssBackend *oss, + const gchar *path, + gboolean *added); + +static gchar * read_device_label (OssBackend *oss, + const gchar *path, + gint fd); + +static gchar * read_device_label_sndstat (OssBackend *oss, + const gchar *sndstat, + const gchar *path, + guint index) G_GNUC_UNUSED; + +static void add_device (OssBackend *oss, + OssDevice *device); +static void remove_device (OssBackend *oss, + OssDevice *device); + +static void remove_stream (OssBackend *oss, + const gchar *name); + +static void select_default_input_stream (OssBackend *oss); +static void select_default_output_stream (OssBackend *oss); + +static MateMixerBackendInfo info; + +void +backend_module_init (GTypeModule *module) +{ + oss_backend_register_type (module); + + info.name = BACKEND_NAME; + info.priority = BACKEND_PRIORITY; + info.g_type = OSS_TYPE_BACKEND; + info.backend_type = MATE_MIXER_BACKEND_OSS; +} + +const MateMixerBackendInfo *backend_module_get_info (void) +{ + return &info; +} + +static void +oss_backend_class_init (OssBackendClass *klass) +{ + GObjectClass *object_class; + MateMixerBackendClass *backend_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = oss_backend_dispose; + object_class->finalize = oss_backend_finalize; + + backend_class = MATE_MIXER_BACKEND_CLASS (klass); + backend_class->open = oss_backend_open; + backend_class->close = oss_backend_close; + backend_class->list_devices = oss_backend_list_devices; + backend_class->list_streams = oss_backend_list_streams; + + g_type_class_add_private (object_class, sizeof (OssBackendPrivate)); +} + +/* Called in the code generated by G_DEFINE_DYNAMIC_TYPE() */ +static void +oss_backend_class_finalize (OssBackendClass *klass) +{ +} + +static void +oss_backend_init (OssBackend *oss) +{ + oss->priv = G_TYPE_INSTANCE_GET_PRIVATE (oss, + OSS_TYPE_BACKEND, + OssBackendPrivate); + + oss->priv->devices = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); +} + +static void +oss_backend_dispose (GObject *object) +{ + MateMixerBackend *backend; + MateMixerState state; + + backend = MATE_MIXER_BACKEND (object); + + state = mate_mixer_backend_get_state (backend); + if (state != MATE_MIXER_STATE_IDLE) + oss_backend_close (backend); + + G_OBJECT_CLASS (oss_backend_parent_class)->dispose (object); +} + +static void +oss_backend_finalize (GObject *object) +{ + OssBackend *oss; + + oss = OSS_BACKEND (object); + + g_hash_table_destroy (oss->priv->devices); + + G_OBJECT_CLASS (oss_backend_parent_class)->finalize (object); +} + +static gboolean +oss_backend_open (MateMixerBackend *backend) +{ + OssBackend *oss; + + g_return_val_if_fail (OSS_IS_BACKEND (backend), FALSE); + + oss = OSS_BACKEND (backend); + + /* Discover added or removed OSS devices every second */ + oss->priv->timeout_source = g_timeout_source_new_seconds (1); + g_source_set_callback (oss->priv->timeout_source, + (GSourceFunc) read_devices, + oss, + NULL); + g_source_attach (oss->priv->timeout_source, + g_main_context_get_thread_default ()); + + /* Read the initial list of devices so we have some starting point, there + * isn't really a way to detect errors here, failing to add a device may + * be a device-related problem so make the backend always open successfully */ + read_devices (oss); + + _mate_mixer_backend_set_state (backend, MATE_MIXER_STATE_READY); + return TRUE; +} + +void +oss_backend_close (MateMixerBackend *backend) +{ + OssBackend *oss; + + g_return_if_fail (OSS_IS_BACKEND (backend)); + + oss = OSS_BACKEND (backend); + + g_source_destroy (oss->priv->timeout_source); + g_hash_table_remove_all (oss->priv->devices); + + g_free (oss->priv->default_device); + oss->priv->default_device = NULL; + + _mate_mixer_backend_set_state (backend, MATE_MIXER_STATE_IDLE); +} + +static GList * +oss_backend_list_devices (MateMixerBackend *backend) +{ + GList *list; + + g_return_val_if_fail (OSS_IS_BACKEND (backend), NULL); + + /* Convert the hash table to a linked list, this list is expected to be + * cached in the main library */ + list = g_hash_table_get_values (OSS_BACKEND (backend)->priv->devices); + if (list != NULL) + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return list; +} + +static GList * +oss_backend_list_streams (MateMixerBackend *backend) +{ + OssBackend *oss; + GHashTableIter iter; + gpointer value; + GList *list = NULL; + + g_return_val_if_fail (OSS_IS_BACKEND (backend), NULL); + + oss = OSS_BACKEND (backend); + + /* We don't keep a list or hash table of all streams here, instead walk + * through the list of devices and create the list manually, each device + * has at most one input and one output stream */ + g_hash_table_iter_init (&iter, oss->priv->devices); + + while (g_hash_table_iter_next (&iter, NULL, &value)) { + OssDevice *device = OSS_DEVICE (value); + OssStream *stream; + + stream = oss_device_get_output_stream (device); + if (stream != NULL) + list = g_list_prepend (list, stream); + stream = oss_device_get_input_stream (device); + if (stream != NULL) + list = g_list_prepend (list, stream); + } + + if (list != NULL) + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return list; +} + +static gboolean +read_devices (OssBackend *oss) +{ + gint i; + gboolean added = FALSE; + + for (i = 0; i < OSS_MAX_DEVICES; i++) { + gboolean added_current; + gchar *path = g_strdup_printf ("/dev/mixer%i", i); + + /* On recent FreeBSD both /dev/mixer and /dev/mixer0 point to the same + * mixer device, on NetBSD and OpenBSD /dev/mixer is a symlink to one + * of the real mixer device nodes, on Linux /dev/mixer is the first + * device and /dev/mixer1 is the second device. + * Handle all of these cases by trying /dev/mixer if /dev/mixer0 fails */ + if (read_device (oss, path, &added_current) == FALSE && i == 0) + read_device (oss, "/dev/mixer", &added_current); + + if (added_current) + added = TRUE; + + g_free (path); + } + + /* If any card has been added, make sure we have the most suitable default + * input and output streams */ + if (added == TRUE) { + select_default_input_stream (oss); + select_default_output_stream (oss); + } + return G_SOURCE_CONTINUE; +} + +static gboolean +read_device (OssBackend *oss, const gchar *path, gboolean *added) +{ + OssDevice *device; + gint fd; + gchar *bname; + gchar *label; + + device = g_hash_table_lookup (oss->priv->devices, path); + *added = FALSE; + + fd = g_open (path, O_RDWR, 0); + if (fd == -1) { + if (errno != ENOENT && errno != ENXIO) + g_debug ("%s: %s", path, g_strerror (errno)); + + if (device != NULL) + remove_device (oss, device); + return FALSE; + } + + /* Don't proceed if the device is already known, opening the device was + * still tested to be absolutely sure that the device is removed it case + * it has disappeared, but normally the device's polling facility should + * handle this by itself */ + if (device != NULL) { + close (fd); + return TRUE; + } + + bname = g_path_get_basename (path); + label = read_device_label (oss, path, fd); + + device = oss_device_new (bname, label, path, fd); + g_free (bname); + g_free (label); + + close (fd); + + if ((*added = oss_device_open (device)) == TRUE) + add_device (oss, device); + + g_object_unref (device); + return *added; +} + +static gchar * +read_device_label (OssBackend *oss, const gchar *path, gint fd) +{ + guint index; + +#ifdef SOUND_MIXER_INFO + do { + struct mixer_info info; + + /* Prefer device name supplied by the system, but this calls fails + * with EINVAL on FreeBSD */ + if (ioctl (fd, SOUND_MIXER_INFO, &info) == 0) + return g_strdup (info.name); + } while (0); +#endif + + index = (guint) g_ascii_strtoull (path + sizeof ("/dev/mixer") - 1, + NULL, + 10); +#ifdef OSS_PATH_SNDSTAT + /* If the ioctl doesn't succeed, assume that the mixer device number + * matches the pcm number in the sndstat file, this is a bit desperate, but + * it should be correct on FreeBSD */ + do { + gchar *label; + + label = read_device_label_sndstat (oss, OSS_PATH_SNDSTAT, path, index); + if (label != NULL) + return label; + } while (0); +#endif + + return g_strdup_printf (_("OSS Mixer %d"), index); +} + +static gchar * +read_device_label_sndstat (OssBackend *oss, + const gchar *sndstat, + const gchar *path, + guint index) +{ + FILE *fp; + gchar *label = NULL; + gchar *prefix; + gchar line[512]; + + fp = g_fopen (sndstat, "r"); + if (fp == NULL) { + g_debug ("Failed to open %s: %s", sndstat, g_strerror (errno)); + return NULL; + } + + /* Example line: + * pcm0: <ATI R6xx (HDMI)> (play) default */ + prefix = g_strdup_printf ("pcm%u: ", index); + + while (fgets (line, sizeof (line), fp) != NULL) { + gchar *p; + + if (g_str_has_prefix (line, prefix) == FALSE) + continue; + + p = strchr (line, '<'); + if (p != NULL && *p && *(++p)) { + gchar *end = strchr (p, '>'); + + if (end != NULL) { + label = g_strndup (p, end - p); + + /* Normally the default OSS device is /dev/dsp, but on FreeBSD + * /dev/dsp doesn't physically exist on the filesystem, but is + * managed by the kernel according to the user-settable default + * device, in sndstat the default card definition ends with the + * word "default" */ + if (g_str_has_suffix (line, "default")) { + g_free (oss->priv->default_device); + + oss->priv->default_device = g_strdup (path); + } + } else { + g_debug ("Failed to read sndstat line: %s", line); + } + break; + } + } + + g_free (prefix); + fclose (fp); + + return label; +} + +static void +add_device (OssBackend *oss, OssDevice *device) +{ + const gchar *name; + + name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + + g_hash_table_insert (oss->priv->devices, + g_strdup (oss_device_get_path (device)), + g_object_ref (device)); + + // XXX make device emit it when closed + g_signal_connect_swapped (G_OBJECT (device), + "stream-removed", + G_CALLBACK (remove_stream), + oss); + + g_signal_emit_by_name (G_OBJECT (oss), "device-added", name); + + oss_device_load (device); +} + +static void +remove_device (OssBackend *oss, OssDevice *device) +{ + const gchar *name; + const gchar *path; + + path = oss_device_get_path (device); + name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + + g_signal_handlers_disconnect_by_func (G_OBJECT (device), + G_CALLBACK (remove_stream), + oss); + + // XXX close the device and make it remove streams + g_hash_table_remove (oss->priv->devices, path); + + g_signal_emit_by_name (G_OBJECT (oss), + "device-removed", + name); +} + +static void +remove_stream (OssBackend *oss, const gchar *name) +{ + MateMixerStream *stream; + + stream = mate_mixer_backend_get_default_input_stream (MATE_MIXER_BACKEND (oss)); + + // XXX see if the change happens after stream is removed or before + if (stream != NULL && strcmp (mate_mixer_stream_get_name (stream), name) == 0) + select_default_input_stream (oss); + + stream = mate_mixer_backend_get_default_output_stream (MATE_MIXER_BACKEND (oss)); + + if (stream != NULL && strcmp (mate_mixer_stream_get_name (stream), name) == 0) + select_default_output_stream (oss); +} + +static void +select_default_input_stream (OssBackend *oss) +{ + OssDevice *device = NULL; + OssStream *stream; + gint i; + + /* Always prefer stream in the "default" device */ + if (oss->priv->default_device != NULL) + device = g_hash_table_lookup (oss->priv->devices, oss->priv->default_device); + if (device != NULL) { + stream = oss_device_get_input_stream (device); + if (stream != NULL) { + _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (oss), + MATE_MIXER_STREAM (stream)); + return; + } + } + + for (i = 0; i < OSS_MAX_DEVICES; i++) { + gchar *path = g_strdup_printf ("/dev/mixer%i", i); + + device = g_hash_table_lookup (oss->priv->devices, path); + if (device == NULL && i == 0) + device = g_hash_table_lookup (oss->priv->devices, "/dev/mixer"); + + if (device != NULL) { + stream = oss_device_get_input_stream (device); + if (stream != NULL) { + _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (oss), + MATE_MIXER_STREAM (stream)); + g_free (path); + return; + } + } + g_free (path); + } + + /* In the worst case unset the default stream */ + _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (oss), NULL); +} + +static void +select_default_output_stream (OssBackend *oss) +{ + OssDevice *device = NULL; + OssStream *stream; + gint i; + + /* Always prefer stream in the "default" device */ + if (oss->priv->default_device != NULL) + device = g_hash_table_lookup (oss->priv->devices, oss->priv->default_device); + if (device != NULL) { + stream = oss_device_get_output_stream (device); + if (stream != NULL) { + _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (oss), + MATE_MIXER_STREAM (stream)); + return; + } + } + + for (i = 0; i < OSS_MAX_DEVICES; i++) { + gchar *path = g_strdup_printf ("/dev/mixer%i", i); + + device = g_hash_table_lookup (oss->priv->devices, path); + if (device == NULL && i == 0) + device = g_hash_table_lookup (oss->priv->devices, "/dev/mixer"); + + if (device != NULL) { + stream = oss_device_get_output_stream (device); + if (stream != NULL) { + _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (oss), + MATE_MIXER_STREAM (stream)); + g_free (path); + return; + } + } + g_free (path); + } + + /* In the worst case unset the default stream */ + _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (oss), NULL); +} diff --git a/backends/oss/oss-backend.h b/backends/oss/oss-backend.h new file mode 100644 index 0000000..325b61c --- /dev/null +++ b/backends/oss/oss-backend.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef OSS_BACKEND_H +#define OSS_BACKEND_H + +#include <glib.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer-backend.h> +#include <libmatemixer/matemixer-backend-module.h> + +#define OSS_TYPE_BACKEND \ + (oss_backend_get_type ()) +#define OSS_BACKEND(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), OSS_TYPE_BACKEND, OssBackend)) +#define OSS_IS_BACKEND(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSS_TYPE_BACKEND)) +#define OSS_BACKEND_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), OSS_TYPE_BACKEND, OssBackendClass)) +#define OSS_IS_BACKEND_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), OSS_TYPE_BACKEND)) +#define OSS_BACKEND_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_BACKEND, OssBackendClass)) + +typedef struct _OssBackend OssBackend; +typedef struct _OssBackendClass OssBackendClass; +typedef struct _OssBackendPrivate OssBackendPrivate; + +struct _OssBackend +{ + MateMixerBackend parent; + + /*< private >*/ + OssBackendPrivate *priv; +}; + +struct _OssBackendClass +{ + MateMixerBackendClass parent_class; +}; + +GType oss_backend_get_type (void) G_GNUC_CONST; + +/* Support function for dynamic loading of the backend module */ +void backend_module_init (GTypeModule *module); +const MateMixerBackendInfo *backend_module_get_info (void); + +#endif /* OSS_BACKEND_H */ diff --git a/backends/oss/oss-common.h b/backends/oss/oss-common.h new file mode 100644 index 0000000..28a138d --- /dev/null +++ b/backends/oss/oss-common.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef OSS_COMMON_H +#define OSS_COMMON_H + +#include "config.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> + +#ifdef HAVE_SYS_SOUNDCARD_H +# include <sys/soundcard.h> +#elif HAVE_SOUNDCARD_H +# include <soundcard.h> +#elif HAVE_MACHINE_SOUNDCARD_H +# include <machine/soundcard.h> +#else +# error "No OSS header file present" +#endif + +#endif /* OSS_COMMON_H */ diff --git a/backends/oss/oss-device.c b/backends/oss/oss-device.c new file mode 100644 index 0000000..cf51705 --- /dev/null +++ b/backends/oss/oss-device.c @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <unistd.h> +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "oss-common.h" +#include "oss-device.h" +#include "oss-stream.h" +#include "oss-stream-control.h" + +#define OSS_DEVICE_ICON "audio-card" + +typedef enum +{ + OSS_DEV_ANY, + OSS_DEV_INPUT, + OSS_DEV_OUTPUT +} OssDevType; + +typedef struct +{ + gchar *name; + gchar *label; + MateMixerStreamControlRole role; + OssDevType type; +} OssDev; + +static const OssDev oss_devices[] = { + { "vol", N_("Volume"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, OSS_DEV_OUTPUT }, + { "bass", N_("Bass"), MATE_MIXER_STREAM_CONTROL_ROLE_BASS, OSS_DEV_OUTPUT }, + { "treble", N_("Treble"), MATE_MIXER_STREAM_CONTROL_ROLE_TREBLE, OSS_DEV_OUTPUT }, + { "synth", N_("Synth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT }, + { "pcm", N_("PCM"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, OSS_DEV_OUTPUT }, + /* OSS manual says this should be the beeper, but Linux OSS seems to assign it to + * regular volume control */ + { "speaker", N_("Speaker"), MATE_MIXER_STREAM_CONTROL_ROLE_SPEAKER, OSS_DEV_OUTPUT }, + { "line", N_("Line-in"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, + { "mic", N_("Microphone"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, + { "cd", N_("CD"), MATE_MIXER_STREAM_CONTROL_ROLE_CD, OSS_DEV_INPUT }, + /* Recording monitor */ + { "mix", N_("Mixer"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, + { "pcm2", N_("PCM-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PCM, OSS_DEV_OUTPUT }, + /* Recording level (master input) */ + { "rec", N_("Record"), MATE_MIXER_STREAM_CONTROL_ROLE_MASTER, OSS_DEV_INPUT }, + { "igain", N_("In-gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT }, + { "ogain", N_("Out-gain"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, + { "line1", N_("Line-1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, + { "line2", N_("Line-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, + { "line3", N_("Line-3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, + { "dig1", N_("Digital-1"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY }, + { "dig2", N_("Digital-2"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY }, + { "dig3", N_("Digital-3"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_ANY }, + { "phin", N_("Phone-in"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, + { "phout", N_("Phone-out"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_OUTPUT }, + { "video", N_("Video"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, + { "radio", N_("Radio"), MATE_MIXER_STREAM_CONTROL_ROLE_PORT, OSS_DEV_INPUT }, + { "monitor", N_("Monitor"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, + { "depth", N_("3D-depth"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, + { "center", N_("3D-center"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_OUTPUT }, + { "midi", N_("MIDI"), MATE_MIXER_STREAM_CONTROL_ROLE_UNKNOWN, OSS_DEV_INPUT } +}; + +#define OSS_N_DEVICES MIN (G_N_ELEMENTS (oss_devices), SOUND_MIXER_NRDEVICES) + +struct _OssDevicePrivate +{ + gint fd; + gchar *path; + gint devmask; + gint stereodevs; + gint recmask; + gint recsrc; + OssStream *input; + OssStream *output; +}; + +static void oss_device_class_init (OssDeviceClass *klass); +static void oss_device_init (OssDevice *device); +static void oss_device_dispose (GObject *object); +static void oss_device_finalize (GObject *object); + +G_DEFINE_TYPE (OssDevice, oss_device, MATE_MIXER_TYPE_DEVICE) + +static GList * oss_device_list_streams (MateMixerDevice *device); + +static gboolean read_mixer_devices (OssDevice *device); + +static gboolean set_stream_default_control (OssStream *stream, + OssStreamControl *control, + gboolean force); + +static void +oss_device_class_init (OssDeviceClass *klass) +{ + GObjectClass *object_class; + MateMixerDeviceClass *device_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = oss_device_dispose; + object_class->finalize = oss_device_finalize; + + device_class = MATE_MIXER_DEVICE_CLASS (klass); + device_class->list_streams = oss_device_list_streams; + + g_type_class_add_private (object_class, sizeof (OssDevicePrivate)); +} + +static void +oss_device_init (OssDevice *device) +{ + device->priv = G_TYPE_INSTANCE_GET_PRIVATE (device, + OSS_TYPE_DEVICE, + OssDevicePrivate); +} + +static void +oss_device_dispose (GObject *object) +{ + OssDevice *device; + + device = OSS_DEVICE (object); + + g_clear_object (&device->priv->input); + g_clear_object (&device->priv->output); + + G_OBJECT_CLASS (oss_device_parent_class)->dispose (object); +} + +static void +oss_device_finalize (GObject *object) +{ + OssDevice *device = OSS_DEVICE (object); + + close (device->priv->fd); + + G_OBJECT_CLASS (oss_device_parent_class)->finalize (object); +} + +OssDevice * +oss_device_new (const gchar *name, const gchar *label, const gchar *path, gint fd) +{ + OssDevice *device; + gchar *stream_name; + + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (label != NULL, NULL); + g_return_val_if_fail (path != NULL, NULL); + + device = g_object_new (OSS_TYPE_DEVICE, + "name", name, + "label", label, + "icon", OSS_DEVICE_ICON, + NULL); + + device->priv->fd = dup (fd); + device->priv->path = g_strdup (path); + + stream_name = g_strdup_printf ("oss-input-%s", name); + device->priv->input = oss_stream_new (stream_name, + MATE_MIXER_DEVICE (device), + MATE_MIXER_STREAM_INPUT); + g_free (stream_name); + + stream_name = g_strdup_printf ("oss-output-%s", name); + device->priv->output = oss_stream_new (stream_name, + MATE_MIXER_DEVICE (device), + MATE_MIXER_STREAM_OUTPUT); + g_free (stream_name); + + return device; +} + +gboolean +oss_device_open (OssDevice *device) +{ + gint ret; + + g_return_val_if_fail (OSS_IS_DEVICE (device), FALSE); + + g_debug ("Opening device %s (%s)", + device->priv->path, + mate_mixer_device_get_label (MATE_MIXER_DEVICE (device))); + + /* Read the essential information about the device, these values are not + * expected to change and will not be queried */ + ret = ioctl (device->priv->fd, + MIXER_READ (SOUND_MIXER_DEVMASK), + &device->priv->devmask); + if (ret != 0) + goto fail; + + ret = ioctl (device->priv->fd, + MIXER_READ (SOUND_MIXER_STEREODEVS), + &device->priv->stereodevs); + if (ret < 0) + goto fail; + + ret = ioctl (device->priv->fd, + MIXER_READ (SOUND_MIXER_RECMASK), + &device->priv->recmask); + if (ret < 0) + goto fail; + + /* The recording source mask may change at any time, here we just read + * the initial value */ + ret = ioctl (device->priv->fd, + MIXER_READ (SOUND_MIXER_RECSRC), + &device->priv->recsrc); + if (ret < 0) + goto fail; + + /* NOTE: Linux also supports SOUND_MIXER_OUTSRC and SOUND_MIXER_OUTMASK which + * inform about/enable input->output, we could potentially create toggles + * for these, but these constants are not defined on any BSD. */ + + return TRUE; + +fail: + g_warning ("Failed to read device %s: %s", + device->priv->path, + g_strerror (errno)); + + return FALSE; +} + +gboolean +oss_device_load (OssDevice *device) +{ + MateMixerStreamControl *control; + + g_return_val_if_fail (OSS_IS_DEVICE (device), FALSE); + + read_mixer_devices (device); + + control = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (device->priv->input)); + if (control == NULL) { + // XXX pick something + } + + if (control != NULL) + g_debug ("Default input stream control is %s", + mate_mixer_stream_control_get_label (control)); + + control = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (device->priv->output)); + if (control == NULL) { + // XXX pick something + } + + if (control != NULL) + g_debug ("Default output stream control is %s", + mate_mixer_stream_control_get_label (control)); + + return TRUE; +} + +gint +oss_device_get_fd (OssDevice *device) +{ + g_return_val_if_fail (OSS_IS_DEVICE (device), -1); + + return device->priv->fd; +} + +const gchar * +oss_device_get_path (OssDevice *device) +{ + g_return_val_if_fail (OSS_IS_DEVICE (device), NULL); + + return device->priv->path; +} + +OssStream * +oss_device_get_input_stream (OssDevice *device) +{ + g_return_val_if_fail (OSS_IS_DEVICE (device), NULL); + + return device->priv->input; +} + +OssStream * +oss_device_get_output_stream (OssDevice *device) +{ + g_return_val_if_fail (OSS_IS_DEVICE (device), NULL); + + return device->priv->output; +} + +static GList * +oss_device_list_streams (MateMixerDevice *mmd) +{ + OssDevice *device; + GList *list = NULL; + + g_return_val_if_fail (OSS_IS_DEVICE (mmd), NULL); + + device = OSS_DEVICE (mmd); + + if (device->priv->output != NULL) + list = g_list_prepend (list, g_object_ref (device->priv->output)); + if (device->priv->input != NULL) + list = g_list_prepend (list, g_object_ref (device->priv->input)); + + return list; +} + +#define OSS_MASK_HAS_DEVICE(mask,i) ((gboolean) (((mask) & (1 << (i))) > 0)) + +static gboolean +read_mixer_devices (OssDevice *device) +{ + gint i; + + for (i = 0; i < OSS_N_DEVICES; i++) { + OssStreamControl *control; + gboolean input = FALSE; + + /* Skip unavailable controls */ + if (OSS_MASK_HAS_DEVICE (device->priv->devmask, i) == FALSE) + continue; + + if (oss_devices[i].type == OSS_DEV_ANY) { + input = OSS_MASK_HAS_DEVICE (device->priv->recmask, i); + } + else if (oss_devices[i].type == OSS_DEV_INPUT) { + input = TRUE; + } + + control = oss_stream_control_new (oss_devices[i].name, + oss_devices[i].label, + oss_devices[i].role, + device->priv->fd, + i, + OSS_MASK_HAS_DEVICE (device->priv->stereodevs, i)); + + if (input == TRUE) { + oss_stream_add_control (OSS_STREAM (device->priv->input), control); + + if (i == SOUND_MIXER_RECLEV || i == SOUND_MIXER_IGAIN) { + if (i == SOUND_MIXER_RECLEV) + set_stream_default_control (OSS_STREAM (device->priv->input), + control, + TRUE); + else + set_stream_default_control (OSS_STREAM (device->priv->input), + control, + FALSE); + } + } else { + oss_stream_add_control (OSS_STREAM (device->priv->output), control); + + if (i == SOUND_MIXER_VOLUME || i == SOUND_MIXER_PCM) { + if (i == SOUND_MIXER_VOLUME) + set_stream_default_control (OSS_STREAM (device->priv->output), + control, + TRUE); + else + set_stream_default_control (OSS_STREAM (device->priv->output), + control, + FALSE); + } + } + + g_debug ("Added control %s", + mate_mixer_stream_control_get_label (MATE_MIXER_STREAM_CONTROL (control))); + + oss_stream_control_update (control); + } + return TRUE; +} + +static gboolean +set_stream_default_control (OssStream *stream, OssStreamControl *control, gboolean force) +{ + MateMixerStreamControl *current; + + current = mate_mixer_stream_get_default_control (MATE_MIXER_STREAM (stream)); + if (current == NULL || force == TRUE) { + oss_stream_set_default_control (stream, OSS_STREAM_CONTROL (control)); + return TRUE; + } + return FALSE; +} diff --git a/backends/oss/oss-device.h b/backends/oss/oss-device.h new file mode 100644 index 0000000..261a884 --- /dev/null +++ b/backends/oss/oss-device.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef OSS_DEVICE_H +#define OSS_DEVICE_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "oss-stream.h" + +G_BEGIN_DECLS + +#define OSS_TYPE_DEVICE \ + (oss_device_get_type ()) +#define OSS_DEVICE(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), OSS_TYPE_DEVICE, OssDevice)) +#define OSS_IS_DEVICE(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSS_TYPE_DEVICE)) +#define OSS_DEVICE_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), OSS_TYPE_DEVICE, OssDeviceClass)) +#define OSS_IS_DEVICE_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), OSS_TYPE_DEVICE)) +#define OSS_DEVICE_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_DEVICE, OssDeviceClass)) + +typedef struct _OssDevice OssDevice; +typedef struct _OssDeviceClass OssDeviceClass; +typedef struct _OssDevicePrivate OssDevicePrivate; + +struct _OssDevice +{ + MateMixerDevice parent; + + /*< private >*/ + OssDevicePrivate *priv; +}; + +struct _OssDeviceClass +{ + MateMixerDeviceClass parent; +}; + +GType oss_device_get_type (void) G_GNUC_CONST; + +OssDevice * oss_device_new (const gchar *name, + const gchar *label, + const gchar *path, + gint fd); + +gboolean oss_device_open (OssDevice *device); +gboolean oss_device_load (OssDevice *device); + +gint oss_device_get_fd (OssDevice *device); +const gchar *oss_device_get_path (OssDevice *device); + +OssStream * oss_device_get_input_stream (OssDevice *device); +OssStream * oss_device_get_output_stream (OssDevice *device); + +G_END_DECLS + +#endif /* OSS_DEVICE_H */ diff --git a/backends/oss/oss-stream-control.c b/backends/oss/oss-stream-control.c new file mode 100644 index 0000000..5161528 --- /dev/null +++ b/backends/oss/oss-stream-control.c @@ -0,0 +1,392 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <glib.h> +#include <glib/gstdio.h> +#include <glib-object.h> + +#include <libmatemixer/matemixer.h> +#include <libmatemixer/matemixer-private.h> + +#include "oss-common.h" +#include "oss-stream-control.h" + +struct _OssStreamControlPrivate +{ + gint fd; + gint devnum; + guint volume[2]; + gfloat balance; + gboolean stereo; + MateMixerStreamControlRole role; + MateMixerStreamControlFlags flags; +}; + +static void oss_stream_control_class_init (OssStreamControlClass *klass); + +static void oss_stream_control_init (OssStreamControl *control); +static void oss_stream_control_finalize (GObject *object); + +G_DEFINE_TYPE (OssStreamControl, oss_stream_control, MATE_MIXER_TYPE_STREAM_CONTROL) + +static gboolean oss_stream_control_set_mute (MateMixerStreamControl *mmsc, + gboolean mute); + +static guint oss_stream_control_get_num_channels (MateMixerStreamControl *mmsc); + +static guint oss_stream_control_get_volume (MateMixerStreamControl *mmsc); + +static gboolean oss_stream_control_set_volume (MateMixerStreamControl *mmsc, + guint volume); + +static gboolean oss_stream_control_has_channel_position (MateMixerStreamControl *mmsc, + MateMixerChannelPosition position); +static MateMixerChannelPosition oss_stream_control_get_channel_position (MateMixerStreamControl *mmsc, + guint channel); + +static guint oss_stream_control_get_channel_volume (MateMixerStreamControl *mmsc, + guint channel); +static gboolean oss_stream_control_set_channel_volume (MateMixerStreamControl *mmsc, + guint channel, + guint volume); + +static gboolean oss_stream_control_set_balance (MateMixerStreamControl *mmsc, + gfloat balance); + +static guint oss_stream_control_get_min_volume (MateMixerStreamControl *mmsc); +static guint oss_stream_control_get_max_volume (MateMixerStreamControl *mmsc); +static guint oss_stream_control_get_normal_volume (MateMixerStreamControl *mmsc); +static guint oss_stream_control_get_base_volume (MateMixerStreamControl *mmsc); + +static gboolean write_volume (OssStreamControl *control, + gint volume); + +static void +oss_stream_control_class_init (OssStreamControlClass *klass) +{ + GObjectClass *object_class; + MateMixerStreamControlClass *control_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = oss_stream_control_finalize; + + control_class = MATE_MIXER_STREAM_CONTROL_CLASS (klass); + control_class->set_mute = oss_stream_control_set_mute; + control_class->get_num_channels = oss_stream_control_get_num_channels; + control_class->get_volume = oss_stream_control_get_volume; + control_class->set_volume = oss_stream_control_set_volume; + control_class->get_channel_volume = oss_stream_control_get_channel_volume; + control_class->set_channel_volume = oss_stream_control_set_channel_volume; + control_class->get_channel_position = oss_stream_control_get_channel_position; + control_class->has_channel_position = oss_stream_control_has_channel_position; + control_class->set_balance = oss_stream_control_set_balance; + control_class->get_min_volume = oss_stream_control_get_min_volume; + control_class->get_max_volume = oss_stream_control_get_max_volume; + control_class->get_normal_volume = oss_stream_control_get_normal_volume; + control_class->get_base_volume = oss_stream_control_get_base_volume; + + g_type_class_add_private (object_class, sizeof (OssStreamControlPrivate)); +} + +static void +oss_stream_control_init (OssStreamControl *control) +{ + control->priv = G_TYPE_INSTANCE_GET_PRIVATE (control, + OSS_TYPE_STREAM_CONTROL, + OssStreamControlPrivate); +} + +static void +oss_stream_control_finalize (GObject *object) +{ + OssStreamControl *control; + + control = OSS_STREAM_CONTROL (object); + + close (control->priv->fd); + + G_OBJECT_CLASS (oss_stream_control_parent_class)->finalize (object); +} + +OssStreamControl * +oss_stream_control_new (const gchar *name, + const gchar *label, + MateMixerStreamControlRole role, + gint fd, + gint devnum, + gboolean stereo) +{ + OssStreamControl *control; + MateMixerStreamControlFlags flags; + + flags = MATE_MIXER_STREAM_CONTROL_HAS_VOLUME | + MATE_MIXER_STREAM_CONTROL_CAN_SET_VOLUME; + if (stereo == TRUE) + flags |= MATE_MIXER_STREAM_CONTROL_CAN_BALANCE; + + control = g_object_new (OSS_TYPE_STREAM_CONTROL, + "name", name, + "label", label, + "flags", flags, + NULL); + + control->priv->fd = fd; + control->priv->devnum = devnum; + control->priv->stereo = stereo; + return control; +} + +gboolean +oss_stream_control_update (OssStreamControl *control) +{ + gint v; + gint ret; + + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), FALSE); + + ret = ioctl (control->priv->fd, MIXER_READ (control->priv->devnum), &v); + if (ret < 0) { + g_warning ("Failed to read volume: %s", g_strerror (errno)); + return FALSE; + } + + control->priv->volume[0] = v & 0xFF; + + if (control->priv->stereo == TRUE) { + gfloat balance; + guint left; + guint right; + + control->priv->volume[1] = (v >> 8) & 0xFF; + + /* Calculate balance */ + left = control->priv->volume[0]; + right = control->priv->volume[1]; + if (left == right) + balance = 0.0f; + else if (left > right) + balance = -1.0f + ((gfloat) right / (gfloat) left); + else + balance = +1.0f - ((gfloat) left / (gfloat) right); + + _mate_mixer_stream_control_set_balance (MATE_MIXER_STREAM_CONTROL (control), + balance); + } + return TRUE; +} + +static gboolean +oss_stream_control_set_mute (MateMixerStreamControl *mmsc, gboolean mute) +{ + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE); + + // TODO + return TRUE; +} + +static guint +oss_stream_control_get_num_channels (MateMixerStreamControl *mmsc) +{ + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), 0); + + return (OSS_STREAM_CONTROL (mmsc)->priv->stereo == TRUE) ? 2 : 1; +} + +static guint +oss_stream_control_get_volume (MateMixerStreamControl *mmsc) +{ + OssStreamControl *control; + + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), 0); + + control = OSS_STREAM_CONTROL (mmsc); + + if (control->priv->stereo == TRUE) + return MAX (control->priv->volume[0], control->priv->volume[1]); + else + return control->priv->volume[0]; +} + +static gboolean +oss_stream_control_set_volume (MateMixerStreamControl *mmsc, guint volume) +{ + OssStreamControl *control; + gint v; + + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE); + + control = OSS_STREAM_CONTROL (mmsc); + + v = CLAMP (volume, 0, 100); + if (control->priv->stereo == TRUE) + v |= (volume & 0xFF) << 8; + + return write_volume (control, v); +} + +static guint +oss_stream_control_get_channel_volume (MateMixerStreamControl *mmsc, guint channel) +{ + OssStreamControl *control; + + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), 0); + + control = OSS_STREAM_CONTROL (mmsc); + + if (control->priv->stereo == TRUE) { + if (channel == 0 || channel == 1) + return control->priv->volume[channel]; + } else { + if (channel == 0) + return control->priv->volume[0]; + } + return 0; +} + +static gboolean +oss_stream_control_set_channel_volume (MateMixerStreamControl *mmsc, + guint channel, + guint volume) +{ + OssStreamControl *control; + gint v; + + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE); + + control = OSS_STREAM_CONTROL (mmsc); + volume = CLAMP (volume, 0, 100); + + if (control->priv->stereo == TRUE) { + if (channel > 1) + return FALSE; + + /* Stereo channel volume - left channel is in the lowest 8 bits and + * right channel is in the higher 8 bits */ + if (channel == 0) + v = volume | (control->priv->volume[1] << 8); + else + v = control->priv->volume[0] | (volume << 8); + } else { + if (channel > 0) + return FALSE; + + /* Single channel volume - only lowest 8 bits are used */ + v = volume; + } + + return write_volume (control, v); +} + +static MateMixerChannelPosition +oss_stream_control_get_channel_position (MateMixerStreamControl *mmsc, guint channel) +{ + OssStreamControl *control; + + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), MATE_MIXER_CHANNEL_UNKNOWN); + + control = OSS_STREAM_CONTROL (mmsc); + + if (control->priv->stereo == TRUE) { + if (channel == 0) + return MATE_MIXER_CHANNEL_FRONT_LEFT; + else if (channel == 1) + return MATE_MIXER_CHANNEL_FRONT_RIGHT; + } else { + if (channel == 0) + return MATE_MIXER_CHANNEL_MONO; + } + return MATE_MIXER_CHANNEL_UNKNOWN; +} + +static gboolean +oss_stream_control_has_channel_position (MateMixerStreamControl *mmsc, + MateMixerChannelPosition position) +{ + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE); + + if (position == MATE_MIXER_CHANNEL_MONO) + return OSS_STREAM_CONTROL (mmsc)->priv->stereo == FALSE; + + if (position == MATE_MIXER_CHANNEL_FRONT_LEFT || + position == MATE_MIXER_CHANNEL_FRONT_RIGHT) + return OSS_STREAM_CONTROL (mmsc)->priv->stereo == TRUE; + + return FALSE; +} + +static gboolean +oss_stream_control_set_balance (MateMixerStreamControl *mmsc, gfloat balance) +{ + OssStreamControl *control; + guint max; + gint v; + + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (mmsc), FALSE); + + control = OSS_STREAM_CONTROL (mmsc); + + max = MAX (control->priv->volume[0], control->priv->volume[1]); + if (balance <= 0) { + control->priv->volume[1] = (balance + 1.0f) * max; + control->priv->volume[0] = max; + } else { + control->priv->volume[0] = (1.0f - balance) * max; + control->priv->volume[1] = max; + } + + v = control->priv->volume[0] | (control->priv->volume[1] << 8); + + return write_volume (control, v); +} + +static guint +oss_stream_control_get_min_volume (MateMixerStreamControl *mmsc) +{ + return 0; +} + +static guint +oss_stream_control_get_max_volume (MateMixerStreamControl *mmsc) +{ + return 100; +} + +static guint +oss_stream_control_get_normal_volume (MateMixerStreamControl *mmsc) +{ + return 100; +} + +static guint +oss_stream_control_get_base_volume (MateMixerStreamControl *mmsc) +{ + return 100; +} + +static gboolean +write_volume (OssStreamControl *control, gint volume) +{ + gint ret; + + ret = ioctl (control->priv->fd, MIXER_WRITE (control->priv->devnum), &volume); + if (ret < 0) { + g_warning ("Failed to set volume: %s", g_strerror (errno)); + return FALSE; + } + return TRUE; +} diff --git a/backends/oss/oss-stream-control.h b/backends/oss/oss-stream-control.h new file mode 100644 index 0000000..c839faf --- /dev/null +++ b/backends/oss/oss-stream-control.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef OSS_STREAM_CONTROL_H +#define OSS_STREAM_CONTROL_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +G_BEGIN_DECLS + +#define OSS_TYPE_STREAM_CONTROL \ + (oss_stream_control_get_type ()) +#define OSS_STREAM_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), OSS_TYPE_STREAM_CONTROL, OssStreamControl)) +#define OSS_IS_STREAM_CONTROL(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSS_TYPE_STREAM_CONTROL)) +#define OSS_STREAM_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), OSS_TYPE_STREAM_CONTROL, OssStreamControlClass)) +#define OSS_IS_STREAM_CONTROL_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), OSS_TYPE_STREAM_CONTROL)) +#define OSS_STREAM_CONTROL_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_STREAM_CONTROL, OssStreamControlClass)) + +typedef struct _OssStreamControl OssStreamControl; +typedef struct _OssStreamControlClass OssStreamControlClass; +typedef struct _OssStreamControlPrivate OssStreamControlPrivate; + +struct _OssStreamControl +{ + MateMixerStreamControl parent; + + /*< private >*/ + OssStreamControlPrivate *priv; +}; + +struct _OssStreamControlClass +{ + MateMixerStreamControlClass parent; +}; + +GType oss_stream_control_get_type (void) G_GNUC_CONST; + +OssStreamControl *oss_stream_control_new (const gchar *name, + const gchar *label, + MateMixerStreamControlRole role, + gint fd, + gint devnum, + gboolean stereo); + +gboolean oss_stream_control_update (OssStreamControl *control); + +G_END_DECLS + +#endif /* OSS_STREAM_CONTROL_H */ diff --git a/backends/oss/oss-stream.c b/backends/oss/oss-stream.c new file mode 100644 index 0000000..5f0c629 --- /dev/null +++ b/backends/oss/oss-stream.c @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "oss-device.h" +#include "oss-stream.h" +#include "oss-stream-control.h" + +struct _OssStreamPrivate +{ + GHashTable *controls; + OssStreamControl *control; +}; + +static void oss_stream_class_init (OssStreamClass *klass); + +static void oss_stream_init (OssStream *ostream); +static void oss_stream_dispose (GObject *object); +static void oss_stream_finalize (GObject *object); + +G_DEFINE_TYPE (OssStream, oss_stream, MATE_MIXER_TYPE_STREAM) + +static MateMixerStreamControl *oss_stream_get_control (MateMixerStream *stream, + const gchar *name); +static MateMixerStreamControl *oss_stream_get_default_control (MateMixerStream *stream); + +static GList * oss_stream_list_controls (MateMixerStream *stream); + +static void +oss_stream_class_init (OssStreamClass *klass) +{ + GObjectClass *object_class; + MateMixerStreamClass *stream_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = oss_stream_dispose; + object_class->finalize = oss_stream_finalize; + + stream_class = MATE_MIXER_STREAM_CLASS (klass); + stream_class->get_control = oss_stream_get_control; + stream_class->get_default_control = oss_stream_get_default_control; + stream_class->list_controls = oss_stream_list_controls; + + g_type_class_add_private (object_class, sizeof (OssStreamPrivate)); +} + +static void +oss_stream_init (OssStream *stream) +{ + stream->priv = G_TYPE_INSTANCE_GET_PRIVATE (stream, + OSS_TYPE_STREAM, + OssStreamPrivate); + + stream->priv->controls = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); +} + +static void +oss_stream_dispose (GObject *object) +{ + OssStream *stream; + + stream = OSS_STREAM (object); + + g_clear_object (&stream->priv->control); + g_hash_table_remove_all (stream->priv->controls); + + G_OBJECT_CLASS (oss_stream_parent_class)->dispose (object); +} + +static void +oss_stream_finalize (GObject *object) +{ + OssStream *stream; + + stream = OSS_STREAM (object); + + g_hash_table_destroy (stream->priv->controls); + + G_OBJECT_CLASS (oss_stream_parent_class)->finalize (object); +} + +OssStream * +oss_stream_new (const gchar *name, + MateMixerDevice *device, + MateMixerStreamFlags flags) +{ + OssStream *stream; + + stream = g_object_new (OSS_TYPE_STREAM, + "name", name, + "device", device, + "flags", flags, + NULL); + return stream; +} + +gboolean +oss_stream_add_control (OssStream *stream, OssStreamControl *control) +{ + const gchar *name; + + g_return_val_if_fail (OSS_IS_STREAM (stream), FALSE); + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), FALSE); + + name = mate_mixer_stream_control_get_name (MATE_MIXER_STREAM_CONTROL (control)); + + g_hash_table_insert (stream->priv->controls, + g_strdup (name), + control); + return TRUE; +} + +gboolean +oss_stream_set_default_control (OssStream *stream, OssStreamControl *control) +{ + g_return_val_if_fail (OSS_IS_STREAM (stream), FALSE); + g_return_val_if_fail (OSS_IS_STREAM_CONTROL (control), FALSE); + + /* This function is only used internally so avoid validating that the control + * belongs to this stream */ + if (stream->priv->control != NULL) + g_object_unref (stream->priv->control); + + if G_LIKELY (control != NULL) + stream->priv->control = g_object_ref (control); + else + stream->priv->control = NULL; + + return TRUE; +} + +static MateMixerStreamControl * +oss_stream_get_control (MateMixerStream *mms, const gchar *name) +{ + g_return_val_if_fail (OSS_IS_STREAM (mms), NULL); + g_return_val_if_fail (name != NULL, NULL); + + return g_hash_table_lookup (OSS_STREAM (mms)->priv->controls, name); +} + +static MateMixerStreamControl * +oss_stream_get_default_control (MateMixerStream *mms) +{ + g_return_val_if_fail (OSS_IS_STREAM (mms), NULL); + + return MATE_MIXER_STREAM_CONTROL (OSS_STREAM (mms)->priv->control); +} + +static GList * +oss_stream_list_controls (MateMixerStream *mms) +{ + GList *list; + + g_return_val_if_fail (OSS_IS_STREAM (mms), NULL); + + /* Convert the hash table to a linked list, this list is expected to be + * cached in the main library */ + list = g_hash_table_get_values (OSS_STREAM (mms)->priv->controls); + if (list != NULL) + g_list_foreach (list, (GFunc) g_object_ref, NULL); + + return list; +} diff --git a/backends/oss/oss-stream.h b/backends/oss/oss-stream.h new file mode 100644 index 0000000..0470eb7 --- /dev/null +++ b/backends/oss/oss-stream.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 Michal Ratajsky <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the licence, 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef OSS_STREAM_H +#define OSS_STREAM_H + +#include <glib.h> +#include <glib-object.h> +#include <libmatemixer/matemixer.h> + +#include "oss-device.h" +#include "oss-stream-control.h" + +G_BEGIN_DECLS + +#define OSS_TYPE_STREAM \ + (oss_stream_get_type ()) +#define OSS_STREAM(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), OSS_TYPE_STREAM, OssStream)) +#define OSS_IS_STREAM(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), OSS_TYPE_STREAM)) +#define OSS_STREAM_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST ((k), OSS_TYPE_STREAM, OssStreamClass)) +#define OSS_IS_STREAM_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE ((k), OSS_TYPE_STREAM)) +#define OSS_STREAM_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), OSS_TYPE_STREAM, OssStreamClass)) + +typedef struct _OssStream OssStream; +typedef struct _OssStreamClass OssStreamClass; +typedef struct _OssStreamPrivate OssStreamPrivate; + +struct _OssStream +{ + MateMixerStream parent; + + /*< private >*/ + OssStreamPrivate *priv; +}; + +struct _OssStreamClass +{ + MateMixerStreamClass parent; +}; + +GType oss_stream_get_type (void) G_GNUC_CONST; + +OssStream *oss_stream_new (const gchar *name, + MateMixerDevice *device, + MateMixerStreamFlags flags); + +gboolean oss_stream_add_control (OssStream *stream, + OssStreamControl *control); + +gboolean oss_stream_set_default_control (OssStream *stream, + OssStreamControl *control); + +G_END_DECLS + +#endif /* OSS_STREAM_H */ |