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/drivers/video/video_stm32_dcmipp.c b/drivers/video/video_stm32_dcmipp.c index 24db419289586..ff0ec996aa089 100644 --- a/drivers/video/video_stm32_dcmipp.c +++ b/drivers/video/video_stm32_dcmipp.c @@ -1331,22 +1331,94 @@ 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) diff --git a/include/zephyr/drivers/video.h b/include/zephyr/drivers/video.h index e50f49b0e382f..65e3fb3769c89 100644 --- a/include/zephyr/drivers/video.h +++ b/include/zephyr/drivers/video.h @@ -982,6 +982,27 @@ 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 as 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 Pointer to the video device struct to set format + * @param fmt Pointer to a video format struct. + * + * @retval 0 Is successful. + * @retval -EINVAL If parameters are invalid. + * @retval -ENOTSUP If format is not supported. + * @retval -EIO General input / output error. + */ +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. diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index a3274356b3457..80cbda7aa542f 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; } diff --git a/samples/subsys/usb/uvc/Kconfig b/samples/subsys/usb/uvc/Kconfig index d1b0c2bec39c3..d1f58f0bbf1a5 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 APP_VIDEO_MAX_RESOLUTIONS + int "Maximum number of advertised 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 7830c3a9427b9..18b3e144779d3 100644 --- a/samples/subsys/usb/uvc/src/main.c +++ b/samples/subsys/usb/uvc/src/main.c @@ -60,7 +60,7 @@ static int app_add_format(uint32_t pixfmt, uint32_t width, uint32_t height, bool } /* Set the format to get the size */ - 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 to %s %ux%u (size %u)", video_dev->name, VIDEO_FOURCC_TO_STR(fmt.pixelformat), @@ -81,6 +81,23 @@ static int app_add_format(uint32_t pixfmt, uint32_t width, uint32_t height, bool return ret; } +struct video_resolution { + uint16_t width; + uint16_t height; +}; + +static struct video_resolution video_common_fmts[] = { + { .width = 160, .height = 120, }, /* QQVGA */ + { .width = 320, .height = 240, }, /* QVGA */ + { .width = 640, .height = 480, }, /* VGA */ + { .width = 854, .height = 480, }, /* WVGA */ + { .width = 800, .height = 600, }, /* SVGA */ + { .width = 1280, .height = 720, }, /* HD */ + { .width = 1280, .height = 1024, }, /* SXGA */ + { .width = 1920, .height = 1080, }, /* FHD */ + { .width = 3840, .height = 2160, }, /* UHD */ +}; + /* Submit to UVC only the formats expected to be working (enough memory for the size, etc.) */ static int app_add_filtered_formats(void) { @@ -89,6 +106,7 @@ static int 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 count = 1; ret = app_add_format(vcap->pixelformat, vcap->width_min, vcap->height_min, has_sup_fmts); @@ -102,6 +120,39 @@ static int app_add_filtered_formats(void) if (ret != 0) { return ret; } + + count++; + } + + 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 (count >= CONFIG_APP_VIDEO_MAX_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; + } + + ret = app_add_format(vcap->pixelformat, video_common_fmts[j].width, + video_common_fmts[j].height, has_sup_fmts); + if (ret != 0) { + return ret; + } + + count++; } } @@ -178,7 +229,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_ERR("Could not set the format of %s to %s %ux%u (size %u)", video_dev->name, VIDEO_FOURCC_TO_STR(fmt.pixelformat), @@ -193,7 +244,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;