blob: 037b0e5a1c5b5afa33dd67d0eebbe91f20ea0366 [file] [log] [blame]
/*
* Copyright (c) 2019-2021, 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.
*/
#define LOG_TAG "auto_hal_extn"
/*#define LOG_NDEBUG 0*/
#include <errno.h>
#include <pthread.h>
#include <log/log.h>
#include <math.h>
#include <audio_hw.h>
#include <tinyalsa/asoundlib.h>
#include "audio_extn.h"
#include "platform_api.h"
#include "platform.h"
#include "audio_hal_plugin.h"
#include "auto_hal.h"
#ifdef DYNAMIC_LOG_ENABLED
#include <log_xml_parser.h>
#define LOG_MASK HAL_MOD_FILE_AUTO_HAL
#include <log_utils.h>
#endif
#define MAX_VOLUME 1.995262
#define DSP_MAX_VOLUME 0x2000
#define DUCKED_VOLUME 0.0035
enum {
AUDIO_DEVICE_DUCKED = 0,
AUDIO_DEVICE_UNDUCKED,
AUDIO_DEVICE_MUTED,
AUDIO_DEVICE_UNMUTED
};
//external feature dependency
static fp_audio_extn_ext_hw_plugin_usecase_start_t fp_audio_extn_ext_hw_plugin_usecase_start;
static fp_audio_extn_ext_hw_plugin_usecase_stop_t fp_audio_extn_ext_hw_plugin_usecase_stop;
static fp_get_usecase_from_list_t fp_get_usecase_from_list;
static fp_get_output_period_size_t fp_get_output_period_size;
static fp_audio_extn_ext_hw_plugin_set_audio_gain_t fp_audio_extn_ext_hw_plugin_set_audio_gain;
static fp_select_devices_t fp_select_devices;
static fp_disable_audio_route_t fp_disable_audio_route;
static fp_disable_snd_device_t fp_disable_snd_device;
static fp_adev_get_active_input_t fp_adev_get_active_input;
static fp_platform_set_echo_reference_t fp_platform_set_echo_reference;
static fp_platform_get_eccarstate_t fp_platform_get_eccarstate;
static fp_generate_patch_handle_t fp_generate_patch_handle;
static fp_platform_get_pcm_device_id_t fp_platform_get_pcm_device_id;
/* Auto hal module struct */
static struct auto_hal_module *auto_hal = NULL;
int auto_hal_release_audio_patch(struct audio_hw_device *dev,
audio_patch_handle_t handle);
int auto_hal_stop_hfp_downlink(struct audio_device *adev,
struct audio_usecase *uc_info);
static struct audio_patch_record *get_patch_from_list(struct audio_device *adev,
audio_patch_handle_t patch_id)
{
struct audio_patch_record *patch;
struct listnode *node;
list_for_each(node, &adev->audio_patch_record_list) {
patch = node_to_item(node, struct audio_patch_record, list);
if (patch->handle == patch_id)
return patch;
}
return NULL;
}
int auto_hal_create_audio_patch(struct audio_hw_device *dev,
unsigned int num_sources,
const struct audio_port_config *sources,
unsigned int num_sinks,
const struct audio_port_config *sinks,
audio_patch_handle_t *handle)
{
struct audio_device *adev = (struct audio_device *)dev;
int ret = 0;
struct audio_usecase *uc_info = NULL;
struct audio_patch_record *patch_record = NULL;
audio_usecase_t usecase = USECASE_INVALID;
ALOGV("%s: enter", __func__);
if (!dev || !sources || !sinks || !handle ) {
ALOGE("%s: null audio patch parameters", __func__);
return -EINVAL;
}
/* Port configuration check & validation */
if (num_sources > MAX_SOURCE_PORTS_PER_PATCH ||
num_sinks > MAX_SINK_PORTS_PER_PATCH) {
ALOGE("%s: invalid audio patch parameters, sources %d sinks %d ",
__func__, num_sources, num_sources);
return -EINVAL;
}
/* No validation on num of sources and sinks to allow patch with
* multiple sinks being created, but only the first source and
* sink are used to create patch.
*
* Stream set_parameters for AUDIO_PARAMETER_STREAM_ROUTING and
* AUDIO_PARAMETER_STREAM_INPUT_SOURCE is replaced with audio_patch
* callback in audioflinger for AUDIO_DEVICE_API_VERSION_3_0 and above.
* Need to handle device routing notification in audio HAL for
* Capture: DEVICE -> MIX
* Playback: MIX -> DEVICE
* For DEVICE -> DEVICE patch type, it refers to routing from/to external
* codec/amplifier and allow Android streams to be mixed at the H/W level.
*
* Auto extension here is to act on Device to Device patch only as playback
* and capture patches as well as the book-keeeping information are already
* being handled at audio_hw entry.
*/
if ((sources->type == AUDIO_PORT_TYPE_DEVICE) &&
(sinks->type == AUDIO_PORT_TYPE_DEVICE)) {
/* allocate use case and call to plugin driver*/
uc_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
if (!uc_info) {
ALOGE("%s fail to allocate uc_info", __func__);
return -ENOMEM;
}
/* TODO - add sink type check and printout for non speaker sink */
switch(sources->ext.device.type) {
#ifdef FM_TUNER_EXT_ENABLED
case AUDIO_DEVICE_IN_FM_TUNER:
ALOGV("Creating audio patch for external FM tuner");
uc_info->id = USECASE_AUDIO_FM_TUNER_EXT;
uc_info->type = PCM_PASSTHROUGH;
list_init(&uc_info->device_list);
reassign_device_list(&uc_info->device_list, AUDIO_DEVICE_IN_FM_TUNER,
sources->ext.device.address);
uc_info->in_snd_device = SND_DEVICE_IN_CAPTURE_FM;
uc_info->out_snd_device = SND_DEVICE_OUT_BUS_MEDIA;
break;
#endif
default:
ALOGE("%s: Unsupported audio source type %x", __func__,
sources->ext.device.type);
goto error;
}
ALOGD("%s: Starting ext hw plugin use case (%d) in_snd_device (%d) out_snd_device (%d)",
__func__, uc_info->id, uc_info->in_snd_device, uc_info->out_snd_device);
ret = fp_audio_extn_ext_hw_plugin_usecase_start(adev->ext_hw_plugin, uc_info);
if (ret) {
ALOGE("%s: failed to start ext hw plugin use case (%d)",
__func__, uc_info->id);
goto error;
}
/* TODO: apply audio port gain to codec if applicable */
usecase = uc_info->id;
pthread_mutex_lock(&adev->lock);
list_add_tail(&adev->usecase_list, &uc_info->list);
pthread_mutex_unlock(&adev->lock);
} else {
ALOGV("%s: audio patch not supported", __func__);
goto exit;
}
/* patch created success, add to patch record list */
patch_record = (struct audio_patch_record *)calloc(1,
sizeof(struct audio_patch_record));
if (!patch_record) {
ALOGE("%s fail to allocate patch_record", __func__);
ret = -ENOMEM;
if (uc_info)
list_remove(&uc_info->list);
goto error;
}
pthread_mutex_lock(&adev->lock);
if (*handle == AUDIO_PATCH_HANDLE_NONE) {
ALOGD("%s: audio patch handle not allocated 0x%x", __func__, *handle);
*handle = fp_generate_patch_handle();
}
patch_record->handle = *handle;
patch_record->usecase = usecase;
patch_record->patch.id = *handle;
patch_record->patch.num_sources = num_sources;
patch_record->patch.num_sinks = num_sinks;
for (int i = 0; i < num_sources; i++)
patch_record->patch.sources[i] = sources[i];
for (int i = 0; i < num_sinks; i++)
patch_record->patch.sinks[i] = sinks[i];
list_add_tail(&adev->audio_patch_record_list, &patch_record->list);
pthread_mutex_unlock(&adev->lock);
goto exit;
error:
if(uc_info)
free(uc_info);
exit:
ALOGV("%s: exit: handle 0x%x", __func__, *handle);
return ret;
}
int auto_hal_release_audio_patch(struct audio_hw_device *dev,
audio_patch_handle_t handle)
{
int ret = 0;
struct audio_device *adev = (struct audio_device *)dev;
struct audio_usecase *uc_info = NULL;
struct audio_patch_record *patch_record = NULL;
ALOGV("%s: enter: handle 0x%x", __func__, handle);
if (!dev) {
ALOGE("%s: null audio patch parameters", __func__);
return -EINVAL;
}
if (handle == AUDIO_PATCH_HANDLE_NONE) {
ALOGW("%s: null audio patch handle", __func__);
return -EINVAL;
}
/* get the patch record from handle */
pthread_mutex_lock(&adev->lock);
patch_record = get_patch_from_list(adev, handle);
if(!patch_record) {
ALOGE("%s: failed to find the patch record with handle (%d) in the list",
__func__, handle);
ret = -EINVAL;
}
pthread_mutex_unlock(&adev->lock);
if(ret)
goto exit;
if (patch_record->usecase != USECASE_INVALID) {
pthread_mutex_lock(&adev->lock);
uc_info = fp_get_usecase_from_list(adev, patch_record->usecase);
if (!uc_info) {
ALOGE("%s: failed to find the usecase (%d)",
__func__, patch_record->usecase);
ret = -EINVAL;
} else {
/* call to plugin to stop the usecase */
ret = fp_audio_extn_ext_hw_plugin_usecase_stop(adev->ext_hw_plugin, uc_info);
if (ret) {
ALOGE("%s: failed to stop ext hw plugin use case (%d)",
__func__, uc_info->id);
}
/* remove usecase from list and free it */
list_remove(&uc_info->list);
free(uc_info);
}
pthread_mutex_unlock(&adev->lock);
}
/* remove the patch record from list and free it */
pthread_mutex_lock(&adev->lock);
list_remove(&patch_record->list);
pthread_mutex_unlock(&adev->lock);
free(patch_record);
exit:
ALOGV("%s: exit", __func__);
return ret;
}
int auto_hal_get_car_audio_stream_from_address(const char *address)
{
int bus_num = -1;
char *str = NULL;
char *last_r = NULL;
char local_address[AUDIO_DEVICE_MAX_ADDRESS_LEN];
/* bus device with null address error out */
if (address == NULL) {
ALOGE("%s: null address for car stream", __func__);
return -1;
}
/* strtok will modify the original string. make a copy first */
strlcpy(local_address, address, AUDIO_DEVICE_MAX_ADDRESS_LEN);
/* extract bus number from address */
str = strtok_r(local_address, "BUS_",&last_r);
if (str != NULL)
bus_num = (int)strtol(str, (char **)NULL, 10);
/* validate bus number */
if ((bus_num < 0) || (bus_num >= MAX_CAR_AUDIO_STREAMS)) {
ALOGE("%s: invalid bus number %d", __func__, bus_num);
return -1;
}
return (0x1 << bus_num);
}
int auto_hal_open_input_stream(struct stream_in *in)
{
int ret = 0;
switch(in->car_audio_stream) {
case CAR_AUDIO_STREAM_IN_PRIMARY:
in->usecase = USECASE_AUDIO_RECORD_BUS;
if (in->flags == AUDIO_INPUT_FLAG_NONE)
in->flags |= AUDIO_INPUT_FLAG_PRIMARY;
break;
case CAR_AUDIO_STREAM_IN_FRONT_PASSENGER:
in->usecase = USECASE_AUDIO_RECORD_BUS_FRONT_PASSENGER;
if (in->flags == AUDIO_INPUT_FLAG_NONE)
in->flags |= AUDIO_INPUT_FLAG_FRONT_PASSENGER;
break;
case CAR_AUDIO_STREAM_IN_REAR_SEAT:
in->usecase = USECASE_AUDIO_RECORD_BUS_REAR_SEAT;
if (in->flags == AUDIO_INPUT_FLAG_NONE)
in->flags |= AUDIO_INPUT_FLAG_REAR_SEAT;
break;
default:
ALOGE("%s: Car audio stream 0x%x not supported", __func__,
in->car_audio_stream);
ret = -EINVAL;
}
return ret;
}
/*
* Function: auto_hal_open_echo_reference_stream
* ---------------------------------------------
* opens an input stream to capture an echo reference
* and sets for external echo reference
*
* param *in: stream to be used for echo reference
*
* returns: 0
*/
int auto_hal_open_echo_reference_stream(struct stream_in *in)
{
/* note: this function may be expanded in the future
to accommodate other echo reference sources
such as an internal AFE loopback. */
in->usecase = USECASE_AUDIO_RECORD_ECHO_REF_EXT;
return 0;
}
int auto_hal_open_output_stream(struct stream_out *out)
{
int ret = 0;
unsigned int channels = audio_channel_count_from_out_mask(out->channel_mask);
switch(out->car_audio_stream) {
case CAR_AUDIO_STREAM_MEDIA:
/* media bus stream shares pcm device with deep-buffer */
out->usecase = USECASE_AUDIO_PLAYBACK_MEDIA;
out->config = pcm_config_media;
out->config.period_size = fp_get_output_period_size(out->sample_rate, out->format,
channels, DEEP_BUFFER_OUTPUT_PERIOD_DURATION);
if (out->config.period_size <= 0) {
ALOGE("Invalid configuration period size is not valid");
ret = -EINVAL;
goto error;
}
if (out->flags == AUDIO_OUTPUT_FLAG_NONE ||
out->flags == AUDIO_OUTPUT_FLAG_PRIMARY)
out->flags |= AUDIO_OUTPUT_FLAG_MEDIA;
out->volume_l = out->volume_r = MAX_VOLUME_GAIN;
break;
case CAR_AUDIO_STREAM_SYS_NOTIFICATION:
/* sys notification bus stream shares pcm device with low-latency */
out->usecase = USECASE_AUDIO_PLAYBACK_SYS_NOTIFICATION;
switch(out->sample_rate)
{
case 48000:
out->config=pcm_config_system_48KHz;
break;
case 32000:
out->config=pcm_config_system_32KHz;
break;
case 24000:
out->config=pcm_config_system_24KHz;
break;
case 16000:
out->config=pcm_config_system_16KHz;
break;
case 8000:
out->config=pcm_config_system_8KHz;
break;
default:
out->config=pcm_config_system_48KHz;
}
if (out->flags == AUDIO_OUTPUT_FLAG_NONE)
out->flags |= AUDIO_OUTPUT_FLAG_SYS_NOTIFICATION;
out->volume_l = out->volume_r = MAX_VOLUME_GAIN;
break;
case CAR_AUDIO_STREAM_NAV_GUIDANCE:
out->usecase = USECASE_AUDIO_PLAYBACK_NAV_GUIDANCE;
out->config = pcm_config_media;
out->config.period_size = fp_get_output_period_size(out->sample_rate, out->format,
channels, DEEP_BUFFER_OUTPUT_PERIOD_DURATION);
if (out->config.period_size <= 0) {
ALOGE("Invalid configuration period size is not valid");
ret = -EINVAL;
goto error;
}
if (out->flags == AUDIO_OUTPUT_FLAG_NONE)
out->flags |= AUDIO_OUTPUT_FLAG_NAV_GUIDANCE;
out->volume_l = out->volume_r = MAX_VOLUME_GAIN;
break;
case CAR_AUDIO_STREAM_PHONE:
out->usecase = USECASE_AUDIO_PLAYBACK_PHONE;
switch(out->sample_rate)
{
case 48000:
out->config=pcm_config_system_48KHz;
break;
case 32000:
out->config=pcm_config_system_32KHz;
break;
case 24000:
out->config=pcm_config_system_24KHz;
break;
case 16000:
out->config=pcm_config_system_16KHz;
break;
case 8000:
out->config=pcm_config_system_8KHz;
break;
default:
out->config=pcm_config_system_48KHz;
}
if (out->flags == AUDIO_OUTPUT_FLAG_NONE)
out->flags |= AUDIO_OUTPUT_FLAG_PHONE;
out->volume_l = out->volume_r = MAX_VOLUME_GAIN;
break;
case CAR_AUDIO_STREAM_ALERTS:
out->usecase = USECASE_AUDIO_PLAYBACK_ALERTS;
out->config = pcm_config_system;
if (out->flags == AUDIO_OUTPUT_FLAG_NONE)
out->flags |= AUDIO_OUTPUT_FLAG_ALERTS;
out->volume_l = out->volume_r = MAX_VOLUME_GAIN;
break;
case CAR_AUDIO_STREAM_FRONT_PASSENGER:
out->usecase = USECASE_AUDIO_PLAYBACK_FRONT_PASSENGER;
out->config = pcm_config_system;
if (out->flags == AUDIO_OUTPUT_FLAG_NONE)
out->flags |= AUDIO_OUTPUT_FLAG_FRONT_PASSENGER;
break;
case CAR_AUDIO_STREAM_REAR_SEAT:
out->usecase = USECASE_AUDIO_PLAYBACK_REAR_SEAT;
out->config = pcm_config_media;
out->config.period_size = fp_get_output_period_size(out->sample_rate, out->format,
channels, DEEP_BUFFER_OUTPUT_PERIOD_DURATION);
if (out->config.period_size <= 0) {
ALOGE("Invalid configuration period size is not valid");
ret = -EINVAL;
goto error;
}
if (out->flags == AUDIO_OUTPUT_FLAG_NONE)
out->flags |= AUDIO_OUTPUT_FLAG_REAR_SEAT;
out->volume_l = out->volume_r = MAX_VOLUME_GAIN;
break;
default:
ALOGE("%s: Car audio stream %x not supported", __func__,
out->car_audio_stream);
ret = -EINVAL;
goto error;
}
error:
return ret;
}
bool auto_hal_is_bus_device_usecase(audio_usecase_t uc_id)
{
unsigned int i;
for (i = 0; i < sizeof(bus_device_usecases)/sizeof(bus_device_usecases[0]); i++) {
if (uc_id == bus_device_usecases[i])
return true;
}
return false;
}
snd_device_t auto_hal_get_snd_device_for_car_audio_stream(int car_audio_stream)
{
snd_device_t snd_device = SND_DEVICE_NONE;
switch(car_audio_stream) {
case CAR_AUDIO_STREAM_MEDIA:
snd_device = SND_DEVICE_OUT_BUS_MEDIA;
break;
case CAR_AUDIO_STREAM_SYS_NOTIFICATION:
snd_device = SND_DEVICE_OUT_BUS_SYS;
break;
case CAR_AUDIO_STREAM_NAV_GUIDANCE:
snd_device = SND_DEVICE_OUT_BUS_NAV;
break;
case CAR_AUDIO_STREAM_PHONE:
snd_device = SND_DEVICE_OUT_BUS_PHN;
break;
case CAR_AUDIO_STREAM_ALERTS:
snd_device = SND_DEVICE_OUT_BUS_ALR;
break;
case CAR_AUDIO_STREAM_FRONT_PASSENGER:
snd_device = SND_DEVICE_OUT_BUS_PAX;
break;
case CAR_AUDIO_STREAM_REAR_SEAT:
snd_device = SND_DEVICE_OUT_BUS_RSE;
break;
case CAR_AUDIO_STREAM_IN_PRIMARY:
snd_device = SND_DEVICE_IN_BUS;
break;
case CAR_AUDIO_STREAM_IN_FRONT_PASSENGER:
snd_device = SND_DEVICE_IN_BUS_PAX;
break;
case CAR_AUDIO_STREAM_IN_REAR_SEAT:
snd_device = SND_DEVICE_IN_BUS_RSE;
break;
default:
ALOGE("%s: Unknown car audio stream (%#x)",
__func__, car_audio_stream);
}
return snd_device;
}
bool auto_hal_overwrite_priority_for_auto(struct stream_in *in)
{
/* Don't use the priority_in stream when the source is
* AUDIO_SOURCE_ECHO_REFERENCE because the platform_get_input_snd_device
* call (below) needs to set the snd_device based the echo ref stream and
* NOT based on higher priority streams (such as concurrent recording
* streams from the mic) */
return (in->source == AUDIO_SOURCE_ECHO_REFERENCE);
}
int auto_hal_get_audio_port(struct audio_hw_device *dev __unused,
struct audio_port *config __unused)
{
return -ENOSYS;
}
int auto_hal_set_audio_port_config(struct audio_hw_device *dev,
const struct audio_port_config *config)
{
struct audio_device *adev = (struct audio_device *)dev;
int ret = 0;
struct listnode *node = NULL;
float volume = 0.0;
ALOGV("%s: enter", __func__);
if (!config) {
ALOGE("%s: invalid input parameters", __func__);
return -EINVAL;
}
/* For Android automotive, audio port config from car framework
* allows volume gain to be set to device at audio HAL level, where
* the gain can be applied in DSP mixer or CODEC amplifier.
*
* Following routing should be considered:
* MIX -> DEVICE
* DEVICE -> MIX
* DEVICE -> DEVICE
*
* For BUS devices routed to/from mixer, gain will be applied to DSP
* mixer via kernel control which audio HAL stream is associated with.
*
* For external (source) device (FM TUNER/AUX), routing is typically
* done with AudioPatch to (sink) device (SPKR), thus gain should be
* applied to CODEC amplifier via codec plugin extention as audio HAL
* stream may not be available for external audio routing.
*/
if (config->type == AUDIO_PORT_TYPE_DEVICE) {
ALOGI("%s: device port: type %x, address %s, gain %d mB", __func__,
config->ext.device.type,
config->ext.device.address,
config->gain.values[0]);
if (config->role == AUDIO_PORT_ROLE_SINK) {
/* handle output devices */
pthread_mutex_lock(&adev->lock);
list_for_each(node, &adev->active_outputs_list) {
streams_output_ctxt_t *out_ctxt = node_to_item(node,
streams_output_ctxt_t,
list);
/* limit audio gain support for bus device only */
if (config->ext.device.type == AUDIO_DEVICE_OUT_BUS &&
compare_device_type_and_address(&out_ctxt->output->device_list,
config->ext.device.type,
config->ext.device.address)) {
/* millibel = 1/100 dB = 1/1000 bel
* q13 = (10^(mdb/100/20))*(2^13)
*/
if(config->gain.values[0] <= (MIN_VOLUME_VALUE_MB + STEP_VALUE_MB))
volume = MIN_VOLUME_GAIN;
else
volume = powf(10.0f, ((float)config->gain.values[0] / 2000));
ALOGV("%s: set volume to stream: %p", __func__,
&out_ctxt->output->stream);
/* set gain if output stream is active */
out_ctxt->output->stream.set_volume(
&out_ctxt->output->stream,
volume, volume);
}
}
/* NOTE: Ideally audio patch list is a superset of output stream list above.
* However, audio HAL does not maintain patches for mix -> device or
* device -> mix currently. Thus doing separate lookups for device ->
* device in audio patch list.
* FIXME: Cannot cache the gain if audio patch is not created. Expected gain
* to be part of port config upon audio patch creation. If not, need
* to create a list of audio port configs in adev context.
*/
list_for_each(node, &adev->audio_patch_record_list) {
struct audio_patch_record *patch_record = node_to_item(node,
struct audio_patch_record,
list);
/* limit audio gain support for device -> bus device patch */
if (patch_record->patch.sources[0].type == AUDIO_PORT_TYPE_DEVICE &&
patch_record->patch.sinks[0].type == AUDIO_PORT_TYPE_DEVICE &&
patch_record->patch.sinks[0].role == AUDIO_PORT_ROLE_SINK &&
patch_record->patch.sinks[0].ext.device.type == AUDIO_DEVICE_OUT_BUS &&
patch_record->patch.sinks[0].ext.device.type == config->ext.device.type &&
strcmp(patch_record->patch.sinks[0].ext.device.address,
config->ext.device.address) == 0) {
/* cache audio port configuration for sink */
memcpy((void *)&patch_record->patch.sinks[0], (void *)config,
sizeof(struct audio_port_config));
struct audio_usecase *uc_info = fp_get_usecase_from_list(adev,
patch_record->usecase);
if (!uc_info) {
ALOGE("%s: failed to find the usecase %d",
__func__, patch_record->usecase);
ret = -EINVAL;
} else {
volume = config->gain.values[0];
/* linear interpolation from millibel to level */
int vol_level = lrint(((volume + (0 - MIN_VOLUME_VALUE_MB)) /
(MAX_VOLUME_VALUE_MB - MIN_VOLUME_VALUE_MB)) * 40);
ALOGV("%s: set volume to patch %x", __func__,
patch_record->handle);
ret = fp_audio_extn_ext_hw_plugin_set_audio_gain(adev,
uc_info, vol_level);
}
}
}
pthread_mutex_unlock(&adev->lock);
} else if (config->role == AUDIO_PORT_ROLE_SOURCE) {
// FIXME: handle input devices.
}
}
/* Only handle device port currently. */
ALOGV("%s: exit", __func__);
return ret;
}
static int auto_hal_out_set_pcm_volume(struct stream_out *out, float volume)
{
/* Volume control for pcm playback */
char mixer_ctl_name[128];
struct audio_device *adev = out->dev;
struct mixer_ctl *ctl;
int pcm_device_id = fp_platform_get_pcm_device_id(out->usecase, PCM_PLAYBACK);
snprintf(mixer_ctl_name, sizeof(mixer_ctl_name), "Playback %d Volume", pcm_device_id);
ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name);
if (!ctl) {
ALOGE("%s : Could not get ctl for mixer cmd - %s", __func__, mixer_ctl_name);
return -EINVAL;
}
int dsp_vol = (int) (volume * DSP_MAX_VOLUME);
int ret = mixer_ctl_set_value(ctl, 0, dsp_vol);
if (ret < 0) {
ALOGE("%s: Could not set ctl, error:%d ", __func__, ret);
return -EINVAL;
}
return 0;
}
static void auto_hal_set_mute_duck_state(struct audio_device *adev,
char* duck_mute_value,
int duck_mute_state)
{
struct listnode *node = NULL;
char *ptr = NULL;
char *saveptr = NULL;
struct stream_out *out = NULL;
int car_audio_stream = -1;
for (ptr = strtok_r(duck_mute_value, ",", &saveptr);
ptr != NULL; ptr = strtok_r(NULL, ",", &saveptr)) {
list_for_each(node, &adev->active_outputs_list) {
streams_output_ctxt_t *out_ctxt = node_to_item(node,
streams_output_ctxt_t,
list);
out = out_ctxt->output;
car_audio_stream = auto_hal_get_car_audio_stream_from_address(ptr);
if (car_audio_stream == out->car_audio_stream) {
switch(duck_mute_state) {
case AUDIO_DEVICE_DUCKED:
ALOGD("%s: Ducking BUS device %s", __func__, ptr);
auto_hal_out_set_pcm_volume(out, DUCKED_VOLUME);
break;
case AUDIO_DEVICE_UNDUCKED:
ALOGD("%s: Unducking BUS device %s", __func__, ptr);
auto_hal_out_set_pcm_volume(out, out->volume_l);
break;
case AUDIO_DEVICE_MUTED:
ALOGD("%s: Muting BUS device %s", __func__, ptr);
out->muted = true;
break;
case AUDIO_DEVICE_UNMUTED:
ALOGD("%s: Unmuting BUS device %s", __func__, ptr);
out->muted = false;
break;
}
}
}
}
}
void auto_hal_set_parameters(struct audio_device *adev,
struct str_parms *parms)
{
int ret = 0;
char value[32]={0};
char duck_mute_value[128] = {0};
char *ptr = NULL;
ALOGV("%s: enter", __func__);
ret = str_parms_get_str(parms, "SND_CARD_STATUS", value, sizeof(value));
if (ret >= 0) {
char *snd_card_status = value+2;
ALOGV("%s: snd card status %s", __func__, snd_card_status);
if (strstr(snd_card_status, "OFFLINE")) {
auto_hal->card_status = CARD_STATUS_OFFLINE;
}
else if (strstr(snd_card_status, "ONLINE")) {
auto_hal->card_status = CARD_STATUS_ONLINE;
}
}
ret = str_parms_get_str(parms, "DevicesToDuck", duck_mute_value, sizeof(duck_mute_value));
if (ret >= 0)
auto_hal_set_mute_duck_state(adev, duck_mute_value, AUDIO_DEVICE_DUCKED);
ret = str_parms_get_str(parms, "DevicesToUnduck", duck_mute_value, sizeof(duck_mute_value));
if (ret >= 0)
auto_hal_set_mute_duck_state(adev, duck_mute_value, AUDIO_DEVICE_UNDUCKED);
ret = str_parms_get_str(parms, "DevicesToMute", duck_mute_value, sizeof(duck_mute_value));
if (ret >= 0)
auto_hal_set_mute_duck_state(adev, duck_mute_value, AUDIO_DEVICE_MUTED);
ret = str_parms_get_str(parms, "DevicesToUnmute", duck_mute_value, sizeof(duck_mute_value));
if (ret >= 0)
auto_hal_set_mute_duck_state(adev, duck_mute_value, AUDIO_DEVICE_UNMUTED);
ALOGV("%s: exit", __func__);
}
int auto_hal_start_hfp_downlink(struct audio_device *adev,
struct audio_usecase *uc_info)
{
int32_t ret = 0;
struct audio_usecase *uc_downlink_info;
ALOGD("%s: enter", __func__);
uc_downlink_info = (struct audio_usecase *)calloc(1, sizeof(struct audio_usecase));
if (!uc_downlink_info)
return -ENOMEM;
uc_downlink_info->type = PCM_HFP_CALL;
uc_downlink_info->stream.out = adev->primary_output;
list_init(&uc_downlink_info->device_list);
assign_devices(&uc_downlink_info->device_list, &adev->primary_output->device_list);
uc_downlink_info->in_snd_device = SND_DEVICE_NONE;
uc_downlink_info->out_snd_device = SND_DEVICE_NONE;
switch (uc_info->id) {
case USECASE_AUDIO_HFP_SCO:
uc_downlink_info->id = USECASE_AUDIO_HFP_SCO_DOWNLINK;
break;
case USECASE_AUDIO_HFP_SCO_WB:
uc_downlink_info->id = USECASE_AUDIO_HFP_SCO_WB_DOWNLINK;
break;
default:
ALOGE("%s: Invalid usecase %d", __func__, uc_info->id);
free(uc_downlink_info);
return -EINVAL;
}
list_add_tail(&adev->usecase_list, &uc_downlink_info->list);
ret = fp_select_devices(adev, uc_downlink_info->id);
if (ret) {
ALOGE("%s: Select devices failed %d", __func__, ret);
goto exit;
}
ALOGD("%s: exit: status(%d)", __func__, ret);
return 0;
exit:
auto_hal_stop_hfp_downlink(adev, uc_info);
ALOGE("%s: Problem in start hfp downlink: status(%d)", __func__, ret);
return ret;
}
int auto_hal_stop_hfp_downlink(struct audio_device *adev,
struct audio_usecase *uc_info)
{
int32_t ret = 0;
struct audio_usecase *uc_downlink_info;
audio_usecase_t ucid;
ALOGD("%s: enter", __func__);
switch (uc_info->id) {
case USECASE_AUDIO_HFP_SCO:
ucid = USECASE_AUDIO_HFP_SCO_DOWNLINK;
break;
case USECASE_AUDIO_HFP_SCO_WB:
ucid = USECASE_AUDIO_HFP_SCO_WB_DOWNLINK;
break;
default:
ALOGE("%s: Invalid usecase %d", __func__, uc_info->id);
return -EINVAL;
}
uc_downlink_info = fp_get_usecase_from_list(adev, ucid);
if (uc_downlink_info == NULL) {
ALOGE("%s: Could not find the usecase (%d) in the list",
__func__, ucid);
return -EINVAL;
}
/* Get and set stream specific mixer controls */
fp_disable_audio_route(adev, uc_downlink_info);
/* Disable the rx and tx devices */
fp_disable_snd_device(adev, uc_downlink_info->out_snd_device);
fp_disable_snd_device(adev, uc_downlink_info->in_snd_device);
list_remove(&uc_downlink_info->list);
free(uc_downlink_info);
ALOGD("%s: exit: status(%d)", __func__, ret);
return ret;
}
snd_device_t auto_hal_get_input_snd_device(struct audio_device *adev,
audio_usecase_t uc_id)
{
snd_device_t snd_device = SND_DEVICE_NONE;
struct listnode out_devices;
struct audio_usecase *usecase = NULL;
struct stream_in *in = fp_adev_get_active_input(adev);
struct listnode in_devices;
struct listnode *node;
struct audio_device_info *item = NULL;
list_init(&in_devices);
if (in != NULL)
assign_devices(&in_devices, &in->device_list);
if (uc_id == USECASE_INVALID) {
ALOGE("%s: Invalid usecase (%d)", __func__, uc_id);
return -EINVAL;
}
usecase = fp_get_usecase_from_list(adev, uc_id);
if (usecase == NULL) {
ALOGE("%s: Could not find the usecase (%d)", __func__, uc_id);
return -EINVAL;
}
if (usecase->stream.out == NULL) {
ALOGE("%s: stream.out is NULL", __func__);
return -EINVAL;
}
list_init(&out_devices);
assign_devices(&out_devices, &usecase->stream.out->device_list);
if (list_empty(&out_devices) ||
compare_device_type(&out_devices, AUDIO_DEVICE_BIT_IN)) {
ALOGE("%s: Invalid output devices (%#x)", __func__, get_device_types(&out_devices));
return -EINVAL;
}
ALOGV("%s: output device(%#x), input device(%#x), usecase(%d)",
__func__, get_device_types(&out_devices), get_device_types(&in_devices), uc_id);
if (compare_device_type(&out_devices, AUDIO_DEVICE_OUT_BUS)) {
/* usecase->id is token as judgement for HFP calls */
switch (usecase->id) {
case USECASE_AUDIO_HFP_SCO:
case USECASE_AUDIO_HFP_SCO_WB:
if (fp_platform_get_eccarstate((void *) adev->platform)) {
snd_device = SND_DEVICE_IN_VOICE_SPEAKER_MIC_HFP_MMSECNS;
} else {
snd_device = SND_DEVICE_IN_VOICE_SPEAKER_MIC_HFP;
}
if (adev->enable_hfp)
fp_platform_set_echo_reference(adev, true, &out_devices);
break;
case USECASE_AUDIO_HFP_SCO_DOWNLINK:
snd_device = SND_DEVICE_IN_BT_SCO_MIC;
break;
case USECASE_AUDIO_HFP_SCO_WB_DOWNLINK:
snd_device = SND_DEVICE_IN_BT_SCO_MIC_WB;
break;
case USECASE_VOICE_CALL:
snd_device = SND_DEVICE_IN_VOICE_SPEAKER_MIC;
break;
case USECASE_ICC_CALL:
snd_device = SND_DEVICE_IN_ICC;
break;
case USECASE_AUDIO_PLAYBACK_SYNTHESIZER:
snd_device = SND_DEVICE_IN_SYNTH_MIC;
break;
default:
ALOGE("%s: Usecase (%d) not supported", __func__, uc_id);
return -EINVAL;
}
} else {
ALOGE("%s: Output devices (%#x) not supported", __func__, get_device_types(&out_devices));
return -EINVAL;
}
return snd_device;
}
snd_device_t auto_hal_get_output_snd_device(struct audio_device *adev,
audio_usecase_t uc_id)
{
snd_device_t snd_device = SND_DEVICE_NONE;
struct listnode devices;
struct audio_usecase *usecase = NULL;
if (uc_id == USECASE_INVALID) {
ALOGE("%s: Invalid usecase (%d)", __func__, uc_id);
return -EINVAL;
}
usecase = fp_get_usecase_from_list(adev, uc_id);
if (usecase == NULL) {
ALOGE("%s: Could not find the usecase (%d)", __func__, uc_id);
return -EINVAL;
}
if (usecase->stream.out == NULL) {
ALOGE("%s: stream.out is NULL", __func__);
return -EINVAL;
}
list_init(&devices);
assign_devices(&devices, &usecase->stream.out->device_list);
if (list_empty(&devices) ||
compare_device_type(&devices, AUDIO_DEVICE_BIT_IN)) {
ALOGE("%s: Invalid output devices (%#x)", __func__, get_device_types(&devices));
return -EINVAL;
}
ALOGV("%s: output devices(%#x), usecase(%d)", __func__,
get_device_types(&devices), uc_id);
if (compare_device_type(&devices, AUDIO_DEVICE_OUT_BUS)) {
/* usecase->id is token as judgement for HFP calls */
switch (usecase->id) {
case USECASE_AUDIO_HFP_SCO:
snd_device = SND_DEVICE_OUT_BT_SCO;
break;
case USECASE_AUDIO_HFP_SCO_WB:
snd_device = SND_DEVICE_OUT_BT_SCO_WB;
break;
case USECASE_AUDIO_HFP_SCO_DOWNLINK:
case USECASE_AUDIO_HFP_SCO_WB_DOWNLINK:
snd_device = SND_DEVICE_OUT_VOICE_SPEAKER_HFP;
break;
case USECASE_VOICE_CALL:
snd_device = SND_DEVICE_OUT_VOICE_SPEAKER;
break;
case USECASE_AUDIO_PLAYBACK_MEDIA:
snd_device = SND_DEVICE_OUT_BUS_MEDIA;
break;
case USECASE_AUDIO_PLAYBACK_OFFLOAD:
case USECASE_AUDIO_PLAYBACK_OFFLOAD2:
case USECASE_AUDIO_PLAYBACK_OFFLOAD3:
case USECASE_AUDIO_PLAYBACK_OFFLOAD4:
case USECASE_AUDIO_PLAYBACK_OFFLOAD5:
case USECASE_AUDIO_PLAYBACK_OFFLOAD6:
case USECASE_AUDIO_PLAYBACK_OFFLOAD7:
case USECASE_AUDIO_PLAYBACK_OFFLOAD8:
case USECASE_AUDIO_PLAYBACK_OFFLOAD9:
case USECASE_AUDIO_PLAYBACK_ULL:
case USECASE_AUDIO_PLAYBACK_MMAP:
case USECASE_AUDIO_PLAYBACK_VOIP:
snd_device = SND_DEVICE_OUT_BUS_MEDIA;
/* Override the snd_device based on the bus address if available */
if (usecase->stream.out->car_audio_stream) {
switch (usecase->stream.out->car_audio_stream) {
case CAR_AUDIO_STREAM_MEDIA:
snd_device = SND_DEVICE_OUT_BUS_MEDIA;
break;
case CAR_AUDIO_STREAM_SYS_NOTIFICATION:
snd_device = SND_DEVICE_OUT_BUS_SYS;
break;
case CAR_AUDIO_STREAM_NAV_GUIDANCE:
snd_device = SND_DEVICE_OUT_BUS_NAV;
break;
case CAR_AUDIO_STREAM_PHONE:
snd_device = SND_DEVICE_OUT_BUS_PHN;
break;
case CAR_AUDIO_STREAM_ALERTS:
snd_device = SND_DEVICE_OUT_BUS_ALR;
break;
case CAR_AUDIO_STREAM_FRONT_PASSENGER:
snd_device = SND_DEVICE_OUT_BUS_PAX;
break;
case CAR_AUDIO_STREAM_REAR_SEAT:
snd_device = SND_DEVICE_OUT_BUS_RSE;
break;
default:
ALOGE("%s: Car audio stream %x not supported", __func__,
usecase->stream.out->car_audio_stream);
return -EINVAL;
}
}
break;
case USECASE_AUDIO_PLAYBACK_SYS_NOTIFICATION:
snd_device = SND_DEVICE_OUT_BUS_SYS;
break;
case USECASE_AUDIO_PLAYBACK_NAV_GUIDANCE:
snd_device = SND_DEVICE_OUT_BUS_NAV;
break;
case USECASE_AUDIO_PLAYBACK_PHONE:
snd_device = SND_DEVICE_OUT_BUS_PHN;
break;
case USECASE_AUDIO_PLAYBACK_ALERTS:
snd_device = SND_DEVICE_OUT_BUS_ALR;
break;
case USECASE_AUDIO_PLAYBACK_FRONT_PASSENGER:
snd_device = SND_DEVICE_OUT_BUS_PAX;
break;
case USECASE_AUDIO_PLAYBACK_REAR_SEAT:
snd_device = SND_DEVICE_OUT_BUS_RSE;
break;
case USECASE_ICC_CALL:
snd_device = SND_DEVICE_OUT_ICC;
break;
case USECASE_AUDIO_PLAYBACK_SYNTHESIZER:
snd_device = SND_DEVICE_OUT_SYNTH_SPKR;
break;
default:
ALOGE("%s: Usecase (%d) not supported", __func__, uc_id);
return -EINVAL;
}
} else {
ALOGE("%s: Output devices (%#x) not supported", __func__, get_device_types(&devices));
return -EINVAL;
}
return snd_device;
}
int auto_hal_init(struct audio_device *adev, auto_hal_init_config_t init_config)
{
int ret = 0;
if (auto_hal != NULL) {
ALOGD("%s: Auto hal module already exists",
__func__);
return ret;
}
auto_hal = calloc(1, sizeof(struct auto_hal_module));
if (auto_hal == NULL) {
ALOGE("%s: Memory allocation failed for auto hal module",
__func__);
return -ENOMEM;
}
auto_hal->adev = adev;
fp_audio_extn_ext_hw_plugin_usecase_start = init_config.fp_audio_extn_ext_hw_plugin_usecase_start;
fp_audio_extn_ext_hw_plugin_usecase_stop = init_config.fp_audio_extn_ext_hw_plugin_usecase_stop;
fp_get_usecase_from_list = init_config.fp_get_usecase_from_list;
fp_get_output_period_size = init_config.fp_get_output_period_size;
fp_audio_extn_ext_hw_plugin_set_audio_gain = init_config.fp_audio_extn_ext_hw_plugin_set_audio_gain;
fp_select_devices = init_config.fp_select_devices;
fp_disable_audio_route = init_config.fp_disable_audio_route;
fp_disable_snd_device = init_config.fp_disable_snd_device;
fp_adev_get_active_input = init_config.fp_adev_get_active_input;
fp_platform_set_echo_reference = init_config.fp_platform_set_echo_reference;
fp_platform_get_eccarstate = init_config.fp_platform_get_eccarstate;
fp_generate_patch_handle = init_config.fp_generate_patch_handle;
fp_platform_get_pcm_device_id = init_config.fp_platform_get_pcm_device_id;
return ret;
}
void auto_hal_deinit(void)
{
if (auto_hal == NULL) {
ALOGE("%s: Auto hal module is NULL, cannot deinitialize",
__func__);
return;
}
if(auto_hal != NULL) {
free(auto_hal);
auto_hal = NULL;
}
return;
}