audio: derive sound device for concurrent playback usecases

When a new playback usecase has to be started, existing playback
usecases might have to be re-routed (or not) to the new device
based on usecase requirements or h/w limitations
(e.g headphones and speaker sharing the same backend).

This change addresses this requirement by deriving the
new device based on pre-defined cases.

Bug: 31671778
Change-Id: Ic0fd4e8d2c1119e7198dc5bb5e5a51817f0110c1
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index 8585a76..21ce868 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -619,7 +619,9 @@
             ALOGE("%s: spkr_start_processing failed", __func__);
             goto on_error;
         }
-    } else if (platform_can_split_snd_device(snd_device, &num_devices, new_snd_devices)) {
+    } else if (platform_can_split_snd_device(snd_device,
+                                             &num_devices,
+                                             new_snd_devices) == 0) {
         for (i = 0; i < num_devices; i++) {
             enable_snd_device(adev, new_snd_devices[i]);
         }
@@ -663,7 +665,9 @@
             snd_device == SND_DEVICE_OUT_VOICE_SPEAKER) &&
             audio_extn_spkr_prot_is_enabled()) {
             audio_extn_spkr_prot_stop_processing(snd_device);
-        } else if (platform_can_split_snd_device(snd_device, &num_devices, new_snd_devices)) {
+        } else if (platform_can_split_snd_device(snd_device,
+                                                 &num_devices,
+                                                 new_snd_devices) == 0) {
             for (i = 0; i < num_devices; i++) {
                 disable_snd_device(adev, new_snd_devices[i]);
             }
@@ -685,6 +689,114 @@
     return 0;
 }
 
+/*
+  legend:
+  uc - existing usecase
+  new_uc - new usecase
+  d1, d11, d2 - SND_DEVICE enums
+  a1, a2 - corresponding ANDROID device enums
+  B, B1, B2 - backend strings
+
+case 1
+  uc->dev  d1 (a1)               B1
+  new_uc->dev d1 (a1), d2 (a2)   B1, B2
+
+  resolution: disable and enable uc->dev on d1
+
+case 2
+  uc->dev d1 (a1)        B1
+  new_uc->dev d11 (a1)   B1
+
+  resolution: need to switch uc since d1 and d11 are related
+  (e.g. speaker and voice-speaker)
+  use ANDROID_DEVICE_OUT enums to match devices since SND_DEVICE enums may vary
+
+case 3
+  uc->dev d1 (a1)        B1
+  new_uc->dev d2 (a2)    B2
+
+  resolution: no need to switch uc
+
+case 4
+  uc->dev d1 (a1)      B
+  new_uc->dev d2 (a2)  B
+
+  resolution: disable enable uc-dev on d2 since backends match
+  we cannot enable two streams on two different devices if they
+  share the same backend. e.g. if offload is on speaker device using
+  QUAD_MI2S backend and a low-latency stream is started on voice-handset
+  using the same backend, offload must also be switched to voice-handset.
+
+case 5
+  uc->dev  d1 (a1)                  B
+  new_uc->dev d1 (a1), d2 (a2)      B
+
+  resolution: disable enable uc-dev on d2 since backends match
+  we cannot enable two streams on two different devices if they
+  share the same backend.
+
+case 6
+  uc->dev  d1 a1    B1
+  new_uc->dev d2 a1 B2
+
+  resolution: no need to switch
+
+case 7
+
+  uc->dev d1 (a1), d2 (a2)       B1, B2
+  new_uc->dev d1                 B1
+
+  resolution: no need to switch
+
+*/
+static snd_device_t derive_playback_snd_device(struct audio_usecase *uc,
+                                               struct audio_usecase *new_uc,
+                                               snd_device_t new_snd_device)
+{
+    audio_devices_t a1 = uc->stream.out->devices;
+    audio_devices_t a2 = new_uc->stream.out->devices;
+
+    snd_device_t d1 = uc->out_snd_device;
+    snd_device_t d2 = new_snd_device;
+
+    // Treat as a special case when a1 and a2 are not disjoint
+    if ((a1 != a2) && (a1 & a2)) {
+        snd_device_t d3[2];
+        int num_devices = 0;
+        int ret = platform_can_split_snd_device(popcount(a1) > 1 ? d1 : d2,
+                                                &num_devices,
+                                                d3);
+        if (ret < 0) {
+            if (ret != -ENOSYS) {
+                ALOGW("%s failed to split snd_device %d",
+                      __func__,
+                      popcount(a1) > 1 ? d1 : d2);
+            }
+            goto end;
+        }
+
+        // NB: case 7 is hypothetical and isn't a practical usecase yet.
+        // But if it does happen, we need to give priority to d2 if
+        // the combo devices active on the existing usecase share a backend.
+        // This is because we cannot have a usecase active on a combo device
+        // and a new usecase requests one device in this combo pair.
+        if (platform_check_backends_match(d3[0], d3[1])) {
+            return d2; // case 5
+        } else {
+            return d1; // case 1
+        }
+    } else {
+        if (platform_check_backends_match(d1, d2)) {
+            return d2; // case 2, 4
+        } else {
+            return d1; // case 6, 3
+        }
+    }
+
+end:
+    return d2; // return whatever was calculated before.
+}
+
 static void check_and_route_playback_usecases(struct audio_device *adev,
                                               struct audio_usecase *uc_info,
                                               snd_device_t snd_device)
@@ -733,10 +845,15 @@
             }
         }
 
+        snd_device_t d_device;
         list_for_each(node, &adev->usecase_list) {
             usecase = node_to_item(node, struct audio_usecase, list);
             if (switch_device[usecase->id]) {
-                enable_snd_device(adev, snd_device);
+                d_device = derive_playback_snd_device(usecase, uc_info,
+                                                      snd_device);
+                enable_snd_device(adev, d_device);
+                /* Update the out_snd_device before enabling the audio route */
+                usecase->out_snd_device = d_device;
             }
         }
 
@@ -744,9 +861,7 @@
            specified usecase to new snd devices */
         list_for_each(node, &adev->usecase_list) {
             usecase = node_to_item(node, struct audio_usecase, list);
-            /* Update the out_snd_device only before enabling the audio route */
             if (switch_device[usecase->id] ) {
-                usecase->out_snd_device = snd_device;
                 enable_audio_route(adev, usecase);
             }
         }
diff --git a/hal/msm8916/platform.c b/hal/msm8916/platform.c
index 52e9fd8..72d9591 100644
--- a/hal/msm8916/platform.c
+++ b/hal/msm8916/platform.c
@@ -1424,35 +1424,35 @@
     return ret;
 }
 
-bool platform_can_split_snd_device(snd_device_t snd_device,
+int platform_can_split_snd_device(snd_device_t snd_device,
                                    int *num_devices,
                                    snd_device_t *new_snd_devices)
 {
-    bool status = false;
+    int ret = -EINVAL;
 
     if (NULL == num_devices || NULL == new_snd_devices) {
         ALOGE("%s: NULL pointer ..", __func__);
-        return false;
+        return -EINVAL;
     }
 
     /*
      * If wired headset/headphones/line devices share the same backend
-     * with speaker/earpiece this routine returns false.
+     * with speaker/earpiece this routine -EINVAL.
      */
     if (snd_device == SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES &&
         !platform_check_backends_match(SND_DEVICE_OUT_SPEAKER, SND_DEVICE_OUT_HEADPHONES)) {
         *num_devices = 2;
         new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER;
         new_snd_devices[1] = SND_DEVICE_OUT_HEADPHONES;
-        status = true;
+        ret = 0;
     } else if (snd_device == SND_DEVICE_OUT_SPEAKER_AND_LINE &&
                !platform_check_backends_match(SND_DEVICE_OUT_SPEAKER, SND_DEVICE_OUT_LINE)) {
         *num_devices = 2;
         new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER;
         new_snd_devices[1] = SND_DEVICE_OUT_LINE;
-        status = true;
+        ret = 0;
     }
-    return status;
+    return ret;
 }
 
 snd_device_t platform_get_output_snd_device(void *platform, audio_devices_t devices)
diff --git a/hal/msm8960/platform.c b/hal/msm8960/platform.c
index d7fc84c..94f4621 100644
--- a/hal/msm8960/platform.c
+++ b/hal/msm8960/platform.c
@@ -1075,11 +1075,11 @@
     return true;
 }
 
-bool platform_can_split_snd_device(snd_device_t in_snd_device __unused,
+int platform_can_split_snd_device(snd_device_t in_snd_device __unused,
                                    int *num_devices __unused,
                                    snd_device_t *out_snd_devices __unused)
 {
-    return false;
+    return -ENOSYS;
 }
 
 bool platform_check_backends_match(snd_device_t snd_device1 __unused,
diff --git a/hal/msm8974/platform.c b/hal/msm8974/platform.c
index 5cdc394..a5cfc18 100644
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -1863,47 +1863,46 @@
     return ret;
 }
 
-bool platform_can_split_snd_device(snd_device_t snd_device,
-                                   int *num_devices,
-                                   snd_device_t *new_snd_devices)
+int platform_can_split_snd_device(snd_device_t snd_device,
+                                  int *num_devices,
+                                  snd_device_t *new_snd_devices)
 {
-    bool status = false;
-
+    int ret = -EINVAL;
     if (NULL == num_devices || NULL == new_snd_devices) {
         ALOGE("%s: NULL pointer ..", __func__);
-        return false;
+        return -EINVAL;
     }
 
     /*
      * If wired headset/headphones/line devices share the same backend
-     * with speaker/earpiece this routine returns false.
+     * with speaker/earpiece this routine returns -EINVAL.
      */
     if (snd_device == SND_DEVICE_OUT_SPEAKER_AND_HEADPHONES &&
         !platform_check_backends_match(SND_DEVICE_OUT_SPEAKER, SND_DEVICE_OUT_HEADPHONES)) {
         *num_devices = 2;
         new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER;
         new_snd_devices[1] = SND_DEVICE_OUT_HEADPHONES;
-        status = true;
+        ret = 0;
     } else if (snd_device == SND_DEVICE_OUT_SPEAKER_AND_LINE &&
                !platform_check_backends_match(SND_DEVICE_OUT_SPEAKER, SND_DEVICE_OUT_LINE)) {
         *num_devices = 2;
         new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER;
         new_snd_devices[1] = SND_DEVICE_OUT_LINE;
-        status = true;
+        ret = 0;
     } else if (snd_device == SND_DEVICE_OUT_SPEAKER_SAFE_AND_HEADPHONES &&
                !platform_check_backends_match(SND_DEVICE_OUT_SPEAKER_SAFE, SND_DEVICE_OUT_HEADPHONES)) {
         *num_devices = 2;
         new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER_SAFE;
         new_snd_devices[1] = SND_DEVICE_OUT_HEADPHONES;
-        status = true;
+        ret = 0;
     } else if (snd_device == SND_DEVICE_OUT_SPEAKER_SAFE_AND_LINE &&
                !platform_check_backends_match(SND_DEVICE_OUT_SPEAKER_SAFE, SND_DEVICE_OUT_LINE)) {
         *num_devices = 2;
         new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER_SAFE;
         new_snd_devices[1] = SND_DEVICE_OUT_LINE;
-        status = true;
+        ret = 0;
     }
-    return status;
+    return ret;
 }
 
 snd_device_t platform_get_output_snd_device(void *platform, audio_devices_t devices)
diff --git a/hal/platform_api.h b/hal/platform_api.h
index 41e600e..fbc84c1 100644
--- a/hal/platform_api.h
+++ b/hal/platform_api.h
@@ -100,9 +100,9 @@
 void platform_set_echo_reference(struct audio_device *adev, bool enable, audio_devices_t out_device);
 int platform_swap_lr_channels(struct audio_device *adev, bool swap_channels);
 
-bool platform_can_split_snd_device(snd_device_t in_snd_device,
-                                   int *num_devices,
-                                   snd_device_t *out_snd_devices);
+int platform_can_split_snd_device(snd_device_t in_snd_device,
+                                  int *num_devices,
+                                  snd_device_t *out_snd_devices);
 
 bool platform_check_backends_match(snd_device_t snd_device1, snd_device_t snd_device2);