diff options
Diffstat (limited to 'src/class')
| -rw-r--r-- | src/class/audio/audio_host.c | 638 | ||||
| -rw-r--r-- | src/class/audio/audio_host.h | 2 |
2 files changed, 373 insertions, 267 deletions
diff --git a/src/class/audio/audio_host.c b/src/class/audio/audio_host.c index b34b7534b..8caecf3d2 100644 --- a/src/class/audio/audio_host.c +++ b/src/class/audio/audio_host.c @@ -29,10 +29,10 @@ * tuh_audio_err_cb(). * * The supported configurations of all Audio Streaming interfaces and alternate - * settings in one direction are combined into a flat list of discrete - * {format, sample_rate, channels} tuples. The driver keeps the mapping from - * each configuration to its interface, alternate setting, and endpoint, and - * applies it when the application calls tuh_audio_configure(). + * settings in one direction are presented as a flat list of discrete + * {format, sample_rate, channels} tuples. Internally, configurations are + * grouped by alternate setting so their format and endpoint properties are + * stored only once. The selected mapping is applied by tuh_audio_configure(). * * Non-PCM formats and sampling-frequency ranges are not registered as * supported configurations during enumeration. @@ -63,9 +63,6 @@ // MACRO CONSTANT TYPEDEF //--------------------------------------------------------------------+ -// Maximum number of supported configurations per stream (per direction) -#define AUDIOH_MAX_CONFIGS (CFG_TUH_AUDIO_MAX_AS * CFG_TUH_AUDIO_MAX_SAM_FREQ) - //--------------------------------------------------------------------+ // Weak stubs: invoked if no strong implementation is available //--------------------------------------------------------------------+ @@ -102,21 +99,45 @@ enum { STREAM_STATE_READY // configured, ready to start/stop }; -// Hardware mapping of one supported configuration +// One Audio Streaming alternate setting. Format, channels, and endpoint +// properties are shared by all of its discrete sampling frequencies. +typedef struct { + uint32_t sample_rate[CFG_TUH_AUDIO_MAX_SAM_FREQ]; + uint16_t ep_size; // endpoint max packet size + uint8_t itf_num; + uint8_t alt_setting; + uint8_t ep_addr; + uint8_t ep_interval; + uint8_t ep_attr; // bmAttributes synchronization and usage bits + uint8_t format; + uint8_t channels; + uint8_t sample_rate_count; + bool sam_freq_ctrl; +} audioh_as_config_t; + +// Explicit feedback exists only for playback alternate settings. +typedef struct { + uint8_t ep_addr; + uint8_t ep_size; // feedback endpoint max packet size (3 or 4) + uint8_t ep_interval; + uint8_t ep_attr; +} audioh_feedback_ep_t; + typedef struct { - uint8_t itf_num; // Audio Streaming interface number - uint8_t alt_setting; // alternate setting that provides this configuration - uint8_t ep_addr; // isochronous endpoint address - uint16_t ep_size; // endpoint max packet size - uint8_t ep_interval; // endpoint bInterval - uint8_t ep_sync; // bmAttributes sync type - uint8_t ep_usage; // bmAttributes usage type - bool sam_freq_ctrl; // endpoint supports sampling-frequency control - uint8_t fb_ep_addr; // explicit feedback endpoint address (0 = none) - uint8_t fb_ep_size; // feedback endpoint max packet size (3 or 4) - uint8_t fb_ep_interval; - uint8_t fb_ep_attr; -} audioh_stream_map_t; + audioh_feedback_ep_t feedback[CFG_TUH_AUDIO_MAX_AS]; + + // Playback pacing in Q16.16 frames per data-endpoint poll interval. + // Feedback is latched only when the current fractional scheduling cycle + // wraps, so one cycle is never generated from two different rates. + uint32_t nominal_frames_q16; + uint32_t target_frames_q16; + uint32_t pending_frames_q16; + uint16_t feedback_min_frames; + uint16_t feedback_max_frames; + uint16_t rem_acc; + bool feedback_pending; + bool feedback_opened; +} audioh_playback_t; // One logical stream (capture or playback) typedef struct { @@ -129,12 +150,14 @@ typedef struct { uint8_t daddr; // Supported configurations (parsed during enumeration) - uint8_t config_count; - tuh_audio_stream_config_t config[AUDIOH_MAX_CONFIGS]; - audioh_stream_map_t map[AUDIOH_MAX_CONFIGS]; + uint8_t as_count; + uint8_t config_count; + audioh_as_config_t as[CFG_TUH_AUDIO_MAX_AS]; // Active stream state - uint8_t active_config; // index into config[]/map[], TUSB_INDEX_INVALID_8 when not configured + uint8_t active_config; // flattened public configuration index + uint8_t active_as; + uint8_t active_rate; uint8_t state; // STREAM_STATE_* bool running; // tuh_audio_start() called, transfers may be submitted @@ -147,18 +170,6 @@ typedef struct { // Size in bytes of one frame (all channels) of the active configuration uint16_t frame_bytes; - // Playback pacing in Q16.16 frames per data-endpoint poll interval. - // Feedback is latched only when the current fractional scheduling cycle - // wraps, so one cycle is never generated from two different rates. - uint32_t nominal_frames_q16; - uint32_t target_frames_q16; - uint32_t pending_frames_q16; - uint32_t feedback_min_q16; - uint32_t feedback_max_q16; - uint16_t rem_acc; - bool feedback_pending; - bool feedback_opened; - // FIFO + endpoint transfer helper (see tu_edpt_stream, used by the MIDI // host driver): the FIFO decouples the application's frame-based read/write // from the endpoint's isochronous transfer cadence. ep_buf is bound at init from @@ -172,31 +183,37 @@ typedef struct { typedef struct { uint8_t daddr; // device address (0 = free slot) uint8_t ac_itf_num; // Audio Control interface number + uint8_t stream_count; + bool mounted; // Logical streams: playback first, then capture (stream index order) tuh_audio_stream_t out_stream; tuh_audio_stream_t in_stream; - uint8_t stream_count; // number of streams with supported configurations - - bool mounted; + audioh_playback_t playback; } audioh_interface_t; typedef struct { - TUH_EPBUF_DEF(sam_freq, 4); // shared sampling-frequency SET data + // Sampling-frequency SET completes before feedback starts, so both + // transfers can use one stable DMA buffer. + TUH_EPBUF_DEF(rate_feedback, 4); TUH_EPBUF_DEF(fu_ctrl, 8); // feature-unit SET data - TUH_EPBUF_DEF(feedback, 4); // explicit-feedback transfer buffer TUH_EPBUF_DEF(epin, CFG_TUH_AUDIO_EPIN_BUFSIZE); // capture transfer buffer TUH_EPBUF_DEF(epout, CFG_TUH_AUDIO_EPOUT_BUFSIZE); // playback transfer buffer // Feature-unit request state: only one operation in flight per device - tuh_xfer_cb_t complete_cb; - uintptr_t user_data; - void *value; - tuh_audio_volume_range_t volume_range_temp; - uint8_t width; - uint8_t value_type; - uint8_t volume_stream_idx; - uint8_t volume_range_step; - bool fu_busy; + tuh_xfer_cb_t complete_cb; + uintptr_t user_data; + void *value; + union { + struct { + uint8_t width; + uint8_t value_type; + } control; + struct { + uint8_t stream_idx; + uint8_t range_step; + } mount; + } fu; + bool fu_busy; } audioh_epbuf_t; static audioh_interface_t _audioh_itf[CFG_TUH_AUDIO_MAX]; @@ -247,6 +264,48 @@ static tuh_audio_stream_t *audioh_get_stream_by_idx(audioh_interface_t *p_audio, return NULL; } +TU_ATTR_ALWAYS_INLINE static inline audioh_playback_t *audioh_get_playback(const tuh_audio_stream_t *s) { + return &_audioh_itf[s->idx].playback; +} + +TU_ATTR_ALWAYS_INLINE static inline audioh_as_config_t *audioh_stream_active_as(tuh_audio_stream_t *s) { + return &s->as[s->active_as]; +} + +static bool audioh_stream_resolve_config(const tuh_audio_stream_t *s, uint8_t config_idx, uint8_t *as_idx, + uint8_t *rate_idx) { + TU_VERIFY(config_idx < s->config_count, false); + + for (uint8_t i = 0; i < s->as_count; i++) { + if (config_idx < s->as[i].sample_rate_count) { + *as_idx = i; + *rate_idx = config_idx; + return true; + } + config_idx -= s->as[i].sample_rate_count; + } + return false; +} + +static void audioh_stream_config_fill(const tuh_audio_stream_t *s, uint8_t as_idx, uint8_t rate_idx, + tuh_audio_stream_config_t *config) { + const audioh_as_config_t *as = &s->as[as_idx]; + config->dir = (s->dir == TUSB_DIR_IN) ? TUH_AUDIO_STREAM_CAPTURE : TUH_AUDIO_STREAM_PLAYBACK; + config->format = (tuh_audio_format_t)as->format; + config->sample_rate = as->sample_rate[rate_idx]; + config->channels = as->channels; +} + +static bool audioh_stream_config_get(const tuh_audio_stream_t *s, uint8_t config_idx, + tuh_audio_stream_config_t *config) { + uint8_t as_idx; + uint8_t rate_idx; + TU_VERIFY(audioh_stream_resolve_config(s, config_idx, &as_idx, &rate_idx), false); + + audioh_stream_config_fill(s, as_idx, rate_idx, config); + return true; +} + static void audioh_stream_set_feature_unit(tuh_audio_stream_t *s, uint8_t unit_id, uint8_t controls) { s->feature_unit_id = unit_id; s->mute_supported = (controls & AUDIO10_FU_CONTROL_BM_MUTE) != 0; @@ -307,29 +366,28 @@ 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->feature_unit_id = 0; - s->mute_supported = false; - s->volume_supported = false; - s->volume_range = (tuh_audio_volume_range_t){0}; - s->frame_bytes = 0; - s->nominal_frames_q16 = 0; - s->target_frames_q16 = 0; - s->pending_frames_q16 = 0; - s->feedback_min_q16 = 0; - s->feedback_max_q16 = 0; - s->rem_acc = 0; - s->feedback_pending = false; - s->feedback_opened = false; + s->daddr = 0; + s->stream_idx = TUSB_INDEX_INVALID_8; + s->as_count = 0; + s->config_count = 0; + s->active_config = TUSB_INDEX_INVALID_8; + s->active_as = TUSB_INDEX_INVALID_8; + s->active_rate = TUSB_INDEX_INVALID_8; + s->state = STREAM_STATE_IDLE; + s->running = false; + s->feature_unit_id = 0; + s->mute_supported = false; + s->volume_supported = false; + s->volume_range = (tuh_audio_volume_range_t){0}; + s->frame_bytes = 0; tu_edpt_stream_close(&s->edpt); tu_edpt_stream_clear(&s->edpt); } +static void audioh_playback_reset(audioh_playback_t *playback) { + tu_memclr(playback, sizeof(*playback)); +} + // Find the stream owning an endpoint (used to dispatch transfer completion) static tuh_audio_stream_t *audioh_find_stream(uint8_t dev_addr, uint8_t ep_addr) { for (uint8_t idx = 0; idx < CFG_TUH_AUDIO_MAX; idx++) { @@ -337,8 +395,12 @@ static tuh_audio_stream_t *audioh_find_stream(uint8_t dev_addr, uint8_t ep_addr) for (uint8_t s = 0; s < 2; s++) { tuh_audio_stream_t *stream = (s == 0) ? &p_audio->in_stream : &p_audio->out_stream; if (stream->daddr == dev_addr && stream->active_config != TUSB_INDEX_INVALID_8) { - const audioh_stream_map_t *map = &stream->map[stream->active_config]; - if (map->ep_addr == ep_addr || (map->fb_ep_addr != 0 && map->fb_ep_addr == ep_addr)) { + const audioh_as_config_t *as = audioh_stream_active_as(stream); + if (as->ep_addr == ep_addr) { + return stream; + } + const uint8_t feedback_ep = p_audio->playback.feedback[stream->active_as].ep_addr; + if (stream->dir == TUSB_DIR_OUT && feedback_ep != 0 && feedback_ep == ep_addr) { return stream; } } @@ -356,10 +418,10 @@ static void audioh_stream_error(tuh_audio_stream_t *s, uint16_t xferred_bytes); static void audioh_stream_feedback_xfer(tuh_audio_stream_t *s) { TU_VERIFY(s->state == STREAM_STATE_READY && s->running, ); - const audioh_stream_map_t *map = &s->map[s->active_config]; - TU_VERIFY(map->fb_ep_addr != 0, ); - TU_VERIFY(usbh_edpt_claim(s->daddr, map->fb_ep_addr), ); - if (!usbh_edpt_xfer(s->daddr, map->fb_ep_addr, _audioh_epbuf[s->idx].feedback, map->fb_ep_size)) { + const audioh_feedback_ep_t *feedback = &audioh_get_playback(s)->feedback[s->active_as]; + TU_VERIFY(feedback->ep_addr != 0, ); + TU_VERIFY(usbh_edpt_claim(s->daddr, feedback->ep_addr), ); + if (!usbh_edpt_xfer(s->daddr, feedback->ep_addr, _audioh_epbuf[s->idx].rate_feedback, feedback->ep_size)) { audioh_stream_error(s, 0); } } @@ -370,11 +432,11 @@ static void audioh_stream_feedback_xfer(tuh_audio_stream_t *s) { static void audioh_stream_capture_xfer(tuh_audio_stream_t *s) { TU_VERIFY(s->state == STREAM_STATE_READY && s->running, ); - const audioh_stream_map_t *map = &s->map[s->active_config]; - TU_VERIFY(usbh_edpt_claim(s->daddr, map->ep_addr), ); // one transfer in flight + const audioh_as_config_t *as = audioh_stream_active_as(s); + TU_VERIFY(usbh_edpt_claim(s->daddr, as->ep_addr), ); // one transfer in flight // ep_size is guaranteed <= CFG_TUH_AUDIO_EPIN_BUFSIZE by enumeration - if (!usbh_edpt_xfer(s->daddr, map->ep_addr, s->edpt.ep_buf, map->ep_size)) { + if (!usbh_edpt_xfer(s->daddr, as->ep_addr, s->edpt.ep_buf, as->ep_size)) { audioh_stream_error(s, 0); } } @@ -385,12 +447,13 @@ static void audioh_stream_capture_xfer(tuh_audio_stream_t *s) { static void audioh_stream_playback_xfer(tuh_audio_stream_t *s) { TU_VERIFY(s->state == STREAM_STATE_READY && s->running, ); - const audioh_stream_map_t *map = &s->map[s->active_config]; - TU_VERIFY(usbh_edpt_claim(s->daddr, map->ep_addr), ); // one transfer in flight + const audioh_as_config_t *as = audioh_stream_active_as(s); + audioh_playback_t *playback = audioh_get_playback(s); + TU_VERIFY(usbh_edpt_claim(s->daddr, as->ep_addr), ); // one transfer in flight - uint32_t frames = s->target_frames_q16 >> 16; - const uint32_t fraction = s->target_frames_q16 & 0xFFFFu; - uint32_t next_rem_acc = s->rem_acc + fraction; + uint32_t frames = playback->target_frames_q16 >> 16; + const uint32_t fraction = playback->target_frames_q16 & 0xFFFFu; + uint32_t next_rem_acc = playback->rem_acc + fraction; bool loop_done = (fraction == 0); if (next_rem_acc >= 65536u) { next_rem_acc -= 65536u; @@ -399,7 +462,7 @@ static void audioh_stream_playback_xfer(tuh_audio_stream_t *s) { } const uint64_t bytes_64 = (uint64_t)frames * s->frame_bytes; - TU_ASSERT(bytes_64 <= map->ep_size && bytes_64 <= CFG_TUH_AUDIO_EPOUT_BUFSIZE && + TU_ASSERT(bytes_64 <= as->ep_size && bytes_64 <= CFG_TUH_AUDIO_EPOUT_BUFSIZE && bytes_64 <= CFG_TUH_AUDIO_STREAM_BUFSIZE, ); const uint16_t bytes = (uint16_t)bytes_64; if (tu_fifo_count(&s->edpt.ff) < bytes) { @@ -410,15 +473,15 @@ static void audioh_stream_playback_xfer(tuh_audio_stream_t *s) { tu_fifo_read_n(&s->edpt.ff, s->edpt.ep_buf, bytes); } - if (!usbh_edpt_xfer(s->daddr, map->ep_addr, s->edpt.ep_buf, bytes)) { + if (!usbh_edpt_xfer(s->daddr, as->ep_addr, s->edpt.ep_buf, bytes)) { audioh_stream_error(s, 0); return; } - s->rem_acc = (uint16_t)next_rem_acc; - if (loop_done && s->feedback_pending) { - s->target_frames_q16 = s->pending_frames_q16; - s->feedback_pending = false; - s->rem_acc = 0; + playback->rem_acc = (uint16_t)next_rem_acc; + if (loop_done && playback->feedback_pending) { + playback->target_frames_q16 = playback->pending_frames_q16; + playback->feedback_pending = false; + playback->rem_acc = 0; } } @@ -427,13 +490,14 @@ static void audioh_stream_playback_xfer(tuh_audio_stream_t *s) { //--------------------------------------------------------------------+ static bool audioh_stream_close_ep(tuh_audio_stream_t *s) { - if (s->feedback_opened) { - const uint8_t fb_ep_addr = s->map[s->active_config].fb_ep_addr; + audioh_playback_t *playback = (s->dir == TUSB_DIR_OUT) ? audioh_get_playback(s) : NULL; + if (playback != NULL && playback->feedback_opened) { + const uint8_t fb_ep_addr = playback->feedback[s->active_as].ep_addr; if (!tuh_edpt_close(s->daddr, fb_ep_addr)) { TU_LOG_DRV(" AUDIO close feedback endpoint failed: addr=%u ep=%02x\r\n", s->daddr, fb_ep_addr); return false; } - s->feedback_opened = false; + playback->feedback_opened = false; } if (!tu_edpt_stream_is_opened(&s->edpt)) { @@ -454,33 +518,38 @@ static void audioh_stream_fail(tuh_audio_stream_t *s) { (void)audioh_stream_close_ep(s); s->state = STREAM_STATE_IDLE; s->active_config = TUSB_INDEX_INVALID_8; + s->active_as = TUSB_INDEX_INVALID_8; + s->active_rate = TUSB_INDEX_INVALID_8; s->running = false; } static void audioh_stream_error(tuh_audio_stream_t *s, uint16_t xferred_bytes) { - s->running = false; - s->target_frames_q16 = s->nominal_frames_q16; - s->feedback_pending = false; - s->rem_acc = 0; + s->running = false; + if (s->dir == TUSB_DIR_OUT) { + audioh_playback_t *playback = audioh_get_playback(s); + playback->target_frames_q16 = playback->nominal_frames_q16; + playback->feedback_pending = false; + playback->rem_acc = 0; + } tu_edpt_stream_clear(&s->edpt); tuh_audio_err_cb(s->idx, s->stream_idx, xferred_bytes); } // Set the endpoint sampling frequency (3 bytes little-endian) static bool audioh_stream_set_freq(tuh_audio_stream_t *s, tuh_xfer_cb_t complete_cb) { - const audioh_stream_map_t *map = &s->map[s->active_config]; - const tuh_audio_stream_config_t *cfg = &s->config[s->active_config]; - uint8_t *ctrl = _audioh_epbuf[s->idx].sam_freq; + const audioh_as_config_t *as = audioh_stream_active_as(s); + const uint32_t sample_rate = as->sample_rate[s->active_rate]; + uint8_t *ctrl = _audioh_epbuf[s->idx].rate_feedback; - ctrl[0] = (uint8_t)(cfg->sample_rate & 0xFF); - ctrl[1] = (uint8_t)((cfg->sample_rate >> 8) & 0xFF); - ctrl[2] = (uint8_t)((cfg->sample_rate >> 16) & 0xFF); + ctrl[0] = (uint8_t)(sample_rate & 0xFF); + ctrl[1] = (uint8_t)((sample_rate >> 8) & 0xFF); + ctrl[2] = (uint8_t)((sample_rate >> 16) & 0xFF); const tusb_control_request_t request = {.bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_ENDPOINT, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_OUT}, .bRequest = AUDIO10_CS_REQ_SET_CUR, .wValue = tu_htole16(tu_u16(AUDIO10_EP_CTRL_SAMPLING_FREQ, 0)), // control selector, channel 0 - .wIndex = tu_htole16(map->ep_addr), + .wIndex = tu_htole16(as->ep_addr), .wLength = 3}; tuh_xfer_t xfer = {.daddr = s->daddr, @@ -494,19 +563,19 @@ static bool audioh_stream_set_freq(tuh_audio_stream_t *s, tuh_xfer_cb_t complete // Reconstruct the endpoint descriptor of the selected configuration and open it static bool audioh_stream_open_ep(tuh_audio_stream_t *s) { - const audioh_stream_map_t *map = &s->map[s->active_config]; + const audioh_as_config_t *as = audioh_stream_active_as(s); const tusb_desc_endpoint_t desc_ep = {.bLength = sizeof(tusb_desc_endpoint_t), .bDescriptorType = TUSB_DESC_ENDPOINT, - .bEndpointAddress = map->ep_addr, + .bEndpointAddress = as->ep_addr, .bmAttributes = {.xfer = TUSB_XFER_ISOCHRONOUS, - .sync = map->ep_sync, - .usage = map->ep_usage}, - .wMaxPacketSize = tu_htole16(map->ep_size), - .bInterval = map->ep_interval}; + .sync = (as->ep_attr >> 2) & 0x03u, + .usage = (as->ep_attr >> 4) & 0x03u}, + .wMaxPacketSize = tu_htole16(as->ep_size), + .bInterval = as->ep_interval}; if (!tuh_edpt_open(s->daddr, &desc_ep)) { - TU_LOG_DRV(" AUDIO open endpoint failed: addr=%u ep=%02x\r\n", s->daddr, map->ep_addr); + TU_LOG_DRV(" AUDIO open endpoint failed: addr=%u ep=%02x\r\n", s->daddr, as->ep_addr); audioh_stream_fail(s); return false; } @@ -516,21 +585,25 @@ static bool audioh_stream_open_ep(tuh_audio_stream_t *s) { tu_edpt_stream_open(&s->edpt, s->daddr, &desc_ep, xfer_len); tu_edpt_stream_clear(&s->edpt); - if (map->fb_ep_addr != 0) { - const tusb_desc_endpoint_t desc_fb = {.bLength = sizeof(tusb_desc_endpoint_t), - .bDescriptorType = TUSB_DESC_ENDPOINT, - .bEndpointAddress = map->fb_ep_addr, - .bmAttributes = {.xfer = TUSB_XFER_ISOCHRONOUS, - .sync = (map->fb_ep_attr >> 2) & 0x03u, - .usage = (map->fb_ep_attr >> 4) & 0x03u}, - .wMaxPacketSize = tu_htole16(map->fb_ep_size), - .bInterval = map->fb_ep_interval}; - if (!tuh_edpt_open(s->daddr, &desc_fb)) { - TU_LOG_DRV(" AUDIO open feedback endpoint failed: addr=%u ep=%02x\r\n", s->daddr, map->fb_ep_addr); - audioh_stream_fail(s); - return false; + if (s->dir == TUSB_DIR_OUT) { + audioh_playback_t *playback = audioh_get_playback(s); + const audioh_feedback_ep_t *feedback = &playback->feedback[s->active_as]; + if (feedback->ep_addr != 0) { + const tusb_desc_endpoint_t desc_fb = {.bLength = sizeof(tusb_desc_endpoint_t), + .bDescriptorType = TUSB_DESC_ENDPOINT, + .bEndpointAddress = feedback->ep_addr, + .bmAttributes = {.xfer = TUSB_XFER_ISOCHRONOUS, + .sync = (feedback->ep_attr >> 2) & 0x03u, + .usage = (feedback->ep_attr >> 4) & 0x03u}, + .wMaxPacketSize = tu_htole16(feedback->ep_size), + .bInterval = feedback->ep_interval}; + if (!tuh_edpt_open(s->daddr, &desc_fb)) { + TU_LOG_DRV(" AUDIO open feedback endpoint failed: addr=%u ep=%02x\r\n", s->daddr, feedback->ep_addr); + audioh_stream_fail(s); + return false; + } + playback->feedback_opened = true; } - s->feedback_opened = true; } s->state = STREAM_STATE_READY; @@ -560,6 +633,7 @@ bool audioh_init(void) { audioh_stream_reset(in); audioh_stream_reset(out); + audioh_playback_reset(&_audioh_itf[idx].playback); } return true; } @@ -588,6 +662,7 @@ void audioh_close(uint8_t daddr) { tuh_audio_stream_t *stream = (s == 0) ? &p_audio->in_stream : &p_audio->out_stream; audioh_stream_reset(stream); } + audioh_playback_reset(&p_audio->playback); _audioh_epbuf[idx].complete_cb = NULL; // drop a pending feature-unit request _audioh_epbuf[idx].fu_busy = false; @@ -599,8 +674,9 @@ void audioh_close(uint8_t daddr) { } static void audioh_feedback_received(tuh_audio_stream_t *s, uint32_t xferred_bytes) { - const uint8_t *fb = _audioh_epbuf[s->idx].feedback; - uint32_t feedback_q16; + const uint8_t *fb = _audioh_epbuf[s->idx].rate_feedback; + audioh_playback_t *playback = audioh_get_playback(s); + uint32_t feedback_q16; if (xferred_bytes == 3) { // Full-speed feedback is normally Q10.14. Keep the scheduler in Q16.16. feedback_q16 = ((uint32_t)fb[0] | ((uint32_t)fb[1] << 8) | ((uint32_t)fb[2] << 16)) << 2; @@ -611,22 +687,24 @@ static void audioh_feedback_received(tuh_audio_stream_t *s, uint32_t xferred_byt return; } - const audioh_stream_map_t *map = &s->map[s->active_config]; - if (feedback_q16 < s->feedback_min_q16 || feedback_q16 > s->feedback_max_q16) { + const uint32_t feedback_min_q16 = (uint32_t)playback->feedback_min_frames << 16; + const uint32_t feedback_max_q16 = (uint32_t)playback->feedback_max_frames << 16; + if (feedback_q16 < feedback_min_q16 || feedback_q16 > feedback_max_q16) { TU_LOG_DRV(" AUDIO feedback out of range: 0x%08lx\r\n", (unsigned long)feedback_q16); return; } // Feedback is expressed per USB frame/microframe. Scale it to the data // endpoint's polling interval before handing it to the packet scheduler. - const uint64_t target_q16_64 = (uint64_t)feedback_q16 << (map->ep_interval - 1u); + const audioh_as_config_t *as = audioh_stream_active_as(s); + const uint64_t target_q16_64 = (uint64_t)feedback_q16 << (as->ep_interval - 1u); if (target_q16_64 > UINT32_MAX) { return; } const uint32_t target_q16 = (uint32_t)target_q16_64; const uint64_t max_bytes = (((uint64_t)target_q16 + 0xFFFFu) >> 16) * s->frame_bytes; - if (max_bytes == 0 || max_bytes > map->ep_size || max_bytes > CFG_TUH_AUDIO_EPOUT_BUFSIZE || + if (max_bytes == 0 || max_bytes > as->ep_size || max_bytes > CFG_TUH_AUDIO_EPOUT_BUFSIZE || max_bytes > CFG_TUH_AUDIO_STREAM_BUFSIZE) { TU_LOG_DRV(" AUDIO feedback exceeds playback packet capacity: 0x%08lx\r\n", (unsigned long)feedback_q16); return; @@ -634,8 +712,8 @@ static void audioh_feedback_received(tuh_audio_stream_t *s, uint32_t xferred_byt // Keep only the newest feedback sample. The packet scheduler promotes it at // the end of its current fractional cycle. - s->pending_frames_q16 = target_q16; - s->feedback_pending = true; + playback->pending_frames_q16 = target_q16; + playback->feedback_pending = true; } bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { @@ -656,8 +734,8 @@ bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uin return true; } - const audioh_stream_map_t *map = &s->map[s->active_config]; - if (ep_addr == map->fb_ep_addr) { + const uint8_t feedback_ep = audioh_get_playback(s)->feedback[s->active_as].ep_addr; + if (s->dir == TUSB_DIR_OUT && feedback_ep != 0 && ep_addr == feedback_ep) { audioh_feedback_received(s, xferred_bytes); audioh_stream_feedback_xfer(s); return true; @@ -709,12 +787,12 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de } // Parse the class-specific and endpoint descriptors of this alternate setting - uint16_t format_tag = 0; - uint8_t num_channels = 0; - uint8_t subframe_size = 0; - uint8_t bit_res = 0; - uint8_t sam_freq_count = 0; - uint32_t sam_freq[CFG_TUH_AUDIO_MAX_SAM_FREQ] = {0}; + uint16_t format_tag = 0; + uint8_t num_channels = 0; + uint8_t subframe_size = 0; + uint8_t bit_res = 0; + uint8_t sam_freq_count = 0; + const uint8_t *sam_freq_data = NULL; // An AS alternate setting has one audio data endpoint and may have one // explicit feedback endpoint. Implicit-feedback endpoints are data endpoints @@ -723,8 +801,7 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de uint8_t ep_addr; uint16_t ep_size; uint8_t ep_interval; - uint8_t ep_sync; - uint8_t ep_usage; + uint8_t ep_attr; bool sam_freq_ctrl; } audioh_ep_info_t; audioh_ep_info_t ep_info = {0}; @@ -745,7 +822,7 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de case AUDIO10_CS_AS_INTERFACE_AS_GENERAL: { TU_VERIFY(TUH_VALIDATE_BASIC(tu_desc_len(p_desc) >= sizeof(audio10_desc_cs_as_interface_t)), NULL); const audio10_desc_cs_as_interface_t *desc_as_general = (const audio10_desc_cs_as_interface_t *)p_desc; - format_tag = tu_le16toh(desc_as_general->wFormatTag); + format_tag = tu_le16toh(desc_as_general->wFormatTag); break; } case AUDIO10_CS_AS_INTERFACE_FORMAT_TYPE: { @@ -753,16 +830,15 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de if (p_desc[3] != AUDIO10_FORMAT_TYPE_I) { break; // only Type I (PCM) is supported } - num_channels = p_desc[4]; - subframe_size = p_desc[5]; - bit_res = p_desc[6]; + num_channels = p_desc[4]; + subframe_size = p_desc[5]; + bit_res = p_desc[6]; + sam_freq_count = 0; + sam_freq_data = NULL; if (p_desc[7] > 0) { TU_VERIFY(TUH_VALIDATE_BASIC(p_desc[7] <= (tu_desc_len(p_desc) - 8u) / 3u), NULL); sam_freq_count = TU_MIN(p_desc[7], CFG_TUH_AUDIO_MAX_SAM_FREQ); - for (uint8_t i = 0; i < sam_freq_count; i++) { - sam_freq[i] = ((uint32_t)p_desc[8 + i * 3] | ((uint32_t)p_desc[9 + i * 3] << 8) | - ((uint32_t)p_desc[10 + i * 3] << 16)); - } + sam_freq_data = &p_desc[8]; } break; } @@ -809,9 +885,9 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de if (fb_info.ep_interval == 0 || fb_info.ep_interval > 16) { fb_info.ep_interval = 1; } - fb_info.ep_sync = desc_endpoint->bmAttributes.sync; - fb_info.ep_usage = desc_endpoint->bmAttributes.usage; - has_feedback_ep = true; + fb_info.ep_attr = + (uint8_t)((desc_endpoint->bmAttributes.sync << 2) | (desc_endpoint->bmAttributes.usage << 4)); + has_feedback_ep = true; break; } @@ -829,9 +905,9 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de if (ep_info.ep_interval == 0 || ep_info.ep_interval > 16) { ep_info.ep_interval = 1; } - ep_info.ep_sync = desc_endpoint->bmAttributes.sync; - ep_info.ep_usage = desc_endpoint->bmAttributes.usage; - has_data_ep = true; + ep_info.ep_attr = + (uint8_t)((desc_endpoint->bmAttributes.sync << 2) | (desc_endpoint->bmAttributes.usage << 4)); + has_data_ep = true; } break; } @@ -869,7 +945,8 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de } const uint16_t frame_bytes = (uint16_t)frame_bytes_32; - // Register one configuration per discrete sampling frequency + // Register one AS alternate setting containing its discrete sampling + // frequencies. The public API flattens these entries when requested. 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); @@ -888,14 +965,27 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de return p_desc; } + audioh_as_config_t as_config = {.ep_size = ep->ep_size, + .itf_num = itf_num, + .alt_setting = alt, + .ep_addr = ep->ep_addr, + .ep_interval = ep->ep_interval, + .ep_attr = ep->ep_attr, + .format = (uint8_t)format, + .channels = num_channels, + .sam_freq_ctrl = ep->sam_freq_ctrl}; + for (uint8_t i = 0; i < sam_freq_count; i++) { - if (sam_freq[i] == 0) { + const uint8_t *rate_data = &sam_freq_data[i * 3u]; + const uint32_t sample_rate = + (uint32_t)rate_data[0] | ((uint32_t)rate_data[1] << 8) | ((uint32_t)rate_data[2] << 16); + if (sample_rate == 0) { continue; } // The largest whole-frame packet for one poll interval must fit the // endpoint. Playback must also stage it in the transfer buffer and FIFO. - const uint64_t frames_numerator = (uint64_t)sam_freq[i] * audioh_interval_us(ep->ep_interval, p_audio->daddr); + const uint64_t frames_numerator = (uint64_t)sample_rate * audioh_interval_us(ep->ep_interval, p_audio->daddr); const uint64_t max_frames = (frames_numerator + 999999u) / 1000000u; const uint64_t packet_bytes = max_frames * frame_bytes; if (packet_bytes == 0 || packet_bytes > ep->ep_size || @@ -904,49 +994,51 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const tusb_de itf_num, alt, ep->ep_size); continue; } - // Skip duplicate configurations + // Skip duplicate configurations already retained from another AS or from + // an earlier frequency entry in this descriptor. bool duplicate = false; - for (uint8_t j = 0; j < stream->config_count; j++) { - if (stream->config[j].format == format && stream->config[j].sample_rate == sam_freq[i] && - stream->config[j].channels == num_channels) { + for (uint8_t as_idx = 0; as_idx < stream->as_count && !duplicate; as_idx++) { + const audioh_as_config_t *existing = &stream->as[as_idx]; + if (existing->format == (uint8_t)format && existing->channels == num_channels) { + for (uint8_t rate_idx = 0; rate_idx < existing->sample_rate_count; rate_idx++) { + if (existing->sample_rate[rate_idx] == sample_rate) { + duplicate = true; + break; + } + } + } + } + for (uint8_t rate_idx = 0; rate_idx < as_config.sample_rate_count && !duplicate; rate_idx++) { + if (as_config.sample_rate[rate_idx] == sample_rate) { duplicate = true; - break; } } if (duplicate) { continue; } - if (stream->config_count >= AUDIOH_MAX_CONFIGS) { - TU_LOG_DRV(" AUDIO AS itf %u alt %u: reach max configurations %u\r\n", itf_num, alt, AUDIOH_MAX_CONFIGS); - return p_desc; - } + as_config.sample_rate[as_config.sample_rate_count++] = sample_rate; + } - stream->config[stream->config_count].dir = - (stream->dir == TUSB_DIR_IN) ? TUH_AUDIO_STREAM_CAPTURE : TUH_AUDIO_STREAM_PLAYBACK; - stream->config[stream->config_count].format = format; - stream->config[stream->config_count].sample_rate = sam_freq[i]; - stream->config[stream->config_count].channels = num_channels; - stream->map[stream->config_count].itf_num = itf_num; - stream->map[stream->config_count].alt_setting = alt; - stream->map[stream->config_count].ep_addr = ep->ep_addr; - stream->map[stream->config_count].ep_size = ep->ep_size; - stream->map[stream->config_count].ep_interval = ep->ep_interval; - stream->map[stream->config_count].ep_sync = ep->ep_sync; - stream->map[stream->config_count].ep_usage = ep->ep_usage; - stream->map[stream->config_count].sam_freq_ctrl = ep->sam_freq_ctrl; - stream->map[stream->config_count].fb_ep_addr = 0; - stream->map[stream->config_count].fb_ep_size = 0; - stream->map[stream->config_count].fb_ep_interval = 0; - stream->map[stream->config_count].fb_ep_attr = 0; - if (stream->dir == TUSB_DIR_OUT && has_feedback_ep) { - stream->map[stream->config_count].fb_ep_addr = fb_info.ep_addr; - stream->map[stream->config_count].fb_ep_size = (uint8_t)fb_info.ep_size; - stream->map[stream->config_count].fb_ep_interval = fb_info.ep_interval; - stream->map[stream->config_count].fb_ep_attr = (uint8_t)((fb_info.ep_sync << 2) | (fb_info.ep_usage << 4)); - } - stream->config_count++; + if (as_config.sample_rate_count == 0) { + return p_desc; } + if (stream->as_count >= CFG_TUH_AUDIO_MAX_AS) { + TU_LOG_DRV(" AUDIO AS itf %u alt %u: reach max alternate settings %u\r\n", itf_num, alt, CFG_TUH_AUDIO_MAX_AS); + return p_desc; + } + + const uint8_t as_idx = stream->as_count; + stream->as[as_idx] = as_config; + if (stream->dir == TUSB_DIR_OUT && has_feedback_ep) { + audioh_feedback_ep_t *feedback = &p_audio->playback.feedback[as_idx]; + feedback->ep_addr = fb_info.ep_addr; + feedback->ep_size = (uint8_t)fb_info.ep_size; + feedback->ep_interval = fb_info.ep_interval; + feedback->ep_attr = fb_info.ep_attr; + } + stream->as_count++; + stream->config_count += as_config.sample_rate_count; return p_desc; } @@ -972,6 +1064,7 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface p_audio->ac_itf_num = desc_itf->bInterfaceNumber; audioh_stream_reset(&p_audio->in_stream); audioh_stream_reset(&p_audio->out_stream); + audioh_playback_reset(&p_audio->playback); p_audio->in_stream.daddr = dev_addr; p_audio->out_stream.daddr = dev_addr; @@ -1128,6 +1221,7 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface open_failed: audioh_stream_reset(&p_audio->in_stream); audioh_stream_reset(&p_audio->out_stream); + audioh_playback_reset(&p_audio->playback); p_audio->daddr = 0; p_audio->ac_itf_num = 0; p_audio->stream_count = 0; @@ -1156,8 +1250,8 @@ bool audioh_set_config(uint8_t dev_addr, uint8_t itf_num) { return true; } - audioh_epbuf_t *epbuf = &_audioh_epbuf[idx]; - epbuf->volume_stream_idx = 0; + audioh_epbuf_t *epbuf = &_audioh_epbuf[idx]; + epbuf->fu.mount.stream_idx = 0; audioh_mount_feature_unit_next(idx); return true; } @@ -1254,8 +1348,7 @@ bool tuh_audio_config_get(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_id TU_VERIFY(s && config, false); TU_VERIFY(config_idx < s->config_count, false); - *config = s->config[config_idx]; - return true; + return audioh_stream_config_get(s, config_idx, config); } bool tuh_audio_configure(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx) { @@ -1266,23 +1359,28 @@ bool tuh_audio_configure(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx); TU_VERIFY(s, false); TU_VERIFY(config_idx < s->config_count, false); - const tuh_audio_stream_config_t *cfg = &s->config[config_idx]; + tuh_audio_stream_config_t cfg; + uint8_t as_idx; + uint8_t rate_idx; + TU_VERIFY(audioh_stream_resolve_config(s, config_idx, &as_idx, &rate_idx), false); + audioh_stream_config_fill(s, as_idx, rate_idx, &cfg); // Reconfiguration is allowed from a stopped stream. TU_VERIFY(!s->running, false); if (s->state == STREAM_STATE_READY) { // Wait for any in-flight transfer to complete and be discarded TU_VERIFY(!usbh_edpt_busy(s->daddr, s->edpt.ep_addr), false); - if (s->feedback_opened) { - TU_VERIFY(!usbh_edpt_busy(s->daddr, s->map[s->active_config].fb_ep_addr), false); + if (s->dir == TUSB_DIR_OUT && p_audio->playback.feedback_opened) { + TU_VERIFY(!usbh_edpt_busy(s->daddr, p_audio->playback.feedback[s->active_as].ep_addr), false); } } tuh_audio_stream_t *other = (s == &p_audio->out_stream) ? &p_audio->in_stream : &p_audio->out_stream; if (other->active_config != TUSB_INDEX_INVALID_8) { - const tuh_audio_stream_config_t *other_cfg = &other->config[other->active_config]; - if (cfg->sample_rate != other_cfg->sample_rate) { + tuh_audio_stream_config_t other_cfg; + audioh_stream_config_fill(other, other->active_as, other->active_rate, &other_cfg); + if (cfg.sample_rate != other_cfg.sample_rate) { TU_LOG_DRV(" AUDIO configure failed: capture/playback sample rates must match (%lu != %lu)\r\n", - (unsigned long)cfg->sample_rate, (unsigned long)other_cfg->sample_rate); + (unsigned long)cfg.sample_rate, (unsigned long)other_cfg.sample_rate); return false; } } @@ -1291,18 +1389,23 @@ bool tuh_audio_configure(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx // the same address, since its packet size and interval may have changed. TU_VERIFY(audioh_stream_close_ep(s), false); - const audioh_stream_map_t *map = &s->map[config_idx]; - const uint32_t frame_div = (tuh_speed_get(s->daddr) == TUSB_SPEED_HIGH) ? 8000u : 1000u; - s->active_config = config_idx; - s->frame_bytes = (uint16_t)tuh_audio_config_frame_size(cfg); - s->nominal_frames_q16 = audioh_nominal_frames_q16(cfg->sample_rate, map->ep_interval, s->daddr); - s->target_frames_q16 = s->nominal_frames_q16; - s->pending_frames_q16 = 0; - s->feedback_min_q16 = ((cfg->sample_rate - 1u) / frame_div) << 16; - s->feedback_max_q16 = (cfg->sample_rate / frame_div + 1u) << 16; - s->feedback_pending = false; - s->rem_acc = 0; - s->state = STREAM_STATE_IDLE; + const audioh_as_config_t *as = &s->as[as_idx]; + s->active_config = config_idx; + s->active_as = as_idx; + s->active_rate = rate_idx; + s->frame_bytes = (uint16_t)tuh_audio_config_frame_size(&cfg); + s->state = STREAM_STATE_IDLE; + if (s->dir == TUSB_DIR_OUT) { + const uint32_t frame_div = (tuh_speed_get(s->daddr) == TUSB_SPEED_HIGH) ? 8000u : 1000u; + audioh_playback_t *playback = &p_audio->playback; + playback->nominal_frames_q16 = audioh_nominal_frames_q16(cfg.sample_rate, as->ep_interval, s->daddr); + playback->target_frames_q16 = playback->nominal_frames_q16; + playback->pending_frames_q16 = 0; + playback->feedback_min_frames = (uint16_t)((cfg.sample_rate - 1u) / frame_div); + playback->feedback_max_frames = (uint16_t)(cfg.sample_rate / frame_div + 1u); + playback->feedback_pending = false; + playback->rem_acc = 0; + } if (s->dir == TUSB_DIR_IN) { // A byte FIFO can overwrite only complete audio frames when its depth is // an exact multiple of the configured frame size. @@ -1314,8 +1417,8 @@ bool tuh_audio_configure(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx } TU_LOG_DRV(" AUDIO configure %s stream %u: itf %u alt %u ep %02x\r\n", - (s->dir == TUSB_DIR_IN) ? "capture" : "playback", s->stream_idx, map->itf_num, map->alt_setting, - map->ep_addr); + (s->dir == TUSB_DIR_IN) ? "capture" : "playback", s->stream_idx, as->itf_num, as->alt_setting, + as->ep_addr); return audioh_stream_open_ep(s); } @@ -1325,9 +1428,9 @@ bool tuh_audio_configure(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx // transfers static void audioh_stream_start_xfer(tuh_audio_stream_t *s) { if (s->dir == TUSB_DIR_IN) { - audioh_stream_capture_xfer(s); // feed the capture endpoint + audioh_stream_capture_xfer(s); // feed the capture endpoint } else { - if (s->map[s->active_config].fb_ep_addr != 0) { + if (audioh_get_playback(s)->feedback[s->active_as].ep_addr != 0) { audioh_stream_feedback_xfer(s); } if (!s->running) { @@ -1351,8 +1454,8 @@ static void audioh_stream_start_set_freq_complete(tuh_xfer_t *xfer) { } static bool audioh_stream_start_active(tuh_audio_stream_t *s) { - const audioh_stream_map_t *map = &s->map[s->active_config]; - if (map->sam_freq_ctrl) { + const audioh_as_config_t *as = audioh_stream_active_as(s); + if (as->sam_freq_ctrl) { if (!audioh_stream_set_freq(s, audioh_stream_start_set_freq_complete)) { audioh_stream_error(s, 0); return false; @@ -1385,19 +1488,21 @@ bool tuh_audio_start(uint8_t dev_idx, uint8_t stream_idx) { TU_VERIFY(s, false); TU_VERIFY(s->state == STREAM_STATE_READY && !s->running, false); // Wait for any in-flight transfer to complete and be discarded - TU_VERIFY(!usbh_edpt_busy(s->daddr, s->map[s->active_config].ep_addr), false); - if (s->feedback_opened) { - TU_VERIFY(!usbh_edpt_busy(s->daddr, s->map[s->active_config].fb_ep_addr), false); + const audioh_as_config_t *as = audioh_stream_active_as(s); + TU_VERIFY(!usbh_edpt_busy(s->daddr, as->ep_addr), false); + if (s->dir == TUSB_DIR_OUT && p_audio->playback.feedback_opened) { + TU_VERIFY(!usbh_edpt_busy(s->daddr, p_audio->playback.feedback[s->active_as].ep_addr), false); } - s->target_frames_q16 = s->nominal_frames_q16; - s->feedback_pending = false; - s->rem_acc = 0; - s->running = true; + if (s->dir == TUSB_DIR_OUT) { + p_audio->playback.target_frames_q16 = p_audio->playback.nominal_frames_q16; + p_audio->playback.feedback_pending = false; + p_audio->playback.rem_acc = 0; + } + s->running = true; // Activate the interface's alternate setting asynchronously: transfers // begin once SET_INTERFACE and sampling-frequency control complete. - const audioh_stream_map_t *map = &s->map[s->active_config]; - if (!tuh_interface_set(s->daddr, map->itf_num, map->alt_setting, audioh_stream_start_complete, (uintptr_t)s)) { + if (!tuh_interface_set(s->daddr, as->itf_num, as->alt_setting, audioh_stream_start_complete, (uintptr_t)s)) { s->running = false; return false; } @@ -1422,19 +1527,21 @@ bool tuh_audio_stop(uint8_t dev_idx, uint8_t stream_idx) { tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx); TU_VERIFY(s && s->state == STREAM_STATE_READY, false); - const audioh_stream_map_t *map = &s->map[s->active_config]; + const audioh_as_config_t *as = audioh_stream_active_as(s); // Leave the stream running if SET_INTERFACE cannot be submitted, so the // caller can retry without the host and device states diverging. - TU_VERIFY(tuh_interface_set(s->daddr, map->itf_num, 0, audioh_stream_stop_complete, (uintptr_t)s), false); + TU_VERIFY(tuh_interface_set(s->daddr, as->itf_num, 0, audioh_stream_stop_complete, (uintptr_t)s), false); // The in-flight transfer (if any) completes and its data is discarded; // queued frames are dropped as well. The interface is being deactivated so // the device stops transferring. - s->running = false; - s->target_frames_q16 = s->nominal_frames_q16; - s->feedback_pending = false; + s->running = false; + if (s->dir == TUSB_DIR_OUT) { + p_audio->playback.target_frames_q16 = p_audio->playback.nominal_frames_q16; + p_audio->playback.feedback_pending = false; + p_audio->playback.rem_acc = 0; + } tu_edpt_stream_clear(&s->edpt); - s->rem_acc = 0; // restart the pacing accumulator on the next tuh_audio_start() return true; } @@ -1526,13 +1633,13 @@ enum { }; static void audioh_fu_value_store(audioh_epbuf_t *epbuf) { - if (epbuf->value_type == AUDIOH_FU_VALUE_BOOL) { + if (epbuf->fu.control.value_type == AUDIOH_FU_VALUE_BOOL) { *((bool *)epbuf->value) = epbuf->fu_ctrl[0] != 0; - } else if (epbuf->width == 1) { + } else if (epbuf->fu.control.width == 1) { *((uint16_t *)epbuf->value) = epbuf->fu_ctrl[0]; } else { const uint16_t value = tu_le16toh(tu_unaligned_read16(epbuf->fu_ctrl)); - if (epbuf->value_type == AUDIOH_FU_VALUE_I16) { + if (epbuf->fu.control.value_type == AUDIOH_FU_VALUE_I16) { *((int16_t *)epbuf->value) = (int16_t)value; } else { *((uint16_t *)epbuf->value) = value; @@ -1550,7 +1657,7 @@ static void audioh_fu_get_complete(tuh_xfer_t *xfer) { epbuf->fu_busy = false; if (epbuf->value != NULL && xfer->result == XFER_RESULT_SUCCESS) { - if (xfer->actual_len == epbuf->width) { + if (xfer->actual_len == epbuf->fu.control.width) { audioh_fu_value_store(epbuf); } else { xfer->result = XFER_RESULT_FAILED; @@ -1583,17 +1690,17 @@ static uint8_t audioh_fu_volume_range_request(uint8_t step) { } } -static void audioh_fu_volume_range_store(audioh_epbuf_t *epbuf) { +static void audioh_fu_volume_range_store(tuh_audio_stream_t *s, audioh_epbuf_t *epbuf) { const uint16_t value = tu_le16toh(tu_unaligned_read16(epbuf->fu_ctrl)); - switch (epbuf->volume_range_step) { + switch (epbuf->fu.mount.range_step) { case AUDIOH_VOLUME_RANGE_MIN: - epbuf->volume_range_temp.min = (int16_t)value; + s->volume_range.min = (int16_t)value; break; case AUDIOH_VOLUME_RANGE_MAX: - epbuf->volume_range_temp.max = (int16_t)value; + s->volume_range.max = (int16_t)value; break; case AUDIOH_VOLUME_RANGE_RES: - epbuf->volume_range_temp.res = value; + s->volume_range.res = value; break; default: break; @@ -1605,12 +1712,12 @@ static void audioh_mount_feature_unit_complete(tuh_xfer_t *xfer); static bool audioh_mount_feature_unit_submit(uint8_t idx) { audioh_interface_t *p_audio = &_audioh_itf[idx]; audioh_epbuf_t *epbuf = &_audioh_epbuf[idx]; - tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, epbuf->volume_stream_idx); + tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, epbuf->fu.mount.stream_idx); TU_ASSERT(s != NULL); const tusb_control_request_t request = { .bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_INTERFACE, .type = TUSB_REQ_TYPE_CLASS, .direction = TUSB_DIR_IN}, - .bRequest = audioh_fu_volume_range_request(epbuf->volume_range_step), + .bRequest = audioh_fu_volume_range_request(epbuf->fu.mount.range_step), .wValue = tu_htole16(tu_u16(AUDIO10_FU_CTRL_VOLUME, 0)), .wIndex = tu_htole16(tu_u16(s->feature_unit_id, p_audio->ac_itf_num)), .wLength = tu_htole16(2), @@ -1628,13 +1735,13 @@ static void audioh_mount_feature_unit_next(uint8_t idx) { audioh_interface_t *p_audio = &_audioh_itf[idx]; audioh_epbuf_t *epbuf = &_audioh_epbuf[idx]; - while (epbuf->volume_stream_idx < p_audio->stream_count) { - tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, epbuf->volume_stream_idx); + while (epbuf->fu.mount.stream_idx < p_audio->stream_count) { + tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, epbuf->fu.mount.stream_idx); TU_ASSERT(s != NULL, ); if (s->volume_supported) { - epbuf->volume_range_temp = (tuh_audio_volume_range_t){0}; - epbuf->volume_range_step = AUDIOH_VOLUME_RANGE_MIN; - epbuf->fu_busy = true; + s->volume_range = (tuh_audio_volume_range_t){0}; + epbuf->fu.mount.range_step = AUDIOH_VOLUME_RANGE_MIN; + epbuf->fu_busy = true; if (audioh_mount_feature_unit_submit(idx)) { return; } @@ -1644,7 +1751,7 @@ static void audioh_mount_feature_unit_next(uint8_t idx) { } epbuf->fu_busy = false; } - epbuf->volume_stream_idx++; + epbuf->fu.mount.stream_idx++; } p_audio->mounted = true; @@ -1660,33 +1767,32 @@ static void audioh_mount_feature_unit_complete(tuh_xfer_t *xfer) { return; } audioh_interface_t *p_audio = &_audioh_itf[idx]; - tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, epbuf->volume_stream_idx); + tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, epbuf->fu.mount.stream_idx); TU_ASSERT(s != NULL, ); if (xfer->result == XFER_RESULT_SUCCESS && xfer->actual_len == 2) { - audioh_fu_volume_range_store(epbuf); - epbuf->volume_range_step++; + audioh_fu_volume_range_store(s, epbuf); + epbuf->fu.mount.range_step++; - if (epbuf->volume_range_step < AUDIOH_VOLUME_RANGE_COUNT) { + if (epbuf->fu.mount.range_step < AUDIOH_VOLUME_RANGE_COUNT) { if (audioh_mount_feature_unit_submit(idx)) { return; } xfer->result = XFER_RESULT_FAILED; - } else if (epbuf->volume_range_temp.min <= epbuf->volume_range_temp.max && epbuf->volume_range_temp.res > 0) { - s->volume_range = epbuf->volume_range_temp; - } else { + } else if (s->volume_range.min > s->volume_range.max || s->volume_range.res == 0) { xfer->result = XFER_RESULT_FAILED; } } if (xfer->result != XFER_RESULT_SUCCESS || xfer->actual_len != 2) { s->volume_supported = false; + s->volume_range = (tuh_audio_volume_range_t){0}; if (!s->mute_supported) { s->feature_unit_id = 0; } } epbuf->fu_busy = false; - epbuf->volume_stream_idx++; + epbuf->fu.mount.stream_idx++; audioh_mount_feature_unit_next(idx); } @@ -1758,10 +1864,10 @@ static bool audioh_feature_unit_get(uint8_t idx, uint8_t stream_idx, uint8_t con audioh_epbuf_t *epbuf = &_audioh_epbuf[idx]; TU_VERIFY(!epbuf->fu_busy, false); - epbuf->fu_busy = true; - epbuf->value = value; - epbuf->width = width; - epbuf->value_type = value_type; + epbuf->fu_busy = true; + epbuf->value = value; + epbuf->fu.control.width = width; + epbuf->fu.control.value_type = value_type; const tusb_control_request_t request = {.bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_INTERFACE, .type = TUSB_REQ_TYPE_CLASS, diff --git a/src/class/audio/audio_host.h b/src/class/audio/audio_host.h index e850e4b7c..f8b6a80c8 100644 --- a/src/class/audio/audio_host.h +++ b/src/class/audio/audio_host.h @@ -79,9 +79,9 @@ typedef enum { // dir is constant for all configs of a given (dev_idx, stream_idx) and // equals the result of tuh_audio_stream_direction(). typedef struct { + uint32_t sample_rate; tuh_audio_direction_t dir; tuh_audio_format_t format; - uint32_t sample_rate; uint8_t channels; } tuh_audio_stream_config_t; |
