summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArun Raghavan <arun.raghavan@collabora.co.uk>2013-10-08 03:43:29 (GMT)
committerArun Raghavan <arun.raghavan@collabora.co.uk>2013-10-09 13:38:43 (GMT)
commitff11cc2b89b65a78328a2c1bccd02179c2bb5876 (patch)
treebb32e460d9e4a983fa5249b257c68851bb9bede0
parent786f9843df45963fe57b076c5ff190b581547a93 (diff)
downloadpulseaudio-android-ff11cc2b89b65a78328a2c1bccd02179c2bb5876.tar.gz
pulseaudio-android-ff11cc2b89b65a78328a2c1bccd02179c2bb5876.tar.xz
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.
-rw-r--r--tools/mako/csd-daemon.c220
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