diff options
| author | HiFiPHile <[email protected]> | 2026-08-25 09:27:36 +0200 |
|---|---|---|
| committer | HiFiPHile <[email protected]> | 2026-08-25 09:27:36 +0200 |
| commit | b26958c47e70d659620ec391731b37c6d1c66ea7 (patch) | |
| tree | 9fc702254f1995a240af7c526e42948d2936c7ca /src | |
| parent | ebec43ef01258e1abd37a08f603c940690257951 (diff) | |
feat(audio): track a Feature Unit per stream direction
Associate one directly connected Feature Unit with each capture and playback stream. This lets applications control microphone and speaker paths independently.
Signed-off-by: HiFiPHile <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/class/audio/audio_host.c | 104 | ||||
| -rw-r--r-- | src/class/audio/audio_host.h | 32 |
2 files changed, 87 insertions, 49 deletions
diff --git a/src/class/audio/audio_host.c b/src/class/audio/audio_host.c index 4f4c1e0d0..951656aa7 100644 --- a/src/class/audio/audio_host.c +++ b/src/class/audio/audio_host.c @@ -138,6 +138,9 @@ typedef struct { uint8_t state; // STREAM_STATE_* bool running; // tuh_audio_start() called, transfers may be submitted + // One Feature Unit associated with this logical stream (0 = none) + uint8_t feature_unit_id; + // Size in bytes of one frame (all channels) of the active configuration uint8_t frame_bytes; @@ -171,9 +174,6 @@ typedef struct { tuh_audio_stream_t in_stream; uint8_t stream_count; // number of streams with supported configurations - // Feature Unit info - uint8_t feature_unit_id; // bUnitID of Feature Unit (0 = none) - bool mounted; } audioh_interface_t; @@ -277,17 +277,18 @@ static uint8_t audioh_fu_control_width(uint8_t control_selector) { // Reset a stream to its unconfigured state (keeps idx, dir, and FIFO configuration) static void audioh_stream_reset(tuh_audio_stream_t *s) { - s->daddr = 0; - s->stream_idx = TUSB_INDEX_INVALID_8; - s->config_count = 0; - s->active_config = TUSB_INDEX_INVALID_8; - s->state = STREAM_STATE_IDLE; - s->running = false; - s->frame_bytes = 0; - s->frames_per_ms = 0; - s->frames_rem = 0; - s->rem_acc = 0; - s->complete_cb = NULL; + s->daddr = 0; + s->stream_idx = TUSB_INDEX_INVALID_8; + s->config_count = 0; + s->active_config = TUSB_INDEX_INVALID_8; + s->state = STREAM_STATE_IDLE; + s->running = false; + s->feature_unit_id = 0; + s->frame_bytes = 0; + s->frames_per_ms = 0; + s->frames_rem = 0; + s->rem_acc = 0; + s->complete_cb = NULL; tu_edpt_stream_close(&s->edpt); tu_edpt_stream_clear(&s->edpt); } @@ -551,10 +552,9 @@ void audioh_close(uint8_t daddr) { _audioh_epbuf[idx].complete_cb = NULL; // drop a pending feature-unit request _audioh_epbuf[idx].fu_busy = false; - p_audio->feature_unit_id = 0; - p_audio->stream_count = 0; - p_audio->daddr = 0; - p_audio->mounted = false; + p_audio->stream_count = 0; + p_audio->daddr = 0; + p_audio->mounted = false; } } @@ -671,7 +671,7 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de switch (tu_desc_subtype(p_desc)) { case AUDIO10_CS_AS_INTERFACE_AS_GENERAL: { const audio10_desc_cs_as_interface_t *desc_as_general = (const audio10_desc_cs_as_interface_t *)p_desc; - if (desc_as_general->bLength >= 5) { + if (desc_as_general->bLength >= sizeof(audio10_desc_cs_as_interface_t)) { format_tag = tu_le16toh(desc_as_general->wFormatTag); } break; @@ -825,7 +825,6 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de continue; } } - // Skip duplicate configurations bool duplicate = false; for (uint8_t j = 0; j < stream->config_count; j++) { @@ -859,6 +858,7 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de stream->map[stream->config_count].sam_freq_ctrl = ep->sam_freq_ctrl; stream->config_count++; } + } return p_desc; @@ -879,7 +879,6 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface audioh_interface_t *p_audio = &_audioh_itf[idx]; p_audio->daddr = dev_addr; p_audio->ac_itf_num = desc_itf->bInterfaceNumber; - p_audio->feature_unit_id = 0; audioh_stream_reset(&p_audio->in_stream); audioh_stream_reset(&p_audio->out_stream); p_audio->in_stream.daddr = dev_addr; @@ -888,8 +887,10 @@ 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); // Parse the Audio Control interface descriptors and the interface collection - audioh_ac_header_t header = {0}; - bool have_header = false; + audioh_ac_header_t header = {0}; + uint8_t usb_input_terminal_id = 0; + uint8_t usb_output_source_id = 0; + bool have_header = false; p_desc = tu_desc_next(p_desc); while (tu_desc_in_bounds(p_desc, desc_end) && tu_desc_type(p_desc) != TUSB_DESC_INTERFACE) { @@ -913,9 +914,33 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface } break; } + case AUDIO10_CS_AC_INTERFACE_INPUT_TERMINAL: { + const audio10_desc_input_terminal_t *terminal = (const audio10_desc_input_terminal_t *)p_desc; + 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; + } + break; + } + case AUDIO10_CS_AC_INTERFACE_OUTPUT_TERMINAL: { + const audio10_desc_output_terminal_t *terminal = (const audio10_desc_output_terminal_t *)p_desc; + 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; + } + break; + } case AUDIO10_CS_AC_INTERFACE_FEATURE_UNIT: { - p_audio->feature_unit_id = p_desc[3]; // bUnitID - TU_LOG_DRV(" Feature Unit: ID=%u\r\n", p_audio->feature_unit_id); + if (p_desc[0] >= 5) { + if (usb_input_terminal_id != 0 && p_audio->out_stream.feature_unit_id == 0 && + p_desc[4] == usb_input_terminal_id) { + p_audio->out_stream.feature_unit_id = p_desc[3]; + } + if (usb_output_source_id != 0 && p_audio->in_stream.feature_unit_id == 0 && + p_desc[3] == usb_output_source_id) { + p_audio->in_stream.feature_unit_id = p_desc[3]; + } + } break; } default: @@ -1007,9 +1032,14 @@ uint8_t tuh_audio_get_dev_addr(uint8_t idx) { return _audioh_itf[idx].daddr; } -uint8_t tuh_audio_get_feature_unit_id(uint8_t idx) { +uint8_t tuh_audio_get_feature_unit_id(uint8_t idx, uint8_t stream_idx) { TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, 0); - return _audioh_itf[idx].feature_unit_id; + audioh_interface_t *p_audio = &_audioh_itf[idx]; + TU_VERIFY(p_audio->daddr != 0, 0); + + tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx); + TU_VERIFY(s, 0); + return s->feature_unit_id; } uint8_t tuh_audio_stream_count(uint8_t dev_idx) { @@ -1309,11 +1339,13 @@ static void audioh_fu_get_complete(tuh_xfer_t *xfer) { } } -bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t control_selector, uint8_t channel, uint16_t value, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) { +bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t stream_idx, uint8_t control_selector, uint8_t channel, + uint16_t value, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, false); audioh_interface_t *p_audio = &_audioh_itf[idx]; - TU_VERIFY(p_audio->mounted && p_audio->feature_unit_id != 0, false); + TU_VERIFY(p_audio->mounted, false); + tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx); + TU_VERIFY(s && s->feature_unit_id != 0, false); const uint8_t width = audioh_fu_control_width(control_selector); TU_VERIFY(width != 0, false); @@ -1327,7 +1359,7 @@ bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t control_selector, uint8_t c .direction = TUSB_DIR_OUT}, .bRequest = AUDIO10_CS_REQ_SET_CUR, .wValue = tu_htole16(tu_u16(control_selector, channel)), - .wIndex = tu_htole16(tu_u16(p_audio->feature_unit_id, p_audio->ac_itf_num)), + .wIndex = tu_htole16(tu_u16(s->feature_unit_id, p_audio->ac_itf_num)), .wLength = width}; uint8_t *val_buf = epbuf->fu_ctrl; @@ -1362,11 +1394,13 @@ bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t control_selector, uint8_t c return true; } -bool tuh_audio_feature_unit_get(uint8_t idx, uint8_t control_selector, uint8_t channel, uint16_t *value, - tuh_xfer_cb_t complete_cb, uintptr_t user_data) { +bool tuh_audio_feature_unit_get(uint8_t idx, uint8_t stream_idx, uint8_t control_selector, uint8_t channel, + uint16_t *value, tuh_xfer_cb_t complete_cb, uintptr_t user_data) { TU_VERIFY(idx < CFG_TUH_AUDIO_MAX, false); audioh_interface_t *p_audio = &_audioh_itf[idx]; - TU_VERIFY(p_audio->mounted && p_audio->feature_unit_id != 0 && value, false); + TU_VERIFY(p_audio->mounted && value, false); + tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx); + TU_VERIFY(s && s->feature_unit_id != 0, false); const uint8_t width = audioh_fu_control_width(control_selector); TU_VERIFY(width != 0, false); @@ -1380,7 +1414,7 @@ bool tuh_audio_feature_unit_get(uint8_t idx, uint8_t control_selector, uint8_t c .direction = TUSB_DIR_IN}, .bRequest = AUDIO10_CS_REQ_GET_CUR, .wValue = tu_htole16(tu_u16(control_selector, channel)), - .wIndex = tu_htole16(tu_u16(p_audio->feature_unit_id, p_audio->ac_itf_num)), + .wIndex = tu_htole16(tu_u16(s->feature_unit_id, p_audio->ac_itf_num)), .wLength = width}; if (complete_cb == NULL) { diff --git a/src/class/audio/audio_host.h b/src/class/audio/audio_host.h index 0133c24f2..367e58003 100644 --- a/src/class/audio/audio_host.h +++ b/src/class/audio/audio_host.h @@ -186,39 +186,43 @@ static inline uint32_t tuh_audio_config_frame_size(const tuh_audio_stream_config bool tuh_audio_mounted(uint8_t idx); // Get device address of Audio device uint8_t tuh_audio_get_dev_addr(uint8_t idx); -// Get Feature Unit ID -uint8_t tuh_audio_get_feature_unit_id(uint8_t idx); +// Get the Feature Unit ID associated with a stream (0 = none) +uint8_t tuh_audio_get_feature_unit_id(uint8_t idx, uint8_t stream_idx); //--------------------------------------------------------------------+ // Control Request API //--------------------------------------------------------------------+ -// Set a Feature Unit control (mute, volume, ...) of the Audio device (UAC 1.0) +// Set a Feature Unit control (mute, volume, ...) associated with an Audio stream (UAC 1.0) // Mute/bass/mid/treble/AGC/bass boost/loudness use one byte; volume/delay use two. // Graphic EQ and unknown selectors are unsupported. -bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t control_selector, uint8_t channel, uint16_t value, - tuh_xfer_cb_t complete_cb, uintptr_t user_data); +bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t stream_idx, uint8_t control_selector, uint8_t channel, + uint16_t value, tuh_xfer_cb_t complete_cb, uintptr_t user_data); -// Get a Feature Unit control (mute, volume, ...) of the Audio device (UAC 1.0) +// Get a Feature Unit control (mute, volume, ...) associated with an Audio stream (UAC 1.0) // The value is converted to host byte order before complete_cb is invoked. // Graphic EQ and unknown selectors are unsupported. // Only one Feature Unit GET or SET request may be in flight per device. -bool tuh_audio_feature_unit_get(uint8_t idx, uint8_t control_selector, uint8_t channel, uint16_t *value, - tuh_xfer_cb_t complete_cb, uintptr_t user_data); +bool tuh_audio_feature_unit_get(uint8_t idx, uint8_t stream_idx, uint8_t control_selector, uint8_t channel, + uint16_t *value, tuh_xfer_cb_t complete_cb, uintptr_t user_data); //--------------------------------------------------------------------+ // Control Request Sync API // Each Function will make a USB control transfer request to/from device the function will block until request is // complete. The function will return the transfer request result //--------------------------------------------------------------------+ -TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t -tuh_audio_feature_unit_set_sync(uint8_t idx, uint8_t control_selector, uint8_t channel, uint16_t value) { - TU_API_SYNC(tuh_audio_feature_unit_set, idx, control_selector, channel, value); +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_audio_feature_unit_set_sync(uint8_t idx, uint8_t stream_idx, + uint8_t control_selector, + uint8_t channel, + uint16_t value) { + TU_API_SYNC(tuh_audio_feature_unit_set, idx, stream_idx, control_selector, channel, value); } -TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t -tuh_audio_feature_unit_get_sync(uint8_t idx, uint8_t control_selector, uint8_t channel, uint16_t *value) { - TU_API_SYNC(tuh_audio_feature_unit_get, idx, control_selector, channel, value); +TU_ATTR_ALWAYS_INLINE static inline tusb_xfer_result_t tuh_audio_feature_unit_get_sync(uint8_t idx, uint8_t stream_idx, + uint8_t control_selector, + uint8_t channel, + uint16_t *value) { + TU_API_SYNC(tuh_audio_feature_unit_get, idx, stream_idx, control_selector, channel, value); } //--------------------------------------------------------------------+ |
