blob: 789592c516383155bf7f3a833c8b7699b4ea828b [file] [log] [blame]
/*
* 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*)&param_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*)&param_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*)&param_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*)&param_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*)&param_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*)&param_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*)&param_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