summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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));