audio: Enable voice call support

Pair-Programmed-With: Andreas Schneider <asn@cryptomilk.org>

Change-Id: I284f21240e56dda93cb8a2ab98b903ff712504a6
diff --git a/audio/audio_hw.c b/audio/audio_hw.c
index 986ef0a..c147acf 100644
--- a/audio/audio_hw.c
+++ b/audio/audio_hw.c
@@ -502,11 +502,11 @@
 static int set_voice_volume_l(struct audio_device *adev, float volume)
 {
     int err = 0;
-    (void)volume;
 
     if (adev->mode == AUDIO_MODE_IN_CALL) {
-        /* TODO */
+        set_voice_session_volume(adev->voice.session, volume);
     }
+
     return err;
 }
 
@@ -861,6 +861,7 @@
 
     if (usecase->type == VOICE_CALL) {
         out_snd_device = get_output_snd_device(adev, active_out->devices);
+        prepare_voice_session(adev->voice.session, active_out->devices);
         in_snd_device = get_input_snd_device(adev, active_out->devices);
         usecase->devices = active_out->devices;
     } else {
@@ -925,6 +926,11 @@
 
     /* Enable new sound devices */
     if (out_snd_device != SND_DEVICE_NONE) {
+        /* We need to update the audio path if we switch the out devices */
+        if (adev->voice.in_call) {
+            set_voice_session_audio_path(adev->voice.session);
+        }
+
         enable_snd_device(adev, usecase, out_snd_device, false);
     }
 
@@ -2079,6 +2085,11 @@
 #endif
 #endif
 
+    if (in->dev->voice.in_call) {
+        ALOGV("%s: in_call, not handling PCMs", __func__);
+        goto skip_pcm_handling;
+    }
+
     /* Open the PCM device.
      * The HW is limited to support only the default pcm_profile settings.
      * As such a change in aux_channels will not have an effect.
@@ -2116,6 +2127,7 @@
         }
     }
 
+skip_pcm_handling:
     /* force read and proc buffer reallocation in case of frame size or
      * channel count change */
     in->proc_buf_frames = 0;
@@ -2250,6 +2262,11 @@
         if (out->flags & AUDIO_OUTPUT_FLAG_DEEP_BUFFER)
             pcm_device_id = pcm_device_deep_buffer.id;
 
+        if (out->dev->voice.in_call) {
+            ALOGV("%s: in_call, not opening PCMs", __func__);
+            return ret;
+        }
+
         ALOGV("%s: Opening PCM device card_id(%d) device_id(%d)",
               __func__, pcm_device_card, pcm_device_id);
 
@@ -2396,14 +2413,14 @@
     return ret;
 }
 
-static int stop_voice_call(struct audio_device *adev)
+int stop_voice_call(struct audio_device *adev)
 {
     struct audio_usecase *uc_info;
 
     ALOGV("%s: enter", __func__);
     adev->voice.in_call = false;
 
-    /* TODO: implement voice call stop */
+    stop_voice_session(adev->voice.session);
 
     uc_info = get_usecase_from_id(adev, USECASE_VOICE_CALL);
     if (uc_info == NULL) {
@@ -2415,7 +2432,6 @@
     disable_snd_device(adev, uc_info, uc_info->out_snd_device, false);
     disable_snd_device(adev, uc_info, uc_info->in_snd_device, true);
 
-    uc_release_pcm_devices(uc_info);
     list_remove(&uc_info->adev_list_node);
     free(uc_info);
 
@@ -2424,7 +2440,7 @@
 }
 
 /* always called with adev lock held */
-static int start_voice_call(struct audio_device *adev)
+int start_voice_call(struct audio_device *adev)
 {
     struct audio_usecase *uc_info;
     int ret = 0;
@@ -2436,6 +2452,12 @@
         ret = -ENOMEM;
         goto exit;
     }
+    /*
+     * We set this early so that functions called after this is being set
+     * can use it. It is e.g. needed in select_devices() to inform the RILD
+     * which output device we use.
+     */
+    adev->voice.in_call = true;
 
     uc_info->id = USECASE_VOICE_CALL;
     uc_info->type = VOICE_CALL;
@@ -2444,19 +2466,19 @@
     uc_info->in_snd_device = SND_DEVICE_NONE;
     uc_info->out_snd_device = SND_DEVICE_NONE;
 
-    uc_select_pcm_devices(uc_info);
+    list_init(&uc_info->mixer_list);
+    list_add_tail(&uc_info->mixer_list,
+                  &adev_get_mixer_for_card(adev, SOUND_CARD)->uc_list_node[uc_info->id]);
 
     list_add_tail(&adev->usecase_list, &uc_info->adev_list_node);
 
     select_devices(adev, USECASE_VOICE_CALL);
 
-
-    /* TODO: implement voice call start */
+    start_voice_session(adev->voice.session);
 
     /* set cached volume */
     set_voice_volume_l(adev, adev->voice.volume);
 
-    adev->voice.in_call = true;
 exit:
     ALOGV("%s: exit", __func__);
     return ret;
@@ -4317,7 +4339,7 @@
         }
     }
 
-    adev->voice.session = voice_session_init();
+    adev->voice.session = voice_session_init(adev);
     if (adev->voice.session == NULL) {
         ALOGE("%s: Failed to initialize voice session data", __func__);
 
diff --git a/audio/include/samsung_audio.h b/audio/include/samsung_audio.h
index 1bd094a..1e84c05 100644
--- a/audio/include/samsung_audio.h
+++ b/audio/include/samsung_audio.h
@@ -39,6 +39,10 @@
 #define SOUND_CAPTURE_DEVICE 0
 #define SOUND_CAPTURE_SCO_DEVICE 2
 
+/* Voice calls */
+#define SOUND_PLAYBACK_VOICE_DEVICE 1
+#define SOUND_CAPTURE_VOICE_DEVICE 1
+
 /* Unusupported
 #define SOUND_CAPTURE_LOOPBACK_AEC_DEVICE 1
 #define SOUND_CAPTURE_HOTWORD_DEVICE 0
diff --git a/audio/voice.c b/audio/voice.c
index 57fcc60..f10d8ed 100644
--- a/audio/voice.c
+++ b/audio/voice.c
@@ -26,11 +26,252 @@
 #include <stdlib.h>
 #include <pthread.h>
 
+#include <cutils/log.h>
+#include <cutils/properties.h>
+
+#include <samsung_audio.h>
+
 #include "audio_hw.h"
 #include "voice.h"
 
-struct voice_session *voice_session_init(void)
+static struct pcm_config pcm_config_voicecall = {
+    .channels = 2,
+    .rate = 8000,
+    .period_size = CAPTURE_PERIOD_SIZE_LOW_LATENCY,
+    .period_count = CAPTURE_PERIOD_COUNT_LOW_LATENCY,
+    .format = PCM_FORMAT_S16_LE,
+};
+
+static struct pcm_config pcm_config_voicecall_wideband = {
+    .channels = 2,
+    .rate = 16000,
+    .period_size = CAPTURE_PERIOD_SIZE_LOW_LATENCY,
+    .period_count = CAPTURE_PERIOD_COUNT_LOW_LATENCY,
+    .format = PCM_FORMAT_S16_LE,
+};
+
+/* Prototypes */
+int start_voice_call(struct audio_device *adev);
+int stop_voice_call(struct audio_device *adev);
+
+void set_voice_session_audio_path(struct voice_session *session)
 {
+    enum _AudioPath device_type;
+
+    switch(session->out_device) {
+        case AUDIO_DEVICE_OUT_SPEAKER:
+            device_type = SOUND_AUDIO_PATH_SPEAKER;
+            break;
+        case AUDIO_DEVICE_OUT_EARPIECE:
+            device_type = SOUND_AUDIO_PATH_HANDSET;
+            break;
+        case AUDIO_DEVICE_OUT_WIRED_HEADSET:
+            device_type = SOUND_AUDIO_PATH_HEADSET;
+            break;
+        case AUDIO_DEVICE_OUT_WIRED_HEADPHONE:
+            device_type = SOUND_AUDIO_PATH_HEADPHONE;
+            break;
+        case AUDIO_DEVICE_OUT_BLUETOOTH_SCO:
+        case AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET:
+        case AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT:
+            device_type = SOUND_AUDIO_PATH_BLUETOOTH;
+            break;
+        default:
+            /* if output device isn't supported, use handset by default */
+            device_type = SOUND_AUDIO_PATH_HANDSET;
+            break;
+    }
+
+    ALOGV("%s: ril_set_call_audio_path(%d)", __func__, device_type);
+
+    ril_set_call_audio_path(&session->ril, device_type);
+}
+
+/*
+ * This decides based on the output device, if we enable
+ * two mic control
+ */
+void prepare_voice_session(struct voice_session *session,
+                           audio_devices_t active_out_devices)
+{
+    ALOGV("%s: active_out_devices: 0x%x", __func__, active_out_devices);
+
+    session->out_device = active_out_devices;
+
+    switch (session->out_device) {
+    case AUDIO_DEVICE_OUT_EARPIECE:
+    case AUDIO_DEVICE_OUT_SPEAKER:
+        session->two_mic_control = true;
+        break;
+    default:
+        session->two_mic_control = false;
+        break;
+    }
+
+    if (session->two_mic_disabled) {
+        session->two_mic_control = false;
+    }
+}
+
+/*
+ * This function must be called with hw device mutex locked, OK to hold other
+ * mutexes
+ */
+int start_voice_session(struct voice_session *session)
+{
+    struct pcm_config *voice_config;
+
+    if (session->pcm_voice_rx != NULL || session->pcm_voice_tx != NULL) {
+        ALOGW("%s: Voice PCMs already open!\n", __func__);
+        return 0;
+    }
+
+    ALOGV("%s: Opening voice PCMs", __func__);
+
+    if (session->wb_amr) {
+        ALOGV("%s: pcm_config wideband", __func__);
+        voice_config = &pcm_config_voicecall_wideband;
+    } else {
+        ALOGV("%s: pcm_config narrowband", __func__);
+        voice_config = &pcm_config_voicecall;
+    }
+
+    /* Open modem PCM channels */
+    session->pcm_voice_rx = pcm_open(SOUND_CARD,
+                                     SOUND_PLAYBACK_VOICE_DEVICE,
+                                     PCM_OUT|PCM_MONOTONIC,
+                                     voice_config);
+    if (session->pcm_voice_rx != NULL && !pcm_is_ready(session->pcm_voice_rx)) {
+        ALOGE("%s: cannot open PCM voice RX stream: %s",
+              __func__,
+              pcm_get_error(session->pcm_voice_rx));
+
+        pcm_close(session->pcm_voice_tx);
+        session->pcm_voice_tx = NULL;
+
+        return -ENOMEM;
+    }
+
+    session->pcm_voice_tx = pcm_open(SOUND_CARD,
+                                     SOUND_CAPTURE_VOICE_DEVICE,
+                                     PCM_IN|PCM_MONOTONIC,
+                                     voice_config);
+    if (session->pcm_voice_tx != NULL && !pcm_is_ready(session->pcm_voice_tx)) {
+        ALOGE("%s: cannot open PCM voice TX stream: %s",
+              __func__,
+              pcm_get_error(session->pcm_voice_tx));
+
+        pcm_close(session->pcm_voice_rx);
+        session->pcm_voice_rx = NULL;
+
+        return -ENOMEM;
+    }
+
+    pcm_start(session->pcm_voice_rx);
+    pcm_start(session->pcm_voice_tx);
+
+    /* TODO: handle SCO */
+
+    if (session->two_mic_control) {
+        ALOGV("%s: enabling two mic control", __func__);
+        ril_set_two_mic_control(&session->ril, AUDIENCE, TWO_MIC_SOLUTION_ON);
+    } else {
+        ALOGV("%s: disabling two mic control", __func__);
+        ril_set_two_mic_control(&session->ril, AUDIENCE, TWO_MIC_SOLUTION_OFF);
+    }
+
+    ril_set_call_clock_sync(&session->ril, SOUND_CLOCK_START);
+
+    return 0;
+}
+
+/*
+ * This function must be called with hw device mutex locked, OK to hold other
+ * mutexes
+ */
+void stop_voice_session(struct voice_session *session)
+{
+    int status = 0;
+
+    ALOGV("%s: Closing active PCMs", __func__);
+
+    if (session->pcm_voice_rx != NULL) {
+        pcm_stop(session->pcm_voice_rx);
+        pcm_close(session->pcm_voice_rx);
+        session->pcm_voice_rx = NULL;
+        status++;
+    }
+
+    if (session->pcm_voice_tx != NULL) {
+        pcm_stop(session->pcm_voice_tx);
+        pcm_close(session->pcm_voice_tx);
+        session->pcm_voice_tx = NULL;
+        status++;
+    }
+
+    /* TODO: handle SCO */
+
+    session->out_device = AUDIO_DEVICE_NONE;
+
+    ALOGV("%s: Successfully closed %d active PCMs", __func__, status);
+}
+
+void set_voice_session_volume(struct voice_session *session, float volume)
+{
+    enum _SoundType sound_type;
+
+    switch (session->out_device) {
+        case AUDIO_DEVICE_OUT_EARPIECE:
+            sound_type = SOUND_TYPE_VOICE;
+            break;
+        case AUDIO_DEVICE_OUT_SPEAKER:
+            sound_type = SOUND_TYPE_SPEAKER;
+            break;
+        case AUDIO_DEVICE_OUT_WIRED_HEADSET:
+        case AUDIO_DEVICE_OUT_WIRED_HEADPHONE:
+            sound_type = SOUND_TYPE_HEADSET;
+            break;
+        case AUDIO_DEVICE_OUT_BLUETOOTH_SCO:
+        case AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET:
+        case AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT:
+        case AUDIO_DEVICE_OUT_ALL_SCO:
+            sound_type = SOUND_TYPE_BTVOICE;
+            break;
+        default:
+            sound_type = SOUND_TYPE_VOICE;
+    }
+
+    ril_set_call_volume(&session->ril, sound_type, volume);
+}
+
+static void voice_session_wb_amr_callback(void *data, int enable)
+{
+    struct audio_device *adev = (struct audio_device *)data;
+    struct voice_session *session =
+        (struct voice_session *)adev->voice.session;
+
+    pthread_mutex_lock(&adev->lock);
+
+    if (session->wb_amr != enable) {
+        session->wb_amr = enable;
+
+        /* reopen the modem PCMs at the new rate */
+        if (adev->voice.in_call) {
+            ALOGV("%s: %s wide band voice call",
+                  __func__,
+                  enable ? "Enable" : "Disable");
+
+            stop_voice_call(adev);
+            start_voice_call(adev);
+        }
+    }
+
+    pthread_mutex_unlock(&adev->lock);
+}
+
+struct voice_session *voice_session_init(struct audio_device *adev)
+{
+    char voice_config[PROPERTY_VALUE_MAX];
     struct voice_session *session;
     int ret;
 
@@ -39,6 +280,12 @@
         return NULL;
     }
 
+    /* Two mic control */
+    ret = property_get_bool("audio_hal.disable_two_mic", false);
+    if (ret > 0) {
+        session->two_mic_disabled = true;
+    }
+
     /* Do this as the last step so we do not have to close it on error */
     ret = ril_open(&session->ril);
     if (ret != 0) {
@@ -46,6 +293,27 @@
         return NULL;
     }
 
+    ret = property_get("audio_hal.force_voice_config", voice_config, "");
+    if (ret > 0) {
+        if ((strncmp(voice_config, "narrow", 6)) == 0)
+            session->wb_amr = false;
+        else if ((strncmp(voice_config, "wide", 4)) == 0)
+            session->wb_amr = true;
+        ALOGV("%s: Forcing voice config: %s", __func__, voice_config);
+    } else {
+        /* register callback for wideband AMR setting */
+        ret = ril_set_wb_amr_callback(&session->ril,
+                                      voice_session_wb_amr_callback,
+                                      (void *)adev);
+        if (ret != 0) {
+            ALOGE("%s: Failed to register WB_AMR callback", __func__);
+            free(session);
+            return NULL;
+        }
+
+        ALOGV("%s: Registered WB_AMR callback", __func__);
+    }
+
     return session;
 }
 
diff --git a/audio/voice.h b/audio/voice.h
index 40bd1b1..0e32f0a 100644
--- a/audio/voice.h
+++ b/audio/voice.h
@@ -33,7 +33,14 @@
     audio_devices_t out_device;
 };
 
-struct voice_session *voice_session_init(void);
+void prepare_voice_session(struct voice_session *session,
+                           audio_devices_t active_out_devices);
+int start_voice_session(struct voice_session *session);
+void stop_voice_session(struct voice_session *session);
+void set_voice_session_volume(struct voice_session *session, float volume);
+void set_voice_session_audio_path(struct voice_session *session);
+
+struct voice_session *voice_session_init(struct audio_device *adev);
 void voice_session_deinit(struct voice_session *s);
 
 #endif /* VOICE_CALL_H */