[pulseaudio] 01/04: Add support for Android 5.x

David Henningsson diwic-guest at moszumanska.debian.org
Mon Feb 1 14:50:13 UTC 2016


This is an automated email from the git hooks/post-receive script.

diwic-guest pushed a commit to branch ubuntu
in repository pulseaudio.

commit c34522b59aed3395b59ce96686b09ccc0f88ecf4
Author: Simon Fels <simon.fels at canonical.com>
Date:   Mon Feb 1 12:21:24 2016 +0100

    Add support for Android 5.x
---
 debian/changelog                                   |   18 +
 debian/control                                     |    5 +-
 ...with-upstream-for-Android-5-support-and-b.patch | 5247 ++++++++++++++++++++
 .../0601-droid-alternative-hw-module-id.patch      |   34 +
 .../0602-droid-inputstream-config-parameters.pach  |   75 +
 ...0603-droid-port-priority-and-availability.patch |  103 +
 debian/patches/series                              |    6 +
 debian/pulseaudio-module-droid.install             |   13 +-
 8 files changed, 5494 insertions(+), 7 deletions(-)

diff --git a/debian/changelog b/debian/changelog
index a416e85..8f43b7b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,21 @@
+pulseaudio (1:7.1-1ubuntu5) xenial; urgency=medium
+
+  * debian/pulseaudio-module-droid.install:
+    - Correctly install all droid modules which have changed names
+      after we're now supporting multiple Android versions.
+
+ -- Simon Fels <simon.fels at canonical.com>  Thu, 28 Jan 2016 11:19:33 +0100
+
+pulseaudio (1:7.1-1ubuntu4) xenial; urgency=medium
+
+  * debian/patches/0600-droid-sync-with-upstream-for-Android-5-support-and-b.patches
+    debian/patches/0601-droid-alternative-hw-module-id.patch
+    debian/patcheshes/0602-droid-inputstream-config-parameters.pach
+    debian/patches/0603-0603droid-port-priority-and-availability.patch:
+    - Import patches from vivid for Android 5.x support
+
+ -- Simon Fels <simon.fels at canonical.com>  Thu, 28 Jan 2016 10:19:54 +0100
+
 pulseaudio (1:7.1-1ubuntu3) xenial; urgency=medium
 
   * trust-store: Update translation string
diff --git a/debian/control b/debian/control
index b14027d..04dcd5f 100644
--- a/debian/control
+++ b/debian/control
@@ -8,13 +8,16 @@ Uploaders: Sjoerd Simons <sjoerd at debian.org>,
     Felipe Sateler <fsateler at debian.org>
 # The following packages can be omitted for bootstrapping (DEB_BUILD_PROFILES=stage1):
 #  libbluetooth-dev
-Build-Depends: debhelper (>= 9),
+Build-Depends: android-headers-19 (>= 23),
+    android-headers-22 (>= 23),
+    debhelper (>= 9),
     cdbs (>= 0.4.93),
     check,
     dh-autoreconf,
     dh-exec,
     intltool,
     libapparmor-dev [linux-any],
+    libandroid-properties-dev [armhf i386 amd64],
     libasound2-dev (>= 1.0.24) [linux-any],
     libasyncns-dev,
     libatomic-ops-dev,
diff --git a/debian/patches/0600-droid-sync-with-upstream-for-Android-5-support-and-b.patch b/debian/patches/0600-droid-sync-with-upstream-for-Android-5-support-and-b.patch
new file mode 100644
index 0000000..50bfdbc
--- /dev/null
+++ b/debian/patches/0600-droid-sync-with-upstream-for-Android-5-support-and-b.patch
@@ -0,0 +1,5247 @@
+From f821497cf8d1031c66ae3a50759045023b43a615 Mon Sep 17 00:00:00 2001
+From: Simon Fels <simon.fels at canonical.com>
+Date: Thu, 14 Jan 2016 09:49:37 +0100
+Subject: [PATCH] droid: sync with upstream for Android 5 support and building
+ for multiple Android versions
+
+---
+ configure.ac                              |   20 +
+ src/Makefile.am                           |  136 +++-
+ src/modules/droid/droid-sink.c            |  391 +++++----
+ src/modules/droid/droid-source.c          |  307 +++----
+ src/modules/droid/droid-source.h          |    3 +
+ src/modules/droid/droid-util-42.h         |    5 +-
+ src/modules/droid/droid-util-44.h         |  358 +++++++++
+ src/modules/droid/droid-util-51.h         |  412 ++++++++++
+ src/modules/droid/droid-util.c            | 1248 +++++++++++++++++++++++------
+ src/modules/droid/droid-util.h            |  117 ++-
+ src/modules/droid/module-droid-card.c     |  585 +++++++++++---
+ src/modules/droid/module-droid-discover.c |   95 +++
+ src/modules/droid/module-droid-sink.c     |   27 +-
+ src/modules/droid/module-droid-source.c   |   12 +-
+ src/modules/module-device-restore.c       |    1 +
+ 15 files changed, 2925 insertions(+), 792 deletions(-)
+ create mode 100644 src/modules/droid/droid-util-44.h
+ create mode 100644 src/modules/droid/droid-util-51.h
+ create mode 100644 src/modules/droid/module-droid-discover.c
+
+Index: overlay/configure.ac
+===================================================================
+--- overlay.orig/configure.ac
++++ overlay/configure.ac
+@@ -822,6 +822,23 @@ AS_IF([test "x$enable_android_hal" = "xy
+ 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?]))
+ 
++AS_IF([test "x$HAVE_ANDROID" = "x1"],
++    [PKG_CHECK_MODULES(LIBANDROID_PROPERTIES, [libandroid-properties], HAVE_ANDROID_PROPERTIES=1, HAVE_ANDROID_PROPERTIES=0)],
++    HAVE_ANDROID_PROPERTIES=0)
++AS_IF([test "x$HAVE_ANDROID" = "x1"],
++    [AS_IF([test "x$HAVE_ANDROID_PROPERTIES" = "x0"],[AC_MSG_ERROR([*** libandroid-properties not found])])],
++    [])
++
++AS_IF([test "x$HAVE_ANDROID" = "x1"],
++    [PKG_CHECK_MODULES(ANDROID_HEADERS_19, [android-headers-19], HAVE_ANDROID_HEADERS_19=1, HAVE_ANDROID_HEADERS_19=0)],
++    HAVE_ANDROID_HEADERS_19=0)
++AM_CONDITIONAL([HAVE_ANDROID_HEADERS_19], [test "x$HAVE_ANDROID_HEADERS_19" = "x1"])
++
++AS_IF([test "x$HAVE_ANDROID" = "x1"],
++    [PKG_CHECK_MODULES(ANDROID_HEADERS_22, [android-headers-22], HAVE_ANDROID_HEADERS_22=1, HAVE_ANDROID_HEADERS_22=0)],
++    HAVE_ANDROID_HEADERS_22=0)
++AM_CONDITIONAL([HAVE_ANDROID_HEADERS_22], [test "x$HAVE_ANDROID_HEADERS_22" = "x1"])
++
+ #### EsounD support (optional) ####
+ 
+ AC_ARG_ENABLE([esound],
+@@ -1527,6 +1544,8 @@ AS_IF([test "x$HAVE_OSS_OUTPUT" = "x1"],
+ 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_ANDROID_HEADERS_19" = "x1"], ENABLE_ANDROID_19=yes, ENABLE_ANDROID_19=no)
++AS_IF([test "x$HAVE_ANDROID_HEADERS_22" = "x1"], ENABLE_ANDROID_22=yes, ENABLE_ANDROID_22=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)
+@@ -1589,6 +1608,8 @@ echo "
+     Enable EsounD:                 ${ENABLE_ESOUND}
+     Enable Alsa:                   ${ENABLE_ALSA}
+     Enable Android Audio HAL:      ${ENABLE_ANDROID}
++      API Level 19:                ${ENABLE_ANDROID_19}
++      API Level 22:                ${ENABLE_ANDROID_22}
+     Enable CoreAudio:              ${ENABLE_COREAUDIO}
+     Enable Solaris:                ${ENABLE_SOLARIS}
+     Enable WaveOut:                ${ENABLE_WAVEOUT}
+Index: overlay/src/Makefile.am
+===================================================================
+--- overlay.orig/src/Makefile.am
++++ overlay/src/Makefile.am
+@@ -1272,12 +1272,25 @@ modlibexec_LTLIBRARIES += \
+ 
+ if HAVE_ANDROID
+ modlibexec_LTLIBRARIES += \
+-		libdroid-util.la \
+-		libdroid-sink.la \
+-		libdroid-source.la \
+-		module-droid-sink.la \
+-		module-droid-source.la \
+-		module-droid-card.la
++		module-droid-discover.la
++if HAVE_ANDROID_HEADERS_19
++modlibexec_LTLIBRARIES += \
++		libdroid-util-19.la \
++		libdroid-sink-19.la \
++		libdroid-source-19.la \
++		module-droid-sink-19.la \
++		module-droid-source-19.la \
++		module-droid-card-19.la
++endif
++if HAVE_ANDROID_HEADERS_22
++modlibexec_LTLIBRARIES += \
++		libdroid-util-22.la \
++		libdroid-sink-22.la \
++		libdroid-source-22.la \
++		module-droid-sink-22.la \
++		module-droid-source-22.la \
++		module-droid-card-22.la
++endif
+ endif
+ 
+ dist_alsaprofilesets_DATA = \
+@@ -1552,9 +1565,19 @@ SYMDEF_FILES = \
+ 
+ if HAVE_ANDROID
+ SYMDEF_FILES += \
+-		module-droid-sink-symdef.h \
+-		module-droid-source-symdef.h \
+-		module-droid-card-symdef.h
++		module-droid-discover-symdef.h
++if HAVE_ANDROID_HEADERS_19
++SYMDEF_FILES += \
++		module-droid-sink-19-symdef.h \
++		module-droid-source-19-symdef.h \
++		module-droid-card-19-symdef.h
++endif
++if HAVE_ANDROID_HEADERS_22
++SYMDEF_FILES += \
++		module-droid-sink-22-symdef.h \
++		module-droid-source-22-symdef.h \
++		module-droid-card-22-symdef.h
++endif
+ endif
+ 
+ if HAVE_ESOUND
+@@ -1867,42 +1890,86 @@ libalsa_util_la_CFLAGS += $(DBUS_CFLAGS)
+ endif
+ 
+ if HAVE_ANDROID
+-libdroid_util_la_SOURCES = modules/droid/droid-util.c modules/droid/droid-util.h
+-libdroid_util_la_LDFLAGS = -avoid-version
+-libdroid_util_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS)
+-libdroid_util_la_CFLAGS = $(AM_CFLAGS) $(LIBHARDWARE_CFLAGS)
+-
+-libdroid_sink_la_SOURCES = modules/droid/droid-sink.c modules/droid/droid-sink.h
+-libdroid_sink_la_LDFLAGS = -avoid-version
+-libdroid_sink_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util.la
+-libdroid_sink_la_CFLAGS = $(AM_CFLAGS) $(LIBHARDWARE_CFLAGS)
+-
+-libdroid_source_la_SOURCES = modules/droid/droid-source.c modules/droid/droid-source.h
+-libdroid_source_la_LDFLAGS = -avoid-version
+-libdroid_source_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util.la
+-libdroid_source_la_CFLAGS = $(AM_CFLAGS) $(LIBHARDWARE_CFLAGS)
+-
+-module_droid_sink_la_SOURCES = modules/droid/module-droid-sink.c
+-module_droid_sink_la_LDFLAGS = $(MODULE_LDFLAGS)
+-module_droid_sink_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util.la libdroid-sink.la
+-module_droid_sink_la_CFLAGS = $(AM_CFLAGS) $(LIBHARDWARE_CFLAGS)
+-
+-module_droid_source_la_SOURCES = modules/droid/module-droid-source.c
+-module_droid_source_la_LDFLAGS = $(MODULE_LDFLAGS)
+-module_droid_source_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util.la libdroid-source.la
+-module_droid_source_la_CFLAGS = $(AM_CFLAGS) $(LIBHARDWARE_CFLAGS)
+-
+-module_droid_card_la_SOURCES = modules/droid/module-droid-card.c
+-module_droid_card_la_LDFLAGS = $(MODULE_LDFLAGS)
+-module_droid_card_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util.la libdroid-sink.la libdroid-source.la
+-module_droid_card_la_CFLAGS = $(AM_CFLAGS) $(LIBHARDWARE_CFLAGS)
++module_droid_discover_la_SOURCES = modules/droid/module-droid-discover.c
++module_droid_discover_la_LDFLAGS = $(MODULE_LDFLAGS)
++module_droid_discover_la_LIBADD = $(MODULE_LIBADD) $(LIBANDROID_PROPERTIES_LIBS)
++module_droid_discover_la_CFLAGS = $(AM_CFLAGS) $(LIBANDROID_PROPERTIES_CFLAGS)
++
++if HAVE_ANDROID_HEADERS_19
++libdroid_util_19_la_SOURCES = modules/droid/droid-util.c modules/droid/droid-util.h
++libdroid_util_19_la_LDFLAGS = -avoid-version
++libdroid_util_19_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS)
++libdroid_util_19_la_CFLAGS = $(AM_CFLAGS) $(ANDROID_HEADERS_19_CFLAGS)
++
++libdroid_sink_19_la_SOURCES = modules/droid/droid-sink.c modules/droid/droid-sink.h
++libdroid_sink_19_la_LDFLAGS = -avoid-version
++libdroid_sink_19_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util-19.la
++libdroid_sink_19_la_CFLAGS = $(AM_CFLAGS) $(ANDROID_HEADERS_19_CFLAGS)
++
++libdroid_source_19_la_SOURCES = modules/droid/droid-source.c modules/droid/droid-source.h
++libdroid_source_19_la_LDFLAGS = -avoid-version
++libdroid_source_19_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util-19.la
++libdroid_source_19_la_CFLAGS = $(AM_CFLAGS) $(ANDROID_HEADERS_19_CFLAGS)
++
++module_droid_sink_19_la_SOURCES = modules/droid/module-droid-sink.c
++module_droid_sink_19_la_LDFLAGS = $(MODULE_LDFLAGS)
++module_droid_sink_19_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util-19.la libdroid-sink-19.la
++module_droid_sink_19_la_CFLAGS = $(AM_CFLAGS) $(ANDROID_HEADERS_19_CFLAGS)
++
++module_droid_source_19_la_SOURCES = modules/droid/module-droid-source.c
++module_droid_source_19_la_LDFLAGS = $(MODULE_LDFLAGS)
++module_droid_source_19_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util-19.la libdroid-source-19.la
++module_droid_source_19_la_CFLAGS = $(AM_CFLAGS) $(ANDROID_HEADERS_19_CFLAGS)
++
++module_droid_card_19_la_SOURCES = modules/droid/module-droid-card.c
++module_droid_card_19_la_LDFLAGS = $(MODULE_LDFLAGS)
++module_droid_card_19_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util-19.la libdroid-sink-19.la libdroid-source-19.la
++module_droid_card_19_la_CFLAGS = $(AM_CFLAGS) $(ANDROID_HEADERS_19_CFLAGS)
+ 
+ if HAVE_UDEV
+-module_droid_card_la_SOURCES += modules/droid/droid-extcon.c modules/droid/droid-extcon.h
+-module_droid_card_la_LIBADD += $(UDEV_LIBS)
+-module_droid_card_la_CFLAGS += $(UDEV_CFLAGS)
+-endif
++module_droid_card_19_la_SOURCES += modules/droid/droid-extcon.c modules/droid/droid-extcon.h
++module_droid_card_19_la_LIBADD += $(UDEV_LIBS)
++module_droid_card_19_la_CFLAGS += $(UDEV_CFLAGS)
++endif
++endif # HAVE_ANDROID_HEADERS_19
++if HAVE_ANDROID_HEADERS_22
++libdroid_util_22_la_SOURCES = modules/droid/droid-util.c modules/droid/droid-util.h
++libdroid_util_22_la_LDFLAGS = -avoid-version
++libdroid_util_22_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS)
++libdroid_util_22_la_CFLAGS = $(AM_CFLAGS) $(ANDROID_HEADERS_22_CFLAGS)
++
++libdroid_sink_22_la_SOURCES = modules/droid/droid-sink.c modules/droid/droid-sink.h
++libdroid_sink_22_la_LDFLAGS = -avoid-version
++libdroid_sink_22_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util-22.la
++libdroid_sink_22_la_CFLAGS = $(AM_CFLAGS) $(ANDROID_HEADERS_22_CFLAGS)
++
++libdroid_source_22_la_SOURCES = modules/droid/droid-source.c modules/droid/droid-source.h
++libdroid_source_22_la_LDFLAGS = -avoid-version
++libdroid_source_22_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util-22.la
++libdroid_source_22_la_CFLAGS = $(AM_CFLAGS) $(ANDROID_HEADERS_22_CFLAGS)
++
++module_droid_sink_22_la_SOURCES = modules/droid/module-droid-sink.c
++module_droid_sink_22_la_LDFLAGS = $(MODULE_LDFLAGS)
++module_droid_sink_22_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util-22.la libdroid-sink-22.la
++module_droid_sink_22_la_CFLAGS = $(AM_CFLAGS) $(ANDROID_HEADERS_22_CFLAGS)
++
++module_droid_source_22_la_SOURCES = modules/droid/module-droid-source.c
++module_droid_source_22_la_LDFLAGS = $(MODULE_LDFLAGS)
++module_droid_source_22_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util-22.la libdroid-source-22.la
++module_droid_source_22_la_CFLAGS = $(AM_CFLAGS) $(ANDROID_HEADERS_22_CFLAGS)
++
++module_droid_card_22_la_SOURCES = modules/droid/module-droid-card.c
++module_droid_card_22_la_LDFLAGS = $(MODULE_LDFLAGS)
++module_droid_card_22_la_LIBADD = $(MODULE_LIBADD) $(LIBHARDWARE_LIBS) libdroid-util-22.la libdroid-sink-22.la libdroid-source-22.la
++module_droid_card_22_la_CFLAGS = $(AM_CFLAGS) $(ANDROID_HEADERS_22_CFLAGS)
++
++if HAVE_UDEV
++module_droid_card_22_la_SOURCES += modules/droid/droid-extcon.c modules/droid/droid-extcon.h
++module_droid_card_22_la_LIBADD += $(UDEV_LIBS)
++module_droid_card_22_la_CFLAGS += $(UDEV_CFLAGS)
+ endif
++endif # HAVE_ANDROID_HEADERS_22
++endif # HAVE_ANDROID
+ 
+ module_alsa_sink_la_SOURCES = modules/alsa/module-alsa-sink.c
+ module_alsa_sink_la_LDFLAGS = $(MODULE_LDFLAGS)
+Index: overlay/src/modules/droid/droid-sink.c
+===================================================================
+--- overlay.orig/src/modules/droid/droid-sink.c
++++ overlay/src/modules/droid/droid-sink.c
+@@ -1,6 +1,5 @@
+ /*
+  * Copyright (C) 2013 Jolla Ltd.
+- * Copyright (C) 2010 Nokia Corporation.
+  *
+  * Contact: Juho Hämäläinen <juho.hamalainen at tieto.com>
+  *
+@@ -76,13 +75,14 @@ struct userdata {
+ 
+     pa_memblockq *memblockq;
+     pa_memchunk silence;
+-    size_t buffer_count;
+     size_t buffer_size;
+-    pa_usec_t buffer_latency;
+-    pa_usec_t timestamp;
++    pa_usec_t buffer_time;
++    pa_usec_t write_time;
++    pa_usec_t write_threshold;
+ 
+     audio_devices_t primary_devices;
+     audio_devices_t extra_devices;
++    pa_hashmap *extra_devices_map;
+ 
+     bool use_hw_volume;
+     bool use_voice_volume;
+@@ -101,16 +101,15 @@ struct userdata {
+ 
+     pa_droid_card_data *card_data;
+     pa_droid_hw_module *hw_module;
+-    struct audio_stream_out *stream_out;
+-
+-    char *sco_fake_sink_name;
+-    struct pa_sink *sco_fake_sink;
++    pa_droid_stream *stream;
+ };
+ 
+ enum {
+     SINK_MESSAGE_DO_ROUTING = PA_SINK_MESSAGE_MAX,
+ };
+ 
++#define PULSEAUDIO_VERSION_MAJOR 6
++
+ #define DEFAULT_MODULE_ID "primary"
+ 
+ /* sink properties */
+@@ -138,6 +137,7 @@ typedef struct droid_parameter_mapping {
+ #define DEFAULT_SCO_FAKE_SINK "sink.fake.sco"
+ #define HSP_PREVENT_SUSPEND_STR "bluetooth.hsp.prevent.suspend.transport"
+ 
++static void parameter_free(droid_parameter_mapping *m);
+ static void userdata_free(struct userdata *u);
+ static void set_voice_volume_from_input(struct userdata *u, pa_sink_input *i);
+ static struct pa_sink *pa_sco_fake_sink_discover(pa_core *core, const char *sink_name);
+@@ -149,67 +149,77 @@ static void set_primary_devices(struct u
+     u->primary_devices = devices;
+ }
+ 
+-static void add_extra_devices(struct userdata *u, audio_devices_t devices) {
+-    pa_assert(u);
+-    pa_assert(devices);
++static bool add_extra_devices(struct userdata *u, audio_devices_t devices) {
++    void *value;
++    uint32_t count;
++    bool need_update = false;
+ 
+-    u->extra_devices |= devices;
+-}
+-
+-static void remove_extra_devices(struct userdata *u, audio_devices_t devices) {
+     pa_assert(u);
++    pa_assert(u->extra_devices_map);
+     pa_assert(devices);
+ 
+-    u->extra_devices &= ~devices;
+-}
++    if ((value = pa_hashmap_get(u->extra_devices_map, PA_UINT_TO_PTR(devices)))) {
++        count = PA_PTR_TO_UINT(value);
++        count++;
++        pa_hashmap_remove(u->extra_devices_map, PA_UINT_TO_PTR(devices));
++        pa_hashmap_put(u->extra_devices_map, PA_UINT_TO_PTR(devices), PA_UINT_TO_PTR(count));
+ 
+-static void parameter_free(droid_parameter_mapping *m) {
+-    pa_assert(m);
++        /* added extra device already exists in hashmap, so no need to update route. */
++        need_update = false;
++    } else {
++        pa_hashmap_put(u->extra_devices_map, PA_UINT_TO_PTR(devices), PA_UINT_TO_PTR(1));
++        u->extra_devices |= devices;
++        need_update = true;
++    }
+ 
+-    pa_xfree(m->key);
+-    pa_xfree(m->value);
+-    pa_xfree(m);
++    return need_update;
+ }
+ 
+-static void set_fake_sco_sink_transport_property(struct userdata *u, const char *value) {
+-    pa_proplist *pl;
++static bool remove_extra_devices(struct userdata *u, audio_devices_t devices) {
++    void *value;
++    uint32_t count;
++    bool need_update = false;
+ 
+     pa_assert(u);
+-    pa_assert(value);
+-    pa_assert(u->sco_fake_sink);
++    pa_assert(u->extra_devices_map);
++    pa_assert(devices);
+ 
+-    pl = pa_proplist_new();
+-    pa_proplist_sets(pl, HSP_PREVENT_SUSPEND_STR, value);
+-    pa_sink_update_proplist(u->sco_fake_sink, PA_UPDATE_REPLACE, pl);
+-    pa_proplist_free(pl);
++    if ((value = pa_hashmap_get(u->extra_devices_map, PA_UINT_TO_PTR(devices)))) {
++        pa_hashmap_remove(u->extra_devices_map, PA_UINT_TO_PTR(devices));
++        count = PA_PTR_TO_UINT(value);
++        count--;
++        if (count == 0) {
++            u->extra_devices &= ~devices;
++            need_update = true;
++        } else {
++            /* added extra devices still exists in hashmap, so no need to update route. */
++            pa_hashmap_put(u->extra_devices_map, PA_UINT_TO_PTR(devices), PA_UINT_TO_PTR(count));
++            need_update = false;
++        }
++    }
++
++    return need_update;
+ }
+ 
+ /* Called from main context during voice calls, and from IO context during media operation. */
+-static bool do_routing(struct userdata *u) {
++static void do_routing(struct userdata *u) {
+     audio_devices_t routing;
+-    char tmp[32];
+ 
+     pa_assert(u);
+-    pa_assert(u->stream_out);
++    pa_assert(u->stream);
+ 
+     routing = u->primary_devices | u->extra_devices;
+ 
+-    pa_snprintf(tmp, sizeof(tmp), "%s=%u;", AUDIO_PARAMETER_STREAM_ROUTING, routing);
+-    pa_log_debug("Routing: set_parameters(): %s (%#010x)", tmp, routing);
+-    pa_droid_hw_module_lock(u->hw_module);
+-    u->stream_out->common.set_parameters(&u->stream_out->common, tmp);
+-    pa_droid_hw_module_unlock(u->hw_module);
+-
+-    return true;
++    pa_droid_stream_set_output_route(u->stream, routing);
+ }
+ 
+ static bool parse_device_list(const char *str, audio_devices_t *dst) {
+-    char *dev;
+-    const char *state = NULL;
+-
+     pa_assert(str);
+     pa_assert(dst);
+ 
++    char *dev;
++    const char *state = NULL;
++
+     *dst = 0;
+ 
+     while ((dev = pa_split(str, "|", &state))) {
+@@ -235,15 +245,18 @@ static int thread_write_silence(struct u
+ 
+     /* Drop our rendered audio and write silence to HAL. */
+     pa_memblockq_drop(u->memblockq, u->buffer_size);
++    u->write_time = pa_rtclock_now();
+ 
+     /* We should be able to write everything in one go as long as memblock size
+      * is multiples of buffer_size. Even if we don't write whole buffer size
+      * here it's okay, as long as mute time isn't configured too strictly. */
+ 
+     p = pa_memblock_acquire(u->silence.memblock);
+-    wrote = u->stream_out->write(u->stream_out, (const uint8_t*) p + u->silence.index, u->silence.length);
++    wrote = u->stream->out->write(u->stream->out, (const uint8_t*) p + u->silence.index, u->silence.length);
+     pa_memblock_release(u->silence.memblock);
+ 
++    u->write_time = pa_rtclock_now() - u->write_time;
++
+     if (wrote < 0)
+         return -1;
+ 
+@@ -260,14 +273,18 @@ static int thread_write(struct userdata
+     /* We should be able to write everything in one go as long as memblock size
+      * is multiples of buffer_size. */
+ 
++    u->write_time = pa_rtclock_now();
++
+     for (;;) {
+         p = pa_memblock_acquire(c.memblock);
+-        wrote = u->stream_out->write(u->stream_out, (const uint8_t*) p + c.index, c.length);
++        wrote = u->stream->out->write(u->stream->out, (const uint8_t*) p + c.index, c.length);
+         pa_memblock_release(c.memblock);
+ 
+         if (wrote < 0) {
+             pa_memblockq_drop(u->memblockq, c.length);
+             pa_memblock_unref(c.memblock);
++            u->write_time = 0;
++            pa_log("failed to write stream (%d)", wrote);
+             return -1;
+         }
+ 
+@@ -283,6 +300,8 @@ static int thread_write(struct userdata
+         break;
+     }
+ 
++    u->write_time = pa_rtclock_now() - u->write_time;
++
+     return 0;
+ }
+ static void thread_render(struct userdata *u) {
+@@ -290,7 +309,7 @@ static void thread_render(struct userdat
+     size_t missing;
+ 
+     length = pa_memblockq_get_length(u->memblockq);
+-    missing = u->buffer_size * u->buffer_count - length;
++    missing = u->buffer_size - length;
+ 
+     if (missing > 0) {
+         pa_memchunk c;
+@@ -352,22 +371,18 @@ static void thread_func(void *userdata)
+ 
+     pa_thread_mq_install(&u->thread_mq);
+ 
+-    u->timestamp = 0;
+-
+     for (;;) {
+         int ret;
+ 
+         if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
+ 
+-            u->timestamp = pa_rtclock_now();
+-
+             if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))
+                 process_rewind(u);
+-            else
+-                thread_render(u);
+ 
+             if (pa_rtpoll_timer_elapsed(u->rtpoll)) {
+-                pa_usec_t now, sleept;
++                pa_usec_t sleept = 0;
++
++                thread_render(u);
+ 
+                 if (u->routing_counter == u->mute_routing_after) {
+                     do_routing(u);
+@@ -378,12 +393,8 @@ static void thread_func(void *userdata)
+                 } else
+                     thread_write(u);
+ 
+-                now = pa_rtclock_now();
+-
+-                if (now - u->timestamp > u->buffer_latency / 2)
+-                    sleept = 0;
+-                else
+-                    sleept = u->buffer_latency / 2 - (now - u->timestamp) ;
++                if (u->write_time > u->write_threshold)
++                    sleept = u->buffer_time;
+ 
+                 pa_rtpoll_set_timer_relative(u->rtpoll, sleept);
+             }
+@@ -391,7 +402,11 @@ static void thread_func(void *userdata)
+             pa_rtpoll_set_timer_disabled(u->rtpoll);
+ 
+         /* Sleep */
++#if (PULSEAUDIO_VERSION_MAJOR == 5)
++        if ((ret = pa_rtpoll_run(u->rtpoll, true)) < 0)
++#elif (PULSEAUDIO_VERSION_MAJOR == 6)
+         if ((ret = pa_rtpoll_run(u->rtpoll)) < 0)
++#endif
+             goto fail;
+ 
+         if (ret == 0)
+@@ -415,9 +430,9 @@ static int suspend(struct userdata *u) {
+ 
+     pa_assert(u);
+     pa_assert(u->sink);
+-    pa_assert(u->stream_out);
++    pa_assert(u->stream->out);
+ 
+-    ret = u->stream_out->common.standby(&u->stream_out->common);
++    ret = u->stream->out->common.standby(&u->stream->out->common);
+ 
+     if (ret == 0) {
+         pa_sink_set_max_request_within_thread(u->sink, 0);
+@@ -462,8 +477,8 @@ static int sink_process_msg(pa_msgobject
+             pa_usec_t r = 0;
+ 
+             /* HAL reports milliseconds */
+-            if (u->stream_out)
+-                r = u->stream_out->get_latency(u->stream_out) * PA_USEC_PER_MSEC * u->buffer_count;
++            if (u->stream->out)
++                r = u->stream->out->get_latency(u->stream->out) * PA_USEC_PER_MSEC;
+ 
+             *((pa_usec_t*) data) = r;
+ 
+@@ -487,7 +502,6 @@ static int sink_process_msg(pa_msgobject
+                     /* Fall through */
+                 case PA_SINK_RUNNING: {
+                     int r;
+-                    u->timestamp = 0;
+ 
+                     if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
+                         if ((r = unsuspend(u)) < 0)
+@@ -498,8 +512,13 @@ static int sink_process_msg(pa_msgobject
+                     break;
+                 }
+ 
++                case PA_SINK_UNLINKED: {
++                    /* Suspending since some implementations do not want to free running stream. */
++                    suspend(u);
++                    break;
++                }
++
+                 /* not needed */
+-                case PA_SINK_UNLINKED:
+                 case PA_SINK_INIT:
+                 case PA_SINK_INVALID_STATE:
+                     ;
+@@ -514,7 +533,6 @@ static int sink_process_msg(pa_msgobject
+ static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
+     struct userdata *u = s->userdata;
+     pa_droid_port_data *data;
+-    const char *sco_transport_enabled;
+ 
+     pa_assert(u);
+     pa_assert(p);
+@@ -532,24 +550,9 @@ static int sink_set_port_cb(pa_sink *s,
+     pa_log_debug("Sink set port %u", data->device);
+ 
+     set_primary_devices(u, data->device);
+-
+-    /* See if the sco fake sink element is available (only when needed) */
+-    if ((u->sco_fake_sink == NULL) && (data->device & AUDIO_DEVICE_OUT_ALL_SCO))
+-        u->sco_fake_sink = pa_sco_fake_sink_discover(u->core, u->sco_fake_sink_name);
+-
+-    /* Update the bluetooth hsp transport property before we do the routing */
+-    if (u->sco_fake_sink) {
+-        sco_transport_enabled = pa_proplist_gets(u->sco_fake_sink->proplist, HSP_PREVENT_SUSPEND_STR);
+-        if (sco_transport_enabled && pa_streq(sco_transport_enabled, "true")) {
+-            if (data->device & ~AUDIO_DEVICE_OUT_ALL_SCO)
+-                set_fake_sco_sink_transport_property(u, "false");
+-        } else if (data->device & AUDIO_DEVICE_OUT_ALL_SCO)
+-            set_fake_sco_sink_transport_property(u, "true");
+-    }
+-
+     /* If we are in voice call, sink is usually in suspended state and routing change can be applied immediately.
+-     * When in media use cases, do the routing change in IO thread. */
+-    if (u->use_voice_volume)
++     * When in media use cases, do the routing change in IO thread if we are currently in RUNNING or IDLE state. */
++    if (u->use_voice_volume || !PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)))
+         do_routing(u);
+     else {
+         pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_DO_ROUTING, NULL, 0, NULL, NULL);
+@@ -569,7 +572,7 @@ static void sink_set_volume_cb(pa_sink *
+         float val = pa_sw_volume_to_linear(r.values[0]);
+         pa_log_debug("Set hw volume %f", val);
+         pa_droid_hw_module_lock(u->hw_module);
+-        if (u->stream_out->set_volume(u->stream_out, val, val) < 0)
++        if (u->stream->out->set_volume(u->stream->out, val, val) < 0)
+             pa_log_warn("Failed to set hw volume.");
+         pa_droid_hw_module_unlock(u->hw_module);
+     } else if (r.channels == 2) {
+@@ -578,7 +581,7 @@ static void sink_set_volume_cb(pa_sink *
+             val[i] = pa_sw_volume_to_linear(r.values[i]);
+         pa_log_debug("Set hw volume %f : %f", val[0], val[1]);
+         pa_droid_hw_module_lock(u->hw_module);
+-        if (u->stream_out->set_volume(u->stream_out, val[0], val[1]) < 0)
++        if (u->stream->out->set_volume(u->stream->out, val[0], val[1]) < 0)
+             pa_log_warn("Failed to set hw volume.");
+         pa_droid_hw_module_unlock(u->hw_module);
+     }
+@@ -635,10 +638,9 @@ static void update_volumes(struct userda
+ 
+     /* set_volume returns 0 if hw volume control is implemented, < 0 otherwise. */
+     pa_droid_hw_module_lock(u->hw_module);
+-    if (u->stream_out->set_volume) {
++    if (u->stream->out->set_volume) {
+         pa_log_debug("Probe hw volume support for %s", u->sink->name);
+-        pa_log_debug("Stream out volume set to 1.0f, 1.0f");
+-        ret = u->stream_out->set_volume(u->stream_out, 1.0f, 1.0f);
++        ret = u->stream->out->set_volume(u->stream->out, 1.0f, 1.0f);
+     }
+     pa_droid_hw_module_unlock(u->hw_module);
+ 
+@@ -808,6 +810,7 @@ void pa_droid_sink_set_voice_control(pa_
+ 
+     if (u->use_voice_volume) {
+         pa_log_debug("Using voice volume control for %s", u->sink->name);
++        pa_sink_set_set_volume_callback(u->sink, NULL);
+ 
+         if (u->voice_virtual_stream)
+             create_voice_virtual_stream(u);
+@@ -818,6 +821,7 @@ void pa_droid_sink_set_voice_control(pa_
+ 
+             /* First disable module-device-restore, as we don't want to save the voice volume
+              * as the default sink volume when restoring to the default mode */
++#define MODULE_DEVICE_RESTORE_SKIP_PROPERTY "module-device-restore.skip"
+             pa_proplist_sets(u->sink->proplist, MODULE_DEVICE_RESTORE_SKIP_PROPERTY, "true");
+ 
+             /* Then map normal sink volume changes to voice call volume changes */
+@@ -888,9 +892,9 @@ static pa_hook_result_t sink_input_put_h
+ 
+             pa_log_debug("Add extra route %s (%u).", dev_str, devices);
+ 
+-            add_extra_devices(u, devices);
+-            /* post routing change */
+-            pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_DO_ROUTING, NULL, 0, NULL, NULL);
++            /* if this device was not routed to previously post routing change */
++            if (add_extra_devices(u, devices))
++                pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_DO_ROUTING, NULL, 0, NULL, NULL);
+         }
+     }
+ 
+@@ -919,9 +923,9 @@ static pa_hook_result_t sink_input_unlin
+ 
+             pa_log_debug("Remove extra route %s (%u).", dev_str, devices);
+ 
+-            remove_extra_devices(u, devices);
+-            /* post routing change */
+-            pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_DO_ROUTING, NULL, 0, NULL, NULL);
++            /* if this device no longer exists in extra devices map post routing change */
++            if (remove_extra_devices(u, devices))
++                pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_DO_ROUTING, NULL, 0, NULL, NULL);
+         }
+     }
+ 
+@@ -973,10 +977,8 @@ static pa_hook_result_t sink_proplist_ch
+         if (changed) {
+             pa_assert(parameter);
+             tmp = pa_sprintf_malloc("%s=%s;", parameter->key, parameter->value);
+-            pa_log_debug("sink proplist changed: set_parameters(): %s", tmp);
+-            pa_droid_hw_module_lock(u->hw_module);
+-            u->stream_out->common.set_parameters(&u->stream_out->common, tmp);
+-            pa_droid_hw_module_unlock(u->hw_module);
++            pa_log_debug("set_parameters(): %s", tmp);
++            pa_droid_stream_set_parameters(u->stream, tmp);
+             pa_xfree(tmp);
+         }
+     }
+@@ -984,25 +986,6 @@ static pa_hook_result_t sink_proplist_ch
+     return PA_HOOK_OK;
+ }
+ 
+-static struct pa_sink *pa_sco_fake_sink_discover(pa_core *core, const char *sink_name) {
+-    struct pa_sink *sink;
+-    pa_idxset *idxset;
+-    void *state = NULL;
+-
+-    pa_assert(core);
+-    pa_assert(sink_name);
+-    pa_assert_se((idxset = core->sinks));
+-
+-    while ((sink = pa_idxset_iterate(idxset, &state, NULL)) != NULL) {
+-        if (pa_streq(sink_name, sink->name)) {
+-            pa_log_debug("Found fake SCO sink '%s'", sink_name);
+-            return sink;
+-        }
+-    }
+-
+-    return NULL;
+-}
+-
+ pa_sink *pa_droid_sink_new(pa_module *m,
+                              pa_modargs *ma,
+                              const char *driver,
+@@ -1019,23 +1002,20 @@ pa_sink *pa_droid_sink_new(pa_module *m,
+     pa_sink_new_data data;
+     const char *module_id = NULL;
+     const char *tmp;
+-    /* char *list = NULL; */
++    char *list = NULL;
+     uint32_t alternate_sample_rate;
+-    uint32_t sample_rate;
++    const char *format;
+     audio_devices_t dev_out;
+     pa_sample_spec sample_spec;
+     pa_channel_map channel_map;
+     bool namereg_fail = false;
+-    uint32_t total_latency;
++    pa_usec_t latency;
+     pa_droid_config_audio *config = NULL; /* Only used when sink is created without card */
+     int32_t mute_routing_before = 0;
+     int32_t mute_routing_after = 0;
+     uint32_t sink_buffer = 0;
+     int ret;
+ 
+-    audio_format_t hal_audio_format = 0;
+-    audio_channel_mask_t hal_channel_mask = 0;
+-
+     pa_assert(m);
+     pa_assert(ma);
+     pa_assert(driver);
+@@ -1054,8 +1034,36 @@ pa_sink *pa_droid_sink_new(pa_module *m,
+     sample_spec = m->core->default_sample_spec;
+     channel_map = m->core->default_channel_map;
+ 
++    /* First parse both sample spec and channel map, then see if sink_* override some
++     * of the values. */
+     if (pa_modargs_get_sample_spec_and_channel_map(ma, &sample_spec, &channel_map, PA_CHANNEL_MAP_AIFF) < 0) {
+-        pa_log("Failed to parse sample specification and channel map.");
++        pa_log("Failed to parse sink sample specification and channel map.");
++        goto fail;
++    }
++
++    if (pa_modargs_get_value(ma, "sink_channel_map", NULL)) {
++        if (pa_modargs_get_channel_map(ma, "sink_channel_map", &channel_map) < 0) {
++            pa_log("Failed to parse sink channel map.");
++            goto fail;
++        }
++
++        sample_spec.channels = channel_map.channels;
++    }
++
++    if ((format = pa_modargs_get_value(ma, "sink_format", NULL))) {
++        if ((sample_spec.format = pa_parse_sample_format(format)) < 0) {
++            pa_log("Failed to parse sink format.");
++            goto fail;
++        }
++    }
++
++    if (pa_modargs_get_value_u32(ma, "sink_rate", &sample_spec.rate) < 0) {
++        pa_log("Failed to parse sink samplerate");
++        goto fail;
++    }
++
++    if (!pa_sample_spec_valid(&sample_spec)) {
++        pa_log("Sample spec is not valid.");
+         goto fail;
+     }
+ 
+@@ -1097,12 +1105,13 @@ pa_sink *pa_droid_sink_new(pa_module *m,
+     u->deferred_volume = deferred_volume;
+     u->rtpoll = pa_rtpoll_new();
+     pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
+-    u->parameters = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) parameter_free);
++    u->parameters = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
++                                        NULL, (pa_free_cb_t) parameter_free);
+     u->voice_volume_call_mode = voice_volume_call_mode;
+     u->voice_virtual_stream = voice_virtual_stream;
+     u->voice_property_key   = pa_xstrdup(pa_modargs_get_value(ma, "voice_property_key", DEFAULT_VOICE_CONTROL_PROPERTY_KEY));
+     u->voice_property_value = pa_xstrdup(pa_modargs_get_value(ma, "voice_property_value", DEFAULT_VOICE_CONTROL_PROPERTY_VALUE));
+-    u->sco_fake_sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sco_fake_sink", DEFAULT_SCO_FAKE_SINK));
++    u->extra_devices_map = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ 
+     if (card_data) {
+         u->card_data = card_data;
+@@ -1110,40 +1119,26 @@ pa_sink *pa_droid_sink_new(pa_module *m,
+         pa_assert_se((u->hw_module = pa_droid_hw_module_get(u->core, NULL, card_data->module_id)));
+     } else {
+         /* Sink wasn't created from inside card module, so we'll need to open
+-         * hw module ourselves.
+-         * TODO some way to share hw module between other sinks/sources since
+-         * opening same module from different places likely isn't a good thing. */
+-
+-        if (!(config = pa_droid_config_load(ma)))
+-            goto fail;
+-
+-        /* Ownership of config transfers to hw_module if opening of hw module succeeds. */
+-        if (!(u->hw_module = pa_droid_hw_module_get(u->core, config, module_id)))
+-            goto fail;
+-    }
+-
+-    if (!pa_convert_format(sample_spec.format, CONV_FROM_PA, &hal_audio_format)) {
+-        pa_log("Sample spec format %u not supported.", sample_spec.format);
+-        goto fail;
+-    }
+-
+-    for (int i = 0; i < channel_map.channels; i++) {
+-        audio_channel_mask_t c;
+-        if (!pa_convert_output_channel(channel_map.map[i], CONV_FROM_PA, &c)) {
+-            pa_log("Failed to convert channel map.");
+-            goto fail;
++         * hw module ourself.
++         *
++         * First let's find out if hw module has already been opened, or if we need to
++         * do it ourself.
++         */
++        if (!(u->hw_module = pa_droid_hw_module_get(u->core, NULL, module_id))) {
++
++            /* No hw module object in shared object db, let's open the module now. */
++
++            if (!(config = pa_droid_config_load(ma)))
++                goto fail;
++
++            /* Ownership of config transfers to hw_module if opening of hw module succeeds. */
++            if (!(u->hw_module = pa_droid_hw_module_get(u->core, config, module_id)))
++                goto fail;
+         }
+-        hal_channel_mask |= c;
+     }
+ 
+-    struct audio_config config_out = {
+-        .sample_rate = sample_spec.rate,
+-        .channel_mask = hal_channel_mask,
+-        .format = hal_audio_format
+-    };
+-
+     /* Default routing */
+-    dev_out = AUDIO_DEVICE_OUT_DEFAULT;
++    dev_out = u->hw_module->config->global_config.default_output_device;
+ 
+     if ((tmp = pa_modargs_get_value(ma, "output_devices", NULL))) {
+         audio_devices_t tmp_dev;
+@@ -1157,26 +1152,14 @@ pa_sink *pa_droid_sink_new(pa_module *m,
+     if (am)
+         flags = am->output->flags;
+ 
+-    pa_droid_hw_module_lock(u->hw_module);
+-    ret = u->hw_module->device->open_output_stream(u->hw_module->device,
+-                                                   u->hw_module->stream_out_id++,
+-                                                   dev_out,
+-                                                   flags,
+-                                                   &config_out,
+-                                                   &u->stream_out);
+-    pa_droid_hw_module_unlock(u->hw_module);
++    u->stream = pa_droid_open_output_stream(u->hw_module, &sample_spec, &channel_map, flags, dev_out);
+ 
+-    if (!u->stream_out) {
+-        pa_log("Failed to open output stream. (errno %d)", ret);
++    if (!u->stream) {
++        pa_log("Failed to open output stream.");
+         goto fail;
+     }
+ 
+-    if ((sample_rate = u->stream_out->common.get_sample_rate(&u->stream_out->common)) != sample_spec.rate) {
+-        pa_log_warn("Requested sample rate %u but got %u instead.", sample_spec.rate, sample_rate);
+-        sample_spec.rate = sample_rate;
+-    }
+-
+-    u->buffer_size = u->stream_out->common.get_buffer_size(&u->stream_out->common);
++    u->buffer_size = u->stream->out->common.get_buffer_size(&u->stream->out->common);
+     if (sink_buffer) {
+         if (sink_buffer < u->buffer_size)
+             pa_log_warn("Requested buffer size %u less than HAL reported buffer size (%u).", sink_buffer, u->buffer_size);
+@@ -1190,40 +1173,32 @@ pa_sink *pa_droid_sink_new(pa_module *m,
+         }
+     }
+ 
+-    u->buffer_latency = pa_bytes_to_usec(u->buffer_size, &sample_spec);
+-    /* Disable internal rewinding for now. */
+-    u->buffer_count = 1;
+-
+-    pa_log_info("Created Android stream with device: %u flags: %u sample rate: %u channel mask: %u format: %u buffer size: %u",
+-            dev_out,
+-            flags,
+-            sample_rate,
+-            config_out.channel_mask,
+-            config_out.format,
+-            u->buffer_size);
+-
++    u->buffer_time = pa_bytes_to_usec(u->buffer_size, &u->stream->sample_spec);
+ 
++    u->write_threshold = u->buffer_time - u->buffer_time / 6;
+     u->mute_routing_before = mute_routing_before / u->buffer_size;
+     u->mute_routing_after = mute_routing_after / u->buffer_size;
+     if (u->mute_routing_before == 0 && mute_routing_before)
+-        u->mute_routing_before = u->buffer_size;
++        u->mute_routing_before = 1;
+     if (u->mute_routing_after == 0 && mute_routing_after)
+-        u->mute_routing_after = u->buffer_size;
++        u->mute_routing_after = 1;
+     if (u->mute_routing_before || u->mute_routing_after)
+         pa_log_debug("Mute playback when routing is changing, %u before and %u after.",
+                      u->mute_routing_before * u->buffer_size,
+                      u->mute_routing_after * u->buffer_size);
+-    pa_silence_memchunk_get(&u->core->silence_cache, u->core->mempool, &u->silence, &sample_spec, u->buffer_size);
+-    u->memblockq = pa_memblockq_new("droid-sink", 0, u->buffer_size * u->buffer_count, u->buffer_size * u->buffer_count, &sample_spec, 1, 0, 0, &u->silence);
++    pa_silence_memchunk_get(&u->core->silence_cache, u->core->mempool, &u->silence, &u->stream->sample_spec, u->buffer_size);
++    u->memblockq = pa_memblockq_new("droid-sink", 0, u->buffer_size, u->buffer_size, &u->stream->sample_spec, 1, 0, 0, &u->silence);
+ 
+     pa_sink_new_data_init(&data);
+     data.driver = driver;
+     data.module = m;
+     data.card = card;
+ 
+-    set_sink_name(ma, &data, module_id);
++    if (am)
++        set_sink_name(ma, &data, am->output->name);
++    else
++        set_sink_name(ma, &data, module_id);
+     pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "sound");
+-    pa_proplist_sets(data.proplist, PA_PROP_DEVICE_FORM_FACTOR, "internal");
+ 
+     /* We need to give pa_modargs_get_value_boolean() a pointer to a local
+      * variable instead of using &data.namereg_fail directly, because
+@@ -1237,8 +1212,8 @@ pa_sink *pa_droid_sink_new(pa_module *m,
+     }
+     data.namereg_fail = namereg_fail;
+ 
+-    pa_sink_new_data_set_sample_spec(&data, &sample_spec);
+-    pa_sink_new_data_set_channel_map(&data, &channel_map);
++    pa_sink_new_data_set_sample_spec(&data, &u->stream->sample_spec);
++    pa_sink_new_data_set_channel_map(&data, &u->stream->channel_map);
+     pa_sink_new_data_set_alternate_sample_rate(&data, alternate_sample_rate);
+ 
+     /*
+@@ -1282,9 +1257,12 @@ pa_sink *pa_droid_sink_new(pa_module *m,
+     pa_sink_set_rtpoll(u->sink, u->rtpoll);
+ 
+     /* Rewind internal memblockq */
+-    pa_sink_set_max_rewind(u->sink, u->buffer_size * (u->buffer_count - 1));
++    pa_sink_set_max_rewind(u->sink, 0);
+ 
+-    thread_name = pa_sprintf_malloc("droid-sink-%s", module_id);
++    if (am)
++        thread_name = pa_sprintf_malloc("droid-sink-%s", am->output->name);
++    else
++        thread_name = pa_sprintf_malloc("droid-sink-%s", module_id);
+     if (!(u->thread = pa_thread_new(thread_name, thread_func, u))) {
+         pa_log("Failed to create thread.");
+         goto fail;
+@@ -1292,11 +1270,11 @@ pa_sink *pa_droid_sink_new(pa_module *m,
+     pa_xfree(thread_name);
+     thread_name = NULL;
+ 
+-    /* Latency consists of HAL latency + our memblockq latency */
+-    total_latency = u->stream_out->get_latency(u->stream_out) + (uint32_t) pa_bytes_to_usec(u->buffer_size * u->buffer_count, &sample_spec);
+-    pa_sink_set_fixed_latency(u->sink, total_latency);
+-    pa_log_debug("Set fixed latency %lu usec", (unsigned long) pa_bytes_to_usec(total_latency, &sample_spec));
+-    pa_sink_set_max_request(u->sink, u->buffer_size * u->buffer_count);
++    /* HAL latencies are in milliseconds. */
++    latency = u->stream->out->get_latency(u->stream->out) * PA_USEC_PER_MSEC;
++    pa_sink_set_fixed_latency(u->sink, latency);
++    pa_log_debug("Set fixed latency %llu usec", latency);
++    pa_sink_set_max_request(u->sink, u->buffer_size);
+ 
+     if (u->sink->active_port)
+         sink_set_port_cb(u->sink, u->sink->active_port);
+@@ -1337,6 +1315,14 @@ void pa_droid_sink_free(pa_sink *s) {
+     userdata_free(u);
+ }
+ 
++static void parameter_free(droid_parameter_mapping *m) {
++    pa_assert(m);
++
++    pa_xfree(m->key);
++    pa_xfree(m->value);
++    pa_xfree(m);
++}
++
+ static void userdata_free(struct userdata *u) {
+ 
+     if (u->sink)
+@@ -1367,11 +1353,8 @@ static void userdata_free(struct userdat
+     if (u->parameters)
+         pa_hashmap_free(u->parameters);
+ 
+-    if (u->hw_module && u->stream_out) {
+-        pa_droid_hw_module_lock(u->hw_module);
+-        u->hw_module->device->close_output_stream(u->hw_module->device, u->stream_out);
+-        pa_droid_hw_module_unlock(u->hw_module);
+-    }
++    if (u->stream)
++        pa_droid_stream_unref(u->stream);
+ 
+     if (u->memblockq)
+         pa_memblockq_free(u->memblockq);
+@@ -1382,13 +1365,13 @@ static void userdata_free(struct userdat
+     if (u->hw_module)
+         pa_droid_hw_module_unref(u->hw_module);
+ 
+-    if (u->sco_fake_sink_name)
+-        pa_xfree(u->sco_fake_sink_name);
+-
+     if (u->voice_property_key)
+         pa_xfree(u->voice_property_key);
+     if (u->voice_property_value)
+         pa_xfree(u->voice_property_value);
+ 
++    if (u->extra_devices_map)
++        pa_hashmap_free(u->extra_devices_map);
++
+     pa_xfree(u);
+ }
+Index: overlay/src/modules/droid/droid-source.c
+===================================================================
+--- overlay.orig/src/modules/droid/droid-source.c
++++ overlay/src/modules/droid/droid-source.c
+@@ -66,7 +66,6 @@ struct userdata {
+ 
+     pa_memchunk memchunk;
+     audio_devices_t primary_devices;
+-    audio_devices_t enabled_devices;
+     bool routing_changes_enabled;
+ 
+     size_t buffer_size;
+@@ -74,58 +73,65 @@ struct userdata {
+ 
+     pa_droid_card_data *card_data;
+     pa_droid_hw_module *hw_module;
+-    audio_stream_in_t *stream;
++    pa_droid_stream *stream;
+ };
+ 
++#define PULSEAUDIO_VERSION_MAJOR 6
++
+ #define DEFAULT_MODULE_ID "primary"
+ 
++#define DROID_AUDIO_SOURCE "droid.audio_source"
++#define DROID_AUDIO_SOURCE_UNDEFINED "undefined"
++
+ static void userdata_free(struct userdata *u);
+ 
+-static bool do_routing(struct userdata *u, audio_devices_t devices) {
+-    char tmp[32];
+-    char *devlist;
++static int do_routing(struct userdata *u, audio_devices_t devices, bool force) {
++    int ret;
++    pa_proplist *p;
++    const char *source_str;
++    audio_devices_t old_device;
++    audio_source_t source;
+ 
+     pa_assert(u);
+     pa_assert(u->stream);
+ 
+-    if (!u->routing_changes_enabled) {
++    if (!force && !u->routing_changes_enabled) {
+         pa_log_debug("Skipping routing change.");
+-        return false;
++        return 0;
+     }
+ 
+     if (u->primary_devices == devices)
+         pa_log_debug("Refresh active device routing.");
+ 
+-    u->enabled_devices &= ~u->primary_devices;
++    old_device = u->primary_devices;
+     u->primary_devices = devices;
+-    u->enabled_devices |= u->primary_devices;
+ 
+-    devlist = pa_list_string_input_device(devices);
+-    pa_assert(devlist);
+-#ifdef DROID_DEVICE_I9305
+-    pa_snprintf(tmp, sizeof(tmp), "%s=%u", AUDIO_PARAMETER_STREAM_ROUTING, devices & ~AUDIO_DEVICE_BIT_IN);
+-#else
+-    pa_snprintf(tmp, sizeof(tmp), "%s=%u", AUDIO_PARAMETER_STREAM_ROUTING, devices);
+-#endif
+-    pa_log_debug("set_parameters(): %s (%s : %#010x)", tmp, devlist, devices);
+-    pa_xfree(devlist);
+-#ifdef DROID_DEVICE_MAKO
+-#warning Using mako set_parameters hack.
+-    u->card_data->set_parameters(u->card_data, tmp);
+-#else
+-    u->stream->common.set_parameters(&u->stream->common, tmp);
+-#endif
++    ret = pa_droid_stream_set_input_route(u->stream, devices, &source);
+ 
+-    return true;
++    if (ret < 0)
++        u->primary_devices = old_device;
++    else {
++        if (source != (uint32_t) -1)
++            pa_assert_se(pa_droid_audio_source_name(source, &source_str));
++        else
++            source_str = DROID_AUDIO_SOURCE_UNDEFINED;
++
++        p = pa_proplist_new();
++        pa_proplist_sets(p, DROID_AUDIO_SOURCE, source_str);
++        pa_source_update_proplist(u->source, PA_UPDATE_REPLACE, p);
++        pa_proplist_free(p);
++    }
++
++    return ret;
+ }
+ 
+ static bool parse_device_list(const char *str, audio_devices_t *dst) {
+-    char *dev;
+-    const char *state = NULL;
+-
+     pa_assert(str);
+     pa_assert(dst);
+ 
++    char *dev;
++    const char *state = NULL;
++
+     *dst = 0;
+ 
+     while ((dev = pa_split(str, "|", &state))) {
+@@ -153,7 +159,7 @@ static int thread_read(struct userdata *
+     chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) u->buffer_size);
+ 
+     p = pa_memblock_acquire(chunk.memblock);
+-    readd = u->stream->read(u->stream, (uint8_t*) p, pa_memblock_get_length(chunk.memblock));
++    readd = u->stream->in->read(u->stream->in, (uint8_t*) p, pa_memblock_get_length(chunk.memblock));
+     pa_memblock_release(chunk.memblock);
+ 
+     if (readd < 0) {
+@@ -179,6 +185,7 @@ static void thread_func(void *userdata)
+     struct userdata *u = userdata;
+ 
+     pa_assert(u);
++    pa_assert(u->stream);
+ 
+     pa_log_debug("Thread starting up.");
+ 
+@@ -189,6 +196,8 @@ static void thread_func(void *userdata)
+ 
+     u->timestamp = pa_rtclock_now();
+ 
++    u->stream->in->common.standby(&u->stream->in->common);
++
+     for (;;) {
+         int ret;
+ 
+@@ -200,7 +209,11 @@ static void thread_func(void *userdata)
+             pa_rtpoll_set_timer_disabled(u->rtpoll);
+ 
+         /* Sleep */
++#if (PULSEAUDIO_VERSION_MAJOR == 5)
++        if ((ret = pa_rtpoll_run(u->rtpoll, true)) < 0)
++#elif (PULSEAUDIO_VERSION_MAJOR == 6)
+         if ((ret = pa_rtpoll_run(u->rtpoll)) < 0)
++#endif
+             goto fail;
+ 
+         if (ret == 0)
+@@ -225,7 +238,7 @@ static int suspend(struct userdata *u) {
+     pa_assert(u);
+     pa_assert(u->stream);
+ 
+-    ret = u->stream->common.standby(&u->stream->common);
++    ret = u->stream->in->common.standby(&u->stream->in->common);
+ 
+     if (ret == 0)
+         pa_log_info("Device suspended.");
+@@ -259,8 +272,13 @@ static int source_process_msg(pa_msgobje
+                     break;
+                 }
+ 
++                case PA_SOURCE_UNLINKED: {
++                    /* Suspending since some implementations do not want to free running stream. */
++                    suspend(u);
++                    break;
++                }
++
+                 /* not needed */
+-                case PA_SOURCE_UNLINKED:
+                 case PA_SOURCE_INIT:
+                 case PA_SOURCE_INVALID_STATE:
+                     ;
+@@ -272,7 +290,7 @@ static int source_process_msg(pa_msgobje
+     return pa_source_process_msg(o, code, data, offset, chunk);
+ }
+ 
+-static int source_set_port_cb(pa_source *s, pa_device_port *p) {
++static int droid_source_set_port(pa_source *s, pa_device_port *p, bool force) {
+     struct userdata *u = s->userdata;
+     pa_droid_port_data *data;
+ 
+@@ -291,11 +309,34 @@ static int source_set_port_cb(pa_source
+ 
+     pa_log_debug("Source set port %u", data->device);
+ 
+-    do_routing(u, data->device);
++    return do_routing(u, data->device, force);
++}
+ 
+-    return 0;
++int pa_droid_source_set_port(pa_source *s, pa_device_port *p) {
++    return droid_source_set_port(s, p, true);
+ }
+ 
++static int source_set_port_cb(pa_source *s, pa_device_port *p) {
++    return droid_source_set_port(s, p, false);
++}
++
++static void source_set_voicecall_source_port(struct userdata *u) {
++    pa_device_port *port;
++    pa_droid_port_data *data;
++    void *state;
++
++    pa_assert(u);
++    pa_assert(u->source);
++
++    PA_HASHMAP_FOREACH(port, u->source->ports, state) {
++        data = PA_DEVICE_PORT_DATA(port);
++
++        if (data->device & AUDIO_DEVICE_IN_VOICE_CALL) {
++            pa_source_set_port(u->source, port->name, false);
++            break;
++        }
++    }
++}
+ 
+ static void source_set_name(pa_modargs *ma, pa_source_new_data *data, const char *module_id) {
+     const char *tmp;
+@@ -318,8 +359,13 @@ static void source_set_name(pa_modargs *
+     }
+ }
+ 
++#if (PULSEAUDIO_VERSION_MAJOR == 5)
+ static void source_get_mute_cb(pa_source *s) {
++#elif (PULSEAUDIO_VERSION_MAJOR == 6)
++static int source_get_mute_cb(pa_source *s, bool *muted) {
++#endif
+     struct userdata *u = s->userdata;
++    int ret = 0;
+     bool b;
+ 
+     pa_assert(u);
+@@ -328,12 +374,19 @@ static void source_get_mute_cb(pa_source
+     pa_droid_hw_module_lock(u->hw_module);
+     if (u->hw_module->device->get_mic_mute(u->hw_module->device, &b) < 0) {
+         pa_log("Failed to get mute state.");
+-        pa_droid_hw_module_unlock(u->hw_module);
+-        return;
++        ret = -1;
+     }
+     pa_droid_hw_module_unlock(u->hw_module);
+ 
+-    s->muted = b;
++#if (PULSEAUDIO_VERSION_MAJOR == 5)
++    if (ret == 0)
++        s->muted = b;
++#elif (PULSEAUDIO_VERSION_MAJOR == 6)
++    if (ret == 0)
++        *muted = b;
++
++    return ret;
++#endif
+ }
+ 
+ static void source_set_mute_cb(pa_source *s) {
+@@ -377,6 +430,7 @@ void pa_droid_source_set_routing(pa_sour
+ pa_source *pa_droid_source_new(pa_module *m,
+                                  pa_modargs *ma,
+                                  const char *driver,
++                                 audio_devices_t device,
+                                  pa_droid_card_data *card_data,
+                                  pa_droid_mapping *am,
+                                  pa_card *card) {
+@@ -385,27 +439,24 @@ pa_source *pa_droid_source_new(pa_module
+     char *thread_name = NULL;
+     pa_source_new_data data;
+     const char *module_id = NULL;
+-    /* const char *tmp; */
++    const char *tmp;
+     uint32_t sample_rate;
+     uint32_t alternate_sample_rate;
+     audio_devices_t dev_in;
+     pa_sample_spec sample_spec;
+     pa_channel_map channel_map;
++    const char *format;
+     bool namereg_fail = false;
+     pa_droid_config_audio *config = NULL; /* Only used when source is created without card */
+     uint32_t source_buffer = 0;
+-    char audio_source[32];
+-    int ret;
+-
+-    audio_format_t hal_audio_format = 0;
+-    audio_channel_mask_t hal_channel_mask = 0;
++    bool voicecall_source = false;
+ 
+     pa_assert(m);
+     pa_assert(ma);
+     pa_assert(driver);
+ 
+     /* When running under card use hw module name for source by default. */
+-    if (card && ma)
++    if (am)
+         module_id = am->input->module->name;
+     else
+         module_id = pa_modargs_get_value(ma, "module_id", DEFAULT_MODULE_ID);
+@@ -413,8 +464,41 @@ pa_source *pa_droid_source_new(pa_module
+     sample_spec = m->core->default_sample_spec;
+     channel_map = m->core->default_channel_map;
+ 
++    if (device & AUDIO_DEVICE_IN_VOICE_CALL) {
++        pa_log_info("Enabling voice call record source. Most module arguments are overridden.");
++        voicecall_source = true;
++    }
++
++    /* First parse both sample spec and channel map, then see if source_* override some
++     * of the values. */
+     if (pa_modargs_get_sample_spec_and_channel_map(ma, &sample_spec, &channel_map, PA_CHANNEL_MAP_AIFF) < 0) {
+-        pa_log("Failed to parse sample specification and channel map.");
++        pa_log("Failed to parse source sample specification and channel map.");
++        goto fail;
++    }
++
++    if (pa_modargs_get_value(ma, "source_channel_map", NULL)) {
++        if (pa_modargs_get_channel_map(ma, "source_channel_map", &channel_map) < 0) {
++            pa_log("Failed to parse source channel map.");
++            goto fail;
++        }
++
++        sample_spec.channels = channel_map.channels;
++    }
++
++    if ((format = pa_modargs_get_value(ma, "source_format", NULL))) {
++        if ((sample_spec.format = pa_parse_sample_format(format)) < 0) {
++            pa_log("Failed to parse source format.");
++            goto fail;
++        }
++    }
++
++    if (pa_modargs_get_value_u32(ma, "source_rate", &sample_spec.rate) < 0) {
++        pa_log("Failed to parse source_rate.");
++        goto fail;
++    }
++
++    if (!pa_sample_spec_valid(&sample_spec)) {
++        pa_log("Sample spec is not valid.");
+         goto fail;
+     }
+ 
+@@ -436,8 +520,8 @@ pa_source *pa_droid_source_new(pa_module
+     u->rtpoll = pa_rtpoll_new();
+     pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
+ 
+-    /* Enabled routing changes by default. */
+-    u->routing_changes_enabled = true;
++    /* Enabled routing changes by default, except for voicecall source. */
++    u->routing_changes_enabled = voicecall_source ? false : true;
+ 
+     if (card_data) {
+         pa_assert(card);
+@@ -446,83 +530,44 @@ pa_source *pa_droid_source_new(pa_module
+     } else {
+         /* Stand-alone source */
+ 
+-        if (!(config = pa_droid_config_load(ma)))
+-            goto fail;
+-
+-        /* Ownership of config transfers to hw_module if opening of hw module succeeds. */
+-        if (!(u->hw_module = pa_droid_hw_module_get(u->core, config, module_id)))
+-            goto fail;
++        if (!(u->hw_module = pa_droid_hw_module_get(u->core, NULL, module_id))) {
++            if (!(config = pa_droid_config_load(ma)))
++                goto fail;
++
++            /* Ownership of config transfers to hw_module if opening of hw module succeeds. */
++            if (!(u->hw_module = pa_droid_hw_module_get(u->core, config, module_id)))
++                goto fail;
++        }
+     }
+ 
+-    if (!pa_convert_format(sample_spec.format, CONV_FROM_PA, &hal_audio_format)) {
+-        pa_log("Sample spec format %u not supported.", sample_spec.format);
+-        goto fail;
+-    }
++    /* Default routing */
++    if (device)
++        dev_in = device;
++    else {
++        /* FIXME So while setting routing through stream with HALv2 API fails, creation of stream
++         * requires HALv2 style device to work properly. So until that oddity is resolved we always
++         * set AUDIO_DEVICE_IN_BUILTIN_MIC as initial device here. */
++        pa_log_info("FIXME: Setting AUDIO_DEVICE_IN_BUILTIN_MIC as initial device.");
++        pa_assert_se(pa_string_convert_input_device_str_to_num("AUDIO_DEVICE_IN_BUILTIN_MIC", &dev_in));
+ 
+-    for (int i = 0; i < channel_map.channels; i++) {
+-        audio_channel_mask_t c;
+-        if (!pa_convert_input_channel(channel_map.map[i], CONV_FROM_PA, &c)) {
+-            pa_log("Failed to convert channel map.");
+-            goto fail;
+-        }
+-        hal_channel_mask |= c;
+-    }
++        if ((tmp = pa_modargs_get_value(ma, "input_devices", NULL))) {
++            audio_devices_t tmp_dev;
+ 
+-    struct audio_config config_in = {
+-        .sample_rate = sample_spec.rate,
+-        .channel_mask = hal_channel_mask,
+-        .format = hal_audio_format
+-    };
++            if (parse_device_list(tmp, &tmp_dev) && tmp_dev)
++                dev_in = tmp_dev;
+ 
+-    /* Default routing */
+-    /* FIXME So while setting routing through stream with HALv2 API fails, creation of stream
+-     * requires HALv2 style device to work properly. So until that oddity is resolved we always
+-     * set AUDIO_DEVICE_IN_BUILTIN_MIC as initial device here. */
+-#if 0
+-    pa_assert_se(pa_string_convert_input_device_str_to_num("AUDIO_DEVICE_IN_BUILTIN_MIC", &dev_in));
+-
+-    if ((tmp = pa_modargs_get_value(ma, "input_devices", NULL))) {
+-        audio_devices_t tmp_dev;
+-
+-        if (parse_device_list(tmp, &tmp_dev) && tmp_dev)
+-            dev_in = tmp_dev;
+-
+-        pa_log_debug("Set initial devices %s", tmp);
+-    }
+-#else
+-    pa_log_info("FIXME: Setting AUDIO_DEVICE_IN_BUILTIN_MIC as initial device.");
+-    dev_in = AUDIO_DEVICE_IN_BUILTIN_MIC;
+-#endif
+-    pa_droid_hw_module_lock(u->hw_module);
+-    ret = u->hw_module->device->open_input_stream(u->hw_module->device,
+-                                                  u->hw_module->stream_in_id,
+-                                                  dev_in,
+-                                                  &config_in,
+-                                                  &u->stream);
+-    /* On some devices the first call will fail if the config parameters are
+-     * not supported, but it'll automatically set the right ones, expecting
+-     * the caller to call it again, so let's try at least one more time */
+-    if (!u->stream)
+-        ret = u->hw_module->device->open_input_stream(u->hw_module->device,
+-                                                      u->hw_module->stream_in_id,
+-                                                      dev_in,
+-                                                      &config_in,
+-                                                      &u->stream);
++            pa_log_debug("Set initial devices %s", tmp);
++        }
++    }
+ 
+-    u->hw_module->stream_in_id++;
+-    pa_droid_hw_module_unlock(u->hw_module);
++    u->stream = pa_droid_open_input_stream(u->hw_module, &sample_spec, &channel_map, dev_in);
+ 
+-    if (ret < 0) {
++    if (!u->stream) {
+         pa_log("Failed to open input stream.");
+         goto fail;
+     }
+ 
+-    if ((sample_rate = u->stream->common.get_sample_rate(&u->stream->common)) != sample_spec.rate) {
+-        pa_log_warn("Requested sample rate %u but got %u instead.", sample_spec.rate, sample_rate);
+-        sample_spec.rate = sample_rate;
+-    }
+-
+-    u->buffer_size = u->stream->common.get_buffer_size(&u->stream->common);
++    u->buffer_size = u->stream->in->common.get_buffer_size(&u->stream->in->common);
+     if (source_buffer) {
+         if (source_buffer < u->buffer_size)
+             pa_log_warn("Requested buffer size %u less than HAL reported buffer size (%u).", source_buffer, u->buffer_size);
+@@ -536,18 +581,6 @@ pa_source *pa_droid_source_new(pa_module
+         }
+     }
+ 
+-    pa_log_info("Created Android stream with device: %u sample rate: %u channel mask: %u format: %u buffer size: %u",
+-            dev_in,
+-            sample_rate,
+-            config_in.channel_mask,
+-            config_in.format,
+-            u->buffer_size);
+-
+-    /* Setting audio source to MIC by default */
+-    pa_snprintf(audio_source, sizeof(audio_source), "%s=%u", AUDIO_PARAMETER_STREAM_INPUT_SOURCE, AUDIO_SOURCE_MIC);
+-    u->stream->common.set_parameters(&u->stream->common, audio_source);
+-    pa_log_debug("Setting audio source to AUDIO_SOURCE_MIC by default");
+-
+     pa_source_new_data_init(&data);
+     data.driver = driver;
+     data.module = m;
+@@ -567,11 +600,11 @@ pa_source *pa_droid_source_new(pa_module
+     }
+     data.namereg_fail = namereg_fail;
+ 
+-    pa_source_new_data_set_sample_spec(&data, &sample_spec);
+-    pa_source_new_data_set_channel_map(&data, &channel_map);
++    pa_source_new_data_set_sample_spec(&data, &u->stream->sample_spec);
++    pa_source_new_data_set_channel_map(&data, &u->stream->channel_map);
+     pa_source_new_data_set_alternate_sample_rate(&data, alternate_sample_rate);
+ 
+-    if (am)
++    if (am && card)
+         pa_droid_add_ports(data.ports, am, card);
+ 
+     u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE);
+@@ -604,12 +637,15 @@ pa_source *pa_droid_source_new(pa_module
+     pa_xfree(thread_name);
+     thread_name = NULL;
+ 
+-    pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->buffer_size, &sample_spec));
+-    pa_log_debug("Set fixed latency %" PRIu64 " usec", pa_bytes_to_usec(u->buffer_size, &sample_spec));
++    pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->buffer_size, &u->stream->sample_spec));
++    pa_log_debug("Set fixed latency %" PRIu64 " usec", pa_bytes_to_usec(u->buffer_size, &u->stream->sample_spec));
+ 
+-    if (u->source->active_port)
++    if (!voicecall_source && u->source->active_port)
+         source_set_port_cb(u->source, u->source->active_port);
+ 
++    if (voicecall_source)
++        source_set_voicecall_source_port(u);
++
+     pa_source_put(u->source);
+ 
+     return u->source;
+@@ -653,11 +689,8 @@ static void userdata_free(struct userdat
+     if (u->memchunk.memblock)
+         pa_memblock_unref(u->memchunk.memblock);
+ 
+-    if (u->hw_module && u->stream) {
+-        pa_droid_hw_module_lock(u->hw_module);
+-        u->hw_module->device->close_input_stream(u->hw_module->device, u->stream);
+-        pa_droid_hw_module_unlock(u->hw_module);
+-    }
++    if (u->stream)
++        pa_droid_stream_unref(u->stream);
+ 
+     // Stand alone source
+     if (u->hw_module)
+Index: overlay/src/modules/droid/droid-source.h
+===================================================================
+--- overlay.orig/src/modules/droid/droid-source.h
++++ overlay/src/modules/droid/droid-source.h
+@@ -43,14 +43,17 @@
+ 
+ #include "droid-util.h"
+ 
++/* If device is non-zero, it will override whatever is set in modargs for input device. */
+ pa_source *pa_droid_source_new(pa_module *m,
+                                  pa_modargs *ma,
+                                  const char *driver,
++                                 audio_devices_t device,
+                                  pa_droid_card_data *card_data,
+                                  pa_droid_mapping *am,
+                                  pa_card *card);
+ void pa_droid_source_free(pa_source *s);
+ 
+ void pa_droid_source_set_routing(pa_source *s, bool enabled);
++int pa_droid_source_set_port(pa_source *s, pa_device_port *p);
+ 
+ #endif
+Index: overlay/src/modules/droid/droid-util-42.h
+===================================================================
+--- overlay.orig/src/modules/droid/droid-util-42.h
++++ overlay/src/modules/droid/droid-util-42.h
+@@ -22,7 +22,10 @@
+ #ifndef _ANDROID_UTIL_V42_H_
+ #define _ANDROID_UTIL_V42_H_
+ 
+-#define HAL_V2
++#define DROID_HAL 2
++
++#include <hardware/audio.h>
++#include <hardware_legacy/audio_policy_conf.h>
+ 
+ // PulseAudio value    -    Android value
+ 
+Index: overlay/src/modules/droid/droid-util-44.h
+===================================================================
+--- /dev/null
++++ overlay/src/modules/droid/droid-util-44.h
+@@ -0,0 +1,349 @@
++/*
++ * Copyright (C) 2013 Jolla Ltd.
++ *
++ * Contact: Juho Hämäläinen <juho.hamalainen at tieto.com>
++ *
++ * These PulseAudio Modules are 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
++ * version 2.1 of the License.
++ *
++ * This library 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
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
++ * USA.
++ */
++
++#ifndef _ANDROID_UTIL_V44_H_
++#define _ANDROID_UTIL_V44_H_
++
++#define DROID_HAL 2
++
++// Android v4.4 has SPEAKER_DRC_ENABLED_TAG, so might the future versions
++#define DROID_HAVE_DRC
++
++#include <hardware/audio.h>
++#include <hardware_legacy/audio_policy_conf.h>
++
++// PulseAudio value    -    Android value
++
++uint32_t conversion_table_output_channel[][2] = {
++    { PA_CHANNEL_POSITION_MONO,                     AUDIO_CHANNEL_OUT_MONO },
++    { PA_CHANNEL_POSITION_FRONT_LEFT,               AUDIO_CHANNEL_OUT_FRONT_LEFT },
++    { PA_CHANNEL_POSITION_FRONT_RIGHT,              AUDIO_CHANNEL_OUT_FRONT_RIGHT},
++    { PA_CHANNEL_POSITION_FRONT_CENTER,             AUDIO_CHANNEL_OUT_FRONT_CENTER },
++    { PA_CHANNEL_POSITION_SUBWOOFER,                AUDIO_CHANNEL_OUT_LOW_FREQUENCY },
++    { PA_CHANNEL_POSITION_REAR_LEFT,                AUDIO_CHANNEL_OUT_BACK_LEFT },
++    { PA_CHANNEL_POSITION_REAR_RIGHT,               AUDIO_CHANNEL_OUT_BACK_RIGHT },
++    { PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,     AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER },
++    { PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,    AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER },
++    { PA_CHANNEL_POSITION_REAR_CENTER,              AUDIO_CHANNEL_OUT_BACK_CENTER },
++    { PA_CHANNEL_POSITION_SIDE_LEFT,                AUDIO_CHANNEL_OUT_SIDE_LEFT },
++    { PA_CHANNEL_POSITION_SIDE_RIGHT,               AUDIO_CHANNEL_OUT_SIDE_RIGHT },
++    { PA_CHANNEL_POSITION_TOP_CENTER,               AUDIO_CHANNEL_OUT_TOP_CENTER },
++    { PA_CHANNEL_POSITION_TOP_FRONT_LEFT,           AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT },
++    { PA_CHANNEL_POSITION_TOP_FRONT_CENTER,         AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER },
++    { PA_CHANNEL_POSITION_TOP_FRONT_RIGHT,          AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT },
++    { PA_CHANNEL_POSITION_TOP_REAR_LEFT,            AUDIO_CHANNEL_OUT_TOP_BACK_LEFT },
++    { PA_CHANNEL_POSITION_TOP_REAR_CENTER,          AUDIO_CHANNEL_OUT_TOP_BACK_CENTER },
++    { PA_CHANNEL_POSITION_TOP_REAR_RIGHT,           AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT }
++};
++
++uint32_t conversion_table_input_channel[][2] = {
++    { PA_CHANNEL_POSITION_MONO,                     AUDIO_CHANNEL_IN_MONO },
++    { PA_CHANNEL_POSITION_FRONT_LEFT,               AUDIO_CHANNEL_IN_LEFT },
++    { PA_CHANNEL_POSITION_FRONT_RIGHT,              AUDIO_CHANNEL_IN_RIGHT},
++    { PA_CHANNEL_POSITION_FRONT_CENTER,             AUDIO_CHANNEL_IN_FRONT },
++    { PA_CHANNEL_POSITION_REAR_CENTER,              AUDIO_CHANNEL_IN_BACK },
++    /* Following are missing suitable counterparts on PulseAudio side. */
++    { AUDIO_CHANNEL_IN_LEFT_PROCESSED,              AUDIO_CHANNEL_IN_LEFT_PROCESSED },
++    { AUDIO_CHANNEL_IN_RIGHT_PROCESSED,             AUDIO_CHANNEL_IN_RIGHT_PROCESSED },
++    { AUDIO_CHANNEL_IN_FRONT_PROCESSED,             AUDIO_CHANNEL_IN_FRONT_PROCESSED },
++    { AUDIO_CHANNEL_IN_BACK_PROCESSED,              AUDIO_CHANNEL_IN_BACK_PROCESSED },
++    { AUDIO_CHANNEL_IN_PRESSURE,                    AUDIO_CHANNEL_IN_PRESSURE },
++    { AUDIO_CHANNEL_IN_X_AXIS,                      AUDIO_CHANNEL_IN_X_AXIS },
++    { AUDIO_CHANNEL_IN_Y_AXIS,                      AUDIO_CHANNEL_IN_Y_AXIS },
++    { AUDIO_CHANNEL_IN_Z_AXIS,                      AUDIO_CHANNEL_IN_Z_AXIS },
++    { AUDIO_CHANNEL_IN_VOICE_UPLINK,                AUDIO_CHANNEL_IN_VOICE_UPLINK },
++    { AUDIO_CHANNEL_IN_VOICE_DNLINK,                AUDIO_CHANNEL_IN_VOICE_DNLINK }
++};
++
++uint32_t conversion_table_format[][2] = {
++    { PA_SAMPLE_U8,             AUDIO_FORMAT_PCM_8_BIT },
++    { PA_SAMPLE_S16LE,          AUDIO_FORMAT_PCM_16_BIT },
++    { PA_SAMPLE_S32LE,          AUDIO_FORMAT_PCM_32_BIT },
++    { PA_SAMPLE_S24LE,          AUDIO_FORMAT_PCM_8_24_BIT }
++};
++
++uint32_t conversion_table_default_audio_source[][2] = {
++#ifdef DROID_DEVICE_HAMMERHEAD
++    { AUDIO_DEVICE_IN_COMMUNICATION,                AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_AMBIENT,                      AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_BUILTIN_MIC,                  AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,        AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_WIRED_HEADSET,                AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_AUX_DIGITAL,                  AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_VOICE_CALL,                   AUDIO_SOURCE_VOICE_CALL },
++    { AUDIO_DEVICE_IN_BACK_MIC,                     AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_REMOTE_SUBMIX,                AUDIO_SOURCE_REMOTE_SUBMIX },
++    { AUDIO_DEVICE_IN_ANC_HEADSET,                  AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_FM_RX,                        AUDIO_SOURCE_FM_RX },
++    { AUDIO_DEVICE_IN_FM_RX_A2DP,                   AUDIO_SOURCE_FM_RX_A2DP },
++#endif
++    { AUDIO_DEVICE_IN_ALL,                          AUDIO_SOURCE_DEFAULT }
++};
++
++struct string_conversion {
++    uint32_t value;
++    const char *str;
++};
++
++#if defined(STRING_ENTRY)
++#error STRING_ENTRY already defined somewhere, fix this lib.
++#endif
++#define STRING_ENTRY(str) { str, #str }
++/* Output devices */
++struct string_conversion string_conversion_table_output_device[] = {
++    STRING_ENTRY(AUDIO_DEVICE_OUT_EARPIECE),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_SPEAKER),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_WIRED_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_WIRED_HEADPHONE),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_SCO),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_AUX_DIGITAL),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_USB_ACCESSORY),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_USB_DEVICE),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_REMOTE_SUBMIX),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_DEFAULT),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_ALL),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_ALL_A2DP),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_ALL_SCO),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_ALL_USB),
++#ifdef QCOM_HARDWARE
++    STRING_ENTRY(AUDIO_DEVICE_OUT_FM),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_FM_TX),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_ANC_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_ANC_HEADPHONE),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_PROXY),
++#endif
++    { 0, NULL }
++};
++
++struct string_conversion string_conversion_table_output_device_fancy[] = {
++    { AUDIO_DEVICE_OUT_EARPIECE,                    "output-earpiece" },
++    { AUDIO_DEVICE_OUT_SPEAKER,                     "output-speaker" },
++    { AUDIO_DEVICE_OUT_SPEAKER
++        | AUDIO_DEVICE_OUT_WIRED_HEADPHONE,         "output-speaker+wired_headphone" },
++    { AUDIO_DEVICE_OUT_WIRED_HEADSET,               "output-wired_headset" },
++    { AUDIO_DEVICE_OUT_WIRED_HEADPHONE,             "output-wired_headphone" },
++    { AUDIO_DEVICE_OUT_BLUETOOTH_SCO,               "output-bluetooth_sco" },
++    { AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET,       "output-sco_headset" },
++    { AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT,        "output-sco_carkit" },
++    { AUDIO_DEVICE_OUT_BLUETOOTH_A2DP,              "output-a2dp" },
++    { AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,   "output-a2dp_headphones" },
++    { AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER,      "output-a2dp_speaker" },
++    { AUDIO_DEVICE_OUT_AUX_DIGITAL,                 "output-aux_digital" },
++    { AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET,           "output-analog_dock_headset" },
++    { AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET,           "output-digital_dock_headset" },
++    { AUDIO_DEVICE_OUT_USB_ACCESSORY,               "output-usb_accessory" },
++    { AUDIO_DEVICE_OUT_USB_DEVICE,                  "output-usb_device" },
++    { AUDIO_DEVICE_OUT_REMOTE_SUBMIX,               "output-remote_submix" },
++#ifdef QCOM_HARDWARE
++    { AUDIO_DEVICE_OUT_FM,                          "output-fm" },
++    { AUDIO_DEVICE_OUT_FM_TX,                       "output-fm_tx" },
++    { AUDIO_DEVICE_OUT_ANC_HEADSET,                 "output-anc_headset" },
++    { AUDIO_DEVICE_OUT_ANC_HEADPHONE,               "output-anc_headphone" },
++    { AUDIO_DEVICE_OUT_PROXY,                       "output-proxy" },
++#endif
++    { 0, NULL }
++};
++
++/* Input devices */
++struct string_conversion string_conversion_table_input_device[] = {
++    STRING_ENTRY(AUDIO_DEVICE_IN_COMMUNICATION),
++    STRING_ENTRY(AUDIO_DEVICE_IN_AMBIENT),
++    STRING_ENTRY(AUDIO_DEVICE_IN_BUILTIN_MIC),
++    STRING_ENTRY(AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_IN_WIRED_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_IN_AUX_DIGITAL),
++    STRING_ENTRY(AUDIO_DEVICE_IN_VOICE_CALL),
++    STRING_ENTRY(AUDIO_DEVICE_IN_BACK_MIC),
++    STRING_ENTRY(AUDIO_DEVICE_IN_REMOTE_SUBMIX),
++    STRING_ENTRY(AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_IN_USB_ACCESSORY),
++    STRING_ENTRY(AUDIO_DEVICE_IN_USB_DEVICE),
++#ifdef QCOM_HARDWARE
++    STRING_ENTRY(AUDIO_DEVICE_IN_ANC_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_IN_FM_RX),
++    STRING_ENTRY(AUDIO_DEVICE_IN_FM_RX_A2DP),
++#endif
++    STRING_ENTRY(AUDIO_DEVICE_IN_DEFAULT),
++    /* Combination entries consisting of multiple devices defined above.
++     * These don't require counterpart in string_conversion_table_input_device_fancy. */
++    STRING_ENTRY(AUDIO_DEVICE_IN_ALL),
++    STRING_ENTRY(AUDIO_DEVICE_IN_ALL_SCO),
++    { 0, NULL }
++};
++
++struct string_conversion string_conversion_table_input_device_fancy[] = {
++    { AUDIO_DEVICE_IN_COMMUNICATION,            "input-communication" },
++    { AUDIO_DEVICE_IN_AMBIENT,                  "input-ambient" },
++    { AUDIO_DEVICE_IN_BUILTIN_MIC,              "input-builtin_mic" },
++    { AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,    "input-bluetooth_sco_headset" },
++    { AUDIO_DEVICE_IN_WIRED_HEADSET,            "input-wired_headset" },
++    { AUDIO_DEVICE_IN_AUX_DIGITAL,              "input-aux_digital" },
++    { AUDIO_DEVICE_IN_VOICE_CALL,               "input-voice_call" },
++    { AUDIO_DEVICE_IN_BACK_MIC,                 "input-back_mic" },
++    { AUDIO_DEVICE_IN_REMOTE_SUBMIX,            "input-remote_submix" },
++    { AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET,        "input-analog_dock_headset" },
++    { AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET,        "input-digital_dock_headset" },
++    { AUDIO_DEVICE_IN_USB_ACCESSORY,            "input-usb_accessory" },
++    { AUDIO_DEVICE_IN_USB_DEVICE,               "input-usb_device" },
++#ifdef QCOM_HARDWARE
++    { AUDIO_DEVICE_IN_ANC_HEADSET,              "input-anc_headset" },
++    { AUDIO_DEVICE_IN_FM_RX,                    "input-fm_rx" },
++    { AUDIO_DEVICE_IN_FM_RX_A2DP,               "input-fm_rx_a2dp" },
++#endif
++    { AUDIO_DEVICE_IN_DEFAULT,                  "input-default" },
++    { 0, NULL }
++};
++
++struct string_conversion string_conversion_table_audio_source_fancy[] = {
++    { AUDIO_SOURCE_DEFAULT,                         "default" },
++    { AUDIO_SOURCE_MIC,                             "mic" },
++    { AUDIO_SOURCE_VOICE_UPLINK,                    "voice uplink" },
++    { AUDIO_SOURCE_VOICE_DOWNLINK,                  "voice downlink" },
++    { AUDIO_SOURCE_VOICE_CALL,                      "voice call" },
++    { AUDIO_SOURCE_CAMCORDER,                       "camcorder" },
++    { AUDIO_SOURCE_VOICE_RECOGNITION,               "voice recognition" },
++    { AUDIO_SOURCE_VOICE_COMMUNICATION,             "voice communication" },
++    { AUDIO_SOURCE_REMOTE_SUBMIX,                   "remote submix" },
++#ifdef QCOM_HARDWARE
++    { AUDIO_SOURCE_FM_RX,                           "fm rx" },
++    { AUDIO_SOURCE_FM_RX_A2DP,                      "fm rx a2dp" },
++#endif
++    { (uint32_t)-1, NULL }
++};
++
++/* Flags */
++struct string_conversion string_conversion_table_output_flag[] = {
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_NONE),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_DIRECT),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_PRIMARY),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_FAST),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_DEEP_BUFFER),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_NON_BLOCKING),
++#ifdef QCOM_HARDWARE
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_LPA),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_TUNNEL),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_VOIP_RX),
++#endif
++    { 0, NULL }
++};
++
++/* Channels */
++struct string_conversion string_conversion_table_output_channels[] = {
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_LEFT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_RIGHT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_LOW_FREQUENCY),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_BACK_LEFT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_BACK_RIGHT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_BACK_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_SIDE_LEFT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_SIDE_RIGHT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_BACK_LEFT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_BACK_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_MONO),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_STEREO),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_QUAD),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_SURROUND),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_5POINT1),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_7POINT1),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_ALL),
++    { 0, NULL }
++};
++struct string_conversion string_conversion_table_input_channels[] = {
++    STRING_ENTRY(AUDIO_CHANNEL_IN_LEFT),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_RIGHT),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_FRONT),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_BACK),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_LEFT_PROCESSED),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_RIGHT_PROCESSED),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_FRONT_PROCESSED),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_BACK_PROCESSED),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_PRESSURE),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_X_AXIS),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_Y_AXIS),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_Z_AXIS),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_UPLINK),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_DNLINK),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_MONO),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_STEREO),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_ALL),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_FRONT_BACK),
++#ifdef QCOM_HARDWARE
++    STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_UPLINK_MONO),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_DNLINK_MONO),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_CALL_MONO),
++#endif
++    { 0, NULL }
++};
++
++/* Formats */
++struct string_conversion string_conversion_table_format[] = {
++    STRING_ENTRY(AUDIO_FORMAT_DEFAULT),
++    STRING_ENTRY(AUDIO_FORMAT_PCM),
++    STRING_ENTRY(AUDIO_FORMAT_MP3),
++    STRING_ENTRY(AUDIO_FORMAT_AMR_NB),
++    STRING_ENTRY(AUDIO_FORMAT_AMR_WB),
++    STRING_ENTRY(AUDIO_FORMAT_AAC),
++    STRING_ENTRY(AUDIO_FORMAT_HE_AAC_V1),
++    STRING_ENTRY(AUDIO_FORMAT_HE_AAC_V2),
++    STRING_ENTRY(AUDIO_FORMAT_VORBIS),
++    STRING_ENTRY(AUDIO_FORMAT_MAIN_MASK),
++    STRING_ENTRY(AUDIO_FORMAT_SUB_MASK),
++#ifdef QCOM_HARDWARE
++    STRING_ENTRY(AUDIO_FORMAT_EVRC),
++    STRING_ENTRY(AUDIO_FORMAT_QCELP),
++    STRING_ENTRY(AUDIO_FORMAT_AC3),
++    STRING_ENTRY(AUDIO_FORMAT_AC3_PLUS),
++    STRING_ENTRY(AUDIO_FORMAT_DTS),
++    STRING_ENTRY(AUDIO_FORMAT_WMA),
++    STRING_ENTRY(AUDIO_FORMAT_WMA_PRO),
++    STRING_ENTRY(AUDIO_FORMAT_AAC_ADIF),
++    STRING_ENTRY(AUDIO_FORMAT_EVRCB),
++    STRING_ENTRY(AUDIO_FORMAT_EVRCWB),
++    STRING_ENTRY(AUDIO_FORMAT_EAC3),
++    STRING_ENTRY(AUDIO_FORMAT_DTS_LBR),
++    STRING_ENTRY(AUDIO_FORMAT_AMR_WB_PLUS),
++#endif
++    STRING_ENTRY(AUDIO_FORMAT_PCM_16_BIT),
++    STRING_ENTRY(AUDIO_FORMAT_PCM_8_BIT),
++    STRING_ENTRY(AUDIO_FORMAT_PCM_32_BIT),
++    STRING_ENTRY(AUDIO_FORMAT_PCM_8_24_BIT),
++    { 0, NULL }
++};
++#undef STRING_ENTRY
++
++#endif
+Index: overlay/src/modules/droid/droid-util-51.h
+===================================================================
+--- /dev/null
++++ overlay/src/modules/droid/droid-util-51.h
+@@ -0,0 +1,405 @@
++/*
++ * Copyright (C) 2015 Jolla Ltd.
++ *
++ * Contact: Juho Hämäläinen <juho.hamalainen at jolla.com>
++ *
++ * These PulseAudio Modules are 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
++ * version 2.1 of the License.
++ *
++ * This library 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
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
++ * USA.
++ */
++
++#ifndef _DROID_UTIL_V51_H_
++#define _DROID_UTIL_V51_H_
++
++#define DROID_HAL 3
++
++#define DROID_HAVE_DRC
++
++#ifdef QCOM_BSP
++#define DROID_AUDIO_HAL_USE_VSID
++#endif
++
++#include <hardware/audio.h>
++#include <hardware_legacy/audio_policy_conf.h>
++
++// PulseAudio value    -    Android value
++
++uint32_t conversion_table_output_channel[][2] = {
++    { PA_CHANNEL_POSITION_MONO,                     AUDIO_CHANNEL_OUT_MONO },
++    { PA_CHANNEL_POSITION_FRONT_LEFT,               AUDIO_CHANNEL_OUT_FRONT_LEFT },
++    { PA_CHANNEL_POSITION_FRONT_RIGHT,              AUDIO_CHANNEL_OUT_FRONT_RIGHT},
++    { PA_CHANNEL_POSITION_FRONT_CENTER,             AUDIO_CHANNEL_OUT_FRONT_CENTER },
++    { PA_CHANNEL_POSITION_SUBWOOFER,                AUDIO_CHANNEL_OUT_LOW_FREQUENCY },
++    { PA_CHANNEL_POSITION_REAR_LEFT,                AUDIO_CHANNEL_OUT_BACK_LEFT },
++    { PA_CHANNEL_POSITION_REAR_RIGHT,               AUDIO_CHANNEL_OUT_BACK_RIGHT },
++    { PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,     AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER },
++    { PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,    AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER },
++    { PA_CHANNEL_POSITION_REAR_CENTER,              AUDIO_CHANNEL_OUT_BACK_CENTER },
++    { PA_CHANNEL_POSITION_SIDE_LEFT,                AUDIO_CHANNEL_OUT_SIDE_LEFT },
++    { PA_CHANNEL_POSITION_SIDE_RIGHT,               AUDIO_CHANNEL_OUT_SIDE_RIGHT },
++    { PA_CHANNEL_POSITION_TOP_CENTER,               AUDIO_CHANNEL_OUT_TOP_CENTER },
++    { PA_CHANNEL_POSITION_TOP_FRONT_LEFT,           AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT },
++    { PA_CHANNEL_POSITION_TOP_FRONT_CENTER,         AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER },
++    { PA_CHANNEL_POSITION_TOP_FRONT_RIGHT,          AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT },
++    { PA_CHANNEL_POSITION_TOP_REAR_LEFT,            AUDIO_CHANNEL_OUT_TOP_BACK_LEFT },
++    { PA_CHANNEL_POSITION_TOP_REAR_CENTER,          AUDIO_CHANNEL_OUT_TOP_BACK_CENTER },
++    { PA_CHANNEL_POSITION_TOP_REAR_RIGHT,           AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT }
++};
++
++uint32_t conversion_table_input_channel[][2] = {
++    { PA_CHANNEL_POSITION_MONO,                     AUDIO_CHANNEL_IN_MONO },
++    { PA_CHANNEL_POSITION_FRONT_LEFT,               AUDIO_CHANNEL_IN_LEFT },
++    { PA_CHANNEL_POSITION_FRONT_RIGHT,              AUDIO_CHANNEL_IN_RIGHT},
++    { PA_CHANNEL_POSITION_FRONT_CENTER,             AUDIO_CHANNEL_IN_FRONT },
++    { PA_CHANNEL_POSITION_REAR_CENTER,              AUDIO_CHANNEL_IN_BACK },
++    /* Following are missing suitable counterparts on PulseAudio side. */
++    { PA_CHANNEL_POSITION_FRONT_LEFT,               AUDIO_CHANNEL_IN_LEFT_PROCESSED },
++    { PA_CHANNEL_POSITION_FRONT_RIGHT,              AUDIO_CHANNEL_IN_RIGHT_PROCESSED },
++    { PA_CHANNEL_POSITION_FRONT_CENTER,             AUDIO_CHANNEL_IN_FRONT_PROCESSED },
++    { PA_CHANNEL_POSITION_REAR_CENTER,              AUDIO_CHANNEL_IN_BACK_PROCESSED },
++    { PA_CHANNEL_POSITION_SUBWOOFER,                AUDIO_CHANNEL_IN_PRESSURE },
++    { PA_CHANNEL_POSITION_AUX0,                     AUDIO_CHANNEL_IN_X_AXIS },
++    { PA_CHANNEL_POSITION_AUX1,                     AUDIO_CHANNEL_IN_Y_AXIS },
++    { PA_CHANNEL_POSITION_AUX2,                     AUDIO_CHANNEL_IN_Z_AXIS },
++    { PA_CHANNEL_POSITION_MONO,                     AUDIO_CHANNEL_IN_VOICE_UPLINK },
++    { PA_CHANNEL_POSITION_MONO,                     AUDIO_CHANNEL_IN_VOICE_DNLINK }
++};
++
++uint32_t conversion_table_format[][2] = {
++    { PA_SAMPLE_U8,             AUDIO_FORMAT_PCM_8_BIT },
++    { PA_SAMPLE_S16LE,          AUDIO_FORMAT_PCM_16_BIT },
++    { PA_SAMPLE_S32LE,          AUDIO_FORMAT_PCM_32_BIT },
++    { PA_SAMPLE_S24LE,          AUDIO_FORMAT_PCM_8_24_BIT }
++};
++
++uint32_t conversion_table_default_audio_source[][2] = {
++    { AUDIO_DEVICE_IN_COMMUNICATION,                AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_AMBIENT,                      AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_BUILTIN_MIC,                  AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,        AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_WIRED_HEADSET,                AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_AUX_DIGITAL,                  AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_VOICE_CALL,                   AUDIO_SOURCE_VOICE_CALL },
++    { AUDIO_DEVICE_IN_BACK_MIC,                     AUDIO_SOURCE_MIC },
++    { AUDIO_DEVICE_IN_REMOTE_SUBMIX,                AUDIO_SOURCE_REMOTE_SUBMIX },
++#ifdef QCOM_HARDWARE
++    { AUDIO_DEVICE_IN_FM_RX,                        AUDIO_SOURCE_FM_RX },
++    { AUDIO_DEVICE_IN_FM_RX_A2DP,                   AUDIO_SOURCE_FM_RX_A2DP },
++#endif
++    { AUDIO_DEVICE_IN_ALL,                          AUDIO_SOURCE_DEFAULT }
++};
++
++struct string_conversion {
++    uint32_t value;
++    const char *str;
++};
++
++#if defined(STRING_ENTRY)
++#error STRING_ENTRY already defined somewhere, fix this lib.
++#endif
++#define STRING_ENTRY(str) { str, #str }
++/* Output devices */
++struct string_conversion string_conversion_table_output_device[] = {
++    /* Each device listed here needs fancy name counterpart
++     * in string_conversion_table_output_device_fancy. */
++    STRING_ENTRY(AUDIO_DEVICE_OUT_EARPIECE),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_SPEAKER),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_WIRED_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_WIRED_HEADPHONE),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_SCO),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_AUX_DIGITAL),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_HDMI),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_USB_ACCESSORY),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_USB_DEVICE),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_REMOTE_SUBMIX),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_TELEPHONY_TX),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_LINE),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_HDMI_ARC),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_SPDIF),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_FM),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_AUX_LINE),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_SPEAKER_SAFE),
++#ifdef QCOM_HARDWARE
++    STRING_ENTRY(AUDIO_DEVICE_OUT_FM_TX),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_PROXY),
++#endif
++    /* Combination entries consisting of multiple devices defined above.
++     * These don't require counterpart in string_conversion_table_output_device_fancy. */
++    STRING_ENTRY(AUDIO_DEVICE_OUT_DEFAULT),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_ALL),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_ALL_A2DP),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_ALL_SCO),
++    STRING_ENTRY(AUDIO_DEVICE_OUT_ALL_USB),
++    { 0, NULL }
++};
++
++struct string_conversion string_conversion_table_output_device_fancy[] = {
++    { AUDIO_DEVICE_OUT_EARPIECE,                    "output-earpiece" },
++    { AUDIO_DEVICE_OUT_SPEAKER,                     "output-speaker" },
++    { AUDIO_DEVICE_OUT_SPEAKER
++        | AUDIO_DEVICE_OUT_WIRED_HEADPHONE,         "output-speaker+wired_headphone" },
++    { AUDIO_DEVICE_OUT_WIRED_HEADSET,               "output-wired_headset" },
++    { AUDIO_DEVICE_OUT_WIRED_HEADPHONE,             "output-wired_headphone" },
++    { AUDIO_DEVICE_OUT_BLUETOOTH_SCO,               "output-bluetooth_sco" },
++    { AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET,       "output-sco_headset" },
++    { AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT,        "output-sco_carkit" },
++    { AUDIO_DEVICE_OUT_BLUETOOTH_A2DP,              "output-a2dp" },
++    { AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,   "output-a2dp_headphones" },
++    { AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER,      "output-a2dp_speaker" },
++    { AUDIO_DEVICE_OUT_AUX_DIGITAL,                 "output-aux_digital" },
++    { AUDIO_DEVICE_OUT_HDMI,                        "output-hdmi" },
++    { AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET,           "output-analog_dock_headset" },
++    { AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET,           "output-digital_dock_headset" },
++    { AUDIO_DEVICE_OUT_USB_ACCESSORY,               "output-usb_accessory" },
++    { AUDIO_DEVICE_OUT_USB_DEVICE,                  "output-usb_device" },
++    { AUDIO_DEVICE_OUT_REMOTE_SUBMIX,               "output-remote_submix" },
++    { AUDIO_DEVICE_OUT_TELEPHONY_TX,                "output-telephony" },
++    { AUDIO_DEVICE_OUT_LINE,                        "output-line" },
++    { AUDIO_DEVICE_OUT_HDMI_ARC,                    "output-hdmi_arc" },
++    { AUDIO_DEVICE_OUT_SPDIF,                       "output-spdif" },
++    { AUDIO_DEVICE_OUT_FM,                          "output-fm" },
++    { AUDIO_DEVICE_OUT_AUX_LINE,                    "output-aux_line" },
++    { AUDIO_DEVICE_OUT_SPEAKER_SAFE,                "output-speaker_safe" },
++#ifdef QCOM_HARDWARE
++    { AUDIO_DEVICE_OUT_FM_TX,                       "output-fm_tx" },
++    { AUDIO_DEVICE_OUT_PROXY,                       "output-proxy" },
++#endif
++    { 0, NULL }
++};
++
++/* Input devices */
++struct string_conversion string_conversion_table_input_device[] = {
++    /* Each device listed here needs fancy name counterpart
++     * in string_conversion_table_input_device_fancy. */
++    STRING_ENTRY(AUDIO_DEVICE_IN_COMMUNICATION),
++    STRING_ENTRY(AUDIO_DEVICE_IN_AMBIENT),
++    STRING_ENTRY(AUDIO_DEVICE_IN_BUILTIN_MIC),
++    STRING_ENTRY(AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_IN_WIRED_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_IN_AUX_DIGITAL),
++    STRING_ENTRY(AUDIO_DEVICE_IN_HDMI),
++    STRING_ENTRY(AUDIO_DEVICE_IN_VOICE_CALL),
++    STRING_ENTRY(AUDIO_DEVICE_IN_TELEPHONY_RX),
++    STRING_ENTRY(AUDIO_DEVICE_IN_BACK_MIC),
++    STRING_ENTRY(AUDIO_DEVICE_IN_REMOTE_SUBMIX),
++    STRING_ENTRY(AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET),
++    STRING_ENTRY(AUDIO_DEVICE_IN_USB_ACCESSORY),
++    STRING_ENTRY(AUDIO_DEVICE_IN_USB_DEVICE),
++    STRING_ENTRY(AUDIO_DEVICE_IN_FM_TUNER),
++    STRING_ENTRY(AUDIO_DEVICE_IN_TV_TUNER),
++    STRING_ENTRY(AUDIO_DEVICE_IN_LINE),
++    STRING_ENTRY(AUDIO_DEVICE_IN_SPDIF),
++    STRING_ENTRY(AUDIO_DEVICE_IN_BLUETOOTH_A2DP),
++    STRING_ENTRY(AUDIO_DEVICE_IN_LOOPBACK),
++#ifdef QCOM_HARDWARE
++    STRING_ENTRY(AUDIO_DEVICE_IN_PROXY),
++    STRING_ENTRY(AUDIO_DEVICE_IN_FM_RX),
++    STRING_ENTRY(AUDIO_DEVICE_IN_FM_RX_A2DP),
++#endif
++    STRING_ENTRY(AUDIO_DEVICE_IN_DEFAULT),
++    /* Combination entries consisting of multiple devices defined above.
++     * These don't require counterpart in string_conversion_table_input_device_fancy. */
++    STRING_ENTRY(AUDIO_DEVICE_IN_ALL),
++    STRING_ENTRY(AUDIO_DEVICE_IN_ALL_SCO),
++    STRING_ENTRY(AUDIO_DEVICE_IN_ALL_USB),
++    { 0, NULL }
++};
++
++struct string_conversion string_conversion_table_input_device_fancy[] = {
++    { AUDIO_DEVICE_IN_COMMUNICATION,            "input-communication" },
++    { AUDIO_DEVICE_IN_AMBIENT,                  "input-ambient" },
++    { AUDIO_DEVICE_IN_BUILTIN_MIC,              "input-builtin_mic" },
++    { AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,    "input-bluetooth_sco_headset" },
++    { AUDIO_DEVICE_IN_WIRED_HEADSET,            "input-wired_headset" },
++    { AUDIO_DEVICE_IN_AUX_DIGITAL,              "input-aux_digital" },
++    { AUDIO_DEVICE_IN_HDMI,                     "input-hdmi" },
++    { AUDIO_DEVICE_IN_VOICE_CALL,               "input-voice_call" },
++    { AUDIO_DEVICE_IN_TELEPHONY_RX,             "input-telephony" },
++    { AUDIO_DEVICE_IN_BACK_MIC,                 "input-back_mic" },
++    { AUDIO_DEVICE_IN_REMOTE_SUBMIX,            "input-remote_submix" },
++    { AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET,        "input-analog_dock_headset" },
++    { AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET,        "input-digital_dock_headset" },
++    { AUDIO_DEVICE_IN_USB_ACCESSORY,            "input-usb_accessory" },
++    { AUDIO_DEVICE_IN_USB_DEVICE,               "input-usb_device" },
++    { AUDIO_DEVICE_IN_FM_TUNER,                 "input-fm_tuner" },
++    { AUDIO_DEVICE_IN_TV_TUNER,                 "input-tv_tuner" },
++    { AUDIO_DEVICE_IN_LINE,                     "input-line" },
++    { AUDIO_DEVICE_IN_SPDIF,                    "input-spdif" },
++    { AUDIO_DEVICE_IN_BLUETOOTH_A2DP,           "input-bluetooth_a2dp" },
++    { AUDIO_DEVICE_IN_LOOPBACK,                 "input-loopback" },
++#ifdef QCOM_HARDWARE
++    { AUDIO_DEVICE_IN_PROXY,                    "input-proxy" },
++    { AUDIO_DEVICE_IN_FM_RX,                    "input-fm_rx" },
++    { AUDIO_DEVICE_IN_FM_RX_A2DP,               "input-fm_rx_a2dp" },
++#endif
++    { AUDIO_DEVICE_IN_DEFAULT,                  "input-default" },
++    { 0, NULL }
++};
++
++struct string_conversion string_conversion_table_audio_source_fancy[] = {
++    { AUDIO_SOURCE_DEFAULT,                         "default" },
++    { AUDIO_SOURCE_MIC,                             "mic" },
++    { AUDIO_SOURCE_VOICE_UPLINK,                    "voice uplink" },
++    { AUDIO_SOURCE_VOICE_DOWNLINK,                  "voice downlink" },
++    { AUDIO_SOURCE_VOICE_CALL,                      "voice call" },
++    { AUDIO_SOURCE_CAMCORDER,                       "camcorder" },
++    { AUDIO_SOURCE_VOICE_RECOGNITION,               "voice recognition" },
++    { AUDIO_SOURCE_VOICE_COMMUNICATION,             "voice communication" },
++    { AUDIO_SOURCE_REMOTE_SUBMIX,                   "remote submix" },
++    { AUDIO_SOURCE_FM_TUNER,                        "fm tuner" },
++#ifdef QCOM_HARDWARE
++    { AUDIO_SOURCE_FM_RX,                           "fm rx" },
++    { AUDIO_SOURCE_FM_RX_A2DP,                      "fm rx a2dp" },
++#endif
++    { (uint32_t)-1, NULL }
++};
++
++/* Flags */
++struct string_conversion string_conversion_table_output_flag[] = {
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_NONE),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_DIRECT),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_PRIMARY),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_FAST),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_DEEP_BUFFER),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_NON_BLOCKING),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_HW_AV_SYNC),
++#ifdef QCOM_HARDWARE
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_VOIP_RX),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_INCALL_MUSIC),
++    STRING_ENTRY(AUDIO_OUTPUT_FLAG_COMPRESS_PASSTHROUGH),
++#endif
++    { 0, NULL }
++};
++
++struct string_conversion string_conversion_table_input_flag[] = {
++    STRING_ENTRY(AUDIO_INPUT_FLAG_NONE),
++    STRING_ENTRY(AUDIO_INPUT_FLAG_FAST),
++    STRING_ENTRY(AUDIO_INPUT_FLAG_HW_HOTWORD),
++    { 0, NULL }
++};
++
++/* Channels */
++struct string_conversion string_conversion_table_output_channels[] = {
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_LEFT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_RIGHT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_LOW_FREQUENCY),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_BACK_LEFT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_BACK_RIGHT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_BACK_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_SIDE_LEFT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_SIDE_RIGHT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_BACK_LEFT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_BACK_CENTER),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_MONO),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_STEREO),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_QUAD),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_5POINT1),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_5POINT1_BACK),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_5POINT1_SIDE),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_7POINT1),
++    STRING_ENTRY(AUDIO_CHANNEL_OUT_ALL),
++    { 0, NULL }
++};
++struct string_conversion string_conversion_table_input_channels[] = {
++    STRING_ENTRY(AUDIO_CHANNEL_IN_LEFT),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_RIGHT),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_FRONT),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_BACK),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_LEFT_PROCESSED),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_RIGHT_PROCESSED),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_FRONT_PROCESSED),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_BACK_PROCESSED),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_PRESSURE),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_X_AXIS),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_Y_AXIS),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_Z_AXIS),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_UPLINK),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_VOICE_DNLINK),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_MONO),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_STEREO),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_ALL),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_FRONT_BACK),
++    STRING_ENTRY(AUDIO_CHANNEL_IN_ALL),
++    { 0, NULL }
++};
++
++/* Formats */
++struct string_conversion string_conversion_table_format[] = {
++    STRING_ENTRY(AUDIO_FORMAT_DEFAULT),
++    STRING_ENTRY(AUDIO_FORMAT_PCM),
++    STRING_ENTRY(AUDIO_FORMAT_MP3),
++    STRING_ENTRY(AUDIO_FORMAT_AMR_NB),
++    STRING_ENTRY(AUDIO_FORMAT_AMR_WB),
++    STRING_ENTRY(AUDIO_FORMAT_AAC),
++    STRING_ENTRY(AUDIO_FORMAT_HE_AAC_V1),
++    STRING_ENTRY(AUDIO_FORMAT_HE_AAC_V2),
++    STRING_ENTRY(AUDIO_FORMAT_VORBIS),
++    STRING_ENTRY(AUDIO_FORMAT_OPUS),
++    STRING_ENTRY(AUDIO_FORMAT_AC3),
++    STRING_ENTRY(AUDIO_FORMAT_E_AC3),
++    STRING_ENTRY(AUDIO_FORMAT_PCM_16_BIT),
++    STRING_ENTRY(AUDIO_FORMAT_PCM_8_BIT),
++    STRING_ENTRY(AUDIO_FORMAT_PCM_32_BIT),
++    STRING_ENTRY(AUDIO_FORMAT_PCM_8_24_BIT),
++    STRING_ENTRY(AUDIO_FORMAT_PCM_FLOAT),
++    STRING_ENTRY(AUDIO_FORMAT_PCM_24_BIT_PACKED),
++    STRING_ENTRY(AUDIO_FORMAT_AAC_MAIN),
++    STRING_ENTRY(AUDIO_FORMAT_AAC_LC),
++    STRING_ENTRY(AUDIO_FORMAT_AAC_SSR),
++    STRING_ENTRY(AUDIO_FORMAT_AAC_LTP),
++    STRING_ENTRY(AUDIO_FORMAT_AAC_HE_V1),
++    STRING_ENTRY(AUDIO_FORMAT_AAC_SCALABLE),
++    STRING_ENTRY(AUDIO_FORMAT_AAC_ERLC),
++    STRING_ENTRY(AUDIO_FORMAT_AAC_LD),
++    STRING_ENTRY(AUDIO_FORMAT_AAC_HE_V2),
++    STRING_ENTRY(AUDIO_FORMAT_AAC_ELD),
++#ifdef QCOM_HARDWARE
++    STRING_ENTRY(AUDIO_FORMAT_EVRC),
++    STRING_ENTRY(AUDIO_FORMAT_QCELP),
++    STRING_ENTRY(AUDIO_FORMAT_DTS),
++    STRING_ENTRY(AUDIO_FORMAT_WMA),
++    STRING_ENTRY(AUDIO_FORMAT_WMA_PRO),
++    STRING_ENTRY(AUDIO_FORMAT_AAC_ADIF),
++    STRING_ENTRY(AUDIO_FORMAT_EVRCB),
++    STRING_ENTRY(AUDIO_FORMAT_EVRCWB),
++    STRING_ENTRY(AUDIO_FORMAT_DTS_LBR),
++    STRING_ENTRY(AUDIO_FORMAT_AMR_WB_PLUS),
++    STRING_ENTRY(AUDIO_FORMAT_MP2),
++    STRING_ENTRY(AUDIO_FORMAT_EVRCNW),
++    STRING_ENTRY(AUDIO_FORMAT_PCM_OFFLOAD),
++    STRING_ENTRY(AUDIO_FORMAT_FLAC),
++    STRING_ENTRY(AUDIO_FORMAT_E_AC3_JOC),
++#endif
++    { 0, NULL }
++};
++#undef STRING_ENTRY
++
++#endif
+Index: overlay/src/modules/droid/droid-util.c
+===================================================================
+--- overlay.orig/src/modules/droid/droid-util.c
++++ overlay/src/modules/droid/droid-util.c
+@@ -54,26 +54,10 @@
+ #include <pulsecore/refcnt.h>
+ #include <pulsecore/shared.h>
+ #include <pulsecore/mutex.h>
+-
+-#include <hardware/audio.h>
+-#include <hardware_legacy/audio_policy_conf.h>
++#include <pulsecore/strlist.h>
+ 
+ #include "droid-util.h"
+ 
+-#include <android-version.h>
+-
+-#ifndef ANDROID_VERSION_MAJOR
+-#error "ANDROID_VERSION_* not defined."
+-#endif
+-
+-#if ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 1
+-#include "droid-util-41qc.h"
+-#elif ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR >= 2
+-#include "droid-util-42.h"
+-#else
+-#error "No valid ANDROID_VERSION found."
+-#endif
+-
+ #define CONVERT_FUNC(TABL) \
+ bool pa_convert_ ## TABL (uint32_t value, pa_conversion_field_t field, uint32_t *to_value) {                    \
+     for (unsigned int i = 0; i < sizeof( conversion_table_ ## TABL )/(sizeof(uint32_t)*2); i++) {               \
+@@ -94,12 +78,15 @@ CONVERT_FUNC(input_channel);
+ 
+ #define DEFAULT_PRIORITY (100)
+ 
++/* Section defining custom global configuration variables. */
++#define GLOBAL_CONFIG_EXT_TAG "custom_properties"
++
++static void droid_port_free(pa_droid_port *p);
++
+ static bool string_convert_num_to_str(const struct string_conversion *list, const uint32_t value, const char **to_str) {
+     pa_assert(list);
+     pa_assert(to_str);
+ 
+-    pa_log_debug("Trying to convert %x to string.", value);
+-
+     for (unsigned int i = 0; list[i].str; i++) {
+         if (list[i].value == value) {
+             *to_str = list[i].str;
+@@ -114,8 +101,6 @@ static bool string_convert_str_to_num(co
+     pa_assert(str);
+     pa_assert(to_value);
+ 
+-    pa_log_debug("Trying to convert %s to num.", str);
+-
+     for (unsigned int i = 0; list[i].str; i++) {
+         if (pa_streq(list[i].str, str)) {
+             *to_value = list[i].value;
+@@ -125,31 +110,17 @@ static bool string_convert_str_to_num(co
+     return false;
+ }
+ 
+-static bool check_port_availability(const char *port) {
+-    pa_assert(port);
+-
+-    pa_log_debug("Checking availability for port '%s'", port);
+-
+-    for (unsigned int i = 0; port_availability[i]; i++) {
+-        if (pa_streq(port_availability[i], port)) {
+-            return true;
+-        }
+-    }
+-
+-    return false;
+-}
+-
+ static char *list_string(struct string_conversion *list, uint32_t flags) {
+     char *str = NULL;
+     char *tmp;
+ 
+-#ifdef HAL_V2
++#if DROID_HAL >= 2
+     if (flags & AUDIO_DEVICE_BIT_IN)
+         flags &= ~AUDIO_DEVICE_BIT_IN;
+ #endif
+ 
+     for (unsigned int i = 0; list[i].str; i++) {
+-#ifdef HAL_V2
++#if DROID_HAL >= 2
+         if (list[i].value & AUDIO_DEVICE_BIT_IN) {
+             if (popcount(list[i].value & ~AUDIO_DEVICE_BIT_IN) != 1)
+                 continue;
+@@ -172,13 +143,6 @@ static char *list_string(struct string_c
+     return str;
+ }
+ 
+-static void droid_port_free(pa_droid_port *p) {
+-    pa_assert(p);
+-
+-    pa_xfree(p->name);
+-    pa_xfree(p->description);
+-    pa_xfree(p);
+-}
+ 
+ /* Output device */
+ bool pa_string_convert_output_device_num_to_str(audio_devices_t value, const char **to_str) {
+@@ -208,39 +172,67 @@ char *pa_list_string_input_device(audio_
+ 
+ /* Flags */
+ bool pa_string_convert_flag_num_to_str(audio_output_flags_t value, const char **to_str) {
+-    return string_convert_num_to_str(string_conversion_table_flag, (uint32_t) value, to_str);
++    return string_convert_num_to_str(string_conversion_table_output_flag, (uint32_t) value, to_str);
+ }
+ 
+ bool pa_string_convert_flag_str_to_num(const char *str, audio_output_flags_t *to_value) {
+-    return string_convert_str_to_num(string_conversion_table_flag, str, (uint32_t*) to_value);
++    return string_convert_str_to_num(string_conversion_table_output_flag, str, (uint32_t*) to_value);
+ }
+ 
+ char *pa_list_string_flags(audio_output_flags_t flags) {
+-    return list_string(string_conversion_table_flag, flags);
++    return list_string(string_conversion_table_output_flag, flags);
++}
++
++bool pa_input_device_default_audio_source(audio_devices_t input_device, audio_source_t *default_source)
++{
++#if DROID_HAL >= 2
++    input_device &= ~AUDIO_DEVICE_BIT_IN;
++#endif
++
++    /* Note converting HAL values to different HAL values! */
++    for (unsigned int i = 0; i < sizeof(conversion_table_default_audio_source) / (sizeof(uint32_t) * 2); i++) {
++        if (conversion_table_default_audio_source[i][0] & input_device) {
++            *default_source = conversion_table_default_audio_source[i][1];
++            return true;
++        }
++    }
++    return false;
+ }
+ 
+ /* Config parser */
+ 
+ #define WHITESPACE "\n\r \t"
+ 
+-static int parse_list(const struct string_conversion *table, const char *str, uint32_t *dst) {
++static int parse_list(const struct string_conversion *table,
++                      const char *str,
++                      uint32_t *dst,
++                      char **unknown_entries) {
+     int count = 0;
+     char *entry;
++    char *unknown = NULL;
+     const char *state = NULL;
+ 
+     pa_assert(table);
+     pa_assert(str);
+     pa_assert(dst);
++    pa_assert(unknown_entries);
+ 
+     *dst = 0;
++    *unknown_entries = NULL;
+ 
+     while ((entry = pa_split(str, "|", &state))) {
+         uint32_t d = 0;
+ 
+         if (!string_convert_str_to_num(table, entry, &d)) {
+-            pa_log("Unknown entry %s", entry);
+-            pa_xfree(entry);
+-            return -1;
++            if (*unknown_entries) {
++                unknown = pa_sprintf_malloc("%s|%s", *unknown_entries, entry);
++                pa_xfree(*unknown_entries);
++                pa_xfree(entry);
++            } else
++                unknown = entry;
++
++            *unknown_entries = unknown;
++            continue;
+         }
+ 
+         *dst |= d;
+@@ -252,24 +244,34 @@ static int parse_list(const struct strin
+     return count;
+ }
+ 
+-static bool parse_sampling_rates(const char *str, uint32_t sampling_rates[32]) {
++static bool parse_sampling_rates(const char *fn, const unsigned ln,
++                                 const char *str, uint32_t sampling_rates[32]) {
++    pa_assert(fn);
++    pa_assert(str);
++
+     char *entry;
+     const char *state = NULL;
+ 
+-    pa_assert(str);
+-
+     uint32_t pos = 0;
+     while ((entry = pa_split(str, "|", &state))) {
+         int32_t val;
+ 
++#if DROID_HAL >= 3
++        if (pos == 0 && pa_streq(entry, "dynamic")) {
++            sampling_rates[pos++] = (uint32_t) -1;
++            pa_xfree(entry);
++            break;
++        }
++#endif
++
+         if (pos == AUDIO_MAX_SAMPLING_RATES) {
+-            pa_log("Too many sample rate entries (> %d)", AUDIO_MAX_SAMPLING_RATES);
++            pa_log("[%s:%u] Too many sample rate entries (> %d)", fn, ln, AUDIO_MAX_SAMPLING_RATES);
+             pa_xfree(entry);
+             return false;
+         }
+ 
+         if (pa_atoi(entry, &val) < 0) {
+-            pa_log("Bad sample rate value %s", entry);
++            pa_log("[%s:%u] Bad sample rate value %s", fn, ln, entry);
+             pa_xfree(entry);
+             return false;
+         }
+@@ -285,14 +287,58 @@ static bool parse_sampling_rates(const c
+     return true;
+ }
+ 
+-static bool parse_formats(const char *str, audio_format_t *formats) {
++static bool check_and_log(const char *fn, const unsigned ln, const char *field,
++                          const int count, const char *str, char *unknown,
++                          const bool must_have_all) {
++    bool fail;
++
++    pa_assert(fn);
++    pa_assert(field);
++    pa_assert(str);
++
++    fail = must_have_all && unknown;
++
++    if (unknown) {
++        pa_log_warn("[%s:%u] Unknown %s entries: %s", fn, ln, field, unknown);
++        pa_xfree(unknown);
++    }
++
++    if (count == 0 || fail) {
++        pa_log("[%s:%u] Failed to parse %s (%s).", fn, ln, field, str);
++        return false;
++    }
++
++    return true;
++}
++
++static bool parse_formats(const char *fn, const unsigned ln,
++                          const char *str, audio_format_t *formats) {
++    int count;
++    char *unknown = NULL;
++
++    pa_assert(fn);
+     pa_assert(str);
+     pa_assert(formats);
+ 
+-    return parse_list(string_conversion_table_format, str, formats) > 0;
++#if DROID_HAL >= 3
++    /* Needs to be probed later */
++    if (pa_streq(str, "dynamic")) {
++        *formats = 0;
++        return true;
++    }
++#endif
++
++    count = parse_list(string_conversion_table_format, str, formats, &unknown);
++
++    return check_and_log(fn, ln, "formats", count, str, unknown, false);
+ }
+ 
+-static int parse_channels(const char *str, bool in_output, audio_channel_mask_t *channels) {
++static int parse_channels(const char *fn, const unsigned ln,
++                          const char *str, bool in_output, audio_channel_mask_t *channels) {
++    int count;
++    char *unknown = NULL;
++
++    pa_assert(fn);
+     pa_assert(str);
+     pa_assert(channels);
+ 
+@@ -302,41 +348,77 @@ static int parse_channels(const char *st
+         return true;
+     }
+ 
+-    if (in_output)
+-        return parse_list(string_conversion_table_output_channels, str, channels);
+-    else
+-        return parse_list(string_conversion_table_input_channels, str, channels);
++    count = parse_list(in_output ? string_conversion_table_output_channels
++                                 : string_conversion_table_input_channels,
++                                 str, channels, &unknown);
++
++    return check_and_log(fn, ln, in_output ? "output channel_masks" : "input channel_masks",
++                         count, str, unknown, false);
+ }
+ 
+-static bool parse_devices(const char *str, bool in_output, audio_devices_t *devices) {
++static bool parse_devices(const char *fn, const unsigned ln,
++                          const char *str, bool in_output, audio_devices_t *devices, bool must_have_all) {
++    int count;
++    char *unknown = NULL;
++
++    pa_assert(fn);
+     pa_assert(str);
+     pa_assert(devices);
+ 
+-    if (in_output)
+-        return parse_list(string_conversion_table_output_device, str, devices) > 0;
+-    else
+-        return parse_list(string_conversion_table_input_device, str, devices) > 0;
++    count = parse_list(in_output ? string_conversion_table_output_device
++                                 : string_conversion_table_input_device,
++                                 str, devices, &unknown);
++
++    return check_and_log(fn, ln, in_output ? "output devices" : "input devices",
++                         count, str, unknown, must_have_all);
++}
++
++static bool parse_output_flags(const char *fn, const unsigned ln,
++                        const char *str, audio_output_flags_t *flags) {
++    int count;
++    char *unknown = NULL;
++
++    pa_assert(fn);
++    pa_assert(str);
++    pa_assert(flags);
++
++    count = parse_list(string_conversion_table_output_flag, str, flags, &unknown);
++
++    return check_and_log(fn, ln, "flags", count, str, unknown, false);
+ }
+ 
+-static bool parse_flags(const char *str, audio_output_flags_t *flags) {
++#if DROID_HAL >= 3
++static bool parse_input_flags(const char *fn, const unsigned ln,
++                        const char *str, audio_input_flags_t *flags) {
++    int count;
++    char *unknown = NULL;
++
++    pa_assert(fn);
+     pa_assert(str);
+     pa_assert(flags);
+ 
+-    return parse_list(string_conversion_table_flag, str, flags) > 0;
++    count = parse_list(string_conversion_table_input_flag, str, flags, &unknown);
++
++    return check_and_log(fn, ln, "flags", count, str, unknown, false);
+ }
++#endif
++
++#define MAX_LINE_LENGTH (1024)
+ 
+ bool pa_parse_droid_audio_config(const char *filename, pa_droid_config_audio *config) {
+     FILE *f;
+-    int n = 0;
++    unsigned n = 0;
+     bool ret = true;
++    char *full_line = NULL;
+ 
+     enum config_loc {
+-        IN_ROOT = 0,
+-        IN_GLOBAL = 1,
+-        IN_HW_MODULES = 1,
+-        IN_MODULE = 2,
+-        IN_OUTPUT_INPUT = 3,
+-        IN_CONFIG = 4
++        IN_ROOT             = 0,
++        IN_GLOBAL           = 1,
++        IN_HW_MODULES       = 2,
++        IN_MODULE           = 3,
++        IN_OUTPUT_INPUT     = 4,
++        IN_CONFIG           = 5,
++        IN_GLOBAL_EXT       = 6,
+     } loc = IN_ROOT;
+ 
+ 
+@@ -362,29 +444,39 @@ bool pa_parse_droid_audio_config(const c
+ 
+     pa_lock_fd(fileno(f), 1);
+ 
++    full_line = pa_xmalloc0(sizeof(char) * MAX_LINE_LENGTH);
++
+     while (!feof(f)) {
+-        char ln[512];
+-        char *d, *v, *val;
++        char *ln, *d, *v, *val;
+ 
+-        if (!fgets(ln, sizeof(ln), f))
++        if (!fgets(full_line, MAX_LINE_LENGTH, f))
+             break;
+ 
+         n++;
+ 
+-        pa_strip_nl(ln);
++        pa_strip_nl(full_line);
+ 
+-        if (ln[0] == '#' || !*ln )
++        if (!*full_line)
+             continue;
+ 
++        ln = full_line + strspn(full_line, WHITESPACE);
++
++        if (ln[0] == '#')
++            continue;
++
++        v = ln;
++        d = v + strcspn(v, WHITESPACE);
++
++        val = d + strspn(d, WHITESPACE);
++        d[0] = '\0';
++        d = val + strcspn(val, WHITESPACE);
++        d[0] = '\0';
++
+         /* Enter section */
+-        if (ln[strlen(ln)-1] == '{') {
+-            d = ln+strspn(ln, WHITESPACE);
+-            v = d;
+-            d = v+strcspn(v, WHITESPACE);
+-            d[0] = '\0';
++        if (pa_streq(val, "{")) {
+ 
+             if (!*v) {
+-                pa_log(__FILE__ ": [%s:%u] failed to parse line - too few words", filename, n);
++                pa_log("[%s:%u] failed to parse line - too few words", filename, n);
+                 goto finish;
+             }
+ 
+@@ -397,7 +489,17 @@ bool pa_parse_droid_audio_config(const c
+                     else if (pa_streq(v, AUDIO_HW_MODULE_TAG))
+                         loc = IN_HW_MODULES;
+                     else {
+-                        pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown field (%s)", filename, n, v);
++                        pa_log("[%s:%u] failed to parse line - unknown field (%s)", filename, n, v);
++                        ret = false;
++                        goto finish;
++                    }
++                    break;
++
++                case IN_GLOBAL:
++                    if (pa_streq(v, GLOBAL_CONFIG_EXT_TAG))
++                        loc = IN_GLOBAL_EXT;
++                    else {
++                        pa_log("[%s:%u] failed to parse line - unknown section (%s)", filename, n, v);
+                         ret = false;
+                         goto finish;
+                     }
+@@ -420,7 +522,7 @@ bool pa_parse_droid_audio_config(const c
+                         loc = IN_OUTPUT_INPUT;
+                         in_output = false;
+                     } else {
+-                        pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown field (%s)", filename, n, v);
++                        pa_log("[%s:%u] failed to parse line - unknown field (%s)", filename, n, v);
+                         ret = false;
+                         goto finish;
+                     }
+@@ -447,7 +549,12 @@ bool pa_parse_droid_audio_config(const c
+                     break;
+ 
+                 case IN_CONFIG:
+-                    pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown field in config (%s)", filename, n, v);
++                    pa_log("[%s:%u] failed to parse line - unknown field in config (%s)", filename, n, v);
++                    ret = false;
++                    goto finish;
++
++                default:
++                    pa_log("[%s:%u] failed to parse line - unknown section (%s)", filename, n, v);
+                     ret = false;
+                     goto finish;
+             }
+@@ -456,108 +563,125 @@ bool pa_parse_droid_audio_config(const c
+         }
+ 
+         /* Exit section */
+-        if (ln[strlen(ln)-1] == '}') {
+-            if (loc == IN_ROOT) {
+-                pa_log(__FILE__ ": [%s:%u] failed to parse line - extra closing bracket", filename, n);
+-                ret = false;
+-                goto finish;
+-            }
++        if (pa_streq(v, "}")) {
++            switch (loc) {
++                case IN_ROOT:
++                    pa_log("[%s:%u] failed to parse line - extra closing bracket", filename, n);
++                    ret = false;
++                    goto finish;
+ 
+-            loc--;
+-            if (loc == IN_MODULE) {
+-                if (in_output)
+-                    output = NULL;
+-                else
+-                    input = NULL;
+-            }
+-            if (loc == IN_ROOT)
+-                module = NULL;
++                case IN_HW_MODULES:
++                    module = NULL;
++                    /* fall through */
++                case IN_GLOBAL:
++                    loc = IN_ROOT;
++                    break;
+ 
++                case IN_OUTPUT_INPUT:
++                    if (in_output)
++                        output = NULL;
++                    else
++                        input = NULL;
++                    /* fall through */
++                case IN_MODULE:
++                    /* fall through */
++                case IN_CONFIG:
++                    /* fall through */
++                case IN_GLOBAL_EXT:
++                    loc--;
++                    break;
++            }
+             in_global = false;
+ 
+             continue;
+         }
+ 
+-        /* Parse global configuration */
+-        if (in_global) {
++        if (loc == IN_GLOBAL ||
++            loc == IN_GLOBAL_EXT ||
++            loc == IN_CONFIG) {
++
+             bool success = false;
+ 
+-            d = ln+strspn(ln, WHITESPACE);
+-            v = d;
+-            d = v+strcspn(v, WHITESPACE);
+-
+-            val = d+strspn(d, WHITESPACE);
+-            d[0] = '\0';
+-            d = val+strcspn(val, WHITESPACE);
+-            d[0] = '\0';
+-
+-            if (pa_streq(v, ATTACHED_OUTPUT_DEVICES_TAG))
+-                success = parse_devices(val, true, &config->global_config.attached_output_devices);
+-            else if (pa_streq(v, DEFAULT_OUTPUT_DEVICE_TAG))
+-                success = parse_devices(val, true, &config->global_config.default_output_device);
+-            else if (pa_streq(v, ATTACHED_INPUT_DEVICES_TAG))
+-                success = parse_devices(val, false, &config->global_config.attached_input_devices);
+-            else if (pa_streq(v, SPEAKER_DRC_ENABLED_TAG)) {
+-                pa_log(__FILE__ ": speaker drc is not yet supported, skipping", filename);
+-                success = true;
+-            } else {
+-                pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown config entry %s", filename, n, v);
+-                success = false;
+-            }
++            if (loc == IN_GLOBAL) {
+ 
+-            if (!success) {
+-                ret = false;
+-                goto finish;
+-            }
+-        }
++                /* Parse global configuration */
+ 
+-        /* Parse per-output or per-input configuration */
+-        if (loc == IN_CONFIG) {
+-            bool success = false;
++                if (pa_streq(v, ATTACHED_OUTPUT_DEVICES_TAG)) {
++                    success = parse_devices(filename, n, val, true,
++                                            &config->global_config.attached_output_devices, false);
++		}
++                else if (pa_streq(v, DEFAULT_OUTPUT_DEVICE_TAG)) {
++                    success = parse_devices(filename, n, val, true,
++                                            &config->global_config.default_output_device, true);
++		}
++                else if (pa_streq(v, ATTACHED_INPUT_DEVICES_TAG)) {
++                    success = parse_devices(filename, n, val, false,
++                                            &config->global_config.attached_input_devices, false);
++		}
++#ifdef DROID_HAVE_DRC
++                // SPEAKER_DRC_ENABLED_TAG is only from Android v4.4
++                else if (pa_streq(v, SPEAKER_DRC_ENABLED_TAG))
++                    /* TODO - Add support for dynamic range control */
++                    success = true; /* Do not fail while parsing speaker_drc_enabled entry */
++#endif
++                else {
++                    pa_log("[%s:%u] failed to parse line - unknown config entry %s", filename, n, v);
++                    success = false;
++                }
+ 
+-            pa_assert(module);
++            } else if (loc == IN_GLOBAL_EXT) {
+ 
+-            d = ln+strspn(ln, WHITESPACE);
+-            v = d;
+-            d = v+strcspn(v, WHITESPACE);
++                /* Parse custom global configuration
++                 * For now just log all custom variables, don't do
++                 * anything with the values.
++                 * TODO: Store custom values somehow */
+ 
+-            val = d+strspn(d, WHITESPACE);
+-            d[0] = '\0';
+-            d = val+strcspn(val, WHITESPACE);
+-            d[0] = '\0';
++                pa_log_debug("[%s:%u] TODO custom variable: %s = %s", filename, n, v, val);
++                success = true;
+ 
++            } else if (loc == IN_CONFIG) {
+ 
+-            if ((in_output && !output) || (!in_output && !input)) {
+-                pa_log(__FILE__ ": [%s:%u] failed to parse line", filename, n);
+-                ret = false;
+-                goto finish;
+-            }
++                /* Parse per-output or per-input configuration */
+ 
+-            if (pa_streq(v, SAMPLING_RATES_TAG))
+-                success = parse_sampling_rates(val, in_output ? output->sampling_rates : input->sampling_rates);
+-            else if (pa_streq(v, FORMATS_TAG))
+-                success = parse_formats(val, in_output ? &output->formats : &input->formats);
+-            else if (pa_streq(v, CHANNELS_TAG)) {
+-                if (in_output)
+-                    success = (parse_channels(val, true, &output->channel_masks) > 0);
+-                else
+-                    success = (parse_channels(val, false, &input->channel_masks) > 0);
+-            } else if (pa_streq(v, DEVICES_TAG)) {
+-                if (in_output)
+-                    success = parse_devices(val, true, &output->devices);
+-                else
+-                    success = parse_devices(val, false, &input->devices);
+-            } else if (pa_streq(v, FLAGS_TAG)) {
+-                if (in_output)
+-                    success = parse_flags(val, &output->flags);
+-                else {
+-                    pa_log(__FILE__ ": [%s:%u] failed to parse line - output flags inside input definition", filename, n);
++                if ((in_output && !output) || (!in_output && !input)) {
++                    pa_log("[%s:%u] failed to parse line", filename, n);
++                    ret = false;
++                    goto finish;
++                }
++
++                if (pa_streq(v, SAMPLING_RATES_TAG))
++                    success = parse_sampling_rates(filename, n, val,
++                                                   in_output ? output->sampling_rates : input->sampling_rates);
++                else if (pa_streq(v, FORMATS_TAG))
++                    success = parse_formats(filename, n, val, in_output ? &output->formats : &input->formats);
++                else if (pa_streq(v, CHANNELS_TAG)) {
++                    if (in_output)
++                        success = parse_channels(filename, n, val, true, &output->channel_masks);
++                    else
++                        success = parse_channels(filename, n, val, false, &input->channel_masks);
++                } else if (pa_streq(v, DEVICES_TAG)) {
++                    if (in_output)
++                        success = parse_devices(filename, n, val, true, &output->devices, false);
++                    else
++                        success = parse_devices(filename, n, val, false, &input->devices, false);
++                } else if (pa_streq(v, FLAGS_TAG)) {
++                    if (in_output)
++                        success = parse_output_flags(filename, n, val, &output->flags);
++                    else {
++#if DROID_HAL >= 3
++                        success = parse_input_flags(filename, n, val, &input->flags);
++#else
++                        pa_log("[%s:%u] failed to parse line - output flags inside input definition", filename, n);
++                        success = false;
++#endif
++                    }
++                } else {
++                    pa_log("[%s:%u] failed to parse line - unknown config entry %s", filename, n, v);
+                     success = false;
+                 }
+-            } else {
+-                pa_log(__FILE__ ": [%s:%u] failed to parse line - unknown config entry %s", filename, n, v);
+-                success = false;
+-            }
++
++            } else
++                pa_assert_not_reached();
+ 
+             if (!success) {
+                 ret = false;
+@@ -574,6 +698,8 @@ finish:
+         fclose(f);
+     }
+ 
++    pa_xfree(full_line);
++
+     return ret;
+ }
+ 
+@@ -614,21 +740,50 @@ const pa_droid_config_hw_module *pa_droi
+     return NULL;
+ }
+ 
+-pa_droid_profile *pa_droid_profile_new(pa_droid_profile_set *ps, const pa_droid_config_output *output, const pa_droid_config_input *input) {
++static pa_droid_profile *profile_new(pa_droid_profile_set *ps,
++                                     const pa_droid_config_hw_module *module,
++                                     const char *name,
++                                     const char *description) {
+     pa_droid_profile *p;
+ 
+     pa_assert(ps);
+-    pa_assert(output);
++    pa_assert(module);
++    pa_assert(name);
++    pa_assert(description);
+ 
+     p = pa_xnew0(pa_droid_profile, 1);
+     p->profile_set = ps;
+-    p->module = output->module;
+-    p->name = pa_sprintf_malloc("%s%s%s", output->name, input ? "-" : "", input ? input->name : "");
+-    p->description = pa_sprintf_malloc("%s output%s%s%s", output->name,
+-                                                          input ? " and " : "",
+-                                                          input ? input->name : "",
+-                                                          input ? " input." : "");
++    p->module = module;
++    p->name = pa_xstrdup(name);
++    p->description = pa_xstrdup(description);
+     p->priority = DEFAULT_PRIORITY;
++
++    p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
++    p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
++
++    pa_hashmap_put(ps->profiles, p->name, p);
++
++    return p;
++}
++
++pa_droid_profile *pa_droid_profile_new(pa_droid_profile_set *ps, const pa_droid_config_output *output, const pa_droid_config_input *input) {
++    pa_droid_profile *p;
++    char *name;
++    char *description;
++
++    pa_assert(ps);
++    pa_assert(output);
++
++    name = pa_sprintf_malloc("%s%s%s", output->name, input ? "-" : "", input ? input->name : "");
++    description = pa_sprintf_malloc("%s output%s%s%s", output->name,
++                                                       input ? " and " : "",
++                                                       input ? input->name : "",
++                                                       input ? " input." : "");
++
++    p = profile_new(ps, output->module, name, description);
++    pa_xfree(name);
++    pa_xfree(description);
++
+     if (pa_streq(output->name, "primary")) {
+         p->priority += DEFAULT_PRIORITY;
+ 
+@@ -637,16 +792,24 @@ pa_droid_profile *pa_droid_profile_new(p
+     }
+ 
+     if (output)
+-        p->output = pa_droid_mapping_get(ps, PA_DIRECTION_OUTPUT, output);
++        pa_idxset_put(p->output_mappings, pa_droid_mapping_get(ps, PA_DIRECTION_OUTPUT, output), NULL);
+     if (input)
+-        p->input = pa_droid_mapping_get(ps, PA_DIRECTION_INPUT, input);
+-
+-    pa_hashmap_put(ps->profiles, p->name, p);
++        pa_idxset_put(p->input_mappings, pa_droid_mapping_get(ps, PA_DIRECTION_INPUT, input), NULL);
+ 
+     return p;
+ }
+ 
+-static void add_profile(pa_droid_profile_set *ps, const pa_droid_config_output *output, const pa_droid_config_input *input) {
++void pa_droid_profile_add_mapping(pa_droid_profile *p, pa_droid_mapping *am) {
++    pa_assert(p);
++    pa_assert(am);
++
++    if (am->direction == PA_DIRECTION_OUTPUT)
++        pa_idxset_put(p->output_mappings, am, NULL);
++    else
++        pa_idxset_put(p->input_mappings, am, NULL);
++}
++
++static pa_droid_profile *add_profile(pa_droid_profile_set *ps, const pa_droid_config_output *output, const pa_droid_config_input *input) {
+     pa_droid_profile *ap;
+ 
+     pa_log_debug("New profile: %s-%s", output->name, input ? input->name : "no input");
+@@ -654,19 +817,121 @@ static void add_profile(pa_droid_profile
+     ap = pa_droid_profile_new(ps, output, input);
+ 
+     pa_hashmap_put(ps->profiles, ap->name, ap);
++
++    return ap;
+ }
+ 
+-pa_droid_profile_set *pa_droid_profile_set_new(const pa_droid_config_hw_module *module) {
++static bool str_in_strlist(const char *str, pa_strlist *list) {
++    pa_strlist *iter;
++
++    pa_assert(str);
++    pa_assert(list);
++
++    for (iter = list; iter; iter = pa_strlist_next(iter)) {
++        if (pa_streq(str, pa_strlist_data(iter)))
++            return true;
++    }
++
++    return false;
++}
++
++/* outputs or inputs string lists can be NULL, which means include all outputs and inputs
++ * from module. */
++static pa_droid_profile *add_combined_profile(pa_droid_profile_set *ps,
++                                              const pa_droid_config_hw_module *module,
++                                              pa_strlist *outputs,
++                                              pa_strlist *inputs) {
++    pa_droid_profile *p;
++    char *description;
++    char *o_str;
++    char *i_str;
++    pa_strlist *to_outputs = NULL;
++    pa_strlist *to_inputs = NULL;
++    pa_droid_mapping *am;
++
++    pa_assert(ps);
++    pa_assert(module);
++
++    for (unsigned i = 0; i < module->outputs_size; i++) {
++        if (outputs && !str_in_strlist(module->outputs[i].name, outputs))
++            continue;
++
++        to_outputs = pa_strlist_prepend(to_outputs, module->outputs[i].name);
++    }
++    to_outputs = pa_strlist_reverse(to_outputs);
++
++    for (unsigned i = 0; i < module->inputs_size; i++) {
++        if (inputs && !str_in_strlist(module->inputs[i].name, inputs))
++            continue;
++
++        to_inputs = pa_strlist_prepend(to_inputs, module->inputs[i].name);
++    }
++    to_inputs = pa_strlist_reverse(to_inputs);
++
++    o_str = pa_strlist_tostring(to_outputs);
++    i_str = pa_strlist_tostring(to_inputs);
++
++    pa_log_debug("New combined profile: %s (outputs: %s, inputs: %s)", module->name, o_str, i_str);
++
++    description = pa_sprintf_malloc("Combined outputs (%s) and inputs (%s) of %s.", o_str,
++                                                                                    i_str,
++                                                                                    module->name);
++    p = profile_new(ps, module, module->name, description);
++    pa_xfree(description);
++    pa_xfree(o_str);
++    pa_xfree(i_str);
++
++    for (unsigned i = 0; i < module->outputs_size; i++) {
++        if (!str_in_strlist(module->outputs[i].name, to_outputs))
++            continue;
++
++        am = pa_droid_mapping_get(ps, PA_DIRECTION_OUTPUT, &module->outputs[i]);
++        pa_droid_profile_add_mapping(p, am);
++
++        if (pa_streq(module->outputs[i].name, "primary"))
++            p->priority += DEFAULT_PRIORITY;
++    }
++
++    for (unsigned i = 0; i < module->inputs_size; i++) {
++        if (!str_in_strlist(module->inputs[i].name, to_inputs))
++            continue;
++
++        am = pa_droid_mapping_get(ps, PA_DIRECTION_INPUT, &module->inputs[i]);
++        pa_droid_profile_add_mapping(p, am);
++
++        if (pa_streq(module->inputs[i].name, "primary"))
++            p->priority += DEFAULT_PRIORITY;
++    }
++
++    pa_strlist_free(to_outputs);
++    pa_strlist_free(to_inputs);
++
++    return p;
++}
++
++static pa_droid_profile_set *profile_set_new(const pa_droid_config_hw_module *module) {
+     pa_droid_profile_set *ps;
+ 
+     pa_assert(module);
+ 
+     ps = pa_xnew0(pa_droid_profile_set, 1);
+     ps->config = module->config;
+-    ps->profiles = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_droid_profile_free);
+-    ps->output_mappings = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_droid_mapping_free);
+-    ps->input_mappings = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_droid_mapping_free);
+-    ps->all_ports = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) droid_port_free);
++    ps->profiles        = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
++                                              NULL, (pa_free_cb_t) pa_droid_profile_free);
++    ps->output_mappings = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
++                                              NULL, (pa_free_cb_t) pa_droid_mapping_free);
++    ps->input_mappings  = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
++                                              NULL, (pa_free_cb_t) pa_droid_mapping_free);
++    ps->all_ports       = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
++                                              NULL, (pa_free_cb_t) droid_port_free);
++
++    return ps;
++}
++
++pa_droid_profile_set *pa_droid_profile_set_new(const pa_droid_config_hw_module *module) {
++    pa_droid_profile_set *ps;
++
++    ps = profile_set_new(module);
+ 
+     /* Each distinct hw module output matches one profile. If there are multiple inputs
+      * combinations are made so that all possible outputs and inputs can be selected.
+@@ -686,6 +951,15 @@ pa_droid_profile_set *pa_droid_profile_s
+     return ps;
+ }
+ 
++pa_droid_profile_set *pa_droid_profile_set_combined_new(const pa_droid_config_hw_module *module, pa_strlist *inputs, pa_strlist *outputs) {
++    pa_droid_profile_set *ps;
++
++    ps = profile_set_new(module);
++    add_combined_profile(ps, module, inputs, outputs);
++
++    return ps;
++}
++
+ void pa_droid_mapping_free(pa_droid_mapping *am) {
+     pa_assert(am);
+ 
+@@ -700,9 +974,21 @@ void pa_droid_profile_free(pa_droid_prof
+ 
+     pa_xfree(ap->name);
+     pa_xfree(ap->description);
++    if (ap->output_mappings)
++        pa_idxset_free(ap->output_mappings, NULL);
++    if (ap->input_mappings)
++        pa_idxset_free(ap->input_mappings, NULL);
+     pa_xfree(ap);
+ }
+ 
++static void droid_port_free(pa_droid_port *p) {
++    pa_assert(p);
++
++    pa_xfree(p->name);
++    pa_xfree(p->description);
++    pa_xfree(p);
++}
++
+ void pa_droid_profile_set_free(pa_droid_profile_set *ps) {
+     pa_assert(ps);
+ 
+@@ -749,9 +1035,6 @@ static pa_droid_port *create_o_port(pa_d
+     if (am->profile_set->config->global_config.default_output_device & device)
+         p->priority += DEFAULT_PRIORITY;
+ 
+-    if (check_port_availability(p->name))
+-        p->priority += (DEFAULT_PRIORITY * 3);
+-
+     return p;
+ }
+ 
+@@ -766,7 +1049,7 @@ static void add_o_ports(pa_droid_mapping
+ 
+     devices = am->output->devices;
+ 
+-    devices &= ~AUDIO_DEVICE_OUT_DEFAULT;
++    devices &= ~AUDIO_DEVICE_BIT_DEFAULT;
+ 
+     /* IHF combo devices, these devices are combined with IHF */
+     combo_devices = AUDIO_DEVICE_OUT_SPEAKER | AUDIO_DEVICE_OUT_WIRED_HEADPHONE;
+@@ -819,6 +1102,35 @@ static void add_o_ports(pa_droid_mapping
+     pa_idxset_put(am->ports, p, NULL);
+ }
+ 
++static void add_i_port(pa_droid_mapping *am, uint32_t device, const char *name) {
++    pa_droid_port *p;
++    char *desc;
++
++    pa_assert(am);
++    pa_assert(name);
++
++    if (!(p = pa_hashmap_get(am->profile_set->all_ports, name))) {
++        pa_log_debug("  New input port %s", name);
++        p = pa_xnew0(pa_droid_port, 1);
++
++        p->mapping = am;
++        p->name = pa_xstrdup(name);
++        desc = pa_replace(name, "input-", "Input from ");
++        p->description = pa_replace(desc, "_", " ");
++        pa_xfree(desc);
++        p->priority = DEFAULT_PRIORITY;
++        p->device = device;
++
++        if (am->profile_set->config->global_config.attached_input_devices & device)
++            p->priority += DEFAULT_PRIORITY;
++
++        pa_hashmap_put(am->profile_set->all_ports, p->name, p);
++    } else
++        pa_log_debug("  Input port %s from cache", name);
++
++    pa_idxset_put(am->ports, p, NULL);
++}
++
+ static void add_i_ports(pa_droid_mapping *am) {
+     pa_droid_port *p;
+     const char *name;
+@@ -828,9 +1140,9 @@ static void add_i_ports(pa_droid_mapping
+ 
+     pa_assert(am);
+ 
+-    devices = am->input->devices;
+-#ifdef HAL_V2
+-    devices &= ~AUDIO_DEVICE_IN_DEFAULT;
++    devices = am->input->devices | AUDIO_DEVICE_IN_DEFAULT;
++#if DROID_HAL >= 2
++    devices &= ~AUDIO_DEVICE_BIT_IN;
+ #endif
+ 
+     while (devices) {
+@@ -838,44 +1150,25 @@ static void add_i_ports(pa_droid_mapping
+ 
+         if (devices & cur_device) {
+ 
+-#ifdef HAL_V2
++#if DROID_HAL >= 2
++#ifndef DROID_DEVICE_MAKO
+             cur_device |= AUDIO_DEVICE_BIT_IN;
+ #endif
++#endif
+ 
+             pa_assert_se(pa_droid_input_port_name(cur_device, &name));
+-
+-            if (!(p = pa_hashmap_get(am->profile_set->all_ports, name))) {
+-                pa_log_debug("  New input port %s", name);
+-                p = pa_xnew0(pa_droid_port, 1);
+-
+-                p->mapping = am;
+-                p->name = pa_xstrdup(name);
+-                desc = pa_replace(name, "input-", "Input from ");
+-                p->description = pa_replace(desc, "_", " ");
+-                pa_xfree(desc);
+-                p->priority = DEFAULT_PRIORITY;
+-                p->device = cur_device;
+-
+-                if (am->profile_set->config->global_config.attached_input_devices & cur_device & ~AUDIO_DEVICE_BIT_IN)
+-                    p->priority += DEFAULT_PRIORITY;
+-
+-                /* Make builtin mic the default input device */
+-                if (cur_device == AUDIO_DEVICE_IN_BUILTIN_MIC)
+-                    p->priority += DEFAULT_PRIORITY;
+-
+-                if (check_port_availability(p->name))
+-                    p->priority += (DEFAULT_PRIORITY * 3);
+-
+-                pa_hashmap_put(am->profile_set->all_ports, p->name, p);
+-            } else
+-                pa_log_debug("  Input port %s from cache", name);
+-
+-            pa_idxset_put(am->ports, p, NULL);
++            add_i_port(am, cur_device, name);
+ 
+             devices &= ~cur_device;
+         }
+     }
+ 
++#if DROID_HAL == 1
++    /* HAL v1 has default input device defined as another input device,
++     * so we need to add it by hand here. */
++    add_i_port(am, AUDIO_DEVICE_IN_DEFAULT, "input-default");
++#endif
++
+     if (!(p = pa_hashmap_get(am->profile_set->all_ports, PA_DROID_INPUT_PARKING))) {
+         pa_log_debug("  New input port %s", PA_DROID_INPUT_PARKING);
+         /* Create parking port for input mapping to be used when audio_mode_t changes. */
+@@ -935,6 +1228,32 @@ pa_droid_mapping *pa_droid_mapping_get(p
+     return am;
+ }
+ 
++bool pa_droid_mapping_is_primary(pa_droid_mapping *am) {
++    pa_assert(am);
++
++    if (am->direction == PA_DIRECTION_OUTPUT) {
++        pa_assert(am->output);
++        return pa_streq(am->output->name, PA_DROID_PRIMARY_DEVICE);
++    } else {
++        pa_assert(am->input);
++        return pa_streq(am->input->name, PA_DROID_PRIMARY_DEVICE);
++    }
++}
++
++pa_droid_mapping *pa_droid_idxset_get_primary(pa_idxset *i) {
++    pa_droid_mapping *am;
++    uint32_t idx;
++
++    pa_assert(i);
++
++    PA_IDXSET_FOREACH(am, i, idx) {
++        if (pa_droid_mapping_is_primary(am))
++            return am;
++    }
++
++    return NULL;
++}
++
+ bool pa_droid_output_port_name(audio_devices_t value, const char **to_str) {
+     return string_convert_num_to_str(string_conversion_table_output_device_fancy, (uint32_t) value, to_str);
+ }
+@@ -943,8 +1262,13 @@ bool pa_droid_input_port_name(audio_devi
+     return string_convert_num_to_str(string_conversion_table_input_device_fancy, (uint32_t) value, to_str);
+ }
+ 
++bool pa_droid_audio_source_name(audio_source_t value, const char **to_str) {
++    return string_convert_num_to_str(string_conversion_table_audio_source_fancy, (uint32_t) value, to_str);
++}
++
+ static int add_ports(pa_core *core, pa_card_profile *cp, pa_hashmap *ports, pa_droid_mapping *am, pa_hashmap *extra) {
+     pa_droid_port *p;
++    pa_device_port_new_data dp_data;
+     pa_device_port *dp;
+     pa_droid_port_data *data;
+     uint32_t idx;
+@@ -955,35 +1279,37 @@ static int add_ports(pa_core *core, pa_c
+     PA_IDXSET_FOREACH(p, am->ports, idx) {
+         if (!(dp = pa_hashmap_get(ports, p->name))) {
+             pa_log_debug("  New port %s", p->name);
++            pa_device_port_new_data_init(&dp_data);
++            pa_device_port_new_data_set_name(&dp_data, p->name);
++            pa_device_port_new_data_set_description(&dp_data, p->description);
++            pa_device_port_new_data_set_direction(&dp_data, p->mapping->direction);
++            pa_device_port_new_data_set_available(&dp_data, PA_AVAILABLE_YES);
+ 
+-            pa_device_port_new_data port_data;
+-            pa_device_port_new_data_init(&port_data);
+-            pa_device_port_new_data_set_name(&port_data, p->name);
+-            pa_device_port_new_data_set_description(&port_data, p->description);
+-            pa_device_port_new_data_set_direction(&port_data, p->mapping->direction);
+-            dp = pa_device_port_new(core, &port_data, sizeof(pa_droid_port_data));
+-            pa_device_port_new_data_done(&port_data);
++            dp = pa_device_port_new(core, &dp_data, sizeof(pa_droid_port_data));
+             dp->priority = p->priority;
+ 
++            pa_device_port_new_data_done(&dp_data);
++
+             pa_hashmap_put(ports, dp->name, dp);
+-            dp->profiles = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_droid_profile_free);
++            dp->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+ 
+             data = PA_DEVICE_PORT_DATA(dp);
+             data->device = p->device;
+         } else
+             pa_log_debug("  Port %s from cache", p->name);
+ 
+-        /* If port/jack detection is available, start as not available by default */
+-        dp->available = check_port_availability(p->name) ? PA_AVAILABLE_NO : PA_AVAILABLE_UNKNOWN;
+-
+-        if (cp)
+-            pa_hashmap_put(dp->profiles, cp->name, cp);
++        if (cp) {
++            if (!pa_hashmap_get(dp->profiles, cp->name))
++                pa_hashmap_put(dp->profiles, cp->name, cp);
++        }
+ 
+         count++;
+ 
+         if (extra) {
+-            pa_hashmap_put(extra, dp->name, dp);
+-            pa_device_port_ref(dp);
++            if (!pa_hashmap_get(extra, dp->name)) {
++                pa_hashmap_put(extra, dp->name, dp);
++                pa_device_port_ref(dp);
++            }
+         }
+     }
+ 
+@@ -1032,15 +1358,14 @@ static pa_droid_hw_module *droid_hw_modu
+ 
+     hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, module->name, (const hw_module_t**) &hwmod);
+     if (!hwmod) {
+-        pa_log("Failed to get hw module id: %s name: %s, trying alternative.", AUDIO_HARDWARE_MODULE_ID, module->name);
+-        hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID2, module->name, (const hw_module_t**) &hwmod);
+-        if (!hwmod) {
+-            pa_log("Failed to get hw module id: %s name: %s.", AUDIO_HARDWARE_MODULE_ID2, module->name);
+-            goto fail;
+-        }
++        pa_log("Failed to get hw module %s.", module->name);
++        goto fail;
+     }
+ 
+-    pa_log_info("Loaded hw module %s", module->name);
++    pa_log_info("Loaded hw module %s (HAL %d.%d.%d)", module->name,
++                                                          ANDROID_VERSION_MAJOR,
++                                                          ANDROID_VERSION_MINOR,
++                                                          ANDROID_VERSION_PATCH);
+ 
+     ret = audio_hw_device_open(hwmod, &device);
+     if (!device) {
+@@ -1058,11 +1383,15 @@ static pa_droid_hw_module *droid_hw_modu
+     hw->core = core;
+     hw->hwmod = hwmod;
+     hw->hw_mutex = pa_mutex_new(true, false);
++    hw->output_mutex = pa_mutex_new(true, false);
++    hw->input_mutex = pa_mutex_new(true, false);
+     hw->device = device;
+     hw->config = config; /* We take ownership of config struct. */
+     hw->enabled_module = pa_droid_config_find_module(hw->config, module_id);
+     hw->module_id = hw->enabled_module->name;
+     hw->shared_name = shared_name_get(hw->module_id);
++    hw->outputs = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
++    hw->inputs = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
+ 
+     pa_assert_se(pa_shared_set(core, hw->shared_name, hw) >= 0);
+ 
+@@ -1117,9 +1446,25 @@ static void droid_hw_module_close(pa_dro
+     if (hw->hw_mutex)
+         pa_mutex_free(hw->hw_mutex);
+ 
++    if (hw->output_mutex)
++        pa_mutex_free(hw->output_mutex);
++
++    if (hw->input_mutex)
++        pa_mutex_free(hw->input_mutex);
++
+     if (hw->shared_name)
+         pa_xfree(hw->shared_name);
+ 
++    if (hw->outputs) {
++        pa_assert(pa_idxset_size(hw->outputs) == 0);
++        pa_idxset_free(hw->outputs, NULL);
++    }
++
++    if (hw->inputs) {
++        pa_assert(pa_idxset_size(hw->inputs) == 0);
++        pa_idxset_free(hw->inputs, NULL);
++    }
++
+     pa_xfree(hw);
+ }
+ 
+@@ -1187,3 +1532,404 @@ void pa_droid_hw_module_unlock(pa_droid_
+ 
+     pa_mutex_unlock(hw->hw_mutex);
+ }
++
++static pa_droid_stream *droid_stream_new(pa_droid_hw_module *module) {
++    pa_droid_stream *s;
++
++    s = pa_xnew0(pa_droid_stream, 1);
++    PA_REFCNT_INIT(s);
++
++    s->module = module;
++
++    return s;
++}
++
++pa_droid_stream *pa_droid_open_output_stream(pa_droid_hw_module *module,
++                                             const pa_sample_spec *spec,
++                                             const pa_channel_map *map,
++                                             audio_output_flags_t flags,
++                                             audio_devices_t devices) {
++    pa_droid_stream *s = NULL;
++    int ret;
++    struct audio_stream_out *stream;
++    audio_format_t hal_audio_format = 0;
++    audio_channel_mask_t hal_channel_mask = 0;
++    struct audio_config config_out;
++    size_t buffer_size;
++
++    pa_assert(module);
++    pa_assert(spec);
++    pa_assert(map);
++
++    if (!pa_convert_format(spec->format, CONV_FROM_PA, &hal_audio_format)) {
++        pa_log("Sample spec format %u not supported.", spec->format);
++        goto fail;
++    }
++
++    for (int i = 0; i < map->channels; i++) {
++        audio_channel_mask_t c;
++        if (!pa_convert_output_channel(map->map[i], CONV_FROM_PA, &c)) {
++            pa_log("Failed to convert channel map.");
++            goto fail;
++        }
++        hal_channel_mask |= c;
++    }
++
++    config_out.sample_rate = spec->rate;
++    config_out.channel_mask = hal_channel_mask;
++    config_out.format = hal_audio_format;
++
++    pa_droid_hw_module_lock(module);
++    ret = module->device->open_output_stream(module->device,
++                                             module->stream_out_id++,
++                                             devices,
++                                             flags,
++                                             &config_out,
++                                             &stream
++#if DROID_HAL >= 3
++                                             /* Go with empty address, should work
++                                              * with most devices for now. */
++                                             , NULL
++#endif
++                                             );
++    pa_droid_hw_module_unlock(module);
++
++    if (ret < 0 || !stream) {
++        pa_log("Failed to open output stream: %d", ret);
++        goto fail;
++    }
++
++    s = droid_stream_new(module);
++    s->out = stream;
++    s->sample_spec = *spec;
++    s->channel_map = *map;
++    s->flags = flags;
++
++    if ((s->sample_spec.rate = s->out->common.get_sample_rate(&s->out->common)) != spec->rate)
++        pa_log_warn("Requested sample rate %u but got %u instead.", spec->rate, s->sample_spec.rate);
++
++    pa_idxset_put(module->outputs, s, NULL);
++
++    buffer_size = s->out->common.get_buffer_size(&s->out->common);
++
++    pa_log_info("Opened droid output stream %p with device: %u flags: %u sample rate: %u channels: %u (%u) format: %u (%u) buffer size: %u (%llu usec)",
++            (void *) s,
++            devices,
++            s->flags,
++            s->sample_spec.rate,
++            s->sample_spec.channels, hal_channel_mask,
++            s->sample_spec.format, hal_audio_format,
++            buffer_size,
++            pa_bytes_to_usec(buffer_size, &s->sample_spec));
++
++    return s;
++
++fail:
++    pa_xfree(s);
++
++    return NULL;
++}
++
++pa_droid_stream *pa_droid_open_input_stream(pa_droid_hw_module *module,
++                                            const pa_sample_spec *spec,
++                                            const pa_channel_map *map,
++                                            audio_devices_t devices) {
++
++    pa_droid_stream *s = NULL;
++    int ret;
++    audio_stream_in_t *stream;
++    audio_format_t hal_audio_format = 0;
++    audio_channel_mask_t hal_channel_mask = 0;
++    pa_channel_map channel_map;
++    pa_sample_spec sample_spec;
++    bool voicecall_record = false;
++    struct audio_config config_in;
++    size_t buffer_size;
++
++#if DROID_HAL >= 2
++    if ((devices & ~AUDIO_DEVICE_BIT_IN) & AUDIO_DEVICE_IN_VOICE_CALL)
++#else
++    if (devices & AUDIO_DEVICE_IN_VOICE_CALL)
++#endif
++        voicecall_record = true;
++
++    channel_map = *map;
++    sample_spec = *spec;
++
++    if (!pa_convert_format(spec->format, CONV_FROM_PA, &hal_audio_format)) {
++        pa_log("Sample spec format %u not supported.", spec->format);
++        goto fail;
++    }
++
++    for (int i = 0; i < map->channels; i++) {
++        audio_channel_mask_t c;
++        if (!pa_convert_input_channel(map->map[i], CONV_FROM_PA, &c)) {
++            pa_log("Failed to convert channel map.");
++            goto fail;
++        }
++        hal_channel_mask |= c;
++    }
++
++    if (voicecall_record) {
++        pa_channel_map_init_mono(&channel_map);
++        sample_spec.channels = 1;
++        /* Only allow recording both downlink and uplink. */
++#ifdef QCOM_HARDWARE
++        hal_channel_mask = AUDIO_CHANNEL_IN_VOICE_CALL_MONO;
++#else
++        hal_channel_mask = AUDIO_CHANNEL_IN_VOICE_UPLINK | AUDIO_CHANNEL_IN_VOICE_DNLINK;
++#endif
++    }
++
++    config_in.sample_rate = sample_spec.rate;
++    config_in.channel_mask = hal_channel_mask;
++    config_in.format = hal_audio_format;
++
++    pa_droid_hw_module_lock(module);
++    ret = module->device->open_input_stream(module->device,
++                                            module->stream_in_id++,
++                                            devices,
++                                            &config_in,
++                                            &stream
++#if DROID_HAL >= 3
++                                                  , AUDIO_INPUT_FLAG_NONE   /* Default to no input flags */
++                                                  , NULL                    /* Don't define address */
++                                                  , AUDIO_SOURCE_DEFAULT    /* Default audio source */
++#endif
++                                                  );
++    pa_droid_hw_module_unlock(module);
++
++    if (ret < 0 || !stream) {
++        pa_log("Failed to open input stream: %d", ret);
++        goto fail;
++    }
++
++    s = droid_stream_new(module);
++    s->in = stream;
++    s->sample_spec = sample_spec;
++    s->channel_map = channel_map;
++    s->flags = 0;
++
++    if ((s->sample_spec.rate = s->in->common.get_sample_rate(&s->in->common)) != spec->rate)
++        pa_log_warn("Requested sample rate %u but got %u instead.", spec->rate, s->sample_spec.rate);
++
++    pa_idxset_put(module->inputs, s, NULL);
++
++    buffer_size = s->in->common.get_buffer_size(&s->in->common);
++
++    pa_log_info("Opened droid input stream %p with device: %u flags: %u sample rate: %u channels: %u (%u) format: %u (%u) buffer size: %u (%llu usec)",
++            (void *) s,
++            devices,
++            s->flags,
++            s->sample_spec.rate,
++            s->sample_spec.channels, hal_channel_mask,
++            s->sample_spec.format, hal_audio_format,
++            buffer_size,
++            pa_bytes_to_usec(buffer_size, &s->sample_spec));
++
++    return s;
++
++fail:
++    pa_xfree(s);
++
++    return NULL;
++}
++
++pa_droid_stream *pa_droid_stream_ref(pa_droid_stream *s) {
++    pa_assert(s);
++    pa_assert(s->out || s->in);
++    pa_assert(PA_REFCNT_VALUE(s) >= 1);
++
++    PA_REFCNT_INC(s);
++    return s;
++}
++
++void pa_droid_stream_unref(pa_droid_stream *s) {
++    pa_assert(s);
++    pa_assert(s->out || s->in);
++    pa_assert(PA_REFCNT_VALUE(s) >= 1);
++
++    if (PA_REFCNT_DEC(s) > 0)
++        return;
++
++    if (s->out) {
++        pa_mutex_lock(s->module->output_mutex);
++        pa_idxset_remove_by_data(s->module->outputs, s, NULL);
++        s->module->device->close_output_stream(s->module->device, s->out);
++        pa_mutex_unlock(s->module->output_mutex);
++    } else {
++        pa_mutex_lock(s->module->input_mutex);
++        pa_idxset_remove_by_data(s->module->inputs, s, NULL);
++        s->module->device->close_input_stream(s->module->device, s->in);
++        pa_mutex_unlock(s->module->input_mutex);
++    }
++
++    pa_xfree(s);
++}
++
++static pa_droid_stream *get_primary_output(pa_droid_hw_module *hw) {
++    pa_droid_stream *s;
++    uint32_t idx;
++
++    pa_assert(hw);
++    pa_assert(hw->outputs);
++
++    PA_IDXSET_FOREACH(s, hw->outputs, idx) {
++        if (s->flags & AUDIO_OUTPUT_FLAG_PRIMARY)
++            return s;
++    }
++
++    return NULL;
++}
++
++int pa_droid_stream_set_output_route(pa_droid_stream *s, audio_devices_t device) {
++    pa_droid_stream *slave;
++    uint32_t idx;
++    char *parameters;
++    int ret;
++
++    pa_assert(s);
++    pa_assert(s->out);
++    pa_assert(s->module);
++    pa_assert(s->module->output_mutex);
++
++    pa_mutex_lock(s->module->output_mutex);
++
++    parameters = pa_sprintf_malloc("%s=%u;", AUDIO_PARAMETER_STREAM_ROUTING, device);
++
++    if (s->flags & AUDIO_OUTPUT_FLAG_PRIMARY || get_primary_output(s->module) == NULL) {
++        pa_log_debug("output stream %p set_parameters(%s) %#010x", (void *) s, parameters, device);
++        ret = s->out->common.set_parameters(&s->out->common, parameters);
++
++        if (ret < 0) {
++            if (ret == -ENOSYS)
++                pa_log_warn("output set_parameters(%s) not allowed while stream is active", parameters);
++            else
++                pa_log_warn("output set_parameters(%s) failed", parameters);
++        }
++    }
++
++    if (s->flags & AUDIO_OUTPUT_FLAG_PRIMARY && pa_idxset_size(s->module->outputs) > 1) {
++
++        PA_IDXSET_FOREACH(slave, s->module->outputs, idx) {
++            if (slave == s)
++                continue;
++
++            pa_log_debug("slave output stream %p set_parameters(%s)", (void *) slave, parameters);
++            ret = slave->out->common.set_parameters(&slave->out->common, parameters);
++
++            if (ret < 0) {
++                if (ret == -ENOSYS)
++                    pa_log_warn("output set_parameters(%s) not allowed while stream is active", parameters);
++                else
++                    pa_log_warn("output set_parameters(%s) failed", parameters);
++            }
++        }
++    }
++
++    pa_xfree(parameters);
++
++    pa_mutex_unlock(s->module->output_mutex);
++
++    return ret;
++}
++
++int pa_droid_stream_set_input_route(pa_droid_stream *s, audio_devices_t device, audio_source_t *new_source) {
++    audio_source_t source = (uint32_t) -1;
++    char *parameters;
++    int ret;
++
++    pa_assert(s);
++    pa_assert(s->in);
++
++#ifdef DROID_DEVICE_I9305
++    device &= ~AUDIO_DEVICE_BIT_IN;
++#endif
++
++    if (pa_input_device_default_audio_source(device, &source))
++#ifdef DROID_AUDIO_HAL_ATOI_FIX
++        parameters = pa_sprintf_malloc("%s=%d;%s=%u", AUDIO_PARAMETER_STREAM_ROUTING, (int32_t) device,
++                                                      AUDIO_PARAMETER_STREAM_INPUT_SOURCE, source);
++#else
++        parameters = pa_sprintf_malloc("%s=%u;%s=%u", AUDIO_PARAMETER_STREAM_ROUTING, device,
++                                                      AUDIO_PARAMETER_STREAM_INPUT_SOURCE, source);
++#endif
++    else
++        parameters = pa_sprintf_malloc("%s=%u", AUDIO_PARAMETER_STREAM_ROUTING, device);
++
++    pa_log_debug("input stream %p set_parameters(%s) %#010x ; %#010x",
++                 (void *) s, parameters, device, source);
++
++
++#if defined(DROID_DEVICE_MAKO) || defined(DROID_DEVICE_ANZU) ||\
++    defined(DROID_DEVICE_COCONUT) || defined(DROID_DEVICE_HAIDA) ||\
++    defined(DROID_DEVICE_HALLON) || defined(DROID_DEVICE_IYOKAN) ||\
++    defined(DROID_DEVICE_MANGO) || defined(DROID_DEVICE_SATSUMA) ||\
++    defined(DROID_DEVICE_SMULTRON) || defined(DROID_DEVICE_URUSHI)
++#warning Using mako set_parameters hack.
++    pa_mutex_lock(s->module->hw_mutex);
++    ret = s->module->device->set_parameters(s->module->device, parameters);
++    pa_mutex_unlock(s->module->hw_mutex);
++#else
++    pa_mutex_lock(s->module->input_mutex);
++    ret = s->in->common.set_parameters(&s->in->common, parameters);
++    pa_mutex_unlock(s->module->input_mutex);
++#endif
++
++    if (ret < 0) {
++        if (ret == -ENOSYS)
++            pa_log_warn("input set_parameters(%s) not allowed while stream is active", parameters);
++        else
++            pa_log_warn("input set_parameters(%s) failed", parameters);
++    }
++
++    if (new_source)
++        *new_source = source;
++
++    pa_xfree(parameters);
++
++    return ret;
++}
++
++int pa_droid_stream_set_parameters(pa_droid_stream *s, const char *parameters) {
++    int ret;
++
++    pa_assert(s);
++    pa_assert(s->out || s->in);
++    pa_assert(parameters);
++
++    if (s->out) {
++        pa_log_debug("output stream %p set_parameters(%s)", (void *) s, parameters);
++        pa_mutex_lock(s->module->output_mutex);
++        ret = s->out->common.set_parameters(&s->out->common, parameters);
++        pa_mutex_unlock(s->module->output_mutex);
++    } else {
++        pa_log_debug("input stream %p set_parameters(%s)", (void *) s, parameters);
++        pa_mutex_lock(s->module->input_mutex);
++        ret = s->in->common.set_parameters(&s->in->common, parameters);
++        pa_mutex_unlock(s->module->input_mutex);
++    }
++
++    if (ret < 0)
++        pa_log("%s stream %p set_parameters(%s) failed: %d",
++               s->out ? "output" : "input", (void *) s, parameters, ret);
++
++    return ret;
++}
++
++int pa_droid_set_parameters(pa_droid_hw_module *hw, const char *parameters) {
++    int ret;
++
++    pa_assert(hw);
++    pa_assert(parameters);
++
++    pa_log_debug("hw %p set_parameters(%s)", (void *) hw, parameters);
++    pa_mutex_lock(hw->hw_mutex);
++    ret = hw->device->set_parameters(hw->device, parameters);
++    pa_mutex_unlock(hw->hw_mutex);
++
++    if (ret < 0)
++        pa_log("hw module %p set_parameters(%s) failed: %d", (void *) hw, parameters, ret);
++
++    return ret;
++}
+Index: overlay/src/modules/droid/droid-util.h
+===================================================================
+--- overlay.orig/src/modules/droid/droid-util.h
++++ overlay/src/modules/droid/droid-util.h
+@@ -28,24 +28,36 @@
+ #include <pulsecore/core-util.h>
+ #include <pulsecore/macro.h>
+ #include <pulsecore/mutex.h>
+-#include <pulsecore/modargs.h>
++#include <pulsecore/strlist.h>
+ 
+-#include <hardware/audio.h>
+-#include <hardware_legacy/audio_policy_conf.h>
++#include <android-config.h>
++
++#if !defined(ANDROID_VERSION_MAJOR) || !defined(ANDROID_VERSION_MINOR) || !defined(ANDROID_VERSION_PATCH)
++#error "ANDROID_VERSION_* not defined. Did you get your headers via extract-headers.sh?"
++#endif
++
++#if ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 1
++#include "droid-util-41qc.h"
++#elif ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 2
++#include "droid-util-42.h"
++#elif ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 4
++#include "droid-util-44.h"
++#elif ANDROID_VERSION_MAJOR == 5 && ANDROID_VERSION_MINOR == 1
++#include "droid-util-51.h"
++#else
++#error "No valid ANDROID_VERSION found."
++#endif
+ 
+ #define PROP_DROID_DEVICES    "droid.devices"
+ #define PROP_DROID_FLAGS      "droid.flags"
+ #define PROP_DROID_HW_MODULE  "droid.hw_module"
+ 
+-/* Alternative module ID */
+-#define AUDIO_HARDWARE_MODULE_ID2 "libaudio"
+-
+-/* From module-device-restore */
+-#define MODULE_DEVICE_RESTORE_SKIP_PROPERTY "module-device-restore.skip"
++#define PA_DROID_PRIMARY_DEVICE     "primary"
+ 
+ typedef struct pa_droid_hw_module pa_droid_hw_module;
++typedef struct pa_droid_stream pa_droid_stream;
+ typedef struct pa_droid_card_data pa_droid_card_data;
+-typedef void (*common_set_parameters_cb_t)(pa_droid_card_data *card_data, const char *str);
++typedef int (*common_set_parameters_cb_t)(pa_droid_card_data *card_data, const char *str);
+ 
+ typedef struct pa_droid_config_audio pa_droid_config_audio;
+ typedef struct pa_droid_config_hw_module pa_droid_config_hw_module;
+@@ -59,6 +71,8 @@ struct pa_droid_hw_module {
+     pa_droid_config_audio *config;
+     const pa_droid_config_hw_module *enabled_module;
+     pa_mutex *hw_mutex;
++    pa_mutex *output_mutex;
++    pa_mutex *input_mutex;
+ 
+     struct hw_module_t *hwmod;
+     audio_hw_device_t *device;
+@@ -68,6 +82,21 @@ struct pa_droid_hw_module {
+     uint32_t stream_out_id;
+     uint32_t stream_in_id;
+ 
++    pa_idxset *outputs;
++    pa_idxset *inputs;
++};
++
++struct pa_droid_stream {
++    PA_REFCNT_DECLARE;
++
++    pa_droid_hw_module *module;
++
++    pa_sample_spec sample_spec;
++    pa_channel_map channel_map;
++    uint32_t flags;
++
++    struct audio_stream_out *out;
++    struct audio_stream_in *in;
+ };
+ 
+ struct pa_droid_card_data {
+@@ -92,9 +121,9 @@ typedef struct pa_droid_config_output {
+     const pa_droid_config_hw_module *module;
+ 
+     char name[AUDIO_HARDWARE_MODULE_ID_MAX_LEN];
+-    uint32_t sampling_rates[AUDIO_MAX_SAMPLING_RATES];
++    uint32_t sampling_rates[AUDIO_MAX_SAMPLING_RATES]; /* (uint32_t) -1 -> dynamic */
+     audio_channel_mask_t channel_masks; /* 0 -> dynamic */
+-    audio_format_t formats;
++    audio_format_t formats; /* 0 -> dynamic */
+     audio_devices_t devices;
+     audio_output_flags_t flags;
+ } pa_droid_config_output;
+@@ -103,10 +132,13 @@ typedef struct pa_droid_config_input {
+     const pa_droid_config_hw_module *module;
+ 
+     char name[AUDIO_HARDWARE_MODULE_ID_MAX_LEN];
+-    uint32_t sampling_rates[AUDIO_MAX_SAMPLING_RATES];
++    uint32_t sampling_rates[AUDIO_MAX_SAMPLING_RATES]; /* (uint32_t) -1 -> dynamic */
+     audio_channel_mask_t channel_masks; /* 0 -> dynamic */
+-    audio_format_t formats;
++    audio_format_t formats; /* 0 -> dynamic */
+     audio_devices_t devices;
++#if DROID_HAL >= 3
++    audio_input_flags_t flags;
++#endif
+ } pa_droid_config_input;
+ 
+ struct pa_droid_config_hw_module {
+@@ -173,9 +205,10 @@ typedef struct pa_droid_profile {
+     char *description;
+     unsigned priority;
+ 
+-    /* Profile doesn't own the mappings */
+-    pa_droid_mapping *output;
+-    pa_droid_mapping *input;
++    /* Idxsets contain pa_droid_mapping objects.
++     * Profile doesn't own the mappings. */
++    pa_idxset *output_mappings;
++    pa_idxset *input_mappings;
+ 
+ } pa_droid_profile;
+ 
+@@ -225,6 +258,10 @@ char *pa_list_string_output_device(audio
+ char *pa_list_string_input_device(audio_devices_t devices);
+ char *pa_list_string_flags(audio_output_flags_t flags);
+ 
++/* Get default audio source associated with input device.
++ * Return true if default source was found, false if not. */
++bool pa_input_device_default_audio_source(audio_devices_t input_device, audio_source_t *default_source);
++
+ /* Config parser */
+ bool pa_parse_droid_audio_config(const char *filename, pa_droid_config_audio *config);
+ pa_droid_config_audio *pa_droid_config_load(pa_modargs *ma);
+@@ -236,21 +273,65 @@ const pa_droid_config_hw_module *pa_droi
+ 
+ /* Profiles */
+ pa_droid_profile_set *pa_droid_profile_set_new(const pa_droid_config_hw_module *module);
++pa_droid_profile_set *pa_droid_profile_set_combined_new(const pa_droid_config_hw_module *module,
++                                                        pa_strlist *inputs,
++                                                        pa_strlist *outputs);
+ void pa_droid_profile_set_free(pa_droid_profile_set *ps);
+ 
+ pa_droid_profile *pa_droid_profile_new(pa_droid_profile_set *ps, const pa_droid_config_output *output, const pa_droid_config_input *input);
++void pa_droid_profile_add_mapping(pa_droid_profile *p, pa_droid_mapping *am);
+ void pa_droid_profile_free(pa_droid_profile *p);
+ 
+ pa_droid_mapping *pa_droid_mapping_get(pa_droid_profile_set *ps, pa_direction_t direction, const void *data);
++bool pa_droid_mapping_is_primary(pa_droid_mapping *am);
++/* Go through idxset containing pa_droid_mapping objects and if primary output or input
++ * mapping is found, return pointer to that mapping. */
++pa_droid_mapping *pa_droid_idxset_get_primary(pa_idxset *i);
+ void pa_droid_mapping_free(pa_droid_mapping *am);
+ 
+-/* Add ports from sinks/sources */
++/* Add ports from sinks/sources.
++ * May be called multiple times for one sink/source. */
+ void pa_droid_add_ports(pa_hashmap *ports, pa_droid_mapping *am, pa_card *card);
+-/* Add ports from card */
++/* Add ports from card.
++ * May be called multiple times for one card profile. */
+ void pa_droid_add_card_ports(pa_card_profile *cp, pa_hashmap *ports, pa_droid_mapping *am, pa_core *core);
+ 
+ /* Pretty port names */
+ bool pa_droid_output_port_name(audio_devices_t value, const char **to_str);
+ bool pa_droid_input_port_name(audio_devices_t value, const char **to_str);
+ 
++/* Pretty audio source names */
++bool pa_droid_audio_source_name(audio_source_t value, const char **to_str);
++
++/* Module operations */
++int pa_droid_set_parameters(pa_droid_hw_module *hw, const char *parameters);
++
++/* Stream operations */
++pa_droid_stream *pa_droid_stream_ref(pa_droid_stream *s);
++void pa_droid_stream_unref(pa_droid_stream *s);
++
++int pa_droid_stream_set_parameters(pa_droid_stream *s, const char *parameters);
++
++/* Output stream operations */
++pa_droid_stream *pa_droid_open_output_stream(pa_droid_hw_module *module,
++                                             const pa_sample_spec *spec,
++                                             const pa_channel_map *map,
++                                             audio_output_flags_t flags,
++                                             audio_devices_t devices);
++
++/* Set routing to the output stream, with following side-effects:
++ * - if routing is set to primary output stream, set routing to all other
++ *   open streams as well
++ * - if routing is set to non-primary stream and primary stream exists, do nothing
++ * - if routing is set to non-primary stream and primary stream doesn't exist, set routing
++ */
++int pa_droid_stream_set_output_route(pa_droid_stream *s, audio_devices_t device);
++
++/* Input stream operations */
++pa_droid_stream *pa_droid_open_input_stream(pa_droid_hw_module *module,
++                                            const pa_sample_spec *spec,
++                                            const pa_channel_map *map,
++                                            audio_devices_t devices);
++int pa_droid_stream_set_input_route(pa_droid_stream *s, audio_devices_t device, audio_source_t *new_source);
++
+ #endif
+Index: overlay/src/modules/droid/module-droid-card.c
+===================================================================
+--- overlay.orig/src/modules/droid/module-droid-card.c
++++ overlay/src/modules/droid/module-droid-card.c
+@@ -54,9 +54,7 @@
+ #include <pulsecore/card.h>
+ #include <pulsecore/device-port.h>
+ #include <pulsecore/idxset.h>
+-
+-#include <hardware/audio.h>
+-#include <system/audio.h>
++#include <pulsecore/strlist.h>
+ 
+ //#include <droid/hardware/audio_policy.h>
+ //#include <droid/system/audio_policy.h>
+@@ -68,7 +66,11 @@
+ #include "droid-extcon.h"
+ #endif
+ 
+-#include "module-droid-card-symdef.h"
++#if ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 2
++#include "module-droid-card-19-symdef.h"
++#elif ANDROID_VERSION_MAJOR == 5 && ANDROID_VERSION_MINOR == 1
++#include "module-droid-card-22-symdef.h"
++#endif
+ 
+ PA_MODULE_AUTHOR("Juho Hämäläinen");
+ PA_MODULE_DESCRIPTION("Droid card");
+@@ -84,10 +86,9 @@ PA_MODULE_USAGE(
+         "voice_source_routing=<route source ports during voice call, default false> "
+         "deferred_volume=<synchronize software and hardware volume changes to avoid momentary jumps?> "
+         "config=<location for droid audio configuration> "
+-        "voice_volume_call_mode=<sink volume controls voice volume during call mode, default false> "
+         "voice_property_key=<proplist key searched for sink-input that should control voice call volume> "
+         "voice_property_value=<proplist value for the key for voice control sink-input> "
+-        "voice_virtual_stream=<true/false> create virtual stream for voice call volume control (default false)"
++        "combine=<comma separated list of outputs that should be merged to one profile. defaults to none>"
+ );
+ 
+ static const char* const valid_modargs[] = {
+@@ -97,6 +98,14 @@ static const char* const valid_modargs[]
+     "namereg_fail",
+     "format",
+     "rate",
++    "channels",
++    "channel_map",
++    "sink_rate",
++    "sink_format",
++    "sink_channel_map",
++    "source_rate",
++    "source_format",
++    "source_channel_map",
+     "output_flags",
+     "module_id",
+     "voice_source_routing",
+@@ -110,21 +119,28 @@ static const char* const valid_modargs[]
+     "voice_property_key",
+     "voice_property_value",
+     "voice_virtual_stream",
++    "combine",
+     NULL,
+ };
+ 
+ #define DEFAULT_MODULE_ID "primary"
+-#define DEFAULT_AUDIO_POLICY_CONF "/system/etc/audio_policy.conf"
+ #define VOICE_CALL_PROFILE_NAME     "voicecall"
+ #define VOICE_CALL_PROFILE_DESC     "Call mode"
++#define VOICE_RECORD_PROFILE_NAME   "voicecall-record"
++#define VOICE_RECORD_PROFILE_DESC   "Call mode record"
+ #define RINGTONE_PROFILE_NAME       "ringtone"
+ #define RINGTONE_PROFILE_DESC       "Ringtone mode"
+ #define COMMUNICATION_PROFILE_NAME  "communication"
+ #define COMMUNICATION_PROFILE_DESC  "Communication mode"
+ 
++struct userdata;
++
++typedef bool (*virtual_profile_event_cb)(struct userdata *u, pa_droid_profile *p, bool enabling);
++
+ struct virtual_profile {
+-    pa_droid_profile *profile;
+-    audio_mode_t mode;
++    pa_card_profile *parent;
++    pa_card_profile *extension;
++    virtual_profile_event_cb event_cb;
+ };
+ 
+ struct userdata {
+@@ -140,11 +156,8 @@ struct userdata {
+     pa_droid_hw_module *hw_module;
+     pa_droid_card_data card_data;
+ 
+-    struct virtual_profile call_profile;
+-    struct virtual_profile comm_profile;
+-    struct virtual_profile ring_profile;
+     pa_droid_profile *old_profile;
+-
++    pa_source *voicecall_source;
+ #ifdef HAVE_UDEV
+     pa_droid_extcon *extcon;
+ #endif
+@@ -157,8 +170,50 @@ struct userdata {
+ 
+ struct profile_data {
+     pa_droid_profile *profile;
++    audio_mode_t mode;
++    bool virtual_profile;
++    /* Variables for virtual profiles: */
++    struct virtual_profile virtual;
+ };
+ 
++#ifdef DROID_AUDIO_HAL_USE_VSID
++
++/* From hal/voice_extn/voice_extn.c */
++#define AUDIO_PARAMETER_KEY_VSID            "vsid"
++#define AUDIO_PARAMETER_KEY_CALL_STATE      "call_state"
++
++/* From hal/voice_extn/voice_extn.c */
++#define VOICE2_VSID 0x10DC1000
++#define VOLTE_VSID  0x10C02000
++#define QCHAT_VSID  0x10803000
++#define VOWLAN_VSID 0x10002000
++
++/* From hal/voice.h */
++#define BASE_CALL_STATE     1
++#define CALL_INACTIVE       (BASE_CALL_STATE)
++#define CALL_ACTIVE         (BASE_CALL_STATE + 1)
++#define VOICE_VSID  0x10C01000
++
++/* For virtual profiles */
++#define VOICE_SESSION_VOICE1_PROFILE_NAME   "voicecall-voice1"
++#define VOICE_SESSION_VOICE1_PROFILE_DESC   "Call mode, default to voice 1 vsid"
++#define VOICE_SESSION_VOICE2_PROFILE_NAME   "voicecall-voice2"
++#define VOICE_SESSION_VOICE2_PROFILE_DESC   "Call mode, default to voice 2 vsid"
++#define VOICE_SESSION_VOLTE_PROFILE_NAME    "voicecall-volte"
++#define VOICE_SESSION_VOLTE_PROFILE_DESC    "Call mode, default to volte vsid"
++#define VOICE_SESSION_QCHAT_PROFILE_NAME    "voicecall-qchat"
++#define VOICE_SESSION_QCHAT_PROFILE_DESC    "Call mode, default to qchat vsid"
++#define VOICE_SESSION_VOWLAN_PROFILE_NAME   "voicecall-vowlan"
++#define VOICE_SESSION_VOWLAN_PROFILE_DESC   "Call mode, default to vowlan vsid"
++
++static bool voicecall_voice1_vsid_profile_event_cb(struct userdata *u, pa_droid_profile *p, bool enabling);
++static bool voicecall_voice2_vsid_profile_event_cb(struct userdata *u, pa_droid_profile *p, bool enabling);
++static bool voicecall_volte_vsid_profile_event_cb(struct userdata *u, pa_droid_profile *p, bool enabling);
++static bool voicecall_qchat_vsid_profile_event_cb(struct userdata *u, pa_droid_profile *p, bool enabling);
++static bool voicecall_vowlan_vsid_profile_event_cb(struct userdata *u, pa_droid_profile *p, bool enabling);
++
++#endif /* DROID_AUDIO_HAL_USE_VSID */
++
+ static void add_disabled_profile(pa_hashmap *profiles) {
+     pa_card_profile *cp;
+     struct profile_data *d;
+@@ -172,10 +227,13 @@ static void add_disabled_profile(pa_hash
+ }
+ 
+ /* Special profile for calls */
+-static pa_droid_profile* add_virtual_profile(struct userdata *u, const char *name, const char *description, pa_hashmap *profiles) {
++static pa_card_profile* add_virtual_profile(struct userdata *u, const char *name, const char *description,
++                                            audio_mode_t audio_mode, virtual_profile_event_cb event_cb,
++                                            pa_available_t available, pa_card_profile *extension_to,
++                                            pa_hashmap *profiles) {
+     pa_droid_profile *ap;
+     pa_card_profile *cp;
+-    struct profile_data *d;
++    struct profile_data *d, *ext;
+ 
+     pa_assert(u);
+     pa_assert(u->profile_set);
+@@ -191,28 +249,34 @@ static pa_droid_profile* add_virtual_pro
+     pa_hashmap_put(u->profile_set->profiles, ap->name, ap);
+ 
+     cp = pa_card_profile_new(ap->name, ap->description, sizeof(struct profile_data));
++    cp->available = available;
+     d = PA_CARD_PROFILE_DATA(cp);
+     d->profile = ap;
++    d->virtual_profile = true;
++    d->mode = audio_mode;
++    d->virtual.event_cb = event_cb;
++    d->virtual.extension = NULL;
++    if (extension_to) {
++        ext = PA_CARD_PROFILE_DATA(extension_to);
++        ext->virtual.extension = cp;
++        d->virtual.parent = extension_to;
++    } else
++        d->virtual.parent = NULL;
+ 
+     pa_hashmap_put(profiles, cp->name, cp);
+ 
+-    return ap;
++    return cp;
+ }
+ 
+-static void set_parameters_cb(pa_droid_card_data *card_data, const char *str) {
++static int set_parameters_cb(pa_droid_card_data *card_data, const char *str) {
+     struct userdata *u;
++    int ret;
+ 
+     pa_assert(card_data);
++    pa_assert_se((u = card_data->userdata));
+     pa_assert(str);
+ 
+-    u = card_data->userdata;
+-
+-    if (u) {
+-        pa_log_debug("Setting parameters: %s", str);
+-        pa_droid_hw_module_lock(u->hw_module);
+-        u->hw_module->device->set_parameters(u->hw_module->device, str);
+-        pa_droid_hw_module_unlock(u->hw_module);
+-    }
++    return pa_droid_set_parameters(u->hw_module, str);
+ }
+ 
+ static void set_card_name(pa_modargs *ma, pa_card_new_data *data, const char *module_id) {
+@@ -238,6 +302,9 @@ static void set_card_name(pa_modargs *ma
+ static void add_profile(struct userdata *u, pa_hashmap *h, pa_hashmap *ports, pa_droid_profile *ap) {
+     pa_card_profile *cp;
+     struct profile_data *d;
++    pa_droid_mapping *am;
++    int max_channels;
++    uint32_t idx;
+ 
+     pa_assert(u);
+     pa_assert(h);
+@@ -247,19 +314,31 @@ static void add_profile(struct userdata
+     pa_log_debug("Card profile %s", ap->name);
+ 
+     cp = pa_card_profile_new(ap->name, ap->description, sizeof(struct profile_data));
++    cp->available = PA_AVAILABLE_YES;
+     cp->priority = ap->priority;
+ 
+-    cp->n_sinks = 1;
+-    pa_droid_add_card_ports(cp, ports, ap->output, u->core);
+-    cp->max_sink_channels = popcount(ap->output->output->channel_masks);
+-    if (ap->input) {
+-        pa_droid_add_card_ports(cp, ports, ap->input, u->core);
+-        cp->n_sources = 1;
+-        cp->max_source_channels = popcount(ap->input->input->channel_masks);
++    max_channels = 0;
++    PA_IDXSET_FOREACH(am, ap->output_mappings, idx) {
++        cp->n_sinks++;
++        pa_droid_add_card_ports(cp, ports, am, u->core);
++        max_channels = popcount(am->output->channel_masks) > max_channels
++                        ? popcount(am->output->channel_masks) : max_channels;
++    }
++    cp->max_sink_channels = max_channels;
++
++    max_channels = 0;
++    PA_IDXSET_FOREACH(am, ap->input_mappings, idx) {
++        cp->n_sources++;
++        pa_droid_add_card_ports(cp, ports, am, u->core);
++        max_channels = popcount(am->input->channel_masks) > max_channels
++                        ? popcount(am->input->channel_masks) : max_channels;
+     }
++    cp->max_source_channels = max_channels;
+ 
+     d = PA_CARD_PROFILE_DATA(cp);
+     d->profile = ap;
++    d->virtual_profile = false;
++    d->mode = AUDIO_MODE_NORMAL;
+ 
+     pa_hashmap_put(h, cp->name, cp);
+ }
+@@ -280,6 +359,7 @@ static void add_profiles(struct userdata
+ static void init_profile(struct userdata *u) {
+     pa_droid_mapping *am;
+     struct profile_data *d;
++    uint32_t idx;
+ 
+     pa_assert(u);
+ 
+@@ -287,14 +367,16 @@ static void init_profile(struct userdata
+ 
+     d = PA_CARD_PROFILE_DATA(u->card->active_profile);
+ 
+-    if (d->profile && d->profile->output) {
+-        am = d->profile->output;
+-        am->sink = pa_droid_sink_new(u->module, u->modargs, __FILE__, &u->card_data, 0, am, u->card);
++    if (d->profile && pa_idxset_size(d->profile->output_mappings) > 0) {
++        PA_IDXSET_FOREACH(am, d->profile->output_mappings, idx) {
++            am->sink = pa_droid_sink_new(u->module, u->modargs, __FILE__, &u->card_data, 0, am, u->card);
++        }
+     }
+ 
+-    if (d->profile && d->profile->input) {
+-        am = d->profile->input;
+-        am->source = pa_droid_source_new(u->module, u->modargs, __FILE__, &u->card_data, am, u->card);
++    if (d->profile && pa_idxset_size(d->profile->input_mappings) > 0) {
++        PA_IDXSET_FOREACH(am, d->profile->input_mappings, idx) {
++            am->source = pa_droid_source_new(u->module, u->modargs, __FILE__, (audio_devices_t) 0, &u->card_data, am, u->card);
++        }
+     }
+ }
+ 
+@@ -332,77 +414,300 @@ static int set_mode(struct userdata *u,
+ }
+ 
+ static void park_profile(pa_droid_profile *dp) {
++    struct profile_data *data;
++    pa_droid_mapping *am;
++    uint32_t idx;
++
+     pa_assert(dp);
+ 
+-    if (dp->output && dp->output->sink)
+-        pa_sink_set_port(dp->output->sink, PA_DROID_OUTPUT_PARKING, false);
+-    if (dp->input && dp->input->source)
+-        pa_source_set_port(dp->input->source, PA_DROID_INPUT_PARKING, false);
++    /* Virtual profiles don't have output mappings. */
++    if (dp->output_mappings) {
++        PA_IDXSET_FOREACH(am, dp->output_mappings, idx) {
++            if (pa_droid_mapping_is_primary(am))
++                pa_sink_set_port(am->sink, PA_DROID_OUTPUT_PARKING, false);
++        }
++    };
++
++    /* Virtual profiles don't have input mappings. */
++    if (dp->input_mappings) {
++        PA_IDXSET_FOREACH(am, dp->input_mappings, idx) {
++            if (pa_droid_mapping_is_primary(am))
++                pa_source_set_port(am->source, PA_DROID_INPUT_PARKING, false);
++        }
++    };
++}
++
++static bool voicecall_profile_event_cb(struct userdata *u, pa_droid_profile *p, bool enabling) {
++    pa_card_profile *cp;
++    pa_droid_mapping *am_output, *am_input;
++
++    pa_assert(u);
++    pa_assert(p);
++    pa_assert(u->old_profile);
++
++    if (!(am_output = pa_droid_idxset_get_primary(u->old_profile->output_mappings))) {
++        pa_log("Active profile doesn't have primary output device.");
++        return false;
++    }
++
++    if (!(am_input = pa_droid_idxset_get_primary(u->old_profile->input_mappings)))
++        pa_log_warn("Active profile doesn't have primary input device.");
++
++    /* call mode specialities */
++    if (enabling) {
++        pa_droid_sink_set_voice_control(am_output->sink, true);
++        if (am_input && !u->voice_source_routing)
++            pa_droid_source_set_routing(am_input->source, false);
++        if (am_input && am_input->input->devices & AUDIO_DEVICE_IN_VOICE_CALL &&
++            (cp = pa_hashmap_get(u->card->profiles, VOICE_RECORD_PROFILE_NAME))) {
++            if (cp->available == PA_AVAILABLE_NO) {
++                pa_log_debug("Enable %s profile.", VOICE_RECORD_PROFILE_NAME);
++                pa_card_profile_set_available(cp, PA_AVAILABLE_YES);
++            }
++        }
++    } else {
++        pa_droid_sink_set_voice_control(am_output->sink, false);
++        if (am_input && !u->voice_source_routing)
++            pa_droid_source_set_routing(am_input->source, true);
++        if (am_input && am_input->input->devices & AUDIO_DEVICE_IN_VOICE_CALL &&
++            (cp = pa_hashmap_get(u->card->profiles, VOICE_RECORD_PROFILE_NAME))) {
++            if (cp->available == PA_AVAILABLE_YES) {
++                pa_log_debug("Disable %s profile.", VOICE_RECORD_PROFILE_NAME);
++                pa_card_profile_set_available(cp, PA_AVAILABLE_NO);
++            }
++        }
++    }
++
++    return true;
++}
++
++#if DROID_HAL == 1
++static bool voicecall_record_profile_event_cb(struct userdata *u, pa_droid_profile *p, bool enabling) {
++    pa_queue *source_outputs = NULL;
++    pa_droid_mapping *am;
++
++    pa_assert_ctl_context();
++    pa_assert(u);
++    pa_assert(p);
++    pa_assert(u->old_profile);
++
++    if (enabling) {
++        /* don't do anything if voicecall source has already been created. */
++        if (u->voicecall_source)
++            return true;
++
++        pa_log_info("Enabling voice call record.");
++
++        am = pa_droid_idxset_get_primary(u->old_profile->input_mappings);
++
++        if (am && am->source) {
++            source_outputs = pa_source_move_all_start(am->source, source_outputs);
++            pa_droid_source_free(am->source);
++            am->source = NULL;
++        }
++
++        u->voicecall_source = pa_droid_source_new(u->module, u->modargs, __FILE__, AUDIO_DEVICE_IN_VOICE_CALL, &u->card_data, am, u->card);
++        if (!u->voicecall_source)
++            pa_log("Failed to enable voice call recording.");
++
++        if (u->voicecall_source && source_outputs) {
++            pa_source_move_all_finish(u->voicecall_source, source_outputs, false);
++            source_outputs = NULL;
++        }
++
++    } else {
++        /* don't do anything if voicecall source has already been destroyed. */
++        if (!u->voicecall_source)
++            return true;
++
++        pa_log_info("Disabling voice call record.");
++
++        source_outputs = pa_source_move_all_start(u->voicecall_source, source_outputs);
++        pa_droid_source_free(u->voicecall_source);
++        u->voicecall_source = NULL;
++
++        am = pa_droid_idxset_get_primary(u->old_profile->input_mappings);
++
++        if (am && !am->source) {
++            am->source = pa_droid_source_new(u->module, u->modargs, __FILE__, (audio_devices_t) 0, &u->card_data, am, u->card);
++
++            if (source_outputs && am->source) {
++                pa_source_move_all_finish(am->source, source_outputs, false);
++                source_outputs = NULL;
++            }
++        }
++    }
++
++    if (source_outputs)
++        pa_source_move_all_fail(source_outputs);
++
++    return true;
++}
++
++#else
++
++static bool voicecall_record_profile_event_cb(struct userdata *u, pa_droid_profile *p, bool enabling) {
++    pa_droid_mapping *am;
++    pa_device_port *port;
++    const char *port_name;
++
++    pa_assert_ctl_context();
++    pa_assert(u);
++    pa_assert(p);
++    pa_assert(u->old_profile);
++
++    if (!(am = pa_droid_idxset_get_primary(u->old_profile->input_mappings))) {
++        pa_log("Active profile doesn't have primary input device. Cannot enable record profile.");
++        return false;
++    }
++
++    if (!am->source) {
++        pa_log("No active source, refusing to switch source port.");
++        return false;
++    }
++
++    pa_source_assert_ref(am->source);
++
++    pa_assert_se(pa_droid_input_port_name(enabling ? AUDIO_DEVICE_IN_VOICE_CALL : AUDIO_DEVICE_IN_DEFAULT,
++                                          &port_name));
++    pa_assert_se((port = pa_hashmap_get(am->source->ports, port_name)));
++
++    if (pa_droid_source_set_port(am->source, port) != 0)
++        return false;
++
++    pa_hook_fire(&u->core->hooks[PA_CORE_HOOK_SOURCE_PORT_CHANGED], am->source);
++
++    return true;
++}
++#endif
++
++#ifdef DROID_AUDIO_HAL_USE_VSID
++static bool voicecall_vsid(struct userdata *u, pa_droid_profile *p, uint32_t vsid, bool enabling)
++{
++    char *setparam;
++
++    voicecall_profile_event_cb(u, p, enabling);
++
++    setparam = pa_sprintf_malloc("%s=%u;%s=%d", AUDIO_PARAMETER_KEY_VSID, vsid,
++                                                AUDIO_PARAMETER_KEY_CALL_STATE,
++                                                enabling ? CALL_ACTIVE : CALL_INACTIVE);
++
++    pa_droid_set_parameters(u->hw_module, setparam);
++    pa_xfree(setparam);
++
++    return true;
++}
++
++static bool voicecall_voice1_vsid_profile_event_cb(struct userdata *u, pa_droid_profile *p, bool enabling)
++{
++    return voicecall_vsid(u, p, VOICE_VSID, enabling);
++}
++
++static bool voicecall_voice2_vsid_profile_event_cb(struct userdata *u, pa_droid_profile *p, bool enabling)
++{
++    return voicecall_vsid(u, p, VOICE2_VSID, enabling);
++}
++
++static bool voicecall_volte_vsid_profile_event_cb(struct userdata *u, pa_droid_profile *p, bool enabling)
++{
++    return voicecall_vsid(u, p, VOLTE_VSID, enabling);
++}
++
++static bool voicecall_qchat_vsid_profile_event_cb(struct userdata *u, pa_droid_profile *p, bool enabling)
++{
++    return voicecall_vsid(u, p, QCHAT_VSID, enabling);
++}
++
++static bool voicecall_vowlan_vsid_profile_event_cb(struct userdata *u, pa_droid_profile *p, bool enabling)
++{
++    return voicecall_vsid(u, p, VOWLAN_VSID, enabling);
++}
++#endif /* DROID_AUDIO_HAL_USE_VSID */
++
++static void leave_virtual_profile(struct userdata *u, pa_card *c, pa_card_profile *cp, pa_card_profile *new_profile) {
++    struct profile_data *pd, *parent;
++
++    pa_assert(u);
++    pa_assert(c);
++    pa_assert(cp);
++    pa_assert_se((pd = PA_CARD_PROFILE_DATA(cp)));
++
++    if (pd->virtual.parent)
++        pa_log_debug("Leaving extension %s.", cp->name);
++
++    /* First run event for old virtual profile, unless new profile is extension
++     * to the old profile. */
++    if (pd->virtual.extension) {
++        if (pd->virtual.extension != new_profile && pd->virtual.event_cb)
++            pd->virtual.event_cb(u, pd->profile, false);
++    } else {
++        if (pd->virtual.event_cb)
++            pd->virtual.event_cb(u, pd->profile, false);
++    }
++
++    /* If old virtual profile was extension, and new profile is not extensions parent, run event
++     * for extension's parent as well. */
++    if (pd->virtual.parent != new_profile && pd->virtual.parent) {
++        parent = PA_CARD_PROFILE_DATA(pd->virtual.parent);
++
++        if (parent->virtual.event_cb)
++            parent->virtual.event_cb(u, parent->profile, false);
++    }
+ }
+ 
+ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
+     struct userdata *u;
+     pa_droid_mapping *am;
+-    struct virtual_profile *new_vp = NULL;
+-    struct virtual_profile *old_vp = NULL;
+     struct profile_data *nd, *od;
+     pa_queue *sink_inputs = NULL, *source_outputs = NULL;
++    uint32_t idx;
+ 
+     pa_assert(c);
+     pa_assert(new_profile);
+     pa_assert_se(u = c->userdata);
+ 
++    if (new_profile->available != PA_AVAILABLE_YES) {
++        pa_log("Profile %s is not available.", new_profile->name);
++        return -1;
++    }
++
+     nd = PA_CARD_PROFILE_DATA(new_profile);
+     od = PA_CARD_PROFILE_DATA(c->active_profile);
+ 
+-    if (nd->profile == u->call_profile.profile)
+-        new_vp = &u->call_profile;
+-    if (nd->profile == u->ring_profile.profile)
+-        new_vp = &u->ring_profile;
+-    if (nd->profile == u->comm_profile.profile)
+-        new_vp = &u->comm_profile;
+-
+-    if (new_vp) {
+-        pa_log_debug("Setting new virtual profile.");
+-        if (u->old_profile == NULL)
++    if (nd->virtual_profile) {
++        /* old_profile should always be real profile. */
++        if (u->old_profile == NULL) {
++            pa_assert(!od->virtual_profile);
+             u->old_profile = od->profile;
++        }
++
++        if (od->virtual_profile)
++            leave_virtual_profile(u, c, c->active_profile, new_profile);
+ 
+         park_profile(od->profile);
+ 
+-        set_mode(u, new_vp->mode);
++        if (nd->mode != od->mode)
++            set_mode(u, nd->mode);
++
++        if (od->virtual.parent != new_profile && nd->virtual.event_cb)
++            nd->virtual.event_cb(u, nd->profile, true);
+ 
+-        /* call mode specialities */
+-        if (new_vp->profile == u->call_profile.profile) {
+-            pa_droid_sink_set_voice_control(u->old_profile->output->sink, true);
+-            if (!u->voice_source_routing)
+-                pa_droid_source_set_routing(u->old_profile->input->source, false);
+-        }
+         return 0;
+     }
+ 
+-    if (od->profile == u->call_profile.profile)
+-        old_vp = &u->call_profile;
+-    if (od->profile == u->ring_profile.profile)
+-        old_vp = &u->ring_profile;
+-    if (od->profile == u->comm_profile.profile)
+-        old_vp = &u->comm_profile;
+-
+-    if (old_vp) {
++    if (od->virtual_profile) {
+         pa_assert(u->old_profile);
+ 
+-        park_profile(nd->profile);
++        if (od->virtual_profile)
++            leave_virtual_profile(u, c, c->active_profile, NULL);
+ 
+-        set_mode(u, AUDIO_MODE_NORMAL);
++        park_profile(nd->profile);
+ 
+-        /* call mode specialities */
+-        if (old_vp->profile == u->call_profile.profile) {
+-            pa_droid_sink_set_voice_control(u->old_profile->output->sink, false);
+-            if (!u->voice_source_routing)
+-                pa_droid_source_set_routing(u->old_profile->input->source, true);
+-        }
++        if (nd->mode != od->mode)
++            set_mode(u, nd->mode);
+ 
+         /* If new profile is the same as from which we switched to
+-         * call profile, transfer ownership back to that profile.
++         * virtual profile, transfer ownership back to that profile.
+          * Otherwise destroy sinks & sources and switch to new profile. */
+         if (nd->profile == u->old_profile) {
+             u->old_profile = NULL;
+@@ -411,66 +716,64 @@ static int card_set_profile(pa_card *c,
+             od->profile = u->old_profile;
+             u->old_profile = NULL;
+ 
+-            /* Continue to sink-input transfer below */
++            /* Continue to sink-input/source-output transfer below. */
+         }
+     }
+ 
+     /* If there are connected sink inputs/source outputs in old profile's sinks/sources move
+      * them all to new sinks/sources. */
+ 
+-    if (od->profile && od->profile->output) {
+-        do {
+-            am = od->profile->output;
+-
++    if (od->profile && pa_idxset_size(od->profile->output_mappings) > 0) {
++        PA_IDXSET_FOREACH(am, od->profile->output_mappings, idx) {
+             if (!am->sink)
+                 continue;
+ 
+-            if (nd->profile && nd->profile->output && am == nd->profile->output)
++            if (nd->profile &&
++                pa_idxset_get_by_data(nd->profile->output_mappings, am, NULL))
+                 continue;
+ 
+             sink_inputs = pa_sink_move_all_start(am->sink, sink_inputs);
+             pa_droid_sink_free(am->sink);
+             am->sink = NULL;
+-        } while(0);
++        }
+     }
+ 
+-    if (od->profile && od->profile->input) {
+-        do {
+-            am = od->profile->input;
+-
++    if (od->profile && pa_idxset_size(od->profile->input_mappings) > 0) {
++        PA_IDXSET_FOREACH(am, od->profile->input_mappings, idx) {
+             if (!am->source)
+                 continue;
+ 
+-            if (nd->profile && nd->profile->input && am == nd->profile->input)
++            if (nd->profile &&
++                pa_idxset_get_by_data(nd->profile->input_mappings, am, NULL))
+                 continue;
+ 
+             source_outputs = pa_source_move_all_start(am->source, source_outputs);
+             pa_droid_source_free(am->source);
+             am->source = NULL;
+-        } while(0);
++        }
+     }
+ 
+-    if (nd->profile && nd->profile->output) {
+-        am = nd->profile->output;
+-
+-        if (!am->sink)
+-            am->sink = pa_droid_sink_new(u->module, u->modargs, __FILE__, &u->card_data, 0, am, u->card);
++    if (nd->profile && pa_idxset_size(nd->profile->output_mappings) > 0) {
++        PA_IDXSET_FOREACH(am, nd->profile->output_mappings, idx) {
++            if (!am->sink)
++                am->sink = pa_droid_sink_new(u->module, u->modargs, __FILE__, &u->card_data, 0, am, u->card);
+ 
+-        if (sink_inputs && am->sink) {
+-            pa_sink_move_all_finish(am->sink, sink_inputs, false);
+-            sink_inputs = NULL;
++            if (sink_inputs && am->sink) {
++                pa_sink_move_all_finish(am->sink, sink_inputs, false);
++                sink_inputs = NULL;
++            }
+         }
+     }
+ 
+-    if (nd->profile && nd->profile->input) {
+-        am = nd->profile->input;
+-
+-        if (!am->source)
+-            am->source = pa_droid_source_new(u->module, u->modargs, __FILE__, &u->card_data, am, u->card);
++    if (nd->profile && pa_idxset_size(nd->profile->input_mappings) > 0) {
++        PA_IDXSET_FOREACH(am, nd->profile->input_mappings, idx) {
++            if (!am->source)
++                am->source = pa_droid_source_new(u->module, u->modargs, __FILE__, (audio_devices_t) 0, &u->card_data, am, u->card);
+ 
+-        if (source_outputs && am->source) {
+-            pa_source_move_all_finish(am->source, source_outputs, false);
+-            source_outputs = NULL;
++            if (source_outputs && am->source) {
++                pa_source_move_all_finish(am->source, source_outputs, false);
++                source_outputs = NULL;
++            }
+         }
+     }
+ 
+@@ -491,14 +794,18 @@ int pa__init(pa_module *m) {
+     const char *module_id;
+     bool namereg_fail = false;
+     bool voice_source_routing = false;
++    pa_card_profile *virtual;
++    const char *combine;
+ 
+     pa_assert(m);
+ 
+     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+-        pa_log("Failed to parse module argumets.");
++        pa_log("Failed to parse module arguments.");
+         goto fail;
+     }
+ 
++    combine = pa_modargs_get_value(ma, "combine", NULL);
++
+     struct userdata *u = pa_xnew0(struct userdata, 1);
+     u->core = m->core;
+ 
+@@ -521,7 +828,19 @@ int pa__init(pa_module *m) {
+     u->card_data.module_id = pa_xstrdup(module_id);
+     u->card_data.userdata = u;
+ 
+-    u->profile_set = pa_droid_profile_set_new(u->hw_module->enabled_module);
++    if (combine) {
++        char *tmp;
++        pa_strlist *o = NULL;
++
++        tmp = pa_replace(combine, ",", " ");
++        o = pa_strlist_parse(tmp);
++
++        u->profile_set = pa_droid_profile_set_combined_new(u->hw_module->enabled_module,
++                                                           o, NULL);
++        pa_strlist_free(o);
++        pa_xfree(tmp);
++    } else
++        u->profile_set = pa_droid_profile_set_new(u->hw_module->enabled_module);
+ 
+     pa_card_new_data_init(&data);
+     data.driver = __FILE__;
+@@ -549,15 +868,37 @@ int pa__init(pa_module *m) {
+         goto fail;
+     }
+ 
+-    u->call_profile.profile = add_virtual_profile(u, VOICE_CALL_PROFILE_NAME,
+-                                                  VOICE_CALL_PROFILE_DESC, data.profiles);
+-    u->call_profile.mode = AUDIO_MODE_IN_CALL;
+-    u->comm_profile.profile = add_virtual_profile(u, COMMUNICATION_PROFILE_NAME,
+-                                                  COMMUNICATION_PROFILE_DESC, data.profiles);
+-    u->comm_profile.mode = AUDIO_MODE_IN_COMMUNICATION;
+-    u->ring_profile.profile = add_virtual_profile(u, RINGTONE_PROFILE_NAME,
+-                                                  RINGTONE_PROFILE_DESC, data.profiles);
+-    u->ring_profile.mode = AUDIO_MODE_RINGTONE;
++    virtual =
++    add_virtual_profile(u, VOICE_CALL_PROFILE_NAME, VOICE_CALL_PROFILE_DESC,
++                        AUDIO_MODE_IN_CALL, voicecall_profile_event_cb,
++                        PA_AVAILABLE_YES, NULL, data.profiles);
++    add_virtual_profile(u, VOICE_RECORD_PROFILE_NAME, VOICE_RECORD_PROFILE_DESC,
++                        AUDIO_MODE_IN_CALL, voicecall_record_profile_event_cb,
++                        PA_AVAILABLE_NO, virtual, data.profiles);
++    add_virtual_profile(u, COMMUNICATION_PROFILE_NAME, COMMUNICATION_PROFILE_DESC,
++                        AUDIO_MODE_IN_COMMUNICATION, NULL,
++                        PA_AVAILABLE_YES, NULL, data.profiles);
++    add_virtual_profile(u, RINGTONE_PROFILE_NAME, RINGTONE_PROFILE_DESC,
++                        AUDIO_MODE_RINGTONE, NULL,
++                        PA_AVAILABLE_YES, NULL, data.profiles);
++#ifdef DROID_AUDIO_HAL_USE_VSID
++    add_virtual_profile(u, VOICE_SESSION_VOICE1_PROFILE_NAME, VOICE_SESSION_VOICE1_PROFILE_DESC,
++                        AUDIO_MODE_IN_CALL, voicecall_voice1_vsid_profile_event_cb,
++                        PA_AVAILABLE_YES, NULL, data.profiles);
++    add_virtual_profile(u, VOICE_SESSION_VOICE2_PROFILE_NAME, VOICE_SESSION_VOICE2_PROFILE_DESC,
++                        AUDIO_MODE_IN_CALL, voicecall_voice2_vsid_profile_event_cb,
++                        PA_AVAILABLE_YES, NULL, data.profiles);
++    /* TODO: Probably enabled state needs to be determined dynamically for VOLTE and friends. */
++    add_virtual_profile(u, VOICE_SESSION_VOLTE_PROFILE_NAME, VOICE_SESSION_VOLTE_PROFILE_DESC,
++                        AUDIO_MODE_IN_CALL, voicecall_volte_vsid_profile_event_cb,
++                        PA_AVAILABLE_YES, NULL, data.profiles);
++    add_virtual_profile(u, VOICE_SESSION_QCHAT_PROFILE_NAME, VOICE_SESSION_QCHAT_PROFILE_DESC,
++                        AUDIO_MODE_IN_CALL, voicecall_qchat_vsid_profile_event_cb,
++                        PA_AVAILABLE_YES, NULL, data.profiles);
++    add_virtual_profile(u, VOICE_SESSION_VOWLAN_PROFILE_NAME, VOICE_SESSION_VOWLAN_PROFILE_DESC,
++                        AUDIO_MODE_IN_CALL, voicecall_vowlan_vsid_profile_event_cb,
++                        PA_AVAILABLE_YES, NULL, data.profiles);
++#endif /* DROID_AUDIO_HAL_USE_VSID */
+ 
+     add_disabled_profile(data.profiles);
+ 
+Index: overlay/src/modules/droid/module-droid-discover.c
+===================================================================
+--- /dev/null
++++ overlay/src/modules/droid/module-droid-discover.c
+@@ -0,0 +1,95 @@
++/***
++  This file is part of PulseAudio.
++
++  Copyright 2016 Simon Fels <simon.fels at canonical.com>
++
++  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, see <http://www.gnu.org/licenses/>.
++***/
++
++#ifdef HAVE_CONFIG_H
++#include <config.h>
++#endif
++
++#include <pulsecore/core-util.h>
++#include <pulsecore/macro.h>
++#include <pulsecore/module.h>
++
++#include <hybris/properties/properties.h>
++
++#include "module-droid-discover-symdef.h"
++
++PA_MODULE_AUTHOR("Simon Fels");
++PA_MODULE_DESCRIPTION("Detect on which Android version we're running and load correct implementation for it");
++PA_MODULE_VERSION(PACKAGE_VERSION);
++PA_MODULE_LOAD_ONCE(true);
++
++#define ANDROID_PROPERTY_VERSION_RELEASE "ro.build.version.release"
++
++#define MODULE_DROID_CARD_PREFIX "module-droid-card-"
++
++struct userdata {
++    uint32_t module_idx;
++};
++
++int pa__init(pa_module* m) {
++    struct userdata *u;
++    pa_module *mm;
++    char version[50];
++    char *module_name = NULL;
++
++    pa_assert(m);
++
++    if (property_get(ANDROID_PROPERTY_VERSION_RELEASE, version, "") < 0 || !version) {
++        pa_log("Failed to determine Android platform version we're running on");
++        return -1;
++    }
++
++    m->userdata = u = pa_xnew0(struct userdata, 1);
++
++	if (!strncmp(version, "5.1", 3) && pa_module_exists(MODULE_DROID_CARD_PREFIX "22"))
++		module_name = MODULE_DROID_CARD_PREFIX "22";
++	else if (!strncmp(version, "4.4", 3) && pa_module_exists(MODULE_DROID_CARD_PREFIX "19"))
++		module_name = MODULE_DROID_CARD_PREFIX "19";
++    else {
++        pa_log("Unsupported Android version %s", version);
++        pa_xfree(u);
++        return -1;
++    }
++
++    mm = pa_module_load(m->core, module_name, m->argument);
++    if (mm)
++        u->module_idx = mm->index;
++
++    if (u->module_idx == PA_INVALID_INDEX) {
++        pa_xfree(u);
++        pa_log("Failed to load droid card module for Android version %s", version);
++        return -1;
++    }
++
++    return 0;
++}
++
++void pa__done(pa_module* m) {
++    struct userdata *u;
++
++    pa_assert(m);
++
++    if (!(u = m->userdata))
++        return;
++
++    if (u->module_idx != PA_INVALID_INDEX)
++        pa_module_unload_by_index(m->core, u->module_idx, true);
++
++    pa_xfree(u);
++}
+Index: overlay/src/modules/droid/module-droid-sink.c
+===================================================================
+--- overlay.orig/src/modules/droid/module-droid-sink.c
++++ overlay/src/modules/droid/module-droid-sink.c
+@@ -40,30 +40,36 @@
+ #include "droid-util.h"
+ #include "droid-sink.h"
+ 
+-#include "module-droid-sink-symdef.h"
++#if ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 2
++#include "module-droid-sink-19-symdef.h"
++#elif ANDROID_VERSION_MAJOR == 5 && ANDROID_VERSION_MINOR == 1
++#include "module-droid-sink-22-symdef.h"
++#endif
+ 
+ PA_MODULE_AUTHOR("Juho Hämäläinen");
+ PA_MODULE_DESCRIPTION("Droid sink");
+ PA_MODULE_USAGE("master_sink=<sink to connect to> "
+-                "sink_name=<name of created sink> "
+-                "sco_fake_sink=<name of the fake sco sink used for hsp>");
++                "sink_name=<name of created sink>");
+ PA_MODULE_VERSION(PACKAGE_VERSION);
+ 
+ static const char* const valid_modargs[] = {
+     "rate",
++    "format",
++    "channels",
++    "channel_map",
++    "sink_rate",
++    "sink_format",
++    "sink_channel_map",
+     "flags",
+-    "devices",
++    "output_devices",
+     "sink_name",
+     "module_id",
+     "mute_routing_before",
+     "mute_routing_after",
+     "sink_buffer",
+     "deferred_volume",
+-    "voice_volume_call_mode",
+     "voice_property_key",
+     "voice_property_value",
+-    "voice_virtual_stream",
+-    "sco_fake_sink",
+     NULL,
+ };
+ 
+@@ -78,15 +84,24 @@ void pa__done(pa_module *m) {
+ 
+ int pa__init(pa_module *m) {
+     pa_modargs *ma = NULL;
++    const char *flags_str;
++    audio_output_flags_t flags = 0;
+ 
+     pa_assert(m);
+ 
+     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+-        pa_log("Failed to parse module argumets.");
++        pa_log("Failed to parse module arguments.");
+         goto fail;
+     }
+ 
+-    if (!(m->userdata = pa_droid_sink_new(m, ma, __FILE__, NULL, 0, NULL, NULL)))
++    if ((flags_str = pa_modargs_get_value(ma, "flags", NULL))) {
++        if (!pa_string_convert_flag_str_to_num(flags_str, &flags)) {
++            pa_log("Failed to parse flags");
++            goto fail;
++        }
++    }
++
++    if (!(m->userdata = pa_droid_sink_new(m, ma, __FILE__, NULL, flags, NULL, NULL)))
+         goto fail;
+ 
+     pa_modargs_free(ma);
+Index: overlay/src/modules/droid/module-droid-source.c
+===================================================================
+--- overlay.orig/src/modules/droid/module-droid-source.c
++++ overlay/src/modules/droid/module-droid-source.c
+@@ -40,7 +40,11 @@
+ #include "droid-util.h"
+ #include "droid-source.h"
+ 
+-#include "module-droid-source-symdef.h"
++#if ANDROID_VERSION_MAJOR == 4 && ANDROID_VERSION_MINOR == 2
++#include "module-droid-source-19-symdef.h"
++#elif ANDROID_VERSION_MAJOR == 5 && ANDROID_VERSION_MINOR == 1
++#include "module-droid-source-22-symdef.h"
++#endif
+ 
+ PA_MODULE_AUTHOR("Juho Hämäläinen");
+ PA_MODULE_DESCRIPTION("Droid source");
+@@ -50,8 +54,14 @@ PA_MODULE_VERSION(PACKAGE_VERSION);
+ 
+ static const char* const valid_modargs[] = {
+     "rate",
++    "format",
++    "channels",
++    "channel_map",
++    "source_rate",
++    "source_format",
++    "source_channel_map",
+     "flags",
+-    "devices",
++    "input_devices",
+     "source_name",
+     "module_id",
+     "source_buffer",
+@@ -74,11 +84,11 @@ int pa__init(pa_module *m) {
+     pa_assert(m);
+ 
+     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+-        pa_log("Failed to parse module argumets.");
++        pa_log("Failed to parse module arguments.");
+         goto fail;
+     }
+ 
+-    if (!(m->userdata = pa_droid_source_new(m, ma, __FILE__, NULL, NULL, NULL)))
++    if (!(m->userdata = pa_droid_source_new(m, ma, __FILE__, (audio_devices_t) 0, NULL, NULL, NULL)))
+         goto fail;
+ 
+     pa_modargs_free(ma);
+Index: overlay/src/modules/module-device-restore.c
+===================================================================
+--- overlay.orig/src/modules/module-device-restore.c
++++ overlay/src/modules/module-device-restore.c
+@@ -824,6 +824,7 @@ static pa_hook_result_t sink_port_hook_c
+     pa_assert(u);
+     pa_assert(u->restore_volume || u->restore_muted);
+ 
++#define MODULE_DEVICE_RESTORE_SKIP_PROPERTY "module-device-restore.skip"
+     if (pa_proplist_gets(sink->proplist, MODULE_DEVICE_RESTORE_SKIP_PROPERTY))
+         return PA_HOOK_OK;
+ 
diff --git a/debian/patches/0601-droid-alternative-hw-module-id.patch b/debian/patches/0601-droid-alternative-hw-module-id.patch
new file mode 100644
index 0000000..9a68453
--- /dev/null
+++ b/debian/patches/0601-droid-alternative-hw-module-id.patch
@@ -0,0 +1,34 @@
+Index: pulseaudio/src/modules/droid/droid-util.c
+===================================================================
+--- pulseaudio.orig/src/modules/droid/droid-util.c
++++ pulseaudio/src/modules/droid/droid-util.c
+@@ -1358,8 +1358,13 @@ static pa_droid_hw_module *droid_hw_modu
+ 
+     hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, module->name, (const hw_module_t**) &hwmod);
+     if (!hwmod) {
+-        pa_log("Failed to get hw module %s.", module->name);
+-        goto fail;
++        /* alternative hardware module id for MTK. */
++        pa_log("Failed to get hw module id: %s name: %s, trying alternative.", AUDIO_HARDWARE_MODULE_ID, module->name);
++        hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID2, module->name, (const hw_module_t**) &hwmod);
++        if (!hwmod) {
++            pa_log("Failed to get hw module id: %s name: %s.", AUDIO_HARDWARE_MODULE_ID2, module->name);
++            goto fail;
++        }
+     }
+ 
+     pa_log_info("Loaded hw module %s (HAL %d.%d.%d)", module->name,
+Index: pulseaudio/src/modules/droid/droid-util.h
+===================================================================
+--- pulseaudio.orig/src/modules/droid/droid-util.h
++++ pulseaudio/src/modules/droid/droid-util.h
+@@ -54,6 +54,9 @@
+ 
+ #define PA_DROID_PRIMARY_DEVICE     "primary"
+ 
++/* Workaround for MTK */
++#define AUDIO_HARDWARE_MODULE_ID2 "libaudio"
++
+ typedef struct pa_droid_hw_module pa_droid_hw_module;
+ typedef struct pa_droid_stream pa_droid_stream;
+ typedef struct pa_droid_card_data pa_droid_card_data;
diff --git a/debian/patches/0602-droid-inputstream-config-parameters.pach b/debian/patches/0602-droid-inputstream-config-parameters.pach
new file mode 100644
index 0000000..55fdc05
--- /dev/null
+++ b/debian/patches/0602-droid-inputstream-config-parameters.pach
@@ -0,0 +1,75 @@
+Index: pulseaudio/src/modules/droid/droid-util.c
+===================================================================
+--- pulseaudio.orig/src/modules/droid/droid-util.c
++++ pulseaudio/src/modules/droid/droid-util.c
+@@ -1134,7 +1158,6 @@ static void add_i_port(pa_droid_mapping
+ static void add_i_ports(pa_droid_mapping *am) {
+     pa_droid_port *p;
+     const char *name;
+-    char *desc;
+     uint32_t devices;
+     uint32_t i = 0;
+ 
+@@ -1697,11 +1723,27 @@ pa_droid_stream *pa_droid_open_input_str
+                                             &config_in,
+                                             &stream
+ #if DROID_HAL >= 3
+-                                                  , AUDIO_INPUT_FLAG_NONE   /* Default to no input flags */
+-                                                  , NULL                    /* Don't define address */
+-                                                  , AUDIO_SOURCE_DEFAULT    /* Default audio source */
++                                            , AUDIO_INPUT_FLAG_NONE   /* Default to no input flags */
++                                            , NULL                    /* Don't define address */
++                                            , AUDIO_SOURCE_DEFAULT    /* Default audio source */
+ #endif
+                                                   );
++    /* On some devices the first call will fail if the config parameters are
++     * not supported, but it'll automatically set the right ones, expecting
++     * the caller to call it again, so let's try at least one more time */
++    if (!stream)
++        ret = module->device->open_input_stream(module->device,
++                                                module->stream_in_id++,
++                                                devices,
++                                                &config_in,
++                                                &stream
++#if DROID_HAL >= 3
++                                                , AUDIO_INPUT_FLAG_NONE   /* Default to no input flags */
++                                                , NULL                    /* Don't define address */
++                                                , AUDIO_SOURCE_DEFAULT    /* Default audio source */
++#endif
++                );
++
+     pa_droid_hw_module_unlock(module);
+ 
+     if (ret < 0 || !stream) {
+@@ -1715,6 +1757,22 @@ pa_droid_stream *pa_droid_open_input_str
+     s->channel_map = channel_map;
+     s->flags = 0;
+ 
++    if (s->in->common.get_channels(&s->in->common) != hal_channel_mask) {
++        pa_log_warn("Requested channel mask %u but got %u instead.", hal_channel_mask, config_in.channel_mask);
++        /* convert the channel mask back to pa_channel_map 
++         * some device support only mono channel.
++         * */
++        pa_channel_position_t c;
++        if (pa_convert_input_channel(config_in.channel_mask, CONV_FROM_HAL, &c) && (c == PA_CHANNEL_POSITION_MONO)) {
++            s->channel_map.channels = 1;
++            s->channel_map.map[0] = PA_CHANNEL_POSITION_MONO;
++            s->sample_spec.channels = 1 ;
++        } else {
++            pa_log("Failed to convert channel map.");
++            goto fail;
++        }
++    }
++
+     if ((s->sample_spec.rate = s->in->common.get_sample_rate(&s->in->common)) != spec->rate)
+         pa_log_warn("Requested sample rate %u but got %u instead.", spec->rate, s->sample_spec.rate);
+ 
+@@ -1727,7 +1785,7 @@ pa_droid_stream *pa_droid_open_input_str
+             devices,
+             s->flags,
+             s->sample_spec.rate,
+-            s->sample_spec.channels, hal_channel_mask,
++            s->sample_spec.channels, config_in.channel_mask,
+             s->sample_spec.format, hal_audio_format,
+             buffer_size,
+             pa_bytes_to_usec(buffer_size, &s->sample_spec));
diff --git a/debian/patches/0603-droid-port-priority-and-availability.patch b/debian/patches/0603-droid-port-priority-and-availability.patch
new file mode 100644
index 0000000..9a03636
--- /dev/null
+++ b/debian/patches/0603-droid-port-priority-and-availability.patch
@@ -0,0 +1,103 @@
+Index: pulseaudio/src/modules/droid/droid-util.c
+===================================================================
+--- pulseaudio.orig/src/modules/droid/droid-util.c
++++ pulseaudio/src/modules/droid/droid-util.c
+@@ -110,6 +110,20 @@ static bool string_convert_str_to_num(co
+     return false;
+ }
+ 
++static bool check_port_availability(const char *port) {
++    pa_assert(port);
++
++    pa_log_debug("Checking availability for port '%s'", port);
++
++    for (unsigned int i = 0; port_availability[i]; i++) {
++        if (pa_streq(port_availability[i], port)) {
++            return true;
++        }
++    }
++
++    return false;
++}
++
+ static char *list_string(struct string_conversion *list, uint32_t flags) {
+     char *str = NULL;
+     char *tmp;
+@@ -1035,6 +1049,9 @@ static pa_droid_port *create_o_port(pa_d
+     if (am->profile_set->config->global_config.default_output_device & device)
+         p->priority += DEFAULT_PRIORITY;
+ 
++    if (check_port_availability(p->name))
++        p->priority += (DEFAULT_PRIORITY * 3);
++
+     return p;
+ }
+ 
+@@ -1124,6 +1141,13 @@ static void add_i_port(pa_droid_mapping
+         if (am->profile_set->config->global_config.attached_input_devices & device)
+             p->priority += DEFAULT_PRIORITY;
+ 
++        /* Make builtin mic the default input device */
++        if (device == AUDIO_DEVICE_IN_BUILTIN_MIC)
++            p->priority += DEFAULT_PRIORITY;
++
++        if (check_port_availability(p->name))
++            p->priority += (DEFAULT_PRIORITY * 3);
++
+         pa_hashmap_put(am->profile_set->all_ports, p->name, p);
+     } else
+         pa_log_debug("  Input port %s from cache", name);
+@@ -1297,6 +1321,9 @@ static int add_ports(pa_core *core, pa_c
+         } else
+             pa_log_debug("  Port %s from cache", p->name);
+ 
++        /* If port/jack detection is available, start as not available by default */
++        dp->available = check_port_availability(p->name) ? PA_AVAILABLE_NO : PA_AVAILABLE_UNKNOWN;
++
+         if (cp) {
+             if (!pa_hashmap_get(dp->profiles, cp->name))
+                 pa_hashmap_put(dp->profiles, cp->name, cp);
+@@ -1749,6 +1776,9 @@ pa_droid_stream *pa_droid_open_input_str
+     if ((s->sample_spec.rate = s->in->common.get_sample_rate(&s->in->common)) != spec->rate)
+         pa_log_warn("Requested sample rate %u but got %u instead.", spec->rate, s->sample_spec.rate);
+ 
++    if (s->sample_spec.channels != spec->channels)
++        pa_log_warn("Requested chennel %u but got %u instead.", spec->channels, s->sample_spec.channels);
++
+     pa_idxset_put(module->inputs, s, NULL);
+ 
+     buffer_size = s->in->common.get_buffer_size(&s->in->common);
+Index: pulseaudio/src/modules/droid/droid-util-44.h
+===================================================================
+--- pulseaudio.orig/src/modules/droid/droid-util-44.h
++++ pulseaudio/src/modules/droid/droid-util-44.h
+@@ -346,4 +346,12 @@ struct string_conversion string_conversi
+ };
+ #undef STRING_ENTRY
+ 
++/* Ports with availability option (for port/jack detection) */
++static const char* port_availability[] = {
++    "output-wired_headset",
++    "output-wired_headphone",
++    "input-wired_headset",
++    NULL
++};
++
+ #endif
+Index: pulseaudio/src/modules/droid/droid-util-51.h
+===================================================================
+--- pulseaudio.orig/src/modules/droid/droid-util-51.h
++++ pulseaudio/src/modules/droid/droid-util-51.h
+@@ -402,4 +402,12 @@ struct string_conversion string_conversi
+ };
+ #undef STRING_ENTRY
+ 
++/* Ports with availability option (for port/jack detection) */
++static const char* port_availability[] = {
++    "output-wired_headset",
++    "output-wired_headphone",
++    "input-wired_headset",
++    NULL
++};
++
+ #endif
diff --git a/debian/patches/series b/debian/patches/series
index b7e614a..d852ed9 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -39,3 +39,9 @@
 0508-bluetooth-bluez5-add-guards-to-prevent-HFP-and-HSP-c.patch
 0509-bluetooth-bluez5-don-t-reactivate-default-profile-wh.patch
 0510-Further-fixes-for-HFP-A2DP-with-BlueZ-5.x.patch
+
+# Ubuntu touch: add support for Android 5.x
+0600-droid-sync-with-upstream-for-Android-5-support-and-b.patch
+0601-droid-alternative-hw-module-id.patch
+0602-droid-inputstream-config-parameters.pach
+0603-droid-port-priority-and-availability.patch
diff --git a/debian/pulseaudio-module-droid.install b/debian/pulseaudio-module-droid.install
index 1ccb2d6..7bf8e41 100644
--- a/debian/pulseaudio-module-droid.install
+++ b/debian/pulseaudio-module-droid.install
@@ -1,6 +1,7 @@
-usr/lib/pulse-*/modules/libdroid-util.so
-usr/lib/pulse-*/modules/libdroid-sink.so
-usr/lib/pulse-*/modules/libdroid-source.so
-usr/lib/pulse-*/modules/module-droid-sink.so
-usr/lib/pulse-*/modules/module-droid-source.so
-usr/lib/pulse-*/modules/module-droid-card.so
+usr/lib/pulse-*/modules/libdroid-util-*.so
+usr/lib/pulse-*/modules/libdroid-sink-*.so
+usr/lib/pulse-*/modules/libdroid-source-*.so
+usr/lib/pulse-*/modules/module-droid-sink-*.so
+usr/lib/pulse-*/modules/module-droid-source-*.so
+usr/lib/pulse-*/modules/module-droid-card-*.so
+usr/lib/pulse-*/modules/module-droid-discover.so

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-pulseaudio/pulseaudio.git



More information about the pkg-pulseaudio-devel mailing list