/* * Copyright (C) 2017 Christopher N. Hesse * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "audio_hw_voice" #define LOG_NDEBUG 0 /*#define VERY_VERY_VERBOSE_LOGGING*/ #ifdef VERY_VERY_VERBOSE_LOGGING #define ALOGVV ALOGV #else #define ALOGVV(a...) do { } while(0) #endif #include #include #include #include #include #include "audio_hw.h" #include "voice.h" 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, }; struct pcm_config pcm_config_voice_sco = { .channels = 1, .rate = SCO_DEFAULT_SAMPLING_RATE, .period_size = SCO_PERIOD_SIZE, .period_count = SCO_PERIOD_COUNT, .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; int rc; 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_EARPIECE; 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 earpiece by default */ device_type = SOUND_AUDIO_PATH_EARPIECE; break; } ALOGV("%s: ril_set_call_audio_path(%d)", __func__, device_type); rc = ril_set_call_audio_path(&session->ril, device_type); ALOGE_IF(rc != 0, "Failed to set audio path: (%d)", rc); } /* * 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 must be called with the hw device mutex locked, OK to hold other * mutexes. */ static void stop_voice_session_bt_sco(struct voice_session *session) { ALOGV("%s: Closing SCO PCMs", __func__); if (session->pcm_sco_rx != NULL) { pcm_stop(session->pcm_sco_rx); pcm_close(session->pcm_sco_rx); session->pcm_sco_rx = NULL; } if (session->pcm_sco_tx != NULL) { pcm_stop(session->pcm_sco_tx); pcm_close(session->pcm_sco_tx); session->pcm_sco_tx = NULL; } } /* must be called with the hw device mutex locked, OK to hold other mutexes */ void start_voice_session_bt_sco(struct voice_session *session) { if (session->pcm_sco_rx != NULL || session->pcm_sco_tx != NULL) { ALOGW("%s: SCO PCMs already open!\n", __func__); return; } ALOGV("%s: Opening SCO PCMs", __func__); /* TODO: Add wideband support */ session->pcm_sco_rx = pcm_open(SOUND_CARD, SOUND_PLAYBACK_SCO_DEVICE, PCM_OUT|PCM_MONOTONIC, &pcm_config_voice_sco); if (session->pcm_sco_rx != NULL && !pcm_is_ready(session->pcm_sco_rx)) { ALOGE("%s: cannot open PCM SCO RX stream: %s", __func__, pcm_get_error(session->pcm_sco_rx)); goto err_sco_rx; } session->pcm_sco_tx = pcm_open(SOUND_CARD, SOUND_CAPTURE_SCO_DEVICE, PCM_IN|PCM_MONOTONIC, &pcm_config_voice_sco); if (session->pcm_sco_tx && !pcm_is_ready(session->pcm_sco_tx)) { ALOGE("%s: cannot open PCM SCO TX stream: %s", __func__, pcm_get_error(session->pcm_sco_tx)); goto err_sco_tx; } pcm_start(session->pcm_sco_rx); pcm_start(session->pcm_sco_tx); return; err_sco_tx: pcm_close(session->pcm_sco_tx); session->pcm_sco_tx = NULL; err_sco_rx: pcm_close(session->pcm_sco_rx); session->pcm_sco_rx = NULL; } /* * 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); if (session->out_device & AUDIO_DEVICE_OUT_ALL_SCO) { start_voice_session_bt_sco(session); } 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++; } if (session->out_device & AUDIO_DEVICE_OUT_ALL_SCO) { stop_voice_session_bt_sco(session); } 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); } void set_voice_session_mic_mute(struct voice_session *session, bool state) { enum _MuteCondition mute_condition = state ? TX_MUTE : TX_UNMUTE; ril_set_mute(&session->ril, mute_condition); } bool voice_session_uses_twomic(struct voice_session *session) { if (session->two_mic_disabled) { return false; } return session->two_mic_control; } bool voice_session_uses_wideband(struct voice_session *session) { return session->wb_amr; } 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; session = calloc(1, sizeof(struct voice_session)); if (session == NULL) { 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) { free(session); 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; } void voice_session_deinit(struct voice_session *session) { ril_close(&session->ril); free(session); }