summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/class/audio/audio_host.c295
-rw-r--r--test/unit-test/test/host/audio/test_audio_host.c95
2 files changed, 241 insertions, 149 deletions
diff --git a/src/class/audio/audio_host.c b/src/class/audio/audio_host.c
index 82d725329..ad2fd677f 100644
--- a/src/class/audio/audio_host.c
+++ b/src/class/audio/audio_host.c
@@ -874,8 +874,6 @@ bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uin
// Enumeration
//--------------------------------------------------------------------+
- #define AUDIOH_MAX_AC_ENTITIES TUH_AUDIO_STREAM_DIRECTION_COUNT
-
typedef struct {
uint8_t id;
uint8_t source_id;
@@ -896,13 +894,9 @@ typedef struct {
} audioh_clock_info_t;
typedef struct {
- audioh_terminal_info_t terminal[AUDIOH_MAX_AC_ENTITIES];
- audioh_fu_info_t feature_unit[AUDIOH_MAX_AC_ENTITIES];
- audioh_clock_info_t clock[AUDIOH_MAX_AC_ENTITIES];
- uint8_t terminal_count;
- uint8_t feature_unit_count;
- uint8_t clock_count;
-} audioh_ac_map_t;
+ const uint8_t *desc_start;
+ const uint8_t *desc_end;
+} audioh_ac_desc_range_t;
#if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
static uint8_t audioh_uac2_control_access(uint32_t controls, uint8_t position) {
@@ -923,156 +917,173 @@ static bool audioh_as_rate_fits(const audioh_interface_t *p_audio, const tuh_aud
(stream->dir != TUSB_DIR_OUT || (packet_bytes <= epbuf_size && packet_bytes <= CFG_TUH_AUDIO_STREAM_BUFSIZE));
}
-static void audioh_ac_terminal_add(audioh_ac_map_t *map, uint8_t id, uint8_t source_id, uint8_t clock_id,
- tusb_dir_t stream_dir) {
- if (map->terminal_count < TU_ARRAY_SIZE(map->terminal)) {
- map->terminal[map->terminal_count++] =
- (audioh_terminal_info_t){.id = id, .source_id = source_id, .clock_id = clock_id, .stream_dir = stream_dir};
- }
-}
-
-static void audioh_ac_feature_unit_add(audioh_ac_map_t *map, uint8_t id, uint8_t source_id, uint8_t mute_access,
- uint8_t volume_access) {
- if ((mute_access != AUDIOH_CTRL_NONE || volume_access != AUDIOH_CTRL_NONE) &&
- map->feature_unit_count < TU_ARRAY_SIZE(map->feature_unit)) {
- map->feature_unit[map->feature_unit_count++] =
- (audioh_fu_info_t){.id = id, .source_id = source_id, .mute_access = mute_access, .volume_access = volume_access};
- }
-}
-
-static const audioh_terminal_info_t *audioh_ac_terminal_find(const audioh_ac_map_t *map, uint8_t id) {
- for (uint8_t i = 0; i < map->terminal_count; i++) {
- if (map->terminal[i].id == id) {
- return &map->terminal[i];
+static bool audioh_ac_entity_valid(const audioh_interface_t *p_audio, const uint8_t *p_desc) {
+ switch (p_audio->protocol) {
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+ case AUDIO_INT_PROTOCOL_CODE_V1: {
+ switch (tu_desc_subtype(p_desc)) {
+ case AUDIO10_CS_AC_INTERFACE_INPUT_TERMINAL:
+ return TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio10_desc_input_terminal_t));
+ case AUDIO10_CS_AC_INTERFACE_OUTPUT_TERMINAL:
+ return TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio10_desc_output_terminal_t));
+ case AUDIO10_CS_AC_INTERFACE_FEATURE_UNIT: {
+ TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= 7), false);
+ const uint8_t control_size = p_desc[5];
+ return TUH_VALIDATE_BASIC(control_size > 0) &&
+ TUH_VALIDATE_BASIC(control_size <= (uint8_t)(tu_desc_len(p_desc) - 7));
+ }
+ default:
+ return true;
+ }
}
+ #endif
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ case AUDIO_INT_PROTOCOL_CODE_V2:
+ switch (tu_desc_subtype(p_desc)) {
+ case AUDIO20_CS_AC_INTERFACE_INPUT_TERMINAL:
+ return TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio20_desc_input_terminal_t));
+ case AUDIO20_CS_AC_INTERFACE_OUTPUT_TERMINAL:
+ return TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio20_desc_output_terminal_t));
+ case AUDIO20_CS_AC_INTERFACE_FEATURE_UNIT:
+ return TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= 10);
+ case AUDIO20_CS_AC_INTERFACE_CLOCK_SOURCE:
+ return TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio20_desc_clock_source_t));
+ default:
+ return true;
+ }
+ #endif
+ default:
+ return false;
}
- return NULL;
}
+static bool audioh_ac_terminal_find(const audioh_interface_t *p_audio, const audioh_ac_desc_range_t *range, uint8_t id,
+ audioh_terminal_info_t *info) {
+ for (const uint8_t *p_desc = range->desc_start; p_desc < range->desc_end; p_desc = tu_desc_next(p_desc)) {
+ if (tu_desc_type(p_desc) != TUSB_DESC_CS_INTERFACE || tu_desc_len(p_desc) < 4 || p_desc[3] != id) {
+ continue;
+ }
+ switch (p_audio->protocol) {
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
+ case AUDIO_INT_PROTOCOL_CODE_V1:
+ if (tu_desc_subtype(p_desc) == AUDIO10_CS_AC_INTERFACE_INPUT_TERMINAL) {
+ const audio10_desc_input_terminal_t *terminal = (const audio10_desc_input_terminal_t *)p_desc;
+ if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING) {
+ *info = (audioh_terminal_info_t){.id = id, .stream_dir = TUSB_DIR_OUT};
+ return true;
+ }
+ } else if (tu_desc_subtype(p_desc) == AUDIO10_CS_AC_INTERFACE_OUTPUT_TERMINAL) {
+ const audio10_desc_output_terminal_t *terminal = (const audio10_desc_output_terminal_t *)p_desc;
+ if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING) {
+ *info = (audioh_terminal_info_t){.id = id, .source_id = terminal->bSourceID, .stream_dir = TUSB_DIR_IN};
+ return true;
+ }
+ }
+ break;
+ #endif
#if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
-static const audioh_clock_info_t *audioh_ac_clock_find(const audioh_ac_map_t *map, uint8_t id) {
- for (uint8_t i = 0; i < map->clock_count; i++) {
- if (map->clock[i].id == id) {
- return &map->clock[i];
+ case AUDIO_INT_PROTOCOL_CODE_V2:
+ if (tu_desc_subtype(p_desc) == AUDIO20_CS_AC_INTERFACE_INPUT_TERMINAL) {
+ const audio20_desc_input_terminal_t *terminal = (const audio20_desc_input_terminal_t *)p_desc;
+ if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING) {
+ *info = (audioh_terminal_info_t){.id = id, .clock_id = terminal->bCSourceID, .stream_dir = TUSB_DIR_OUT};
+ return true;
+ }
+ } else if (tu_desc_subtype(p_desc) == AUDIO20_CS_AC_INTERFACE_OUTPUT_TERMINAL) {
+ const audio20_desc_output_terminal_t *terminal = (const audio20_desc_output_terminal_t *)p_desc;
+ if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING) {
+ *info = (audioh_terminal_info_t){.id = id,
+ .source_id = terminal->bSourceID,
+ .clock_id = terminal->bCSourceID,
+ .stream_dir = TUSB_DIR_IN};
+ return true;
+ }
+ }
+ break;
+ #endif
+ default:
+ return false;
}
}
- return NULL;
+ return false;
}
- #endif
+static bool audioh_ac_feature_unit_parse(const audioh_interface_t *p_audio, const uint8_t *p_desc,
+ audioh_fu_info_t *info) {
+ if (tu_desc_type(p_desc) != TUSB_DESC_CS_INTERFACE) {
+ return false;
+ }
+ switch (p_audio->protocol) {
#if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
-static bool audioh_uac1_parse_ac_entity(audioh_ac_map_t *map, const uint8_t *p_desc) {
- switch (tu_desc_subtype(p_desc)) {
- case AUDIO10_CS_AC_INTERFACE_INPUT_TERMINAL: {
- TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio10_desc_input_terminal_t)), false);
- const audio10_desc_input_terminal_t *terminal = (const audio10_desc_input_terminal_t *)p_desc;
- if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING) {
- audioh_ac_terminal_add(map, terminal->bTerminalID, 0, 0, TUSB_DIR_OUT);
+ case AUDIO_INT_PROTOCOL_CODE_V1:
+ if (tu_desc_subtype(p_desc) == AUDIO10_CS_AC_INTERFACE_FEATURE_UNIT) {
+ const uint8_t controls = p_desc[6];
+ *info = (audioh_fu_info_t){.id = p_desc[3],
+ .source_id = p_desc[4],
+ .mute_access = (controls & AUDIO10_FU_CONTROL_BM_MUTE) ? AUDIOH_CTRL_READ_WRITE
+ : AUDIOH_CTRL_NONE,
+ .volume_access = (controls & AUDIO10_FU_CONTROL_BM_VOLUME) ? AUDIOH_CTRL_READ_WRITE
+ : AUDIOH_CTRL_NONE};
+ return info->mute_access != AUDIOH_CTRL_NONE || info->volume_access != AUDIOH_CTRL_NONE;
}
break;
- }
- case AUDIO10_CS_AC_INTERFACE_OUTPUT_TERMINAL: {
- TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio10_desc_output_terminal_t)), false);
- const audio10_desc_output_terminal_t *terminal = (const audio10_desc_output_terminal_t *)p_desc;
- if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING) {
- audioh_ac_terminal_add(map, terminal->bTerminalID, terminal->bSourceID, 0, TUSB_DIR_IN);
+ #endif
+ #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
+ case AUDIO_INT_PROTOCOL_CODE_V2:
+ if (tu_desc_subtype(p_desc) == AUDIO20_CS_AC_INTERFACE_FEATURE_UNIT) {
+ const uint32_t controls = tu_le32toh(tu_unaligned_read32(&p_desc[5]));
+ *info =
+ (audioh_fu_info_t){.id = p_desc[3],
+ .source_id = p_desc[4],
+ .mute_access = audioh_uac2_control_access(controls, AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS),
+ .volume_access =
+ audioh_uac2_control_access(controls, AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS)};
+ return info->mute_access != AUDIOH_CTRL_NONE || info->volume_access != AUDIOH_CTRL_NONE;
}
break;
- }
- case AUDIO10_CS_AC_INTERFACE_FEATURE_UNIT: {
- TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= 7), false);
- const uint8_t control_size = p_desc[5];
- TU_VERIFY(TUH_VALIDATE_BASIC(control_size > 0), false);
- TU_VERIFY(TUH_VALIDATE_BASIC(control_size <= (uint8_t)(tu_desc_len(p_desc) - 7)), false);
- const uint8_t controls = p_desc[6];
- audioh_ac_feature_unit_add(map, p_desc[3], p_desc[4],
- (controls & AUDIO10_FU_CONTROL_BM_MUTE) ? AUDIOH_CTRL_READ_WRITE : AUDIOH_CTRL_NONE,
- (controls & AUDIO10_FU_CONTROL_BM_VOLUME) ? AUDIOH_CTRL_READ_WRITE : AUDIOH_CTRL_NONE);
- break;
- }
+ #endif
default:
break;
}
- return true;
+ return false;
}
- #endif
#if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
-static bool audioh_uac2_parse_ac_entity(audioh_ac_map_t *map, const uint8_t *p_desc) {
- switch (tu_desc_subtype(p_desc)) {
- case AUDIO20_CS_AC_INTERFACE_INPUT_TERMINAL: {
- TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio20_desc_input_terminal_t)), false);
- const audio20_desc_input_terminal_t *terminal = (const audio20_desc_input_terminal_t *)p_desc;
- if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING) {
- audioh_ac_terminal_add(map, terminal->bTerminalID, 0, terminal->bCSourceID, TUSB_DIR_OUT);
- }
- break;
- }
- case AUDIO20_CS_AC_INTERFACE_OUTPUT_TERMINAL: {
- TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio20_desc_output_terminal_t)), false);
- const audio20_desc_output_terminal_t *terminal = (const audio20_desc_output_terminal_t *)p_desc;
- if (tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING) {
- audioh_ac_terminal_add(map, terminal->bTerminalID, terminal->bSourceID, terminal->bCSourceID, TUSB_DIR_IN);
- }
- break;
- }
- case AUDIO20_CS_AC_INTERFACE_FEATURE_UNIT: {
- TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= 10), false);
- const uint32_t controls = tu_le32toh(tu_unaligned_read32(&p_desc[5]));
- audioh_ac_feature_unit_add(map, p_desc[3], p_desc[4],
- audioh_uac2_control_access(controls, AUDIO20_FEATURE_UNIT_CTRL_MUTE_POS),
- audioh_uac2_control_access(controls, AUDIO20_FEATURE_UNIT_CTRL_VOLUME_POS));
- break;
- }
- case AUDIO20_CS_AC_INTERFACE_CLOCK_SOURCE: {
- TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio20_desc_clock_source_t)), false);
+static bool audioh_ac_clock_find(const audioh_ac_desc_range_t *range, uint8_t id, audioh_clock_info_t *info) {
+ for (const uint8_t *p_desc = range->desc_start; p_desc < range->desc_end; p_desc = tu_desc_next(p_desc)) {
+ if (tu_desc_type(p_desc) == TUSB_DESC_CS_INTERFACE &&
+ tu_desc_subtype(p_desc) == AUDIO20_CS_AC_INTERFACE_CLOCK_SOURCE && p_desc[3] == id) {
const audio20_desc_clock_source_t *clock = (const audio20_desc_clock_source_t *)p_desc;
- if (map->clock_count < TU_ARRAY_SIZE(map->clock)) {
- map->clock[map->clock_count++] =
- (audioh_clock_info_t){.id = clock->bClockID,
- .frequency_access =
- audioh_uac2_control_access(clock->bmControls, AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS)};
- }
- break;
+ *info =
+ (audioh_clock_info_t){.id = id,
+ .frequency_access =
+ audioh_uac2_control_access(clock->bmControls, AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS)};
+ return true;
}
- default:
- break;
}
- return true;
+ return false;
}
#endif
-static bool audioh_parse_ac_entity(audioh_interface_t *p_audio, audioh_ac_map_t *map, const uint8_t *p_desc) {
- switch (p_audio->protocol) {
- #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
- case AUDIO_INT_PROTOCOL_CODE_V1:
- return audioh_uac1_parse_ac_entity(map, p_desc);
- #endif
- #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
- case AUDIO_INT_PROTOCOL_CODE_V2:
- return audioh_uac2_parse_ac_entity(map, p_desc);
- #endif
- default:
- return false;
- }
-}
-
-static void audioh_link_feature_units(audioh_interface_t *p_audio, const audioh_ac_map_t *map) {
+static void audioh_link_feature_units(audioh_interface_t *p_audio, const audioh_ac_desc_range_t *range) {
for (uint8_t direction = TUSB_DIR_OUT; direction <= TUSB_DIR_IN; direction++) {
tuh_audio_stream_t *stream = audioh_get_stream(p_audio, (tusb_dir_t)direction);
if (stream == NULL || stream->as_count == 0) {
continue;
}
- const audioh_terminal_info_t *terminal = audioh_ac_terminal_find(map, stream->as[0].terminal_id);
- if (terminal == NULL || terminal->stream_dir != direction) {
+ audioh_terminal_info_t terminal;
+ if (!audioh_ac_terminal_find(p_audio, range, stream->as[0].terminal_id, &terminal) ||
+ terminal.stream_dir != direction) {
continue;
}
- for (uint8_t i = 0; i < map->feature_unit_count; i++) {
- const audioh_fu_info_t *fu = &map->feature_unit[i];
- const bool linked =
- (direction == TUSB_DIR_OUT) ? (fu->source_id == terminal->id) : (fu->id == terminal->source_id);
+ for (const uint8_t *p_desc = range->desc_start; p_desc < range->desc_end; p_desc = tu_desc_next(p_desc)) {
+ audioh_fu_info_t fu;
+ if (!audioh_ac_feature_unit_parse(p_audio, p_desc, &fu)) {
+ continue;
+ }
+ const bool linked = (direction == TUSB_DIR_OUT) ? (fu.source_id == terminal.id) : (fu.id == terminal.source_id);
if (linked) {
- audioh_stream_set_feature_unit(stream, fu->id, fu->mute_access, fu->volume_access);
+ audioh_stream_set_feature_unit(stream, fu.id, fu.mute_access, fu.volume_access);
break;
}
}
@@ -1170,17 +1181,17 @@ static bool audioh_parse_as_interface(audioh_interface_t *p_audio, const uint8_t
}
#if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
-static int8_t audioh_uac2_rate_source_get(audioh_interface_t *p_audio, const audioh_ac_map_t *map,
+static int8_t audioh_uac2_rate_source_get(audioh_interface_t *p_audio, const audioh_ac_desc_range_t *range,
const audioh_terminal_info_t *terminal) {
if (terminal->clock_id == 0) {
return -1;
}
- const audioh_clock_info_t *clock = audioh_ac_clock_find(map, terminal->clock_id);
- if (clock == NULL || clock->frequency_access == AUDIOH_CTRL_NONE) {
+ audioh_clock_info_t clock;
+ if (!audioh_ac_clock_find(range, terminal->clock_id, &clock) || clock.frequency_access == AUDIOH_CTRL_NONE) {
return -1;
}
for (uint8_t i = 0; i < p_audio->rate_source_count; i++) {
- if (p_audio->rate_source[i].control_id == clock->id) {
+ if (p_audio->rate_source[i].control_id == clock.id) {
return (int8_t)i;
}
}
@@ -1189,7 +1200,7 @@ static int8_t audioh_uac2_rate_source_get(audioh_interface_t *p_audio, const aud
}
const uint8_t idx = p_audio->rate_source_count++;
p_audio->rate_source[idx] =
- (audioh_rate_source_t){.control_id = clock->id, .frequency_access = clock->frequency_access};
+ (audioh_rate_source_t){.control_id = clock.id, .frequency_access = clock.frequency_access};
return (int8_t)idx;
}
#endif
@@ -1217,12 +1228,9 @@ static bool audioh_uac1_rates_store(const audioh_interface_t *p_audio, const tuh
// Parse one Audio Streaming interface alternate setting and register its
// supported configurations into the matching stream. Returns the descriptor
// pointer of the next interface.
-static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const audioh_ac_map_t *ac_map,
+static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const audioh_ac_desc_range_t *ac_desc,
const tusb_desc_interface_t *desc_itf, const uint8_t *p_desc,
const uint8_t *desc_end) {
- #if !(CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2)
- (void)ac_map;
- #endif
TU_VERIFY(audioh_desc_valid(p_desc, desc_end, sizeof(tusb_desc_interface_t)), NULL);
const uint8_t itf_num = desc_itf->bInterfaceNumber;
@@ -1392,8 +1400,9 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const audioh_
const audioh_ep_info_t *ep = &ep_info;
tuh_audio_stream_t *stream = audioh_get_stream(p_audio, tu_edpt_dir(ep->ep_addr));
TU_ASSERT(stream != NULL, p_desc);
- const audioh_terminal_info_t *terminal = audioh_ac_terminal_find(ac_map, class_info.terminal_id);
- if (terminal == NULL || terminal->stream_dir != stream->dir) {
+ audioh_terminal_info_t terminal;
+ if (!audioh_ac_terminal_find(p_audio, ac_desc, class_info.terminal_id, &terminal) ||
+ terminal.stream_dir != stream->dir) {
TU_LOG_DRV(" AUDIO AS itf %u alt %u: terminal %u does not match endpoint direction\r\n", itf_num, alt,
class_info.terminal_id);
return p_desc;
@@ -1439,7 +1448,7 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const audioh_
#endif
#if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
case AUDIO_INT_PROTOCOL_CODE_V2: {
- const int8_t rate_source_idx = audioh_uac2_rate_source_get(p_audio, ac_map, terminal);
+ const int8_t rate_source_idx = audioh_uac2_rate_source_get(p_audio, ac_desc, &terminal);
if (rate_source_idx < 0) {
TU_LOG_DRV(" AUDIO AS itf %u alt %u: direct Clock Source not found\r\n", itf_num, alt);
return p_desc;
@@ -1514,9 +1523,8 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface
TU_LOG_DRV("AUDIO opening AC Interface %u (addr = %u)\r\n", desc_itf->bInterfaceNumber, dev_addr);
- audioh_ac_map_t ac_map = {0};
-
- p_desc = tu_desc_next(p_desc);
+ p_desc = tu_desc_next(p_desc);
+ audioh_ac_desc_range_t ac_desc = {.desc_start = p_desc};
while (p_desc < desc_end) {
if (!audioh_desc_valid(p_desc, desc_end, 2)) {
goto open_failed;
@@ -1529,12 +1537,13 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface
if (!audioh_desc_valid(p_desc, desc_end, 3)) {
goto open_failed;
}
- if (!audioh_parse_ac_entity(p_audio, &ac_map, p_desc)) {
+ if (!audioh_ac_entity_valid(p_audio, p_desc)) {
goto open_failed;
}
}
p_desc = tu_desc_next(p_desc);
}
+ ac_desc.desc_end = p_desc;
// Parse the contiguous Audio Streaming interfaces of this audio function.
while (p_desc < desc_end) {
@@ -1558,13 +1567,13 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface
TU_LOG_DRV(" Found AS Interface %u (alt = %u)\r\n", desc_interface->bInterfaceNumber,
desc_interface->bAlternateSetting);
- p_desc = audioh_parse_as(p_audio, &ac_map, desc_interface, p_desc, desc_end);
+ p_desc = audioh_parse_as(p_audio, &ac_desc, desc_interface, p_desc, desc_end);
if (p_desc == NULL) {
goto open_failed;
}
}
- audioh_link_feature_units(p_audio, &ac_map);
+ audioh_link_feature_units(p_audio, &ac_desc);
// UAC2 configurations receive their rates asynchronously during mount.
// Release the tentative instance when no supported AS alternate was found.
diff --git a/test/unit-test/test/host/audio/test_audio_host.c b/test/unit-test/test/host/audio/test_audio_host.c
index 2f92b232f..55b5e8ef6 100644
--- a/test/unit-test/test/host/audio/test_audio_host.c
+++ b/test/unit-test/test/host/audio/test_audio_host.c
@@ -5,6 +5,7 @@
#include "unity.h"
#include "audio_host_test.h"
+#include "class/midi/midi.h"
TEST_SOURCE_FILE("audio_host.c")
TEST_SOURCE_FILE("tusb_fifo.c")
@@ -23,7 +24,13 @@ enum {
CAPTURE_FU = 12,
CAPTURE_OUTPUT_TERM = 13,
- PLAYBACK_CLOCK = 10,
+ PLAYBACK_CLOCK = 10,
+ UNRELATED_CLOCK_0 = 20,
+ UNRELATED_CLOCK_1 = 21,
+ UNRELATED_TERM_0 = 22,
+ UNRELATED_TERM_1 = 23,
+ UNRELATED_FU_0 = 24,
+ UNRELATED_FU_1 = 25,
};
static bool interface_set_result;
@@ -306,7 +313,7 @@ static const uint8_t playback_with_explicit_feedback[] = {
TEST_UAC1_DATA_EP(0x81, TUSB_ISO_EP_ATT_EXPLICIT_FB, 3, 1),
};
-static const uint8_t midi_only_collection[] = {
+static const uint8_t midi1_only_collection[] = {
TEST_UAC1_AC_HEADER,
9,
TUSB_DESC_INTERFACE,
@@ -319,6 +326,24 @@ static const uint8_t midi_only_collection[] = {
0,
};
+static const uint8_t midi2_only_collection[] = {
+ TEST_UAC1_AC_HEADER,
+ 9,
+ TUSB_DESC_INTERFACE,
+ AUDIO_AS_ITF,
+ 1,
+ 0,
+ TUSB_CLASS_AUDIO,
+ AUDIO_SUBCLASS_MIDI_STREAMING,
+ AUDIO_FUNC_PROTOCOL_CODE_UNDEF,
+ 0,
+ 7,
+ TUSB_DESC_CS_INTERFACE,
+ MIDI_CS_INTERFACE_HEADER,
+ U16_TO_U8S_LE(MIDI_VERSION_2_0),
+ U16_TO_U8S_LE(7),
+};
+
static const uint8_t uac2_control_interface[] = {
9, TUSB_DESC_INTERFACE, AUDIO_AC_ITF, 0, 0, TUSB_CLASS_AUDIO, AUDIO_SUBCLASS_CONTROL, AUDIO_INT_PROTOCOL_CODE_V2, 0,
};
@@ -340,6 +365,21 @@ static const uint8_t uac2_playback[] = {
TEST_UAC2_CS_DATA_EP,
};
+static const uint8_t uac2_playback_after_unrelated_clocks[] = {
+ TEST_UAC2_AC_HEADER(62),
+ TEST_UAC2_CLOCK_SOURCE(UNRELATED_CLOCK_0, AUDIO20_CTRL_RW << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS),
+ TEST_UAC2_CLOCK_SOURCE(UNRELATED_CLOCK_1, AUDIO20_CTRL_RW << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS),
+ TEST_UAC2_INPUT_TERM(PLAYBACK_INPUT_TERM, AUDIO_TERM_TYPE_USB_STREAMING, PLAYBACK_CLOCK, 2),
+ TEST_UAC2_OUTPUT_TERM(PLAYBACK_OUTPUT_TERM, AUDIO_TERM_TYPE_OUT_HEADPHONES, PLAYBACK_INPUT_TERM, PLAYBACK_CLOCK),
+ TEST_UAC2_CLOCK_SOURCE(PLAYBACK_CLOCK, AUDIO20_CTRL_RW << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS),
+ TEST_UAC2_AS_ALT0,
+ TEST_UAC2_AS_INTERFACE(1, 1),
+ TEST_UAC2_AS_GENERAL(PLAYBACK_INPUT_TERM, 2),
+ TEST_UAC2_FORMAT(2, 16),
+ TEST_UAC1_DATA_EP(0x01, TUSB_ISO_EP_ATT_ASYNCHRONOUS, 192, 1),
+ TEST_UAC2_CS_DATA_EP,
+};
+
static const uint8_t uac2_playback_read_only_feature_unit[] = {
TEST_UAC2_AC_HEADER(64),
TEST_UAC2_CLOCK_SOURCE(PLAYBACK_CLOCK, AUDIO20_CTRL_RW << AUDIO20_CLOCK_SOURCE_CTRL_CLK_FRQ_POS),
@@ -436,6 +476,23 @@ static const uint8_t playback_fu_before_terminal[] = {
TEST_UAC1_CS_DATA_EP,
};
+static const uint8_t playback_after_unrelated_ac_entities[] = {
+ TEST_UAC1_AC_HEADER,
+ TEST_UAC1_INPUT_TERM(UNRELATED_TERM_0, AUDIO_TERM_TYPE_USB_STREAMING, 2),
+ TEST_UAC1_INPUT_TERM(UNRELATED_TERM_1, AUDIO_TERM_TYPE_USB_STREAMING, 2),
+ TEST_UAC1_FEATURE_UNIT(UNRELATED_FU_0, UNRELATED_TERM_0),
+ TEST_UAC1_FEATURE_UNIT(UNRELATED_FU_1, UNRELATED_TERM_1),
+ TEST_UAC1_INPUT_TERM(PLAYBACK_INPUT_TERM, AUDIO_TERM_TYPE_USB_STREAMING, 2),
+ TEST_UAC1_FEATURE_UNIT(PLAYBACK_FU, PLAYBACK_INPUT_TERM),
+ TEST_UAC1_OUTPUT_TERM(PLAYBACK_OUTPUT_TERM, AUDIO_TERM_TYPE_OUT_HEADPHONES, PLAYBACK_FU),
+ TEST_UAC1_AS_ALT0,
+ TEST_UAC1_AS_INTERFACE(1, 1),
+ TEST_UAC1_AS_GENERAL(PLAYBACK_INPUT_TERM),
+ TEST_UAC1_FORMAT(2, 2, 16, 48000),
+ TEST_UAC1_DATA_EP(0x01, TUSB_ISO_EP_ATT_ADAPTIVE, 192, 1),
+ TEST_UAC1_CS_DATA_EP,
+};
+
static const uint8_t playback_fu_without_mute_volume[] = {
TEST_UAC1_AC_HEADER,
TEST_UAC1_FEATURE_UNIT_CTRL(PLAYBACK_FU, PLAYBACK_INPUT_TERM, AUDIO10_FU_CONTROL_BM_BASS),
@@ -831,12 +888,19 @@ static void mount_descriptors(const uint8_t *desc, uint16_t desc_len) {
control_xfer_count = 0;
}
-void test_audio_host_saves_and_opens_explicit_feedback_endpoint(void) {
- // A MIDI-only AC collection must not consume an Audio Host instance.
- TEST_ASSERT_EQUAL_UINT16(0, audioh_open(0, AUDIO_DEV_ADDR, (const tusb_desc_interface_t *)midi_only_collection,
- sizeof(midi_only_collection)));
+void test_audio_host_rejects_midi1_collection_without_consuming_instance(void) {
+ TEST_ASSERT_EQUAL_UINT16(0, audioh_open(0, AUDIO_DEV_ADDR, (const tusb_desc_interface_t *)midi1_only_collection,
+ sizeof(midi1_only_collection)));
TEST_ASSERT_EQUAL_UINT8(0, tuh_audio_get_dev_addr(0));
+}
+
+void test_audio_host_rejects_midi2_collection_without_consuming_instance(void) {
+ TEST_ASSERT_EQUAL_UINT16(0, audioh_open(0, AUDIO_DEV_ADDR, (const tusb_desc_interface_t *)midi2_only_collection,
+ sizeof(midi2_only_collection)));
+ TEST_ASSERT_EQUAL_UINT8(0, tuh_audio_get_dev_addr(0));
+}
+void test_audio_host_saves_and_opens_explicit_feedback_endpoint(void) {
mount_descriptors(playback_with_explicit_feedback, sizeof(playback_with_explicit_feedback));
TEST_ASSERT_EQUAL_UINT8(1, tuh_audio_stream_count(0));
@@ -976,6 +1040,18 @@ void test_audio_host_uac2_shared_clock_is_discovered_once_for_both_streams(void)
TEST_ASSERT_EQUAL_UINT8(2, tuh_audio_config_count(0, 1));
}
+void test_audio_host_uac2_finds_clock_source_after_unrelated_clocks(void) {
+ open_descriptors(uac2_playback_after_unrelated_clocks, sizeof(uac2_playback_after_unrelated_clocks));
+ TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
+
+ TEST_ASSERT_EQUAL_UINT8(AUDIO20_CS_REQ_RANGE, control_request.bRequest);
+ TEST_ASSERT_EQUAL_HEX16(tu_u16(PLAYBACK_CLOCK, AUDIO_AC_ITF), tu_le16toh(control_request.wIndex));
+ complete_uac2_clock_range(44100, 48000);
+
+ TEST_ASSERT_TRUE(tuh_audio_mounted(0));
+ TEST_ASSERT_EQUAL_UINT8(2, tuh_audio_config_count(0, 0));
+}
+
void test_audio_host_reconfigures_stopped_duplex_streams_to_a_new_common_rate(void) {
open_descriptors(uac2_duplex_shared_clock, sizeof(uac2_duplex_shared_clock));
TEST_ASSERT_TRUE(audioh_set_config(AUDIO_DEV_ADDR, AUDIO_AC_ITF));
@@ -1038,6 +1114,13 @@ void test_audio_host_maps_playback_fu_declared_before_usb_input_terminal(void) {
TEST_ASSERT_EQUAL_UINT8(PLAYBACK_FU, tuh_audio_get_feature_unit_id(0, 0));
}
+void test_audio_host_maps_relevant_terminal_and_fu_after_unrelated_entities(void) {
+ open_descriptors(playback_after_unrelated_ac_entities, sizeof(playback_after_unrelated_ac_entities));
+
+ TEST_ASSERT_EQUAL_UINT8(1, tuh_audio_stream_count(0));
+ TEST_ASSERT_EQUAL_UINT8(PLAYBACK_FU, tuh_audio_get_feature_unit_id(0, 0));
+}
+
void test_audio_host_maps_capture_fu_declared_before_usb_output_terminal(void) {
uint8_t captured[CFG_TUH_AUDIO_STREAM_BUFSIZE];
const uint16_t fifo_depth = CFG_TUH_AUDIO_STREAM_BUFSIZE - (CFG_TUH_AUDIO_STREAM_BUFSIZE % 3);