| /* |
| * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * * Neither the name of The Linux Foundation nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
| * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
| * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
| * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * Changes from Qualcomm Innovation Center are provided under the following license: |
| * |
| * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. |
| * SPDX-License-Identifier: BSD-3-Clause-Clear |
| */ |
| |
| #define LOG_TAG "AHAL: hfp" |
| #define LOG_NDDEBUG 0 |
| |
| #include <errno.h> |
| #include <math.h> |
| #include "AudioCommon.h" |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <sys/stat.h> |
| #include <stdlib.h> |
| #include <dlfcn.h> |
| #include <math.h> |
| #include <cutils/properties.h> |
| #include "PalApi.h" |
| #include "AudioDevice.h" |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| |
| #define AUDIO_PARAMETER_HFP_ENABLE "hfp_enable" |
| #define AUDIO_PARAMETER_HFP_SET_SAMPLING_RATE "hfp_set_sampling_rate" |
| #define AUDIO_PARAMETER_KEY_HFP_VOLUME "hfp_volume" |
| #define AUDIO_PARAMETER_HFP_PCM_DEV_ID "hfp_pcm_dev_id" |
| |
| #define AUDIO_PARAMETER_KEY_HFP_MIC_VOLUME "hfp_mic_volume" |
| |
| struct hfp_module { |
| bool is_hfp_running; |
| float hfp_volume; |
| int ucid; |
| float mic_volume; |
| bool mic_mute; |
| uint32_t sample_rate; |
| pal_stream_handle_t *rx_stream_handle; |
| pal_stream_handle_t *tx_stream_handle; |
| }; |
| |
| #define PLAYBACK_VOLUME_MAX 0x2000 |
| #define CAPTURE_VOLUME_DEFAULT (15.0) |
| static struct hfp_module hfpmod = { |
| .is_hfp_running = 0, |
| .hfp_volume = 0, |
| .ucid = USECASE_AUDIO_HFP_SCO, |
| .mic_volume = CAPTURE_VOLUME_DEFAULT, |
| .mic_mute = 0, |
| .sample_rate = 16000, |
| }; |
| |
| static int32_t hfp_set_volume(float value) |
| { |
| int32_t vol, ret = 0; |
| struct pal_volume_data *pal_volume = NULL; |
| |
| AHAL_VERBOSE("entry"); |
| AHAL_DBG("(%f)\n", value); |
| |
| hfpmod.hfp_volume = value; |
| |
| if (value < 0.0) { |
| AHAL_DBG("(%f) Under 0.0, assuming 0.0\n", value); |
| value = 0.0; |
| } else { |
| value = ((value > 15.000000) ? 1.0 : (value / 15)); |
| AHAL_DBG("Volume brought with in range (%f)\n", value); |
| } |
| vol = lrint((value * 0x2000) + 0.5); |
| |
| if (!hfpmod.is_hfp_running) { |
| AHAL_VERBOSE("HFP not active, ignoring set_hfp_volume call"); |
| return -EIO; |
| } |
| |
| AHAL_DBG("Setting HFP volume to %d \n", vol); |
| |
| pal_volume = (struct pal_volume_data *)malloc(sizeof(struct pal_volume_data) |
| +sizeof(struct pal_channel_vol_kv)); |
| |
| if (!pal_volume) |
| return -ENOMEM; |
| |
| pal_volume->no_of_volpair = 1; |
| pal_volume->volume_pair[0].channel_mask = 0x03; |
| pal_volume->volume_pair[0].vol = value; |
| ret = pal_stream_set_volume(hfpmod.rx_stream_handle, pal_volume); |
| if (ret) |
| AHAL_ERR("set volume failed: %d \n", ret); |
| |
| free(pal_volume); |
| AHAL_VERBOSE("exit"); |
| return ret; |
| } |
| |
| /*Set mic volume to value. |
| * * |
| * * This interface is used for mic volume control, set mic volume as value(range 0 ~ 15). |
| * */ |
| static int hfp_set_mic_volume(float value) |
| { |
| int volume, ret = 0; |
| struct pal_volume_data *pal_volume = NULL; |
| |
| AHAL_DBG("enter, value=%f", value); |
| |
| if (!hfpmod.is_hfp_running) { |
| AHAL_ERR("HFP not active, ignoring set_hfp_mic_volume call"); |
| return -EIO; |
| } |
| |
| if (value < 0.0) { |
| AHAL_DBG("(%f) Under 0.0, assuming 0.0\n", value); |
| value = 0.0; |
| } else if (value > CAPTURE_VOLUME_DEFAULT) { |
| value = CAPTURE_VOLUME_DEFAULT; |
| AHAL_DBG("Volume brought within range (%f)\n", value); |
| } |
| |
| value = value / CAPTURE_VOLUME_DEFAULT; |
| |
| volume = (int)(value * PLAYBACK_VOLUME_MAX); |
| |
| pal_volume = (struct pal_volume_data *)malloc(sizeof(struct pal_volume_data) |
| +sizeof(struct pal_channel_vol_kv)); |
| if (!pal_volume) { |
| AHAL_ERR("Failed to allocate memory for pal_volume"); |
| return -ENOMEM; |
| } |
| pal_volume->no_of_volpair = 1; |
| pal_volume->volume_pair[0].channel_mask = 0x03; |
| pal_volume->volume_pair[0].vol = value; |
| if (pal_stream_set_volume(hfpmod.tx_stream_handle, pal_volume) < 0) { |
| AHAL_ERR("Couldn't set HFP Volume: [%d]", volume); |
| free(pal_volume); |
| pal_volume = NULL; |
| return -EINVAL; |
| } |
| |
| free(pal_volume); |
| pal_volume = NULL; |
| |
| return ret; |
| } |
| |
| static float hfp_get_mic_volume(void) |
| { |
| return hfpmod.mic_volume; |
| } |
| |
| static int32_t start_hfp(std::shared_ptr<AudioDevice> adev __unused, |
| struct str_parms *parms __unused) |
| { |
| int32_t ret = 0; |
| uint32_t no_of_devices = 2; |
| struct pal_stream_attributes stream_attr = {}; |
| struct pal_stream_attributes stream_tx_attr = {}; |
| struct pal_device devices[2] = {}; |
| struct pal_channel_info ch_info; |
| |
| AHAL_DBG("HFP start enter"); |
| if (hfpmod.rx_stream_handle || hfpmod.tx_stream_handle) |
| return 0; //hfp already running; |
| |
| pal_param_device_connection_t param_device_connection; |
| |
| param_device_connection.id = PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET; |
| param_device_connection.connection_state = true; |
| ret = pal_set_param(PAL_PARAM_ID_DEVICE_CONNECTION, |
| (void*)¶m_device_connection, |
| sizeof(pal_param_device_connection_t)); |
| if (ret != 0) { |
| AHAL_ERR("Set PAL_PARAM_ID_DEVICE_CONNECTION for %d failed", param_device_connection.id); |
| return ret; |
| } |
| |
| param_device_connection.id = PAL_DEVICE_OUT_BLUETOOTH_SCO; |
| param_device_connection.connection_state = true; |
| ret = pal_set_param(PAL_PARAM_ID_DEVICE_CONNECTION, |
| (void*)¶m_device_connection, |
| sizeof(pal_param_device_connection_t)); |
| if (ret != 0) { |
| AHAL_ERR("Set PAL_PARAM_ID_DEVICE_CONNECTION for %d failed", param_device_connection.id); |
| return ret; |
| } |
| |
| pal_param_btsco_t param_btsco; |
| |
| param_btsco.is_bt_hfp = true; |
| param_btsco.bt_sco_on = true; |
| ret = pal_set_param(PAL_PARAM_ID_BT_SCO, |
| (void*)¶m_btsco, |
| sizeof(pal_param_btsco_t)); |
| if (ret != 0) { |
| AHAL_ERR("Set PAL_PARAM_ID_BT_SCO failed"); |
| return ret; |
| } |
| |
| if (hfpmod.sample_rate == 16000) { |
| param_btsco.bt_wb_speech_enabled = true; |
| } |
| else |
| { |
| param_btsco.bt_wb_speech_enabled = false; |
| } |
| |
| ret = pal_set_param(PAL_PARAM_ID_BT_SCO_WB, |
| (void*)¶m_btsco, |
| sizeof(pal_param_btsco_t)); |
| if (ret != 0) { |
| AHAL_ERR("Set PAL_PARAM_ID_BT_SCO_WB failed"); |
| return ret; |
| } |
| |
| ch_info.channels = 1; |
| ch_info.ch_map[0] = PAL_CHMAP_CHANNEL_FL; |
| |
| /* BT SCO -> Spkr */ |
| stream_attr.type = PAL_STREAM_LOOPBACK; |
| stream_attr.info.opt_stream_info.loopback_type = PAL_STREAM_LOOPBACK_HFP_RX; |
| stream_attr.direction = PAL_AUDIO_INPUT_OUTPUT; |
| stream_attr.in_media_config.sample_rate = hfpmod.sample_rate; |
| stream_attr.in_media_config.bit_width = 16; |
| stream_attr.in_media_config.ch_info = ch_info; |
| stream_attr.in_media_config.aud_fmt_id = PAL_AUDIO_FMT_PCM_S16_LE; |
| |
| stream_attr.out_media_config.sample_rate = 48000; |
| stream_attr.out_media_config.bit_width = 16; |
| stream_attr.out_media_config.ch_info = ch_info; |
| stream_attr.out_media_config.aud_fmt_id = PAL_AUDIO_FMT_PCM_S16_LE; |
| |
| devices[0].id = PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET; |
| devices[0].config.sample_rate = hfpmod.sample_rate; |
| devices[0].config.bit_width = 16; |
| devices[0].config.ch_info = ch_info; |
| devices[0].config.aud_fmt_id = PAL_AUDIO_FMT_PCM_S16_LE; |
| |
| devices[1].id = PAL_DEVICE_OUT_SPEAKER; |
| |
| ret = pal_stream_open(&stream_attr, |
| no_of_devices, devices, |
| 0, |
| NULL, |
| NULL, |
| 0, |
| &hfpmod.rx_stream_handle); |
| if (ret != 0) { |
| AHAL_ERR("HFP rx stream (BT SCO->Spkr) open failed, rc %d", ret); |
| return ret; |
| } |
| ret = pal_stream_start(hfpmod.rx_stream_handle); |
| if (ret != 0) { |
| AHAL_ERR("HFP rx stream (BT SCO->Spkr) start failed, rc %d", ret); |
| pal_stream_close(hfpmod.rx_stream_handle); |
| return ret; |
| } |
| |
| /* Mic -> BT SCO */ |
| stream_tx_attr.type = PAL_STREAM_LOOPBACK; |
| stream_tx_attr.info.opt_stream_info.loopback_type = PAL_STREAM_LOOPBACK_HFP_TX; |
| stream_tx_attr.direction = PAL_AUDIO_INPUT_OUTPUT; |
| stream_tx_attr.in_media_config.sample_rate = hfpmod.sample_rate; |
| stream_tx_attr.in_media_config.bit_width = 16; |
| stream_tx_attr.in_media_config.ch_info = ch_info; |
| stream_tx_attr.in_media_config.aud_fmt_id = PAL_AUDIO_FMT_PCM_S16_LE; |
| |
| stream_tx_attr.out_media_config.sample_rate = 48000; |
| stream_tx_attr.out_media_config.bit_width = 16; |
| stream_tx_attr.out_media_config.ch_info = ch_info; |
| stream_tx_attr.out_media_config.aud_fmt_id = PAL_AUDIO_FMT_PCM_S16_LE; |
| |
| devices[0].id = PAL_DEVICE_OUT_BLUETOOTH_SCO; |
| devices[0].config.sample_rate = hfpmod.sample_rate; |
| devices[0].config.bit_width = 16; |
| devices[0].config.ch_info = ch_info; |
| devices[0].config.aud_fmt_id = PAL_AUDIO_FMT_PCM_S16_LE; |
| |
| devices[1].id = PAL_DEVICE_IN_SPEAKER_MIC; |
| |
| ret = pal_stream_open(&stream_tx_attr, |
| no_of_devices, devices, |
| 0, |
| NULL, |
| NULL, |
| 0, |
| &hfpmod.tx_stream_handle); |
| if (ret != 0) { |
| AHAL_ERR("HFP tx stream (Mic->BT SCO) open failed, rc %d", ret); |
| pal_stream_stop(hfpmod.rx_stream_handle); |
| pal_stream_close(hfpmod.rx_stream_handle); |
| hfpmod.rx_stream_handle = NULL; |
| return ret; |
| } |
| ret = pal_stream_start(hfpmod.tx_stream_handle); |
| if (ret != 0) { |
| AHAL_ERR("HFP tx stream (Mic->BT SCO) start failed, rc %d", ret); |
| pal_stream_close(hfpmod.tx_stream_handle); |
| pal_stream_stop(hfpmod.rx_stream_handle); |
| pal_stream_close(hfpmod.rx_stream_handle); |
| hfpmod.rx_stream_handle = NULL; |
| hfpmod.tx_stream_handle = NULL; |
| return ret; |
| } |
| hfpmod.mic_mute = false; |
| hfpmod.is_hfp_running = true; |
| hfp_set_volume(hfpmod.hfp_volume); |
| |
| AHAL_DBG("HFP start end"); |
| return ret; |
| } |
| |
| static int32_t stop_hfp() |
| { |
| int32_t ret = 0; |
| |
| AHAL_DBG("HFP stop enter"); |
| hfpmod.is_hfp_running = false; |
| if (hfpmod.rx_stream_handle) { |
| pal_stream_stop(hfpmod.rx_stream_handle); |
| pal_stream_close(hfpmod.rx_stream_handle); |
| hfpmod.rx_stream_handle = NULL; |
| } |
| if (hfpmod.tx_stream_handle) { |
| pal_stream_stop(hfpmod.tx_stream_handle); |
| pal_stream_close(hfpmod.tx_stream_handle); |
| hfpmod.tx_stream_handle = NULL; |
| } |
| |
| pal_param_btsco_t param_btsco; |
| |
| param_btsco.is_bt_hfp = true; |
| param_btsco.bt_sco_on = true; |
| ret = pal_set_param(PAL_PARAM_ID_BT_SCO, |
| (void*)¶m_btsco, |
| sizeof(pal_param_btsco_t)); |
| if (ret != 0) { |
| AHAL_ERR("Set PAL_PARAM_ID_BT_SCO failed"); |
| } |
| |
| pal_param_device_connection_t param_device_connection; |
| |
| param_device_connection.id = PAL_DEVICE_IN_BLUETOOTH_SCO_HEADSET; |
| param_device_connection.connection_state = false; |
| ret = pal_set_param(PAL_PARAM_ID_DEVICE_CONNECTION, |
| (void*)¶m_device_connection, |
| sizeof(pal_param_device_connection_t)); |
| if (ret != 0) { |
| AHAL_ERR("Set PAL_PARAM_ID_DEVICE_DISCONNECTION for %d failed", param_device_connection.id); |
| } |
| |
| param_device_connection.id = PAL_DEVICE_OUT_BLUETOOTH_SCO; |
| param_device_connection.connection_state = false; |
| ret = pal_set_param(PAL_PARAM_ID_DEVICE_CONNECTION, |
| (void*)¶m_device_connection, |
| sizeof(pal_param_device_connection_t)); |
| if (ret != 0) { |
| AHAL_ERR("Set PAL_PARAM_ID_DEVICE_DISCONNECTION for %d failed", param_device_connection.id); |
| } |
| |
| AHAL_DBG("HFP stop end"); |
| return ret; |
| } |
| |
| void hfp_init() |
| { |
| return; |
| } |
| |
| bool hfp_is_active(std::shared_ptr<AudioDevice> adev __unused) |
| { |
| return hfpmod.is_hfp_running; |
| } |
| |
| audio_usecase_t hfp_get_usecase() |
| { |
| return hfpmod.ucid; |
| } |
| |
| /*Set mic mute state. |
| * * |
| * * This interface is used for mic mute state control |
| * */ |
| int hfp_set_mic_mute(bool state) |
| { |
| int rc = 0; |
| |
| if (state == hfpmod.mic_mute) { |
| AHAL_DBG("mute state already %d", state); |
| return rc; |
| } |
| rc = hfp_set_mic_volume((state == true) ? 0.0 : hfpmod.mic_volume); |
| if (rc == 0) |
| hfpmod.mic_mute = state; |
| AHAL_DBG("Setting mute state %d, rc %d\n", state, rc); |
| return rc; |
| } |
| |
| int hfp_set_mic_mute2(std::shared_ptr<AudioDevice> adev __unused, bool state __unused) |
| { |
| AHAL_DBG("Unsupported\n"); |
| return 0; |
| } |
| |
| void hfp_set_parameters(std::shared_ptr<AudioDevice> adev, struct str_parms *parms) |
| { |
| int status = 0; |
| char value[32]={0}; |
| float vol; |
| int val; |
| int rate; |
| |
| AHAL_DBG("enter"); |
| |
| status = str_parms_get_str(parms, AUDIO_PARAMETER_HFP_ENABLE, value, |
| sizeof(value)); |
| if (status >= 0) { |
| if (!strncmp(value, "true", sizeof(value)) && !hfpmod.is_hfp_running) { |
| status = start_hfp(adev, parms); |
| /* |
| * Sync to adev mic mute state if hfpmod.mic_mute state is lost due |
| * to HFP session tear down during device switch on companion device. |
| */ |
| if (hfpmod.mic_mute != adev->mute_) { |
| AHAL_DBG("update mic mute with latest mute state = %d", adev->mute_); |
| hfp_set_mic_mute(adev->mute_); |
| } |
| } else if (!strncmp(value, "false", sizeof(value)) && hfpmod.is_hfp_running) { |
| stop_hfp(); |
| } else { |
| AHAL_ERR("hfp_enable=%s is unsupported", value); |
| } |
| } |
| |
| memset(value, 0, sizeof(value)); |
| status = str_parms_get_str(parms,AUDIO_PARAMETER_HFP_SET_SAMPLING_RATE, value, |
| sizeof(value)); |
| if (status >= 0) { |
| rate = atoi(value); |
| if (rate == 8000){ |
| hfpmod.ucid = USECASE_AUDIO_HFP_SCO; |
| hfpmod.sample_rate = (uint32_t) rate; |
| } else if (rate == 16000){ |
| hfpmod.ucid = USECASE_AUDIO_HFP_SCO_WB; |
| hfpmod.sample_rate = (uint32_t) rate; |
| } else |
| AHAL_ERR("Unsupported rate.. %d", rate); |
| } |
| |
| memset(value, 0, sizeof(value)); |
| status = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_HFP_VOLUME, |
| value, sizeof(value)); |
| if (status >= 0) { |
| if (sscanf(value, "%f", &vol) != 1){ |
| AHAL_ERR("error in retrieving hfp volume"); |
| status = -EIO; |
| goto exit; |
| } |
| AHAL_DBG("set_hfp_volume usecase, Vol: [%f]", vol); |
| hfp_set_volume(vol); |
| } |
| |
| memset(value, 0, sizeof(value)); |
| status = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_HFP_MIC_VOLUME, |
| value, sizeof(value)); |
| if (status >= 0) { |
| if (sscanf(value, "%f", &vol) != 1){ |
| AHAL_ERR("error in retrieving hfp mic volume"); |
| status = -EIO; |
| goto exit; |
| } |
| AHAL_DBG("set_hfp_mic_volume usecase, Vol: [%f]", vol); |
| if (hfp_set_mic_volume(vol) == 0) |
| hfpmod.mic_volume = vol; |
| } |
| |
| exit: |
| AHAL_VERBOSE("Exit"); |
| } |
| |
| #ifdef __cplusplus |
| } |
| #endif |