audio: Enable voice call support

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

Change-Id: I284f21240e56dda93cb8a2ab98b903ff712504a6
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;
 }