diff options
Diffstat (limited to 'src/audio_out/audio_alsa_out.c')
-rw-r--r-- | src/audio_out/audio_alsa_out.c | 315 |
1 files changed, 302 insertions, 13 deletions
diff --git a/src/audio_out/audio_alsa_out.c b/src/audio_out/audio_alsa_out.c index e887b277d..0ec7de759 100644 --- a/src/audio_out/audio_alsa_out.c +++ b/src/audio_out/audio_alsa_out.c @@ -26,7 +26,7 @@ * (c) 2001 James Courtier-Dutton <James@superbug.demon.co.uk> * * - * $Id: audio_alsa_out.c,v 1.28 2001/09/14 20:44:01 jcdutton Exp $ + * $Id: audio_alsa_out.c,v 1.29 2001/10/01 23:04:57 f1rmb Exp $ */ #ifdef HAVE_CONFIG_H @@ -40,7 +40,8 @@ #include <stdlib.h> #include <fcntl.h> #include <math.h> -#include <sys/asoundlib.h> +#include <alloca.h> +#include <alsa/asoundlib.h> #include <sys/ioctl.h> #include <inttypes.h> @@ -75,11 +76,18 @@ #define AO_OUT_ALSA_IFACE_VERSION 2 -#define GAP_TOLERANCE 5000 +#define GAP_TOLERANCE 5000 + +#define MIXER_MASK_LEFT (1 << 0) +#define MIXER_MASK_RIGHT (1 << 1) +#define MIXER_MASK_STEREO (MIXER_MASK_LEFT|MIXER_MASK_RIGHT) typedef struct alsa_driver_s { ao_driver_t ao_driver; + + config_values_t *config; + char audio_default_device[20]; char audio_front_device[20]; char audio_surround40_device[20]; @@ -97,9 +105,46 @@ typedef struct alsa_driver_s { uint32_t bytes_per_frame; uint32_t bytes_in_buffer; /* number of bytes writen to audio hardware */ + struct { + char *name; + snd_mixer_t *handle; + snd_mixer_elem_t *elem; + long min; + long max; + long left_vol; + long right_vol; + int mute; + } mixer; + } alsa_driver_t; - static snd_output_t *jcd_out; +static snd_output_t *jcd_out; + +static int ao_alsa_get_percent_from_volume(long val, long min, long max) +{ + int range = max - min; + int tmp; + + if (range == 0) + return 0; + val -= min; + tmp = rint((double)val / (double)range * 100); + return tmp; +} + +static long ao_alsa_get_volume_from_percent(int val, long min, long max) +{ + int range = max - min; + long tmp; + + if (range == 0) + return 0; + val -= min; + tmp = (long) ((range * val) / 100); + return tmp; +} + + /* * open the audio device for writing to */ @@ -402,44 +447,283 @@ static uint32_t ao_alsa_get_capabilities (ao_driver_t *this_gen) { static void ao_alsa_exit(ao_driver_t *this_gen) { alsa_driver_t *this = (alsa_driver_t *) this_gen; + + config_values_t *config = this->config; + + config->set_int (config, "mixer_volume", + (((ao_alsa_get_percent_from_volume(this->mixer.left_vol, + this->mixer.min, this->mixer.max)) + + (ao_alsa_get_percent_from_volume(this->mixer.right_vol, + this->mixer.min, this->mixer.max))) /2)); + config->save(config); + if (this->audio_fd) snd_pcm_close(this->audio_fd); free (this); } -static int ao_alsa_get_property (ao_driver_t *this, int property) { +static int ao_alsa_get_property (ao_driver_t *this_gen, int property) { + alsa_driver_t *this = (alsa_driver_t *) this_gen; + int err; - /* FIXME: implement some properties switch(property) { case AO_PROP_MIXER_VOL: - break; case AO_PROP_PCM_VOL: + if(this->mixer.elem) { + if((err = snd_mixer_selem_get_playback_volume(this->mixer.elem, SND_MIXER_SCHN_FRONT_LEFT, + &this->mixer.left_vol)) < 0) { + printf("snd_mixer_selem_get_playback_volume(): %s\n", snd_strerror(err)); + goto __done; + } + + if((err = snd_mixer_selem_get_playback_volume(this->mixer.elem, SND_MIXER_SCHN_FRONT_RIGHT, + &this->mixer.right_vol)) < 0) { + printf("snd_mixer_selem_get_playback_volume(): %s\n", snd_strerror(err)); + goto __done; + } + + __done: + return (((ao_alsa_get_percent_from_volume(this->mixer.left_vol, + this->mixer.min, this->mixer.max)) + + (ao_alsa_get_percent_from_volume(this->mixer.right_vol, + this->mixer.min, this->mixer.max))) /2); + } break; + case AO_PROP_MUTE_VOL: + return (this->mixer.mute) ? 1 : 0; break; } - */ + return 0; } /* * */ -static int ao_alsa_set_property (ao_driver_t *this, int property, int value) { +static int ao_alsa_set_property (ao_driver_t *this_gen, int property, int value) { + alsa_driver_t *this = (alsa_driver_t *) this_gen; + int err; - /* FIXME: Implement property support. switch(property) { case AO_PROP_MIXER_VOL: - break; case AO_PROP_PCM_VOL: + if(this->mixer.elem) { + + this->mixer.left_vol = this->mixer.right_vol = ao_alsa_get_volume_from_percent(value, this->mixer.min, this->mixer.max); + + if((err = snd_mixer_selem_set_playback_volume(this->mixer.elem, SND_MIXER_SCHN_FRONT_LEFT, + this->mixer.left_vol)) < 0) { + printf("snd_mixer_selem_get_playback_volume(): %s\n", snd_strerror(err)); + return ~value; + } + + if((err = snd_mixer_selem_set_playback_volume(this->mixer.elem, SND_MIXER_SCHN_FRONT_RIGHT, + this->mixer.right_vol)) < 0) { + printf("snd_mixer_selem_get_playback_volume(): %s\n", snd_strerror(err)); + return ~value; + } + + return value; + } break; + case AO_PROP_MUTE_VOL: + if(this->mixer.elem) { + int sw; + int old_mute = this->mixer.mute; + + this->mixer.mute = (value) ? MIXER_MASK_STEREO : 0; + + if ((this->mixer.mute != old_mute) + && snd_mixer_selem_has_playback_switch(this->mixer.elem)) { + if (snd_mixer_selem_has_playback_switch_joined(this->mixer.elem)) { + snd_mixer_selem_get_playback_switch(this->mixer.elem, SND_MIXER_SCHN_FRONT_LEFT, &sw); + snd_mixer_selem_set_playback_switch_all(this->mixer.elem, !sw); + } else { + if (this->mixer.mute & MIXER_MASK_LEFT) { + snd_mixer_selem_get_playback_switch(this->mixer.elem, SND_MIXER_SCHN_FRONT_LEFT, &sw); + snd_mixer_selem_set_playback_switch(this->mixer.elem, SND_MIXER_SCHN_FRONT_LEFT, !sw); + } + if (SND_MIXER_SCHN_FRONT_RIGHT != SND_MIXER_SCHN_UNKNOWN && + (this->mixer.mute & MIXER_MASK_RIGHT)) { + snd_mixer_selem_get_playback_switch(this->mixer.elem, SND_MIXER_SCHN_FRONT_RIGHT, &sw); + snd_mixer_selem_set_playback_switch(this->mixer.elem, SND_MIXER_SCHN_FRONT_RIGHT, !sw); + } + } + } + + return value; + } + + return ~value; break; } - */ return ~value; } +static void ao_alsa_mixer_init(ao_driver_t *this_gen) { + alsa_driver_t *this = (alsa_driver_t *) this_gen; + config_values_t *config = this->config; + snd_ctl_card_info_t *hw_info; + snd_ctl_t *ctl_handle; + int err; + void *mixer_sid; + snd_mixer_elem_t *elem; + int mixer_n_selems = 0; + snd_mixer_selem_id_t *sid; + int loop = 0; + int found; + int sw; + + snd_ctl_card_info_alloca(&hw_info); + + if ((err = snd_ctl_open (&ctl_handle, this->audio_default_device, 0)) < 0) { + printf("snd_ctl_open(): %s\n", snd_strerror(err)); + return; + } + + if ((err = snd_ctl_card_info (ctl_handle, hw_info)) < 0) { + printf("snd_ctl_card_info(): %s\n", snd_strerror(err)); + snd_ctl_close(ctl_handle); + return; + } + + snd_ctl_close (ctl_handle); + + /* + * Open mixer device + */ + if ((err = snd_mixer_open (&this->mixer.handle, 0)) < 0) { + printf("snd_mixer_open(): %s\n", snd_strerror(err)); + return; + } + + if ((err = snd_mixer_attach (this->mixer.handle, this->audio_default_device)) < 0) { + printf("snd_mixer_attach(): %s\n", snd_strerror(err)); + snd_mixer_close(this->mixer.handle); + return; + } + + if ((err = snd_mixer_selem_register (this->mixer.handle, NULL, NULL)) < 0) { + printf("snd_mixer_selem_register(): %s\n", snd_strerror(err)); + snd_mixer_close(this->mixer.handle); + return; + } + + // snd_mixer_set_callback (mixer_handle, mixer_event); + + if ((err = snd_mixer_load (this->mixer.handle)) < 0) { + printf("snd_mixer_load(): %s\n", snd_strerror(err)); + snd_mixer_close(this->mixer.handle); + return; + } + + mixer_sid = alloca(snd_mixer_selem_id_sizeof() * snd_mixer_get_count(this->mixer.handle)); + if (mixer_sid == NULL) { + printf("alloca() failed: %s\n", strerror(errno)); + snd_mixer_close(this->mixer.handle); + return; + } + + __again: + + found = 0; + mixer_n_selems = 0; + for (elem = snd_mixer_first_elem(this->mixer.handle); elem; elem = snd_mixer_elem_next(elem)) { + sid = (snd_mixer_selem_id_t *)(((char *)mixer_sid) + snd_mixer_selem_id_sizeof() * mixer_n_selems); + + if (!snd_mixer_selem_is_active(elem)) + continue; + + snd_mixer_selem_get_id(elem, sid); + mixer_n_selems++; + + if(!strcmp((snd_mixer_selem_get_name(elem)), this->mixer.name)) { + + // printf("found %s\n", snd_mixer_selem_get_name(elem)); + + this->mixer.elem = elem; + + snd_mixer_selem_get_playback_volume_range(this->mixer.elem, + &this->mixer.min, &this->mixer.max); + if((err = snd_mixer_selem_get_playback_volume(this->mixer.elem, SND_MIXER_SCHN_FRONT_LEFT, + &this->mixer.left_vol)) < 0) { + printf("snd_mixer_selem_get_playback_volume(): %s\n", snd_strerror(err)); + snd_mixer_close(this->mixer.handle); + return; + } + + if((err = snd_mixer_selem_get_playback_volume(this->mixer.elem, SND_MIXER_SCHN_FRONT_RIGHT, + &this->mixer.right_vol)) < 0) { + printf("snd_mixer_selem_get_playback_volume(): %s\n", snd_strerror(err)); + snd_mixer_close(this->mixer.handle); + return; + } + + /* Channels mute */ + this->mixer.mute = 0; + if(snd_mixer_selem_has_playback_switch(this->mixer.elem)) { + + if (snd_mixer_selem_has_playback_switch_joined(this->mixer.elem)) { + snd_mixer_selem_get_playback_switch(this->mixer.elem, SND_MIXER_SCHN_FRONT_LEFT, &sw); + if(!sw) + this->mixer.mute = MIXER_MASK_STEREO; + } + else { + if (this->mixer.mute & MIXER_MASK_LEFT) { + snd_mixer_selem_get_playback_switch(this->mixer.elem, SND_MIXER_SCHN_FRONT_LEFT, &sw); + if(!sw) + this->mixer.mute |= MIXER_MASK_LEFT; + } + if (SND_MIXER_SCHN_FRONT_RIGHT != SND_MIXER_SCHN_UNKNOWN && + (this->mixer.mute & MIXER_MASK_RIGHT)) { + snd_mixer_selem_get_playback_switch(this->mixer.elem, SND_MIXER_SCHN_FRONT_RIGHT, &sw); + if(!sw) + this->mixer.mute |= MIXER_MASK_RIGHT; + } + } + + this->capabilities |= AO_CAP_MUTE_VOL; + } + + found++; + + goto __mixer_found; + } + } + + if(loop) + goto __mixer_found; /* Yes, untrue but... ;-) */ + + if(!strcmp(this->mixer.name, "PCM")) { + config->set_str(config, "mixer_name", "Master"); + loop++; + } + else { + config->set_str(config, "mixer_name", "PCM"); + } + + config->save(config); + + this->mixer.name = config->lookup_str(config, "mixer_name", "PCM"); + + goto __again; + + __mixer_found: + + /* + * Ugly: yes[*] no[ ] + */ + if(found) { + if(!strcmp(this->mixer.name, "Master")) + this->capabilities |= AO_CAP_MIXER_VOL; + else + this->capabilities |= AO_CAP_PCM_VOL; + } + +} + ao_driver_t *init_audio_out_plugin (config_values_t *config) { alsa_driver_t *this; @@ -514,7 +798,12 @@ ao_driver_t *init_audio_out_plugin (config_values_t *config) { this->capabilities |= AO_CAP_MODE_AC5; } printf("audio_alsa_out: Capabilities 0x%X\n",this->capabilities); - + + this->config = config; + + this->mixer.name = config->lookup_str(config, "mixer_name", "PCM"); + ao_alsa_mixer_init(&this->ao_driver); + this->ao_driver.get_capabilities = ao_alsa_get_capabilities; this->ao_driver.get_property = ao_alsa_get_property; this->ao_driver.set_property = ao_alsa_set_property; |