audio: Parse USB data service interval
Parse data service interval for all stream descriptors
and use minimum of them to notify ALSA drivers
Bug: 77867216
Test: Make. manual audio playback capture with/without USB headset
Change-Id: I9bae50d4635b5126bc1dd5783b7a33d142c77342
diff --git a/hal/audio_extn/usb.c b/hal/audio_extn/usb.c
index aca4f17..71e1aed 100644
--- a/hal/audio_extn/usb.c
+++ b/hal/audio_extn/usb.c
@@ -37,10 +37,13 @@
#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 DEFAULT_SERVICE_INTERVAL_US 1000
+
/* TODO: dynamically populate supported sample rates */
static uint32_t supported_sample_rates[] =
{192000, 176400, 96000, 88200, 64000, 48000, 44100};
@@ -50,10 +53,10 @@
// assert on sizeof bm v/s size of rates if needed
-enum usb_usecase_type{
+typedef enum usb_usecase_type{
USB_PLAYBACK = 0,
USB_CAPTURE,
-};
+} usb_usecase_type_t;
enum {
USB_SIDETONE_ENABLE_INDEX = 0,
@@ -67,6 +70,8 @@
unsigned int channel_count;
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 {
@@ -271,6 +276,43 @@
return 0;
}
+static int usb_get_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;
+ ALOGV("%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)
@@ -286,6 +328,7 @@
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;
@@ -366,6 +409,7 @@
ret = -ENOMEM;
break;
}
+ usb_device_info->type = type;
/* Bit bit_width parsing */
bit_width_start = strstr(str_start, "Format: ");
if (bit_width_start == NULL) {
@@ -443,6 +487,18 @@
free(usb_device_info);
continue;
}
+ // Data packet interval is an optional field.
+ // Assume 1ms interval if this cannot be read
+ 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 = usb_get_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);
@@ -697,12 +753,14 @@
return true;
}
-static bool usb_get_best_match_for_sample_rate(
+static bool usb_get_best_match_for_sample_rate (
struct listnode *dev_list,
unsigned int bit_width,
unsigned int channel_count,
unsigned int stream_sample_rate,
- unsigned int *sr)
+ unsigned int *sr,
+ unsigned int service_interval,
+ bool do_service_interval_check)
{
struct listnode *node_i;
struct usb_device_config *dev_info;
@@ -720,7 +778,10 @@
"%s: USB ch(%d)bw(%d), stm ch(%d)bw(%d)sr(%d), candidate(%d)",
__func__, dev_info->channel_count, dev_info->bit_width,
channel_count, bit_width, stream_sample_rate, candidate);
- if ((dev_info->bit_width != bit_width) || dev_info->channel_count != channel_count)
+ if ((dev_info->bit_width != bit_width) ||
+ (dev_info->channel_count != channel_count) ||
+ (do_service_interval_check && (dev_info->service_interval_us !=
+ service_interval)))
continue;
candidate = 0;
@@ -787,7 +848,9 @@
*bit_width,
*channel_count,
*sample_rate,
- 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, *channel_count);
@@ -1110,6 +1173,84 @@
return access(path, F_OK) == 0;
}
+unsigned long audio_extn_usb_find_service_interval(bool min,
+ bool playback) {
+ struct usb_card_config *card_info;
+ struct usb_device_config *dev_info;
+ struct listnode *node_i;
+ struct listnode *node_j;
+ unsigned long interval_us = min ? ULONG_MAX : 1; // 0 is invalid
+ 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);
+ if ((playback && (dev_info->type == USB_PLAYBACK)) ||
+ (!playback && (dev_info->type == USB_CAPTURE))) {
+ interval_us = min ?
+ _MIN(interval_us, dev_info->service_interval_us) :
+ _MAX(interval_us, dev_info->service_interval_us);
+ }
+ }
+ break;
+ }
+ return interval_us;
+}
+
+int audio_extn_usb_altset_for_service_interval(bool playback,
+ unsigned long service_interval,
+ uint32_t *bit_width,
+ uint32_t *sample_rate,
+ uint32_t *channel_count)
+{
+ struct usb_card_config *card_info;
+ struct usb_device_config *dev_info;
+ struct listnode *node_i;
+ struct listnode *node_j;
+ uint32_t bw = 0;
+ uint32_t ch = 0;
+ uint32_t sr = 0;
+ 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);
+ if ((playback && dev_info->type == USB_PLAYBACK) ||
+ (!playback && dev_info->type == USB_CAPTURE)) {
+ if (dev_info->service_interval_us != service_interval)
+ continue;
+ if (dev_info->bit_width > bw) {
+ bw = dev_info->bit_width;
+ ch = dev_info->channel_count;
+ } else if (dev_info->bit_width == bw &&
+ dev_info->channel_count > ch) {
+ ch = dev_info->channel_count;
+ }
+ }
+ }
+ break;
+ }
+ if (bw == 0 || ch == 0)
+ return -1;
+ 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;
+ }
+ if (sr == 0)
+ return -1;
+ *bit_width = bw;
+ *sample_rate = sr;
+ *channel_count = ch;
+ return 0;
+}
+
void audio_extn_usb_init(void *adev)
{
if (usbmod == NULL) {