diff options
| author | HiFiPHile <[email protected]> | 2026-08-27 06:01:12 +0200 |
|---|---|---|
| committer | HiFiPHile <[email protected]> | 2026-08-27 10:34:34 +0200 |
| commit | fa6283249362278b887364ae51f9d866fcb96ad3 (patch) | |
| tree | 3b7eca9572356f54deba3c19954b133bbba5ee0e /src/class/audio | |
| parent | 544ba0453a02bbee3243f592a088eb6838614dfd (diff) | |
audio: clarify host driver comments
Group comments with the structures and helpers they describe, remove review-specific wording, and explain feedback-rate staging and control-buffer lifetimes in terms of runtime behavior.
Signed-off-by: HiFiPHile <[email protected]>
Diffstat (limited to 'src/class/audio')
| -rw-r--r-- | src/class/audio/audio_host.c | 393 |
1 files changed, 180 insertions, 213 deletions
diff --git a/src/class/audio/audio_host.c b/src/class/audio/audio_host.c index 5c7ce5f8e..370958aaf 100644 --- a/src/class/audio/audio_host.c +++ b/src/class/audio/audio_host.c @@ -7,13 +7,11 @@ */ /* - * This driver implements a USB Audio Host (UAC1/UAC2) class driver with a - * WASAPI/ALSA-like high-level streaming API. - * The USB Audio topology (Audio Control interface, Audio Streaming interfaces, alternate settings, and endpoints) is - * kept private to the driver. + * USB Audio Host driver for UAC1 and UAC2 devices. The public API exposes + * logical capture and playback streams while keeping the USB Audio topology + * private. * - * Each instance (Audio Control interface) provides at most one logical stream - * per direction: + * Each Audio Control interface provides at most one stream per direction: * - capture stream (TUSB_DIR_IN): device -> host, filled by isochronous IN * transfers scheduled by the driver into a FIFO, drained by the application * with tuh_audio_read() @@ -21,83 +19,45 @@ * OUT transfers from a FIFO filled by the application with tuh_audio_write() * * While a stream is running, the driver keeps one isochronous transfer in - * flight at the endpoint's polling cadence and re-submits on completion. The - * FIFO + endpoint-claim pattern is modeled after the tu_edpt_stream helper - * used by the MIDI host driver: the application's frame-based read/write is - * decoupled from the USB transfer cadence, and only whole frames are ever - * queued or transferred. Completion of each transfer is reported through - * tuh_audio_capture_cb()/tuh_audio_playback_cb(), failures through + * flight and submits the next transfer from its completion callback. A FIFO + * decouples application I/O from the endpoint's polling cadence; only whole + * audio frames are queued or transferred. Completion is reported through + * tuh_audio_capture_cb()/tuh_audio_playback_cb(); failures are reported through * tuh_audio_err_cb(). * - * The supported configurations of all Audio Streaming interfaces and alternate - * 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(). + * Configurations are exposed as a flat list of discrete format, sample-rate, + * and channel-count tuples. Internally, configurations are grouped by + * alternate setting so shared endpoint properties are stored only once. * - * Non-PCM formats are not registered as supported configurations during - * enumeration. UAC1 continuous - * sampling-frequency ranges are unsupported. - * The driver owns: - * 1. Endpoint selection and opening; only the alternate setting selected by - * tuh_audio_configure() is activated by tuh_audio_start(). - * 2. Protocol-specific sampling-frequency control. + * Only Type-I PCM formats and discrete UAC1 sampling frequencies are + * registered. tuh_audio_configure() selects and opens endpoints; + * tuh_audio_start() activates the alternate setting and performs the + * protocol-specific sampling-frequency control. */ #include "tusb_option.h" #if (CFG_TUH_ENABLED && CFG_TUH_AUDIO) -#include "host/usbh.h" -#include "host/usbh_pvt.h" -#include "audio_host.h" + #include "host/usbh.h" + #include "host/usbh_pvt.h" + #include "audio_host.h" -// Level where CFG_TUSB_DEBUG must be at least for this driver is logged -#ifndef CFG_TUH_AUDIO_LOG_LEVEL - #define CFG_TUH_AUDIO_LOG_LEVEL CFG_TUH_LOG_LEVEL -#endif - -#define TU_LOG_DRV(...) TU_LOG(CFG_TUH_AUDIO_LOG_LEVEL, __VA_ARGS__) + // Driver-specific log level; defaults to the host-stack log level. + #ifndef CFG_TUH_AUDIO_LOG_LEVEL + #define CFG_TUH_AUDIO_LOG_LEVEL CFG_TUH_LOG_LEVEL + #endif + #define TU_LOG_DRV(...) TU_LOG(CFG_TUH_AUDIO_LOG_LEVEL, __VA_ARGS__) -//--------------------------------------------------------------------+ -// MACRO CONSTANT TYPEDEF -//--------------------------------------------------------------------+ //--------------------------------------------------------------------+ -// Weak stubs: invoked if no strong implementation is available +// MACROS, CONSTANTS, AND TYPES //--------------------------------------------------------------------+ -TU_ATTR_WEAK void tuh_audio_mount_cb(uint8_t idx) { - (void)idx; -} - -TU_ATTR_WEAK void tuh_audio_umount_cb(uint8_t idx) { - (void)idx; -} - -TU_ATTR_WEAK void tuh_audio_capture_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) { - (void)idx; - (void)stream_idx; - (void)xferred_bytes; -} - -TU_ATTR_WEAK void tuh_audio_playback_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) { - (void)idx; - (void)stream_idx; - (void)xferred_bytes; -} - -TU_ATTR_WEAK void tuh_audio_err_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) { - (void)idx; - (void)stream_idx; - (void)xferred_bytes; -} - -// Stream state machine enum { - STREAM_STATE_IDLE = 0, // not configured - STREAM_STATE_READY // configured, ready to start/stop + STREAM_STATE_IDLE = 0, // No active configuration. + STREAM_STATE_READY // Configured and ready to start. }; enum { @@ -106,30 +66,29 @@ enum { AUDIOH_CTRL_READ_WRITE = 3 }; -#if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1 - #define AUDIOH_MAX_RATE_SOURCES (2 * CFG_TUH_AUDIO_MAX_AS) -#else - #define AUDIOH_MAX_RATE_SOURCES TUH_AUDIO_STREAM_DIRECTION_COUNT -#endif + #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1 + #define AUDIOH_MAX_RATE_SOURCES (2 * CFG_TUH_AUDIO_MAX_AS) + #else + #define AUDIOH_MAX_RATE_SOURCES TUH_AUDIO_STREAM_DIRECTION_COUNT + #endif -// A UAC1 alternate setting owns one descriptor-provided rate source. UAC2 -// alternate settings attached to the same Clock Source share one rate source. +// UAC1 stores one rate source per alternate setting. UAC2 alternate settings +// that reference the same Clock Source share one rate source. typedef struct { uint32_t sample_rate[CFG_TUH_AUDIO_MAX_SAM_FREQ]; - uint8_t control_id; // UAC1 endpoint address or UAC2 Clock Source ID + uint8_t control_id; // UAC1 endpoint address or UAC2 Clock Source ID. uint8_t sample_rate_count; uint8_t frequency_access; } audioh_rate_source_t; -// One Audio Streaming alternate setting. Format, channels, and endpoint -// properties are shared by all of its discrete sampling frequencies. +// Properties shared by every sampling frequency of one AS alternate setting. typedef struct { - uint16_t ep_size; // endpoint max packet size + uint16_t ep_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 ep_attr; // Synchronization and usage fields from bmAttributes. uint8_t format; uint8_t channels; uint8_t terminal_id; @@ -137,10 +96,10 @@ typedef struct { uint8_t rate_count; } audioh_as_config_t; -// Explicit feedback exists only for playback alternate settings. +// Explicit-feedback endpoint associated with a playback alternate setting. typedef struct { uint8_t ep_addr; - uint8_t ep_size; // feedback endpoint max packet size (3 or 4) + uint8_t ep_size; uint8_t ep_interval; uint8_t ep_attr; } audioh_feedback_ep_t; @@ -148,9 +107,9 @@ typedef struct { typedef struct { 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. + // Packet rates use Q16.16 audio frames per data-endpoint poll interval. + // A new feedback rate is staged in pending_frames_q16 and adopted when + // rem_acc wraps. uint32_t nominal_frames_q16; uint32_t target_frames_q16; uint32_t pending_frames_q16; @@ -161,7 +120,7 @@ typedef struct { bool feedback_opened; } audioh_playback_t; -// Control-transfer state does not require USB-accessible memory. +// Control-transfer bookkeeping; transfer payloads are stored in audioh_epbuf_t. typedef struct { tuh_xfer_cb_t complete_cb; uintptr_t user_data; @@ -185,58 +144,55 @@ typedef struct { bool fu_busy; } audioh_ctrl_state_t; -// One logical stream (capture or playback) +// One logical capture or playback stream. typedef struct { - // instance info (set at init, preserved across close/open) - uint8_t idx; // instance index - uint8_t stream_idx; // logical stream index within the instance - tusb_dir_t dir; // TUSB_DIR_IN = capture, TUSB_DIR_OUT = playback + // Identity is initialized once and preserved when the stream is reset. + uint8_t idx; + uint8_t stream_idx; + tusb_dir_t dir; // TUSB_DIR_IN is capture; TUSB_DIR_OUT is playback. - // device owning this stream (0 = no device) + // Device address, or zero while this stream slot is unused. uint8_t daddr; - // Supported configurations (parsed during enumeration) + // Configurations discovered during enumeration. 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; // flattened public configuration index + // Selected configuration and runtime state. + uint8_t active_config; // Index in the flattened public configuration list. uint8_t active_as; uint8_t active_rate; - uint8_t state; // STREAM_STATE_* - bool running; // tuh_audio_start() called, transfers may be submitted + uint8_t state; + bool running; - // One Feature Unit associated with this logical stream (0 = none) + // Directly associated Feature Unit, or zero when none is usable. uint8_t feature_unit_id; uint8_t mute_access; uint8_t volume_access; tuh_audio_volume_range_t volume_range; - // Size in bytes of one frame (all channels) of the active configuration + // Bytes in one interleaved audio frame across all channels. uint16_t frame_bytes; - // 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 - // _audioh_epbuf[], the endpoint is bound by tu_edpt_stream_open() when the - // stream is configured. + // The FIFO decouples application I/O from isochronous transfers. ep_buf is + // assigned during driver initialization and the endpoint during configure. tu_edpt_stream_t edpt; uint8_t ff_buf[CFG_TUH_AUDIO_STREAM_BUFSIZE]; } tuh_audio_stream_t; -// Per-instance (Audio device) storage +// State owned by one Audio Control interface. typedef struct { - uint8_t daddr; // device address (0 = free slot) - uint8_t ac_itf_num; // Audio Control interface number - uint8_t protocol; // AUDIO_INT_PROTOCOL_CODE_V1/V2 + uint8_t daddr; // Device address, or zero for a free instance. + uint8_t ac_itf_num; + uint8_t protocol; uint8_t stream_count; uint8_t rate_source_count; bool mounted; audioh_rate_source_t rate_source[AUDIOH_MAX_RATE_SOURCES]; - // Logical streams: playback first, then capture (stream index order) + // Public stream indices are assigned in playback-then-capture order. tuh_audio_stream_t out_stream; tuh_audio_stream_t in_stream; audioh_playback_t playback; @@ -248,8 +204,8 @@ typedef struct { #endif typedef struct { - // Clock discovery completes before mount. Afterwards its storage is reused - // by independently cache-aligned sampling-frequency and Feature Unit buffers. + // Clock discovery finishes before mount, so its buffer can be reused by + // runtime sampling-frequency and Feature Unit requests. union { #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2 TUH_EPBUF_DEF(clock_range, AUDIOH_CLOCK_RANGE_BUFSIZE); @@ -259,16 +215,50 @@ typedef struct { TUH_EPBUF_DEF(fu_ctrl, 8); } runtime; } control; - // Explicit feedback can overlap both runtime control transfers. + // Feedback transfers may overlap runtime control transfers, so the feedback buffer is separate. TUH_EPBUF_DEF(feedback, 4); - TUH_EPBUF_DEF(epin, CFG_TUH_AUDIO_EPIN_BUFSIZE); // capture transfer buffer - TUH_EPBUF_DEF(epout, CFG_TUH_AUDIO_EPOUT_BUFSIZE); // playback transfer buffer + TUH_EPBUF_DEF(epin, CFG_TUH_AUDIO_EPIN_BUFSIZE); + TUH_EPBUF_DEF(epout, CFG_TUH_AUDIO_EPOUT_BUFSIZE); } audioh_epbuf_t; static audioh_interface_t _audioh_itf[CFG_TUH_AUDIO_MAX]; CFG_TUH_MEM_SECTION static audioh_epbuf_t _audioh_epbuf[CFG_TUH_AUDIO_MAX]; +//--------------------------------------------------------------------+ +// WEAK APPLICATION CALLBACKS +//--------------------------------------------------------------------+ + +TU_ATTR_WEAK void tuh_audio_mount_cb(uint8_t idx) { + (void)idx; +} + +TU_ATTR_WEAK void tuh_audio_umount_cb(uint8_t idx) { + (void)idx; +} + +TU_ATTR_WEAK void tuh_audio_capture_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) { + (void)idx; + (void)stream_idx; + (void)xferred_bytes; +} + +TU_ATTR_WEAK void tuh_audio_playback_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) { + (void)idx; + (void)stream_idx; + (void)xferred_bytes; +} + +TU_ATTR_WEAK void tuh_audio_err_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) { + (void)idx; + (void)stream_idx; + (void)xferred_bytes; +} + +//--------------------------------------------------------------------+ +// HELPERS +//--------------------------------------------------------------------+ + TU_ATTR_ALWAYS_INLINE static inline uint8_t *audioh_rate_ctrl(audioh_epbuf_t *epbuf) { return epbuf->control.runtime.rate_ctrl; } @@ -277,9 +267,6 @@ TU_ATTR_ALWAYS_INLINE static inline uint8_t *audioh_fu_ctrl(audioh_epbuf_t *epbu return epbuf->control.runtime.fu_ctrl; } -//--------------------------------------------------------------------+ -// Helper -//--------------------------------------------------------------------+ TU_ATTR_ALWAYS_INLINE static inline uint8_t find_new_audio_index(void) { for (uint8_t idx = 0; idx < CFG_TUH_AUDIO_MAX; idx++) { if (_audioh_itf[idx].daddr == 0) { @@ -321,7 +308,6 @@ static tuh_audio_stream_t *audioh_get_stream(audioh_interface_t *p_audio, tusb_d } } -// Look up a stream by its logical index within the instance static tuh_audio_stream_t *audioh_get_stream_by_idx(audioh_interface_t *p_audio, uint8_t stream_idx) { for (uint8_t i = 0; i < 2; i++) { tuh_audio_stream_t *s = (i == 0) ? &p_audio->out_stream : &p_audio->in_stream; @@ -400,7 +386,6 @@ static void audioh_stream_set_feature_unit(tuh_audio_stream_t *s, uint8_t unit_i s->volume_access = volume_access; } -// Map a Type-I PCM (subslot size, bit resolution) pair to a supported format. static bool audioh_format_from_pcm(uint8_t subslot_size, uint8_t bit_resolution, tuh_audio_format_t *format) { if (subslot_size == 1 && bit_resolution == 8) { *format = TUH_AUDIO_FORMAT_S8; @@ -418,8 +403,7 @@ static bool audioh_format_from_pcm(uint8_t subslot_size, uint8_t bit_resolution, return true; } -// Isochronous bInterval is a power-of-2 exponent in 1 ms full-speed frames -// or 125 us high-speed microframes. +// bInterval encodes 2^(bInterval-1) full-speed frames or high-speed microframes. static uint32_t audioh_interval_us(uint8_t ep_interval, uint8_t daddr) { const uint32_t unit_us = (tuh_speed_get(daddr) == TUSB_SPEED_HIGH) ? 125u : 1000u; return ((uint32_t)1u << (ep_interval - 1)) * unit_us; @@ -433,7 +417,7 @@ static uint32_t audioh_nominal_frames_q16(uint32_t sample_rate, uint8_t ep_inter return (uint32_t)((numerator + 500000u) / 1000000u); } -// Supported Feature Unit control widths (0 = unsupported variable/unknown width). +// Feature Unit controls with variable or unknown width return zero. static uint8_t audioh_fu_control_width(uint8_t control_selector) { switch (control_selector) { case AUDIO10_FU_CTRL_MUTE: @@ -452,7 +436,7 @@ static uint8_t audioh_fu_control_width(uint8_t control_selector) { } } -// Reset a stream to its unconfigured state (keeps idx, dir, and FIFO configuration) +// Preserve the stream identity and FIFO allocation while clearing device state. static void audioh_stream_reset(tuh_audio_stream_t *s) { s->daddr = 0; s->stream_idx = TUSB_INDEX_INVALID_8; @@ -476,7 +460,6 @@ 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++) { audioh_interface_t *p_audio = &_audioh_itf[idx]; @@ -498,7 +481,7 @@ static tuh_audio_stream_t *audioh_find_stream(uint8_t dev_addr, uint8_t ep_addr) } //--------------------------------------------------------------------+ -// Packet scheduler +// PACKET SCHEDULER //--------------------------------------------------------------------+ static void audioh_stream_error(tuh_audio_stream_t *s, uint16_t xferred_bytes); @@ -514,31 +497,26 @@ static void audioh_stream_feedback_xfer(tuh_audio_stream_t *s) { } } -// Re-arm the capture endpoint: request one full packet (the device sends at -// most its max packet size per poll interval). The overwritable FIFO retains -// the newest capture frames when the application cannot drain it in time. static void audioh_stream_capture_xfer(tuh_audio_stream_t *s) { TU_VERIFY(s->state == STREAM_STATE_READY && s->running, ); 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 + TU_VERIFY(usbh_edpt_claim(s->daddr, as->ep_addr), ); - // ep_size is guaranteed <= CFG_TUH_AUDIO_EPIN_BUFSIZE by enumeration if (!usbh_edpt_xfer(s->daddr, as->ep_addr, s->edpt.ep_buf, as->ep_size)) { audioh_stream_error(s, 0); } } -// Submit the next queued playback packet. Fractional frames per endpoint poll -// interval are accumulated on each successful submission, keeping the average -// data rate at the active nominal or feedback target. static void audioh_stream_playback_xfer(tuh_audio_stream_t *s) { TU_VERIFY(s->state == STREAM_STATE_READY && s->running, ); 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 + TU_VERIFY(usbh_edpt_claim(s->daddr, as->ep_addr), ); + // Accumulate fractional frames across endpoint intervals so packet lengths + // average to the active nominal or feedback rate. 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; @@ -554,8 +532,8 @@ static void audioh_stream_playback_xfer(tuh_audio_stream_t *s) { bytes_64 <= CFG_TUH_AUDIO_STREAM_BUFSIZE, ); const uint16_t bytes = (uint16_t)bytes_64; if (tu_fifo_count(&s->edpt.ff) < bytes) { - // Keep the isochronous stream active without consuming a partial frame. - // The queued audio is sent once a complete poll interval is available. + // Isochronous OUT must continue at every interval. Send silence until a + // complete packet is queued, leaving any partial packet in the FIFO. tu_memclr(s->edpt.ep_buf, bytes); } else { tu_fifo_read_n(&s->edpt.ff, s->edpt.ep_buf, bytes); @@ -569,13 +547,13 @@ static void audioh_stream_playback_xfer(tuh_audio_stream_t *s) { if (loop_done && playback->feedback_pending) { playback->target_frames_q16 = playback->pending_frames_q16; playback->feedback_pending = false; - // Keep the remainder from the completed cycle. Clearing it for every - // feedback update biases the average toward the integer packet sizes. + // Preserve the accumulator at the wrap boundary. Resetting it for each + // feedback update would bias the average toward integer packet lengths. } } //--------------------------------------------------------------------+ -// Configure state machine +// STREAM CONFIGURATION //--------------------------------------------------------------------+ static bool audioh_stream_close_ep(tuh_audio_stream_t *s) { @@ -624,7 +602,6 @@ static void audioh_stream_error(tuh_audio_stream_t *s, uint16_t xferred_bytes) { tuh_audio_err_cb(s->idx, s->stream_idx, xferred_bytes); } -// Submit the protocol-specific sampling-frequency control request. static bool audioh_stream_set_freq(tuh_audio_stream_t *s, tuh_xfer_cb_t complete_cb) { const audioh_as_config_t *as = audioh_stream_active_as(s); const audioh_rate_source_t *rate_source = audioh_as_rate_source(s, as); @@ -672,7 +649,6 @@ static bool audioh_stream_set_freq(tuh_audio_stream_t *s, tuh_xfer_cb_t complete return tuh_control_xfer(&xfer); } -// Reconstruct the endpoint descriptor of the selected configuration and open it static bool audioh_stream_open_ep(tuh_audio_stream_t *s) { const audioh_as_config_t *as = audioh_stream_active_as(s); @@ -691,7 +667,7 @@ static bool audioh_stream_open_ep(tuh_audio_stream_t *s) { return false; } - // Bind the transfer helper to the endpoint and start with an empty FIFO + // Bind the transfer helper to the selected endpoint and empty its FIFO. const uint16_t xfer_len = (s->dir == TUSB_DIR_IN) ? CFG_TUH_AUDIO_EPIN_BUFSIZE : CFG_TUH_AUDIO_EPOUT_BUFSIZE; tu_edpt_stream_open(&s->edpt, s->daddr, &desc_ep, xfer_len); tu_edpt_stream_clear(&s->edpt); @@ -722,7 +698,7 @@ static bool audioh_stream_open_ep(tuh_audio_stream_t *s) { } //--------------------------------------------------------------------+ -// USBH API +// USB HOST CLASS DRIVER //--------------------------------------------------------------------+ bool audioh_init(void) { tu_memclr(&_audioh_itf, sizeof(_audioh_itf)); @@ -736,7 +712,6 @@ bool audioh_init(void) { out->idx = idx; out->dir = TUSB_DIR_OUT; - // Bind FIFO buffer and transfer buffer (see tu_edpt_stream_init) TU_VERIFY(tu_edpt_stream_init(&in->edpt, true, false, true, in->ff_buf, CFG_TUH_AUDIO_STREAM_BUFSIZE, _audioh_epbuf[idx].epin)); TU_VERIFY(tu_edpt_stream_init(&out->edpt, true, true, false, out->ff_buf, CFG_TUH_AUDIO_STREAM_BUFSIZE, @@ -775,7 +750,8 @@ void audioh_close(uint8_t daddr) { } audioh_playback_reset(&p_audio->playback); - tu_memclr(&p_audio->ctrl, sizeof(p_audio->ctrl)); // drop pending control state + // A disconnected device cannot complete its pending control request. + tu_memclr(&p_audio->ctrl, sizeof(p_audio->ctrl)); p_audio->stream_count = 0; p_audio->daddr = 0; @@ -790,7 +766,7 @@ static void audioh_feedback_received(tuh_audio_stream_t *s, uint32_t xferred_byt 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. + // Three-byte feedback is Q10.14; the scheduler uses Q16.16 throughout. feedback_q16 = ((uint32_t)fb[0] | ((uint32_t)fb[1] << 8) | ((uint32_t)fb[2] << 16)) << 2; } else if (xferred_bytes == 4) { feedback_q16 = (uint32_t)fb[0] | ((uint32_t)fb[1] << 8) | ((uint32_t)fb[2] << 16) | ((uint32_t)fb[3] << 24); @@ -806,8 +782,8 @@ static void audioh_feedback_received(tuh_audio_stream_t *s, uint32_t xferred_byt return; } - // Feedback is expressed per USB frame/microframe. Scale it to the data - // endpoint's polling interval before handing it to the packet scheduler. + // Feedback is measured per USB frame or microframe. Scale it to the data + // endpoint's polling interval. 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) { @@ -822,8 +798,8 @@ static void audioh_feedback_received(tuh_audio_stream_t *s, uint32_t xferred_byt return; } - // Keep only the newest feedback sample. The packet scheduler promotes it at - // the end of its current fractional cycle. + // Replace any unconsumed sample; only the newest rate matters. The scheduler + // adopts it when the current fractional packet pattern wraps. playback->pending_frames_q16 = target_q16; playback->feedback_pending = true; } @@ -834,14 +810,14 @@ bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uin return false; } - // Failed, stalled, or aborted transfers never carry valid audio data + // Failed, stalled, and aborted transfers do not carry valid audio data. if (result != XFER_RESULT_SUCCESS) { TU_LOG_DRV(" AUDIO transfer failed: addr=%u ep=%02x result=%u\r\n", dev_addr, ep_addr, result); audioh_stream_error(s, (uint16_t)xferred_bytes); return true; } - // Stopped stream: the in-flight transfer completes and its data is discarded + // A stop does not cancel the transfer that was already in flight. if (!s->running) { return true; } @@ -854,8 +830,7 @@ bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uin } if (s->dir == TUSB_DIR_IN) { - // Capture: move the received bytes into the FIFO (whole frames only), - // notify, then re-arm for the next packet + // Queue whole capture frames, notify the application, then re-arm. const uint16_t bytes = (uint16_t)(xferred_bytes - (xferred_bytes % s->frame_bytes)); if (bytes > 0) { tu_fifo_write_n(&s->edpt.ff, s->edpt.ep_buf, bytes); @@ -863,7 +838,7 @@ bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uin tuh_audio_capture_cb(s->idx, s->stream_idx, (uint16_t)xferred_bytes); audioh_stream_capture_xfer(s); } else { - // Playback: notify, then submit the next queued packet + // Notify the application before requesting the next playback packet. tuh_audio_playback_cb(s->idx, s->stream_idx, (uint16_t)xferred_bytes); audioh_stream_playback_xfer(s); } @@ -871,7 +846,7 @@ bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uin } //--------------------------------------------------------------------+ -// Enumeration +// ENUMERATION //--------------------------------------------------------------------+ typedef struct { @@ -1225,9 +1200,8 @@ static bool audioh_uac1_rates_store(const audioh_interface_t *p_audio, const tuh } #endif -// Parse one Audio Streaming interface alternate setting and register its -// supported configurations into the matching stream. Returns the descriptor -// pointer of the next interface. +// Parse one AS alternate setting and return the next interface descriptor. +// Supported configurations are appended to the stream matching its endpoint. 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) { @@ -1238,7 +1212,7 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const audioh_ p_desc = tu_desc_next(p_desc); - // Alternate setting 0 has no endpoints: nothing to stream + // Alternate setting zero is the zero-bandwidth setting, not a configuration. if (alt == 0 || desc_itf->bNumEndpoints == 0) { while (p_desc < desc_end) { TU_VERIFY(audioh_desc_valid(p_desc, desc_end, 2), NULL); @@ -1250,12 +1224,11 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const audioh_ return p_desc; } - // Parse the class-specific and endpoint descriptors of this alternate setting audioh_as_class_info_t class_info = {0}; - // An AS alternate setting has one audio data endpoint and may have one - // explicit feedback endpoint. Implicit-feedback endpoints are data endpoints - // and are handled normally when they are the AS interface's data endpoint. + // Retain one data endpoint and, for playback, one explicit-feedback endpoint. + // An implicit-feedback IN endpoint remains the data endpoint of its own AS + // interface and is therefore exposed as a capture stream. typedef struct { uint8_t ep_addr; uint16_t ep_size; @@ -1299,6 +1272,8 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const audioh_ bool is_data_ep = false; bool is_explicit_feedback = false; + // UAC1 distinguishes feedback by synchronization type; UAC2 uses the + // endpoint usage field. switch (p_audio->protocol) { #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1 case AUDIO_INT_PROTOCOL_CODE_V1: @@ -1348,7 +1323,7 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const audioh_ ep_info.ep_addr = desc_endpoint->bEndpointAddress; ep_info.ep_size = tu_edpt_packet_size(desc_endpoint); ep_info.ep_interval = desc_endpoint->bInterval; - // bInterval must be in [1, 16] for isochronous endpoints + // Isochronous bInterval is an exponent in the inclusive range 1..16. if (ep_info.ep_interval == 0 || ep_info.ep_interval > 16) { ep_info.ep_interval = 1; } @@ -1368,7 +1343,6 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const audioh_ return p_desc; } - // Reject unsupported formats explicitly. bool pcm_supported = false; switch (p_audio->protocol) { #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1 @@ -1408,8 +1382,8 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const audioh_ TU_LOG_DRV(" AUDIO AS itf %u: frame size %lu not supported\r\n", itf_num, (unsigned long)frame_bytes_32); return p_desc; } - // Register one AS alternate setting containing its discrete sampling - // frequencies. The public API flattens these entries when requested. + // Store the alternate setting once; the public API expands its sampling + // frequencies into separate configurations. 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); @@ -1428,8 +1402,8 @@ static const uint8_t *audioh_parse_as(audioh_interface_t *p_audio, const audioh_ return p_desc; } - // Capture: the device can deliver up to its max packet size per poll - // interval, the transfer buffer must fit it + // Capture always requests the endpoint's maximum packet size, so both the + // transfer buffer and FIFO must hold it. if (stream->dir == TUSB_DIR_IN && (ep->ep_size > epbuf_size || ep->ep_size > CFG_TUH_AUDIO_STREAM_BUFSIZE)) { TU_LOG_DRV(" AUDIO AS itf %u alt %u: capture ep size %u exceeds buffer capacity\r\n", itf_num, alt, ep->ep_size); return p_desc; @@ -1558,7 +1532,8 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface } ac_desc.desc_end = p_desc; - // Parse the contiguous Audio Streaming interfaces of this audio function. + // Audio Streaming interfaces belonging to this function immediately follow + // its Audio Control descriptor block. while (p_desc < desc_end) { if (!audioh_desc_valid(p_desc, desc_end, 2)) { goto open_failed; @@ -1588,14 +1563,11 @@ uint16_t audioh_open(uint8_t rhport, uint8_t dev_addr, const tusb_desc_interface 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. if (p_audio->in_stream.as_count == 0 && p_audio->out_stream.as_count == 0) { goto open_failed; } - // Assign stream indices: playback first, then capture, so the application - // can iterate [0, stream_count) without gaps + // Assign contiguous public indices in playback-then-capture order. uint8_t stream_idx = 0; if (p_audio->out_stream.as_count > 0) { p_audio->out_stream.stream_idx = stream_idx++; @@ -1621,7 +1593,7 @@ open_failed: } //--------------------------------------------------------------------+ -// Set Configuration +// SET CONFIGURATION //--------------------------------------------------------------------+ static void audioh_mount_feature_unit_next(uint8_t idx); @@ -1805,8 +1777,8 @@ bool audioh_set_config(uint8_t dev_addr, uint8_t itf_num) { } if (idx == TUSB_INDEX_INVALID_8) { - // Audio Streaming interface (or another driver's interface): nothing to do at mount. - // Alternate settings are activated by tuh_audio_start(). + // Only the Audio Control interface drives mounting. Streaming alternate + // settings are selected later by tuh_audio_start(). usbh_driver_set_config_complete(dev_addr, itf_num); return true; } @@ -1826,7 +1798,7 @@ bool audioh_set_config(uint8_t dev_addr, uint8_t itf_num) { } //--------------------------------------------------------------------+ -// Application API +// APPLICATION API //--------------------------------------------------------------------+ bool tuh_audio_mounted(uint8_t idx) { TU_VERIFY(idx < CFG_TUH_AUDIO_MAX); @@ -1933,18 +1905,17 @@ bool tuh_audio_configure(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_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 + // Configuration cannot close an endpoint while its final transfer drains. TU_VERIFY(!usbh_edpt_busy(s->daddr, s->edpt.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); } } - // The HCD endpoint must be reopened even when the new configuration uses - // the same address, since its packet size and interval may have changed. + // Reopen even when the address is unchanged: packet size and interval belong + // to the alternate setting and may differ. TU_VERIFY(audioh_stream_close_ep(s), false); const audioh_as_config_t *as = &s->as[as_idx]; @@ -1965,8 +1936,7 @@ bool tuh_audio_configure(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx 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. + // Overwrite mode is frame-safe only when FIFO depth is a whole-frame multiple. const uint16_t fifo_depth = CFG_TUH_AUDIO_STREAM_BUFSIZE - (CFG_TUH_AUDIO_STREAM_BUFSIZE % s->frame_bytes); if (!tu_fifo_config(&s->edpt.ff, s->ff_buf, fifo_depth, true)) { audioh_stream_fail(s); @@ -1981,12 +1951,11 @@ bool tuh_audio_configure(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx return audioh_stream_open_ep(s); } -// Invoked when the SET_INTERFACE activating the stream's interface completes: -// the interface is active, set its sampling frequency before submitting -// transfers +// Start endpoint transfers after the alternate setting and sampling frequency +// are both active. 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); } else { if (audioh_get_playback(s)->feedback[s->active_as].ep_addr != 0) { audioh_stream_feedback_xfer(s); @@ -1994,7 +1963,7 @@ static void audioh_stream_start_xfer(tuh_audio_stream_t *s) { if (!s->running) { return; } - audioh_stream_playback_xfer(s); // start the continuous playback transfer chain + audioh_stream_playback_xfer(s); } } @@ -2008,7 +1977,8 @@ static bool audioh_stream_activate(tuh_audio_stream_t *s) { static void audioh_stream_start_set_freq_complete(tuh_xfer_t *xfer) { tuh_audio_stream_t *s = (tuh_audio_stream_t *)xfer->user_data; if (s->daddr != xfer->daddr || s->state != STREAM_STATE_READY || !s->running) { - return; // device is gone or the stream was stopped meanwhile + // Ignore a completion delivered after disconnect or stop. + return; } if (xfer->result != XFER_RESULT_SUCCESS) { TU_LOG_DRV(" AUDIO set sampling frequency failed: result=%u\r\n", xfer->result); @@ -2048,7 +2018,8 @@ static bool audioh_stream_start_active(tuh_audio_stream_t *s) { static void audioh_stream_start_complete(tuh_xfer_t *xfer) { tuh_audio_stream_t *s = (tuh_audio_stream_t *)xfer->user_data; if (s->daddr != xfer->daddr || s->state != STREAM_STATE_READY || !s->running) { - return; // device is gone or the stream was stopped meanwhile + // Ignore a completion delivered after disconnect or stop. + return; } if (xfer->result != XFER_RESULT_SUCCESS) { TU_LOG_DRV(" AUDIO SET_INTERFACE activate failed: result=%u\r\n", xfer->result); @@ -2066,7 +2037,7 @@ bool tuh_audio_start(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, false); TU_VERIFY(s->state == STREAM_STATE_READY && !s->running, false); - // Wait for any in-flight transfer to complete and be discarded + // A stopped transfer must drain before the endpoint can be restarted. const audioh_as_config_t *as = audioh_stream_active_as(s); const audioh_rate_source_t *rate_source = audioh_as_rate_source(s, as); TU_VERIFY(!usbh_edpt_busy(s->daddr, as->ep_addr), false); @@ -2074,8 +2045,7 @@ bool tuh_audio_start(uint8_t dev_idx, uint8_t stream_idx) { TU_VERIFY(!usbh_edpt_busy(s->daddr, p_audio->playback.feedback[s->active_as].ep_addr), false); } - // Do not change a device-wide/shared clock while the other direction is - // running. Stopped streams may be reconfigured independently in either order. + // Capture and playback must use the same rate while both are running. tuh_audio_stream_t *other = (s == &p_audio->out_stream) ? &p_audio->in_stream : &p_audio->out_stream; if (other->running) { const audioh_as_config_t *other_as = audioh_stream_active_as(other); @@ -2095,8 +2065,8 @@ bool tuh_audio_start(uint8_t dev_idx, uint8_t stream_idx) { p_audio->playback.rem_acc = 0; } s->running = true; - // UAC2 changes the Clock Source before selecting the alternate setting; - // UAC1 selects the alternate first because its control targets the endpoint. + // UAC2 controls a Clock Source that exists before endpoint activation. UAC1 + // controls the endpoint itself, so its alternate setting must be active first. bool submitted = false; #if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2 if (p_audio->protocol == AUDIO_INT_PROTOCOL_CODE_V2 && rate_source->frequency_access == AUDIOH_CTRL_READ_WRITE) { @@ -2113,8 +2083,6 @@ bool tuh_audio_start(uint8_t dev_idx, uint8_t stream_idx) { return true; } -// Invoked when the SET_INTERFACE deactivating the stream's interface (alt 0) -// completes static void audioh_stream_stop_complete(tuh_xfer_t *xfer) { tuh_audio_stream_t *s = (tuh_audio_stream_t *)xfer->user_data; if (s->daddr != xfer->daddr) { @@ -2132,13 +2100,11 @@ bool tuh_audio_stop(uint8_t dev_idx, uint8_t stream_idx) { TU_VERIFY(s && s->state == STREAM_STATE_READY, false); 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. + // Preserve running state when submission fails so the caller can retry. 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. + // SET_INTERFACE stops future traffic. The current transfer drains, while its + // data and all queued frames are discarded. s->running = false; if (s->dir == TUSB_DIR_OUT) { p_audio->playback.target_frames_q16 = p_audio->playback.nominal_frames_q16; @@ -2155,12 +2121,11 @@ uint32_t tuh_audio_write(uint8_t dev_idx, uint8_t stream_idx, const void *buffer TU_VERIFY(p_audio->mounted && buffer, 0); tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx); - // Writes are only accepted by the playback stream TU_VERIFY(s && s->dir == TUSB_DIR_OUT, 0); TU_VERIFY(s->state == STREAM_STATE_READY && s->running, 0); TU_VERIFY(frame_count > 0, 0); - // Queue as many whole frames as the FIFO can hold + // Never split an audio frame at the FIFO boundary. const uint32_t frames = TU_MIN(frame_count, tu_fifo_remaining(&s->edpt.ff) / s->frame_bytes); if (frames == 0) { return 0; @@ -2176,12 +2141,11 @@ uint32_t tuh_audio_read(uint8_t dev_idx, uint8_t stream_idx, void *buffer, uint3 TU_VERIFY(p_audio->mounted && buffer, 0); tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx); - // Reads are only accepted by the capture stream TU_VERIFY(s && s->dir == TUSB_DIR_IN, 0); TU_VERIFY(s->state == STREAM_STATE_READY && s->running, 0); TU_VERIFY(frame_count > 0, 0); - // Drain as many whole frames as are queued + // Never return a partial audio frame. const uint32_t frames = TU_MIN(frame_count, tu_fifo_count(&s->edpt.ff) / s->frame_bytes); if (frames > 0) { tu_fifo_read_n(&s->edpt.ff, buffer, (uint16_t)(frames * s->frame_bytes)); @@ -2212,10 +2176,11 @@ uint32_t tuh_audio_read_available(uint8_t dev_idx, uint8_t stream_idx) { } //--------------------------------------------------------------------+ -// Feature Unit Control API +// FEATURE UNIT CONTROLS //--------------------------------------------------------------------+ -// Release the stable SET buffer and chain to the application callback +// Release driver-owned request state before invoking the application callback, +// allowing the callback to submit another Feature Unit request immediately. static void audioh_fu_set_complete(tuh_xfer_t *xfer) { const uint8_t idx = (uint8_t)xfer->user_data; audioh_ctrl_state_t *ctrl = &_audioh_itf[idx].ctrl; @@ -2281,7 +2246,8 @@ static void audioh_fu_value_store(audioh_ctrl_state_t *ctrl, audioh_epbuf_t *epb } } -// Convert the raw control value to host order and chain to the application callback +// Convert the driver-owned response before releasing the request state and +// invoking the application callback. static void audioh_fu_get_complete(tuh_xfer_t *xfer) { const uint8_t idx = (uint8_t)xfer->user_data; audioh_ctrl_state_t *ctrl = &_audioh_itf[idx].ctrl; @@ -2467,7 +2433,8 @@ bool tuh_audio_feature_unit_set(uint8_t idx, uint8_t stream_idx, uint8_t control audioh_ctrl_state_t *ctrl = &p_audio->ctrl; audioh_epbuf_t *epbuf = &_audioh_epbuf[idx]; TU_VERIFY(!ctrl->fu_busy, false); - ctrl->fu_busy = true; // reserve the request state and fu_ctrl before writing + // Reserve both bookkeeping and payload storage before populating the request. + ctrl->fu_busy = true; const tusb_control_request_t request = {.bmRequestType_bit = {.recipient = TUSB_REQ_RCPT_INTERFACE, .type = TUSB_REQ_TYPE_CLASS, @@ -2545,8 +2512,8 @@ static bool audioh_feature_unit_get(uint8_t idx, uint8_t stream_idx, uint8_t con .wLength = width}; if (complete_cb == NULL) { - // Sync (blocking) path: user_data points to a tusb_xfer_result_t, the raw - // bytes are converted to host order after the transfer completes + // The synchronous transfer completes before its driver-owned response is + // converted to host order. tuh_xfer_t xfer = {.daddr = p_audio->daddr, .ep_addr = 0, .setup = &request, @@ -2566,7 +2533,7 @@ static bool audioh_feature_unit_get(uint8_t idx, uint8_t stream_idx, uint8_t con return true; } - // Async path: chain the host-order conversion to the application callback + // The asynchronous wrapper converts the response before calling the application. ctrl->complete_cb = complete_cb; ctrl->user_data = user_data; tuh_xfer_t xfer = {.daddr = p_audio->daddr, |
