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.

CRs-Fixed: 1077122
Bug: 31671778
Change-Id: Ic0fd4e8d2c1119e7198dc5bb5e5a51817f0110c1
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index 13e4ae9..bf2908f 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -796,8 +796,10 @@
             audio_extn_dev_arbi_release(snd_device);
             return -EINVAL;
         }
-    } else if (platform_can_split_snd_device(adev->platform, snd_device,
-            &num_devices, new_snd_devices)) {
+    } else if (platform_split_snd_device(adev->platform,
+                                         snd_device,
+                                         &num_devices,
+                                         new_snd_devices) == 0) {
         for (i = 0; i < num_devices; i++) {
             enable_snd_device(adev, new_snd_devices[i]);
         }
@@ -870,8 +872,10 @@
         if (platform_can_enable_spkr_prot_on_device(snd_device) &&
              audio_extn_spkr_prot_is_enabled()) {
             audio_extn_spkr_prot_stop_processing(snd_device);
-        } else if (platform_can_split_snd_device(adev->platform, snd_device,
-                    &num_devices, new_snd_devices)) {
+        } else if (platform_split_snd_device(adev->platform,
+                                             snd_device,
+                                             &num_devices,
+                                             new_snd_devices) == 0) {
             for (i = 0; i < num_devices; i++) {
                 disable_snd_device(adev, new_snd_devices[i]);
             }
@@ -908,6 +912,115 @@
     return 0;
 }
 
+/*
+  legend:
+  uc - existing usecase
+  new_uc - new usecase
+  d1, d11, d2 - SND_DEVICE enums
+  a1, a2 - corresponding ANDROID device enums
+  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)      B1
+  new_uc->dev d2 (a2)  B1
+
+  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)                  B1
+  new_uc->dev d1 (a1), d2 (a2)      B1
+
+  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 (a1)            B1
+
+  resolution: no need to switch
+
+*/
+static snd_device_t derive_playback_snd_device(void * platform,
+                                               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_split_snd_device(platform,
+                                            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_usecases_codec_backend(struct audio_device *adev,
                                               struct audio_usecase *uc_info,
                                               snd_device_t snd_device)
@@ -963,7 +1076,9 @@
               platform_check_backends_match(snd_device, usecase->out_snd_device));
         if (usecase->type != PCM_CAPTURE &&
             usecase != uc_info &&
-            (usecase->out_snd_device != snd_device || force_routing) &&
+            (derive_playback_snd_device(adev->platform,
+                                        usecase, uc_info,
+                                        snd_device) != usecase->out_snd_device || force_routing) &&
             ((usecase->devices & AUDIO_DEVICE_OUT_ALL_CODEC_BACKEND) ||
              (usecase->devices & AUDIO_DEVICE_OUT_AUX_DIGITAL) ||
              (usecase->devices & AUDIO_DEVICE_OUT_USB_DEVICE) ||
diff --git a/hal/msm8916/platform.c b/hal/msm8916/platform.c
index a42f984..fe533ac 100644
--- a/hal/msm8916/platform.c
+++ b/hal/msm8916/platform.c
@@ -2556,7 +2556,8 @@
         snd_device = usecase->in_snd_device;
     acdb_dev_id = acdb_device_table[platform_get_spkr_prot_snd_device(snd_device)];
 
-    if(!platform_can_split_snd_device(platform, snd_device, &num_devices, new_snd_device)) {
+    if (platform_split_snd_device(platform, snd_device, &num_devices,
+                                  new_snd_device) < 0) {
         new_snd_device[0] = snd_device;
     }
 
@@ -2858,22 +2859,21 @@
     return ret;
 }
 
-bool platform_can_split_snd_device(void *platform,
-                                   snd_device_t snd_device,
-                                   int *num_devices,
-                                   snd_device_t *new_snd_devices)
+int platform_split_snd_device(void *platform,
+                              snd_device_t snd_device,
+                              int *num_devices,
+                              snd_device_t *new_snd_devices)
 {
-    bool status = false;
+    int ret = -EINVAL;
     struct platform_data *my_data = (struct platform_data *)platform;
-
     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)) {
@@ -2887,7 +2887,7 @@
              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_HDMI &&
                !platform_check_backends_match(SND_DEVICE_OUT_SPEAKER, SND_DEVICE_OUT_HDMI)) {
         *num_devices = 2;
@@ -2900,7 +2900,7 @@
             new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER;
 
         new_snd_devices[1] = SND_DEVICE_OUT_HDMI;
-        status = true;
+        ret = 0;
     } else if (snd_device == SND_DEVICE_OUT_SPEAKER_AND_DISPLAY_PORT &&
                !platform_check_backends_match(SND_DEVICE_OUT_SPEAKER, SND_DEVICE_OUT_DISPLAY_PORT)) {
         *num_devices = 2;
@@ -2913,24 +2913,24 @@
             new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER;
 
         new_snd_devices[1] = SND_DEVICE_OUT_DISPLAY_PORT;
-        status = true;
+        ret = 0;
     } else if (snd_device == SND_DEVICE_OUT_SPEAKER_AND_USB_HEADSET &&
                !platform_check_backends_match(SND_DEVICE_OUT_SPEAKER, SND_DEVICE_OUT_USB_HEADSET)) {
         *num_devices = 2;
         new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER;
         new_snd_devices[1] = SND_DEVICE_OUT_USB_HEADSET;
-        status = true;
+        ret = 0;
     } else if (SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP == snd_device) {
         *num_devices = 2;
         new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER;
         new_snd_devices[1] = SND_DEVICE_OUT_BT_A2DP;
-        status = true;
+        ret = 0;
     }
 
     ALOGD("%s: snd_device(%d) num devices(%d) new_snd_devices(%d)", __func__,
         snd_device, *num_devices, *new_snd_devices);
 
-    return status;
+    return ret;
 }
 
 int platform_get_ext_disp_type(void *platform)
@@ -4753,8 +4753,8 @@
           backend_cfg.sample_rate,  backend_cfg.channels, backend_idx, usecase->id,
           platform_get_snd_device_name(snd_device));
 
-    if (!platform_can_split_snd_device(adev->platform, snd_device,
-            &num_devices, new_snd_devices))
+    if (platform_split_snd_device(adev->platform, snd_device,
+                                  &num_devices, new_snd_devices) < 0)
         new_snd_devices[0] = snd_device;
 
     for (i = 0; i < num_devices; i++) {
diff --git a/hal/msm8960/platform.c b/hal/msm8960/platform.c
index e025772..33bbc2f 100644
--- a/hal/msm8960/platform.c
+++ b/hal/msm8960/platform.c
@@ -1237,12 +1237,14 @@
 int platform_spkr_prot_is_wsa_analog_mode(void *adev __unused)
 {
     return 0;
-bool platform_can_split_snd_device(void *platform __unused,
-                                   snd_device_t in_snd_device __unused,
-                                   int *num_devices __unused,
-                                   snd_device_t *out_snd_devices __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 e947f91..697888e 100644
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -2393,8 +2393,8 @@
         return -EINVAL;
     }
 
-    if(!platform_can_split_snd_device(my_data, snd_device,
-            &num_devices, new_snd_device)) {
+    if (platform_split_snd_device(my_data, snd_device,
+                                  &num_devices, new_snd_device) < 0) {
         new_snd_device[0] = snd_device;
     }
 
@@ -2697,58 +2697,58 @@
     return ret;
 }
 
-bool platform_can_split_snd_device(void *platform,
-                                   snd_device_t snd_device,
-                                   int *num_devices,
-                                   snd_device_t *new_snd_devices)
+int platform_split_snd_device(void *platform,
+                              snd_device_t snd_device,
+                              int *num_devices,
+                              snd_device_t *new_snd_devices)
 {
-    bool status = false;
+    int ret = -EINVAL;
     struct platform_data *my_data = (struct platform_data *)platform;
 
     if ( NULL == num_devices || NULL == new_snd_devices || NULL == my_data) {
         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_HDMI &&
                !platform_check_backends_match(SND_DEVICE_OUT_SPEAKER, SND_DEVICE_OUT_HDMI)) {
         *num_devices = 2;
         new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER;
         new_snd_devices[1] = SND_DEVICE_OUT_HDMI;
-        status = true;
+        ret = 0;
     } else if (snd_device == SND_DEVICE_OUT_SPEAKER_AND_DISPLAY_PORT &&
                !platform_check_backends_match(SND_DEVICE_OUT_SPEAKER, SND_DEVICE_OUT_DISPLAY_PORT)) {
         *num_devices = 2;
         new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER;
         new_snd_devices[1] = SND_DEVICE_OUT_DISPLAY_PORT;
-        status = true;
+        ret = 0;
     } else if (snd_device == SND_DEVICE_OUT_SPEAKER_AND_USB_HEADSET &&
                !platform_check_backends_match(SND_DEVICE_OUT_SPEAKER, SND_DEVICE_OUT_USB_HEADSET)) {
         *num_devices = 2;
         new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER;
         new_snd_devices[1] = SND_DEVICE_OUT_USB_HEADSET;
-        status = true;
+        ret = 0;
     } else if (SND_DEVICE_OUT_SPEAKER_AND_BT_A2DP == snd_device) {
         *num_devices = 2;
         new_snd_devices[0] = SND_DEVICE_OUT_SPEAKER;
         new_snd_devices[1] = SND_DEVICE_OUT_BT_A2DP;
-        status = true;
+        ret = 0;
     }
 
     ALOGD("%s: snd_device(%d) num devices(%d) new_snd_devices(%d)", __func__,
         snd_device, *num_devices, *new_snd_devices);
 
-    return status;
+    return ret;
 }
 
 int platform_get_ext_disp_type(void *platform)
@@ -4829,7 +4829,8 @@
           backend_cfg.sample_rate, backend_cfg.channels, backend_idx, usecase->id,
           platform_get_snd_device_name(snd_device));
 
-    if (!platform_can_split_snd_device(my_data, snd_device, &num_devices, new_snd_devices))
+    if (platform_split_snd_device(my_data, snd_device, &num_devices,
+                                  new_snd_devices) < 0)
         new_snd_devices[0] = snd_device;
 
     for (i = 0; i < num_devices; i++) {
diff --git a/hal/platform_api.h b/hal/platform_api.h
index 61f42de..6e50e72 100644
--- a/hal/platform_api.h
+++ b/hal/platform_api.h
@@ -147,10 +147,10 @@
 int platform_get_spkr_prot_snd_device(snd_device_t snd_device);
 int platform_get_vi_feedback_snd_device(snd_device_t snd_device);
 int platform_spkr_prot_is_wsa_analog_mode(void *adev);
-bool platform_can_split_snd_device(void *platform,
-                                   snd_device_t snd_device,
-                                   int *num_devices,
-                                   snd_device_t *new_snd_devices);
+int platform_split_snd_device(void *platform,
+                              snd_device_t snd_device,
+                              int *num_devices,
+                              snd_device_t *new_snd_devices);
 
 bool platform_check_backends_match(snd_device_t snd_device1, snd_device_t snd_device2);
 int platform_set_sidetone(struct audio_device *adev,