diff options
| -rw-r--r-- | src/class/audio/audio_host.c | 295 | ||||
| -rw-r--r-- | test/unit-test/test/host/audio/test_audio_host.c | 95 |
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); |
