fade: Switch to modular mixer API.
authorAndre Noll <maan@systemlinux.org>
Mon, 16 Jul 2012 19:29:19 +0000 (21:29 +0200)
committerAndre Noll <maan@systemlinux.org>
Sun, 7 Oct 2012 09:12:07 +0000 (11:12 +0200)
Although only the oss mixer is supported ATM, this commit paves the way
for supporting more than one mixer API. It moves the oss-specific part
of para_fade to the new oss_mix.c file added by the previous commit.

Different mixer APIs will support different sets of mixer channels,
so handling of the various mixer channels is changed from a ggo enum
config option to a string option which allows to specify arbitrary
strings. The mixer implementations must check the given strings
themselves and are supposed to define a ->get_channels method which
returns the available channels. This function is called from fade.c
if an invalid channel is given.

The default mixer device, currently hardcoded in the ggo file to
"/dev/mixer", is fine for OSS. The ALSA mixer will need another default
however. Therefore this patch removes the default setting from the ggo
file so that the corresponding ggo config variable will be NULL if no
mixer device was given. The mixer implementations are supposed to check
this variable and fall back to their individual defaults if it is NULL.

error.h
fade.c
m4/gengetopt/fade.m4
mix.h [new file with mode: 0644]
oss_mix.c

diff --git a/error.h b/error.h
index 59c2076..c7c5776 100644 (file)
--- a/error.h
+++ b/error.h
@@ -33,10 +33,13 @@ DEFINE_ERRLIST_OBJECT_ENUM;
 #define FILE_WRITE_ERRORS
 #define STDIN_ERRORS
 #define WRITE_ERRORS
-#define OSS_MIX_ERRORS
 
 extern const char **para_errlist[];
 
+#define OSS_MIX_ERRORS \
+       PARA_ERROR(OSS_MIXER_CHANNEL, "invalid mixer channel"), \
+
+
 #define SIDEBAND_ERRORS \
        PARA_ERROR(BAD_BAND, "invalid or unexpected band designator"), \
        PARA_ERROR(SB_PACKET_SIZE, "invalid sideband packet size or protocol error"), \
diff --git a/fade.c b/fade.c
index 36ff1fc..1bfa0ba 100644 (file)
--- a/fade.c
+++ b/fade.c
@@ -7,21 +7,23 @@
 /** \file fade.c A volume fader and alarm clock for OSS. */
 
 #include <regex.h>
-#include <sys/types.h>
-#include <sys/ioctl.h>
-#include <signal.h>
-#include <sys/soundcard.h>
 
 #include "fade.cmdline.h"
 #include "para.h"
 #include "fd.h"
 #include "string.h"
+#include "mix.h"
 #include "error.h"
 #include "version.h"
 
 INIT_FADE_ERRLISTS;
 static struct fade_args_info conf;
 
+enum mixer_id {MIXER_ENUM};
+static char *mixer_name[] = {MIXER_NAMES};
+DECLARE_MIXER_INITS;
+static struct mixer supported_mixer[] = {MIXER_ARRAY};
+
 __printf_2_3 void date_log(__a_unused int ll, const char *fmt, ...)
 {
        va_list argp;
@@ -37,102 +39,10 @@ __printf_2_3 void date_log(__a_unused int ll, const char *fmt, ...)
 }
 __printf_2_3 void (*para_log)(int, const char*, ...) = date_log;
 
-/*
- * Open the mixer device.
- */
-static int open_mixer(void)
-{
-       PARA_INFO_LOG("opening %s\n", conf.mixer_device_arg);
-       return para_open(conf.mixer_device_arg, O_RDWR, 42);
-}
-
-/*
- * Get channel volume via mixer_fd.
- */
-static int get_mixer_channel(int mixer_fd)
-{
-       int volume;
-
-       if (ioctl(mixer_fd, MIXER_READ(conf.mixer_channel_arg), &volume) < 0)
-               return -ERRNO_TO_PARA_ERROR(errno);
-       /* take the mean value of left and right volume */
-       return (volume % 256 + (volume >> 8)) / 2;
-}
-
-/*
- * Open mixer, get volume and close mixer.
- */
-static int open_and_get_mixer_channel(void)
-{
-       int mixer_fd;
-       int volume;
-
-       mixer_fd = open_mixer();
-       if (mixer_fd < 0)
-               return mixer_fd;
-       volume = get_mixer_channel(mixer_fd);
-       close(mixer_fd);
-       return volume;
-}
-
-/*
- * Set channel volume via mixer_fd.
- */
-static int set_mixer_channel(int mixer_fd, int volume)
-{
-       int tmp = (volume << 8) + volume;
-
-       if (ioctl(mixer_fd, MIXER_WRITE(conf.mixer_channel_arg), &tmp) < 0)
-               return -ERRNO_TO_PARA_ERROR(errno);
-       return 1;
-}
-
-/*
- * Open mixer, set volume and close mixer.
- */
-static int open_and_set_mixer_channel(int volume)
-{
-       int mixer_fd, ret = open_mixer();
-
-       if (ret < 0)
-               return ret;
-       mixer_fd = ret;
-       ret = set_mixer_channel(mixer_fd, volume);
-       close(mixer_fd);
-       return ret;
-}
-
-static void fixup_mixer_channel_arg(void)
+/* Fade to new volume in fade_time seconds. */
+static int fade(struct mixer *m, struct mixer_handle *h, int new_vol, int fade_time)
 {
-       int val = SOUND_MIXER_VOLUME; /* STFU, gcc */
-
-       switch (conf.mixer_channel_arg) {
-               case mixer_channel_arg_volume: val = SOUND_MIXER_VOLUME; break;
-               case mixer_channel_arg_bass: val = SOUND_MIXER_BASS; break;
-               case mixer_channel_arg_treble: val = SOUND_MIXER_TREBLE; break;
-               case mixer_channel_arg_synth: val = SOUND_MIXER_SYNTH; break;
-               case mixer_channel_arg_pcm: val = SOUND_MIXER_PCM; break;
-               case mixer_channel_arg_speaker: val = SOUND_MIXER_SPEAKER; break;
-               case mixer_channel_arg_line: val = SOUND_MIXER_LINE; break;
-               case mixer_channel_arg_mic: val = SOUND_MIXER_MIC; break;
-               case mixer_channel_arg_cd: val = SOUND_MIXER_CD; break;
-               case mixer_channel_arg_imix: val = SOUND_MIXER_IMIX; break;
-               case mixer_channel_arg_altpcm: val = SOUND_MIXER_ALTPCM; break;
-               case mixer_channel_arg_reclev: val = SOUND_MIXER_RECLEV; break;
-               case mixer_channel_arg_igain: val = SOUND_MIXER_IGAIN; break;
-               case mixer_channel_arg_ogain: val = SOUND_MIXER_OGAIN; break;
-               default: break;
-       }
-       conf.mixer_channel_arg = val;
-}
-
-/*
- * Open mixer, get volume, fade to new_vol in secs seconds and
- * close mixer.
- */
-static int fade(int new_vol, int fade_time)
-{
-       int vol, mixer_fd, diff, incr, ret;
+       int vol, diff, incr, ret;
        unsigned secs;
        struct timespec ts;
        unsigned long long tmp, tmp2; /* Careful with that axe, Eugene! */
@@ -141,11 +51,7 @@ static int fade(int new_vol, int fade_time)
                return 1;
        secs = fade_time;
        PARA_NOTICE_LOG("fading to %d in %d seconds\n", new_vol, secs);
-       ret = open_mixer();
-       if (ret < 0)
-               return ret;
-       mixer_fd = ret;
-       ret = get_mixer_channel(mixer_fd);
+       ret = m->get(h);
        if (ret < 0)
                goto out;
        vol = ret;
@@ -163,14 +69,13 @@ static int fade(int new_vol, int fade_time)
                ts.tv_sec = tmp / 1000; /* really nec ?*/
                //printf("ts.tv_sec: %i\n", ts.tv_nsec);
                vol += incr;
-               ret = set_mixer_channel(mixer_fd, vol);
+               ret = m->set(h, vol);
                if (ret < 0)
                        goto out;
                //printf("vol = %i\n", vol);
                nanosleep(&ts, NULL);
        }
 out:
-       close(mixer_fd);
        return ret;
 }
 
@@ -215,10 +120,7 @@ static void change_afs_mode_and_play(char *afs_mode)
        client_cmd("play");
 }
 
-/*
- * sleep
- */
-static int sweet_dreams(void)
+static int sweet_dreams(struct mixer *m, struct mixer_handle *h)
 {
        time_t t1, wake_time_epoch;
        unsigned int delay;
@@ -256,15 +158,15 @@ static int sweet_dreams(void)
        sleep(1);
        if (fot) {
                PARA_INFO_LOG("initial volume: %d\n", iv);
-               ret = open_and_set_mixer_channel(iv);
+               ret = m->set(h, iv);
                if (ret < 0)
                        return ret;
                change_afs_mode_and_play(fo_mood);
-               ret = fade(fov, fot);
+               ret = fade(m, h, fov, fot);
                if (ret < 0)
                        return ret;
        } else {
-               ret = open_and_set_mixer_channel(fov);
+               ret = m->set(h, fov);
                if (ret < 0)
                        return ret;
        }
@@ -285,28 +187,32 @@ static int sweet_dreams(void)
                sleep(delay);
        }
        change_afs_mode_and_play(fi_mood);
-       ret = fade(fiv, fit);
+       ret = fade(m, h, fiv, fit);
        PARA_INFO_LOG("fade complete, returning\n");
        return ret;
 }
 
-static int snooze(void)
+static int snooze(struct mixer *m, struct mixer_handle *h)
 {
-       int ret;
+       int ret, val;
 
        if (conf.so_time_arg <= 0)
                return 1;
-       if (open_and_get_mixer_channel() < conf.so_vol_arg)
-               ret = open_and_set_mixer_channel(conf.so_vol_arg);
+       ret = m->get(h);
+       if (ret < 0)
+               return ret;
+       val = ret;
+       if (val < conf.so_vol_arg)
+               ret = m->set(h, conf.so_vol_arg);
        else
-               ret = fade(conf.so_vol_arg, conf.so_time_arg);
+               ret = fade(m, h, conf.so_vol_arg, conf.so_time_arg);
        if (ret < 0)
                return ret;
        client_cmd("pause");
        PARA_NOTICE_LOG("%d seconds snooze time...\n", conf.snooze_time_arg);
        sleep(conf.snooze_time_arg);
        client_cmd("play");
-       return fade(conf.si_vol_arg, conf.si_time_arg);
+       return fade(m, h, conf.si_vol_arg, conf.si_time_arg);
 }
 
 static int configfile_exists(void)
@@ -323,9 +229,41 @@ static int configfile_exists(void)
        return file_exists(conf.config_file_arg);
 }
 
+static void init_mixers(void)
+{
+       int i;
+
+       for (i = 0; i < NUM_SUPPORTED_MIXERS; i++) {
+               struct mixer *m = &supported_mixer[i];
+               PARA_DEBUG_LOG("initializing mixer API #%d (%s)\n",
+                       i, mixer_name[i]);
+               m->init(m);
+       }
+}
+
+static int set_channel(struct mixer *m, struct mixer_handle *h)
+{
+       char *channels;
+       int ret;
+
+       ret = m->set_channel(h, conf.mixer_channel_arg);
+       if (ret >= 0) {
+               PARA_NOTICE_LOG("using %s mixer channel\n",
+                       conf.mixer_channel_arg?  conf.mixer_channel_arg
+                               : "default");
+               return ret;
+       }
+       channels = m->get_channels(h);
+       printf("Available channels: %s\n", channels);
+       free(channels);
+       return ret;
+}
+
 int main(int argc, char *argv[])
 {
        int ret;
+       struct mixer *m;
+       struct mixer_handle *h = NULL;
 
        if (fade_cmdline_parser(argc, argv, &conf))
                exit(EXIT_FAILURE);
@@ -347,18 +285,28 @@ int main(int argc, char *argv[])
                fade_cmdline_parser_config_file(conf.config_file_arg,
                        &conf, &params);
        }
-       fixup_mixer_channel_arg();
+       init_mixers();
+       m = &supported_mixer[DEFAULT_MIXER];
+       PARA_INFO_LOG("using %s mixer\n", mixer_name[DEFAULT_MIXER]);
+       ret = m->open(conf.mixer_device_arg, &h);
+       if (ret < 0)
+               goto out;
+       ret = set_channel(m, h);
+       if (ret < 0)
+               goto out;
        switch (conf.mode_arg) {
        case mode_arg_fade:
-               ret = fade(conf.fade_vol_arg, conf.fade_time_arg);
+               ret = fade(m, h, conf.fade_vol_arg, conf.fade_time_arg);
                break;
        case mode_arg_snooze:
-               ret = snooze();
+               ret = snooze(m, h);
                break;
        default: /* sleep mode */
-               ret = sweet_dreams();
+               ret = sweet_dreams(m, h);
                break;
        }
+out:
+       m->close(&h);
        if (ret < 0)
                PARA_EMERG_LOG("%s\n", para_strerror(-ret));
        return ret < 0? EXIT_FAILURE : EXIT_SUCCESS;
index ff4b451..e5d08bf 100644 (file)
@@ -34,23 +34,26 @@ option "config-file" c
 
 option "mixer-device" m
 #~~~~~~~~~~~~~~~~~~~~~~
-"mixer device file"
+"choose mixer device"
        string typestr = "device"
-       default = "/dev/mixer"
        optional
+       details = "
+               If this option is not given '/dev/mixer' is used as
+               the default.
+       "
 
 option "mixer-channel" C
 #~~~~~~~~~~~~~~~~~~~~~~~
 "select the analog mixer channel"
-       enum typestr = "channel"
-       values = "volume", "bass", "treble", "synth", "pcm", "speaker", "line",
-               "mic", "cd", "imix", "altpcm", "reclev", "igain", "ogain"
-       default = "volume"
+       string typestr = "channel"
        optional
        details = "
-               Not all listed channels might be supported on any
-               particular hardware.
-"
+               The possible values are 'volume', 'bass', 'treble',
+               'synth', 'pcm', 'speaker', 'line', 'mic', 'cd', 'imix',
+               'altpcm', 'reclev', 'igain', 'ogain'. However, not all
+               listed channels might be supported on any particular
+               hardware. The default mixer channel is 'volume'.
+       "
 
 section "Options for sleep mode"
 ################################
diff --git a/mix.h b/mix.h
new file mode 100644 (file)
index 0000000..a410554
--- /dev/null
+++ b/mix.h
@@ -0,0 +1,28 @@
+/**
+ * Opaque structure which corresponds to an instance of a mixer.
+ *
+ * A pointer to a structure of this type is returned by ->open().  This pointer
+ * must be passed to most other methods of \ref struct mixer.
+ */
+struct mixer_handle;
+
+/**
+ * Operations provided by each mixer plugin.
+ */
+struct mixer {
+       /** Called on startup, must fill in all other members. */
+       void (*init)(struct mixer *self);
+       /** Return a handle that can be passed to other methods. */
+       int (*open)(const char *dev, struct mixer_handle **handle);
+       /** Returns a string of all valid mixer channels. */
+       char *(*get_channels)(struct mixer_handle *handle);
+       /** Select the channel for subsequent get/set operations. */
+       int (*set_channel)(struct mixer_handle *handle,
+               const char *mixer_channel);
+       /** Return the (normalized) current value of the selected channel. */
+       int (*get)(struct mixer_handle *handle);
+       /** Change the value of the selected channel. */
+       int (*set)(struct mixer_handle *handle, int val);
+       /** Free all ressources associated with the given handle. */
+       void (*close)(struct mixer_handle **handle);
+};
index e69de29..b675647 100644 (file)
--- a/oss_mix.c
+++ b/oss_mix.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 1998-2012 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file oss_mix.c The OSS mixer plugin. */
+
+#include <sys/soundcard.h>
+#include <sys/ioctl.h>
+#include <regex.h>
+
+#include "para.h"
+#include "error.h"
+#include "mix.h"
+#include "fd.h"
+#include "string.h"
+
+struct oss_mixer_channel {
+       const char *name;
+       int id;
+};
+
+static const struct oss_mixer_channel channels[] = {
+       {.name = "volume", .id = SOUND_MIXER_VOLUME},
+       {.name = "bass", .id = SOUND_MIXER_BASS},
+       {.name = "treble", .id = SOUND_MIXER_TREBLE},
+       {.name = "synth", .id = SOUND_MIXER_SYNTH},
+       {.name = "pcm", .id = SOUND_MIXER_PCM},
+       {.name = "speaker", .id = SOUND_MIXER_SPEAKER},
+       {.name = "line", .id = SOUND_MIXER_LINE},
+       {.name = "mic", .id = SOUND_MIXER_MIC},
+       {.name = "cd", .id = SOUND_MIXER_CD},
+       {.name = "imix", .id = SOUND_MIXER_IMIX},
+       {.name = "altpcm", .id = SOUND_MIXER_ALTPCM},
+       {.name = "reclev", .id = SOUND_MIXER_RECLEV},
+       {.name = "igain", .id = SOUND_MIXER_IGAIN},
+       {.name = "ogain", .id = SOUND_MIXER_OGAIN},
+};
+
+/** Iterate over all defined mixer channels. */
+#define FOR_EACH_CHANNEL(i) for ((i) = 0; (i) < ARRAY_SIZE(channels); (i)++)
+
+struct mixer_handle {
+       int fd;
+       int id;
+};
+
+static int oss_mix_open(const char *dev, struct mixer_handle **handle)
+{
+       int ret;
+       struct mixer_handle *h;
+
+       *handle = NULL;
+       if (!dev)
+               dev = "/dev/mixer";
+       PARA_INFO_LOG("opening %s\n", dev);
+       ret = para_open(dev, O_RDWR, 42);
+       if (ret < 0)
+               return ret;
+       h = para_malloc(sizeof(*h));
+       h->fd = ret;
+       *handle = h;
+       return 1;
+}
+
+static char *oss_mix_get_channels(__a_unused struct mixer_handle *handle)
+{
+       int i;
+       char *list = NULL;
+
+       FOR_EACH_CHANNEL(i) {
+               const char *name = channels[i].name;
+               char *tmp = list;
+               list = make_message("%s%s%s",
+                       list? list : "",
+                       list? ", " : "",
+                       name);
+               free(tmp);
+       }
+       return list;
+}
+
+static int oss_mix_set_channel(struct mixer_handle *handle,
+               const char *mixer_channel)
+{
+       int i;
+
+       if (!mixer_channel) {
+               handle->id = SOUND_MIXER_VOLUME; /* default */
+               return 0;
+       }
+       FOR_EACH_CHANNEL(i) {
+               if (strcasecmp(channels[i].name, mixer_channel))
+                       continue;
+               handle->id = i;
+               return 1;
+       }
+       return -E_OSS_MIXER_CHANNEL;
+}
+
+static int oss_mix_get(struct mixer_handle *handle)
+{
+       int val, fd = handle->fd, id = handle->id;
+
+       if (ioctl(fd, MIXER_READ(id), &val) < 0)
+               return -ERRNO_TO_PARA_ERROR(errno);
+       /* take the mean value of left and right */
+       return (val % 256 + (val >> 8)) / 2;
+}
+
+static int oss_mix_set(struct mixer_handle *handle, int val)
+{
+       int fd = handle->fd, id = handle->id, tmp = (val << 8) + val;
+
+       if (ioctl(fd, MIXER_WRITE(id), &tmp) < 0)
+               return -ERRNO_TO_PARA_ERROR(errno);
+       return 1;
+}
+
+static void oss_mix_close(struct mixer_handle **handle)
+{
+       struct mixer_handle *h;
+
+       if (!handle)
+               return;
+       h = *handle;
+       if (h) {
+               if (h->fd >= 0)
+                       close(h->fd);
+               free(h);
+       }
+       *handle = NULL;
+}
+
+/**
+ * The init function of the OSS mixer.
+ *
+ * \param self The structure to initialize.
+ *
+ * \sa struct \ref mixer, \ref alsa_mix_init().
+ */
+void oss_mix_init(struct mixer *self)
+{
+       self->open = oss_mix_open;
+       self->get_channels = oss_mix_get_channels;
+       self->set_channel = oss_mix_set_channel;
+       self->get = oss_mix_get;
+       self->set = oss_mix_set;
+       self->close = oss_mix_close;
+}