1524 lines
53 KiB
C
1524 lines
53 KiB
C
/*
|
|
* Copyright (c) 2013, 2016-2019 The Linux Foundation. All rights reserved.
|
|
* Not a Contribution.
|
|
*
|
|
* Copyright (C) 2013 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 "audio_hw_usb"
|
|
#define LOG_NDEBUG 0
|
|
#define LOG_NDDEBUG 0
|
|
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <stdlib.h>
|
|
#include <log/log.h>
|
|
#include <cutils/str_parms.h>
|
|
#include <sys/ioctl.h>
|
|
#include <fcntl.h>
|
|
#include <sys/stat.h>
|
|
#include <system/audio.h>
|
|
#include <tinyalsa/asoundlib.h>
|
|
#include <audio_hw.h>
|
|
#include <cutils/properties.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include <unistd.h>
|
|
#include "audio_extn.h"
|
|
|
|
#ifdef DYNAMIC_LOG_ENABLED
|
|
#include <log_xml_parser.h>
|
|
#define LOG_MASK HAL_MOD_FILE_USB
|
|
#include <log_utils.h>
|
|
#endif
|
|
|
|
#define USB_BUFF_SIZE 2048
|
|
#define CHANNEL_NUMBER_STR "Channels: "
|
|
#define PLAYBACK_PROFILE_STR "Playback:"
|
|
#define CAPTURE_PROFILE_STR "Capture:"
|
|
#define DATA_PACKET_INTERVAL_STR "Data packet interval:"
|
|
#define USB_SIDETONE_GAIN_STR "usb_sidetone_gain"
|
|
#define ABS_SUB(A, B) (((A) > (B)) ? ((A) - (B)):((B) - (A)))
|
|
#define SAMPLE_RATE_8000 8000
|
|
#define SAMPLE_RATE_11025 11025
|
|
#define SAMPLE_RATE_192000 192000
|
|
// Supported sample rates for USB
|
|
static uint32_t supported_sample_rates[] =
|
|
{384000, 352800, 192000, 176400, 96000, 88200, 64000, 48000, 44100, 32000, 22050, 16000, 11025, 8000};
|
|
static uint32_t supported_sample_rates_mask[2];
|
|
|
|
#define MAX_SAMPLE_RATE_SIZE sizeof(supported_sample_rates)/sizeof(supported_sample_rates[0])
|
|
|
|
#define DEFAULT_SERVICE_INTERVAL_US 0
|
|
|
|
#define _MAX(x, y) (((x) >= (y)) ? (x) : (y))
|
|
#define _MIN(x, y) (((x) <= (y)) ? (x) : (y))
|
|
|
|
typedef enum usb_usecase_type{
|
|
USB_PLAYBACK = 0,
|
|
USB_CAPTURE,
|
|
} usb_usecase_type_t;
|
|
|
|
enum {
|
|
USB_SIDETONE_ENABLE_INDEX = 0,
|
|
USB_SIDETONE_VOLUME_INDEX,
|
|
USB_SIDETONE_MAX_INDEX,
|
|
};
|
|
|
|
struct usb_device_config {
|
|
struct listnode list;
|
|
unsigned int bit_width;
|
|
unsigned int channels;
|
|
unsigned int rate_size;
|
|
unsigned int rates[MAX_SAMPLE_RATE_SIZE];
|
|
unsigned long service_interval_us;
|
|
usb_usecase_type_t type;
|
|
};
|
|
|
|
struct usb_card_config {
|
|
struct listnode list;
|
|
audio_devices_t usb_device_type;
|
|
int usb_card;
|
|
struct listnode usb_device_conf_list;
|
|
struct mixer *usb_snd_mixer;
|
|
int usb_sidetone_index[USB_SIDETONE_MAX_INDEX];
|
|
int usb_sidetone_vol_min;
|
|
int usb_sidetone_vol_max;
|
|
int endian;
|
|
};
|
|
|
|
struct usb_module {
|
|
struct listnode usb_card_conf_list;
|
|
struct audio_device *adev;
|
|
int sidetone_gain;
|
|
bool is_capture_supported;
|
|
bool usb_reconfig;
|
|
};
|
|
|
|
static struct usb_module *usbmod = NULL;
|
|
static bool usb_audio_debug_enable = false;
|
|
static int usb_sidetone_gain = 0;
|
|
|
|
static const char * const usb_sidetone_enable_str[] = {
|
|
"Sidetone Playback Switch",
|
|
"Mic Playback Switch",
|
|
};
|
|
|
|
static const char * const usb_sidetone_volume_str[] = {
|
|
"Sidetone Playback Volume",
|
|
"Mic Playback Volume",
|
|
};
|
|
|
|
static int usb_get_sidetone_gain(struct usb_card_config *card_info)
|
|
{
|
|
int gain = card_info->usb_sidetone_vol_min + usbmod->sidetone_gain;
|
|
if (gain > card_info->usb_sidetone_vol_max)
|
|
gain = card_info->usb_sidetone_vol_max;
|
|
return gain;
|
|
}
|
|
|
|
static void usb_get_sidetone_volume(struct usb_card_config *usb_card_info)
|
|
{
|
|
struct mixer_ctl *ctl;
|
|
unsigned int index;
|
|
|
|
if (!audio_extn_usb_is_sidetone_volume_enabled())
|
|
return;
|
|
|
|
for (index = 0;
|
|
index < sizeof(usb_sidetone_volume_str)/sizeof(usb_sidetone_volume_str[0]);
|
|
index++) {
|
|
ctl = mixer_get_ctl_by_name(usb_card_info->usb_snd_mixer,
|
|
usb_sidetone_volume_str[index]);
|
|
if (ctl) {
|
|
usb_card_info->usb_sidetone_index[USB_SIDETONE_VOLUME_INDEX] = index;
|
|
usb_card_info->usb_sidetone_vol_min = mixer_ctl_get_range_min(ctl);
|
|
usb_card_info->usb_sidetone_vol_max = mixer_ctl_get_range_max(ctl);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void usb_set_sidetone_volume(struct usb_card_config *usb_card_info,
|
|
bool enable, int index)
|
|
{
|
|
struct mixer_ctl *ctl;
|
|
|
|
if (!audio_extn_usb_is_sidetone_volume_enabled())
|
|
return;
|
|
|
|
ctl = mixer_get_ctl_by_name(usb_card_info->usb_snd_mixer,
|
|
usb_sidetone_volume_str[index]);
|
|
|
|
if (ctl == NULL)
|
|
ALOGV("%s: sidetone gain mixer command is not found",
|
|
__func__);
|
|
else if (enable)
|
|
mixer_ctl_set_value(ctl, 0,
|
|
usb_get_sidetone_gain(usb_card_info));
|
|
}
|
|
|
|
|
|
|
|
static void usb_mixer_print_enum(struct mixer_ctl *ctl)
|
|
{
|
|
unsigned int num_enums;
|
|
unsigned int i;
|
|
const char *string;
|
|
|
|
num_enums = mixer_ctl_get_num_enums(ctl);
|
|
|
|
for (i = 0; i < num_enums; i++) {
|
|
string = mixer_ctl_get_enum_string(ctl, i);
|
|
ALOGI("\t%s%s", mixer_ctl_get_value(ctl, 0) == (int)i ? ">" : "", string);
|
|
}
|
|
}
|
|
|
|
static void usb_soundcard_detail_control(struct mixer *mixer, const char *control)
|
|
{
|
|
struct mixer_ctl *ctl;
|
|
enum mixer_ctl_type type;
|
|
unsigned int num_values;
|
|
unsigned int i;
|
|
int min, max;
|
|
|
|
if (isdigit(control[0]))
|
|
ctl = mixer_get_ctl(mixer, atoi(control));
|
|
else
|
|
ctl = mixer_get_ctl_by_name(mixer, control);
|
|
|
|
if (!ctl) {
|
|
fprintf(stderr, "Invalid mixer control\n");
|
|
return;
|
|
}
|
|
|
|
type = mixer_ctl_get_type(ctl);
|
|
num_values = mixer_ctl_get_num_values(ctl);
|
|
|
|
ALOGI("%s:", mixer_ctl_get_name(ctl));
|
|
|
|
for (i = 0; i < num_values; i++) {
|
|
switch (type) {
|
|
case MIXER_CTL_TYPE_INT:
|
|
ALOGI(" %d", mixer_ctl_get_value(ctl, i));
|
|
break;
|
|
case MIXER_CTL_TYPE_BOOL:
|
|
ALOGI(" %s", mixer_ctl_get_value(ctl, i) ? "On" : "Off");
|
|
break;
|
|
case MIXER_CTL_TYPE_ENUM:
|
|
usb_mixer_print_enum(ctl);
|
|
break;
|
|
case MIXER_CTL_TYPE_BYTE:
|
|
ALOGI(" 0x%02x", mixer_ctl_get_value(ctl, i));
|
|
break;
|
|
default:
|
|
ALOGI(" unknown");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (type == MIXER_CTL_TYPE_INT) {
|
|
min = mixer_ctl_get_range_min(ctl);
|
|
max = mixer_ctl_get_range_max(ctl);
|
|
ALOGI(" (range %d->%d)", min, max);
|
|
}
|
|
}
|
|
|
|
static void usb_soundcard_list_controls(struct mixer *mixer)
|
|
{
|
|
struct mixer_ctl *ctl;
|
|
const char *name, *type;
|
|
unsigned int num_ctls, num_values;
|
|
unsigned int i;
|
|
|
|
num_ctls = mixer_get_num_ctls(mixer);
|
|
|
|
ALOGI("Number of controls: %d\n", num_ctls);
|
|
|
|
ALOGI("ctl\ttype\tnum\t%-40s value\n", "name");
|
|
for (i = 0; i < num_ctls; i++) {
|
|
ctl = mixer_get_ctl(mixer, i);
|
|
if (ctl != NULL) {
|
|
name = mixer_ctl_get_name(ctl);
|
|
type = mixer_ctl_get_type_string(ctl);
|
|
num_values = mixer_ctl_get_num_values(ctl);
|
|
ALOGI("%d\t%s\t%d\t%-40s", i, type, num_values, name);
|
|
if (name != NULL)
|
|
usb_soundcard_detail_control(mixer, name);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int usb_set_dev_id_mixer_ctl(unsigned int usb_usecase_type, int card,
|
|
char *dev_mixer_ctl_name)
|
|
{
|
|
struct mixer_ctl *ctl;
|
|
unsigned int dev_token;
|
|
unsigned int pcm_device_number = 0;
|
|
|
|
/*
|
|
* usb_dev_token_id is 32 bit number and is defined as below:
|
|
* usb_sound_card_idx(31:16) | usb PCM device ID(15:8) | usb_usecase_type(7:0)
|
|
*/
|
|
dev_token = (card << 16 ) |
|
|
(pcm_device_number << 8) | (usb_usecase_type & 0xFF);
|
|
|
|
ctl = mixer_get_ctl_by_name(usbmod->adev->mixer, dev_mixer_ctl_name);
|
|
if (!ctl) {
|
|
ALOGE("%s: Could not get ctl for mixer cmd - %s",
|
|
__func__, dev_mixer_ctl_name);
|
|
return -EINVAL;
|
|
}
|
|
mixer_ctl_set_value(ctl, 0, dev_token);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int usb_set_endian_mixer_ctl(int endian, char *endian_mixer_ctl_name)
|
|
{
|
|
struct mixer_ctl *ctl = mixer_get_ctl_by_name(usbmod->adev->mixer,
|
|
endian_mixer_ctl_name);
|
|
if (!ctl) {
|
|
ALOGE("%s: Could not get ctl for mixer cmd - %s",
|
|
__func__, endian_mixer_ctl_name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (endian) {
|
|
case 0:
|
|
case 1:
|
|
mixer_ctl_set_value(ctl, 0, endian);
|
|
break;
|
|
default:
|
|
ALOGW("%s: endianness(%d) not supported",
|
|
__func__, endian);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int usb_get_sample_rates(int type, char *rates_str,
|
|
struct usb_device_config *config)
|
|
{
|
|
uint32_t i;
|
|
char *next_sr_string, *temp_ptr;
|
|
uint32_t sr, min_sr, max_sr, sr_size = 0;
|
|
|
|
/* Sample rate string can be in any of the folloing two bit_widthes:
|
|
* Rates: 8000 - 48000 (continuous)
|
|
* Rates: 8000, 44100, 48000
|
|
* Support both the bit_widths
|
|
*/
|
|
ALOGV("%s: rates_str %s", __func__, rates_str);
|
|
next_sr_string = strtok_r(rates_str, "Rates: ", &temp_ptr);
|
|
if (next_sr_string == NULL) {
|
|
ALOGE("%s: could not find min rates string", __func__);
|
|
return -EINVAL;
|
|
}
|
|
if (strstr(rates_str, "continuous") != NULL) {
|
|
min_sr = (uint32_t)atoi(next_sr_string);
|
|
next_sr_string = strtok_r(NULL, " ,.-", &temp_ptr);
|
|
if (next_sr_string == NULL) {
|
|
ALOGE("%s: could not find max rates string", __func__);
|
|
return -EINVAL;
|
|
}
|
|
max_sr = (uint32_t)atoi(next_sr_string);
|
|
|
|
for (i = 0; i < MAX_SAMPLE_RATE_SIZE; i++) {
|
|
if (supported_sample_rates[i] >= min_sr &&
|
|
supported_sample_rates[i] <= max_sr) {
|
|
// FIXME: we don't support >192KHz in recording path for now
|
|
if ((supported_sample_rates[i] > SAMPLE_RATE_192000) &&
|
|
(type == USB_CAPTURE))
|
|
continue;
|
|
config->rates[sr_size++] = supported_sample_rates[i];
|
|
supported_sample_rates_mask[type] |= (1<<i);
|
|
ALOGI_IF(usb_audio_debug_enable,
|
|
"%s: continuous sample rate supported_sample_rates[%d] %d",
|
|
__func__, i, supported_sample_rates[i]);
|
|
}
|
|
}
|
|
} else {
|
|
do {
|
|
sr = (uint32_t)atoi(next_sr_string);
|
|
// FIXME: we don't support >192KHz in recording path for now
|
|
if ((sr > SAMPLE_RATE_192000) && (type == USB_CAPTURE)) {
|
|
next_sr_string = strtok_r(NULL, " ,.-", &temp_ptr);
|
|
continue;
|
|
}
|
|
|
|
for (i = 0; i < MAX_SAMPLE_RATE_SIZE; i++) {
|
|
if (supported_sample_rates[i] == sr) {
|
|
ALOGI_IF(usb_audio_debug_enable,
|
|
"%s: sr %d, supported_sample_rates[%d] %d -> matches!!",
|
|
__func__, sr, i, supported_sample_rates[i]);
|
|
config->rates[sr_size++] = supported_sample_rates[i];
|
|
supported_sample_rates_mask[type] |= (1<<i);
|
|
}
|
|
}
|
|
next_sr_string = strtok_r(NULL, " ,.-", &temp_ptr);
|
|
} while (next_sr_string != NULL);
|
|
}
|
|
config->rate_size = sr_size;
|
|
return 0;
|
|
}
|
|
|
|
static int get_usb_service_interval(const char *interval_str_start,
|
|
struct usb_device_config *usb_device_info)
|
|
{
|
|
unsigned long interval = 0;
|
|
char time_unit[8] = {0};
|
|
int multiplier = 0;
|
|
|
|
char *eol = strchr(interval_str_start, '\n');
|
|
if (!eol) {
|
|
ALOGE("%s: No EOL found", __func__);
|
|
return -1;
|
|
}
|
|
char *tmp = (char *)calloc(1, eol-interval_str_start+1);
|
|
if (!tmp) {
|
|
ALOGE("%s: failed to allocate tmp", __func__);
|
|
return -1;
|
|
}
|
|
memcpy(tmp, interval_str_start, eol-interval_str_start);
|
|
sscanf(tmp, "%lu %2s", &interval, &time_unit[0]);
|
|
if (!strcmp(time_unit, "us")) {
|
|
multiplier = 1;
|
|
} else if (!strcmp(time_unit, "ms")) {
|
|
multiplier = 1000;
|
|
} else if (!strcmp(time_unit, "s")) {
|
|
multiplier = 1000000;
|
|
} else {
|
|
ALOGE("%s: unknown time_unit %s, assume default", __func__, time_unit);
|
|
interval = DEFAULT_SERVICE_INTERVAL_US;
|
|
multiplier = 1;
|
|
}
|
|
interval *= multiplier;
|
|
ALOGD("%s: set service_interval_us %lu", __func__, interval);
|
|
usb_device_info->service_interval_us = interval;
|
|
free(tmp);
|
|
return 0;
|
|
}
|
|
|
|
static int usb_get_capability(int type,
|
|
struct usb_card_config *usb_card_info,
|
|
int card)
|
|
{
|
|
int32_t size = 0;
|
|
int32_t fd=-1;
|
|
int32_t channels_no;
|
|
char *str_start = NULL;
|
|
char *str_end = NULL;
|
|
char *channel_start = NULL;
|
|
char *bit_width_start = NULL;
|
|
char *rates_str_start = NULL;
|
|
char *target = NULL;
|
|
char *read_buf = NULL;
|
|
char *rates_str = NULL;
|
|
char *interval_str_start = NULL;
|
|
char path[128];
|
|
int ret = 0;
|
|
char *bit_width_str = NULL;
|
|
struct usb_device_config * usb_device_info;
|
|
bool check = false;
|
|
|
|
memset(path, 0, sizeof(path));
|
|
ALOGV("%s: for %s", __func__, (type == USB_PLAYBACK) ?
|
|
PLAYBACK_PROFILE_STR : CAPTURE_PROFILE_STR);
|
|
|
|
ret = snprintf(path, sizeof(path), "/proc/asound/card%u/stream0",
|
|
card);
|
|
if(ret < 0) {
|
|
ALOGE("%s: failed on snprintf (%d) to path %s\n",
|
|
__func__, ret, path);
|
|
goto done;
|
|
}
|
|
|
|
fd = open(path, O_RDONLY);
|
|
if (fd <0) {
|
|
ALOGE("%s: error failed to open config file %s error: %d\n",
|
|
__func__, path, errno);
|
|
ret = -EINVAL;
|
|
goto done;
|
|
}
|
|
|
|
read_buf = (char *)calloc(1, USB_BUFF_SIZE + 1);
|
|
|
|
if (!read_buf) {
|
|
ALOGE("Failed to create read_buf");
|
|
ret = -ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
if(read(fd, read_buf, USB_BUFF_SIZE) < 0) {
|
|
ALOGE("file read error\n");
|
|
goto done;
|
|
}
|
|
str_start = strstr(read_buf, ((type == USB_PLAYBACK) ?
|
|
PLAYBACK_PROFILE_STR : CAPTURE_PROFILE_STR));
|
|
if (str_start == NULL) {
|
|
ALOGE("%s: error %s section not found in usb config file",
|
|
__func__, ((type == USB_PLAYBACK) ?
|
|
PLAYBACK_PROFILE_STR : CAPTURE_PROFILE_STR));
|
|
ret = -EINVAL;
|
|
goto done;
|
|
}
|
|
str_end = strstr(read_buf, ((type == USB_PLAYBACK) ?
|
|
CAPTURE_PROFILE_STR : PLAYBACK_PROFILE_STR));
|
|
if (str_end > str_start)
|
|
check = true;
|
|
|
|
ALOGV("%s: usb_config = %s, check %d\n", __func__, str_start, check);
|
|
|
|
while (str_start != NULL) {
|
|
str_start = strstr(str_start, "Altset");
|
|
if ((str_start == NULL) || (check && (str_start >= str_end))) {
|
|
ALOGV("%s: done parsing %s\n", __func__, str_start);
|
|
break;
|
|
}
|
|
ALOGV("%s: remaining string %s\n", __func__, str_start);
|
|
str_start += sizeof("Altset");
|
|
usb_device_info = calloc(1, sizeof(struct usb_device_config));
|
|
if (usb_device_info == NULL) {
|
|
ALOGE("%s: error unable to allocate memory",
|
|
__func__);
|
|
ret = -ENOMEM;
|
|
break;
|
|
}
|
|
usb_device_info->type = type;
|
|
/* Bit bit_width parsing */
|
|
bit_width_start = strstr(str_start, "Format: ");
|
|
if (bit_width_start == NULL) {
|
|
ALOGI("%s: Could not find bit_width string", __func__);
|
|
free(usb_device_info);
|
|
continue;
|
|
}
|
|
target = strchr(bit_width_start, '\n');
|
|
if (target == NULL) {
|
|
ALOGI("%s:end of line not found", __func__);
|
|
free(usb_device_info);
|
|
continue;
|
|
}
|
|
size = target - bit_width_start;
|
|
if ((bit_width_str = (char *)malloc(size + 1)) == NULL) {
|
|
ALOGE("%s: unable to allocate memory to hold bit width strings",
|
|
__func__);
|
|
ret = -EINVAL;
|
|
free(usb_device_info);
|
|
break;
|
|
}
|
|
memcpy(bit_width_str, bit_width_start, size);
|
|
bit_width_str[size] = '\0';
|
|
|
|
const char * formats[] = { "S32", "S24_3", "S24", "S16" };
|
|
const int bit_width[] = { 32, 24, 24, 16};
|
|
for (size_t i = 0; i < ARRAY_SIZE(formats); i++) {
|
|
const char * s = strstr(bit_width_str, formats[i]);
|
|
if (s) {
|
|
usb_device_info->bit_width = bit_width[i];
|
|
usb_card_info->endian = strstr(s, "BE") ? 1 : 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bit_width_str)
|
|
free(bit_width_str);
|
|
|
|
/* channels parsing */
|
|
channel_start = strstr(str_start, CHANNEL_NUMBER_STR);
|
|
if (channel_start == NULL) {
|
|
ALOGI("%s: could not find Channels string", __func__);
|
|
free(usb_device_info);
|
|
continue;
|
|
}
|
|
channels_no = atoi(channel_start + strlen(CHANNEL_NUMBER_STR));
|
|
usb_device_info->channels = channels_no;
|
|
|
|
/* Sample rates parsing */
|
|
rates_str_start = strstr(str_start, "Rates: ");
|
|
if (rates_str_start == NULL) {
|
|
ALOGI("%s: cant find rates string", __func__);
|
|
free(usb_device_info);
|
|
continue;
|
|
}
|
|
target = strchr(rates_str_start, '\n');
|
|
if (target == NULL) {
|
|
ALOGI("%s: end of line not found", __func__);
|
|
free(usb_device_info);
|
|
continue;
|
|
}
|
|
size = target - rates_str_start;
|
|
if ((rates_str = (char *)malloc(size + 1)) == NULL) {
|
|
ALOGE("%s: unable to allocate memory to hold sample rate strings",
|
|
__func__);
|
|
ret = -EINVAL;
|
|
free(usb_device_info);
|
|
break;
|
|
}
|
|
memcpy(rates_str, rates_str_start, size);
|
|
rates_str[size] = '\0';
|
|
ret = usb_get_sample_rates(type, rates_str, usb_device_info);
|
|
if (rates_str)
|
|
free(rates_str);
|
|
if (ret < 0) {
|
|
ALOGI("%s: error unable to get sample rate values",
|
|
__func__);
|
|
free(usb_device_info);
|
|
continue;
|
|
}
|
|
// Data packet interval is an optional field.
|
|
// Assume 0ms interval if this cannot be read
|
|
// LPASS USB and HLOS USB will figure out the default to use
|
|
usb_device_info->service_interval_us = DEFAULT_SERVICE_INTERVAL_US;
|
|
interval_str_start = strstr(str_start, DATA_PACKET_INTERVAL_STR);
|
|
if (interval_str_start != NULL) {
|
|
interval_str_start += strlen(DATA_PACKET_INTERVAL_STR);
|
|
ret = get_usb_service_interval(interval_str_start, usb_device_info);
|
|
if (ret < 0) {
|
|
ALOGE("%s: error unable to get service interval, assume default",
|
|
__func__);
|
|
}
|
|
}
|
|
/* Add to list if every field is valid */
|
|
list_add_tail(&usb_card_info->usb_device_conf_list,
|
|
&usb_device_info->list);
|
|
}
|
|
|
|
done:
|
|
if (fd >= 0) close(fd);
|
|
if (read_buf) free(read_buf);
|
|
return ret;
|
|
}
|
|
|
|
static int usb_get_device_pb_config(struct usb_card_config *usb_card_info,
|
|
int card)
|
|
{
|
|
int ret;
|
|
|
|
/* get capabilities */
|
|
if ((ret = usb_get_capability(USB_PLAYBACK, usb_card_info, card))) {
|
|
ALOGE("%s: could not get Playback capabilities from usb device",
|
|
__func__);
|
|
goto exit;
|
|
}
|
|
usb_set_dev_id_mixer_ctl(USB_PLAYBACK, card, "USB_AUDIO_RX dev_token");
|
|
usb_set_endian_mixer_ctl(usb_card_info->endian, "USB_AUDIO_RX endian");
|
|
exit:
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int usb_get_device_cap_config(struct usb_card_config *usb_card_info,
|
|
int card)
|
|
{
|
|
int ret;
|
|
|
|
/* get capabilities */
|
|
if ((ret = usb_get_capability(USB_CAPTURE, usb_card_info, card))) {
|
|
ALOGE("%s: could not get Playback capabilities from usb device",
|
|
__func__);
|
|
goto exit;
|
|
}
|
|
usb_set_dev_id_mixer_ctl(USB_CAPTURE, card, "USB_AUDIO_TX dev_token");
|
|
usb_set_endian_mixer_ctl(usb_card_info->endian, "USB_AUDIO_TX endian");
|
|
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
static void usb_get_sidetone_mixer(struct usb_card_config *usb_card_info)
|
|
{
|
|
struct mixer_ctl *ctl;
|
|
unsigned int index;
|
|
|
|
for (index = 0; index < USB_SIDETONE_MAX_INDEX; index++)
|
|
usb_card_info->usb_sidetone_index[index] = -1;
|
|
|
|
usb_card_info->usb_snd_mixer = mixer_open(usb_card_info->usb_card);
|
|
for (index = 0;
|
|
index < sizeof(usb_sidetone_enable_str)/sizeof(usb_sidetone_enable_str[0]);
|
|
index++) {
|
|
ctl = mixer_get_ctl_by_name(usb_card_info->usb_snd_mixer,
|
|
usb_sidetone_enable_str[index]);
|
|
if (ctl) {
|
|
usb_card_info->usb_sidetone_index[USB_SIDETONE_ENABLE_INDEX] = index;
|
|
/* Disable device sidetone by default */
|
|
mixer_ctl_set_value(ctl, 0, false);
|
|
ALOGV("%s: sidetone mixer Control found(%s) ... disabling by default",
|
|
__func__, usb_sidetone_enable_str[index]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
usb_get_sidetone_volume(usb_card_info);
|
|
|
|
if ((usb_card_info->usb_snd_mixer != NULL) && (usb_audio_debug_enable))
|
|
usb_soundcard_list_controls(usb_card_info->usb_snd_mixer);
|
|
|
|
return;
|
|
}
|
|
|
|
static inline bool usb_output_device(audio_devices_t device) {
|
|
// ignore accessory for now
|
|
if (device == AUDIO_DEVICE_OUT_USB_ACCESSORY)
|
|
return false;
|
|
return audio_is_usb_out_device(device);
|
|
}
|
|
|
|
static inline bool usb_input_device(audio_devices_t device) {
|
|
// ignore accessory for now
|
|
if (device == AUDIO_DEVICE_IN_USB_ACCESSORY)
|
|
return false;
|
|
return audio_is_usb_in_device(device);
|
|
}
|
|
|
|
static bool usb_valid_device(audio_devices_t device)
|
|
{
|
|
return usb_output_device(device) ||
|
|
usb_input_device(device);
|
|
}
|
|
|
|
static void usb_print_active_device(void){
|
|
struct listnode *node_i, *node_j;
|
|
struct usb_device_config *dev_info;
|
|
struct usb_card_config *card_info;
|
|
unsigned int i;
|
|
|
|
ALOGI("%s", __func__);
|
|
list_for_each(node_i, &usbmod->usb_card_conf_list) {
|
|
card_info = node_to_item(node_i, struct usb_card_config, list);
|
|
ALOGI("%s: card_dev_type (0x%x), card_no(%d), %s",
|
|
__func__, card_info->usb_device_type,
|
|
card_info->usb_card, card_info->endian ? "BE" : "LE");
|
|
list_for_each(node_j, &card_info->usb_device_conf_list) {
|
|
dev_info = node_to_item(node_j, struct usb_device_config, list);
|
|
ALOGI("%s: bit-width(%d) channel(%d)",
|
|
__func__, dev_info->bit_width, dev_info->channels);
|
|
for (i = 0; i < dev_info->rate_size; i++)
|
|
ALOGI("%s: rate %d", __func__, dev_info->rates[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool usb_get_best_match_for_bit_width(
|
|
struct listnode *dev_list,
|
|
unsigned int stream_bit_width,
|
|
unsigned int *bit_width)
|
|
{
|
|
struct listnode *node_i;
|
|
struct usb_device_config *dev_info;
|
|
unsigned int candidate = 0;
|
|
|
|
list_for_each(node_i, dev_list) {
|
|
dev_info = node_to_item(node_i, struct usb_device_config, list);
|
|
ALOGI_IF(usb_audio_debug_enable,
|
|
"%s: USB bw(%d), stream bw(%d), candidate(%d)",
|
|
__func__, dev_info->bit_width,
|
|
stream_bit_width, candidate);
|
|
if (dev_info->bit_width == stream_bit_width) {
|
|
*bit_width = dev_info->bit_width;
|
|
ALOGV("%s: Found match bit-width (%d)",
|
|
__func__, dev_info->bit_width);
|
|
goto exit;
|
|
} else if (candidate == 0) {
|
|
candidate = dev_info->bit_width;
|
|
}
|
|
/*
|
|
* If stream bit is 24, USB supports both 16 bit and 32 bit, then
|
|
* higher bit width 32 is picked up instead of 16-bit
|
|
*/
|
|
else if (ABS_SUB(stream_bit_width, dev_info->bit_width) <
|
|
ABS_SUB(stream_bit_width, candidate)) {
|
|
candidate = dev_info->bit_width;
|
|
}
|
|
else if ((ABS_SUB(stream_bit_width, dev_info->bit_width) ==
|
|
ABS_SUB(stream_bit_width, candidate)) &&
|
|
(dev_info->bit_width > candidate)) {
|
|
candidate = dev_info->bit_width;
|
|
}
|
|
}
|
|
ALOGV("%s: No match found, use the best candidate bw(%d)",
|
|
__func__, candidate);
|
|
*bit_width = candidate;
|
|
exit:
|
|
return true;
|
|
}
|
|
|
|
static bool usb_get_best_match_for_channels(
|
|
struct listnode *dev_list,
|
|
unsigned int bit_width,
|
|
unsigned int stream_ch,
|
|
unsigned int *ch)
|
|
{
|
|
struct listnode *node_i;
|
|
struct usb_device_config *dev_info;
|
|
unsigned int candidate = 0;
|
|
|
|
list_for_each(node_i, dev_list) {
|
|
dev_info = node_to_item(node_i, struct usb_device_config, list);
|
|
ALOGI_IF(usb_audio_debug_enable,
|
|
"%s: USB ch(%d)bw(%d), stream ch(%d)bw(%d), candidate(%d)",
|
|
__func__, dev_info->channels, dev_info->bit_width,
|
|
stream_ch, bit_width, candidate);
|
|
if (dev_info->bit_width != bit_width)
|
|
continue;
|
|
if (dev_info->channels== stream_ch) {
|
|
*ch = dev_info->channels;
|
|
ALOGV("%s: Found match channels (%d)",
|
|
__func__, dev_info->channels);
|
|
goto exit;
|
|
} else if (candidate == 0)
|
|
candidate = dev_info->channels;
|
|
/*
|
|
* If stream channel is 4, USB supports both 3 and 5, then
|
|
* higher channel 5 is picked up instead of 3
|
|
*/
|
|
else if (ABS_SUB(stream_ch, dev_info->channels) <
|
|
ABS_SUB(stream_ch, candidate)) {
|
|
candidate = dev_info->channels;
|
|
} else if ((ABS_SUB(stream_ch, dev_info->channels) ==
|
|
ABS_SUB(stream_ch, candidate)) &&
|
|
(dev_info->channels > candidate)) {
|
|
candidate = dev_info->channels;
|
|
}
|
|
}
|
|
ALOGV("%s: No match found, use the best candidate ch(%d)",
|
|
__func__, candidate);
|
|
*ch = candidate;
|
|
exit:
|
|
return true;
|
|
|
|
}
|
|
|
|
static bool usb_sample_rate_multiple(
|
|
unsigned int stream_sample_rate,
|
|
unsigned int base)
|
|
{
|
|
return (((stream_sample_rate / base) * base) == stream_sample_rate);
|
|
}
|
|
|
|
static bool usb_find_sample_rate_candidate(unsigned int base,
|
|
unsigned stream_rate,
|
|
unsigned int usb_rate,
|
|
unsigned int cur_candidate,
|
|
unsigned int *update_candidate)
|
|
{
|
|
/* For sample rate, we should consider fracational sample rate as high priority.
|
|
* For example, if the stream is 88.2kHz and USB device support both 44.1kH and
|
|
* 48kHz sample rate, we should pick 44.1kHz instead of 48kHz
|
|
*/
|
|
if (!usb_sample_rate_multiple(cur_candidate, base) &&
|
|
usb_sample_rate_multiple(usb_rate, base)) {
|
|
*update_candidate = usb_rate;
|
|
} else if (usb_sample_rate_multiple(cur_candidate, base) &&
|
|
usb_sample_rate_multiple(usb_rate, base)) {
|
|
if (ABS_SUB(stream_rate, usb_rate) <
|
|
ABS_SUB(stream_rate, cur_candidate)) {
|
|
*update_candidate = usb_rate;
|
|
} else if ((ABS_SUB(stream_rate, usb_rate) ==
|
|
ABS_SUB(stream_rate, cur_candidate)) &&
|
|
(usb_rate > cur_candidate)) {
|
|
*update_candidate = usb_rate;
|
|
}
|
|
} else if (!usb_sample_rate_multiple(cur_candidate, base) &&
|
|
!usb_sample_rate_multiple(usb_rate, base)) {
|
|
if (ABS_SUB(stream_rate, usb_rate) <
|
|
ABS_SUB(stream_rate, cur_candidate)) {
|
|
*update_candidate = usb_rate;
|
|
} else if ((ABS_SUB(stream_rate, usb_rate) ==
|
|
ABS_SUB(stream_rate, cur_candidate)) &&
|
|
(usb_rate > cur_candidate)) {
|
|
*update_candidate = usb_rate;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool usb_get_best_match_for_sample_rate(
|
|
struct listnode *dev_list,
|
|
unsigned int bit_width,
|
|
unsigned int ch,
|
|
unsigned int stream_sample_rate,
|
|
unsigned int *sr,
|
|
unsigned int service_interval,
|
|
bool do_service_interval_check)
|
|
{
|
|
struct listnode *node_i;
|
|
struct usb_device_config *dev_info;
|
|
unsigned int candidate = 48000;
|
|
unsigned int base = SAMPLE_RATE_8000;
|
|
bool multiple_8k = usb_sample_rate_multiple(stream_sample_rate, base);
|
|
unsigned int i;
|
|
|
|
ALOGV("%s: stm ch(%d)bw(%d)sr(%d), stream sample multiple of 8kHz(%d)",
|
|
__func__, ch, bit_width, stream_sample_rate, multiple_8k);
|
|
|
|
list_for_each(node_i, dev_list) {
|
|
dev_info = node_to_item(node_i, struct usb_device_config, list);
|
|
ALOGI_IF(usb_audio_debug_enable,
|
|
"%s: USB ch(%d)bw(%d), stm ch(%d)bw(%d)sr(%d), candidate(%d)",
|
|
__func__, dev_info->channels, dev_info->bit_width,
|
|
ch, bit_width, stream_sample_rate, candidate);
|
|
|
|
if ((dev_info->bit_width != bit_width) ||
|
|
(dev_info->channels != ch) ||
|
|
(do_service_interval_check && (dev_info->service_interval_us !=
|
|
service_interval)))
|
|
continue;
|
|
|
|
candidate = 0;
|
|
for (i = 0; i < dev_info->rate_size; i++) {
|
|
ALOGI_IF(usb_audio_debug_enable,
|
|
"%s: USB ch(%d)bw(%d)sr(%d), stm ch(%d)bw(%d)sr(%d), candidate(%d)",
|
|
__func__, dev_info->channels,
|
|
dev_info->bit_width, dev_info->rates[i],
|
|
ch, bit_width, stream_sample_rate, candidate);
|
|
if (stream_sample_rate == dev_info->rates[i]) {
|
|
*sr = dev_info->rates[i];
|
|
ALOGV("%s: Found match sample rate (%d)",
|
|
__func__, dev_info->rates[i]);
|
|
goto exit;
|
|
} else if (candidate == 0) {
|
|
candidate = dev_info->rates[i];
|
|
/*
|
|
* For sample rate, we should consider fracational sample rate as high priority.
|
|
* For example, if the stream is 88.2kHz and USB device support both 44.1kH and
|
|
* 48kHz sample rate, we should pick 44.1kHz instead of 48kHz
|
|
*/
|
|
} else if (multiple_8k) {
|
|
usb_find_sample_rate_candidate(SAMPLE_RATE_8000,
|
|
stream_sample_rate,
|
|
dev_info->rates[i],
|
|
candidate,
|
|
&candidate);
|
|
} else {
|
|
usb_find_sample_rate_candidate(SAMPLE_RATE_11025,
|
|
stream_sample_rate,
|
|
dev_info->rates[i],
|
|
candidate,
|
|
&candidate);
|
|
}
|
|
}
|
|
}
|
|
ALOGV("%s: No match found, use the best candidate sr(%d)",
|
|
__func__, candidate);
|
|
*sr = candidate;
|
|
exit:
|
|
return true;
|
|
}
|
|
|
|
static bool usb_audio_backend_apply_policy(struct listnode *dev_list,
|
|
unsigned int *bit_width,
|
|
unsigned int *sample_rate,
|
|
unsigned int *ch)
|
|
{
|
|
bool is_usb_supported = true;
|
|
|
|
ALOGV("%s: from stream: bit-width(%d) sample_rate(%d) channels (%d)",
|
|
__func__, *bit_width, *sample_rate, *ch);
|
|
if (list_empty(dev_list)) {
|
|
*sample_rate = 48000;
|
|
*bit_width = 16;
|
|
*ch = 2;
|
|
ALOGI("%s: list is empty,fall back to default setting", __func__);
|
|
goto exit;
|
|
}
|
|
usb_get_best_match_for_bit_width(dev_list, *bit_width, bit_width);
|
|
usb_get_best_match_for_channels(dev_list,
|
|
*bit_width,
|
|
*ch,
|
|
ch);
|
|
usb_get_best_match_for_sample_rate(dev_list,
|
|
*bit_width,
|
|
*ch,
|
|
*sample_rate,
|
|
sample_rate,
|
|
0 /*service int*/,
|
|
false /*do service int check*/);
|
|
exit:
|
|
ALOGV("%s: Updated sample rate per profile: bit-width(%d) rate(%d) chs(%d)",
|
|
__func__, *bit_width, *sample_rate, *ch);
|
|
return is_usb_supported;
|
|
}
|
|
|
|
void usb_set_sidetone_gain(struct str_parms *parms,
|
|
char *value, int len)
|
|
{
|
|
int err;
|
|
|
|
err = str_parms_get_str(parms, USB_SIDETONE_GAIN_STR,
|
|
value, len);
|
|
if (err >= 0) {
|
|
usb_sidetone_gain = pow(10.0, (float)(atoi(value))/10.0);
|
|
ALOGV("%s: sidetone gain(%s) decimal %d",
|
|
__func__, value, usb_sidetone_gain);
|
|
str_parms_del(parms, USB_SIDETONE_GAIN_STR);
|
|
}
|
|
return;
|
|
}
|
|
|
|
int usb_enable_sidetone(int device, bool enable)
|
|
{
|
|
int ret = -ENODEV;
|
|
struct listnode *node_i;
|
|
struct usb_card_config *card_info;
|
|
int i;
|
|
ALOGV("%s: card_dev_type (0x%x), sidetone enable(%d)",
|
|
__func__, device, enable);
|
|
|
|
list_for_each(node_i, &usbmod->usb_card_conf_list) {
|
|
card_info = node_to_item(node_i, struct usb_card_config, list);
|
|
ALOGV("%s: card_dev_type (0x%x), card_no(%d)",
|
|
__func__, card_info->usb_device_type, card_info->usb_card);
|
|
if (usb_output_device(card_info->usb_device_type)) {
|
|
if ((i = card_info->usb_sidetone_index[USB_SIDETONE_ENABLE_INDEX]) != -1) {
|
|
struct mixer_ctl *ctl = mixer_get_ctl_by_name(
|
|
card_info->usb_snd_mixer,
|
|
usb_sidetone_enable_str[i]);
|
|
if (ctl)
|
|
mixer_ctl_set_value(ctl, 0, enable);
|
|
else
|
|
break;
|
|
|
|
if ((i = card_info->usb_sidetone_index[USB_SIDETONE_VOLUME_INDEX]) != -1) {
|
|
usb_set_sidetone_volume(card_info, enable, i);
|
|
}
|
|
|
|
ret = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool usb_is_config_supported(unsigned int *bit_width,
|
|
unsigned int *sample_rate,
|
|
unsigned int *ch,
|
|
bool is_playback)
|
|
{
|
|
struct listnode *node_i;
|
|
struct usb_card_config *card_info;
|
|
bool is_usb_supported = false;
|
|
|
|
ALOGV("%s: from stream: bit-width(%d) sample_rate(%d) ch(%d) is_playback(%d)",
|
|
__func__, *bit_width, *sample_rate, *ch, is_playback);
|
|
list_for_each(node_i, &usbmod->usb_card_conf_list) {
|
|
card_info = node_to_item(node_i, struct usb_card_config, list);
|
|
ALOGI_IF(usb_audio_debug_enable,
|
|
"%s: card_dev_type (0x%x), card_no(%d)",
|
|
__func__, card_info->usb_device_type, card_info->usb_card);
|
|
/* Currently only apply the first playback sound card configuration */
|
|
if ((is_playback && usb_output_device(card_info->usb_device_type)) ||
|
|
(!is_playback && usb_input_device(card_info->usb_device_type))){
|
|
is_usb_supported = usb_audio_backend_apply_policy(
|
|
&card_info->usb_device_conf_list,
|
|
bit_width,
|
|
sample_rate,
|
|
ch);
|
|
break;
|
|
}
|
|
}
|
|
ALOGV("%s: updated: bit-width(%d) sample_rate(%d) channels (%d)",
|
|
__func__, *bit_width, *sample_rate, *ch);
|
|
|
|
return is_usb_supported;
|
|
}
|
|
|
|
int usb_get_max_channels(bool is_playback)
|
|
{
|
|
struct listnode *node_i, *node_j;
|
|
struct usb_device_config *dev_info;
|
|
struct usb_card_config *card_info;
|
|
unsigned int max_ch = 1;
|
|
list_for_each(node_i, &usbmod->usb_card_conf_list) {
|
|
card_info = node_to_item(node_i, struct usb_card_config, list);
|
|
if (usb_output_device(card_info->usb_device_type) && !is_playback)
|
|
continue;
|
|
else if (usb_input_device(card_info->usb_device_type) && is_playback)
|
|
continue;
|
|
|
|
list_for_each(node_j, &card_info->usb_device_conf_list) {
|
|
dev_info = node_to_item(node_j, struct usb_device_config, list);
|
|
max_ch = _MAX(max_ch, dev_info->channels);
|
|
}
|
|
}
|
|
|
|
return max_ch;
|
|
}
|
|
|
|
int usb_get_max_bit_width(bool is_playback)
|
|
{
|
|
struct listnode *node_i, *node_j;
|
|
struct usb_device_config *dev_info;
|
|
struct usb_card_config *card_info;
|
|
unsigned int max_bw = 16;
|
|
list_for_each(node_i, &usbmod->usb_card_conf_list) {
|
|
card_info = node_to_item(node_i, struct usb_card_config, list);
|
|
if (usb_output_device(card_info->usb_device_type) && !is_playback)
|
|
continue;
|
|
else if (usb_input_device(card_info->usb_device_type) && is_playback)
|
|
continue;
|
|
|
|
list_for_each(node_j, &card_info->usb_device_conf_list) {
|
|
dev_info = node_to_item(node_j, struct usb_device_config, list);
|
|
max_bw = _MAX(max_bw, dev_info->bit_width);
|
|
}
|
|
}
|
|
|
|
return max_bw;
|
|
}
|
|
|
|
int usb_get_sup_sample_rates(bool is_playback,
|
|
uint32_t *sample_rates,
|
|
uint32_t sample_rate_size)
|
|
{
|
|
int type = is_playback ? USB_PLAYBACK : USB_CAPTURE;
|
|
|
|
ALOGV("%s supported_sample_rates_mask 0x%x", __func__, supported_sample_rates_mask[type]);
|
|
uint32_t bm = supported_sample_rates_mask[type];
|
|
uint32_t tries = _MIN(sample_rate_size, (uint32_t)__builtin_popcount(bm));
|
|
|
|
int i = 0;
|
|
while (tries--) {
|
|
int idx = __builtin_ffs(bm) - 1;
|
|
sample_rates[i++] = supported_sample_rates[idx];
|
|
bm &= ~(1<<idx);
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
bool usb_is_capture_supported()
|
|
{
|
|
if (usbmod == NULL) {
|
|
ALOGE("%s: USB device object is NULL", __func__);
|
|
return false;
|
|
}
|
|
ALOGV("%s: capture_supported %d",__func__,usbmod->is_capture_supported);
|
|
return usbmod->is_capture_supported;
|
|
}
|
|
|
|
bool usb_is_tunnel_supported()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void usb_add_device(audio_devices_t device, int card)
|
|
{
|
|
struct usb_card_config *usb_card_info;
|
|
char check_debug_enable[PROPERTY_VALUE_MAX];
|
|
struct listnode *node_i;
|
|
|
|
if ((property_get("vendor.audio.usb.enable.debug",
|
|
check_debug_enable, NULL) > 0) ||
|
|
(property_get("audio.usb.enable.debug",
|
|
check_debug_enable, NULL) > 0)) {
|
|
if (atoi(check_debug_enable))
|
|
usb_audio_debug_enable = true;
|
|
}
|
|
|
|
ALOGI_IF(usb_audio_debug_enable,
|
|
"%s: parameters device(0x%x), card(%d)",
|
|
__func__, device, card);
|
|
if (usbmod == NULL) {
|
|
ALOGE("%s: USB device object is NULL", __func__);
|
|
goto exit;
|
|
}
|
|
|
|
if (!(usb_valid_device(device)) || (card < 0)) {
|
|
ALOGE("%s:device(0x%x), card(%d)",
|
|
__func__, device, card);
|
|
goto exit;
|
|
}
|
|
|
|
list_for_each(node_i, &usbmod->usb_card_conf_list) {
|
|
usb_card_info = node_to_item(node_i, struct usb_card_config, list);
|
|
ALOGI_IF(usb_audio_debug_enable,
|
|
"%s: list has capability for card_dev_type (0x%x), card_no(%d)",
|
|
__func__, usb_card_info->usb_device_type, usb_card_info->usb_card);
|
|
/* If we have cached the capability */
|
|
if ((usb_card_info->usb_device_type == device) && (usb_card_info->usb_card == card)) {
|
|
ALOGV("%s: capability for device(0x%x), card(%d) is cached, no need to update",
|
|
__func__, device, card);
|
|
goto exit;
|
|
}
|
|
}
|
|
usb_card_info = calloc(1, sizeof(struct usb_card_config));
|
|
if (usb_card_info == NULL) {
|
|
ALOGE("%s: error unable to allocate memory",
|
|
__func__);
|
|
goto exit;
|
|
}
|
|
list_init(&usb_card_info->usb_device_conf_list);
|
|
if (usb_output_device(device)) {
|
|
if (!usb_get_device_pb_config(usb_card_info, card)){
|
|
usb_card_info->usb_card = card;
|
|
usb_card_info->usb_device_type = device;
|
|
usb_get_sidetone_mixer(usb_card_info);
|
|
struct usb_card_config *usb_card_info_temp = NULL;
|
|
usb_card_info_temp = calloc(1, sizeof(struct usb_card_config));
|
|
if (usb_card_info_temp != NULL) {
|
|
list_init(&usb_card_info_temp->usb_device_conf_list);
|
|
if (!usb_get_capability(USB_CAPTURE, usb_card_info_temp, card))
|
|
usbmod->is_capture_supported = true;
|
|
free(usb_card_info_temp);
|
|
}
|
|
list_add_tail(&usbmod->usb_card_conf_list, &usb_card_info->list);
|
|
goto exit;
|
|
}
|
|
} else if (usb_input_device(device)) {
|
|
if (!usb_get_device_cap_config(usb_card_info, card)) {
|
|
usb_card_info->usb_card = card;
|
|
usb_card_info->usb_device_type = device;
|
|
usbmod->is_capture_supported = true;
|
|
list_add_tail(&usbmod->usb_card_conf_list, &usb_card_info->list);
|
|
goto exit;
|
|
}
|
|
}
|
|
/* free memory in error case */
|
|
if (usb_card_info != NULL)
|
|
free(usb_card_info);
|
|
exit:
|
|
if (usb_audio_debug_enable)
|
|
usb_print_active_device();
|
|
return;
|
|
}
|
|
|
|
void usb_remove_device(audio_devices_t device, int card)
|
|
{
|
|
struct listnode *node_i, *temp_i;
|
|
struct listnode *node_j, *temp_j;
|
|
struct usb_device_config *dev_info;
|
|
struct usb_card_config *card_info;
|
|
unsigned int i;
|
|
|
|
ALOGV("%s: device(0x%x), card(%d)",
|
|
__func__, device, card);
|
|
|
|
if (usbmod == NULL) {
|
|
ALOGE("%s: USB device object is NULL", __func__);
|
|
goto exit;
|
|
}
|
|
|
|
if (!(usb_valid_device(device)) || (card < 0)) {
|
|
ALOGE("%s: Invalid parameters device(0x%x), card(%d)",
|
|
__func__, device, card);
|
|
goto exit;
|
|
}
|
|
list_for_each_safe(node_i, temp_i, &usbmod->usb_card_conf_list) {
|
|
card_info = node_to_item(node_i, struct usb_card_config, list);
|
|
ALOGV("%s: card_dev_type (0x%x), card_no(%d)",
|
|
__func__, card_info->usb_device_type, card_info->usb_card);
|
|
if ((device == card_info->usb_device_type) && (card == card_info->usb_card)){
|
|
list_for_each_safe(node_j, temp_j, &card_info->usb_device_conf_list) {
|
|
dev_info = node_to_item(node_j, struct usb_device_config, list);
|
|
ALOGV("%s: bit-width(%d) channel(%d)",
|
|
__func__, dev_info->bit_width, dev_info->channels);
|
|
for (i = 0; i < dev_info->rate_size; i++)
|
|
ALOGV("%s: rate %d", __func__, dev_info->rates[i]);
|
|
|
|
list_remove(node_j);
|
|
free(node_to_item(node_j, struct usb_device_config, list));
|
|
}
|
|
list_remove(node_i);
|
|
free(node_to_item(node_i, struct usb_card_config, list));
|
|
}
|
|
}
|
|
if (audio_is_usb_in_device(device)) { // XXX not sure if we need to check for card
|
|
usbmod->is_capture_supported = false;
|
|
supported_sample_rates_mask[USB_CAPTURE] = 0;
|
|
} else
|
|
supported_sample_rates_mask[USB_PLAYBACK] = 0;
|
|
|
|
exit:
|
|
if (usb_audio_debug_enable)
|
|
usb_print_active_device();
|
|
|
|
return;
|
|
}
|
|
|
|
bool usb_alive(int card) {
|
|
char path[PATH_MAX] = {0};
|
|
// snprintf should never fail
|
|
(void) snprintf(path, sizeof(path), "/proc/asound/card%u/stream0", card);
|
|
return access(path, F_OK) == 0;
|
|
}
|
|
|
|
unsigned long usb_find_service_interval(bool min,
|
|
bool playback) {
|
|
struct usb_card_config *card_info = NULL;
|
|
struct usb_device_config *dev_info = NULL;
|
|
struct listnode *node_i = NULL;
|
|
struct listnode *node_j = NULL;
|
|
unsigned long interval_us = min ? ULONG_MAX : 0;
|
|
list_for_each(node_i, &usbmod->usb_card_conf_list) {
|
|
card_info = node_to_item(node_i, struct usb_card_config, list);
|
|
list_for_each(node_j, &card_info->usb_device_conf_list) {
|
|
dev_info = node_to_item(node_j, struct usb_device_config, list);
|
|
bool match = (playback && (dev_info->type == USB_PLAYBACK)) ||
|
|
(!playback && (dev_info->type == USB_CAPTURE));
|
|
if (match) {
|
|
interval_us = min ?
|
|
_MIN(interval_us, dev_info->service_interval_us) :
|
|
_MAX(interval_us, dev_info->service_interval_us);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return interval_us;
|
|
}
|
|
|
|
int usb_altset_for_service_interval(bool playback,
|
|
unsigned long service_interval,
|
|
uint32_t *bit_width,
|
|
uint32_t *sample_rate,
|
|
uint32_t *channels)
|
|
{
|
|
struct usb_card_config *card_info = NULL;
|
|
struct usb_device_config *dev_info = NULL;
|
|
struct listnode *node_i = NULL;
|
|
struct listnode *node_j = NULL;
|
|
uint32_t bw = 0;
|
|
uint32_t ch = 0;
|
|
uint32_t sr = 0;
|
|
|
|
if (service_interval == 0)
|
|
return 0;
|
|
/* not a valid service interval to search for */
|
|
|
|
#define FIND_BEST_MATCH(local_var, field, cond) \
|
|
list_for_each(node_i, &usbmod->usb_card_conf_list) { \
|
|
/* Currently only apply the first playback sound card configuration */ \
|
|
card_info = node_to_item(node_i, struct usb_card_config, list); \
|
|
list_for_each(node_j, &card_info->usb_device_conf_list) { \
|
|
dev_info = node_to_item(node_j, struct usb_device_config, list); \
|
|
bool match = (playback && (dev_info->type == USB_PLAYBACK)) || \
|
|
(!playback && (dev_info->type == USB_CAPTURE)); \
|
|
if (match && (cond)) { \
|
|
if (dev_info->field == *field) { \
|
|
local_var = dev_info->field; \
|
|
break; \
|
|
} \
|
|
local_var = _MAX(local_var, dev_info->field); \
|
|
} \
|
|
} \
|
|
break; \
|
|
}
|
|
|
|
FIND_BEST_MATCH(bw, bit_width, dev_info->service_interval_us == service_interval);
|
|
FIND_BEST_MATCH(ch, channels, \
|
|
dev_info->service_interval_us == service_interval && \
|
|
dev_info->bit_width == bw);
|
|
sr = *sample_rate;
|
|
list_for_each(node_i, &usbmod->usb_card_conf_list) {
|
|
/* Currently only apply the first playback sound card configuration */
|
|
card_info = node_to_item(node_i, struct usb_card_config, list);
|
|
if ((playback && usb_output_device(card_info->usb_device_type)) ||
|
|
(!playback && usb_input_device(card_info->usb_device_type))) {
|
|
usb_get_best_match_for_sample_rate(&card_info->usb_device_conf_list,
|
|
bw, ch, sr, &sr,
|
|
service_interval,
|
|
true);
|
|
}
|
|
break;
|
|
}
|
|
|
|
#define SET_OR_RETURN_ON_ERROR(arg, local_var, cond) \
|
|
if (local_var != (cond)) arg = local_var; else return -1;
|
|
|
|
SET_OR_RETURN_ON_ERROR(*bit_width, bw, 0);
|
|
SET_OR_RETURN_ON_ERROR(*sample_rate, sr, 0);
|
|
SET_OR_RETURN_ON_ERROR(*channels, ch, 0);
|
|
return 0;
|
|
#undef FIND_BEST_MATCH
|
|
#undef SET_OR_RETURN_ON_ERROR
|
|
}
|
|
|
|
int usb_get_service_interval(bool playback,
|
|
unsigned long *service_interval)
|
|
{
|
|
const char *ctl_name = "USB_AUDIO_RX service_interval";
|
|
struct mixer_ctl *ctl = mixer_get_ctl_by_name(usbmod->adev->mixer,
|
|
ctl_name);
|
|
|
|
if (!playback) {
|
|
ALOGE("%s not valid for capture", __func__);
|
|
return -1;
|
|
}
|
|
|
|
if (!ctl) {
|
|
ALOGV("%s: could not get mixer %s", __func__, ctl_name);
|
|
return -1;
|
|
}
|
|
|
|
*service_interval = mixer_ctl_get_value(ctl, 0);
|
|
return 0;
|
|
}
|
|
|
|
int usb_set_service_interval(bool playback,
|
|
unsigned long service_interval,
|
|
bool *reconfig)
|
|
{
|
|
*reconfig = false;
|
|
unsigned long current_service_interval = 0;
|
|
const char *ctl_name = "USB_AUDIO_RX service_interval";
|
|
struct mixer_ctl *ctl = mixer_get_ctl_by_name(usbmod->adev->mixer,
|
|
ctl_name);
|
|
|
|
if (!playback) {
|
|
ALOGE("%s not valid for capture", __func__);
|
|
return -1;
|
|
}
|
|
|
|
if (!ctl) {
|
|
ALOGV("%s: could not get mixer %s", __func__, ctl_name);
|
|
return -1;
|
|
}
|
|
|
|
if (usb_get_service_interval(playback,
|
|
¤t_service_interval) != 0) {
|
|
ALOGE("%s Unable to get current service interval", __func__);
|
|
return -1;
|
|
}
|
|
|
|
if (current_service_interval != service_interval) {
|
|
mixer_ctl_set_value(ctl, 0, service_interval);
|
|
*reconfig = usbmod->usb_reconfig = true;
|
|
}
|
|
else
|
|
*reconfig = usbmod->usb_reconfig = false;
|
|
return 0;
|
|
}
|
|
|
|
int usb_check_and_set_svc_int(struct audio_usecase *uc_info,
|
|
bool starting_output_stream)
|
|
{
|
|
struct listnode *node = NULL;
|
|
struct audio_usecase *usecase = uc_info;
|
|
bool reconfig = false;
|
|
bool burst_mode = true;
|
|
unsigned long service_interval = 0;
|
|
struct audio_device *adev = usbmod->adev;
|
|
|
|
ALOGV("%s: enter:", __func__);
|
|
|
|
if ((starting_output_stream == true &&
|
|
((uc_info->id == USECASE_AUDIO_PLAYBACK_MMAP) ||
|
|
(uc_info->id == USECASE_AUDIO_PLAYBACK_ULL))) ||
|
|
(voice_is_call_state_active(usbmod->adev))) {
|
|
burst_mode = false;
|
|
} else {
|
|
/* set if the valid usecase do not already exist */
|
|
list_for_each(node, &adev->usecase_list) {
|
|
usecase = node_to_item(node, struct audio_usecase, list);
|
|
if (usecase->type == PCM_PLAYBACK &&
|
|
audio_is_usb_out_device(usecase->devices & AUDIO_DEVICE_OUT_ALL_USB )) {
|
|
switch (usecase->id) {
|
|
case USECASE_AUDIO_PLAYBACK_MMAP:
|
|
case USECASE_AUDIO_PLAYBACK_ULL:
|
|
{
|
|
if (uc_info != usecase) {
|
|
//another ULL stream exists
|
|
ALOGV("%s: another ULL Stream in active use-case list burst mode = false.", __func__);
|
|
burst_mode = false;
|
|
} else {
|
|
ALOGV("%s:current ULL uc is the same as incoming uc_info \
|
|
which means we are stopping the output stream, \
|
|
we don't want to set burst mode to false", __func__);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ALOGV("%s: burst mode(%d).", __func__,burst_mode);
|
|
|
|
service_interval =
|
|
usb_find_service_interval(!burst_mode, true /*playback*/);
|
|
|
|
usb_set_service_interval(true /*playback*/,
|
|
service_interval,
|
|
&reconfig);
|
|
|
|
/* no change or not supported or no active usecases */
|
|
if (reconfig)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
bool usb_is_reconfig_req()
|
|
{
|
|
return usbmod->usb_reconfig;
|
|
}
|
|
|
|
void usb_set_reconfig(bool is_required)
|
|
{
|
|
usbmod->usb_reconfig = is_required;
|
|
}
|
|
|
|
bool usb_connected(struct str_parms *parms) {
|
|
int card = -1;
|
|
struct listnode *node_i = NULL;
|
|
struct usb_card_config *usb_card_info = NULL;
|
|
bool usb_connected = false;
|
|
|
|
if ((parms != NULL) && str_parms_get_int(parms, "card", &card) >= 0) {
|
|
usb_connected = usb_alive(card);
|
|
} else {
|
|
list_for_each(node_i, &usbmod->usb_card_conf_list) {
|
|
usb_card_info = node_to_item(node_i, struct usb_card_config, list);
|
|
if (usb_alive(usb_card_info->usb_card)) {
|
|
usb_connected = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return usb_connected;
|
|
}
|
|
|
|
void usb_init(void *adev)
|
|
{
|
|
if (usbmod == NULL) {
|
|
usbmod = calloc(1, sizeof(struct usb_module));
|
|
if (usbmod == NULL) {
|
|
ALOGE("%s: error unable to allocate memory", __func__);
|
|
goto exit;
|
|
}
|
|
} else {
|
|
memset(usbmod, 0, sizeof(*usbmod));
|
|
}
|
|
|
|
list_init(&usbmod->usb_card_conf_list);
|
|
usbmod->adev = (struct audio_device*)adev;
|
|
usbmod->sidetone_gain = usb_sidetone_gain;
|
|
usbmod->is_capture_supported = false;
|
|
usbmod->usb_reconfig = false;
|
|
exit:
|
|
return;
|
|
}
|
|
|
|
void usb_deinit(void)
|
|
{
|
|
if (NULL != usbmod){
|
|
free(usbmod);
|
|
usbmod = NULL;
|
|
}
|
|
}
|