diff options
author | Arun Raghavan <arun.raghavan@collabora.co.uk> | 2013-10-08 09:13:29 +0530 |
---|---|---|
committer | Arun Raghavan <arun.raghavan@collabora.co.uk> | 2013-10-09 19:08:43 +0530 |
commit | ff11cc2b89b65a78328a2c1bccd02179c2bb5876 (patch) | |
tree | bb32e460d9e4a983fa5249b257c68851bb9bede0 /tools | |
parent | 786f9843df45963fe57b076c5ff190b581547a93 (diff) |
mako: Expose voice controls as ALSA mixer controls
This provides a volume mixer control and a mute switch for the mic that
allow standard ALSA controls to be used to control voice call playback
volume and capture muting.
Diffstat (limited to 'tools')
-rw-r--r-- | tools/mako/csd-daemon.c | 220 |
1 files changed, 219 insertions, 1 deletions
diff --git a/tools/mako/csd-daemon.c b/tools/mako/csd-daemon.c index bf11b4c..3f04fd1 100644 --- a/tools/mako/csd-daemon.c +++ b/tools/mako/csd-daemon.c @@ -33,6 +33,7 @@ #include <dlfcn.h> #include <errno.h> #include <unistd.h> +#include <poll.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> @@ -53,6 +54,15 @@ struct state { int enabled_device; snd_pcm_t *play, *rec; + snd_ctl_t *ctl; + + snd_ctl_elem_id_t *play_id, *rec_id; + + int volume; + int mic_mute; + + struct pollfd *pollfds; + int nfds; }; static struct state state; @@ -228,6 +238,167 @@ static void close_pcms(void) } } +static void remove_user_ctls(void) +{ + snd_ctl_subscribe_events(state.ctl, 0); + + if (state.play_id) { + snd_ctl_elem_remove(state.ctl, state.play_id); + state.play_id = NULL; + } + + if (state.rec_id) { + snd_ctl_elem_remove(state.ctl, state.rec_id); + state.rec_id = NULL; + } + + if (state.ctl) { + snd_ctl_close(state.ctl); + state.ctl = NULL; + } +} + +static void set_volume(int volume) +{ + if (state.volume == volume) + return; + + if (csd_client_volume(100 - volume) < 0) + ERR("Could not set volume"); + else + state.volume = volume; +} + +static void set_mic_mute(int mic_mute) +{ + if (state.mic_mute == mic_mute) + return; + + if (csd_client_mic_mute(mic_mute) < 0) + ERR("Could not set mic_mute"); + else + state.mic_mute = mic_mute; +} + +static void handle_play_volume() +{ + snd_ctl_elem_value_t *elem; + + snd_ctl_elem_value_alloca(&elem); + snd_ctl_elem_value_set_id(elem, state.play_id); + + if (snd_ctl_elem_read(state.ctl, elem) < 0) { + ERR("Error reading volume value\n"); + return; + } + + set_volume(snd_ctl_elem_value_get_integer(elem, 0)); + DBG("Volume set to: %d\n", state.volume); +} + +static void handle_mic_switch() +{ + snd_ctl_elem_value_t *elem; + + snd_ctl_elem_value_alloca(&elem); + snd_ctl_elem_value_set_id(elem, state.rec_id); + + if (snd_ctl_elem_read(state.ctl, elem) < 0) { + ERR("Error reading mic mute value\n"); + return; + } + + set_mic_mute(1 - snd_ctl_elem_value_get_boolean(elem, 0)); + DBG("Mic mute set to: %d\n", state.mic_mute); +} + +#define CTL_DEVICE "hw:0" + +static int create_user_ctls(void) +{ + snd_ctl_elem_value_t *elem; + int err; + + if (snd_ctl_open(&state.ctl, CTL_DEVICE, SND_CTL_NONBLOCK) < 0) { + ERR("Could not open playback ctl device\n"); + goto error; + } + + snd_ctl_elem_id_malloc(&state.play_id); + snd_ctl_elem_id_set_interface(state.play_id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(state.play_id, "Voice Call Playback Volume"); + snd_ctl_elem_id_set_device(state.play_id, 0); + snd_ctl_elem_id_set_subdevice(state.play_id, 0); + + /* We ignore -EEXIST to make things more robust if a previous instance of + * csd-daemon didn't clean up properly */ + err = snd_ctl_elem_add_integer(state.ctl, state.play_id, 1, 0, 100, 1); + if (err < 0 && err != -EEXIST) { + ERR("Could not add voice call playback volume control (%d)\n", err); + goto error; + } + + snd_ctl_elem_id_malloc(&state.rec_id); + snd_ctl_elem_id_set_interface(state.rec_id, SND_CTL_ELEM_IFACE_MIXER); + snd_ctl_elem_id_set_name(state.rec_id, "Voice Call Capture Switch"); + snd_ctl_elem_id_set_device(state.rec_id, 0); + snd_ctl_elem_id_set_subdevice(state.rec_id, 0); + + err = snd_ctl_elem_add_boolean(state.ctl, state.rec_id, 1); + if (err < 0 && err != -EEXIST) { + ERR("Could not add voice call capture switch (%d)\n", err); + goto error; + } + + snd_ctl_elem_unlock(state.ctl, state.play_id); + snd_ctl_elem_unlock(state.ctl, state.rec_id); + + if (snd_ctl_subscribe_events(state.ctl, 1) < 0) { + ERR("Could not subscribe to mixer events"); + goto error; + } + + snd_ctl_elem_value_alloca(&elem); + + snd_ctl_elem_value_set_id(elem, state.play_id); + snd_ctl_elem_value_set_integer(elem, 0, 100); + if ((err = snd_ctl_elem_write(state.ctl, elem)) < 0) + ERR("Could not set volume: %d", err); + + snd_ctl_elem_value_set_id(elem, state.rec_id); + snd_ctl_elem_value_set_boolean(elem, 0, 1); + if ((err = snd_ctl_elem_write(state.ctl, elem)) < 0) + ERR("Could not set mic mute: %d", err); + + return 0; + +error: + remove_user_ctls(); + return -1; +} + +static int prepare_poll(void) +{ + int i; + + state.nfds = 1 /* socket */ + snd_ctl_poll_descriptors_count(state.ctl); + state.pollfds = (struct pollfd *) calloc(state.nfds, sizeof(struct pollfd)); + + state.pollfds[0].fd = state.sockfd; + state.pollfds[0].events = POLLIN; + + i = snd_ctl_poll_descriptors(state.ctl, &state.pollfds[1], state.nfds - 1); + if (i != state.nfds - 1) { + ERR("Got a wrong number of pollfds\n"); + return -1; + } + + for (i = 1; i < state.nfds; i++) + state.pollfds[i].events = POLLIN; + + return 0; +} + static int start_voice(void) { int ret; @@ -369,6 +540,7 @@ int main(int argc, char **argv) char command[COMMAND_MAX]; int child, fd, done = 0, ret = 0; unsigned int i; + unsigned short revents; if (argc > 1) { ERR("Bad arguments\n"); @@ -384,6 +556,9 @@ int main(int argc, char **argv) if (init_daemon() < 0) return -1; + if (create_user_ctls() < 0) + return -1; + /* Basic setup was successful, so clients try to connect. Daemonize before * making any actual library init, else nothing will work. */ if ((child = fork()) < 0) { @@ -409,8 +584,46 @@ int main(int argc, char **argv) return ret; } + if ((ret = prepare_poll()) < 0) { + ERR("Could not set up poll()\n"); + return ret; + } + while (!done) { + ret = poll(state.pollfds, state.nfds, -1); + if (ret < 0) { + ERR("Error in poll()\n"); + break; + } + + if ((ret = snd_ctl_poll_descriptors_revents(state.ctl, + &state.pollfds[1], + state.nfds - 1, + &revents)) < 0) { + ERR("Could not get ALSA revents\n"); + } else { + if (revents) { + snd_ctl_event_t *event; + + /* Just force update */ + handle_play_volume(); + handle_mic_switch(); + + /* Now consume all pending events */ + snd_ctl_event_alloca(&event); + do { + ret = snd_ctl_read(state.ctl, event); + } while (ret != 0 && ret != -EAGAIN); + } + } + + if (state.pollfds[0].revents == 0) + continue; + + /* Got a connection */ + fd = accept(state.sockfd, NULL, NULL); + if (fd < 0) { ERR("Could not accept incoming connection\n"); continue; @@ -446,9 +659,14 @@ next: close(fd); } +out: close(state.sockfd); -out: + if (state.pollfds) + free(state.pollfds); + + remove_user_ctls(); + /* Clearly deinitialisation is for the weak - this breaks the radio * firmware until reboot! */ #if 0 |