summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHiFiPHile <[email protected]>2026-08-27 15:28:07 +0200
committerHiFiPHile <[email protected]>2026-08-27 15:28:07 +0200
commit95a99e18d4a46d4bec2994a74ffd1818d28d1dc4 (patch)
tree133f182be283b1c80e17c393604fe892cfb9a6ed
parented70f7206694e2ee5dfc0e7b3b947c27413deba0 (diff)
Improve audio host stream event reporting
Report asynchronous start and stop completion with transfer results. Replace the ambiguous stream error callback and byte count with explicit transfer-failure events, and ignore stale completions after stopping.
-rw-r--r--examples/host/audio_host/README.md4
-rw-r--r--examples/host/audio_host/src/audio_app.c33
-rw-r--r--src/class/audio/audio_host.c157
-rw-r--r--src/class/audio/audio_host.h23
-rw-r--r--test/unit-test/test/host/audio/test_audio_host.c101
5 files changed, 207 insertions, 111 deletions
diff --git a/examples/host/audio_host/README.md b/examples/host/audio_host/README.md
index 706752a05..3d4307b42 100644
--- a/examples/host/audio_host/README.md
+++ b/examples/host/audio_host/README.md
@@ -73,7 +73,7 @@ make BOARD=<your_board> flash
- Echo captured audio to an S16_LE playback configuration at the same sample rate (same channel count preferred, converted otherwise)
- Read/unmute the microphone and speaker Feature Units and set supported master volumes near -6 dB
- Service both FIFOs from `audio_app_task()` at their half-full/half-drained watermarks; a sine test tone plays on the playback stream when no capture stream is echoing
- - Cycle through the three phases (mic-only / spk-only / echo, 5 s each) with `tuh_audio_start()` / `tuh_audio_stop()`; a failed stream is restarted automatically 100 ms after the error callback
+ - Cycle through the three phases (mic-only / spk-only / echo, 5 s each) with `tuh_audio_start()` / `tuh_audio_stop()`; their asynchronous results are printed from `tuh_audio_event_cb()`, and a failed stream is restarted automatically after 100 ms
## Serial Output Example
@@ -116,7 +116,7 @@ Edit `src/tusb_config.h` to modify:
## Notes
- `tuh_audio_descriptor_cb()` exposes the validated Audio Control descriptor block during enumeration. Applications that need raw entity controls must copy the required entity IDs or descriptor fields before the callback returns, then use `tuh_audio_control_xfer()` after the device mounts.
-- While a stream is running, the driver keeps one isochronous transfer in flight and re-submits on completion, so transfers follow the endpoint's `bInterval`. `tuh_audio_capture_cb()` / `tuh_audio_playback_cb()` only count completed transfers; `audio_app_task()` services the FIFOs independently from the main loop. `tuh_audio_err_cb()` reports failures, and the example restarts the failed stream automatically 100 ms later.
+- While a stream is running, the driver keeps one isochronous transfer in flight and re-submits on completion, so transfers follow the endpoint's `bInterval`. `tuh_audio_capture_cb()` / `tuh_audio_playback_cb()` only count completed transfers; `audio_app_task()` services the FIFOs independently from the main loop. `tuh_audio_event_cb()` reports asynchronous start/stop results and unrecoverable transfer failures. The example restarts a failed stream automatically 100 ms later.
- Capture and playback streams running concurrently in the same Audio Control instance must use the same sample rate.
- `tuh_audio_read()` / `tuh_audio_write()` are non-blocking FIFO operations: they return the number of whole frames actually read/queued. `tuh_audio_read_available()` reports captured frames ready to read; `tuh_audio_write_available()` reports free playback capacity. `tuh_audio_write()` only queues data; the playback transfer-completion chain sends it, or sends silence when the FIFO does not contain a complete polling interval without consuming the partial data.
- Isochronous transfers require the host to poll `tuh_task()` continuously; the capture FIFO absorbs short scheduling gaps and overwrites the oldest frames when full.
diff --git a/examples/host/audio_host/src/audio_app.c b/examples/host/audio_host/src/audio_app.c
index d3cc79c41..55efe3295 100644
--- a/examples/host/audio_host/src/audio_app.c
+++ b/examples/host/audio_host/src/audio_app.c
@@ -41,7 +41,7 @@ static tuh_audio_stream_config_t mic_config; // se
static tuh_audio_stream_config_t spk_config; // selected playback configuration
static uint32_t spk_cb_count = 0; // playback callbacks (for debug)
static uint32_t mic_cb_count = 0; // capture callbacks (for debug)
-static uint32_t err_cb_count = 0; // error callbacks (for debug)
+static uint32_t fail_event_count = 0; // transfer/start failures (for debug)
//--------------------------------------------------------------------+
@@ -238,11 +238,11 @@ void led_blinking_task(void) {
board_led_write(led_state);
led_state = 1 - led_state; // toggle
#if 1
- printf(" MIC CB=%lu SPK CB=%lu ERR CB=%lu\r\n", (unsigned long)mic_cb_count, (unsigned long)spk_cb_count,
- (unsigned long)err_cb_count);
+ printf(" MIC CB=%lu SPK CB=%lu FAIL EVENT=%lu\r\n", (unsigned long)mic_cb_count, (unsigned long)spk_cb_count,
+ (unsigned long)fail_event_count);
mic_cb_count = 0;
spk_cb_count = 0;
- err_cb_count = 0;
+ fail_event_count = 0;
#endif
}
@@ -325,18 +325,29 @@ static void audio_app_restart_stream(uintptr_t param) {
}
}
-// Invoked when stream activation or an isochronous transfer fails: the stream
-// was stopped by the driver, re-open it after a short delay so the device can
-// recover.
-void tuh_audio_err_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) {
- (void)xferred_bytes;
- err_cb_count++;
+void tuh_audio_event_cb(uint8_t idx, uint8_t stream_idx, tuh_audio_event_t event, tusb_xfer_result_t result) {
+ const char *stream_name = (stream_idx == cap_stream_idx) ? "capture" : "playback";
+
+ if (event == TUH_AUDIO_EVENT_START_COMPLETE) {
+ printf(" %s start %s: result=%u\r\n", stream_name, result == XFER_RESULT_SUCCESS ? "complete" : "failed",
+ result);
+ if (result == XFER_RESULT_SUCCESS) {
+ return;
+ }
+ } else if (event == TUH_AUDIO_EVENT_STOP_COMPLETE) {
+ printf(" %s stop %s: result=%u\r\n", stream_name, result == XFER_RESULT_SUCCESS ? "complete" : "failed",
+ result);
+ return;
+ } else {
+ printf(" %s transfer failed: result=%u\r\n", stream_name, result);
+ }
+
+ fail_event_count++;
if (stream_idx == cap_stream_idx) {
mic_ready = false;
} else if (stream_idx == spk_stream_idx) {
spk_ready = false;
}
- printf(" AUDIO stream error: idx=%u stream=%u xferred_bytes=%u\r\n", idx, stream_idx, (unsigned)xferred_bytes);
app_defer_ms_async(100, (app_defer_func_t)audio_app_restart_stream, ((uintptr_t)idx << 8) | stream_idx);
}
diff --git a/src/class/audio/audio_host.c b/src/class/audio/audio_host.c
index 432109022..00d01b391 100644
--- a/src/class/audio/audio_host.c
+++ b/src/class/audio/audio_host.c
@@ -21,9 +21,9 @@
* While a stream is running, the driver keeps one isochronous transfer in
* 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().
+ * audio frames are queued or transferred. Successful packets are reported
+ * through tuh_audio_capture_cb()/tuh_audio_playback_cb(); stream operation and
+ * transport results are reported through tuh_audio_event_cb().
*
* Configurations are exposed as a flat list of discrete format, sample-rate,
* and channel-count tuples. Internally, configurations are grouped by
@@ -61,6 +61,12 @@ enum {
};
enum {
+ AUDIOH_STREAM_OP_NONE = 0,
+ AUDIOH_STREAM_OP_START,
+ AUDIOH_STREAM_OP_STOP
+};
+
+enum {
AUDIOH_CTRL_NONE = 0,
AUDIOH_CTRL_READ = 1,
AUDIOH_CTRL_READ_WRITE = 3
@@ -164,6 +170,7 @@ typedef struct {
uint8_t active_as;
uint8_t active_rate;
uint8_t state;
+ uint8_t operation;
bool running;
// Directly associated Feature Unit, or zero when none is usable.
@@ -254,10 +261,12 @@ TU_ATTR_WEAK void tuh_audio_playback_cb(uint8_t idx, uint8_t stream_idx, uint16_
(void)xferred_bytes;
}
-TU_ATTR_WEAK void tuh_audio_err_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) {
+TU_ATTR_WEAK void tuh_audio_event_cb(uint8_t idx, uint8_t stream_idx, tuh_audio_event_t event,
+ tusb_xfer_result_t result) {
(void)idx;
(void)stream_idx;
- (void)xferred_bytes;
+ (void)event;
+ (void)result;
}
//--------------------------------------------------------------------+
@@ -470,36 +479,31 @@ static tuh_audio_stream_t *audioh_find_stream(uint8_t dev_addr, uint8_t ep_addr)
// PACKET SCHEDULER
//--------------------------------------------------------------------+
-static void audioh_stream_error(tuh_audio_stream_t *s, uint16_t xferred_bytes);
+static void audioh_stream_xfer_failed(tuh_audio_stream_t *s, tusb_xfer_result_t result);
-static void audioh_stream_feedback_xfer(tuh_audio_stream_t *s) {
- TU_VERIFY(s->state == STREAM_STATE_READY && s->running, );
+static bool audioh_stream_feedback_xfer(tuh_audio_stream_t *s) {
+ TU_VERIFY(s->state == STREAM_STATE_READY && s->running, false);
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].feedback, feedback->ep_size)) {
- audioh_stream_error(s, 0);
- }
+ TU_VERIFY(feedback->ep_addr != 0, false);
+ TU_VERIFY(usbh_edpt_claim(s->daddr, feedback->ep_addr), false);
+ return usbh_edpt_xfer(s->daddr, feedback->ep_addr, _audioh_epbuf[s->idx].feedback, feedback->ep_size);
}
-static void audioh_stream_capture_xfer(tuh_audio_stream_t *s) {
- TU_VERIFY(s->state == STREAM_STATE_READY && s->running, );
+static bool audioh_stream_capture_xfer(tuh_audio_stream_t *s) {
+ TU_VERIFY(s->state == STREAM_STATE_READY && s->running, false);
const audioh_as_config_t *as = audioh_stream_active_as(s);
- TU_VERIFY(usbh_edpt_claim(s->daddr, as->ep_addr), );
-
- if (!usbh_edpt_xfer(s->daddr, as->ep_addr, s->edpt.ep_buf, as->ep_size)) {
- audioh_stream_error(s, 0);
- }
+ TU_VERIFY(usbh_edpt_claim(s->daddr, as->ep_addr), false);
+ return usbh_edpt_xfer(s->daddr, as->ep_addr, s->edpt.ep_buf, as->ep_size);
}
-static void audioh_stream_playback_xfer(tuh_audio_stream_t *s) {
- TU_VERIFY(s->state == STREAM_STATE_READY && s->running, );
+static bool audioh_stream_playback_xfer(tuh_audio_stream_t *s) {
+ TU_VERIFY(s->state == STREAM_STATE_READY && s->running, false);
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), );
+ TU_VERIFY(usbh_edpt_claim(s->daddr, as->ep_addr), false);
// Accumulate fractional frames across endpoint intervals so packet lengths
// average to the active nominal or feedback rate.
@@ -515,7 +519,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 <= as->ep_size && bytes_64 <= CFG_TUH_AUDIO_EPOUT_BUFSIZE &&
- bytes_64 <= CFG_TUH_AUDIO_STREAM_BUFSIZE, );
+ bytes_64 <= CFG_TUH_AUDIO_STREAM_BUFSIZE, false);
const uint16_t bytes = (uint16_t)bytes_64;
if (tu_fifo_count(&s->edpt.ff) < bytes) {
// Isochronous OUT must continue at every interval. Send silence until a
@@ -526,8 +530,7 @@ static void audioh_stream_playback_xfer(tuh_audio_stream_t *s) {
}
if (!usbh_edpt_xfer(s->daddr, as->ep_addr, s->edpt.ep_buf, bytes)) {
- audioh_stream_error(s, 0);
- return;
+ return false;
}
playback->rem_acc = (uint16_t)next_rem_acc;
if (loop_done && playback->feedback_pending) {
@@ -536,6 +539,7 @@ static void audioh_stream_playback_xfer(tuh_audio_stream_t *s) {
// Preserve the accumulator at the wrap boundary. Resetting it for each
// feedback update would bias the average toward integer packet lengths.
}
+ return true;
}
//--------------------------------------------------------------------+
@@ -573,10 +577,11 @@ static void audioh_stream_fail(tuh_audio_stream_t *s) {
s->active_config = TUSB_INDEX_INVALID_8;
s->active_as = TUSB_INDEX_INVALID_8;
s->active_rate = TUSB_INDEX_INVALID_8;
+ s->operation = AUDIOH_STREAM_OP_NONE;
s->running = false;
}
-static void audioh_stream_error(tuh_audio_stream_t *s, uint16_t xferred_bytes) {
+static void audioh_stream_stop_xfers(tuh_audio_stream_t *s) {
s->running = false;
if (s->dir == TUSB_DIR_OUT) {
audioh_playback_t *playback = audioh_get_playback(s);
@@ -585,7 +590,11 @@ static void audioh_stream_error(tuh_audio_stream_t *s, uint16_t xferred_bytes) {
playback->rem_acc = 0;
}
tu_edpt_stream_clear(&s->edpt);
- tuh_audio_err_cb(s->idx, s->stream_idx, xferred_bytes);
+}
+
+static void audioh_stream_xfer_failed(tuh_audio_stream_t *s, tusb_xfer_result_t result) {
+ audioh_stream_stop_xfers(s);
+ tuh_audio_event_cb(s->idx, s->stream_idx, TUH_AUDIO_EVENT_XFER_FAILED, result);
}
static bool audioh_stream_set_freq(tuh_audio_stream_t *s, tuh_xfer_cb_t complete_cb) {
@@ -796,22 +805,26 @@ bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uin
return false;
}
- // 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);
+ // Stopping one endpoint does not cancel every transfer that may already be
+ // in flight (for example, a playback data and feedback pair). Ignore those
+ // completions after the stream has stopped.
+ if (!s->running) {
return true;
}
- // A stop does not cancel the transfer that was already in flight.
- if (!s->running) {
+ // 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_xfer_failed(s, (tusb_xfer_result_t)result);
return true;
}
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);
+ if (!audioh_stream_feedback_xfer(s)) {
+ audioh_stream_xfer_failed(s, XFER_RESULT_FAILED);
+ }
return true;
}
@@ -822,11 +835,15 @@ bool audioh_xfer_cb(uint8_t dev_addr, uint8_t ep_addr, xfer_result_t result, uin
tu_fifo_write_n(&s->edpt.ff, s->edpt.ep_buf, bytes);
}
tuh_audio_capture_cb(s->idx, s->stream_idx, (uint16_t)xferred_bytes);
- audioh_stream_capture_xfer(s);
+ if (s->running && !audioh_stream_capture_xfer(s)) {
+ audioh_stream_xfer_failed(s, XFER_RESULT_FAILED);
+ }
} else {
// 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);
+ if (s->running && !audioh_stream_playback_xfer(s)) {
+ audioh_stream_xfer_failed(s, XFER_RESULT_FAILED);
+ }
}
return true;
}
@@ -1936,18 +1953,28 @@ bool tuh_audio_configure(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx
// Start endpoint transfers after the alternate setting and sampling frequency
// are both active.
-static void audioh_stream_start_xfer(tuh_audio_stream_t *s) {
+static bool audioh_stream_start_xfer(tuh_audio_stream_t *s) {
if (s->dir == TUSB_DIR_IN) {
- audioh_stream_capture_xfer(s);
+ return audioh_stream_capture_xfer(s);
} else {
if (audioh_get_playback(s)->feedback[s->active_as].ep_addr != 0) {
- audioh_stream_feedback_xfer(s);
+ TU_VERIFY(audioh_stream_feedback_xfer(s), false);
}
- if (!s->running) {
- return;
- }
- audioh_stream_playback_xfer(s);
+ return audioh_stream_playback_xfer(s);
+ }
+}
+
+static void audioh_stream_start_done(tuh_audio_stream_t *s, tusb_xfer_result_t result) {
+ if (result != XFER_RESULT_SUCCESS) {
+ audioh_stream_stop_xfers(s);
}
+ s->operation = AUDIOH_STREAM_OP_NONE;
+ tuh_audio_event_cb(s->idx, s->stream_idx, TUH_AUDIO_EVENT_START_COMPLETE, result);
+}
+
+static void audioh_stream_start_xfers(tuh_audio_stream_t *s) {
+ const tusb_xfer_result_t result = audioh_stream_start_xfer(s) ? XFER_RESULT_SUCCESS : XFER_RESULT_FAILED;
+ audioh_stream_start_done(s, result);
}
static void audioh_stream_start_complete(tuh_xfer_t *xfer);
@@ -1965,37 +1992,36 @@ static void audioh_stream_start_set_freq_complete(tuh_xfer_t *xfer) {
}
if (xfer->result != XFER_RESULT_SUCCESS) {
TU_LOG_DRV(" AUDIO set sampling frequency failed: result=%u\r\n", xfer->result);
- audioh_stream_error(s, 0);
+ audioh_stream_start_done(s, xfer->result);
return;
}
#if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC2
if (_audioh_itf[s->idx].protocol == AUDIO_INT_PROTOCOL_CODE_V2) {
if (!audioh_stream_activate(s)) {
- audioh_stream_error(s, 0);
+ audioh_stream_start_done(s, XFER_RESULT_FAILED);
}
} else
#endif
{
- audioh_stream_start_xfer(s);
+ audioh_stream_start_xfers(s);
}
}
-static bool audioh_stream_start_active(tuh_audio_stream_t *s) {
+static void audioh_stream_start_active(tuh_audio_stream_t *s) {
#if CFG_TUH_AUDIO_PROTOCOLS & TUH_AUDIO_PROTOCOL_UAC1
const audioh_as_config_t *as = audioh_stream_active_as(s);
const audioh_rate_source_t *rate_source = audioh_as_rate_source(s, as);
if (_audioh_itf[s->idx].protocol == AUDIO_INT_PROTOCOL_CODE_V1 &&
rate_source->frequency_access == AUDIOH_CTRL_READ_WRITE) {
if (!audioh_stream_set_freq(s, audioh_stream_start_set_freq_complete)) {
- audioh_stream_error(s, 0);
- return false;
+ audioh_stream_start_done(s, XFER_RESULT_FAILED);
}
+ return;
} else
#endif
{
- audioh_stream_start_xfer(s);
+ audioh_stream_start_xfers(s);
}
- return true;
}
static void audioh_stream_start_complete(tuh_xfer_t *xfer) {
@@ -2006,10 +2032,10 @@ static void audioh_stream_start_complete(tuh_xfer_t *xfer) {
}
if (xfer->result != XFER_RESULT_SUCCESS) {
TU_LOG_DRV(" AUDIO SET_INTERFACE activate failed: result=%u\r\n", xfer->result);
- audioh_stream_error(s, 0);
+ audioh_stream_start_done(s, xfer->result);
return;
}
- (void)audioh_stream_start_active(s);
+ audioh_stream_start_active(s);
}
bool tuh_audio_start(uint8_t dev_idx, uint8_t stream_idx) {
@@ -2019,7 +2045,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);
+ TU_VERIFY(s->state == STREAM_STATE_READY && s->operation == AUDIOH_STREAM_OP_NONE && !s->running, false);
// 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);
@@ -2047,7 +2073,8 @@ bool tuh_audio_start(uint8_t dev_idx, uint8_t stream_idx) {
p_audio->playback.feedback_pending = false;
p_audio->playback.rem_acc = 0;
}
- s->running = true;
+ s->running = true;
+ s->operation = AUDIOH_STREAM_OP_START;
// 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;
@@ -2060,7 +2087,8 @@ bool tuh_audio_start(uint8_t dev_idx, uint8_t stream_idx) {
submitted = audioh_stream_activate(s);
}
if (!submitted) {
- s->running = false;
+ s->operation = AUDIOH_STREAM_OP_NONE;
+ s->running = false;
return false;
}
return true;
@@ -2068,10 +2096,12 @@ bool tuh_audio_start(uint8_t dev_idx, uint8_t stream_idx) {
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) {
+ if (s->daddr != xfer->daddr || s->operation != AUDIOH_STREAM_OP_STOP) {
return;
}
TU_LOG_DRV(" AUDIO SET_INTERFACE deactivate done: result=%u\r\n", xfer->result);
+ s->operation = AUDIOH_STREAM_OP_NONE;
+ tuh_audio_event_cb(s->idx, s->stream_idx, TUH_AUDIO_EVENT_STOP_COMPLETE, xfer->result);
}
bool tuh_audio_stop(uint8_t dev_idx, uint8_t stream_idx) {
@@ -2080,7 +2110,7 @@ bool tuh_audio_stop(uint8_t dev_idx, uint8_t stream_idx) {
TU_VERIFY(p_audio->mounted, false);
tuh_audio_stream_t *s = audioh_get_stream_by_idx(p_audio, stream_idx);
- TU_VERIFY(s && s->state == STREAM_STATE_READY, false);
+ TU_VERIFY(s && s->state == STREAM_STATE_READY && s->operation == AUDIOH_STREAM_OP_NONE && s->running, false);
const audioh_as_config_t *as = audioh_stream_active_as(s);
// Preserve running state when submission fails so the caller can retry.
@@ -2088,13 +2118,8 @@ bool tuh_audio_stop(uint8_t dev_idx, uint8_t stream_idx) {
// 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;
- p_audio->playback.feedback_pending = false;
- p_audio->playback.rem_acc = 0;
- }
- tu_edpt_stream_clear(&s->edpt);
+ s->operation = AUDIOH_STREAM_OP_STOP;
+ audioh_stream_stop_xfers(s);
return true;
}
diff --git a/src/class/audio/audio_host.h b/src/class/audio/audio_host.h
index f83d7ea63..3cb1bb00c 100644
--- a/src/class/audio/audio_host.h
+++ b/src/class/audio/audio_host.h
@@ -84,6 +84,13 @@ typedef enum {
TUH_AUDIO_STREAM_DIRECTION_COUNT
} tuh_audio_direction_t;
+// Asynchronous stream operation and transport events.
+typedef enum {
+ TUH_AUDIO_EVENT_START_COMPLETE = 0,
+ TUH_AUDIO_EVENT_STOP_COMPLETE,
+ TUH_AUDIO_EVENT_XFER_FAILED
+} tuh_audio_event_t;
+
// Discrete Type-I PCM sample format. UAC1 requires bSamFreqType > 0; UAC2
// configurations are built from the directly connected Clock Source RANGE.
typedef enum {
@@ -173,10 +180,13 @@ bool tuh_audio_configure(uint8_t dev_idx, uint8_t stream_idx, uint8_t config_idx
// UAC1 activates the alternate setting before setting an endpoint frequency;
// UAC2 sets a writable Clock Source before activating the alternate setting.
// Startup is asynchronous: true means that the first request was submitted.
-// Later startup failures are reported through tuh_audio_err_cb().
+// Completion is reported through tuh_audio_event_cb(); no event is emitted
+// when this function returns false.
bool tuh_audio_start(uint8_t dev_idx, uint8_t stream_idx);
// Stop transferring and asynchronously deactivate the Audio Streaming
// interface (alt 0). true means that the deactivation request was submitted.
+// Completion is reported through tuh_audio_event_cb(); no event is emitted
+// when this function returns false.
bool tuh_audio_stop(uint8_t dev_idx, uint8_t stream_idx);
// Frame-based transfer. One frame = channels * bytes per sample.
@@ -310,10 +320,13 @@ void tuh_audio_capture_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_byte
// from the stream FIFO, or silence when a complete packet is unavailable.
void tuh_audio_playback_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes);
-// Invoked when asynchronous stream activation or an isochronous transfer
-// fails. The stream is stopped (tuh_audio_start() must be called again to
-// resume). xferred_bytes is zero for an activation failure.
-void tuh_audio_err_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes);
+// Reports completion of asynchronous start/stop operations and unrecoverable
+// transfer failures. START_COMPLETE is emitted after the complete activation
+// sequence and initial endpoint transfers are submitted. XFER_FAILED means the
+// HCD could not submit a transfer or completed it unsuccessfully; it is not a
+// notification for an individual dropped isochronous packet. The driver stops
+// the stream before reporting START_COMPLETE failure or XFER_FAILED.
+void tuh_audio_event_cb(uint8_t idx, uint8_t stream_idx, tuh_audio_event_t event, tusb_xfer_result_t result);
//--------------------------------------------------------------------+
// Internal Class Driver API
diff --git a/test/unit-test/test/host/audio/test_audio_host.c b/test/unit-test/test/host/audio/test_audio_host.c
index 2ede6b662..05b699b68 100644
--- a/test/unit-test/test/host/audio/test_audio_host.c
+++ b/test/unit-test/test/host/audio/test_audio_host.c
@@ -63,10 +63,11 @@ static uint8_t *edpt_xfer_buffer[AUDIO_TEST_MAX_XFERS];
static uint8_t edpt_xfer_count;
static tusb_speed_t test_speed;
-static uint8_t err_cb_count;
-static uint8_t err_cb_idx;
-static uint8_t err_cb_stream_idx;
-static uint16_t err_cb_xferred_bytes;
+static uint8_t event_cb_count;
+static uint8_t event_cb_idx;
+static uint8_t event_cb_stream_idx;
+static tuh_audio_event_t event_cb_event;
+static tusb_xfer_result_t event_cb_result;
static uint8_t descriptor_cb_count;
static uint8_t descriptor_cb_idx;
@@ -84,11 +85,12 @@ static void complete_edpt(uint8_t ep_addr) {
edpt_busy_mask &= ~edpt_mask(ep_addr);
}
-void tuh_audio_err_cb(uint8_t idx, uint8_t stream_idx, uint16_t xferred_bytes) {
- err_cb_count++;
- err_cb_idx = idx;
- err_cb_stream_idx = stream_idx;
- err_cb_xferred_bytes = xferred_bytes;
+void tuh_audio_event_cb(uint8_t idx, uint8_t stream_idx, tuh_audio_event_t event, tusb_xfer_result_t result) {
+ event_cb_count++;
+ event_cb_idx = idx;
+ event_cb_stream_idx = stream_idx;
+ event_cb_event = event;
+ event_cb_result = result;
}
void tuh_audio_descriptor_cb(uint8_t idx, const tuh_audio_descriptor_cb_t *desc_cb) {
@@ -893,10 +895,11 @@ void setUp(void) {
memset(edpt_xfer_buffer, 0, sizeof(edpt_xfer_buffer));
edpt_xfer_count = 0;
- err_cb_count = 0;
- err_cb_idx = TUSB_INDEX_INVALID_8;
- err_cb_stream_idx = TUSB_INDEX_INVALID_8;
- err_cb_xferred_bytes = 0;
+ event_cb_count = 0;
+ event_cb_idx = TUSB_INDEX_INVALID_8;
+ event_cb_stream_idx = TUSB_INDEX_INVALID_8;
+ event_cb_event = TUH_AUDIO_EVENT_START_COMPLETE;
+ event_cb_result = XFER_RESULT_INVALID;
descriptor_cb_count = 0;
descriptor_cb_idx = TUSB_INDEX_INVALID_8;
@@ -1332,20 +1335,45 @@ void test_audio_host_reports_asynchronous_start_failures(void) {
TEST_ASSERT_TRUE(tuh_audio_start(0, 0));
complete_interface_set(XFER_RESULT_FAILED);
- TEST_ASSERT_EQUAL_UINT8(1, err_cb_count);
- TEST_ASSERT_EQUAL_UINT8(0, err_cb_idx);
- TEST_ASSERT_EQUAL_UINT8(0, err_cb_stream_idx);
- TEST_ASSERT_EQUAL_UINT16(0, err_cb_xferred_bytes);
+ TEST_ASSERT_EQUAL_UINT8(1, event_cb_count);
+ TEST_ASSERT_EQUAL_UINT8(0, event_cb_idx);
+ TEST_ASSERT_EQUAL_UINT8(0, event_cb_stream_idx);
+ TEST_ASSERT_EQUAL(TUH_AUDIO_EVENT_START_COMPLETE, event_cb_event);
+ TEST_ASSERT_EQUAL(XFER_RESULT_FAILED, event_cb_result);
TEST_ASSERT_TRUE(tuh_audio_start(0, 0));
complete_interface_set(XFER_RESULT_SUCCESS);
complete_control_xfer(XFER_RESULT_STALLED);
- TEST_ASSERT_EQUAL_UINT8(2, err_cb_count);
+ TEST_ASSERT_EQUAL_UINT8(2, event_cb_count);
+ TEST_ASSERT_EQUAL(TUH_AUDIO_EVENT_START_COMPLETE, event_cb_event);
+ TEST_ASSERT_EQUAL(XFER_RESULT_STALLED, event_cb_result);
control_xfer_result = false;
TEST_ASSERT_TRUE(tuh_audio_start(0, 0));
complete_interface_set(XFER_RESULT_SUCCESS);
- TEST_ASSERT_EQUAL_UINT8(3, err_cb_count);
+ TEST_ASSERT_EQUAL_UINT8(3, event_cb_count);
+ TEST_ASSERT_EQUAL(TUH_AUDIO_EVENT_START_COMPLETE, event_cb_event);
+ TEST_ASSERT_EQUAL(XFER_RESULT_FAILED, event_cb_result);
+}
+
+void test_audio_host_reports_asynchronous_start_and_stop_completion(void) {
+ mount_descriptors(capture_fu_before_usb_output, sizeof(capture_fu_before_usb_output));
+ TEST_ASSERT_TRUE(tuh_audio_configure(0, 0, 0));
+
+ TEST_ASSERT_TRUE(tuh_audio_start(0, 0));
+ TEST_ASSERT_EQUAL_UINT8(0, event_cb_count);
+ complete_interface_set(XFER_RESULT_SUCCESS);
+ complete_control_xfer(XFER_RESULT_SUCCESS);
+ TEST_ASSERT_EQUAL_UINT8(1, event_cb_count);
+ TEST_ASSERT_EQUAL(TUH_AUDIO_EVENT_START_COMPLETE, event_cb_event);
+ TEST_ASSERT_EQUAL(XFER_RESULT_SUCCESS, event_cb_result);
+
+ TEST_ASSERT_TRUE(tuh_audio_stop(0, 0));
+ TEST_ASSERT_EQUAL_UINT8(1, event_cb_count);
+ complete_interface_set(XFER_RESULT_STALLED);
+ TEST_ASSERT_EQUAL_UINT8(2, event_cb_count);
+ TEST_ASSERT_EQUAL(TUH_AUDIO_EVENT_STOP_COMPLETE, event_cb_event);
+ TEST_ASSERT_EQUAL(XFER_RESULT_STALLED, event_cb_result);
}
void test_audio_host_reports_capture_submission_failure(void) {
@@ -1357,10 +1385,11 @@ void test_audio_host_reports_capture_submission_failure(void) {
complete_interface_set(XFER_RESULT_SUCCESS);
complete_control_xfer(XFER_RESULT_SUCCESS);
- TEST_ASSERT_EQUAL_UINT8(1, err_cb_count);
- TEST_ASSERT_EQUAL_UINT8(0, err_cb_idx);
- TEST_ASSERT_EQUAL_UINT8(0, err_cb_stream_idx);
- TEST_ASSERT_EQUAL_UINT16(0, err_cb_xferred_bytes);
+ TEST_ASSERT_EQUAL_UINT8(1, event_cb_count);
+ TEST_ASSERT_EQUAL_UINT8(0, event_cb_idx);
+ TEST_ASSERT_EQUAL_UINT8(0, event_cb_stream_idx);
+ TEST_ASSERT_EQUAL(TUH_AUDIO_EVENT_START_COMPLETE, event_cb_event);
+ TEST_ASSERT_EQUAL(XFER_RESULT_FAILED, event_cb_result);
edpt_xfer_result = true;
TEST_ASSERT_TRUE(tuh_audio_start(0, 0));
@@ -1374,15 +1403,33 @@ void test_audio_host_reports_playback_submission_failure(void) {
TEST_ASSERT_TRUE(tuh_audio_start(0, 0));
complete_interface_set(XFER_RESULT_SUCCESS);
- TEST_ASSERT_EQUAL_UINT8(1, err_cb_count);
- TEST_ASSERT_EQUAL_UINT8(0, err_cb_idx);
- TEST_ASSERT_EQUAL_UINT8(0, err_cb_stream_idx);
- TEST_ASSERT_EQUAL_UINT16(0, err_cb_xferred_bytes);
+ TEST_ASSERT_EQUAL_UINT8(1, event_cb_count);
+ TEST_ASSERT_EQUAL_UINT8(0, event_cb_idx);
+ TEST_ASSERT_EQUAL_UINT8(0, event_cb_stream_idx);
+ TEST_ASSERT_EQUAL(TUH_AUDIO_EVENT_START_COMPLETE, event_cb_event);
+ TEST_ASSERT_EQUAL(XFER_RESULT_FAILED, event_cb_result);
edpt_xfer_result = true;
TEST_ASSERT_TRUE(tuh_audio_start(0, 0));
}
+void test_audio_host_reports_runtime_transfer_failure_without_byte_count(void) {
+ mount_descriptors(capture_fu_before_usb_output, sizeof(capture_fu_before_usb_output));
+ TEST_ASSERT_TRUE(tuh_audio_configure(0, 0, 0));
+ TEST_ASSERT_TRUE(tuh_audio_start(0, 0));
+ complete_interface_set(XFER_RESULT_SUCCESS);
+ complete_control_xfer(XFER_RESULT_SUCCESS);
+ TEST_ASSERT_EQUAL_UINT8(1, event_cb_count);
+
+ complete_edpt(0x81);
+ TEST_ASSERT_TRUE(audioh_xfer_cb(AUDIO_DEV_ADDR, 0x81, XFER_RESULT_TIMEOUT, 37));
+ TEST_ASSERT_EQUAL_UINT8(2, event_cb_count);
+ TEST_ASSERT_EQUAL_UINT8(0, event_cb_idx);
+ TEST_ASSERT_EQUAL_UINT8(0, event_cb_stream_idx);
+ TEST_ASSERT_EQUAL(TUH_AUDIO_EVENT_XFER_FAILED, event_cb_event);
+ TEST_ASSERT_EQUAL(XFER_RESULT_TIMEOUT, event_cb_result);
+}
+
void test_audio_host_keeps_running_when_stop_cannot_be_submitted(void) {
mount_descriptors(capture_fu_before_usb_output, sizeof(capture_fu_before_usb_output));
TEST_ASSERT_TRUE(tuh_audio_configure(0, 0, 0));