| /* |
| * Copyright (c) 2013, 2016-2020, 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 |
| #define USBID_SIZE 16 |
| 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; |
| char usbid[USBID_SIZE]; |
| }; |
| |
| 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); |
| tmp[eol-interval_str_start] = '\0'; |
| 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_usbid(struct usb_card_config *usb_card_info, |
| int card) |
| { |
| int32_t fd=-1; |
| char path[128]; |
| int ret = 0; |
| char *saveptr = NULL; |
| |
| memset(usb_card_info->usbid, 0, sizeof(usb_card_info->usbid)); |
| |
| ret = snprintf(path, sizeof(path), "/proc/asound/card%u/usbid", |
| 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 file %s error: %d\n", |
| __func__, path, errno); |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| if (read(fd, usb_card_info->usbid, USBID_SIZE - 1) < 0) { |
| ALOGE("file read error\n"); |
| ret = -EINVAL; |
| usb_card_info->usbid[0] = '\0'; |
| goto done; |
| } |
| |
| strtok_r(usb_card_info->usbid, "\n", &saveptr); |
| |
| done: |
| if (fd >= 0) |
| close(fd); |
| |
| 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); |
| tries--; |
| } |
| |
| 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) { |
| 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_usbid(usb_card_info, card) < 0) { |
| ALOGE("parse card %d usbid fail", card); |
| } |
| |
| 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); |
| list_add_tail(&usbmod->usb_card_conf_list, &usb_card_info->list); |
| goto exit; |
| } |
| } else if (usb_input_device(device)) { |
| if (usb_get_usbid(usb_card_info, card) < 0) { |
| ALOGE("parse card %d usbid fail", card); |
| } |
| |
| 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 && |
| is_usb_out_device_type(&usecase->device_list)) { |
| 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; |
| } |
| |
| char *usb_usbid() |
| { |
| struct usb_card_config *card_info; |
| |
| if (usbmod == NULL) |
| return NULL; |
| |
| if (list_empty(&usbmod->usb_card_conf_list)) |
| return NULL; |
| |
| card_info = node_to_item(list_head(&usbmod->usb_card_conf_list),\ |
| struct usb_card_config, list); |
| |
| return strdup(card_info->usbid); |
| } |
| |
| 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; |
| } |
| } |