You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
598 lines
18 KiB
598 lines
18 KiB
/* |
|
* Copyright (C) 2014-2016 The Android Open Source Project |
|
* |
|
* 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 "voice_extn" |
|
/*#define LOG_NDEBUG 0*/ |
|
#define LOG_NDDEBUG 0 |
|
|
|
#include <errno.h> |
|
#include <math.h> |
|
#include <stdlib.h> |
|
#include <cutils/log.h> |
|
#include <cutils/str_parms.h> |
|
#include <sys/ioctl.h> |
|
#include <sound/voice_params.h> |
|
|
|
#include "audio_hw.h" |
|
#include "voice.h" |
|
#include "platform.h" |
|
#include "platform_api.h" |
|
#include "voice_extn.h" |
|
|
|
#define AUDIO_PARAMETER_KEY_VSID "vsid" |
|
#define AUDIO_PARAMETER_KEY_CALL_STATE "call_state" |
|
#define AUDIO_PARAMETER_KEY_AUDIO_MODE "audio_mode" |
|
#define AUDIO_PARAMETER_KEY_ALL_CALL_STATES "all_call_states" |
|
#define AUDIO_PARAMETER_KEY_DEVICE_MUTE "device_mute" |
|
#define AUDIO_PARAMETER_KEY_DIRECTION "direction" |
|
|
|
#define VOICE_EXTN_PARAMETER_VALUE_MAX_LEN 256 |
|
|
|
#define VOICE2_VSID 0x10DC1000 |
|
#define VOLTE_VSID 0x10C02000 |
|
#define QCHAT_VSID 0x10803000 |
|
#define VOWLAN_VSID 0x10002000 |
|
#define VOICEMMODE1_VSID 0x11C05000 |
|
#define VOICEMMODE2_VSID 0x11DC5000 |
|
#define ALL_VSID 0xFFFFFFFF |
|
|
|
/* Voice Session Indices */ |
|
#define VOICE2_SESS_IDX (VOICE_SESS_IDX + 1) |
|
#define VOLTE_SESS_IDX (VOICE_SESS_IDX + 2) |
|
#define QCHAT_SESS_IDX (VOICE_SESS_IDX + 3) |
|
#define VOWLAN_SESS_IDX (VOICE_SESS_IDX + 4) |
|
#define MMODE1_SESS_IDX (VOICE_SESS_IDX + 5) |
|
#define MMODE2_SESS_IDX (VOICE_SESS_IDX + 6) |
|
|
|
/* Call States */ |
|
#define CALL_HOLD (BASE_CALL_STATE + 2) |
|
#define CALL_LOCAL_HOLD (BASE_CALL_STATE + 3) |
|
|
|
struct pcm_config pcm_config_incall_music = { |
|
.channels = 1, |
|
.rate = DEFAULT_OUTPUT_SAMPLING_RATE, |
|
.period_size = LOW_LATENCY_OUTPUT_PERIOD_SIZE, |
|
.period_count = LOW_LATENCY_OUTPUT_PERIOD_COUNT, |
|
.format = PCM_FORMAT_S16_LE, |
|
.start_threshold = LOW_LATENCY_OUTPUT_PERIOD_SIZE / 4, |
|
.stop_threshold = INT_MAX, |
|
.avail_min = LOW_LATENCY_OUTPUT_PERIOD_SIZE / 4, |
|
}; |
|
|
|
int voice_extn_is_call_state_active(struct audio_device *adev, bool *is_call_active); |
|
|
|
static bool is_valid_call_state(int call_state) |
|
{ |
|
if (call_state < CALL_INACTIVE || call_state > CALL_LOCAL_HOLD) |
|
return false; |
|
else |
|
return true; |
|
} |
|
|
|
static bool is_valid_vsid(uint32_t vsid) |
|
{ |
|
if (vsid == VOICE_VSID || |
|
vsid == VOICE2_VSID || |
|
vsid == VOLTE_VSID || |
|
vsid == QCHAT_VSID || |
|
vsid == VOICEMMODE1_VSID || |
|
vsid == VOICEMMODE2_VSID || |
|
vsid == VOWLAN_VSID) |
|
return true; |
|
else |
|
return false; |
|
} |
|
|
|
static audio_usecase_t voice_extn_get_usecase_for_session_idx(const int index) |
|
{ |
|
audio_usecase_t usecase_id = -1; |
|
|
|
switch(index) { |
|
case VOICE_SESS_IDX: |
|
usecase_id = USECASE_VOICE_CALL; |
|
break; |
|
|
|
case VOICE2_SESS_IDX: |
|
usecase_id = USECASE_VOICE2_CALL; |
|
break; |
|
|
|
case VOLTE_SESS_IDX: |
|
usecase_id = USECASE_VOLTE_CALL; |
|
break; |
|
|
|
case QCHAT_SESS_IDX: |
|
usecase_id = USECASE_QCHAT_CALL; |
|
break; |
|
|
|
case VOWLAN_SESS_IDX: |
|
usecase_id = USECASE_VOWLAN_CALL; |
|
break; |
|
|
|
case MMODE1_SESS_IDX: |
|
usecase_id = USECASE_VOICEMMODE1_CALL; |
|
break; |
|
|
|
case MMODE2_SESS_IDX: |
|
usecase_id = USECASE_VOICEMMODE2_CALL; |
|
break; |
|
|
|
default: |
|
ALOGE("%s: Invalid voice session index\n", __func__); |
|
} |
|
|
|
return usecase_id; |
|
} |
|
|
|
static uint32_t get_session_id_with_state(struct audio_device *adev, |
|
int call_state) |
|
{ |
|
struct voice_session *session = NULL; |
|
int i = 0; |
|
uint32_t session_id = 0; |
|
|
|
for (i = 0; i < MAX_VOICE_SESSIONS; i++) { |
|
session = &adev->voice.session[i]; |
|
if(session->state.current == call_state){ |
|
session_id = session->vsid; |
|
break; |
|
} |
|
} |
|
|
|
return session_id; |
|
} |
|
|
|
static int update_calls(struct audio_device *adev) |
|
{ |
|
int i = 0; |
|
audio_usecase_t usecase_id = 0; |
|
enum voice_lch_mode lch_mode; |
|
struct voice_session *session = NULL; |
|
int fd = 0; |
|
int ret = 0; |
|
|
|
ALOGD("%s: enter:", __func__); |
|
|
|
for (i = 0; i < MAX_VOICE_SESSIONS; i++) { |
|
usecase_id = voice_extn_get_usecase_for_session_idx(i); |
|
session = &adev->voice.session[i]; |
|
ALOGD("%s: cur_state=%d new_state=%d vsid=%x", |
|
__func__, session->state.current, session->state.new, session->vsid); |
|
|
|
switch(session->state.new) |
|
{ |
|
case CALL_ACTIVE: |
|
switch(session->state.current) |
|
{ |
|
case CALL_INACTIVE: |
|
ALOGD("%s: INACTIVE -> ACTIVE vsid:%x", __func__, session->vsid); |
|
ret = voice_start_usecase(adev, usecase_id); |
|
if(ret < 0) { |
|
ALOGE("%s: voice_start_usecase() failed for usecase: %d\n", |
|
__func__, usecase_id); |
|
} else { |
|
session->state.current = session->state.new; |
|
} |
|
break; |
|
|
|
case CALL_HOLD: |
|
ALOGD("%s: HOLD -> ACTIVE vsid:%x", __func__, session->vsid); |
|
session->state.current = session->state.new; |
|
break; |
|
|
|
case CALL_LOCAL_HOLD: |
|
ALOGD("%s: LOCAL_HOLD -> ACTIVE vsid:%x", __func__, session->vsid); |
|
lch_mode = VOICE_LCH_STOP; |
|
if (pcm_ioctl(session->pcm_tx, SNDRV_VOICE_IOCTL_LCH, &lch_mode) < 0) { |
|
ALOGE("LOCAL_HOLD -> ACTIVE failed"); |
|
} else { |
|
session->state.current = session->state.new; |
|
} |
|
break; |
|
|
|
default: |
|
ALOGV("%s: CALL_ACTIVE cannot be handled in state=%d vsid:%x", |
|
__func__, session->state.current, session->vsid); |
|
break; |
|
} |
|
break; |
|
|
|
case CALL_INACTIVE: |
|
switch(session->state.current) |
|
{ |
|
case CALL_ACTIVE: |
|
case CALL_HOLD: |
|
case CALL_LOCAL_HOLD: |
|
ALOGD("%s: ACTIVE/HOLD/LOCAL_HOLD -> INACTIVE vsid:%x", __func__, session->vsid); |
|
ret = voice_stop_usecase(adev, usecase_id); |
|
if(ret < 0) { |
|
ALOGE("%s: voice_stop_usecase() failed for usecase: %d\n", |
|
__func__, usecase_id); |
|
} else { |
|
session->state.current = session->state.new; |
|
} |
|
break; |
|
|
|
default: |
|
ALOGV("%s: CALL_INACTIVE cannot be handled in state=%d vsid:%x", |
|
__func__, session->state.current, session->vsid); |
|
break; |
|
} |
|
break; |
|
|
|
case CALL_HOLD: |
|
switch(session->state.current) |
|
{ |
|
case CALL_ACTIVE: |
|
ALOGD("%s: CALL_ACTIVE -> HOLD vsid:%x", __func__, session->vsid); |
|
session->state.current = session->state.new; |
|
break; |
|
|
|
case CALL_LOCAL_HOLD: |
|
ALOGD("%s: CALL_LOCAL_HOLD -> HOLD vsid:%x", __func__, session->vsid); |
|
lch_mode = VOICE_LCH_STOP; |
|
if (pcm_ioctl(session->pcm_tx, SNDRV_VOICE_IOCTL_LCH, &lch_mode) < 0) { |
|
ALOGE("LOCAL_HOLD -> HOLD failed"); |
|
} else { |
|
session->state.current = session->state.new; |
|
} |
|
break; |
|
|
|
default: |
|
ALOGV("%s: CALL_HOLD cannot be handled in state=%d vsid:%x", |
|
__func__, session->state.current, session->vsid); |
|
break; |
|
} |
|
break; |
|
|
|
case CALL_LOCAL_HOLD: |
|
switch(session->state.current) |
|
{ |
|
case CALL_ACTIVE: |
|
case CALL_HOLD: |
|
ALOGD("%s: ACTIVE/CALL_HOLD -> LOCAL_HOLD vsid:%x", __func__, |
|
session->vsid); |
|
lch_mode = VOICE_LCH_START; |
|
if (pcm_ioctl(session->pcm_tx, SNDRV_VOICE_IOCTL_LCH, &lch_mode) < 0) { |
|
ALOGE("LOCAL_HOLD -> HOLD failed"); |
|
} else { |
|
session->state.current = session->state.new; |
|
} |
|
break; |
|
|
|
default: |
|
ALOGV("%s: CALL_LOCAL_HOLD cannot be handled in state=%d vsid:%x", |
|
__func__, session->state.current, session->vsid); |
|
break; |
|
} |
|
break; |
|
|
|
default: |
|
break; |
|
} //end out switch loop |
|
} //end for loop |
|
|
|
return ret; |
|
} |
|
|
|
static int update_call_states(struct audio_device *adev, |
|
const uint32_t vsid, const int call_state) |
|
{ |
|
struct voice_session *session = NULL; |
|
int i = 0; |
|
bool is_call_active; |
|
|
|
for (i = 0; i < MAX_VOICE_SESSIONS; i++) { |
|
if (vsid == adev->voice.session[i].vsid) { |
|
session = &adev->voice.session[i]; |
|
break; |
|
} |
|
} |
|
|
|
if (session) { |
|
session->state.new = call_state; |
|
voice_extn_is_call_state_active(adev, &is_call_active); |
|
ALOGD("%s is_call_active:%d in_call:%d, mode:%d\n", |
|
__func__, is_call_active, adev->voice.in_call, adev->mode); |
|
/* Dont start voice call before device routing for voice usescases has |
|
* occured, otherwise voice calls will be started unintendedly on |
|
* speaker. |
|
*/ |
|
if (is_call_active || |
|
(adev->voice.in_call && adev->mode == AUDIO_MODE_IN_CALL)) { |
|
/* Device routing is not triggered for voice calls on the subsequent |
|
* subs, Hence update the call states if voice call is already |
|
* active on other sub. |
|
*/ |
|
update_calls(adev); |
|
} |
|
} else { |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
|
|
} |
|
|
|
int voice_extn_get_active_session_id(struct audio_device *adev, |
|
uint32_t *session_id) |
|
{ |
|
*session_id = get_session_id_with_state(adev, CALL_ACTIVE); |
|
return 0; |
|
} |
|
|
|
int voice_extn_is_call_state_active(struct audio_device *adev, bool *is_call_active) |
|
{ |
|
struct voice_session *session = NULL; |
|
int i = 0; |
|
*is_call_active = false; |
|
|
|
for (i = 0; i < MAX_VOICE_SESSIONS; i++) { |
|
session = &adev->voice.session[i]; |
|
if(session->state.current != CALL_INACTIVE){ |
|
*is_call_active = true; |
|
break; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int voice_extn_is_in_call_rec_stream(struct stream_in *in, bool *in_call_rec) |
|
{ |
|
*in_call_rec = false; |
|
|
|
if(in->source == AUDIO_SOURCE_VOICE_DOWNLINK || |
|
in->source == AUDIO_SOURCE_VOICE_UPLINK || |
|
in->source == AUDIO_SOURCE_VOICE_CALL) { |
|
*in_call_rec = true; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
void voice_extn_init(struct audio_device *adev) |
|
{ |
|
adev->voice.session[VOICE_SESS_IDX].vsid = VOICE_VSID; |
|
adev->voice.session[VOICE2_SESS_IDX].vsid = VOICE2_VSID; |
|
adev->voice.session[VOLTE_SESS_IDX].vsid = VOLTE_VSID; |
|
adev->voice.session[QCHAT_SESS_IDX].vsid = QCHAT_VSID; |
|
adev->voice.session[VOWLAN_SESS_IDX].vsid = VOWLAN_VSID; |
|
adev->voice.session[MMODE1_SESS_IDX].vsid = VOICEMMODE1_VSID; |
|
adev->voice.session[MMODE2_SESS_IDX].vsid = VOICEMMODE2_VSID; |
|
} |
|
|
|
int voice_extn_get_session_from_use_case(struct audio_device *adev, |
|
const audio_usecase_t usecase_id, |
|
struct voice_session **session) |
|
{ |
|
|
|
switch(usecase_id) |
|
{ |
|
case USECASE_VOICE_CALL: |
|
*session = &adev->voice.session[VOICE_SESS_IDX]; |
|
break; |
|
|
|
case USECASE_VOICE2_CALL: |
|
*session = &adev->voice.session[VOICE2_SESS_IDX]; |
|
break; |
|
|
|
case USECASE_VOLTE_CALL: |
|
*session = &adev->voice.session[VOLTE_SESS_IDX]; |
|
break; |
|
|
|
case USECASE_QCHAT_CALL: |
|
*session = &adev->voice.session[QCHAT_SESS_IDX]; |
|
break; |
|
|
|
case USECASE_VOWLAN_CALL: |
|
*session = &adev->voice.session[VOWLAN_SESS_IDX]; |
|
break; |
|
|
|
case USECASE_VOICEMMODE1_CALL: |
|
*session = &adev->voice.session[MMODE1_SESS_IDX]; |
|
break; |
|
|
|
case USECASE_VOICEMMODE2_CALL: |
|
*session = &adev->voice.session[MMODE2_SESS_IDX]; |
|
break; |
|
|
|
default: |
|
ALOGE("%s: Invalid usecase_id:%d\n", __func__, usecase_id); |
|
*session = NULL; |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int voice_extn_start_call(struct audio_device *adev) |
|
{ |
|
/* Start voice calls on sessions whose call state has been |
|
* udpated. |
|
*/ |
|
ALOGV("%s: enter:", __func__); |
|
return update_calls(adev); |
|
} |
|
|
|
int voice_extn_stop_call(struct audio_device *adev) |
|
{ |
|
int i; |
|
int ret = 0; |
|
|
|
ALOGV("%s: enter:", __func__); |
|
|
|
/* If BT device is enabled and voice calls are ended, telephony will call |
|
* set_mode(AUDIO_MODE_NORMAL) which will trigger audio policy manager to |
|
* set routing with device BT A2DP profile. Hence end all voice calls when |
|
* set_mode(AUDIO_MODE_NORMAL) before BT A2DP profile is selected. |
|
*/ |
|
if (adev->mode == AUDIO_MODE_NORMAL) { |
|
ALOGD("%s: end all calls", __func__); |
|
for (i = 0; i < MAX_VOICE_SESSIONS; i++) { |
|
adev->voice.session[i].state.new = CALL_INACTIVE; |
|
} |
|
|
|
ret = update_calls(adev); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
int voice_extn_set_parameters(struct audio_device *adev, |
|
struct str_parms *parms) |
|
{ |
|
char *str; |
|
int value; |
|
int ret = 0, err; |
|
char *kv_pairs = str_parms_to_str(parms); |
|
char str_value[256] = {0}; |
|
|
|
ALOGV_IF(kv_pairs != NULL, "%s: enter: %s", __func__, kv_pairs); |
|
|
|
err = str_parms_get_int(parms, AUDIO_PARAMETER_KEY_VSID, &value); |
|
if (err >= 0) { |
|
str_parms_del(parms, AUDIO_PARAMETER_KEY_VSID); |
|
uint32_t vsid = value; |
|
int call_state = -1; |
|
err = str_parms_get_int(parms, AUDIO_PARAMETER_KEY_CALL_STATE, &value); |
|
if (err >= 0) { |
|
call_state = value; |
|
str_parms_del(parms, AUDIO_PARAMETER_KEY_CALL_STATE); |
|
} else { |
|
ALOGE("%s: call_state key not found", __func__); |
|
ret = -EINVAL; |
|
goto done; |
|
} |
|
|
|
if (is_valid_vsid(vsid) && is_valid_call_state(call_state)) { |
|
ret = update_call_states(adev, vsid, call_state); |
|
} else { |
|
ALOGE("%s: invalid vsid:%x or call_state:%d", |
|
__func__, vsid, call_state); |
|
ret = -EINVAL; |
|
goto done; |
|
} |
|
} |
|
|
|
err = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_DEVICE_MUTE, str_value, |
|
sizeof(str_value)); |
|
if (err >= 0) { |
|
str_parms_del(parms, AUDIO_PARAMETER_KEY_DEVICE_MUTE); |
|
bool mute = false; |
|
|
|
if (!strncmp("true", str_value, sizeof("true"))) { |
|
mute = true; |
|
} |
|
|
|
err = str_parms_get_str(parms, AUDIO_PARAMETER_KEY_DIRECTION, str_value, |
|
sizeof(str_value)); |
|
if (err >= 0) { |
|
str_parms_del(parms, AUDIO_PARAMETER_KEY_DIRECTION); |
|
} else { |
|
ALOGE("%s: direction key not found", __func__); |
|
ret = -EINVAL; |
|
goto done; |
|
} |
|
|
|
ret = platform_set_device_mute(adev->platform, mute, str_value); |
|
if (ret != 0) { |
|
ALOGE("%s: Failed to set mute err:%d", __func__, ret); |
|
ret = -EINVAL; |
|
goto done; |
|
} |
|
} |
|
|
|
done: |
|
ALOGV("%s: exit with code(%d)", __func__, ret); |
|
free(kv_pairs); |
|
return ret; |
|
} |
|
|
|
static int get_all_call_states_str(const struct audio_device *adev, |
|
char *value) |
|
{ |
|
int ret = 0; |
|
char *cur_ptr = value; |
|
int i, len=0; |
|
|
|
for (i = 0; i < MAX_VOICE_SESSIONS; i++) { |
|
snprintf(cur_ptr, VOICE_EXTN_PARAMETER_VALUE_MAX_LEN - len, |
|
"%d:%d,",adev->voice.session[i].vsid, |
|
adev->voice.session[i].state.current); |
|
len = strlen(cur_ptr); |
|
cur_ptr = cur_ptr + len; |
|
} |
|
ALOGV("%s:value=%s", __func__, value); |
|
return ret; |
|
} |
|
|
|
void voice_extn_get_parameters(const struct audio_device *adev, |
|
struct str_parms *query, |
|
struct str_parms *reply) |
|
{ |
|
int ret; |
|
char value[VOICE_EXTN_PARAMETER_VALUE_MAX_LEN] = {0}; |
|
char *str = str_parms_to_str(query); |
|
|
|
ALOGV_IF(str != NULL, "%s: enter %s", __func__, str); |
|
free(str); |
|
|
|
ret = str_parms_get_str(query, AUDIO_PARAMETER_KEY_AUDIO_MODE, value, |
|
sizeof(value)); |
|
if (ret >= 0) { |
|
str_parms_add_int(reply, AUDIO_PARAMETER_KEY_AUDIO_MODE, adev->mode); |
|
} |
|
|
|
ret = str_parms_get_str(query, AUDIO_PARAMETER_KEY_ALL_CALL_STATES, |
|
value, sizeof(value)); |
|
if (ret >= 0) { |
|
ret = get_all_call_states_str(adev, value); |
|
if (ret) { |
|
ALOGE("%s: Error fetching call states, err:%d", __func__, ret); |
|
return; |
|
} |
|
str_parms_add_str(reply, AUDIO_PARAMETER_KEY_ALL_CALL_STATES, value); |
|
} |
|
|
|
str = str_parms_to_str(reply); |
|
ALOGV_IF(str != NULL, "%s: exit: returns \"%s\"", __func__, str); |
|
free(str); |
|
} |
|
|
|
#ifdef INCALL_MUSIC_ENABLED |
|
int voice_extn_check_and_set_incall_music_usecase(struct audio_device *adev, |
|
struct stream_out *out) |
|
{ |
|
uint32_t session_id = 0; |
|
|
|
session_id = get_session_id_with_state(adev, CALL_LOCAL_HOLD); |
|
if (session_id == VOICE_VSID) { |
|
out->usecase = USECASE_INCALL_MUSIC_UPLINK; |
|
} else if (session_id == VOICE2_VSID) { |
|
out->usecase = USECASE_INCALL_MUSIC_UPLINK2; |
|
} else { |
|
ALOGE("%s: Invalid session id %x", __func__, session_id); |
|
return -EINVAL; |
|
} |
|
|
|
out->config = pcm_config_incall_music; |
|
out->supported_channel_masks[0] = AUDIO_CHANNEL_OUT_MONO; |
|
out->channel_mask = AUDIO_CHANNEL_OUT_MONO; |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
|