diff --git a/drivers/video/video_shell.c b/drivers/video/video_shell.c index 1db58dab7b37f..ce260cb95d517 100644 --- a/drivers/video/video_shell.c +++ b/drivers/video/video_shell.c @@ -576,7 +576,7 @@ static int cmd_video_format(const struct shell *sh, size_t argc, char **argv) ret = video_shell_parse_in_out(sh, arg_in_out, &type); if (ret < 0) { - return -ret; + return ret; } fmt.type = caps.type = type; @@ -1045,6 +1045,137 @@ static void complete_video_format_dev(size_t idx, struct shell_static_entry *ent } SHELL_DYNAMIC_CMD_CREATE(dsub_video_format_dev, complete_video_format_dev); +/* Video selection handling */ + +static void video_shell_print_selection(const struct shell *sh, struct video_selection *sel) +{ + shell_print(sh, "\tselection target: %s: (%u,%u)/%ux%u", + sel->target == VIDEO_SEL_TGT_CROP ? "crop" : + sel->target == VIDEO_SEL_TGT_CROP_BOUND ? "crop_bound" : + sel->target == VIDEO_SEL_TGT_NATIVE_SIZE ? "native_size" : + sel->target == VIDEO_SEL_TGT_COMPOSE ? "compose" : "unknown", + sel->rect.left, sel->rect.top, sel->rect.width, sel->rect.height); +} + +static int video_shell_selection_parse_target(const struct shell *sh, char const *arg_target, + enum video_selection_target *sel_target) +{ + if (strcmp(arg_target, "crop") == 0) { + *sel_target = VIDEO_SEL_TGT_CROP; + } else if (strcmp(arg_target, "crop_bound") == 0) { + *sel_target = VIDEO_SEL_TGT_CROP_BOUND; + } else if (strcmp(arg_target, "native_size") == 0) { + *sel_target = VIDEO_SEL_TGT_NATIVE_SIZE; + } else if (strcmp(arg_target, "compose") == 0) { + *sel_target = VIDEO_SEL_TGT_COMPOSE; + } else if (strcmp(arg_target, "compose_bound") == 0) { + *sel_target = VIDEO_SEL_TGT_COMPOSE_BOUND; + } else { + shell_error(sh, + "Target must be crop, crop_bound, native_size, compose or compose_bound"); + return -EINVAL; + } + + return 0; +} + +static int video_shell_selection_parse_rect(const struct shell *sh, char **args_rect, + struct video_rect *rect) +{ + char *arg_left = args_rect[0]; + char *arg_top = args_rect[1]; + char *arg_width_height = args_rect[2]; + char *end_size; + + rect->left = strtoul(arg_left, &end_size, 10); + if (*end_size != '\0') { + shell_error(sh, + "Invalid left value in rect x parameters"); + return -EINVAL; + } + + rect->top = strtoul(arg_top, &end_size, 10); + if (*end_size != '\0') { + shell_error(sh, + "Invalid top value in rect x parameters"); + return -EINVAL; + } + + rect->width = strtoul(arg_width_height, &end_size, 10); + if (*end_size != 'x' || rect->width == 0) { + shell_error(sh, + "Invalid width value in rect left> x parameters"); + return -EINVAL; + } + end_size++; + + rect->height = strtoul(end_size, &end_size, 10); + if (*end_size != '\0' || rect->height == 0) { + shell_error(sh, + "Invalid height value in rect left> x parameters"); + return -EINVAL; + } + + return 0; +} + +static int cmd_video_selection(const struct shell *sh, size_t argc, char **argv) +{ + const struct device *dev; + struct video_selection sel = {0}; + char *arg_device = argv[1]; + char *arg_in_out = argv[2]; + char *arg_target = argv[3]; + int ret; + + dev = device_get_binding(arg_device); + ret = video_shell_check_device(sh, dev); + if (ret < 0) { + return ret; + } + + ret = video_shell_parse_in_out(sh, arg_in_out, &sel.type); + if (ret < 0) { + return ret; + } + + ret = video_shell_selection_parse_target(sh, arg_target, &sel.target); + if (ret < 0) { + return ret; + } + + switch (argc) { + case 4: + ret = video_get_selection(dev, &sel); + if (ret < 0) { + shell_error(sh, "Failed to get %s selection", dev->name); + return -ENODEV; + } + + video_shell_print_selection(sh, &sel); + break; + case 7: + ret = video_shell_selection_parse_rect(sh, &argv[4], &sel.rect); + if (ret < 0) { + return ret; + } + + ret = video_set_selection(dev, &sel); + if (ret < 0) { + shell_error(sh, "Failed to set %s selection", dev->name); + return -ENODEV; + } + + video_shell_print_selection(sh, &sel); + break; + default: + shell_error(sh, "Wrong parameter count"); + return -EINVAL; + } + + return 0; +} + /* Video shell commands declaration */ SHELL_STATIC_SUBCMD_SET_CREATE(sub_video_cmds, @@ -1070,6 +1201,10 @@ SHELL_STATIC_SUBCMD_SET_CREATE(sub_video_cmds, SHELL_HELP("Query or set video controls of a device", " [ ]"), cmd_video_ctrl, 2, 2), + SHELL_CMD_ARG(selection, &dsub_video_format_dev, + SHELL_HELP("Query or set the video selection of a device", + " [ x]"), + cmd_video_selection, 4, 3), SHELL_SUBCMD_SET_END ); diff --git a/drivers/video/video_stm32_dcmipp.c b/drivers/video/video_stm32_dcmipp.c index 37d63fc46b5cb..7018b3ff8cdff 100644 --- a/drivers/video/video_stm32_dcmipp.c +++ b/drivers/video/video_stm32_dcmipp.c @@ -77,6 +77,8 @@ struct stm32_dcmipp_pipe_data { struct stm32_dcmipp_data *dcmipp; struct k_mutex lock; struct video_format fmt; + struct video_rect crop; + struct video_rect compose; struct k_fifo fifo_in; struct k_fifo fifo_out; struct video_buffer *next; @@ -479,6 +481,17 @@ static const struct stm32_dcmipp_mapping *stm32_dcmipp_get_mapping(uint32_t pixe return NULL; } +static inline void stm32_dcmipp_compute_fmt_pitch(uint32_t pipe_id, struct video_format *fmt) +{ + fmt->pitch = fmt->width * video_bits_per_pixel(fmt->pixelformat) / BITS_PER_BYTE; +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + if (pipe_id == DCMIPP_PIPE1 || pipe_id == DCMIPP_PIPE2) { + /* On Pipe1 and Pipe2, the pitch must be multiple of 16 bytes */ + fmt->pitch = ROUND_UP(fmt->pitch, 16); + } +#endif +} + static int stm32_dcmipp_set_fmt(const struct device *dev, struct video_format *fmt) { struct stm32_dcmipp_pipe_data *pipe = dev->data; @@ -510,13 +523,13 @@ static int stm32_dcmipp_set_fmt(const struct device *dev, struct video_format *f return -EINVAL; } - fmt->pitch = fmt->width * video_bits_per_pixel(fmt->pixelformat) / BITS_PER_BYTE; -#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) - if (pipe->id == DCMIPP_PIPE1 || pipe->id == DCMIPP_PIPE2) { - /* On Pipe1 and Pipe2, the pitch must be multiple of 16 bytes */ - fmt->pitch = ROUND_UP(fmt->pitch, 16); + if (fmt->width != pipe->compose.width || fmt->height != pipe->compose.height) { + LOG_ERR("Format width/height (%d x %d) do not match compose width/height (%d x %d)", + fmt->width, fmt->height, pipe->compose.width, pipe->compose.height); + return -EINVAL; } -#endif + + stm32_dcmipp_compute_fmt_pitch(pipe->id, fmt); k_mutex_lock(&pipe->lock, K_FOREVER); @@ -617,6 +630,67 @@ static int stm32_dcmipp_get_fmt(const struct device *dev, struct video_format *f return 0; } +static int stm32_dcmipp_set_crop(struct stm32_dcmipp_pipe_data *pipe) +{ + struct stm32_dcmipp_data *dcmipp = pipe->dcmipp; + DCMIPP_CropConfTypeDef crop_cfg; + uint32_t frame_width = dcmipp->source_fmt.width; + uint32_t frame_height = dcmipp->source_fmt.height; + int ret; + +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + if (pipe->id == DCMIPP_PIPE1 || pipe->id == DCMIPP_PIPE2) { + frame_width /= dcmipp->isp_dec_hratio; + frame_height /= dcmipp->isp_dec_vratio; + } +#endif + + /* If crop area is equal to frame size, disable the crop */ + if (pipe->crop.width == frame_width && pipe->crop.height == frame_height) { + ret = HAL_DCMIPP_PIPE_DisableCrop(&dcmipp->hdcmipp, pipe->id); + if (ret != HAL_OK) { + LOG_ERR("Failed to disable pipe crop"); + return -EIO; + } + + return 0; + } + + crop_cfg.VStart = pipe->crop.top; + crop_cfg.VSize = pipe->crop.height; + + /* + * In case of Pipe0, left & width are expressed in word (32bit). + * set_selection ensure that value leads to a multiple of 32bit word + */ + if (pipe->id == DCMIPP_PIPE0) { + unsigned int bpp = video_bits_per_pixel(pipe->fmt.pixelformat); + + crop_cfg.HStart = pipe->crop.left * bpp / 32; + crop_cfg.HSize = pipe->crop.width * bpp / 32; + } +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + else { + crop_cfg.HStart = pipe->crop.left; + crop_cfg.HSize = pipe->crop.width; + } +#endif + + ret = HAL_DCMIPP_PIPE_SetCropConfig(&dcmipp->hdcmipp, pipe->id, &crop_cfg); + if (ret != HAL_OK) { + LOG_ERR("Failed to configure pipe crop"); + return -EIO; + } + + ret = HAL_DCMIPP_PIPE_EnableCrop(&dcmipp->hdcmipp, pipe->id); + if (ret != HAL_OK) { + LOG_ERR("Failed to enable pipe crop"); + return -EIO; + } + + return 0; +} + #if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) #define STM32_DCMIPP_DSIZE_HVRATIO_CONS 8192 #define STM32_DCMIPP_DSIZE_HVRATIO_MAX 65535 @@ -625,15 +699,15 @@ static int stm32_dcmipp_get_fmt(const struct device *dev, struct video_format *f static int stm32_dcmipp_set_downscale(struct stm32_dcmipp_pipe_data *pipe) { struct stm32_dcmipp_data *dcmipp = pipe->dcmipp; - uint32_t post_decimate_width = dcmipp->source_fmt.width / dcmipp->isp_dec_hratio; - uint32_t post_decimate_height = dcmipp->source_fmt.height / dcmipp->isp_dec_vratio; + uint32_t post_decimate_width = pipe->crop.width; + uint32_t post_decimate_height = pipe->crop.height; DCMIPP_DecimationConfTypeDef dec_cfg; DCMIPP_DownsizeTypeDef downsize_cfg; - struct video_format *fmt = &pipe->fmt; + struct video_rect *compose = &pipe->compose; uint32_t hdec = 1, vdec = 1; int ret; - if (fmt->width == post_decimate_width && fmt->height == post_decimate_height) { + if (compose->width == pipe->crop.width && compose->height == pipe->crop.height) { ret = HAL_DCMIPP_PIPE_DisableDecimation(&dcmipp->hdcmipp, pipe->id); if (ret != HAL_OK) { LOG_ERR("Failed to disable the pipe decimation"); @@ -650,11 +724,11 @@ static int stm32_dcmipp_set_downscale(struct stm32_dcmipp_pipe_data *pipe) } /* Compute decimation factors (HDEC/VDEC) */ - while (fmt->width * STM32_DCMIPP_MAX_PIPE_DSIZE_FACTOR < post_decimate_width) { + while (compose->width * STM32_DCMIPP_MAX_PIPE_DSIZE_FACTOR < post_decimate_width) { hdec *= 2; post_decimate_width /= 2; } - while (fmt->height * STM32_DCMIPP_MAX_PIPE_DSIZE_FACTOR < post_decimate_height) { + while (compose->height * STM32_DCMIPP_MAX_PIPE_DSIZE_FACTOR < post_decimate_height) { vdec *= 2; post_decimate_height /= 2; } @@ -683,25 +757,28 @@ static int stm32_dcmipp_set_downscale(struct stm32_dcmipp_pipe_data *pipe) } /* Compute downsize factor */ - downsize_cfg.HRatio = post_decimate_width * STM32_DCMIPP_DSIZE_HVRATIO_CONS / fmt->width; + downsize_cfg.HRatio = + post_decimate_width * STM32_DCMIPP_DSIZE_HVRATIO_CONS / compose->width; if (downsize_cfg.HRatio > STM32_DCMIPP_DSIZE_HVRATIO_MAX) { downsize_cfg.HRatio = STM32_DCMIPP_DSIZE_HVRATIO_MAX; } - downsize_cfg.VRatio = post_decimate_height * STM32_DCMIPP_DSIZE_HVRATIO_CONS / fmt->height; + downsize_cfg.VRatio = + post_decimate_height * STM32_DCMIPP_DSIZE_HVRATIO_CONS / compose->height; if (downsize_cfg.VRatio > STM32_DCMIPP_DSIZE_HVRATIO_MAX) { downsize_cfg.VRatio = STM32_DCMIPP_DSIZE_HVRATIO_MAX; } - downsize_cfg.HDivFactor = STM32_DCMIPP_DSIZE_HVDIV_CONS * fmt->width / post_decimate_width; + downsize_cfg.HDivFactor = + STM32_DCMIPP_DSIZE_HVDIV_CONS * compose->width / post_decimate_width; if (downsize_cfg.HDivFactor > STM32_DCMIPP_DSIZE_HVDIV_MAX) { downsize_cfg.HDivFactor = STM32_DCMIPP_DSIZE_HVDIV_MAX; } downsize_cfg.VDivFactor = - STM32_DCMIPP_DSIZE_HVDIV_CONS * fmt->height / post_decimate_height; + STM32_DCMIPP_DSIZE_HVDIV_CONS * compose->height / post_decimate_height; if (downsize_cfg.VDivFactor > STM32_DCMIPP_DSIZE_HVDIV_MAX) { downsize_cfg.VDivFactor = STM32_DCMIPP_DSIZE_HVDIV_MAX; } - downsize_cfg.HSize = fmt->width; - downsize_cfg.VSize = fmt->height; + downsize_cfg.HSize = compose->width; + downsize_cfg.VSize = compose->height; ret = HAL_DCMIPP_PIPE_SetDownsizeConfig(&dcmipp->hdcmipp, pipe->id, &downsize_cfg); if (ret != HAL_OK) { @@ -881,6 +958,12 @@ static int stm32_dcmipp_stream_enable(const struct device *dev) } if (pipe->id == DCMIPP_PIPE0) { + /* Configure the Pipe crop */ + ret = stm32_dcmipp_set_crop(pipe); + if (ret < 0) { + goto out; + } + /* Only the PIPE0 has a limiter */ /* Set Limiter to avoid buffer overflow, in number of 32 bits words */ ret = HAL_DCMIPP_PIPE_EnableLimitEvent(&dcmipp->hdcmipp, DCMIPP_PIPE0, @@ -932,6 +1015,12 @@ static int stm32_dcmipp_stream_enable(const struct device *dev) } } + /* Configure the Pipe crop */ + ret = stm32_dcmipp_set_crop(pipe); + if (ret < 0) { + goto out; + } + /* Configure the Pipe decimation / downsize */ ret = stm32_dcmipp_set_downscale(pipe); if (ret < 0) { @@ -1183,6 +1272,173 @@ static int stm32_dcmipp_enum_frmival(const struct device *dev, struct video_frmi return video_enum_frmival(config->source_dev, fie); } +static inline int stm32_dcmipp_pipe0_pixel_align(uint32_t pixelformat) +{ + unsigned int bpp = video_bits_per_pixel(pixelformat); + + /* + * Pipe0 crop work in number of lines (vertically) but in 32bit words horizontally. + * So capabilities of crop depends on the format. As an example, if the format is + * 8bpp, then step will be 4 pixels, for 16bpp, step will be 2 pixels, + * for 32bpp, step will be 1 pixel, and for 24bpp step will be 4 pixels + */ + + if (bpp == 8 || bpp == 24) { + return 4; + } + if (bpp == 16) { + return 2; + } + if (bpp == 32) { + return 1; + } + + /* Unknown bpp */ + return 0; +} + +static int stm32_dcmipp_set_selection(const struct device *dev, struct video_selection *sel) +{ + struct stm32_dcmipp_pipe_data *pipe = dev->data; + struct stm32_dcmipp_data *dcmipp = pipe->dcmipp; + uint32_t frame_width = dcmipp->source_fmt.width; + uint32_t frame_height = dcmipp->source_fmt.height; + + if (sel->type != VIDEO_BUF_TYPE_OUTPUT) { + return -EINVAL; + } + +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + if (pipe->id == DCMIPP_PIPE1 || pipe->id == DCMIPP_PIPE2) { + frame_width /= dcmipp->isp_dec_hratio; + frame_height /= dcmipp->isp_dec_vratio; + } +#endif + + switch (sel->target) { + case VIDEO_SEL_TGT_CROP: + /* Reset to the whole frame if the requested rectangle isn't part of the frame */ + if (!IN_RANGE(sel->rect.top, 0, frame_height - 1) || + !IN_RANGE(sel->rect.height, 1, frame_height - sel->rect.top) || + !IN_RANGE(sel->rect.left, 0, frame_width - 1) || + !IN_RANGE(sel->rect.width, 1, frame_width - sel->rect.left)) { + sel->rect.top = 0; + sel->rect.left = 0; + sel->rect.width = frame_width; + sel->rect.height = frame_height; + } + + /* + * Adjust value to horizontal alignment constraints for PIPE0 + * except if crop area is the full frame + */ + if (pipe->id == DCMIPP_PIPE0 && + !(sel->rect.left == 0 || sel->rect.width == frame_width)) { + int h_pixel_align = stm32_dcmipp_pipe0_pixel_align(pipe->fmt.pixelformat); + + if (h_pixel_align == 0) { + LOG_ERR("Cannot figure out required pixel alignment"); + return -EIO; + } + + sel->rect.left = ROUND_DOWN(sel->rect.left, h_pixel_align); + sel->rect.width = ROUND_DOWN(sel->rect.width, h_pixel_align); + } + + k_mutex_lock(&pipe->lock, K_FOREVER); + pipe->crop = sel->rect; + pipe->compose.width = sel->rect.width; + pipe->compose.height = sel->rect.height; + pipe->fmt.width = sel->rect.width; + pipe->fmt.height = sel->rect.height; + stm32_dcmipp_compute_fmt_pitch(pipe->id, &pipe->fmt); + k_mutex_unlock(&pipe->lock); + break; + case VIDEO_SEL_TGT_COMPOSE: + /* Compose not available on Pipe0 */ + if (pipe->id == DCMIPP_PIPE0) { + sel->rect = pipe->crop; + goto out; + } + + if (sel->rect.left != 0) { + sel->rect.left = 0; + } + if (sel->rect.top != 0) { + sel->rect.top = 0; + } + + if (!IN_RANGE(sel->rect.width, + pipe->crop.width / STM32_DCMIPP_MAX_PIPE_SCALE_FACTOR, + pipe->crop.width)) { + sel->rect.width = pipe->crop.width / STM32_DCMIPP_MAX_PIPE_SCALE_FACTOR; + } + + if (!IN_RANGE(sel->rect.height, + pipe->crop.height / STM32_DCMIPP_MAX_PIPE_SCALE_FACTOR, + pipe->crop.height)) { + sel->rect.height = pipe->crop.height / STM32_DCMIPP_MAX_PIPE_SCALE_FACTOR; + } + + k_mutex_lock(&pipe->lock, K_FOREVER); + pipe->compose = sel->rect; + pipe->fmt.width = sel->rect.width; + pipe->fmt.height = sel->rect.height; + stm32_dcmipp_compute_fmt_pitch(pipe->id, &pipe->fmt); + k_mutex_unlock(&pipe->lock); + break; + default: + return -EINVAL; + }; + +out: + return 0; +} + +static int stm32_dcmipp_get_selection(const struct device *dev, struct video_selection *sel) +{ + struct stm32_dcmipp_pipe_data *pipe = dev->data; + struct stm32_dcmipp_data *dcmipp = pipe->dcmipp; + + if (sel->type != VIDEO_BUF_TYPE_OUTPUT) { + return -EINVAL; + } + + switch (sel->target) { + case VIDEO_SEL_TGT_CROP: + sel->rect = pipe->crop; + break; + case VIDEO_SEL_TGT_COMPOSE: + sel->rect = pipe->compose; + break; + case VIDEO_SEL_TGT_CROP_BOUND: + case VIDEO_SEL_TGT_COMPOSE_BOUND: + sel->rect.top = 0; + sel->rect.left = 0; + if (pipe->id == DCMIPP_PIPE0) { + sel->rect.width = dcmipp->source_fmt.width; + sel->rect.height = dcmipp->source_fmt.height; + } +#if defined(STM32_DCMIPP_HAS_PIXEL_PIPES) + else if (pipe->id == DCMIPP_PIPE1 || pipe->id == DCMIPP_PIPE2) { + sel->rect.width = dcmipp->source_fmt.width / dcmipp->isp_dec_hratio; + sel->rect.height = dcmipp->source_fmt.height / dcmipp->isp_dec_vratio; + } +#endif + break; + case VIDEO_SEL_TGT_NATIVE_SIZE: + sel->rect.top = 0; + sel->rect.left = 0; + sel->rect.width = dcmipp->source_fmt.width; + sel->rect.height = dcmipp->source_fmt.height; + break; + default: + return -EINVAL; + }; + + return 0; +} + static DEVICE_API(video, stm32_dcmipp_driver_api) = { .set_format = stm32_dcmipp_set_fmt, .get_format = stm32_dcmipp_get_fmt, @@ -1193,6 +1449,8 @@ static DEVICE_API(video, stm32_dcmipp_driver_api) = { .get_frmival = stm32_dcmipp_get_frmival, .set_frmival = stm32_dcmipp_set_frmival, .enum_frmival = stm32_dcmipp_enum_frmival, + .set_selection = stm32_dcmipp_set_selection, + .get_selection = stm32_dcmipp_get_selection, }; static int stm32_dcmipp_enable_clock(const struct device *dev) @@ -1310,7 +1568,16 @@ static int stm32_dcmipp_pipe_init(const struct device *dev) k_fifo_init(&pipe->fifo_in); k_fifo_init(&pipe->fifo_out); - /* TODO - need to init formats to valid values */ + /* Initialize format/crop/compose */ + pipe->fmt.type = VIDEO_BUF_TYPE_OUTPUT; + pipe->fmt.width = dcmipp->source_fmt.width; + pipe->fmt.height = dcmipp->source_fmt.height; + pipe->fmt.pixelformat = dcmipp->source_fmt.pixelformat; + pipe->crop.top = 0; + pipe->crop.left = 0; + pipe->crop.width = pipe->fmt.width; + pipe->crop.height = pipe->fmt.height; + pipe->compose = pipe->crop; /* Store the pipe data pointer into dcmipp data structure */ dcmipp->pipe[pipe->id] = pipe; diff --git a/include/zephyr/drivers/video.h b/include/zephyr/drivers/video.h index 21025413724d5..c359859a4c9fe 100644 --- a/include/zephyr/drivers/video.h +++ b/include/zephyr/drivers/video.h @@ -7,6 +7,7 @@ /* * Copyright (c) 2019 Linaro Limited. * Copyright 2025 NXP + * Copyright (c) 2025 STMicroelectronics * * SPDX-License-Identifier: Apache-2.0 */ @@ -237,6 +238,57 @@ enum video_signal_result { VIDEO_BUF_ERROR, }; +/** + * @struct video_selection_target + * @brief Video selection target enum + * + * Used to indicate which selection to query or set on a video device + */ +enum video_selection_target { + /** Current crop setting */ + VIDEO_SEL_TGT_CROP, + /** Crop bound (aka the maximum crop achievable) */ + VIDEO_SEL_TGT_CROP_BOUND, + /** Native size of the input frame */ + VIDEO_SEL_TGT_NATIVE_SIZE, + /** Current compose setting */ + VIDEO_SEL_TGT_COMPOSE, + /** Compose bound (aka the maximum compose achievable) */ + VIDEO_SEL_TGT_COMPOSE_BOUND, +}; + +/** + * @struct video_rect + * @brief Description of a rectangle area. + * + * Used for crop/compose and possibly within drivers as well + */ +struct video_rect { + /** left offset of selection rectangle */ + uint32_t left; + /** top offset of selection rectangle */ + uint32_t top; + /** width of selection rectangle */ + uint32_t width; + /** height of selection rectangle */ + uint32_t height; +}; + +/** + * @struct video_selection + * @brief Video selection (crop / compose) structure + * + * Used to describe the query and set selection target on a video device + */ +struct video_selection { + /** buffer type, allow to select for device having both input and output */ + enum video_buf_type type; + /** selection target enum */ + enum video_selection_target target; + /** selection target rectangle */ + struct video_rect rect; +}; + /** * @typedef video_api_format_t * @brief Function pointer type for video_set/get_format() @@ -327,6 +379,14 @@ typedef int (*video_api_get_caps_t)(const struct device *dev, struct video_caps */ typedef int (*video_api_set_signal_t)(const struct device *dev, struct k_poll_signal *sig); +/** + * @typedef video_api_selection_t + * @brief Get/Set video selection (crop / compose) + * + * See @ref video_set_selection and @ref video_get_selection for argument descriptions. + */ +typedef int (*video_api_selection_t)(const struct device *dev, struct video_selection *sel); + __subsystem struct video_driver_api { /* mandatory callbacks */ video_api_format_t set_format; @@ -343,6 +403,8 @@ __subsystem struct video_driver_api { video_api_frmival_t set_frmival; video_api_frmival_t get_frmival; video_api_enum_frmival_t enum_frmival; + video_api_selection_t set_selection; + video_api_selection_t get_selection; }; /** @@ -756,6 +818,72 @@ static inline int video_set_signal(const struct device *dev, struct k_poll_signa return api->set_signal(dev, sig); } +/** + * @brief Set video selection (crop/compose). + * + * Configure the optional crop and compose feature of a video device. + * Crop is first applied on the input frame, and the result of that crop is applied + * to the compose. The result of the compose (width/height) is equal to the format + * width/height given to the @ref video_set_format function. + * + * Some targets are inter-dependents. For instance, setting a @ref VIDEO_SEL_TGT_CROP will + * reset @ref VIDEO_SEL_TGT_COMPOSE to the same size. + * + * @param dev Pointer to the device structure for the driver instance. + * @param sel Pointer to a video selection structure + * + * @retval 0 Is successful. + * @retval -EINVAL If parameters are invalid. + * @retval -ENOTSUP If format is not supported. + * @retval -EIO General input / output error. + */ +static inline int video_set_selection(const struct device *dev, struct video_selection *sel) +{ + const struct video_driver_api *api; + + __ASSERT_NO_MSG(dev != NULL); + __ASSERT_NO_MSG(sel != NULL); + + api = (const struct video_driver_api *)dev->api; + if (api->set_selection == NULL) { + return -ENOSYS; + } + + return api->set_selection(dev, sel); +} + +/** + * @brief Get video selection (crop/compose). + * + * Retrieve the current settings related to the crop and compose of the video device. + * This can also be used to read the native size of the input stream of the video + * device. + * This function can be used to read crop / compose capabilities of the device prior + * to performing configuration via the @ref video_set_selection api. + * + * @param dev Pointer to the device structure for the driver instance. + * @param sel Pointer to a video selection structure, @c type and @c target set by the caller + * + * @retval 0 Is successful. + * @retval -EINVAL If parameters are invalid. + * @retval -ENOTSUP If format is not supported. + * @retval -EIO General input / output error. + */ +static inline int video_get_selection(const struct device *dev, struct video_selection *sel) +{ + const struct video_driver_api *api; + + __ASSERT_NO_MSG(dev != NULL); + __ASSERT_NO_MSG(sel != NULL); + + api = (const struct video_driver_api *)dev->api; + if (api->get_selection == NULL) { + return -ENOSYS; + } + + return api->get_selection(dev, sel); +} + /** * @brief Allocate aligned video buffer. * diff --git a/samples/drivers/video/capture/Kconfig b/samples/drivers/video/capture/Kconfig index 9a1e54aa451b6..362a6037a6cf1 100644 --- a/samples/drivers/video/capture/Kconfig +++ b/samples/drivers/video/capture/Kconfig @@ -5,6 +5,32 @@ mainmenu "Video capture sample application" menu "Video capture configuration" +config VIDEO_SOURCE_CROP_LEFT + int "Crop area left value" + default 0 + help + Left value of the crop area within the video source. + +config VIDEO_SOURCE_CROP_TOP + int "Crop area top value" + default 0 + help + Top value of the crop area within the video source. + +config VIDEO_SOURCE_CROP_WIDTH + int "Crop area width value" + default 0 + help + Width value of the crop area within the video source. + If set to 0, the crop is not applied. + +config VIDEO_SOURCE_CROP_HEIGHT + int "Crop area height value" + default 0 + help + Height value of the crop area within the video source. + If set to 0, the crop is not applied. + config VIDEO_FRAME_HEIGHT int "Height of the video frame" default 0 diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index ce9631bc8a533..b9e89d3b8067c 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -95,6 +95,12 @@ 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 = { + .type = VIDEO_BUF_TYPE_OUTPUT, + }; +#endif unsigned int frame = 0; size_t bsize; int i = 0; @@ -139,6 +145,22 @@ int main(void) return 0; } + /* 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)) { + LOG_ERR("Unable to set selection crop"); + return 0; + } + LOG_INF("Selection crop set to (%u,%u)/%ux%u", + 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 @@ -147,6 +169,31 @@ 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); }