From: Andre Noll Date: Sun, 4 Nov 2012 13:20:36 +0000 (+0100) Subject: Merge branch 't/fade_improvements' X-Git-Tag: v0.4.12~10 X-Git-Url: http://git.tuebingen.mpg.de/?a=commitdiff_plain;h=db2cd7b5404f5f01d25b72beacb3e5245b1bb94c;hp=c36307a2819b65845568696808430a3753e658e4;p=paraslash.git Merge branch 't/fade_improvements' Was cooking for almost a month, let's merge it. 483d31 fade: Upgrade client command log message to loglevel NOTICE. 49412f fade: Handle non-positive fade time gracefully. 376456 fade: Add --loglevel option. dec704 fade: Generate fade.ggo from m4 template. 0968b2 fade: Introduce --mixer-api to choose between ALSA and OSS. b4171b fade: Add the ALSA mixer implementation. 46af7d fade: Switch to modular mixer API. 702457 fade: Infrastructure for the modular mixer API. 3a2580 fade: Abort on client command failures. b618da fade: Remove some unnecessary includes. f502e6 configure: Add fade.o to object list only if oss was detected. --- diff --git a/NEWS b/NEWS index 6ee5086a..2c05b8f9 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ in the new "releases" branch. - Overhaul of the build system: All generated files are now written to the "build" directory. + - The modular mixer API and the alsa mixer. -------------------------------------- 0.4.11 (2012-07-20) "mutual diversity" diff --git a/alsa_mix.c b/alsa_mix.c new file mode 100644 index 00000000..5a583546 --- /dev/null +++ b/alsa_mix.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2012 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file alsa_mix.c The ALSA mixer plugin. */ + +#include +#include + +#include "para.h" +#include "error.h" +#include "mix.h" +#include "string.h" + +struct mixer_handle { + /* The ALSA mixer handle */ + snd_mixer_t *mixer; + /* Copy of the alsa device name, e.g. "hw:0". */ + char *card; + /* ALSA's representation of the given mixer channel. */ + snd_mixer_elem_t *elem; + /* Set in ->set_channel(), used for ->get() and ->set(). */ + long pmin, pmax; +}; + +static void alsa_mix_close(struct mixer_handle **handle) +{ + struct mixer_handle *h; + + if (!handle) + return; + h = *handle; + if (h) { + PARA_INFO_LOG("closing mixer handle\n"); + if (h->mixer) /* nec. */ + snd_mixer_close(h->mixer); + free(h->card); + free(h); + /* + * The global ALSA configuration is cached for next usage, + * which causes valgrind to report many memory leaks. Calling + * snd_config_update_free_global() frees the cache. + */ + snd_config_update_free_global(); + } + *handle = NULL; +} + +static int alsa_mix_open(const char *dev, struct mixer_handle **handle) +{ + int ret; + char *msg; + struct mixer_handle *h; + + PARA_INFO_LOG("snd_mixer_{open,attach,register,load}\n"); + *handle = NULL; + h = para_calloc(sizeof(*h)); + h->card = para_strdup(dev? dev : "hw:0"); + ret = snd_mixer_open(&h->mixer, 0); + if (ret < 0) { + msg = make_message("snd_mixer_open() failed: %s", + snd_strerror(ret)); + goto fail; + } + ret = snd_mixer_attach(h->mixer, h->card); + if (ret < 0) { + msg = make_message("mixer attach error (%s): %s", h->card, + snd_strerror(ret)); + goto fail; + } + ret = snd_mixer_selem_register(h->mixer, NULL, NULL); + if (ret < 0) { + msg = make_message("mixer register error (%s): %s", h->card, + snd_strerror(ret)); + goto fail; + } + ret = snd_mixer_load(h->mixer); + if (ret < 0) { + msg = make_message("mixer load error (%s): %s", h->card, + snd_strerror(ret)); + goto fail; + } + /* success */ + *handle = h; + return 1; +fail: + PARA_NOTICE_LOG("%s\n", msg); + free(msg); + alsa_mix_close(&h); + return -E_ALSA_MIX_OPEN; +} + +static bool channel_has_playback(snd_mixer_selem_channel_id_t chn, + snd_mixer_elem_t *elem) +{ + return snd_mixer_selem_has_playback_channel(elem, chn); +} + +static char *alsa_mix_get_channels(struct mixer_handle *handle) +{ + char *list = NULL; + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t *elem; + + snd_mixer_selem_id_alloca(&sid); + elem = snd_mixer_first_elem(handle->mixer); + for (; elem; elem = snd_mixer_elem_next(elem)) { + char *tmp = list; + const char *name; + + if (!channel_has_playback(0, elem)) + continue; + if (!snd_mixer_selem_has_playback_volume(elem)) + continue; + snd_mixer_selem_get_id(elem, sid); + name = snd_mixer_selem_id_get_name(sid); + list = make_message("%s%s%s", + list? list : "", + list? ", " : "", + name); + free(tmp); + } + return list; +} + +static int alsa_mix_set_channel(struct mixer_handle *h, + const char *mixer_channel) +{ + int ret, selem_id = 0; + snd_mixer_selem_id_t *sid; + + if (!mixer_channel) + mixer_channel = "Master"; + snd_mixer_selem_id_alloca(&sid); + snd_mixer_selem_id_set_index(sid, selem_id); + snd_mixer_selem_id_set_name(sid, mixer_channel); + h->elem = snd_mixer_find_selem(h->mixer, sid); + if (!h->elem) { + PARA_NOTICE_LOG("unable to find simple control '%s',%i\n", + snd_mixer_selem_id_get_name(sid), + snd_mixer_selem_id_get_index(sid)); + return -E_ALSA_MIX_BAD_ELEM; + } + ret = snd_mixer_selem_get_playback_volume_range(h->elem, + &h->pmin, &h->pmax); + if (ret < 0) { + PARA_NOTICE_LOG("unable to get %s range (%s): %s\n", + mixer_channel, h->card, snd_strerror(ret)); + return -E_ALSA_MIX_BAD_ELEM; + } + if (h->pmin < 0 || h->pmax < 0 || h->pmin >= h->pmax) { + PARA_NOTICE_LOG("alsa reported %s range %ld-%ld (%s)\n", + mixer_channel, h->pmin, h->pmax, h->card); + return -E_ALSA_MIX_BAD_ELEM; + } + return 1; +} + +static int alsa_mix_get(struct mixer_handle *h) +{ + snd_mixer_selem_channel_id_t chn; + int n = 0; + float avg = 0; + + /* compute average over all channels */ + for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) { + long val; + int ret; + + if (!channel_has_playback(chn, h->elem)) + continue; + ret = snd_mixer_selem_get_playback_volume(h->elem, chn, &val); + if (ret < 0) { + PARA_NOTICE_LOG("unable to get value for channel %d (%s): %s\n", + (int)chn, h->card, snd_strerror(ret)); + return -E_ALSA_MIX_GET_VAL; + } + /* update the rolling average */ + avg = (val + n * avg) / (n + 1); + n++; + } + /* scale to 0..100 */ + avg = 100 * (avg - h->pmin) / (h->pmax - h->pmin); + return avg; +} + +static int alsa_mix_set(struct mixer_handle *h, int val) +{ + int ret; + snd_mixer_selem_channel_id_t chn; + long scaled = val / 100.0 * (h->pmax - h->pmin) + h->pmin; + + PARA_INFO_LOG("new value: %d\n", val); + for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) { + if (!channel_has_playback(chn, h->elem)) + continue; + ret = snd_mixer_selem_set_playback_volume(h->elem, chn, scaled); + if (ret < 0) { + PARA_NOTICE_LOG("unable to set value for channel %d (%s): %s\n", + (int)chn, h->card, snd_strerror(ret)); + return -E_ALSA_MIX_SET_VAL; + } + } + return 1; +} + +/** + * The init function of the ALSA mixer. + * + * \param self The structure to initialize. + * + * \sa struct \ref mixer, \ref oss_mix_init(). + */ +void alsa_mix_init(struct mixer *self) +{ + self->open = alsa_mix_open; + self->get_channels = alsa_mix_get_channels; + self->set_channel = alsa_mix_set_channel; + self->close = alsa_mix_close; + self->get = alsa_mix_get; + self->set = alsa_mix_set; +} diff --git a/configure.ac b/configure.ac index ec8bfadd..32ad846d 100644 --- a/configure.ac +++ b/configure.ac @@ -7,6 +7,11 @@ AC_PREREQ([2.61]) AC_INIT([paraslash],[git],[maan@systemlinux.org]) AC_CONFIG_HEADER([config.h]) +AC_SUBST(install_sh, [$INSTALL]) +AC_CONFIG_FILES([Makefile]) +AC_DEFUN([add_dot_o],[$(for i in $@; do printf "$i.o "; done)]) +AC_DEFUN([add_para],[$(for i in $@; do printf "para_$i "; done)]) +AC_DEFUN([objlist_to_errlist],[$(for i in $@; do printf "DEFINE_ERRLIST($(echo $i| tr 'a-z' 'A-Z'));"; done) [const char **para_errlist[[]]] = {$(for i in $@; do printf "PARA_ERRLIST($(echo $i | tr 'a-z' 'A-Z')), "; done) }]) AC_PATH_PROG(UNAMEPATH, uname, no) if test "$UNAMEPATH" = "no"; then AC_MSG_ERROR(unable to determine system type) @@ -90,7 +95,7 @@ AC_DEFUN([add_cmdline],[$(for i in $@; do printf "${i}.cmdline "; done)]) all_errlist_objs="mp3_afh afh_common net string signal time daemon - stat afh fade amp_filter fd ringbuffer sched audiod + stat afh amp_filter fd ringbuffer sched audiod grab_client filter_common wav_filter compress_filter http_recv dccp_recv recv_common write_common file_write audiod_command client_common recv stdout filter stdin audioc write client @@ -146,11 +151,6 @@ client_ldflags="" gui_cmdline_objs="add_cmdline(gui)" gui_errlist_objs="exec signal string stat ringbuffer fd gui gui_theme" gui_objs="$gui_cmdline_objs $gui_errlist_objs" - -fade_cmdline_objs="add_cmdline(fade)" -fade_errlist_objs="fade exec string fd" - - ########################################################################### snprintf # =========================================================================== # http://www.nongnu.org/autoconf-archive/ax_func_snprintf.html @@ -804,19 +804,21 @@ OLD_LD_FLAGS="$LDFLAGS" OLD_LIBS="$LIBS" have_oss="yes" -msg="=> will not build para_fade/oss writer" +msg="=> will not build oss writer" AC_CHECK_HEADER(sys/soundcard.h, [ - extras="$extras fade" - executables="$executables fade" - all_errlist_objs="$all_errlist_objs oss_write" audiod_errlist_objs="$audiod_errlist_objs oss_write" audiod_cmdline_objs="$audiod_cmdline_objs add_cmdline(oss_write)" - write_errlist_objs="$write_errlist_objs oss_write" write_cmdline_objs="$write_cmdline_objs add_cmdline(oss_write)" + fade_errlist_objs="$fade_errlist_objs oss_mix" + all_errlist_objs="$all_errlist_objs oss_write oss_mix" + writers="$writers oss" default_writer="OSS_WRITE" + mixers="${mixers}oss " + default_mixer="OSS_MIX" + AC_CHECK_LIB(ossaudio, _oss_ioctl, [ audiod_ldflags="$audiod_ldflags -lossaudio" write_ldflags="$write_ldflags -lossaudio" @@ -859,21 +861,63 @@ if test "$have_alsa" = "yes"; then fi if test "$have_alsa" = "yes"; then - all_errlist_objs="$all_errlist_objs alsa_write" audiod_errlist_objs="$audiod_errlist_objs alsa_write" audiod_cmdline_objs="$audiod_cmdline_objs add_cmdline(alsa_write)" audiod_ldflags="$audiod_ldflags -lasound" - write_errlist_objs="$write_errlist_objs alsa_write" write_cmdline_objs="$write_cmdline_objs add_cmdline(alsa_write)" write_ldflags="$write_ldflags -lasound" + fade_errlist_objs="$fade_errlist_objs alsa_mix" + fade_ldflags="$fade_ldflags -lasound" + all_errlist_objs="$all_errlist_objs alsa_write alsa_mix" + writers="$writers alsa" default_writer="ALSA_WRITE" + mixers="${mixers}alsa " + default_mixer="ALSA_MIX" fi CPPFLAGS="$OLD_CPPFLAGS" LDFLAGS="$OLD_LDFLAGS" LIBS="$OLD_LIBS" +########################################################################### fade +if test -n "$mixers"; then + extras="$extras fade" + executables="$executables fade" + all_errlist_objs="$all_errlist_objs fade" + fade_errlist_objs="$fade_errlist_objs fade exec string fd" + fade_cmdline_objs="add_cmdline(fade)" + fade_objs="$fade_cmdline_objs $fade_errlist_objs" + AC_SUBST(fade_objs, add_dot_o($fade_objs)) + AC_SUBST(fade_ldflags, $fade_ldflags) + AC_DEFINE_UNQUOTED(INIT_FADE_ERRLISTS, + objlist_to_errlist($fade_errlist_objs), + errors used by para_fade) + enum="$( + for i in $mixers; do + printf "${i}_MIX, " | tr '[a-z]' '[A-Z]' + done + )" + AC_DEFINE_UNQUOTED(MIXER_ENUM, $enum NUM_SUPPORTED_MIXERS, + enum of supported mixers) + AC_DEFINE_UNQUOTED(DEFAULT_MIXER, $default_mixer, + use this mixer if none was specified) + names="$(for i in $mixers; do printf \"$i\",' ' ; done)" + AC_DEFINE_UNQUOTED(MIXER_NAMES, $names, supported mixer names) + inits="$( + for i in $mixers; do + printf 'extern void '$i'_mix_init(struct mixer *); ' + done + )" + AC_DEFINE_UNQUOTED(DECLARE_MIXER_INITS, $inits, + init functions of the supported mixers) + array="$(for i in $mixers; do printf '{.init = '$i'_mix_init},'; done)" + AC_DEFINE_UNQUOTED(MIXER_ARRAY, $array, array of supported mixers) + mixer_summary="supported mixers:: $mixers, default: $default_mixer" +else + AC_MSG_WARN([no mixer support]) + mixer_summary="para_fade: no" +fi ########################################################################### libao OLD_CPPFLAGS="$CPPFLAGS" OLD_LD_FLAGS="$LDFLAGS" @@ -995,16 +1039,6 @@ fi CPPFLAGS="$OLD_CPPFLAGS" LDFLAGS="$OLD_LDFLAGS" LIBS="$OLD_LIBS" -############################################################# - - -AC_SUBST(install_sh, [$INSTALL]) -AC_CONFIG_FILES([Makefile]) - - -AC_DEFUN([add_dot_o],[$(for i in $@; do printf "$i.o "; done)]) -AC_DEFUN([add_para],[$(for i in $@; do printf "para_$i "; done)]) -AC_DEFUN([objlist_to_errlist],[$(for i in $@; do printf "DEFINE_ERRLIST($(echo $i| tr 'a-z' 'A-Z'));"; done) [const char **para_errlist[[]]] = {$(for i in $@; do printf "PARA_ERRLIST($(echo $i | tr 'a-z' 'A-Z')), "; done) }]) ############################################################# error2.h AC_MSG_NOTICE(creating error2.h) for i in $executables; do @@ -1056,7 +1090,6 @@ write_objs="$write_cmdline_objs $write_errlist_objs" client_objs="$client_cmdline_objs $client_errlist_objs" audioc_objs="$audioc_cmdline_objs $audioc_errlist_objs" afh_objs="$afh_cmdline_objs $afh_errlist_objs" -fade_objs="$fade_cmdline_objs $fade_errlist_objs" AC_SUBST(recv_objs, add_dot_o($recv_objs)) AC_SUBST(recv_ldflags, $recv_ldflags) @@ -1102,12 +1135,6 @@ AC_SUBST(gui_objs, add_dot_o($gui_objs)) AC_DEFINE_UNQUOTED(INIT_GUI_ERRLISTS, objlist_to_errlist($gui_errlist_objs), errors used by para_gui) -AC_SUBST(fade_objs, add_dot_o($fade_objs)) -AC_SUBST(fade_ldflags, $fade_ldflags) -AC_DEFINE_UNQUOTED(INIT_FADE_ERRLISTS, - objlist_to_errlist($fade_errlist_objs), errors used by para_fade) - - enum="$(for i in $filters; do printf "${i}_FILTER, " | tr '[a-z]' '[A-Z]'; done)" AC_DEFINE_UNQUOTED(FILTER_ENUM, $enum NUM_SUPPORTED_FILTERS, enum of supported filters) @@ -1144,4 +1171,5 @@ id3 version2 support: $have_libid3tag filters supported by para_audiod/para_filter: $filters writers supported by para_audiod/para_write: $writers optional executables: $extras +$mixer_summary ]) diff --git a/error.h b/error.h index 2474e0ab..8579bd8d 100644 --- a/error.h +++ b/error.h @@ -36,6 +36,15 @@ DEFINE_ERRLIST_OBJECT_ENUM; extern const char **para_errlist[]; +#define OSS_MIX_ERRORS \ + PARA_ERROR(OSS_MIXER_CHANNEL, "invalid mixer channel"), \ + +#define ALSA_MIX_ERRORS \ + PARA_ERROR(ALSA_MIX_OPEN, "could not open mixer"), \ + PARA_ERROR(ALSA_MIX_BAD_ELEM, "invalid/unsupported control element"), \ + PARA_ERROR(ALSA_MIX_GET_VAL, "could not read control element state"), \ + PARA_ERROR(ALSA_MIX_SET_VAL, "could not set control element state"), \ + #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 52c83c4b..8ee192cd 100644 --- a/fade.c +++ b/fade.c @@ -7,32 +7,33 @@ /** \file fade.c A volume fader and alarm clock for OSS. */ #include -#include -#include -#include -#include /* EXIT_SUCCESS */ -#include -#include -#include -#include -#include #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; -__printf_2_3 void date_log(__a_unused int ll, const char *fmt, ...) +enum mixer_id {MIXER_ENUM}; +static char *mixer_name[] = {MIXER_NAMES}; +DECLARE_MIXER_INITS; +static struct mixer supported_mixer[] = {MIXER_ARRAY}; +#define FOR_EACH_MIXER(i) for ((i) = 0; (i) < NUM_SUPPORTED_MIXERS; (i)++) + +static int loglevel; +__printf_2_3 void date_log(int ll, const char *fmt, ...) { va_list argp; time_t t1; struct tm *tm; + if (ll < loglevel) + return; time(&t1); tm = localtime(&t1); printf("%d:%02d:%02d ", tm->tm_hour, tm->tm_min, tm->tm_sec); @@ -42,115 +43,19 @@ __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) +/* 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 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) -{ - 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! */ if (fade_time <= 0) - return 1; + return m->set(h, new_vol); 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; @@ -168,33 +73,42 @@ 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; } static void client_cmd(const char *cmd) { - int ret, fds[3] = {0, 0, 0}; + int ret, status, fds[3] = {0, 0, 0}; pid_t pid; char *cmdline = make_message(BINDIR "/para_client %s", cmd); - PARA_INFO_LOG("%s\n", cmdline); + PARA_NOTICE_LOG("%s\n", cmdline); ret = para_exec_cmdline_pid(&pid, cmdline, fds); free(cmdline); if (ret < 0) { - PARA_EMERG_LOG("%s\n", para_strerror(-ret)); - exit(EXIT_FAILURE); + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + goto fail; } do - ret = wait(NULL); - while (ret != -1 && errno != ECHILD); + pid = waitpid(pid, &status, 0); + while (pid == -1 && errno == EINTR); + if (pid < 0) { + PARA_ERROR_LOG("%s\n", strerror(errno)); + goto fail; + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + goto fail; + return; +fail: + PARA_EMERG_LOG("command \"%s\" failed\n", cmd); + exit(EXIT_FAILURE); } static void change_afs_mode_and_play(char *afs_mode) @@ -210,10 +124,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; @@ -251,15 +162,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; } @@ -280,28 +191,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) @@ -318,9 +233,64 @@ static int configfile_exists(void) return file_exists(conf.config_file_arg); } +static void init_mixers(void) +{ + int i; + + FOR_EACH_MIXER(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; +} + +static struct mixer *get_mixer_or_die(void) +{ + int i; + + if (!conf.mixer_api_given) + i = DEFAULT_MIXER; + else + FOR_EACH_MIXER(i) + if (!strcmp(mixer_name[i], conf.mixer_api_arg)) + break; + if (i < NUM_SUPPORTED_MIXERS) { + PARA_NOTICE_LOG("using %s mixer API\n", mixer_name[i]); + return supported_mixer + i; + } + printf("available mixer APIs: "); + FOR_EACH_MIXER(i) { + int d = (i == DEFAULT_MIXER); + printf("%s%s%s ", d? "[" : "", mixer_name[i], d? "]" : ""); + } + printf("\n"); + exit(EXIT_FAILURE); +} + 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); @@ -342,18 +312,28 @@ int main(int argc, char *argv[]) fade_cmdline_parser_config_file(conf.config_file_arg, &conf, ¶ms); } - fixup_mixer_channel_arg(); + loglevel = get_loglevel_by_name(conf.loglevel_arg); + init_mixers(); + m = get_mixer_or_die(); + 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; diff --git a/m4/gengetopt/fade.m4 b/m4/gengetopt/fade.m4 index ff4b451b..da7a27e2 100644 --- a/m4/gengetopt/fade.m4 +++ b/m4/gengetopt/fade.m4 @@ -1,8 +1,16 @@ args "--conf-parser --no-handle-version" +include(header.m4) +define(CURRENT_PROGRAM,para_fade) +define(DEFAULT_CONFIG_FILE,~/.paraslash/fade.conf) + section "General options" ######################### + +include(config_file.m4) +include(loglevel.m4) + option "mode" o #~~~~~~~~~~~~~~ "how to fade volume" @@ -26,31 +34,49 @@ option "mode" o snooze: Fade out, sleep a bit and fade in. " -option "config-file" c -#~~~~~~~~~~~~~~~~~~~~~ -"(default='~/.paraslash/fade.conf')" - string typestr = "filename" +option "mixer-api" a +#~~~~~~~~~~~~~~~~~~~ +"choose the mixer API" + string typestr = "api" optional + details = " + ALSA is preferred over OSS if both APIs are supported + and this option is not given. To see the supported + mixer APIs, use this option with an invalid string + as the mixer API, e.g. --mixer-api help. + " option "mixer-device" m #~~~~~~~~~~~~~~~~~~~~~~ -"mixer device file" +"choose mixer device" string typestr = "device" - default = "/dev/mixer" optional + details = " + The default device (used if this option is not given) + depends on the selected mixer API. For ALSA, the + default is 'hw:0' which corresponds to the first sound + device. For OSS, '/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. -" + For the ALSA mixer API, the possible values are + determined at runtime depending on the hardware and + can be printed by specifying an invalid mixer channel, + for example --mixer-channel help. The default channel + is 'Master'. + + For OSS the possible values are invariably '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 + channel is 'volume'. + " section "Options for sleep mode" ################################ @@ -200,3 +226,4 @@ option "fade-time" t int typestr = "seconds" default = "5" optional + diff --git a/m4/gengetopt/makefile b/m4/gengetopt/makefile index c613816f..c81eda46 100644 --- a/m4/gengetopt/makefile +++ b/m4/gengetopt/makefile @@ -33,6 +33,7 @@ $(ggo_dir)/client.ggo: \ $(m4_ggo_dir)/config_file.m4 \ $(m4_ggo_dir)/history_file.m4 \ $(m4_ggo_dir)/complete.m4 +$(ggo_dir)/fade.ggo: $(m4_ggo_dir)/loglevel.m4 $(m4_ggo_dir)/config_file.m4 $(ggo_dir)/%.ggo: $(m4_ggo_dir)/%.m4 $(m4_ggo_dir)/header.m4 | $(ggo_dir) @[ -z "$(Q)" ] || echo 'M4 $<' diff --git a/mix.h b/mix.h new file mode 100644 index 00000000..a4105544 --- /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); +}; diff --git a/oss_mix.c b/oss_mix.c new file mode 100644 index 00000000..b6756477 --- /dev/null +++ b/oss_mix.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 1998-2012 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file oss_mix.c The OSS mixer plugin. */ + +#include +#include +#include + +#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; +} diff --git a/web/manual.m4 b/web/manual.m4 index 5a348bb9..56ee3469 100644 --- a/web/manual.m4 +++ b/web/manual.m4 @@ -183,7 +183,7 @@ can be added easily. *para_fade* -An (OSS-only) alarm clock and volume-fader. +An alarm clock and volume-fader for OSS and ALSA. ----------- Quick start