Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(usb_host_uvc): support dual camera with hub (IEC-246) #109

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 59 additions & 11 deletions host/class/uvc/usb_host_uvc/include/usb/uvc_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,69 @@
// 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" {
#endif

typedef struct uvc_host_stream_s *uvc_host_stream_hdl_t;

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_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 {
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);

/**
* @brief Configuration structure of USB Host UVC driver
*/
Expand All @@ -30,6 +86,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;

/**
Expand Down Expand Up @@ -64,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 */
Expand Down Expand Up @@ -122,6 +169,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 */
Expand Down
48 changes: 48 additions & 0 deletions host/class/uvc/usb_host_uvc/private_include/uvc_descriptors_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -75,6 +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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that you used this function for debugging... Could we revert it and make it private again?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is still private for the user (priv_include). It is called within the uvc_host_device_connected function to print the parsed UVC descriptor. This helps us debug issues with some customer cameras, such as when they cannot be opened properly. By printing the full descriptor upon insertion, it provides useful information for troubleshooting.


/**
* @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
98 changes: 98 additions & 0 deletions host/class/uvc/usb_host_uvc/uvc_descriptor_parsing.c
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,101 @@
}
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;
}

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);

Check warning

Code scanning / clang-tidy

Potential leak of memory pointed to by 'frame_info' [clang-analyzer-unix.Malloc] Warning

Potential leak of memory pointed to by 'frame_info' [clang-analyzer-unix.Malloc]

Check warning

Code scanning / clang-tidy

Potential leak of memory pointed to by 'new_frame_info' [clang-analyzer-unix.Malloc] Warning

Potential leak of memory pointed to by 'new_frame_info' [clang-analyzer-unix.Malloc]

// 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;
}
10 changes: 1 addition & 9 deletions host/class/uvc/usb_host_uvc/uvc_descriptor_printing.c
Original file line number Diff line number Diff line change
Expand Up @@ -357,15 +357,7 @@ 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
*/
static void uvc_print_desc(const usb_standard_desc_t *_desc)
void uvc_print_desc(const usb_standard_desc_t *_desc)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we revert this change?

{
switch (_desc->bDescriptorType) {
case UVC_CS_INTERFACE:
Expand Down
Loading
Loading