From a09556ee46712ece40ae8eaff14b015187cd1605 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Tue, 15 Jul 2025 22:33:54 +0000 Subject: [PATCH 01/10] usb: uvc: remove application decisions from the UVC class The UVC class was deciding itself which formats were sent to the host. Remove this logic out of the UVC class and introduce uvc_add_format() to give the application the freedom of which format to list. Signed-off-by: Josuah Demangeon --- include/zephyr/usb/class/usbd_uvc.h | 25 ++- samples/subsys/usb/uvc/src/main.c | 83 ++++++++-- subsys/usb/device_next/class/usbd_uvc.c | 209 +++++++++++------------- 3 files changed, 184 insertions(+), 133 deletions(-) diff --git a/include/zephyr/usb/class/usbd_uvc.h b/include/zephyr/usb/class/usbd_uvc.h index bd5562389206b..b99a3376d19c9 100644 --- a/include/zephyr/usb/class/usbd_uvc.h +++ b/include/zephyr/usb/class/usbd_uvc.h @@ -26,20 +26,33 @@ */ /** - * @brief Set the video device that a UVC instance will use. + * @brief Set the video device that a UVC instance will use for control requests. * - * It will query its supported controls, formats and frame rates, and use this information to - * generate USB descriptors sent to the host. - * - * At runtime, it will forward all USB controls from the host to this device. + * It will query its supported video controls and frame intervals and use this information to + * generate the USB descriptors presented to the host. At runtime, it will forward all USB controls + * from the host to this device. * * @note This function must be called before @ref usbd_enable. * * @param uvc_dev The UVC device - * @param video_dev The video device that this UVC instance controls + * @param video_dev The video device that this UVC instance send controls requests to */ void uvc_set_video_dev(const struct device *uvc_dev, const struct device *video_dev); +/** + * @brief Set the video format capabilities that a UVC instance will present to the host. + * + * This information will be used to generate USB descriptors. + * The particular format selected by the host can be queried with @ref video_get_format. + * + * @note This function must be called before @ref usbd_enable and before @ref uvc_set_video_dev. + * + * @param uvc_dev The UVC device to configure + * @param fmt The video format to add to this UVC instance + * @return 0 on success, negative value on error + */ +int uvc_add_format(const struct device *const uvc_dev, const struct video_format *const fmt); + /** * @} */ diff --git a/samples/subsys/usb/uvc/src/main.c b/samples/subsys/usb/uvc/src/main.c index 454bed7bdd9c5..17fbf0207bdbc 100644 --- a/samples/subsys/usb/uvc/src/main.c +++ b/samples/subsys/usb/uvc/src/main.c @@ -17,15 +17,57 @@ LOG_MODULE_REGISTER(uvc_sample, LOG_LEVEL_INF); -const struct device *const uvc_dev = DEVICE_DT_GET(DT_NODELABEL(uvc)); -const struct device *const video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera)); +const static struct device *const uvc_dev = DEVICE_DT_GET(DT_NODELABEL(uvc)); +const static struct device *const video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera)); + +/* Format capabilities of video_dev, used everywhere through the sample */ +static struct video_caps video_caps = {.type = VIDEO_BUF_TYPE_OUTPUT}; + +static void app_add_format(uint32_t pixfmt, uint16_t width, uint16_t height) +{ + struct video_format fmt = { + .pixelformat = pixfmt, + .width = width, + .height = height, + .type = VIDEO_BUF_TYPE_OUTPUT, + }; + int ret; + + /* Set the format to get the pitch */ + ret = video_set_format(video_dev, &fmt); + if (ret != 0) { + LOG_ERR("Could not set the format of %s", video_dev->name); + return; + } + + if (fmt.size > CONFIG_VIDEO_BUFFER_POOL_SZ_MAX) { + LOG_WRN("Skipping format %ux%u", fmt.width, fmt.height); + return; + } + + uvc_add_format(uvc_dev, &fmt); +} + +/* Submit to UVC only the formats expected to be working (enough memory for the size, etc.) */ +static void app_add_filtered_formats(void) +{ + for (int i = 0; video_caps.format_caps[i].pixelformat != 0; i++) { + const struct video_format_cap *vcap = &video_caps.format_caps[i]; + + app_add_format(vcap->pixelformat, vcap->width_min, vcap->height_min); + + if (vcap->width_min != vcap->width_max || vcap->height_min != vcap->height_max) { + app_add_format(vcap->pixelformat, vcap->width_max, vcap->height_max); + } + } +} int main(void) { struct usbd_context *sample_usbd; struct video_buffer *vbuf; struct video_format fmt = {0}; - struct video_caps caps; + struct video_frmival frmival = {0}; struct k_poll_signal sig; struct k_poll_event evt[1]; k_timeout_t timeout = K_FOREVER; @@ -36,16 +78,18 @@ int main(void) return -ENODEV; } - caps.type = VIDEO_BUF_TYPE_OUTPUT; - - if (video_get_caps(video_dev, &caps)) { + ret = video_get_caps(video_dev, &video_caps); + if (ret != 0) { LOG_ERR("Unable to retrieve video capabilities"); return 0; } - /* Must be done before initializing USB */ + /* Must be called before usb_enable() */ uvc_set_video_dev(uvc_dev, video_dev); + /* Must be called before uvc_set_video_dev() */ + app_add_filtered_formats(); + sample_usbd = sample_usbd_init_device(NULL); if (sample_usbd == NULL) { return -ENODEV; @@ -58,7 +102,6 @@ int main(void) LOG_INF("Waiting the host to select the video format"); - /* Get the video format once it is selected by the host */ while (true) { fmt.type = VIDEO_BUF_TYPE_INPUT; @@ -74,9 +117,29 @@ int main(void) k_sleep(K_MSEC(10)); } - LOG_INF("The host selected format '%s' %ux%u, preparing %u buffers of %u bytes", + ret = video_get_frmival(uvc_dev, &frmival); + if (ret != 0) { + LOG_ERR("Failed to get the video frame interval"); + return ret; + } + + LOG_INF("The host selected format '%s' %ux%u at frame interval %u/%u", VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height, - CONFIG_VIDEO_BUFFER_POOL_NUM_MAX, fmt.pitch * fmt.height); + frmival.numerator, frmival.denominator); + + fmt.type = VIDEO_BUF_TYPE_OUTPUT; + + ret = video_set_format(video_dev, &fmt); + if (ret != 0) { + LOG_WRN("Could not set the format of %s", video_dev->name); + } + + ret = video_set_frmival(video_dev, &frmival); + if (ret != 0) { + LOG_WRN("Could not set the framerate of %s", video_dev->name); + } + + LOG_INF("Preparing %u buffers of %u bytes", CONFIG_VIDEO_BUFFER_POOL_NUM_MAX, fmt.size); for (int i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) { vbuf = video_buffer_alloc(fmt.size, K_NO_WAIT); diff --git a/subsys/usb/device_next/class/usbd_uvc.c b/subsys/usb/device_next/class/usbd_uvc.c index 9038a8e0d3f15..f281e9e9b623d 100644 --- a/subsys/usb/device_next/class/usbd_uvc.c +++ b/subsys/usb/device_next/class/usbd_uvc.c @@ -111,6 +111,10 @@ struct uvc_data { struct video_frmival video_frmival; /* Signal to alert video devices of buffer-related evenets */ struct k_poll_signal *video_sig; + /* Last pixel format that was added by uvc_add_format() */ + uint32_t last_pix_fmt; + /* Last format descriptor that was added by uvc_add_format() */ + struct uvc_format_descriptor *last_format_desc; /* Makes sure flushing the stream only happens in one context at a time */ struct k_mutex mutex; /* Zero Length packet used to reset a stream when restarted */ @@ -179,15 +183,6 @@ UDC_BUF_POOL_VAR_DEFINE(uvc_buf_pool, UVC_TOTAL_BUFS, UVC_TOTAL_BUFS * USBD_MAX_ static void uvc_flush_queue(const struct device *dev); -/* UVC public API */ - -void uvc_set_video_dev(const struct device *const dev, const struct device *const video_dev) -{ - struct uvc_data *data = dev->data; - - data->video_dev = video_dev; -} - /* UVC helper functions */ static const struct uvc_guid_quirk uvc_guid_quirks[] = { @@ -541,8 +536,8 @@ static int uvc_get_vs_probe_frame_interval(const struct device *dev, struct uvc_ const uint8_t request) { struct uvc_data *data = dev->data; - struct uvc_format_descriptor *format_desc; - struct uvc_frame_discrete_descriptor *frame_desc; + struct uvc_format_descriptor *format_desc = NULL; + struct uvc_frame_discrete_descriptor *frame_desc = NULL; int max; uvc_get_vs_fmtfrm_desc(dev, &format_desc, &frame_desc); @@ -604,7 +599,7 @@ static int uvc_get_vs_format_from_desc(const struct device *dev, struct video_fo { struct uvc_data *data = dev->data; struct uvc_format_descriptor *format_desc = NULL; - struct uvc_frame_discrete_descriptor *frame_desc; + struct uvc_frame_discrete_descriptor *frame_desc = NULL; /* Update the format based on the probe message from the host */ uvc_get_vs_fmtfrm_desc(dev, &format_desc, &frame_desc); @@ -793,8 +788,8 @@ static int uvc_get_vs_commit(const struct device *dev, struct net_buf *const buf static int uvc_set_vs_commit(const struct device *dev, const struct net_buf *const buf) { struct uvc_data *data = dev->data; - struct video_format fmt = data->video_fmt; - struct video_frmival frmival = data->video_frmival; + struct video_format *fmt = &data->video_fmt; + struct video_frmival *frmival = &data->video_frmival; int ret; __ASSERT_NO_MSG(data->video_dev != NULL); @@ -804,27 +799,9 @@ static int uvc_set_vs_commit(const struct device *dev, const struct net_buf *con return ret; } - LOG_INF("Ready to transfer, setting source format to '%s' %ux%u", - VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height); - - fmt.type = VIDEO_BUF_TYPE_OUTPUT; - - ret = video_set_format(data->video_dev, &fmt); - if (ret != 0) { - LOG_ERR("Could not set the format of %s", data->video_dev->name); - return ret; - } - - LOG_DBG("Setting frame interval of %s to %u/%u", - data->video_dev->name, - data->video_frmival.numerator, data->video_frmival.denominator); - - ret = video_set_frmival(data->video_dev, &frmival); - if (ret != 0) { - LOG_WRN("Could not set the framerate of %s", data->video_dev->name); - } - - LOG_DBG("UVC device ready, %s can now be started", data->video_dev->name); + LOG_INF("Host selected format '%s' %ux%u, frame interval %u/%u", + VIDEO_FOURCC_TO_STR(fmt->pixelformat), fmt->width, fmt->height, + frmival->numerator, frmival->denominator); if (atomic_test_bit(&data->state, UVC_STATE_STREAM_READY)) { atomic_set_bit(&data->state, UVC_STATE_STREAM_RESTART); @@ -1409,13 +1386,13 @@ static union uvc_fmt_desc *uvc_new_fmt_desc(const struct device *dev) static int uvc_add_vs_format_desc(const struct device *dev, struct uvc_format_descriptor **const format_desc, - const struct video_format_cap *const cap) + uint32_t fourcc) { const struct uvc_config *cfg = dev->config; __ASSERT_NO_MSG(format_desc != NULL); - if (cap->pixelformat == VIDEO_PIX_FMT_JPEG) { + if (fourcc == VIDEO_PIX_FMT_JPEG) { struct uvc_format_mjpeg_descriptor *desc; LOG_INF("Adding format descriptor #%u for MJPEG", @@ -1438,7 +1415,7 @@ static int uvc_add_vs_format_desc(const struct device *dev, struct uvc_format_uncomp_descriptor *desc; LOG_INF("Adding format descriptor #%u for '%s'", - cfg->desc->if1_hdr.bNumFormats + 1, VIDEO_FOURCC_TO_STR(cap->pixelformat)); + cfg->desc->if1_hdr.bNumFormats + 1, VIDEO_FOURCC_TO_STR(fourcc)); desc = &uvc_new_fmt_desc(dev)->fmt_uncomp; if (desc == NULL) { @@ -1449,8 +1426,8 @@ static int uvc_add_vs_format_desc(const struct device *dev, desc->bFormatIndex = cfg->desc->if1_hdr.bNumFormats + 1; desc->bLength = sizeof(*desc); desc->bDescriptorSubtype = UVC_VS_FORMAT_UNCOMPRESSED; - uvc_fourcc_to_guid(desc->guidFormat, cap->pixelformat); - desc->bBitsPerPixel = video_bits_per_pixel(cap->pixelformat); + uvc_fourcc_to_guid(desc->guidFormat, fourcc); + desc->bBitsPerPixel = video_bits_per_pixel(fourcc); desc->bDefaultFrameIndex = 1; cfg->desc->if1_hdr.bNumFormats++; cfg->desc->if1_hdr.wTotalLength += desc->bLength; @@ -1474,7 +1451,8 @@ static int uvc_compare_frmival_desc(const void *const a, const void *const b) } static void uvc_set_vs_bitrate_range(struct uvc_frame_discrete_descriptor *const desc, - const uint64_t frmival_nsec, struct video_format *const fmt) + const uint64_t frmival_nsec, + const struct video_format *const fmt) { uint32_t bitrate_min = sys_le32_to_cpu(desc->dwMinBitRate); uint32_t bitrate_max = sys_le32_to_cpu(desc->dwMaxBitRate); @@ -1501,7 +1479,7 @@ static void uvc_set_vs_bitrate_range(struct uvc_frame_discrete_descriptor *const static int uvc_add_vs_frame_interval(struct uvc_frame_discrete_descriptor *const desc, const struct video_frmival *const frmival, - struct video_format *const fmt) + const struct video_format *const fmt) { int i = desc->bFrameIntervalType; @@ -1521,24 +1499,19 @@ static int uvc_add_vs_frame_interval(struct uvc_frame_discrete_descriptor *const static int uvc_add_vs_frame_desc(const struct device *dev, struct uvc_format_descriptor *const format_desc, - const struct video_format_cap *const cap, const bool min) + const struct video_format *const fmt) { const struct uvc_config *cfg = dev->config; struct uvc_data *data = dev->data; struct uvc_frame_discrete_descriptor *desc; - uint16_t w = min ? cap->width_min : cap->width_max; - uint16_t h = min ? cap->height_min : cap->height_max; - uint16_t p = MAX(video_bits_per_pixel(cap->pixelformat), 8) * w / BITS_PER_BYTE; - struct video_format fmt = {.pixelformat = cap->pixelformat, - .width = w, .height = h, .pitch = p}; - struct video_frmival_enum fie = {.format = &fmt}; - uint32_t max_size = MAX(p, w) * h; + struct video_frmival_enum fie = {.format = fmt}; + uint32_t max_size = MAX(fmt->pitch, fmt->width) * fmt->height; __ASSERT_NO_MSG(data->video_dev != NULL); __ASSERT_NO_MSG(format_desc != NULL); LOG_INF("Adding frame descriptor #%u for %ux%u", - format_desc->bNumFrameDescriptors + 1, w, h); + format_desc->bNumFrameDescriptors + 1, fmt->width, fmt->height); desc = &uvc_new_fmt_desc(dev)->frm_disc; if (desc == NULL) { @@ -1548,8 +1521,8 @@ static int uvc_add_vs_frame_desc(const struct device *dev, desc->bLength = sizeof(*desc) - CONFIG_USBD_VIDEO_MAX_FRMIVAL * sizeof(uint32_t); desc->bDescriptorType = USB_DESC_CS_INTERFACE; desc->bFrameIndex = format_desc->bNumFrameDescriptors + 1; - desc->wWidth = sys_cpu_to_le16(w); - desc->wHeight = sys_cpu_to_le16(h); + desc->wWidth = sys_cpu_to_le16(fmt->width); + desc->wHeight = sys_cpu_to_le16(fmt->height); desc->dwMaxVideoFrameBufferSize = sys_cpu_to_le32(max_size); desc->bDescriptorSubtype = (format_desc->bDescriptorSubtype == UVC_VS_FORMAT_UNCOMPRESSED) ? UVC_VS_FRAME_UNCOMPRESSED : UVC_VS_FRAME_MJPEG; @@ -1561,12 +1534,12 @@ static int uvc_add_vs_frame_desc(const struct device *dev, switch (fie.type) { case VIDEO_FRMIVAL_TYPE_DISCRETE: LOG_DBG("Adding discrete frame interval %u", fie.index); - uvc_add_vs_frame_interval(desc, &fie.discrete, &fmt); + uvc_add_vs_frame_interval(desc, &fie.discrete, fmt); break; case VIDEO_FRMIVAL_TYPE_STEPWISE: LOG_DBG("Adding stepwise frame interval %u", fie.index); - uvc_add_vs_frame_interval(desc, &fie.stepwise.min, &fmt); - uvc_add_vs_frame_interval(desc, &fie.stepwise.max, &fmt); + uvc_add_vs_frame_interval(desc, &fie.stepwise.min, fmt); + uvc_add_vs_frame_interval(desc, &fie.stepwise.max, fmt); break; default: CODE_UNREACHABLE; @@ -1578,7 +1551,7 @@ static int uvc_add_vs_frame_desc(const struct device *dev, if (desc->bFrameIntervalType == 0) { struct video_frmival frmival = {.numerator = 1, .denominator = 30}; - uvc_add_vs_frame_interval(desc, &frmival, &fmt); + uvc_add_vs_frame_interval(desc, &frmival, fmt); } /* UVC requires the frame intervals to be sorted, but not Zephyr */ @@ -1620,10 +1593,6 @@ static int uvc_init(struct usbd_class_data *const c_data) const struct device *dev = usbd_class_get_private(c_data); const struct uvc_config *cfg = dev->config; struct uvc_data *data = dev->data; - struct uvc_format_descriptor *format_desc = NULL; - struct video_caps caps; - uint32_t prev_pixfmt = 0; - uint32_t mask = 0; int ret; __ASSERT_NO_MSG(data->video_dev != NULL); @@ -1633,9 +1602,41 @@ static int uvc_init(struct usbd_class_data *const c_data) return 0; } - cfg->desc->if0_hdr.baInterfaceNr[0] = cfg->desc->if1.bInterfaceNumber; + cfg->desc->if1_hdr.wTotalLength = sys_le16_to_cpu(cfg->desc->if1_hdr.wTotalLength); + cfg->desc->if1_hdr.wTotalLength += cfg->desc->if1_color.bLength; - /* Generating VideoControl descriptors (interface 0) */ + uvc_assign_desc(dev, &cfg->desc->if1_color, true, true); + uvc_assign_desc(dev, &cfg->desc->if1_ep_fs, true, false); + uvc_assign_desc(dev, &cfg->desc->if1_ep_hs, false, true); + + cfg->desc->if1_hdr.wTotalLength = sys_cpu_to_le16(cfg->desc->if1_hdr.wTotalLength); + + /* Generating the default probe message now that descriptors are complete */ + + ret = uvc_get_vs_probe_struct(dev, &data->default_probe, UVC_GET_CUR); + if (ret != 0) { + LOG_ERR("init: failed to query the default probe"); + return ret; + } + + atomic_set_bit(&data->state, UVC_STATE_INITIALIZED); + + return 0; +} + +/* UVC public API */ + +void uvc_set_video_dev(const struct device *const dev, const struct device *const video_dev) +{ + struct uvc_data *data = dev->data; + const struct uvc_config *cfg = dev->config; + uint32_t mask = 0; + + data->video_dev = video_dev; + + /* Generate VideoControl descriptors (interface 0) */ + + cfg->desc->if0_hdr.baInterfaceNr[0] = cfg->desc->if1.bInterfaceNumber; mask = uvc_get_mask(data->video_dev, uvc_control_map_ct, ARRAY_SIZE(uvc_control_map_ct)); cfg->desc->if0_ct.bmControls[0] = mask >> 0; @@ -1652,65 +1653,37 @@ static int uvc_init(struct usbd_class_data *const c_data) cfg->desc->if0_xu.bmControls[1] = mask >> 8; cfg->desc->if0_xu.bmControls[2] = mask >> 16; cfg->desc->if0_xu.bmControls[3] = mask >> 24; +} - /* Generating VideoStreaming descriptors (interface 1) */ - - caps.type = VIDEO_BUF_TYPE_OUTPUT; +int uvc_add_format(const struct device *const dev, const struct video_format *const fmt) +{ + struct uvc_data *data = dev->data; + const struct uvc_config *cfg = dev->config; + int ret; - ret = video_get_caps(data->video_dev, &caps); - if (ret != 0) { - LOG_ERR("Could not load %s video format list", data->video_dev->name); - return ret; + if (data->video_dev == NULL) { + LOG_ERR("Video device not yet configured into UVC"); + return -EINVAL; } - cfg->desc->if1_hdr.wTotalLength = sys_le16_to_cpu(cfg->desc->if1_hdr.wTotalLength); - - for (int i = 0; caps.format_caps[i].pixelformat != 0; i++) { - const struct video_format_cap *cap = &caps.format_caps[i]; - - if (prev_pixfmt != cap->pixelformat) { - if (prev_pixfmt != 0) { - cfg->desc->if1_hdr.wTotalLength += cfg->desc->if1_color.bLength; - uvc_assign_desc(dev, &cfg->desc->if1_color, true, true); - } - - ret = uvc_add_vs_format_desc(dev, &format_desc, cap); - if (ret != 0) { - return ret; - } + if (data->last_pix_fmt != fmt->pixelformat) { + if (data->last_pix_fmt != 0) { + cfg->desc->if1_hdr.wTotalLength += cfg->desc->if1_color.bLength; + uvc_assign_desc(dev, &cfg->desc->if1_color, true, true); } - ret = uvc_add_vs_frame_desc(dev, format_desc, cap, true); + ret = uvc_add_vs_format_desc(dev, &data->last_format_desc, fmt->pixelformat); if (ret != 0) { return ret; } - - if (cap->width_min != cap->width_max || cap->height_min != cap->height_max) { - ret = uvc_add_vs_frame_desc(dev, format_desc, cap, false); - if (ret != 0) { - return ret; - } - } - - prev_pixfmt = cap->pixelformat; } - cfg->desc->if1_hdr.wTotalLength += cfg->desc->if1_color.bLength; - uvc_assign_desc(dev, &cfg->desc->if1_color, true, true); - uvc_assign_desc(dev, &cfg->desc->if1_ep_fs, true, false); - uvc_assign_desc(dev, &cfg->desc->if1_ep_hs, false, true); - - cfg->desc->if1_hdr.wTotalLength = sys_cpu_to_le16(cfg->desc->if1_hdr.wTotalLength); - - /* Generating the default probe message now that descriptors are complete */ - - ret = uvc_get_vs_probe_struct(dev, &data->default_probe, UVC_GET_CUR); + ret = uvc_add_vs_frame_desc(dev, data->last_format_desc, fmt); if (ret != 0) { - LOG_ERR("init: failed to query the default probe"); return ret; } - atomic_set_bit(&data->state, UVC_STATE_INITIALIZED); + data->last_pix_fmt = fmt->pixelformat; return 0; } @@ -2055,26 +2028,27 @@ static int uvc_dequeue(const struct device *dev, struct video_buffer **const vbu static int uvc_get_format(const struct device *dev, struct video_format *const fmt) { struct uvc_data *data = dev->data; - struct video_format tmp_fmt = {0}; - int ret; - - __ASSERT_NO_MSG(data->video_dev != NULL); if (!atomic_test_bit(&data->state, UVC_STATE_ENABLED) || !atomic_test_bit(&data->state, UVC_STATE_STREAM_READY)) { return -EAGAIN; } - LOG_DBG("Querying the format from %s", data->video_dev->name); + *fmt = data->video_fmt; - tmp_fmt.type = VIDEO_BUF_TYPE_OUTPUT; + return 0; +} - ret = video_get_format(data->video_dev, &tmp_fmt); - if (ret != 0) { - return ret; +static int uvc_get_frmival(const struct device *dev, struct video_frmival *const frmival) +{ + struct uvc_data *data = dev->data; + + if (!atomic_test_bit(&data->state, UVC_STATE_ENABLED) || + !atomic_test_bit(&data->state, UVC_STATE_STREAM_READY)) { + return -EAGAIN; } - *fmt = tmp_fmt; + *frmival = data->video_frmival; return 0; } @@ -2107,6 +2081,7 @@ static int uvc_set_signal(const struct device *dev, struct k_poll_signal *const static DEVICE_API(video, uvc_video_api) = { .get_format = uvc_get_format, + .get_frmival = uvc_get_frmival, .set_stream = uvc_set_stream, .enqueue = uvc_enqueue, .dequeue = uvc_dequeue, From ecc6621d05bbf5666d896eed2102f2bb5ac2a274 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Wed, 16 Jul 2025 07:24:20 +0000 Subject: [PATCH 02/10] samples: usb: uvc: add filtering of the format The UVC class now lets the application select the format list sent to the host. Leverage this in the sample to filter out any format that is not expected to work (buffer too big, rarely supported formats). Signed-off-by: Josuah Demangeon --- samples/subsys/usb/uvc/src/main.c | 34 ++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/samples/subsys/usb/uvc/src/main.c b/samples/subsys/usb/uvc/src/main.c index 17fbf0207bdbc..33d35057a3d29 100644 --- a/samples/subsys/usb/uvc/src/main.c +++ b/samples/subsys/usb/uvc/src/main.c @@ -23,7 +23,27 @@ const static struct device *const video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_cam /* Format capabilities of video_dev, used everywhere through the sample */ static struct video_caps video_caps = {.type = VIDEO_BUF_TYPE_OUTPUT}; -static void app_add_format(uint32_t pixfmt, uint16_t width, uint16_t height) +static bool app_is_standard_format(uint32_t pixfmt) +{ + return pixfmt == VIDEO_PIX_FMT_GREY || pixfmt == VIDEO_PIX_FMT_JPEG || + pixfmt == VIDEO_PIX_FMT_YUYV; +} + +/* Check whether the video device supports one of the wisespread image sensor formats */ +static bool app_has_standard_formats(void) +{ + const struct video_format_cap *fmts = video_caps.format_caps; + + for (int i = 0; fmts[i].pixelformat != 0; i++) { + if (app_is_standard_format(fmts[i].pixelformat)) { + return true; + } + } + + return false; +} + +static void app_add_format(uint32_t pixfmt, uint16_t width, uint16_t height, bool has_std_fmts) { struct video_format fmt = { .pixelformat = pixfmt, @@ -33,6 +53,11 @@ static void app_add_format(uint32_t pixfmt, uint16_t width, uint16_t height) }; int ret; + /* If the system has any standard pixel format, only propose them to the host */ + if (has_std_fmts && !app_is_standard_format(pixfmt)) { + return; + } + /* Set the format to get the pitch */ ret = video_set_format(video_dev, &fmt); if (ret != 0) { @@ -51,13 +76,16 @@ static void app_add_format(uint32_t pixfmt, uint16_t width, uint16_t height) /* Submit to UVC only the formats expected to be working (enough memory for the size, etc.) */ static void app_add_filtered_formats(void) { + const bool has_std_fmts = app_has_standard_formats(); + for (int i = 0; video_caps.format_caps[i].pixelformat != 0; i++) { const struct video_format_cap *vcap = &video_caps.format_caps[i]; - app_add_format(vcap->pixelformat, vcap->width_min, vcap->height_min); + app_add_format(vcap->pixelformat, vcap->width_min, vcap->height_min, has_std_fmts); if (vcap->width_min != vcap->width_max || vcap->height_min != vcap->height_max) { - app_add_format(vcap->pixelformat, vcap->width_max, vcap->height_max); + app_add_format(vcap->pixelformat, vcap->width_max, vcap->height_max, + has_std_fmts); } } } From 86628a618037c655cd46536f03d585dfb3a754fc Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Thu, 24 Jul 2025 11:04:50 +0000 Subject: [PATCH 03/10] doc: release: 4.3: add UVC changes Add USB UVC device's new uvc_add_format() function to the release note, and document the semantic changes of UVC APIs in the migration guide. Signed-off-by: Josuah Demangeon --- doc/releases/migration-guide-4.3.rst | 6 ++++++ doc/releases/release-notes-4.3.rst | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/doc/releases/migration-guide-4.3.rst b/doc/releases/migration-guide-4.3.rst index a6a58850cc987..527c6b1199a20 100644 --- a/doc/releases/migration-guide-4.3.rst +++ b/doc/releases/migration-guide-4.3.rst @@ -131,6 +131,12 @@ Stepper * :dtcompatible:`zephyr,gpio-stepper` has been replaced by :dtcompatible:`zephyr,h-bridge-stepper`. +USB +=== + +* The USB Video Class was configuring the framerate and format of the source video device. + This is now to be done by the application after the host selected the format (:github:`93192`). + .. zephyr-keep-sorted-stop Bluetooth diff --git a/doc/releases/release-notes-4.3.rst b/doc/releases/release-notes-4.3.rst index b7a137c68cf84..236c5da67f2d3 100644 --- a/doc/releases/release-notes-4.3.rst +++ b/doc/releases/release-notes-4.3.rst @@ -279,6 +279,12 @@ New APIs and options * :c:macro:`__deprecated_version` +* USB + + * Video + + * :c:func:`uvc_add_format` + * Video * :c:member:`video_format.size` field From a047d471ef12d029efdf5de751984e0e80cdf506 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Thu, 14 Aug 2025 21:39:24 +0200 Subject: [PATCH 04/10] video: dcmipp: expose dcmipp caps for all 3 pipes. Currently the DCMIPP driver rely on a Kconfig in order to select the right sensor resolution / format to pick. This also makes the exposure of caps easier since it can be exposed as: DUMP pipe: same caps as mentioned in Kconfig MAIN pipe: any format supported on this pipe and resolution starting at sensor selected resolution down to 64 times smaller (which is the maximum of the downscale) AUX pipe: same as MAIN except without the semi-planar and planar formats Signed-off-by: Alain Volmat --- drivers/video/video_stm32_dcmipp.c | 89 +++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/drivers/video/video_stm32_dcmipp.c b/drivers/video/video_stm32_dcmipp.c index 37c7d1724598b..91826533bfda5 100644 --- a/drivers/video/video_stm32_dcmipp.c +++ b/drivers/video/video_stm32_dcmipp.c @@ -1359,22 +1359,93 @@ static int stm32_dcmipp_dequeue(const struct device *dev, struct video_buffer ** } /* - * TODO: caps aren't yet handled hence give back straight the caps given by the - * source. Normally this should be the intersection of what the source produces - * vs what the DCMIPP can input (for pipe0) and, for pipe 1 and 2, for a given - * input format, generate caps based on capabilities, color conversion, decimation - * etc + * For MAIN / AUX pipe, it is necessary that the pitch is a multiple of 16 bytes. + * Give here the multiple in number of pixels, which depends on the format chosen */ +#define DCMIPP_CEIL_DIV_ROUND_UP_MUL(val, div, mul) \ + ((((val) + (div) - 1) / (div) + (mul) - 1) / (mul) * (mul)) +#define DCMIPP_CEIL_DIV(val, div) \ + (((val) + (div) - 1) / (div)) +#define DCMIPP_VIDEO_FORMAT_CAP(format, pixmul) { \ + .pixelformat = VIDEO_PIX_FMT_##format, \ + .width_min = DCMIPP_CEIL_DIV_ROUND_UP_MUL(CONFIG_VIDEO_STM32_DCMIPP_SENSOR_WIDTH, \ + STM32_DCMIPP_MAX_PIPE_SCALE_FACTOR, \ + pixmul), \ + .width_max = CONFIG_VIDEO_STM32_DCMIPP_SENSOR_WIDTH / (pixmul) * (pixmul), \ + .height_min = DCMIPP_CEIL_DIV(CONFIG_VIDEO_STM32_DCMIPP_SENSOR_HEIGHT, \ + STM32_DCMIPP_MAX_PIPE_SCALE_FACTOR), \ + .height_max = CONFIG_VIDEO_STM32_DCMIPP_SENSOR_HEIGHT, \ + .width_step = pixmul, .height_step = 1, \ +} + +static const struct video_format_cap stm32_dcmipp_dump_fmt[] = { + { + .pixelformat = + VIDEO_FOURCC_FROM_STR(CONFIG_VIDEO_STM32_DCMIPP_SENSOR_PIXEL_FORMAT), + .width_min = CONFIG_VIDEO_STM32_DCMIPP_SENSOR_WIDTH, + .width_max = CONFIG_VIDEO_STM32_DCMIPP_SENSOR_WIDTH, + .height_min = CONFIG_VIDEO_STM32_DCMIPP_SENSOR_HEIGHT, + .height_max = CONFIG_VIDEO_STM32_DCMIPP_SENSOR_HEIGHT, + .width_step = 1, .height_step = 1, + }, + {0}, +}; + +static const struct video_format_cap stm32_dcmipp_main_fmts[] = { + DCMIPP_VIDEO_FORMAT_CAP(RGB565, 8), + DCMIPP_VIDEO_FORMAT_CAP(YUYV, 8), + DCMIPP_VIDEO_FORMAT_CAP(YVYU, 8), + DCMIPP_VIDEO_FORMAT_CAP(GREY, 16), + DCMIPP_VIDEO_FORMAT_CAP(RGB24, 16), + DCMIPP_VIDEO_FORMAT_CAP(BGR24, 16), + DCMIPP_VIDEO_FORMAT_CAP(ARGB32, 4), + DCMIPP_VIDEO_FORMAT_CAP(ABGR32, 4), + DCMIPP_VIDEO_FORMAT_CAP(RGBA32, 4), + DCMIPP_VIDEO_FORMAT_CAP(BGRA32, 4), + DCMIPP_VIDEO_FORMAT_CAP(NV12, 16), + DCMIPP_VIDEO_FORMAT_CAP(NV21, 16), + DCMIPP_VIDEO_FORMAT_CAP(NV16, 16), + DCMIPP_VIDEO_FORMAT_CAP(NV61, 16), + DCMIPP_VIDEO_FORMAT_CAP(YUV420, 16), + DCMIPP_VIDEO_FORMAT_CAP(YVU420, 16), + {0}, +}; + +static const struct video_format_cap stm32_dcmipp_aux_fmts[] = { + DCMIPP_VIDEO_FORMAT_CAP(RGB565, 8), + DCMIPP_VIDEO_FORMAT_CAP(YUYV, 8), + DCMIPP_VIDEO_FORMAT_CAP(YVYU, 8), + DCMIPP_VIDEO_FORMAT_CAP(GREY, 16), + DCMIPP_VIDEO_FORMAT_CAP(RGB24, 16), + DCMIPP_VIDEO_FORMAT_CAP(BGR24, 16), + DCMIPP_VIDEO_FORMAT_CAP(ARGB32, 4), + DCMIPP_VIDEO_FORMAT_CAP(ABGR32, 4), + DCMIPP_VIDEO_FORMAT_CAP(RGBA32, 4), + DCMIPP_VIDEO_FORMAT_CAP(BGRA32, 4), + {0}, +}; + static int stm32_dcmipp_get_caps(const struct device *dev, struct video_caps *caps) { - const struct stm32_dcmipp_config *config = dev->config; - int ret; + struct stm32_dcmipp_pipe_data *pipe = dev->data; - ret = video_get_caps(config->source_dev, caps); + switch (pipe->id) { + case DCMIPP_PIPE0: + caps->format_caps = stm32_dcmipp_dump_fmt; + break; + case DCMIPP_PIPE1: + caps->format_caps = stm32_dcmipp_main_fmts; + break; + case DCMIPP_PIPE2: + caps->format_caps = stm32_dcmipp_aux_fmts; + break; + default: + CODE_UNREACHABLE; + } caps->min_vbuf_count = 1; - return ret; + return 0; } static int stm32_dcmipp_get_frmival(const struct device *dev, struct video_frmival *frmival) From d03da1cc3128239c6b60ca862f70335ec263b04a Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 15 Aug 2025 14:29:25 +0200 Subject: [PATCH 05/10] video: introduce video_set_compose_format helper Some devices allow for downscale / upscale via the set_selection compose API. When using it, it is necessary to perform a set_selection of the compose target prior to setting the format. In order to allow non-compose aware application to benefit from it, introduce a helper which take care of setting the compose prior to setting the format. Signed-off-by: Alain Volmat --- drivers/video/video_common.c | 21 +++++++++++++++++++++ include/zephyr/drivers/video.h | 16 ++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/drivers/video/video_common.c b/drivers/video/video_common.c index 95793e66db689..b75fb3165a454 100644 --- a/drivers/video/video_common.c +++ b/drivers/video/video_common.c @@ -468,3 +468,24 @@ int video_estimate_fmt_size(struct video_format *fmt) return 0; } + +int video_set_compose_format(const struct device *dev, struct video_format *fmt) +{ + struct video_selection sel = { + .type = fmt->type, + .target = VIDEO_SEL_TGT_COMPOSE, + .rect.left = 0, + .rect.top = 0, + .rect.width = fmt->width, + .rect.height = fmt->height, + }; + int ret; + + ret = video_set_selection(dev, &sel); + if (ret < 0 && ret != -ENOSYS) { + LOG_ERR("Unable to set selection compose"); + return ret; + } + + return video_set_format(dev, fmt); +} diff --git a/include/zephyr/drivers/video.h b/include/zephyr/drivers/video.h index e50f49b0e382f..9d545e51c711f 100644 --- a/include/zephyr/drivers/video.h +++ b/include/zephyr/drivers/video.h @@ -982,6 +982,22 @@ int64_t video_get_csi_link_freq(const struct device *dev, uint8_t bpp, uint8_t l */ int video_estimate_fmt_size(struct video_format *fmt); +/** + * @brief Set compose rectangle (if applicable) prior to setting format + * + * Some devices expose compose capabilities, allowing them to apply a transformation + * (downscale / upscale) to the frame. For those devices, it is necessary to set the + * compose rectangle before being able to apply the frame format (which must have the + * same width / height and the compose rectangle width / height. + * In order to allow non-compose aware application to be able to control such devices, + * introduce a helper which, if available, will apply the compose rectangle prior to + * setting the format. + * + * @param dev Video device to query. + * @param fmt Video format structure pointer + */ +int video_set_compose_format(const struct device *dev, struct video_format *fmt); + /** * @defgroup video_pixel_formats Video pixel formats * The '|' characters separate the pixels or logical blocks, and spaces separate the bytes. From d38361d965ff09c5d96821f886c215f592fb75d9 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 15 Aug 2025 14:33:20 +0200 Subject: [PATCH 06/10] samples: video: capture: use video_set_compose_format helper Simplify the code by using the video_set_compose_format helper. Signed-off-by: Alain Volmat --- samples/drivers/video/capture/src/main.c | 45 +++++------------------- 1 file changed, 9 insertions(+), 36 deletions(-) diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index fe702a1bdb367..6f16479d8a8bf 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -98,10 +98,14 @@ int main(void) struct video_frmival frmival; struct video_frmival_enum fie; enum video_buf_type type = VIDEO_BUF_TYPE_OUTPUT; -#if (CONFIG_VIDEO_SOURCE_CROP_WIDTH && CONFIG_VIDEO_SOURCE_CROP_HEIGHT) || \ - CONFIG_VIDEO_FRAME_HEIGHT || CONFIG_VIDEO_FRAME_WIDTH - struct video_selection sel = { +#if (CONFIG_VIDEO_SOURCE_CROP_WIDTH && CONFIG_VIDEO_SOURCE_CROP_HEIGHT) + struct video_selection crop_sel = { .type = VIDEO_BUF_TYPE_OUTPUT, + .target = VIDEO_SEL_TGT_CROP; + .rect.left = CONFIG_VIDEO_SOURCE_CROP_LEFT; + .rect.top = CONFIG_VIDEO_SOURCE_CROP_TOP; + .rect.width = CONFIG_VIDEO_SOURCE_CROP_WIDTH; + .rect.height = CONFIG_VIDEO_SOURCE_CROP_HEIGHT; }; #endif unsigned int frame = 0; @@ -149,12 +153,7 @@ int main(void) /* Set the crop setting if necessary */ #if CONFIG_VIDEO_SOURCE_CROP_WIDTH && CONFIG_VIDEO_SOURCE_CROP_HEIGHT - sel.target = VIDEO_SEL_TGT_CROP; - sel.rect.left = CONFIG_VIDEO_SOURCE_CROP_LEFT; - sel.rect.top = CONFIG_VIDEO_SOURCE_CROP_TOP; - sel.rect.width = CONFIG_VIDEO_SOURCE_CROP_WIDTH; - sel.rect.height = CONFIG_VIDEO_SOURCE_CROP_HEIGHT; - if (video_set_selection(video_dev, &sel)) { + if (video_set_selection(video_dev, &crop_sel)) { LOG_ERR("Unable to set selection crop"); return 0; } @@ -162,7 +161,6 @@ int main(void) sel.rect.left, sel.rect.top, sel.rect.width, sel.rect.height); #endif -#if CONFIG_VIDEO_FRAME_HEIGHT || CONFIG_VIDEO_FRAME_WIDTH #if CONFIG_VIDEO_FRAME_HEIGHT fmt.height = CONFIG_VIDEO_FRAME_HEIGHT; #endif @@ -171,31 +169,6 @@ int main(void) fmt.width = CONFIG_VIDEO_FRAME_WIDTH; #endif - /* - * Check (if possible) if targeted size is same as crop - * and if compose is necessary - */ - sel.target = VIDEO_SEL_TGT_CROP; - err = video_get_selection(video_dev, &sel); - if (err < 0 && err != -ENOSYS) { - LOG_ERR("Unable to get selection crop"); - return 0; - } - - if (err == 0 && (sel.rect.width != fmt.width || sel.rect.height != fmt.height)) { - sel.target = VIDEO_SEL_TGT_COMPOSE; - sel.rect.left = 0; - sel.rect.top = 0; - sel.rect.width = fmt.width; - sel.rect.height = fmt.height; - err = video_set_selection(video_dev, &sel); - if (err < 0 && err != -ENOSYS) { - LOG_ERR("Unable to set selection compose"); - return 0; - } - } -#endif - if (strcmp(CONFIG_VIDEO_PIXEL_FORMAT, "")) { fmt.pixelformat = VIDEO_FOURCC_FROM_STR(CONFIG_VIDEO_PIXEL_FORMAT); } @@ -203,7 +176,7 @@ int main(void) LOG_INF("- Video format: %s %ux%u", VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height); - if (video_set_format(video_dev, &fmt)) { + if (video_set_compose_format(video_dev, &fmt)) { LOG_ERR("Unable to set format"); return 0; } From 11291ed7478a1a74edd7477f51338361f889fd35 Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 15 Aug 2025 17:58:53 +0200 Subject: [PATCH 07/10] samples: usb: uvc: ensure video buffer is properly aligned Honor the CONFIG_VIDEO_BUFFER_POOL_ALIGN config by using the video_buffer_aligned_alloc function instead of video_buffer_alloc in order to provide properly aligned buffers to drivers. Signed-off-by: Alain Volmat --- samples/subsys/usb/uvc/src/main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/subsys/usb/uvc/src/main.c b/samples/subsys/usb/uvc/src/main.c index 33d35057a3d29..b413fff3a99a5 100644 --- a/samples/subsys/usb/uvc/src/main.c +++ b/samples/subsys/usb/uvc/src/main.c @@ -170,7 +170,8 @@ int main(void) LOG_INF("Preparing %u buffers of %u bytes", CONFIG_VIDEO_BUFFER_POOL_NUM_MAX, fmt.size); for (int i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) { - vbuf = video_buffer_alloc(fmt.size, K_NO_WAIT); + vbuf = video_buffer_aligned_alloc(fmt.size, CONFIG_VIDEO_BUFFER_POOL_ALIGN, + K_NO_WAIT); if (vbuf == NULL) { LOG_ERR("Could not allocate the video buffer"); return -ENOMEM; From 8c889cfe1ae403610303a0ab53a49d17e432412b Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 15 Aug 2025 21:58:42 +0200 Subject: [PATCH 08/10] samples: usb: uvc: use video_set_compose_format Use the helper video_set_compose_format in order to allow controlling the compose. Signed-off-by: Alain Volmat --- samples/subsys/usb/uvc/src/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/subsys/usb/uvc/src/main.c b/samples/subsys/usb/uvc/src/main.c index b413fff3a99a5..61b78086f7150 100644 --- a/samples/subsys/usb/uvc/src/main.c +++ b/samples/subsys/usb/uvc/src/main.c @@ -59,7 +59,7 @@ static void app_add_format(uint32_t pixfmt, uint16_t width, uint16_t height, boo } /* Set the format to get the pitch */ - ret = video_set_format(video_dev, &fmt); + ret = video_set_compose_format(video_dev, &fmt); if (ret != 0) { LOG_ERR("Could not set the format of %s", video_dev->name); return; @@ -157,7 +157,7 @@ int main(void) fmt.type = VIDEO_BUF_TYPE_OUTPUT; - ret = video_set_format(video_dev, &fmt); + ret = video_set_compose_format(video_dev, &fmt); if (ret != 0) { LOG_WRN("Could not set the format of %s", video_dev->name); } From 5c9faa02e00ac543d6f08692c59b6c8c01cc27db Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Fri, 15 Aug 2025 22:01:08 +0200 Subject: [PATCH 09/10] samples: usb: uvc: select applicable resolutions from range Select from commonly used resolution when the video device advertise capabilities using range. Signed-off-by: Alain Volmat --- samples/subsys/usb/uvc/Kconfig | 12 +++++++++ samples/subsys/usb/uvc/src/main.c | 42 +++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/samples/subsys/usb/uvc/Kconfig b/samples/subsys/usb/uvc/Kconfig index d1b0c2bec39c3..76ee42eceee14 100644 --- a/samples/subsys/usb/uvc/Kconfig +++ b/samples/subsys/usb/uvc/Kconfig @@ -6,4 +6,16 @@ # tree, you cannot use them in your own application. source "samples/subsys/usb/common/Kconfig.sample_usbd" +menu "UVC specific configuration" + +config VIDEO_MAX_RANGE_RESOLUTIONS + int "Maximum number of intermediate resolutions" + default 5 + help + Control the maximum number of resolution that will be advertised + to the USB client in case of the video capture supports a range + of resolutions. + +endmenu + source "Kconfig.zephyr" diff --git a/samples/subsys/usb/uvc/src/main.c b/samples/subsys/usb/uvc/src/main.c index 61b78086f7150..6b1ff0ef8eaa6 100644 --- a/samples/subsys/usb/uvc/src/main.c +++ b/samples/subsys/usb/uvc/src/main.c @@ -73,6 +73,23 @@ static void app_add_format(uint32_t pixfmt, uint16_t width, uint16_t height, boo uvc_add_format(uvc_dev, &fmt); } +struct video_resolution { + uint16_t width; + uint16_t height; +}; + +static struct video_resolution video_common_fmts[] = { + { .width = 3840, .height = 2160, }, /* UHD */ + { .width = 1920, .height = 1080, }, /* FHD */ + { .width = 1280, .height = 1024, }, /* SXGA */ + { .width = 1280, .height = 720, }, /* HD */ + { .width = 800, .height = 600, }, /* SVGA */ + { .width = 854, .height = 480, }, /* WVGA */ + { .width = 640, .height = 480, }, /* VGA */ + { .width = 320, .height = 240, }, /* QVGA */ + { .width = 160, .height = 120, }, /* QQVGA */ +}; + /* Submit to UVC only the formats expected to be working (enough memory for the size, etc.) */ static void app_add_filtered_formats(void) { @@ -80,6 +97,7 @@ static void app_add_filtered_formats(void) for (int i = 0; video_caps.format_caps[i].pixelformat != 0; i++) { const struct video_format_cap *vcap = &video_caps.format_caps[i]; + int range_count = 0; app_add_format(vcap->pixelformat, vcap->width_min, vcap->height_min, has_std_fmts); @@ -87,6 +105,30 @@ static void app_add_filtered_formats(void) app_add_format(vcap->pixelformat, vcap->width_max, vcap->height_max, has_std_fmts); } + + if (vcap->width_step == 0 && vcap->height_step == 0) { + continue; + } + + /* RANGE Resolution processing */ + for (int j = 0; j < ARRAY_SIZE(video_common_fmts); j++) { + if (range_count >= CONFIG_VIDEO_MAX_RANGE_RESOLUTIONS) { + break; + } + if (!IN_RANGE(video_common_fmts[j].width, + vcap->width_min, vcap->width_max) || + !IN_RANGE(video_common_fmts[j].height, + vcap->height_min, vcap->height_max)) { + continue; + } + if ((video_common_fmts[j].width - vcap->width_min) % vcap->width_step || + (video_common_fmts[j].height - vcap->height_min) % vcap->height_step) { + continue; + } + + app_add_format(vcap->pixelformat, video_common_fmts[j].width, + video_common_fmts[j].height, has_std_fmts); + } } } From 99218f4ee4e4185aabb1836df75fcfca4d714e0d Mon Sep 17 00:00:00 2001 From: Alain Volmat Date: Sat, 11 Oct 2025 15:03:32 +0200 Subject: [PATCH 10/10] samples: usb: uvc: add stm32n6570_dk conf files Add board specific conf files for the stm32n6570_dk Signed-off-by: Alain Volmat --- samples/subsys/usb/uvc/boards/stm32n6570_dk.conf | 3 +++ .../subsys/usb/uvc/boards/stm32n6570_dk_stm32n657xx_fsbl.conf | 3 +++ .../subsys/usb/uvc/boards/stm32n6570_dk_stm32n657xx_sb.conf | 3 +++ 3 files changed, 9 insertions(+) create mode 100644 samples/subsys/usb/uvc/boards/stm32n6570_dk.conf create mode 100644 samples/subsys/usb/uvc/boards/stm32n6570_dk_stm32n657xx_fsbl.conf create mode 100644 samples/subsys/usb/uvc/boards/stm32n6570_dk_stm32n657xx_sb.conf diff --git a/samples/subsys/usb/uvc/boards/stm32n6570_dk.conf b/samples/subsys/usb/uvc/boards/stm32n6570_dk.conf new file mode 100644 index 0000000000000..ca2eeb033fcf1 --- /dev/null +++ b/samples/subsys/usb/uvc/boards/stm32n6570_dk.conf @@ -0,0 +1,3 @@ +# Enough for 2592x1944 YUYV frames +CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=10100000 +CONFIG_MAIN_STACK_SIZE=2048 diff --git a/samples/subsys/usb/uvc/boards/stm32n6570_dk_stm32n657xx_fsbl.conf b/samples/subsys/usb/uvc/boards/stm32n6570_dk_stm32n657xx_fsbl.conf new file mode 100644 index 0000000000000..ca2eeb033fcf1 --- /dev/null +++ b/samples/subsys/usb/uvc/boards/stm32n6570_dk_stm32n657xx_fsbl.conf @@ -0,0 +1,3 @@ +# Enough for 2592x1944 YUYV frames +CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=10100000 +CONFIG_MAIN_STACK_SIZE=2048 diff --git a/samples/subsys/usb/uvc/boards/stm32n6570_dk_stm32n657xx_sb.conf b/samples/subsys/usb/uvc/boards/stm32n6570_dk_stm32n657xx_sb.conf new file mode 100644 index 0000000000000..ca2eeb033fcf1 --- /dev/null +++ b/samples/subsys/usb/uvc/boards/stm32n6570_dk_stm32n657xx_sb.conf @@ -0,0 +1,3 @@ +# Enough for 2592x1944 YUYV frames +CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=10100000 +CONFIG_MAIN_STACK_SIZE=2048