From 3fa43ce9c864a2e2f4cacfb70f2eff67f41917b0 Mon Sep 17 00:00:00 2001 From: Li Junru Date: Fri, 3 Jan 2025 20:27:35 +0800 Subject: [PATCH 1/2] feat(usb_host_uvc): support dual camera with hub --- .../uvc/usb_host_uvc/include/usb/uvc_host.h | 20 +++ .../private_include/uvc_descriptors_priv.h | 5 + .../uvc/usb_host_uvc/uvc_descriptor_parsing.c | 16 +++ .../usb_host_uvc/uvc_descriptor_printing.c | 2 +- host/class/uvc/usb_host_uvc/uvc_host.c | 127 +++++++++++++++--- 5 files changed, 152 insertions(+), 18 deletions(-) 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 195b7ffe..7a828a3f 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 @@ -14,6 +14,7 @@ // Use this macros for opening a UVC stream with any VID or PID #define UVC_HOST_ANY_VID (0) #define UVC_HOST_ANY_PID (0) +#define UVC_HOST_ANY_DEV_ADDR (0) #ifdef __cplusplus extern "C" { @@ -21,6 +22,22 @@ extern "C" { typedef struct uvc_host_stream_s *uvc_host_stream_hdl_t; +enum uvc_host_driver_event { + UVC_HOST_DRIVER_EVENT_DEVICE_CONNECTED = 0x0, +}; + +typedef struct { + enum uvc_host_driver_event type; + union { + struct { + uint8_t dev_addr; + uint8_t iface_num; //!< Disconnection event + } device_connected; // UVC_HOST_DEVICE_DISCONNECTED + }; +} uvc_host_driver_event_data_t; + +typedef void (*uvc_host_driver_event_callback_t)(const uvc_host_driver_event_data_t *event, void *user_ctx); + /** * @brief Configuration structure of USB Host UVC driver */ @@ -30,6 +47,8 @@ typedef struct { int xCoreID; /**< Core affinity of the driver's task */ bool create_background_task; /**< When set to true, background task handling usb events is created. Otherwise user has to periodically call uvc_host_handle_events function */ + uvc_host_driver_event_callback_t event_cb; /**< Callback function to handle events */ + void *user_ctx; } uvc_host_driver_config_t; /** @@ -122,6 +141,7 @@ typedef struct { uvc_host_frame_callback_t frame_cb; /**< Stream's frame callback function */ void *user_ctx; /**< User's argument that will be passed to the callbacks */ struct { + uint8_t dev_addr; /**< USB address of device. Set to 0 for any. */ uint16_t vid; /**< Device's Vendor ID. Set to 0 for any */ uint16_t pid; /**< Device's Product ID. Set to 0 for any */ uint8_t uvc_stream_index; /**< Index of UVC function you want to use. Set to 0 to use first available UVC function */ diff --git a/host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h b/host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h index b110ff56..676f25d6 100644 --- a/host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h +++ b/host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h @@ -15,6 +15,7 @@ #define UVC_DESC_FPS_TO_DWFRAMEINTERVAL(fps) (((fps) != 0) ? 10000000.0f / (fps) : 0) #define UVC_DESC_DWFRAMEINTERVAL_TO_FPS(dwFrameInterval) (((dwFrameInterval) != 0) ? 10000000.0f / ((float)(dwFrameInterval)) : 0) + #ifdef __cplusplus extern "C" { #endif @@ -75,6 +76,10 @@ esp_err_t uvc_desc_get_frame_format_by_format( const uvc_format_desc_t **format_desc_ret, const uvc_frame_desc_t **frame_desc_ret); +bool uvc_desc_is_uvc_device(const usb_config_desc_t *cfg_desc); + +void uvc_print_desc(const usb_standard_desc_t *_desc); + #ifdef __cplusplus } #endif diff --git a/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c b/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c index 2f891609..3ffb8c52 100644 --- a/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c +++ b/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c @@ -375,3 +375,19 @@ esp_err_t uvc_desc_get_streaming_interface_num( } return ret; } + +bool uvc_desc_is_uvc_device(const usb_config_desc_t *cfg_desc) +{ + assert(cfg_desc); + int offset = 0; + int total_len = cfg_desc->wTotalLength; + + const usb_standard_desc_t *current_desc = (const usb_standard_desc_t *)cfg_desc; + while ((current_desc = usb_parse_next_descriptor_of_type((const usb_standard_desc_t *)current_desc, total_len, USB_B_DESCRIPTOR_TYPE_INTERFACE, &offset))) { + const usb_intf_desc_t *iface_desc = (const usb_intf_desc_t *)current_desc; + if (USB_CLASS_VIDEO == iface_desc->bInterfaceClass) { + return true; + } + } + return false; +} diff --git a/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c b/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c index e0d54a3d..bd21baaa 100644 --- a/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c +++ b/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c @@ -365,7 +365,7 @@ static void print_class_specific_desc(const usb_standard_desc_t *_desc) * * @param[in] _desc UVC specific descriptor */ -static void uvc_print_desc(const usb_standard_desc_t *_desc) +void uvc_print_desc(const usb_standard_desc_t *_desc) { switch (_desc->bDescriptorType) { case UVC_CS_INTERFACE: diff --git a/host/class/uvc/usb_host_uvc/uvc_host.c b/host/class/uvc/usb_host_uvc/uvc_host.c index c9b71303..f70ecc1c 100644 --- a/host/class/uvc/usb_host_uvc/uvc_host.c +++ b/host/class/uvc/usb_host_uvc/uvc_host.c @@ -48,16 +48,90 @@ void bulk_transfer_callback(usb_transfer_t *transfer); // UVC driver object typedef struct { - usb_host_client_handle_t usb_client_hdl; /*!< USB Host handle reused for all UVC devices in the system */ - SemaphoreHandle_t open_close_mutex; /*!< Protects list of opened devices from concurrent access */ - EventGroupHandle_t driver_status; /*!< Holds status of the driver */ - usb_transfer_t *ctrl_transfer; /*!< CTRL (endpoint 0) transfer */ - SemaphoreHandle_t ctrl_mutex; /*!< CTRL mutex */ + usb_host_client_handle_t usb_client_hdl; /*!< USB Host handle reused for all UVC devices in the system */ + SemaphoreHandle_t open_close_mutex; /*!< Protects list of opened devices from concurrent access */ + EventGroupHandle_t driver_status; /*!< Holds status of the driver */ + usb_transfer_t *ctrl_transfer; /*!< CTRL (endpoint 0) transfer */ + SemaphoreHandle_t ctrl_mutex; /*!< CTRL mutex */ + uvc_host_driver_event_callback_t user_cb; /*!< Callback function to handle events */ + void *user_ctx; SLIST_HEAD(list_dev, uvc_host_stream_s) uvc_stream_list; /*!< List of open streams */ } uvc_host_driver_t; static uvc_host_driver_t *p_uvc_host_driver = NULL; +static esp_err_t uvc_host_interface_check(uint8_t addr, const usb_config_desc_t *config_desc) +{ + assert(config_desc); + size_t total_length = config_desc->wTotalLength; + int iface_offset = 0; + bool is_uvc_interface = false; + + // Get first Interface descriptor + // Check every uac stream interface + const usb_standard_desc_t *current_desc = (const usb_standard_desc_t *)config_desc; + while ((current_desc = usb_parse_next_descriptor_of_type((const usb_standard_desc_t *)current_desc, total_length, USB_B_DESCRIPTOR_TYPE_INTERFACE, &iface_offset))) { + const usb_intf_desc_t *iface_desc = (const usb_intf_desc_t *)current_desc; + if (USB_CLASS_VIDEO == iface_desc->bInterfaceClass && UVC_SC_VIDEOSTREAMING == iface_desc->bInterfaceSubClass) { + // notify user about the connected Interfaces + is_uvc_interface = true; + + if (p_uvc_host_driver->user_cb) { + const uvc_host_driver_event_data_t conn_event = { + .type = UVC_HOST_DRIVER_EVENT_DEVICE_CONNECTED, + .device_connected.dev_addr = addr, + .device_connected.iface_num = iface_desc->bInterfaceNumber, + }; + p_uvc_host_driver->user_cb(&conn_event, p_uvc_host_driver->user_ctx); + } + + const usb_intf_desc_t *iface_alt_desc = GET_NEXT_INTERFACE_DESC(iface_desc, total_length, iface_offset); + // Skip all alternate settings belonging to the current interface + while (iface_alt_desc != NULL) { + // Check if the alternate setting is for the same interface + if (iface_alt_desc->bInterfaceNumber != iface_desc->bInterfaceNumber) { + break; + } + iface_alt_desc = GET_NEXT_INTERFACE_DESC(iface_alt_desc, total_length, iface_offset); + } + } + } + + return is_uvc_interface ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +/** + * @brief Handler for USB device connected event + * + * @param[in] addr USB device physical address + * @return esp_err_t + */ +static esp_err_t uvc_host_device_connected(uint8_t addr) +{ + bool is_uvc_device = false; + usb_device_handle_t dev_hdl; + const usb_config_desc_t *config_desc = NULL; + + if (usb_host_device_open(p_uvc_host_driver->usb_client_hdl, addr, &dev_hdl) == ESP_OK) { + if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) == ESP_OK) { + is_uvc_device = uvc_desc_is_uvc_device(config_desc); + } + ESP_RETURN_ON_ERROR(usb_host_device_close(p_uvc_host_driver->usb_client_hdl, dev_hdl), TAG, "Unable to close USB device"); + } + + // Create UAC interfaces list in RAM, connected to the particular USB dev + if (is_uvc_device) { + // TODO: add config to control it + usb_print_config_descriptor(config_desc, &uvc_print_desc); + // Create Interfaces list for a possibility to claim Interface + ESP_RETURN_ON_ERROR(uvc_host_interface_check(addr, config_desc), TAG, "uvc stream interface not found"); + } else { + ESP_LOGW(TAG, "USB device with addr(%d) is not UVC device", addr); + } + + return is_uvc_device ? ESP_OK : ESP_ERR_NOT_FOUND; +} + /** * @brief USB Host Client event callback * @@ -71,6 +145,7 @@ static void usb_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg switch (event_msg->event) { case USB_HOST_CLIENT_EVENT_NEW_DEV: ESP_LOGD(TAG, "New device connected"); + uvc_host_device_connected(event_msg->new_dev.address); break; case USB_HOST_CLIENT_EVENT_DEV_GONE: { ESP_LOGD(TAG, "Device suddenly disconnected"); @@ -247,10 +322,12 @@ static void uvc_device_remove(uvc_stream_t *uvc_stream) * @brief Open USB device with requested VID/PID * * This function has two regular return paths: + * TODO: dev_addr * 1. USB device with matching VID/PID is already opened by this driver: allocate new UVC device on top of the already opened USB device. * 2. USB device with matching VID/PID is NOT opened by this driver yet: poll USB connected devices until it is found. * * @note This function will block for timeout_ticks, if the device is not enumerated at the moment of calling this function. + * @param[in] dev_addr Device address * @param[in] vid Vendor ID * @param[in] pid Product ID * @param[in] timeout_ticks Connection timeout in FreeRTOS ticks @@ -259,7 +336,7 @@ static void uvc_device_remove(uvc_stream_t *uvc_stream) * - ESP_OK: Success - device opened * - ESP_ERR_NOT_FOUND: Device not found in given timeout */ -static esp_err_t uvc_find_and_open_usb_device(uint16_t vid, uint16_t pid, TickType_t timeout_ticks, uvc_stream_t **dev) +static esp_err_t uvc_find_and_open_usb_device(uint8_t dev_addr, uint16_t vid, uint16_t pid, TickType_t timeout_ticks, uvc_stream_t **dev) { assert(p_uvc_host_driver); assert(dev); @@ -274,11 +351,15 @@ static esp_err_t uvc_find_and_open_usb_device(uint16_t vid, uint16_t pid, TickTy uvc_stream_t *uvc_stream; SLIST_FOREACH(uvc_stream, &p_uvc_host_driver->uvc_stream_list, list_entry) { const usb_device_desc_t *device_desc; + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(uvc_stream->constant.dev_hdl, &dev_info)); ESP_ERROR_CHECK(usb_host_get_device_descriptor(uvc_stream->constant.dev_hdl, &device_desc)); if ((vid == device_desc->idVendor || vid == UVC_HOST_ANY_VID) && - (pid == device_desc->idProduct || pid == UVC_HOST_ANY_PID)) { - // Return path 1: + (pid == device_desc->idProduct || pid == UVC_HOST_ANY_PID) && + (dev_addr == dev_info.dev_addr || dev_addr == UVC_HOST_ANY_DEV_ADDR)) { + // Return path 1: t (*dev)->constant.dev_hdl = uvc_stream->constant.dev_hdl; + printf("UVC device already opened by this driver\n"); return ESP_OK; } } @@ -297,18 +378,28 @@ static esp_err_t uvc_find_and_open_usb_device(uint16_t vid, uint16_t pid, TickTy // Go through device address list and find the one we are looking for for (int i = 0; i < num_of_devices; i++) { usb_device_handle_t current_device; + bool is_uvc_device = false; + const usb_config_desc_t *config_desc = NULL; + // Open USB device if (usb_host_device_open(p_uvc_host_driver->usb_client_hdl, dev_addr_list[i], ¤t_device) != ESP_OK) { continue; // In case we failed to open this device, continue with next one in the list } - assert(current_device); - const usb_device_desc_t *device_desc; - ESP_ERROR_CHECK(usb_host_get_device_descriptor(current_device, &device_desc)); - if ((vid == device_desc->idVendor || vid == UVC_HOST_ANY_VID) && - (pid == device_desc->idProduct || pid == UVC_HOST_ANY_PID)) { - // Return path 2: - (*dev)->constant.dev_hdl = current_device; - return ESP_OK; + // Skip non-UVC devices + if (usb_host_get_active_config_descriptor(current_device, &config_desc) == ESP_OK) { + is_uvc_device = uvc_desc_is_uvc_device(config_desc); + } + if (is_uvc_device) { + assert(current_device); + const usb_device_desc_t *device_desc; + ESP_ERROR_CHECK(usb_host_get_device_descriptor(current_device, &device_desc)); + if ((vid == device_desc->idVendor || vid == UVC_HOST_ANY_VID) && + (pid == device_desc->idProduct || pid == UVC_HOST_ANY_PID) && + (dev_addr == dev_addr_list[i] || dev_addr == UVC_HOST_ANY_DEV_ADDR)) { + // Return path 2: + (*dev)->constant.dev_hdl = current_device; + return ESP_OK; + } } usb_host_device_close(p_uvc_host_driver->usb_client_hdl, current_device); } @@ -431,6 +522,8 @@ esp_err_t uvc_host_install(const uvc_host_driver_config_t *driver_config) uvc_obj->ctrl_transfer->bEndpointAddress = 0; uvc_obj->ctrl_transfer->timeout_ms = 5000; uvc_obj->ctrl_transfer->callback = ctrl_xfer_cb; + uvc_obj->user_cb = driver_config->event_cb; + uvc_obj->user_ctx = driver_config->user_ctx; // Between 1st call of this function and following section, another task might try to install this driver: // Make sure that there is only one instance of this driver in the system @@ -530,7 +623,7 @@ esp_err_t uvc_host_stream_open(const uvc_host_stream_config_t *stream_config, in xSemaphoreTake(p_uvc_host_driver->open_close_mutex, portMAX_DELAY); // Find underlying USB device - ret = uvc_find_and_open_usb_device(stream_config->usb.vid, stream_config->usb.pid, timeout, &uvc_stream); + ret = uvc_find_and_open_usb_device(stream_config->usb.dev_addr, stream_config->usb.vid, stream_config->usb.pid, timeout, &uvc_stream); if (ESP_OK != ret) { goto not_found; } From 290d524cd9a3878bd1798038ee4e5a8b0aa6a81d Mon Sep 17 00:00:00 2001 From: Li Junru Date: Mon, 6 Jan 2025 20:55:01 +0800 Subject: [PATCH 2/2] feat(usb_host_uvc): support get frame list when device insert --- .../uvc/usb_host_uvc/include/usb/uvc_host.h | 58 +++++++++---- .../private_include/uvc_descriptors_priv.h | 43 ++++++++++ .../uvc/usb_host_uvc/uvc_descriptor_parsing.c | 82 +++++++++++++++++++ .../usb_host_uvc/uvc_descriptor_printing.c | 8 -- host/class/uvc/usb_host_uvc/uvc_host.c | 24 +++--- 5 files changed, 181 insertions(+), 34 deletions(-) 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 7a828a3f..630de283 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 @@ -26,16 +26,55 @@ enum uvc_host_driver_event { UVC_HOST_DRIVER_EVENT_DEVICE_CONNECTED = 0x0, }; +/** + * @brief Formats supported by this driver + */ +enum uvc_host_stream_format { + UVC_VS_FORMAT_UNDEFINED = 0, // Invalid format. Do not request this format from the camera. + UVC_VS_FORMAT_MJPEG, + UVC_VS_FORMAT_YUY2, + UVC_VS_FORMAT_H264, + UVC_VS_FORMAT_H265, +}; + +/** + * @brief Frame information + * + */ typedef struct { - enum uvc_host_driver_event type; + enum uvc_host_stream_format format; /**< Format of this frame buffer */ + unsigned h_res; /**< Horizontal resolution */ + unsigned v_res; /**< Vertical resolution */ + uint32_t default_interval; /**< Default frame interval */ + uint8_t interval_type; /**< 0: Continuous frame interval, 1..255: The number of discrete frame intervals supported (n) */ union { struct { - uint8_t dev_addr; - uint8_t iface_num; //!< Disconnection event - } device_connected; // UVC_HOST_DEVICE_DISCONNECTED + uint32_t interval_min; /**< Minimum frame interval */ + uint32_t interval_max; /**< Maximum frame interval */ + uint32_t interval_step; /**< Frame interval step */ + }; + uint32_t interval[3]; /**< We must put a fixed size here because of the union type. This is flexible size array though */ + }; +} uvc_host_frame_info_t; + +typedef struct { + enum uvc_host_driver_event type; /**< Event type */ + union { + struct { + uint8_t dev_addr; /**< Device address */ + uint8_t uvc_stream_index; /**< Index of UVC function you want to use. Set to 0 to use first available UVC function */ + uvc_host_frame_info_t *frame_info; /**< Frame information list, it points to the first element of the array, which resides in a contiguous block of memory.*/ + size_t frame_info_num; /**< Number of frame information list */ + } device_connected; /**< UVC_HOST_DEVICE_CONNECTED event */ }; } uvc_host_driver_event_data_t; +/** + * @brief USB Host UVC driver event callback function + * + * @param[out] event Event structure + * @param[out] user_ctx User's argument passed to open function + */ typedef void (*uvc_host_driver_event_callback_t)(const uvc_host_driver_event_data_t *event, void *user_ctx); /** @@ -83,17 +122,6 @@ typedef struct { }; } uvc_host_stream_event_data_t; -/** - * @brief Formats supported by this driver - */ -enum uvc_host_stream_format { - UVC_VS_FORMAT_UNDEFINED = 0, // Invalid format. Do not request this format from the camera. - UVC_VS_FORMAT_MJPEG, - UVC_VS_FORMAT_YUY2, - UVC_VS_FORMAT_H264, - UVC_VS_FORMAT_H265, -}; - typedef struct { unsigned h_res; /**< Horizontal resolution */ unsigned v_res; /**< Vertical resolution */ diff --git a/host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h b/host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h index 676f25d6..2c549258 100644 --- a/host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h +++ b/host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h @@ -76,10 +76,53 @@ esp_err_t uvc_desc_get_frame_format_by_format( const uvc_format_desc_t **format_desc_ret, const uvc_frame_desc_t **frame_desc_ret); +/** + * @brief Check if the given USB configuration descriptor belongs to a UVC (USB Video Class) device. + * + * This function iterates through the descriptors in the provided configuration descriptor to determine + * if there is any interface descriptor indicating the device is a UVC device. + * + * @param[in] cfg_desc Pointer to the USB configuration descriptor. + * + * @return + * - true: If the configuration descriptor contains a UVC interface. + * - false: Otherwise. + */ bool uvc_desc_is_uvc_device(const usb_config_desc_t *cfg_desc); +/** + * @brief Print UVC specific descriptor in human readable form + * + * This is a callback function that is called from USB Host library, + * when it wants to print full configuration descriptor to stdout. + * + * @param[in] _desc UVC specific descriptor + */ void uvc_print_desc(const usb_standard_desc_t *_desc); +/** + * @brief Retrieve the list of frame descriptors for a specific streaming interface in a UVC device. + * + * This function extracts all frame descriptors associated with the given interface number + * and organizes them into a list of `uvc_host_frame_info_t` structures. + * + * @param[in] config_desc Pointer to the USB configuration descriptor. + * @param[in] bInterfaceNumber The interface number to search for frame descriptors. + * @param[out] frame_info_list Pointer to a list of frame info structures (allocated dynamically). + * @param[out] list_size Pointer to store the number of frames in the list. + * + * @return + * - ESP_OK: Success. + * - ESP_ERR_INVALID_ARG: One or more invalid arguments. + * - ESP_ERR_NOT_FOUND: Input header descriptor not found. + * - ESP_ERR_NO_MEM: Memory allocation failure. + */ +esp_err_t uvc_desc_get_frame_list( + const usb_config_desc_t *config_desc, + uint8_t bInterfaceNumber, + uvc_host_frame_info_t **frame_info_list, + size_t *list_size); + #ifdef __cplusplus } #endif diff --git a/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c b/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c index 3ffb8c52..fc2795a8 100644 --- a/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c +++ b/host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c @@ -391,3 +391,85 @@ bool uvc_desc_is_uvc_device(const usb_config_desc_t *cfg_desc) } return false; } + +esp_err_t uvc_desc_get_frame_list(const usb_config_desc_t *config_desc, uint8_t bInterfaceNumber, uvc_host_frame_info_t **frame_info_list, size_t *list_size) +{ + esp_err_t ret = ESP_OK; + UVC_CHECK(config_desc && frame_info_list && list_size, ESP_ERR_INVALID_ARG); + uvc_host_frame_info_t *new_frame_info = NULL; + size_t num_frame = 0; + *list_size = 0; + const uvc_vs_input_header_desc_t *input_header = uvc_desc_get_streaming_input_header(config_desc, bInterfaceNumber); + UVC_CHECK(input_header, ESP_ERR_NOT_FOUND); + + // Find requested Format descriptors + int format_offset = 0; + const usb_standard_desc_t *current_desc = (const usb_standard_desc_t *)input_header; + while ((current_desc = usb_parse_next_descriptor_of_type(current_desc, input_header->wTotalLength, UVC_CS_INTERFACE, &format_offset))) { + if (!uvc_desc_is_format_desc(current_desc)) { + continue; + } + + const uvc_format_desc_t *this_format = (const uvc_format_desc_t *)(current_desc); + enum uvc_host_stream_format format_type = uvc_desc_parse_format(this_format); + if (UVC_VS_FORMAT_UNDEFINED == format_type) { + continue; + } + + num_frame = this_format->bNumFrameDescriptors; + new_frame_info = calloc(num_frame, sizeof(uvc_host_frame_info_t)); + UVC_CHECK(new_frame_info != NULL, ESP_ERR_NO_MEM); + + // We found required Format Descriptor + // Now we look for correct Frame Descriptors which should be directly after Format + while ((current_desc = usb_parse_next_descriptor_of_type(current_desc, input_header->wTotalLength, UVC_CS_INTERFACE, &format_offset))) { + if (!uvc_desc_is_frame_desc(current_desc)) { + break; + } + uvc_frame_desc_t *this_frame = (uvc_frame_desc_t *)current_desc; + + uvc_host_frame_info_t *frame_info = &(new_frame_info)[*list_size]; + frame_info->format = format_type; + frame_info->h_res = this_frame->wWidth; + frame_info->v_res = this_frame->wHeight; + switch (format_type) { + case UVC_VS_FORMAT_MJPEG: + frame_info->default_interval = this_frame->mjpeg_uncompressed.dwDefaultFrameInterval; + frame_info->interval_type = this_frame->mjpeg_uncompressed.bFrameIntervalType; + if (frame_info->interval_type == 0) { + frame_info->interval_min = this_frame->mjpeg_uncompressed.dwMinFrameInterval; + frame_info->interval_max = this_frame->mjpeg_uncompressed.dwMaxFrameInterval; + frame_info->interval_step = this_frame->mjpeg_uncompressed.dwFrameIntervalStep; + } else { + // TODO: make 3 can be configured + for (int i = 0; i < 3; i ++) { + frame_info->interval[i] = this_frame->mjpeg_uncompressed.dwFrameInterval[i]; + } + } + break; + case UVC_VS_FORMAT_H265: + case UVC_VS_FORMAT_H264: + frame_info->default_interval = this_frame->frame_based.dwDefaultFrameInterval; + frame_info->interval_type = this_frame->frame_based.bFrameIntervalType; + if (frame_info->interval_type == 0) { + frame_info->interval_min = this_frame->frame_based.dwMinFrameInterval; + frame_info->interval_max = this_frame->frame_based.dwMaxFrameInterval; + frame_info->interval_step = this_frame->frame_based.dwFrameIntervalStep; + } else { + // TODO: make 3 can be configured + for (int i = 0; i < 3; i ++) { + frame_info->interval[i] = this_frame->frame_based.dwFrameInterval[i]; + } + } + break; + default: + break; + } + + (*list_size)++; + } + } + *frame_info_list = new_frame_info; + assert(*list_size == num_frame); + return ret; +} \ No newline at end of file diff --git a/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c b/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c index bd21baaa..283c9562 100644 --- a/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c +++ b/host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c @@ -357,14 +357,6 @@ static void print_class_specific_desc(const usb_standard_desc_t *_desc) } } -/** - * @brief Print UVC specific descriptor in human readable form - * - * This is a callback function that is called from USB Host library, - * when it wants to print full configuration descriptor to stdout. - * - * @param[in] _desc UVC specific descriptor - */ void uvc_print_desc(const usb_standard_desc_t *_desc) { switch (_desc->bDescriptorType) { diff --git a/host/class/uvc/usb_host_uvc/uvc_host.c b/host/class/uvc/usb_host_uvc/uvc_host.c index f70ecc1c..585f6ca5 100644 --- a/host/class/uvc/usb_host_uvc/uvc_host.c +++ b/host/class/uvc/usb_host_uvc/uvc_host.c @@ -66,6 +66,7 @@ static esp_err_t uvc_host_interface_check(uint8_t addr, const usb_config_desc_t size_t total_length = config_desc->wTotalLength; int iface_offset = 0; bool is_uvc_interface = false; + uint8_t uvc_stream_index = 0; // Get first Interface descriptor // Check every uac stream interface @@ -75,24 +76,25 @@ static esp_err_t uvc_host_interface_check(uint8_t addr, const usb_config_desc_t if (USB_CLASS_VIDEO == iface_desc->bInterfaceClass && UVC_SC_VIDEOSTREAMING == iface_desc->bInterfaceSubClass) { // notify user about the connected Interfaces is_uvc_interface = true; + uvc_stream_index++; if (p_uvc_host_driver->user_cb) { + uvc_host_frame_info_t *frame_info = NULL; + size_t frame_info_num = 0; + if (uvc_desc_get_frame_list(config_desc, iface_desc->bInterfaceNumber, &frame_info, &frame_info_num) != ESP_OK) { + ESP_LOGE(TAG, "Failed to get frame list for interface %d", iface_desc->bInterfaceNumber); + return ESP_FAIL; + } + const uvc_host_driver_event_data_t conn_event = { .type = UVC_HOST_DRIVER_EVENT_DEVICE_CONNECTED, .device_connected.dev_addr = addr, - .device_connected.iface_num = iface_desc->bInterfaceNumber, + .device_connected.uvc_stream_index = uvc_stream_index, + .device_connected.frame_info = frame_info, + .device_connected.frame_info_num = frame_info_num }; p_uvc_host_driver->user_cb(&conn_event, p_uvc_host_driver->user_ctx); - } - - const usb_intf_desc_t *iface_alt_desc = GET_NEXT_INTERFACE_DESC(iface_desc, total_length, iface_offset); - // Skip all alternate settings belonging to the current interface - while (iface_alt_desc != NULL) { - // Check if the alternate setting is for the same interface - if (iface_alt_desc->bInterfaceNumber != iface_desc->bInterfaceNumber) { - break; - } - iface_alt_desc = GET_NEXT_INTERFACE_DESC(iface_alt_desc, total_length, iface_offset); + free(frame_info); } } }