diff options
| -rw-r--r-- | examples/host/audio_host/README.md | 4 | ||||
| -rw-r--r-- | examples/host/audio_host/src/audio_app.c | 33 | ||||
| -rw-r--r-- | src/class/audio/audio_host.c | 157 | ||||
| -rw-r--r-- | src/class/audio/audio_host.h | 23 | ||||
| -rw-r--r-- | test/unit-test/test/host/audio/test_audio_host.c | 101 |
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)); |
