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/Android.mk b/hal/Android.mk
index ac6ea74..d249115 100644
--- a/hal/Android.mk
+++ b/hal/Android.mk
@@ -183,6 +183,10 @@
LOCAL_SRC_FILES += audio_extn/sndmonitor.c
endif
+ifeq ($(strip $(AUDIO_FEATURE_ENABLED_USB_SERVICE_INTERVAL)), true)
+ LOCAL_CFLAGS += -DUSB_SERVICE_INTERVAL_ENABLED
+endif
+
LOCAL_SHARED_LIBRARIES += libbase libhidlbase libhwbinder libutils android.hardware.power@1.2 liblog
LOCAL_SRC_FILES += audio_perf.cpp
diff --git a/hal/audio_extn/audio_extn.h b/hal/audio_extn/audio_extn.h
index eb7ddde..054beea 100644
--- a/hal/audio_extn/audio_extn.h
+++ b/hal/audio_extn/audio_extn.h
@@ -81,6 +81,8 @@
#define audio_extn_usb_get_max_bit_width(dir) (0)
#define audio_extn_usb_sup_sample_rates(t, s, l) ((t), (s), (l), 0) /* fix unused warn */
#define audio_extn_usb_alive(adev) (false)
+#define audio_extn_usb_find_service_interval(m, p) ((m), (p), 0) /* fix unused warn */
+#define audio_extn_usb_altset_for_service_interval(p, si, bw, sr, ch) (-1)
#else
void audio_extn_usb_init(void *adev);
void audio_extn_usb_deinit();
@@ -98,6 +100,12 @@
int audio_extn_usb_get_max_bit_width(bool is_playback);
int audio_extn_usb_sup_sample_rates(bool is_playback, uint32_t *sr, uint32_t l);
bool audio_extn_usb_alive(int card);
+unsigned long audio_extn_usb_find_service_interval(bool min, bool playback);
+int audio_extn_usb_altset_for_service_interval(bool is_playback,
+ unsigned long service_interval,
+ uint32_t *bit_width,
+ uint32_t *sample_rate,
+ uint32_t *channel_count);
#endif
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) {
diff --git a/hal/audio_hw.c b/hal/audio_hw.c
index 231b159..cdd10b3 100644
--- a/hal/audio_hw.c
+++ b/hal/audio_hw.c
@@ -1869,6 +1869,54 @@
return 0;
}
+static int check_and_set_usb_service_interval(struct audio_device *adev,
+ struct audio_usecase *uc_info,
+ bool min)
+{
+ struct listnode *node;
+ struct audio_usecase *usecase;
+ bool switch_usecases = false;
+ bool reconfig = false;
+
+ if ((uc_info->id != USECASE_AUDIO_PLAYBACK_MMAP) &&
+ (uc_info->id != USECASE_AUDIO_PLAYBACK_ULL))
+ return -1;
+
+ /* 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:
+ // cannot reconfig while mmap/ull is present.
+ return -1;
+ default:
+ switch_usecases = true;
+ break;
+ }
+ }
+ if (switch_usecases)
+ break;
+ }
+ /*
+ * client can try to set service interval in start_output_stream
+ * to min or to 0 (i.e reset) in stop_output_stream .
+ */
+ unsigned long service_interval =
+ audio_extn_usb_find_service_interval(min, true /*playback*/);
+ int ret = platform_set_usb_service_interval(adev->platform,
+ true /*playback*/,
+ service_interval,
+ &reconfig);
+ /* no change or not supported or no active usecases */
+ if (ret || !reconfig || !switch_usecases)
+ return -1;
+ return 0;
+#undef VALID_USECASE
+}
+
static int stop_output_stream(struct stream_out *out)
{
int i, ret = 0;
@@ -1901,7 +1949,6 @@
disable_snd_device(adev, uc_info->out_snd_device);
list_remove(&uc_info->list);
- free(uc_info);
audio_extn_extspk_update(adev->extspk);
@@ -1916,8 +1963,17 @@
if (usecase->devices & AUDIO_DEVICE_OUT_SPEAKER)
select_devices(adev, usecase->id);
}
+ } else if (audio_is_usb_out_device(out->devices & AUDIO_DEVICE_OUT_ALL_USB)) {
+ ret = check_and_set_usb_service_interval(adev, uc_info, false /*min*/);
+ if (ret == 0) {
+ /* default service interval was successfully updated,
+ reopen USB backend with new service interval */
+ check_and_route_playback_usecases(adev, uc_info, uc_info->out_snd_device);
+ }
+ ret = 0;
}
+ free(uc_info);
ALOGV("%s: exit: status(%d)", __func__, ret);
return ret;
}
@@ -1971,6 +2027,11 @@
/* This must be called before adding this usecase to the list */
if (out->devices & AUDIO_DEVICE_OUT_AUX_DIGITAL)
check_and_set_hdmi_channels(adev, out->config.channels);
+ else if (audio_is_usb_out_device(out->devices & AUDIO_DEVICE_OUT_ALL_USB)) {
+ check_and_set_usb_service_interval(adev, uc_info, true /*min*/);
+ /* USB backend is not reopened immediately.
+ This is eventually done as part of select_devices */
+ }
list_add_tail(&adev->usecase_list, &uc_info->list);
diff --git a/hal/msm8916/platform.c b/hal/msm8916/platform.c
index 170dbd8..1e6e95f 100644
--- a/hal/msm8916/platform.c
+++ b/hal/msm8916/platform.c
@@ -2512,3 +2512,19 @@
size_t *mic_count __unused) {
return -ENOSYS;
}
+
+int platform_set_usb_service_interval(void *platform __unused,
+ bool playback __unused,
+ unsigned long service_interval __unused,
+ bool *reconfig)
+{
+ *reconfig = false;
+ return 0;
+}
+
+int platform_set_backend_cfg(const struct audio_device* adev,
+ snd_device_t snd_device,
+ const struct audio_backend_cfg *backend_cfg)
+{
+ return -1;
+}
diff --git a/hal/msm8960/platform.c b/hal/msm8960/platform.c
index 9f10aeb..5f8bd32 100644
--- a/hal/msm8960/platform.c
+++ b/hal/msm8960/platform.c
@@ -1361,3 +1361,19 @@
size_t *mic_count __unused) {
return -ENOSYS;
}
+
+int platform_set_usb_service_interval(void *platform __unused,
+ bool playback __unused,
+ unsigned long service_interval __unused,
+ bool *reconfig)
+{
+ *reconfig = false;
+ return 0;
+}
+
+int platform_set_backend_cfg(const struct audio_device* adev,
+ snd_device_t snd_device,
+ const struct audio_backend_cfg *backend_cfg)
+{
+ return -1;
+}
diff --git a/hal/msm8974/platform.c b/hal/msm8974/platform.c
index d39e043..c80f994 100644
--- a/hal/msm8974/platform.c
+++ b/hal/msm8974/platform.c
@@ -3479,9 +3479,9 @@
/*
* configures afe with bit width and Sample Rate
*/
-static int platform_set_backend_cfg(const struct audio_device* adev,
- snd_device_t snd_device,
- const struct audio_backend_cfg *backend_cfg)
+int platform_set_backend_cfg(const struct audio_device* adev,
+ snd_device_t snd_device,
+ const struct audio_backend_cfg *backend_cfg)
{
int ret = 0;
@@ -3806,6 +3806,7 @@
unsigned int sample_rate;
unsigned int channels;
int backend_idx = DEFAULT_CODEC_BACKEND;
+ unsigned long service_interval = 0; // 0 is invalid
struct platform_data *my_data = (struct platform_data *)adev->platform;
if (snd_device == SND_DEVICE_OUT_BT_SCO ||
@@ -3850,6 +3851,21 @@
case USB_AUDIO_RX_BACKEND:
audio_extn_usb_is_config_supported(&bit_width,
&sample_rate, &channels, true);
+ if (platform_get_usb_service_interval(adev->platform, true,
+ &service_interval) == 0) {
+ /* overwrite with best altset for this service interval */
+ int ret =
+ audio_extn_usb_altset_for_service_interval(true /*playback*/,
+ service_interval,
+ &bit_width,
+ &sample_rate,
+ &channels);
+ if (ret < 0) {
+ ALOGE("Failed to find altset for service interval %lu, skip reconfig",
+ service_interval);
+ return false;
+ }
+ }
ALOGV("%s: USB BE configured as bit_width(%d)sample_rate(%d)channels(%d)",
__func__, bit_width, sample_rate, channels);
break;
@@ -4387,3 +4403,63 @@
*mic_count = actual_mic_count;
return 0;
}
+
+int platform_set_usb_service_interval(void *platform,
+ bool playback,
+ unsigned long service_interval,
+ bool *reconfig)
+{
+#if defined (USB_SERVICE_INTERVAL_ENABLED)
+ struct platform_data *_platform = (struct platform_data *)platform;
+ *reconfig = false;
+ if (!playback) {
+ ALOGE("%s not valid for capture", __func__);
+ return -1;
+ }
+ const char *ctl_name = "USB_AUDIO_RX service_interval";
+ struct mixer_ctl *ctl = mixer_get_ctl_by_name(_platform->adev->mixer,
+ ctl_name);
+ if (!ctl) {
+ ALOGV("%s: could not get mixer %s", __func__, ctl_name);
+ return -1;
+ }
+ if (mixer_ctl_get_value(ctl, 0) != (int)service_interval) {
+ mixer_ctl_set_value(ctl, 0, service_interval);
+ *reconfig = true;
+ }
+ return 0;
+#else
+ *reconfig = false;
+ (void)platform;
+ (void)playback;
+ (void)service_interval;
+ return -1;
+#endif
+}
+
+int platform_get_usb_service_interval(void *platform,
+ bool playback,
+ unsigned long *service_interval)
+{
+#if defined (USB_SERVICE_INTERVAL_ENABLED)
+ struct platform_data *_platform = (struct platform_data *)platform;
+ if (!playback) {
+ ALOGE("%s not valid for capture", __func__);
+ return -1;
+ }
+ const char *ctl_name = "USB_AUDIO_RX service_interval";
+ struct mixer_ctl *ctl = mixer_get_ctl_by_name(_platform->adev->mixer,
+ ctl_name);
+ if (!ctl) {
+ ALOGV("%s: could not get mixer %s", __func__, ctl_name);
+ return -1;
+ }
+ *service_interval = mixer_ctl_get_value(ctl, 0);
+ return 0;
+#else
+ (void)platform;
+ (void)playback;
+ (void)service_interval;
+ return -1;
+#endif
+}
diff --git a/hal/platform_api.h b/hal/platform_api.h
index e2120b7..e3dc5d1 100644
--- a/hal/platform_api.h
+++ b/hal/platform_api.h
@@ -36,6 +36,7 @@
enum card_status_t;
struct audio_usecase;
enum usecase_type_t;
+struct audio_backend_cfg;
void *platform_init(struct audio_device *adev);
void platform_deinit(void *platform);
@@ -162,4 +163,11 @@
unsigned int channels, int source, audio_usecase_t usecase,
struct audio_microphone_characteristic_t *mic_array,
size_t *mic_count);
+int platform_set_usb_service_interval(void *platform,
+ bool playback,
+ unsigned long service_interval,
+ bool *reconfig);
+int platform_get_usb_service_interval(void *platform,
+ bool playback,
+ unsigned long *service_interval);
#endif // AUDIO_PLATFORM_API_H