[pulseaudio-discuss] [PATCH 4/4] alsa: Add module to talk to the Android audio hal to set up voice calls
David Henningsson
david.henningsson at canonical.com
Wed Sep 18 07:01:52 PDT 2013
---
configure.ac | 17 +
src/Makefile.am | 12 +
src/daemon/default.pa.in | 4 +
src/modules/alsa/module-android-audio-hal.c | 508 +++++++++++++++++++++++++++
4 files changed, 541 insertions(+)
create mode 100644 src/modules/alsa/module-android-audio-hal.c
diff --git a/configure.ac b/configure.ac
index 15f2f99..105c1d2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -781,6 +781,21 @@ AM_CONDITIONAL([HAVE_ALSA], [test "x$HAVE_ALSA" = x1])
AS_IF([test "x$HAVE_ALSA" = "x1"], AC_DEFINE([HAVE_ALSA], 1, [Have ALSA?]))
AS_IF([test "x$HAVE_ALSA_UCM" = "x1"], AC_DEFINE([HAVE_ALSA_UCM], 1, [Have ALSA UCM?]))
+#### Android audio HAL support (optional) ####
+
+AC_ARG_ENABLE([android],
+ AS_HELP_STRING([--disable-android],[Disable optional Android audio HAL support]))
+
+AS_IF([test "x$enable_android" != "xno"],
+ [AC_CHECK_HEADERS([android/hardware/audio.h], HAVE_ANDROID=1, HAVE_ANDROID=0)],
+ HAVE_ANDROID=0)
+
+AS_IF([test "x$enable_android" = "xyes" && test "x$HAVE_ANDROID" = "x0"],
+ [AC_MSG_ERROR([*** Android audio HAL support not found])])
+
+AM_CONDITIONAL([HAVE_ANDROID], [test "x$HAVE_ANDROID" = "x1"])
+AS_IF([test "x$HAVE_ANDROID" = "x1"], AC_DEFINE([HAVE_ANDROID], 1, [Have Android audio HAL?]))
+
#### EsounD support (optional) ####
AC_ARG_ENABLE([esound],
@@ -1375,6 +1390,7 @@ AS_IF([test "x$HAVE_X11" = "x1"], ENABLE_X11=yes, ENABLE_X11=no)
AS_IF([test "x$HAVE_OSS_OUTPUT" = "x1"], ENABLE_OSS_OUTPUT=yes, ENABLE_OSS_OUTPUT=no)
AS_IF([test "x$HAVE_OSS_WRAPPER" = "x1"], ENABLE_OSS_WRAPPER=yes, ENABLE_OSS_WRAPPER=no)
AS_IF([test "x$HAVE_ALSA" = "x1"], ENABLE_ALSA=yes, ENABLE_ALSA=no)
+AS_IF([test "x$HAVE_ANDROID" = "x1"], ENABLE_ANDROID=yes, ENABLE_ANDROID=no)
AS_IF([test "x$HAVE_COREAUDIO" = "x1"], ENABLE_COREAUDIO=yes, ENABLE_COREAUDIO=no)
AS_IF([test "x$HAVE_SOLARIS" = "x1"], ENABLE_SOLARIS=yes, ENABLE_SOLARIS=no)
AS_IF([test "x$HAVE_WAVEOUT" = "x1"], ENABLE_WAVEOUT=yes, ENABLE_WAVEOUT=no)
@@ -1428,6 +1444,7 @@ echo "
Enable OSS Wrapper: ${ENABLE_OSS_WRAPPER}
Enable EsounD: ${ENABLE_ESOUND}
Enable Alsa: ${ENABLE_ALSA}
+ Enable Android audio HAL: ${ENABLE_ANDROID}
Enable CoreAudio: ${ENABLE_COREAUDIO}
Enable Solaris: ${ENABLE_SOLARIS}
Enable WaveOut: ${ENABLE_WAVEOUT}
diff --git a/src/Makefile.am b/src/Makefile.am
index 7caa1db..34a22a5 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1187,6 +1187,10 @@ modlibexec_LTLIBRARIES += \
module-alsa-source.la \
module-alsa-card.la
+if HAVE_ANDROID
+modlibexec_LTLIBRARIES += module-android-audio-hal.la
+endif
+
dist_alsaprofilesets_DATA = \
modules/alsa/mixer/profile-sets/default.conf \
modules/alsa/mixer/profile-sets/extra-hdmi.conf \
@@ -1394,6 +1398,7 @@ SYMDEF_FILES = \
module-alsa-sink-symdef.h \
module-alsa-source-symdef.h \
module-alsa-card-symdef.h \
+ module-android-audio-hal-symdef.h \
module-coreaudio-detect-symdef.h \
module-coreaudio-device-symdef.h \
module-solaris-symdef.h \
@@ -1750,6 +1755,13 @@ libalsa_util_la_LIBADD += $(DBUS_LIBS)
libalsa_util_la_CFLAGS += $(DBUS_CFLAGS)
endif
+if HAVE_ANDROID
+module_android_audio_hal_la_SOURCES = modules/alsa/module-android-audio-hal.c
+module_android_audio_hal_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_android_audio_hal_la_LIBADD = $(MODULE_LIBADD) $(ASOUNDLIB_LIBS) libalsa-util.la -lhardware
+module_android_audio_hal_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS)
+endif
+
module_alsa_sink_la_SOURCES = modules/alsa/module-alsa-sink.c
module_alsa_sink_la_LDFLAGS = $(MODULE_LDFLAGS)
module_alsa_sink_la_LIBADD = $(MODULE_LIBADD) $(ASOUNDLIB_LIBS) libalsa-util.la
diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in
index f50d929..1b17689 100755
--- a/src/daemon/default.pa.in
+++ b/src/daemon/default.pa.in
@@ -78,6 +78,10 @@ load-module module-udev-detect
load-module module-detect
.endif
+.ifexists module-android-audio-hal at PA_SOEXT@
+load-module module-android-audio-hal
+.endif
+
### Automatically connect sink and source if JACK server is present
.ifexists module-jackdbus-detect at PA_SOEXT@
.nofail
diff --git a/src/modules/alsa/module-android-audio-hal.c b/src/modules/alsa/module-android-audio-hal.c
new file mode 100644
index 0000000..c5e66c3
--- /dev/null
+++ b/src/modules/alsa/module-android-audio-hal.c
@@ -0,0 +1,508 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2013 David Henningsson, Canonical Ltd.
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+/*
+ Okay, so this module might need some explanation.
+
+ First, this module is currently used to set up and tear down voice calls only.
+ This is because Android handles this by calling into proprietary blobs, which
+ e g talks to the modem/baseband to set up routes correctly. For normal HiFi
+ playback, we use UCM only.
+
+ In Ubuntu Phone this is used together with libhybris to link into android code
+ from Ubuntu/Linux code.
+
+ The UCM file needs to set up a source and sink in order to have devices. We
+ need devices in order to be able to select headset/speaker/earpiece correctly.
+ This sink/source also controls call volume and mic mute.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "module-android-audio-hal-symdef.h"
+
+PA_MODULE_AUTHOR("David Henningsson");
+PA_MODULE_DESCRIPTION("Android Audio HAL (Voice call helper)");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE("");
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include <pulsecore/log.h>
+#include <pulsecore/card.h>
+#include <pulsecore/once.h>
+#include <pulsecore/core-util.h>
+
+#include <android/hardware/hardware.h>
+#include <android/hardware/audio.h>
+
+#include "alsa-mixer.h"
+
+const hw_module_t *primary_audio_module = NULL;
+
+typedef struct pa_android_audio_hal {
+ struct audio_hw_device *dev;
+ struct audio_stream_out *ostream;
+ struct audio_stream_in *istream;
+ int odevice; /* AUDIO_DEVICE_OUT_xxx */
+ int idevice; /* AUDIO_DEVICE_IN_xxx */
+} pa_android_audio_hal;
+
+static pa_android_audio_hal* pa_android_audio_hal_new() {
+
+ int r;
+ struct audio_hw_device *device;
+ struct pa_android_audio_hal *hal;
+
+ if (!primary_audio_module) {
+ r = hw_get_module_by_class("audio", "primary", &primary_audio_module);
+ if (r < 0 || primary_audio_module == NULL) {
+ pa_log("hw_get_module_by_class failed with error %d", r);
+ return NULL;
+ }
+ pa_log_debug("Opened Android module: '%s' - '%s', author: '%s'",
+ primary_audio_module->id, primary_audio_module->name,
+ primary_audio_module->author);
+ }
+
+ r = audio_hw_device_open(primary_audio_module, &device);
+ if (r || device == NULL) {
+ pa_log("audio_hw_device_open failed with error %d", r);
+ return NULL;
+ }
+ pa_log_debug("Opened audio hw device");
+
+ r = device->init_check(device);
+ if (r) {
+ pa_log("init_check failed with error %d", r);
+ audio_hw_device_close(device);
+ return NULL;
+ }
+ pa_log_debug("init_check succeeded");
+
+ hal = pa_xnew0(struct pa_android_audio_hal, 1);
+ hal->dev = device;
+ return hal;
+}
+
+static void print_err(int err, const char *func) {
+
+ if (err)
+ pa_log("%s returned error %d", func, err);
+ else
+ pa_log_debug("%s succeeded", func);
+}
+
+static void set_stream_device(struct audio_stream *stream, int device) {
+
+ char *s;
+ int err;
+
+ if (!stream->set_parameters) {
+ pa_log_warn("no set_parameters callback");
+ return;
+ }
+
+ s = pa_sprintf_malloc("routing=%d;", device);
+ pa_log_debug("Calling set_parameters with '%s'", s);
+
+ err = stream->set_parameters(stream, s);
+ print_err(err < 0 ? err : 0, "set_parameters");
+
+ pa_xfree(s);
+}
+
+static void start_voice_call(pa_android_audio_hal *hal) {
+
+ pa_assert(hal);
+ pa_assert(hal->dev);
+
+ if (!hal->dev->set_mode)
+ pa_log_warn("no set_mode callback");
+ else
+ print_err(hal->dev->set_mode(hal->dev, AUDIO_MODE_IN_CALL), "set_mode");
+
+ if (!hal->ostream) {
+ if (!hal->dev->open_output_stream)
+ pa_log_warn("no open_output_stream callback");
+ else {
+ struct audio_config config = { .sample_rate = 8000,
+ .channel_mask = AUDIO_CHANNEL_OUT_MONO, .format = AUDIO_FORMAT_PCM_16_BIT };
+ print_err(hal->dev->open_output_stream(hal->dev, 1, hal->odevice,
+ AUDIO_OUTPUT_FLAG_PRIMARY,
+ &config, &hal->ostream), "open_output_stream");
+ }
+ }
+
+ if (hal->ostream)
+ set_stream_device(&hal->ostream->common, hal->odevice);
+
+ if (!hal->istream) {
+ if (!hal->dev->open_input_stream)
+ pa_log_warn("no open_input_stream callback");
+ else {
+ struct audio_config config = { .sample_rate = 8000,
+ .channel_mask = AUDIO_CHANNEL_IN_MONO, .format = AUDIO_FORMAT_PCM_16_BIT };
+ print_err(hal->dev->open_input_stream(hal->dev, 2, hal->idevice,
+ &config, &hal->istream), "open_input_stream");
+ }
+ }
+
+ if (hal->istream)
+ set_stream_device(&hal->istream->common, hal->idevice);
+}
+
+static void set_voice_call_volume(pa_android_audio_hal *hal, double v) {
+
+ if (!hal->dev->set_voice_volume)
+ pa_log_warn("no set_voice_volume callback");
+ else
+ print_err(hal->dev->set_voice_volume(hal->dev, v), "set_voice_volume");
+}
+
+static void set_voice_mic_mute(pa_android_audio_hal *hal, bool mute) {
+
+ if (!hal->dev->set_mic_mute)
+ pa_log_warn("no set_mic_mute callback");
+ else
+ print_err(hal->dev->set_mic_mute(hal->dev, mute), "set_mic_mute");
+}
+
+static void stop_voice_call(pa_android_audio_hal *hal) {
+
+ pa_assert(hal);
+ pa_assert(hal->dev);
+
+ if (hal->ostream) {
+ if (!hal->dev->close_output_stream)
+ pa_log_warn("no close_output_stream callback");
+ else {
+ pa_log_debug("Closing output device");
+ hal->dev->close_output_stream(hal->dev, hal->ostream);
+ hal->ostream = NULL;
+ }
+ }
+
+ if (hal->istream) {
+ if (!hal->dev->close_input_stream)
+ pa_log_warn("no close_input_stream callback");
+ else {
+ pa_log_debug("Closing input device");
+ hal->dev->close_input_stream(hal->dev, hal->istream);
+ hal->istream = NULL;
+ }
+ }
+
+ if (hal->dev->set_parameters) {
+ pa_log_debug("Setting mode to normal through set_parameters (Nexus 4 workaround)");
+ print_err(hal->dev->set_parameters(hal->dev, "CALL_KEY=0;"), "device set_parameters");
+ }
+
+ if (!hal->dev->set_mode)
+ pa_log_warn("no set_mode callback");
+ else
+ print_err(hal->dev->set_mode(hal->dev, AUDIO_MODE_NORMAL), "set_mode");
+}
+
+static void update_devices(pa_android_audio_hal *hal) {
+
+ if (hal->istream)
+ set_stream_device(&hal->istream->common, hal->idevice);
+ if (hal->ostream)
+ set_stream_device(&hal->ostream->common, hal->odevice);
+}
+
+static void pa_android_audio_hal_free(pa_android_audio_hal *hal) {
+
+ if (hal->dev) {
+ stop_voice_call(hal);
+ audio_hw_device_close(hal->dev);
+ }
+
+ pa_xfree(hal);
+}
+
+static bool card_has_voice_call(pa_card *c) {
+
+ pa_card_profile *p;
+ void *state;
+
+ PA_HASHMAP_FOREACH(p, c->profiles, state)
+ if (pa_streq(p->name, SND_USE_CASE_VERB_VOICECALL))
+ return true;
+
+ return false;
+}
+
+static bool profile_in_voice_call(pa_card_profile *p) {
+
+ return p && pa_streq(p->name, SND_USE_CASE_VERB_VOICECALL);
+}
+
+struct userdata {
+ pa_hook_slot
+ *card_profile_before_slot,
+ *card_profile_after_slot,
+ *sink_port_slot,
+ *source_port_slot;
+ pa_android_audio_hal *hal;
+ int savedidev, savedodev;
+};
+
+static struct userdata* userdata_instance = NULL; /* We need this for the volume callback */
+
+static bool get_devices_for_card(struct userdata *u, pa_card *card) {
+
+ pa_sink *sink;
+ pa_source *source;
+ uint32_t state;
+ int idev = 0, odev = 0;
+ bool hasports = false;
+
+ PA_IDXSET_FOREACH(sink, card->sinks, state) {
+ const char *n;
+ if (!sink->active_port)
+ continue;
+ n = sink->active_port->name;
+ hasports = true;
+
+ pa_log_debug("Current output port: %s", n);
+ if (pa_alsa_ucm_port_contains(n, SND_USE_CASE_DEV_EARPIECE, true))
+ odev |= AUDIO_DEVICE_OUT_EARPIECE;
+ if (pa_alsa_ucm_port_contains(n, SND_USE_CASE_DEV_SPEAKER, true))
+ odev |= AUDIO_DEVICE_OUT_SPEAKER;
+ if (pa_alsa_ucm_port_contains(n, SND_USE_CASE_DEV_HEADSET, true))
+ odev |= AUDIO_DEVICE_OUT_WIRED_HEADSET;
+ if (pa_alsa_ucm_port_contains(n, SND_USE_CASE_DEV_HEADPHONES, true))
+ odev |= AUDIO_DEVICE_OUT_WIRED_HEADPHONE;
+ }
+
+ PA_IDXSET_FOREACH(source, card->sources, state) {
+ const char *n;
+ if (!source->active_port)
+ continue;
+ n = source->active_port->name;
+ hasports = true;
+
+ pa_log_debug("Current input port: %s", n);
+ if (pa_alsa_ucm_port_contains(n, SND_USE_CASE_DEV_HANDSET, false))
+ idev |= (odev & AUDIO_DEVICE_OUT_SPEAKER) ? AUDIO_DEVICE_IN_BACK_MIC : AUDIO_DEVICE_IN_BUILTIN_MIC;
+ if (pa_alsa_ucm_port_contains(n, SND_USE_CASE_DEV_HEADSET, false))
+ idev |= AUDIO_DEVICE_IN_WIRED_HEADSET;
+ }
+
+ if (hasports) {
+ pa_log_debug("Card %s - active android devices: 0x%x (output) and 0x%x (input)",
+ card->name, odev, idev);
+ u->savedidev = idev;
+ u->savedodev = odev;
+ }
+ if (u->hal) {
+ u->hal->idevice = u->savedidev;
+ u->hal->odevice = u->savedodev;
+ }
+
+ return hasports;
+}
+
+/* We can "hack" into the volume setting code, because PulseAudio does not (yet)
+ support hardware volumes for UCM, so set_volume callback is always NULL. */
+
+static void sink_set_volume_cb(pa_sink *s) {
+
+ struct userdata *u = userdata_instance;
+ pa_volume_t v = pa_cvolume_avg(&s->real_volume);
+ double d = (double) v / (double) PA_VOLUME_NORM; /* Should this be pa_sw_volume_to_linear(v)? */
+ pa_log_debug("Setting voice volume for sink '%s' to %f", s->name, d);
+ if (u->hal)
+ set_voice_call_volume(u->hal, d);
+}
+
+static void source_set_mute_cb(pa_source *s) {
+
+ struct userdata *u = userdata_instance;
+ pa_log_debug("Setting voice mic mute for source '%s' to %d", s->name, (int) s->muted);
+ if (u->hal)
+ set_voice_mic_mute(u->hal, s->muted);
+}
+
+static void teardown_voice_call(struct userdata *u, pa_card *card) {
+
+ /* remove volume hook */
+ uint32_t state;
+ pa_sink *sink;
+ pa_source *source;
+
+ PA_IDXSET_FOREACH(sink, card->sinks, state) {
+ if (sink->set_volume == sink_set_volume_cb)
+ sink->set_volume = NULL;
+ }
+
+ PA_IDXSET_FOREACH(source, card->sources, state) {
+ if (source->set_mute == source_set_mute_cb)
+ source->set_mute = NULL;
+ }
+
+ stop_voice_call(u->hal);
+}
+
+static bool setup_voice_call(struct userdata *u, pa_card *card) {
+
+ uint32_t state;
+ pa_sink *sink;
+ pa_source *source;
+
+ if (!u->hal) {
+ u->hal = pa_android_audio_hal_new();
+ if (!u->hal)
+ return false;
+ }
+
+ get_devices_for_card(u, card);
+ start_voice_call(u->hal);
+
+ /* setup volume hook */
+ PA_IDXSET_FOREACH(sink, card->sinks, state) {
+ if (!sink->set_volume)
+ sink->set_volume = sink_set_volume_cb;
+ sink_set_volume_cb(sink);
+ }
+
+ PA_IDXSET_FOREACH(source, card->sources, state) {
+ if (!source->set_mute)
+ source->set_mute = source_set_mute_cb;
+ source_set_mute_cb(source);
+ }
+
+ return true;
+}
+
+
+static pa_hook_result_t sink_port_hook_callback(pa_core *c, pa_sink *sink, struct userdata* u) {
+
+ if (!sink->card)
+ return PA_HOOK_OK;
+ if (!card_has_voice_call(sink->card) || !profile_in_voice_call(sink->card->active_profile))
+ return PA_HOOK_OK;
+
+ get_devices_for_card(u, sink->card);
+ if (u->hal)
+ update_devices(u->hal);
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_port_hook_callback(pa_core *c, pa_source *source, struct userdata* u) {
+
+ if (!source->card)
+ return PA_HOOK_OK;
+ if (!card_has_voice_call(source->card) || !profile_in_voice_call(source->card->active_profile))
+ return PA_HOOK_OK;
+
+ get_devices_for_card(u, source->card);
+ if (u->hal)
+ update_devices(u->hal);
+ return PA_HOOK_OK;
+}
+
+static pa_hook_result_t card_profile_before_hook_callback(pa_core *c, pa_card_profile *profile, struct userdata *u) {
+
+ if (!card_has_voice_call(profile->card))
+ return PA_HOOK_OK;
+
+ if (u->hal && !profile_in_voice_call(profile)) {
+ teardown_voice_call(u, profile->card);
+ }
+
+ get_devices_for_card(u, profile->card); /* Save for later usage */
+ return PA_HOOK_OK;
+}
+
+
+static pa_hook_result_t card_profile_after_hook_callback(pa_core *c, pa_card *card, struct userdata *u) {
+
+ if (!card_has_voice_call(card))
+ return PA_HOOK_OK;
+
+ if (profile_in_voice_call(card->active_profile))
+ setup_voice_call(u, card);
+
+ return PA_HOOK_OK;
+}
+
+int pa__init(pa_module*m) {
+
+ pa_card *card;
+ uint32_t state;
+
+ struct userdata *u = pa_xnew0(struct userdata, 1);
+
+ pa_assert(userdata_instance == NULL);
+ userdata_instance = u;
+
+ u->card_profile_before_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_CHANGING],
+ PA_HOOK_LATE+30, (pa_hook_cb_t) card_profile_before_hook_callback, u);
+ u->card_profile_after_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_PROFILE_CHANGED],
+ PA_HOOK_LATE+30, (pa_hook_cb_t) card_profile_after_hook_callback, u);
+ u->sink_port_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PORT_CHANGED],
+ PA_HOOK_LATE+30, (pa_hook_cb_t) sink_port_hook_callback, u);
+ u->source_port_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PORT_CHANGED],
+ PA_HOOK_LATE+30, (pa_hook_cb_t) source_port_hook_callback, u);
+
+ PA_IDXSET_FOREACH(card, m->core->cards, state) {
+ if (!card_has_voice_call(card))
+ continue;
+
+ get_devices_for_card(u, card); /* Save for later usage */
+ }
+
+ return 0;
+}
+
+void pa__done(pa_module*m) {
+
+ struct userdata *u;
+
+ pa_assert(m);
+
+ if (!(u = m->userdata))
+ return;
+
+ if (u->card_profile_before_slot)
+ pa_hook_slot_free(u->card_profile_before_slot);
+ if (u->card_profile_after_slot)
+ pa_hook_slot_free(u->card_profile_after_slot);
+ if (u->sink_port_slot)
+ pa_hook_slot_free(u->sink_port_slot);
+ if (u->source_port_slot)
+ pa_hook_slot_free(u->source_port_slot);
+
+ if (u->hal)
+ pa_android_audio_hal_free(u->hal);
+
+ if (u == userdata_instance)
+ userdata_instance = NULL;
+
+ pa_xfree(u);
+}
--
1.7.9.5
More information about the pulseaudio-discuss
mailing list