From 696959dda1cd4437dd672a61d7a3266169ad3019 Mon Sep 17 00:00:00 2001 From: "Christopher N. Hesse" Date: Thu, 2 Feb 2017 20:49:55 +0100 Subject: [PATCH] audio: Enable voice call support Pair-Programmed-With: Andreas Schneider Change-Id: I284f21240e56dda93cb8a2ab98b903ff712504a6 --- audio/audio_hw.c | 44 ++++-- audio/include/samsung_audio.h | 4 + audio/voice.c | 270 +++++++++++++++++++++++++++++++++- audio/voice.h | 9 +- 4 files changed, 314 insertions(+), 13 deletions(-) diff --git a/audio/audio_hw.c b/audio/audio_hw.c index 986ef0ae..c147acf6 100644 --- a/audio/audio_hw.c +++ b/audio/audio_hw.c @@ -502,11 +502,11 @@ static struct audio_usecase *get_usecase_from_type(struct audio_device *adev, 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 @@ static int select_devices(struct audio_device *adev, 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 @@ static int select_devices(struct audio_device *adev, /* 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 @@ static int start_input_stream(struct stream_in *in) #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 @@ static int start_input_stream(struct stream_in *in) } } +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 @@ static int out_open_pcm_devices(struct stream_out *out) 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 @@ error_config: 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 @@ static int stop_voice_call(struct audio_device *adev) 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 @@ static int stop_voice_call(struct audio_device *adev) } /* 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 @@ static int start_voice_call(struct audio_device *adev) 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 @@ static int start_voice_call(struct audio_device *adev) 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 @@ static int adev_open(const hw_module_t *module, const char *name, } } - 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 1bd094a6..1e84c05f 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 57fcc600..f10d8ed7 100644 --- a/audio/voice.c +++ b/audio/voice.c @@ -26,11 +26,252 @@ #include #include +#include +#include + +#include + #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 @@ struct voice_session *voice_session_init(void) 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 @@ struct voice_session *voice_session_init(void) 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 40bd1b16..0e32f0ae 100644 --- a/audio/voice.h +++ b/audio/voice.h @@ -33,7 +33,14 @@ struct voice_session { 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 */