diff options
| author | HiFiPHile <[email protected]> | 2026-08-25 09:29:07 +0200 |
|---|---|---|
| committer | HiFiPHile <[email protected]> | 2026-08-25 09:29:07 +0200 |
| commit | ad9ecddd7f51ede415992397b82399d7a2639c0f (patch) | |
| tree | 748be25df046f87485f61db940455f971a1e95d5 | |
| parent | 3f0a82a6bbe35135ed04749b4022ca6563073d5d (diff) | |
fix(audio): retain multiple pending Feature Units
Keep one unresolved Feature Unit candidate per stream direction while parsing AudioControl entities. Duplex functions are now associated correctly even when both Feature Units precede their USB terminals.
Signed-off-by: HiFiPHile <[email protected]>
| -rw-r--r-- | src/class/audio/audio_host.c | 38 | ||||
| -rw-r--r-- | test/unit-test/test/host/audio/test_audio_host.c | 30 |
2 files changed, 56 insertions, 12 deletions
diff --git a/src/class/audio/audio_host.c b/src/class/audio/audio_host.c index 2c18a22d8..2aad4040e 100644 --- a/src/class/audio/audio_host.c +++ b/src/class/audio/audio_host.c @@ -800,8 +800,11 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface uint8_t usb_output_source_id = 0; bool found_as_interface = false; // A Feature Unit may precede the USB terminal that identifies its stream. - uint8_t pending_fu_id = 0; - uint8_t pending_fu_source_id = 0; + typedef struct { + uint8_t id; + uint8_t source_id; + } audioh_fu_info_t; + audioh_fu_info_t pending_fu[TUH_AUDIO_STREAM_DIRECTION_COUNT] = {0}; p_desc = tu_desc_next(p_desc); while (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) != TUSB_DESC_INTERFACE) { @@ -812,10 +815,13 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface if (terminal->bLength >= sizeof(audio10_desc_input_terminal_t) && tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING && usb_input_terminal_id == 0) { usb_input_terminal_id = terminal->bTerminalID; - if (pending_fu_source_id == usb_input_terminal_id) { - p_audio->out_stream.feature_unit_id = pending_fu_id; - pending_fu_id = 0; - pending_fu_source_id = 0; + for (uint8_t i = 0; i < TU_ARRAY_SIZE(pending_fu); i++) { + if (pending_fu[i].source_id == usb_input_terminal_id) { + p_audio->out_stream.feature_unit_id = pending_fu[i].id; + pending_fu[i].id = 0; + pending_fu[i].source_id = 0; + break; + } } } break; @@ -825,10 +831,13 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface if (terminal->bLength >= sizeof(audio10_desc_output_terminal_t) && tu_le16toh(terminal->wTerminalType) == AUDIO_TERM_TYPE_USB_STREAMING && usb_output_source_id == 0) { usb_output_source_id = terminal->bSourceID; - if (pending_fu_id == usb_output_source_id) { - p_audio->in_stream.feature_unit_id = pending_fu_id; - pending_fu_id = 0; - pending_fu_source_id = 0; + for (uint8_t i = 0; i < TU_ARRAY_SIZE(pending_fu); i++) { + if (pending_fu[i].id == usb_output_source_id) { + p_audio->in_stream.feature_unit_id = pending_fu[i].id; + pending_fu[i].id = 0; + pending_fu[i].source_id = 0; + break; + } } } break; @@ -847,8 +856,13 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface mapped = true; } if (!mapped) { - pending_fu_id = p_desc[3]; - pending_fu_source_id = p_desc[4]; + for (uint8_t i = 0; i < TU_ARRAY_SIZE(pending_fu); i++) { + if (pending_fu[i].id == 0) { + pending_fu[i].id = p_desc[3]; + pending_fu[i].source_id = p_desc[4]; + break; + } + } } } break; 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 72026e682..b11d5a766 100644 --- a/test/unit-test/test/host/audio/test_audio_host.c +++ b/test/unit-test/test/host/audio/test_audio_host.c @@ -324,6 +324,28 @@ static const uint8_t capture_fu_before_usb_output[] = { TEST_UAC1_DATA_EP(0x81, TUSB_ISO_EP_ATT_ASYNCHRONOUS, 96, 1), }; +static const uint8_t duplex_fus_before_usb_terminals[] = { + TEST_UAC1_AC_HEADER_2, + TEST_UAC1_FEATURE_UNIT(PLAYBACK_FU, PLAYBACK_INPUT_TERM), + TEST_UAC1_FEATURE_UNIT(CAPTURE_FU, CAPTURE_INPUT_TERM), + TEST_UAC1_INPUT_TERM(PLAYBACK_INPUT_TERM, AUDIO_TERM_TYPE_USB_STREAMING, 2), + TEST_UAC1_OUTPUT_TERM(CAPTURE_OUTPUT_TERM, AUDIO_TERM_TYPE_USB_STREAMING, CAPTURE_FU), + TEST_UAC1_OUTPUT_TERM(PLAYBACK_OUTPUT_TERM, AUDIO_TERM_TYPE_OUT_HEADPHONES, PLAYBACK_FU), + TEST_UAC1_INPUT_TERM(CAPTURE_INPUT_TERM, AUDIO_TERM_TYPE_IN_GENERIC_MIC, 1), + 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, + TEST_UAC1_AS_INTERFACE_NUM(AUDIO_AS_IN_ITF, 0, 0), + TEST_UAC1_AS_INTERFACE_NUM(AUDIO_AS_IN_ITF, 1, 1), + TEST_UAC1_AS_GENERAL(CAPTURE_OUTPUT_TERM), + TEST_UAC1_FORMAT(1, 2, 16, 48000), + TEST_UAC1_DATA_EP(0x81, TUSB_ISO_EP_ATT_ASYNCHRONOUS, 96, 1), + TEST_UAC1_CS_DATA_EP, +}; + static const uint8_t playback_with_two_frequencies[] = { TEST_UAC1_AC_HEADER, TEST_UAC1_INPUT_TERM(PLAYBACK_INPUT_TERM, AUDIO_TERM_TYPE_USB_STREAMING, 2), @@ -554,6 +576,14 @@ void test_audio_host_maps_capture_fu_declared_before_usb_output_terminal(void) { TEST_ASSERT_EACH_EQUAL_UINT8(11, captured + fifo_depth - 96, 96); } +void test_audio_host_maps_duplex_fus_declared_before_usb_terminals(void) { + open_descriptors(duplex_fus_before_usb_terminals, sizeof(duplex_fus_before_usb_terminals)); + + TEST_ASSERT_EQUAL_UINT8(2, tuh_audio_stream_count(0)); + TEST_ASSERT_EQUAL_UINT8(PLAYBACK_FU, tuh_audio_get_feature_unit_id(0, 0)); + TEST_ASSERT_EQUAL_UINT8(CAPTURE_FU, tuh_audio_get_feature_unit_id(0, 1)); +} + void test_audio_host_sets_sampling_frequency_after_each_stream_activation(void) { mount_descriptors(capture_fu_before_usb_output, sizeof(capture_fu_before_usb_output)); |
