diff --git a/host/class/uvc/usb_host_uvc/examples/basic_uvc_stream/main/basic_uvc_stream.c b/host/class/uvc/usb_host_uvc/examples/basic_uvc_stream/main/basic_uvc_stream.c index 1effa1c4..86ad66c1 100644 --- a/host/class/uvc/usb_host_uvc/examples/basic_uvc_stream/main/basic_uvc_stream.c +++ b/host/class/uvc/usb_host_uvc/examples/basic_uvc_stream/main/basic_uvc_stream.c @@ -3,7 +3,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#include #include #include @@ -11,15 +10,12 @@ #include #include #include + +#include #include "esp_system.h" #include "esp_log.h" #include "esp_err.h" -#include "esp_vfs_fat.h" -#include "sdmmc_cmd.h" -#include "driver/sdmmc_host.h" -#include "sd_pwr_ctrl_by_on_chip_ldo.h" - #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" @@ -27,29 +23,31 @@ #include "usb/usb_host.h" #include "usb/uvc_host.h" -#include "esp_private/uvc_stream.h" #define EXAMPLE_USB_HOST_PRIORITY (15) -#define EXAMPLE_USB_DEVICE_VID (0x2207) -#define EXAMPLE_USB_DEVICE_PID (0x0018) // Customer's dual camera +#define EXAMPLE_USB_DEVICE_VID (UVC_HOST_ANY_VID) +#define EXAMPLE_USB_DEVICE_PID (UVC_HOST_ANY_PID) +#if CONFIG_SPIRAM #define EXAMPLE_FRAME_COUNT (3) +#else +#define EXAMPLE_FRAME_COUNT (2) +#endif #define EXAMPLE_STREAM_FPS (15) -#define EXAMPLE_RECORDING_LENGTH_S (5) -#define EXAMPLE_NUMBER_OF_STREAMS (2) - -#define MOUNT_POINT "/sdcard" +#define EXAMPLE_RECORDING_LENGTH_S (5) // The stream(s) is enabled, run for EXAMPLE_RECORDING_LENGTH_S and then stopped +#define EXAMPLE_NUMBER_OF_STREAMS (2) // This example shows how to control multiple UVC streams. Set this to 1 if you camera offers only 1 stream +#define EXAMPLE_USE_SDCARD (0) // SD card on P4 evaluation board will be initialized static const char *TAG = "UVC example"; -static SemaphoreHandle_t device_disconnected_sem; static QueueHandle_t rx_frames_queue[EXAMPLE_NUMBER_OF_STREAMS]; +static bool dev_connected = false; bool frame_callback(const uvc_host_frame_t *frame, void *user_ctx) { assert(frame); assert(user_ctx); QueueHandle_t frame_q = *((QueueHandle_t *)user_ctx); - // ESP_LOGI(TAG, "Frame callback! data len: %d", frame->data_len); - BaseType_t result = xQueueSend(frame_q, &frame, 0); + ESP_LOGD(TAG, "Frame callback! data len: %d", frame->data_len); + BaseType_t result = xQueueSendToBack(frame_q, &frame, 0); if (pdPASS != result) { ESP_LOGW(TAG, "Queue full, losing frame"); // This should never happen. We allocated queue with the same size as EXAMPLE_FRAME_COUNT return true; // We will not process this frame, return it immediately @@ -57,36 +55,29 @@ bool frame_callback(const uvc_host_frame_t *frame, void *user_ctx) return false; // We only passed the frame to Queue, so we must return false and call uvc_host_frame_return() later } -/** - * @brief Device event callback - * - * Apart from handling device disconnection it doesn't do anything useful - * - * @param[in] event Device event type and data - * @param[in] user_ctx Argument we passed to the device open function - */ -static void handle_event(const uvc_host_stream_event_data_t *event, void *user_ctx) +static void stream_callback(const uvc_host_stream_event_data_t *event, void *user_ctx) { switch (event->type) { case UVC_HOST_TRANSFER_ERROR: - ESP_LOGE(TAG, "USB error has occurred, err_no = %i", event->data.error); + ESP_LOGE(TAG, "USB error has occurred, err_no = %i", event->transfer_error.error); break; case UVC_HOST_DEVICE_DISCONNECTED: ESP_LOGI(TAG, "Device suddenly disconnected"); - ESP_ERROR_CHECK(uvc_host_stream_close(event->data.stream_hdl)); - xSemaphoreGive(device_disconnected_sem); + dev_connected = false; + ESP_ERROR_CHECK(uvc_host_stream_close(event->device_disconnected.stream_hdl)); + break; + case UVC_HOST_FRAME_BUFFER_OVERFLOW: + ESP_LOGW(TAG, "Frame buffer overflow"); + break; + case UVC_HOST_FRAME_BUFFER_UNDERFLOW: + ESP_LOGW(TAG, "Frame buffer underflow"); break; default: - ESP_LOGW(TAG, "Unsupported event: %i", event->type); + abort(); break; } } -/** - * @brief USB Host library handling task - * - * @param arg Unused - */ static void usb_lib_task(void *arg) { while (1) { @@ -94,7 +85,7 @@ static void usb_lib_task(void *arg) uint32_t event_flags; usb_host_lib_handle_events(portMAX_DELAY, &event_flags); if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { - ESP_ERROR_CHECK(usb_host_device_free_all()); + usb_host_device_free_all(); } if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { ESP_LOGI(TAG, "USB: All devices freed"); @@ -121,20 +112,20 @@ static void frame_handling_task(void *arg) continue; } //uvc_host_desc_print(uvc_stream); + dev_connected = true; ESP_LOGI(TAG, "Device 0x%04X:0x%04X-%d OPENED!", stream_config->usb.vid, stream_config->usb.pid, uvc_index); vTaskDelay(pdMS_TO_TICKS(100)); unsigned count = 0; - // This is the main processing loop. It enables the stream for 2 seconds and then closes it - uvc_host_stream_start(uvc_stream); + // This is the main processing loop. It enables the stream for EXAMPLE_RECORDING_LENGTH_S seconds and then closes it while (true) { ESP_LOGI(TAG, "Stream %d start. Iteration %u", uvc_index, count); count++; + uvc_host_stream_start(uvc_stream); TickType_t timeout_ticks = pdMS_TO_TICKS(EXAMPLE_RECORDING_LENGTH_S * 1000); - TimeOut_t connection_timeout; - vTaskSetTimeOutState(&connection_timeout); + TimeOut_t stream_timeout; + vTaskSetTimeOutState(&stream_timeout); - uvc_host_stream_unpause(uvc_stream); do { uvc_host_frame_t *frame; if (xQueueReceive(frame_q, &frame, pdMS_TO_TICKS(1000)) == pdPASS) { @@ -145,57 +136,78 @@ static void frame_handling_task(void *arg) uvc_host_frame_return(uvc_stream, frame); } else { ESP_LOGW(TAG, "Stream %d: Frame not received on time", uvc_index); + break; } - } while (xTaskCheckForTimeOut(&connection_timeout, &timeout_ticks) == pdFALSE); - ESP_LOGI(TAG, "Stream %d stop", uvc_index); - uvc_host_stream_pause(uvc_stream); - - vTaskDelay(pdMS_TO_TICKS(2000)); + } while (xTaskCheckForTimeOut(&stream_timeout, &timeout_ticks) == pdFALSE); + + if (dev_connected) { + // Stop and wait 2 seconds + ESP_LOGI(TAG, "Stream %d stop", uvc_index); + uvc_host_stream_stop(uvc_stream); + vTaskDelay(pdMS_TO_TICKS(2000)); + } + if (!dev_connected) { + ESP_LOGI(TAG, "device disconnected, breaking"); + break; + } } - uvc_host_stream_stop(uvc_stream); - uvc_host_stream_close(uvc_stream); - - // We are done. Wait for device disconnection and start over - ESP_LOGI(TAG, "Example finished successfully! You can reconnect the device to run again."); - xSemaphoreTake(device_disconnected_sem, portMAX_DELAY); } } static const uvc_host_stream_config_t stream_mjpeg_config = { - .event_cb = handle_event, + .event_cb = stream_callback, .frame_cb = frame_callback, .user_ctx = &rx_frames_queue[0], - .usb.vid = EXAMPLE_USB_DEVICE_VID, - .usb.pid = EXAMPLE_USB_DEVICE_PID, - .usb.uvc_stream_index = 0, - .vs_format.h_res = 720, - .vs_format.v_res = 1280, - .vs_format.fps = 15, - .vs_format.format = UVC_VS_FORMAT_MJPEG, - .advanced.number_of_frame_buffers = EXAMPLE_FRAME_COUNT, - .advanced.frame_size = 0, - .advanced.number_of_urbs = 6, - .advanced.urb_size = 20 * 1024, + .usb = { + .vid = EXAMPLE_USB_DEVICE_VID, + .pid = EXAMPLE_USB_DEVICE_PID, + .uvc_stream_index = 0, + }, + .vs_format = { + .h_res = 720, + .v_res = 1280, + .fps = 15, + .format = UVC_VS_FORMAT_MJPEG, + }, + .advanced = { + .number_of_frame_buffers = EXAMPLE_FRAME_COUNT, + .frame_size = 0, + .number_of_urbs = 4, + .urb_size = 10 * 1024, + }, }; +#if EXAMPLE_NUMBER_OF_STREAMS > 1 static const uvc_host_stream_config_t stream_h265_config = { - .event_cb = handle_event, + .event_cb = stream_callback, .frame_cb = frame_callback, .user_ctx = &rx_frames_queue[1], - .usb.vid = EXAMPLE_USB_DEVICE_VID, - .usb.pid = EXAMPLE_USB_DEVICE_PID, // Customer's device - .usb.uvc_stream_index = 1, - .vs_format.h_res = 1280, - .vs_format.v_res = 720, - .vs_format.fps = 15, - .vs_format.format = UVC_VS_FORMAT_H265, - .advanced.number_of_frame_buffers = EXAMPLE_FRAME_COUNT, - .advanced.frame_size = 0, - .advanced.number_of_urbs = 6, - .advanced.urb_size = 20 * 1024, + .usb = { + .vid = EXAMPLE_USB_DEVICE_VID, + .pid = EXAMPLE_USB_DEVICE_PID, + .uvc_stream_index = 1, + }, + .vs_format = { + .h_res = 1280, + .v_res = 720, + .fps = 15, + .format = UVC_VS_FORMAT_H265, + }, + .advanced = { + .number_of_frame_buffers = EXAMPLE_FRAME_COUNT, + .frame_size = 0, + .number_of_urbs = 4, + .urb_size = 10 * 1024, + }, }; +#endif // EXAMPLE_NUMBER_OF_STREAMS > 1 -/* +#if EXAMPLE_USE_SDCARD +#define MOUNT_POINT "/sdcard" +#include "esp_vfs_fat.h" +#include "sdmmc_cmd.h" +#include "driver/sdmmc_host.h" +#include "sd_pwr_ctrl_by_on_chip_ldo.h" void app_init_sdcard(void) { esp_err_t ret; @@ -265,23 +277,22 @@ void app_init_sdcard(void) } return; } - //esp_vfs_fat_sdcard_format(mount_point, card); } -*/ +#endif // EXAMPLE_USE_SDCARD /** * @brief Main application */ void app_main(void) { - device_disconnected_sem = xSemaphoreCreateBinary(); for (int i = 0; i < EXAMPLE_NUMBER_OF_STREAMS; i++) { rx_frames_queue[i] = xQueueCreate(EXAMPLE_FRAME_COUNT, sizeof(uvc_host_frame_t *)); assert(rx_frames_queue[i]); } - assert(device_disconnected_sem); - //app_init_sdcard(); // Uncomment this if you want to init the SD card +#if EXAMPLE_USE_SDCARD + app_init_sdcard(); +#endif // EXAMPLE_USE_SDCARD // Install USB Host driver. Should only be called once in entire application ESP_LOGI(TAG, "Installing USB Host"); @@ -306,7 +317,10 @@ void app_main(void) task_created = xTaskCreatePinnedToCore(frame_handling_task, "mjpeg_handling", 4096, (void *)&stream_mjpeg_config, EXAMPLE_USB_HOST_PRIORITY - 2, NULL, tskNO_AFFINITY); assert(task_created == pdTRUE); + +#if EXAMPLE_NUMBER_OF_STREAMS > 1 vTaskDelay(pdMS_TO_TICKS(1000)); task_created = xTaskCreatePinnedToCore(frame_handling_task, "h265_handling", 4096, (void *)&stream_h265_config, EXAMPLE_USB_HOST_PRIORITY - 3, NULL, tskNO_AFFINITY); assert(task_created == pdTRUE); +#endif // EXAMPLE_USE_SDCARD } diff --git a/host/class/uvc/usb_host_uvc/examples/basic_uvc_stream/sdkconfig.defaults b/host/class/uvc/usb_host_uvc/examples/basic_uvc_stream/sdkconfig.defaults index fad12967..e7f51bad 100644 --- a/host/class/uvc/usb_host_uvc/examples/basic_uvc_stream/sdkconfig.defaults +++ b/host/class/uvc/usb_host_uvc/examples/basic_uvc_stream/sdkconfig.defaults @@ -1,15 +1,15 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration -# -CONFIG_IDF_TARGET="esp32p4" -CONFIG_BOOTLOADER_LOG_COLORS=y -CONFIG_ESP32P4_REV_MIN_0=y -CONFIG_RTC_CLK_SRC_EXT_CRYS=y -CONFIG_RTC_CLK_CAL_CYCLES=1024 -CONFIG_SPIRAM=y -CONFIG_SPIRAM_SPEED_200M=y -CONFIG_LOG_COLORS=y -CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=2048 -CONFIG_USB_HOST_HW_BUFFER_BIAS_IN=y -CONFIG_USB_HOST_HUBS_SUPPORTED=y -CONFIG_IDF_EXPERIMENTAL_FEATURES=y +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration +# +CONFIG_IDF_TARGET="esp32p4" +CONFIG_BOOTLOADER_LOG_COLORS=y +CONFIG_ESP32P4_REV_MIN_0=y +CONFIG_RTC_CLK_SRC_EXT_CRYS=y +CONFIG_RTC_CLK_CAL_CYCLES=1024 +CONFIG_SPIRAM=y +CONFIG_SPIRAM_SPEED_200M=y +CONFIG_LOG_COLORS=y +CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=4096 +CONFIG_USB_HOST_HW_BUFFER_BIAS_IN=y +CONFIG_USB_HOST_HUBS_SUPPORTED=y +CONFIG_IDF_EXPERIMENTAL_FEATURES=y diff --git a/host/class/uvc/usb_host_uvc/examples/camera_display/main/camera_display.c b/host/class/uvc/usb_host_uvc/examples/camera_display/main/camera_display.c index 6298d60e..91793665 100644 --- a/host/class/uvc/usb_host_uvc/examples/camera_display/main/camera_display.c +++ b/host/class/uvc/usb_host_uvc/examples/camera_display/main/camera_display.c @@ -53,7 +53,7 @@ void stream_callback(const uvc_host_stream_event_data_t *event, void *user_ctx) break; case UVC_HOST_DEVICE_DISCONNECTED: ESP_LOGW(TAG, "Device disconnected"); - ESP_ERROR_CHECK(uvc_host_stream_close(event->data.stream_hdl)); + ESP_ERROR_CHECK(uvc_host_stream_close(event->device_disconnected.stream_hdl)); xSemaphoreGive(device_disconnected_sem); break; case UVC_HOST_FRAME_BUFFER_OVERFLOW: @@ -155,12 +155,11 @@ static void usb_lib_task(void *arg) uint32_t event_flags; usb_host_lib_handle_events(portMAX_DELAY, &event_flags); if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { - ESP_ERROR_CHECK(usb_host_device_free_all()); + usb_host_device_free_all(); } if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { ESP_LOGI(TAG, "USB: All devices freed"); // Continue handling USB events to allow device reconnection - // The only way this task can be stopped is by calling bsp_usb_host_stop() } } } diff --git a/host/class/uvc/usb_host_uvc/include/usb/uvc_host.h b/host/class/uvc/usb_host_uvc/include/usb/uvc_host.h index 66e0e014..685f3eb2 100644 --- a/host/class/uvc/usb_host_uvc/include/usb/uvc_host.h +++ b/host/class/uvc/usb_host_uvc/include/usb/uvc_host.h @@ -51,9 +51,17 @@ enum uvc_host_dev_event { typedef struct { enum uvc_host_dev_event type; union { - esp_err_t error; //!< Error code from USB Host - uvc_host_stream_hdl_t stream_hdl; //!< Disconnection event - } data; + struct { + esp_err_t error; //!< Error code from USB Host + } transfer_error; // UVC_HOST_TRANSFER_ERROR + struct { + uvc_host_stream_hdl_t stream_hdl; //!< Disconnection event + } device_disconnected; // UVC_HOST_DEVICE_DISCONNECTED + struct { + } frame_overflow; // UVC_HOST_FRAME_BUFFER_OVERFLOW + struct { + } frame_underflow; // UVC_HOST_FRAME_BUFFER_UNDERFLOW + }; } uvc_host_stream_event_data_t; /** @@ -155,6 +163,21 @@ esp_err_t uvc_host_install(const uvc_host_driver_config_t *driver_config); */ esp_err_t uvc_host_uninstall(void); +/** + * @brief Handle UVC HOST events + * + * If UVC Host install was called with create_background_task=false configuration, application needs to handle USB Host events. + * Do not call this function if UVC host install was called with create_background_task=true configuration + * + * @param[in] timeout Timeout in FreeRTOS tick + * @return + * - ESP_OK: All events handled + * - ESP_ERR_INVALID_STATE: UVC driver not installed + * - ESP_ERR_TIMEOUT: No events handled within the timeout + * - ESP_FAIL: Event handling finished, driver uninstalled. You do not have to call this function further + */ +esp_err_t uvc_host_handle_events(uint32_t timeout); + /** * @brief Open UVC compliant device * diff --git a/host/class/uvc/usb_host_uvc/uvc_bulk.c b/host/class/uvc/usb_host_uvc/uvc_bulk.c index cee71dcd..9f8212f5 100644 --- a/host/class/uvc/usb_host_uvc/uvc_bulk.c +++ b/host/class/uvc/usb_host_uvc/uvc_bulk.c @@ -9,6 +9,7 @@ #include "esp_log.h" +#include "uvc_stream.h" // For uvc_host_stream_pause() #include "uvc_types_priv.h" #include "uvc_check_priv.h" #include "uvc_frame_priv.h" @@ -42,18 +43,6 @@ void bulk_transfer_callback(usb_transfer_t *transfer) ESP_LOGD(TAG, "%s", __FUNCTION__); uvc_stream_t *uvc_stream = (uvc_stream_t *)transfer->context; - if (!UVC_ATOMIC_LOAD(uvc_stream->dynamic.streaming)) { - return; // If the streaming was turned off, we don't have to do anything - } - - // In BULK implementation, 'payload' is a constant pointer to constant data, - // meaning both the pointer and the data it points to cannot be changed. - // This contrasts with the ISOC implementation, where 'payload' is a variable - // pointer and is increased after every ISOC packet processing - const uint8_t *const payload = transfer->data_buffer; - const uint8_t *payload_data = payload; - size_t payload_data_len = transfer->actual_num_bytes; - // Check USB transfer status switch (transfer->status) { case USB_TRANSFER_STATUS_COMPLETED: @@ -64,21 +53,27 @@ void bulk_transfer_callback(usb_transfer_t *transfer) case USB_TRANSFER_STATUS_OVERFLOW: case USB_TRANSFER_STATUS_STALL: // On Bulk errors we stop the stream - //@todo not tested yet //@todo Stall, error and overflow errors should be propagated to the user - UVC_ENTER_CRITICAL(); - uvc_stream->dynamic.streaming = false; - uvc_host_frame_t *this_frame = uvc_stream->dynamic.current_frame; - uvc_stream->dynamic.current_frame = NULL; - UVC_EXIT_CRITICAL(); - uvc_host_frame_return(uvc_stream, this_frame); - return; // No need to process the rest + ESP_ERROR_CHECK(uvc_host_stream_pause(uvc_stream)); // This should never fail + break; case USB_TRANSFER_STATUS_TIMED_OUT: case USB_TRANSFER_STATUS_SKIPPED: // Should never happen to BULK transfer default: assert(false); } + if (!UVC_ATOMIC_LOAD(uvc_stream->dynamic.streaming)) { + return; // If the streaming was turned off, we don't have to do anything + } + + // In BULK implementation, 'payload' is a constant pointer to constant data, + // meaning both the pointer and the data it points to cannot be changed. + // This contrasts with the ISOC implementation, where 'payload' is a variable + // pointer and is increased after every ISOC packet processing + const uint8_t *const payload = transfer->data_buffer; + const uint8_t *payload_data = payload; + size_t payload_data_len = transfer->actual_num_bytes; + // Note for developers: // The order of SoF, Data, and EoF handling is intentional and represents a workaround for detecting EoF in the Bulk stream. // Normally, a complete Sample transfer includes two short packets: one marking the last data packet and another with the EoF header. diff --git a/host/class/uvc/usb_host_uvc/uvc_host.c b/host/class/uvc/usb_host_uvc/uvc_host.c index 9723f57c..9fc0852c 100644 --- a/host/class/uvc/usb_host_uvc/uvc_host.c +++ b/host/class/uvc/usb_host_uvc/uvc_host.c @@ -36,9 +36,10 @@ static const char *TAG = "uvc"; // UVC spinlock portMUX_TYPE uvc_lock = portMUX_INITIALIZER_UNLOCKED; -// UVC events -#define UVC_TEARDOWN BIT0 -#define UVC_TEARDOWN_COMPLETE BIT1 +// UVC driver status +#define UVC_STARTED BIT0 // UVC driver events handling started +#define UVC_TEARDOWN BIT1 // UVC is being uninstalled +#define UVC_TEARDOWN_COMPLETE BIT2 // UVC uninstall finished // Transfer callbacks static void ctrl_xfer_cb(usb_transfer_t *transfer); @@ -84,7 +85,7 @@ static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg if (uvc_stream->constant.stream_cb) { const uvc_host_stream_event_data_t disconn_event = { .type = UVC_HOST_DEVICE_DISCONNECTED, - .data.stream_hdl = (uvc_host_stream_hdl_t) uvc_stream, + .device_disconnected.stream_hdl = (uvc_host_stream_hdl_t) uvc_stream, }; uvc_stream->constant.stream_cb(&disconn_event, uvc_stream->constant.cb_arg); } @@ -98,32 +99,42 @@ static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg } } -//@todo revise this section according to MSC and HID drivers +esp_err_t uvc_host_handle_events(uint32_t timeout) +{ + static bool called = false; + uvc_host_driver_t *uvc_obj = UVC_ATOMIC_LOAD(p_uvc_host_driver); // Make local copy of the driver's handle + UVC_CHECK(uvc_obj, ESP_ERR_INVALID_STATE); + + // We use this static variable so we don't have to call FreeRTOS API in every handling call + if (!called) { + xEventGroupSetBits(uvc_obj->driver_status, UVC_STARTED); + called = true; + } + + ESP_LOGV(TAG, "USB UVC handling"); + esp_err_t ret = usb_host_client_handle_events(uvc_obj->usb_client_hdl, portMAX_DELAY); + EventBits_t events = xEventGroupGetBits(uvc_obj->driver_status); + if (events & UVC_TEARDOWN) { + xEventGroupSetBits(uvc_obj->driver_status, UVC_TEARDOWN_COMPLETE); + ret = ESP_FAIL; + } + return ret; +} + /** * @brief UVC driver handling task * - * USB host client registration and deregistration is handled here. - * * @param[in] arg User's argument. Handle of a task that started this task. */ static void uvc_client_task(void *arg) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - uvc_host_driver_t *uvc_obj = UVC_ATOMIC_LOAD(p_uvc_host_driver); // Make local copy of the driver's handle - assert(uvc_obj->usb_client_hdl); + ESP_LOGD(TAG, "USB UVC handling start"); // Start handling client's events - while (1) { - usb_host_client_handle_events(uvc_obj->usb_client_hdl, portMAX_DELAY); - EventBits_t events = xEventGroupGetBits(uvc_obj->driver_status); - if (events & UVC_TEARDOWN) { - break; - } + while (uvc_host_handle_events(portMAX_DELAY) != ESP_FAIL) { } - - ESP_LOGD(TAG, "Deregistering client"); - ESP_ERROR_CHECK(usb_host_client_deregister(uvc_obj->usb_client_hdl)); - xEventGroupSetBits(uvc_obj->driver_status, UVC_TEARDOWN_COMPLETE); + ESP_LOGD(TAG, "USB UVC handling stop"); vTaskDelete(NULL); } @@ -386,11 +397,14 @@ esp_err_t uvc_host_install(const uvc_host_driver_config_t *driver_config) usb_transfer_t *ctrl_xfer = NULL; usb_host_transfer_alloc(64, 0, &ctrl_xfer); // Worst case HS MPS TaskHandle_t driver_task_h = NULL; - xTaskCreatePinnedToCore( - uvc_client_task, "USB-UVC", driver_config->driver_task_stack_size, NULL, - driver_config->driver_task_priority, &driver_task_h, driver_config->xCoreID); - if (uvc_obj == NULL || driver_task_h == NULL || driver_status == NULL || + if (driver_config->create_background_task) { + xTaskCreatePinnedToCore( + uvc_client_task, "USB-UVC", driver_config->driver_task_stack_size, NULL, + driver_config->driver_task_priority, &driver_task_h, driver_config->xCoreID); + } + + if (uvc_obj == NULL || (driver_task_h == NULL && driver_config->create_background_task) || driver_status == NULL || mutex == NULL || ctrl_mutex == NULL || ctrl_xfer == NULL || ctrl_sem == NULL) { ret = ESP_ERR_NO_MEM; goto err; @@ -426,7 +440,9 @@ esp_err_t uvc_host_install(const uvc_host_driver_config_t *driver_config) } // Everything OK: Start UVC-Driver task and return - xTaskNotifyGive(driver_task_h); + if (driver_task_h) { + xTaskNotifyGive(driver_task_h); + } return ESP_OK; client_err: @@ -475,10 +491,16 @@ esp_err_t uvc_host_uninstall() // Signal to UVC task to stop, unblock it and wait for its deletion xEventGroupSetBits(uvc_obj->driver_status, UVC_TEARDOWN); - usb_host_client_unblock(uvc_obj->usb_client_hdl); - ESP_GOTO_ON_FALSE( - xEventGroupWaitBits(uvc_obj->driver_status, UVC_TEARDOWN_COMPLETE, pdFALSE, pdFALSE, pdMS_TO_TICKS(100)), - ESP_ERR_NOT_FINISHED, unblock, TAG,); + EventBits_t driver_status = xEventGroupGetBits(uvc_obj->driver_status); + if (driver_status & UVC_STARTED) { + usb_host_client_unblock(uvc_obj->usb_client_hdl); + ESP_GOTO_ON_FALSE( + xEventGroupWaitBits(uvc_obj->driver_status, UVC_TEARDOWN_COMPLETE, pdFALSE, pdFALSE, pdMS_TO_TICKS(100)), + ESP_ERR_NOT_FINISHED, unblock, TAG,); + } + + ESP_LOGD(TAG, "Deregistering client"); + ESP_ERROR_CHECK(usb_host_client_deregister(uvc_obj->usb_client_hdl)); // Free remaining resources and return vEventGroupDelete(uvc_obj->driver_status); @@ -695,6 +717,9 @@ esp_err_t uvc_host_stream_stop(uvc_host_stream_hdl_t stream_hdl) ESP_RETURN_ON_ERROR(uvc_host_stream_pause(stream_hdl), TAG, "Could not pause the stream"); + //@todo this is not a clean solution + vTaskDelay(pdMS_TO_TICKS(50)); // Wait for all transfers to finish + if (uvc_stream->constant.bAlternateSetting != 0) { // if (is_isoc_stream) // ISOC streams are stopped by setting alternate interface 0 return uvc_set_interface(stream_hdl, false); @@ -722,8 +747,6 @@ esp_err_t uvc_host_stream_pause(uvc_host_stream_hdl_t stream_hdl) uvc_host_frame_return(uvc_stream, current_frame); } - //@todo this is not a clean solution - vTaskDelay(pdMS_TO_TICKS(50)); // Wait for all transfers to finish return ESP_OK; } diff --git a/host/class/uvc/usb_host_uvc/uvc_isoc.c b/host/class/uvc/usb_host_uvc/uvc_isoc.c index 63da7916..78d9df16 100644 --- a/host/class/uvc/usb_host_uvc/uvc_isoc.c +++ b/host/class/uvc/usb_host_uvc/uvc_isoc.c @@ -9,6 +9,7 @@ #include "esp_log.h" +#include "uvc_stream.h" // For uvc_host_stream_pause() #include "uvc_types_priv.h" #include "uvc_check_priv.h" #include "uvc_frame_priv.h" @@ -43,6 +44,12 @@ void isoc_transfer_callback(usb_transfer_t *transfer) ESP_LOGD(TAG, "%s", __FUNCTION__); uvc_stream_t *uvc_stream = (uvc_stream_t *)transfer->context; + // USB_TRANSFER_STATUS_NO_DEVICE is set in transfer->status. + // Other error codes are saved in status of each ISOC packet descriptor + if (transfer->status == USB_TRANSFER_STATUS_NO_DEVICE) { + ESP_ERROR_CHECK(uvc_host_stream_pause(uvc_stream)); // This should never fail + } + if (!UVC_ATOMIC_LOAD(uvc_stream->dynamic.streaming)) { return; // If the streaming was turned off, we don't have to do anything } @@ -57,12 +64,7 @@ void isoc_transfer_callback(usb_transfer_t *transfer) break; case USB_TRANSFER_STATUS_NO_DEVICE: case USB_TRANSFER_STATUS_CANCELED: - UVC_ENTER_CRITICAL(); - uvc_stream->dynamic.streaming = false; - uvc_host_frame_t *this_frame = uvc_stream->dynamic.current_frame; - uvc_stream->dynamic.current_frame = NULL; - UVC_EXIT_CRITICAL(); - uvc_host_frame_return(uvc_stream, this_frame); + ESP_ERROR_CHECK(uvc_host_stream_pause(uvc_stream)); // This should never fail return; // No need to process the rest case USB_TRANSFER_STATUS_ERROR: case USB_TRANSFER_STATUS_OVERFLOW: