diff options
| -rw-r--r-- | examples/host/audio_host/README.md | 6 | ||||
| -rw-r--r-- | src/class/audio/audio_host.c | 11 | ||||
| -rw-r--r-- | test/unit-test/test/host/audio/test_audio_host.c | 38 |
3 files changed, 51 insertions, 4 deletions
diff --git a/examples/host/audio_host/README.md b/examples/host/audio_host/README.md index 5413895a4..27e6425b8 100644 --- a/examples/host/audio_host/README.md +++ b/examples/host/audio_host/README.md @@ -52,8 +52,9 @@ make BOARD=<your_board> all ## Flashing ```bash -# Using CMake -ninja flash +# Using CMake: list the board-specific flash targets, then select one +ninja -t targets +ninja audio_host-jlink # example for a board with J-Link support # Using Make make BOARD=<your_board> flash @@ -111,5 +112,6 @@ Edit `src/tusb_config.h` to modify: ## Notes - 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()` report each completed transfer; `tuh_audio_err_cb()` reports failures. The example restarts the failed stream automatically 100 ms after the error callback. +- Capture and playback streams 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 queued/read (0 when the FIFO is empty/full or the stream is not running), and `tuh_audio_read_available()` / `tuh_audio_write_available()` report the FIFO occupancy in frames. `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/src/class/audio/audio_host.c b/src/class/audio/audio_host.c index 2a8df9075..98822b9c0 100644 --- a/src/class/audio/audio_host.c +++ b/src/class/audio/audio_host.c @@ -325,6 +325,8 @@ 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); + // 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. @@ -335,7 +337,9 @@ static void audioh_stream_capture_xfer(tuh_audio_stream_t *s) { TU_VERIFY(usbh_edpt_claim(s->daddr, map->ep_addr), ); // one transfer in flight // ep_size is guaranteed <= CFG_TUH_AUDIO_EPIN_BUFSIZE by enumeration - TU_ASSERT(usbh_edpt_xfer(s->daddr, map->ep_addr, s->edpt.ep_buf, map->ep_size), ); + if (!usbh_edpt_xfer(s->daddr, map->ep_addr, s->edpt.ep_buf, map->ep_size)) { + audioh_stream_error(s, 0); + } } // Submit the next queued playback packet. Fractional frames per endpoint poll @@ -366,7 +370,10 @@ static void audioh_stream_playback_xfer(tuh_audio_stream_t *s) { tu_fifo_read_n(&s->edpt.ff, s->edpt.ep_buf, bytes); } - TU_ASSERT(usbh_edpt_xfer(s->daddr, map->ep_addr, s->edpt.ep_buf, bytes), ); + if (!usbh_edpt_xfer(s->daddr, map->ep_addr, s->edpt.ep_buf, bytes)) { + audioh_stream_error(s, 0); + return; + } s->rem_acc = next_rem_acc; } 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 f02c44095..e5056bc2c 100644 --- a/test/unit-test/test/host/audio/test_audio_host.c +++ b/test/unit-test/test/host/audio/test_audio_host.c @@ -123,6 +123,9 @@ bool usbh_edpt_xfer_with_callback(uint8_t dev_addr, uint8_t ep_addr, uint8_t *bu memcpy(edpt_xfer_data[edpt_xfer_count], buffer, TU_MIN(sizeof(edpt_xfer_data[0]), total_bytes)); edpt_xfer_count++; } + if (!edpt_xfer_result) { + edpt_busy = false; // match usbh_edpt_xfer() cleanup after HCD rejection + } return edpt_xfer_result; } @@ -749,6 +752,41 @@ void test_audio_host_reports_asynchronous_start_failures(void) { TEST_ASSERT_EQUAL_UINT8(3, err_cb_count); } +void test_audio_host_reports_capture_submission_failure(void) { + mount_descriptors(capture_fu_before_usb_output, sizeof(capture_fu_before_usb_output)); + TEST_ASSERT_TRUE(tuh_audio_configure(0, 0, 0)); + + edpt_xfer_result = false; + 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, 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); + + edpt_xfer_result = true; + TEST_ASSERT_TRUE(tuh_audio_start(0, 0)); +} + +void test_audio_host_reports_playback_submission_failure(void) { + mount_descriptors(playback_44100_max_packets_only, sizeof(playback_44100_max_packets_only)); + TEST_ASSERT_TRUE(tuh_audio_configure(0, 0, 0)); + + edpt_xfer_result = false; + 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); + + edpt_xfer_result = true; + TEST_ASSERT_TRUE(tuh_audio_start(0, 0)); +} + 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)); |
