From f547b1f3276ceda2a6bb6415a8814114752aa2e9 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Tue, 16 Sep 2025 10:35:56 +0000 Subject: [PATCH 01/10] drivers: video: sw_generator: support test pattern CID Add an always-on CID for enabling the test pattern, which makes it possible to use it in tests enabling the test first enabling the pattern. Signed-off-by: Josuah Demangeon --- drivers/video/video_sw_generator.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/video/video_sw_generator.c b/drivers/video/video_sw_generator.c index 6c270646ef503..116e9b277b562 100644 --- a/drivers/video/video_sw_generator.c +++ b/drivers/video/video_sw_generator.c @@ -32,6 +32,7 @@ LOG_MODULE_REGISTER(video_sw_generator, CONFIG_VIDEO_LOG_LEVEL); struct sw_ctrls { struct video_ctrl hflip; + struct video_ctrl test_pattern; }; struct video_sw_generator_data { @@ -69,6 +70,11 @@ static const struct video_format_cap fmts[] = { {0}, }; +static const char *const test_pattern_menu[] = { + "Color bars", + NULL, +}; + static int video_sw_generator_set_fmt(const struct device *dev, struct video_format *fmt) { struct video_sw_generator_data *data = dev->data; @@ -454,6 +460,13 @@ static DEVICE_API(video, video_sw_generator_driver_api) = { static int video_sw_generator_init_controls(const struct device *dev) { struct video_sw_generator_data *data = dev->data; + int ret; + + ret = video_init_menu_ctrl(&data->ctrls.test_pattern, dev, VIDEO_CID_TEST_PATTERN, + 0, test_pattern_menu); + if (ret < 0) { + return ret; + } return video_init_ctrl(&data->ctrls.hflip, dev, VIDEO_CID_HFLIP, (struct video_ctrl_range){.min = 0, .max = 1, .step = 1, .def = 0}); From c858bd34d6c278bdf2458d662dd9f796214807aa Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Mon, 15 Sep 2025 13:56:20 +0000 Subject: [PATCH 02/10] samples: drivers: video: capture: use static initializers When possible, use static initializer instead of memset() and initialization at runtime. Fixes a bug where uninitialized memory of caps would be used, in case drivers do not initialize them like they should. Signed-off-by: Josuah Demangeon --- samples/drivers/video/capture/src/main.c | 29 ++++++++++++------------ 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index 80cbda7aa542f..d1d0ed0efc3e2 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -93,13 +93,11 @@ int main(void) { struct video_buffer *vbuf = &(struct video_buffer){}; const struct device *video_dev; - struct video_format fmt; - struct video_caps caps; - 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) - struct video_selection crop_sel = { + struct video_format fmt = { + .type = VIDEO_BUF_TYPE_OUTPUT, + }; +#if CONFIG_VIDEO_SOURCE_CROP_WIDTH && CONFIG_VIDEO_SOURCE_CROP_HEIGHT + struct video_selection sel = { .type = VIDEO_BUF_TYPE_OUTPUT, .target = VIDEO_SEL_TGT_CROP; .rect.left = CONFIG_VIDEO_SOURCE_CROP_LEFT; @@ -108,6 +106,13 @@ int main(void) .rect.height = CONFIG_VIDEO_SOURCE_CROP_HEIGHT; }; #endif + struct video_caps caps = { + .type = VIDEO_BUF_TYPE_OUTPUT, + }; + struct video_frmival frmival = {}; + struct video_frmival_enum fie = { + .format = &fmt, + }; unsigned int frame = 0; int i = 0; int err; @@ -127,7 +132,6 @@ int main(void) LOG_INF("Video device: %s", video_dev->name); /* Get capabilities */ - caps.type = type; if (video_get_caps(video_dev, &caps)) { LOG_ERR("Unable to retrieve video capabilities"); return 0; @@ -145,7 +149,6 @@ int main(void) } /* Get default/native format */ - fmt.type = type; if (video_get_format(video_dev, &fmt)) { LOG_ERR("Unable to retrieve video format"); return 0; @@ -187,8 +190,6 @@ int main(void) } LOG_INF("- Supported frame intervals for the default format:"); - memset(&fie, 0, sizeof(fie)); - fie.format = &fmt; while (video_enum_frmival(video_dev, &fie) == 0) { if (fie.type == VIDEO_FRMIVAL_TYPE_DISCRETE) { LOG_INF(" %u/%u", fie.discrete.numerator, fie.discrete.denominator); @@ -266,12 +267,12 @@ int main(void) LOG_ERR("Unable to alloc video buffer"); return 0; } - vbuf->type = type; + vbuf->type = VIDEO_BUF_TYPE_OUTPUT; video_enqueue(video_dev, vbuf); } /* Start video capture */ - if (video_stream_start(video_dev, type)) { + if (video_stream_start(video_dev, VIDEO_BUF_TYPE_OUTPUT)) { LOG_ERR("Unable to start capture (interface)"); return 0; } @@ -279,7 +280,7 @@ int main(void) LOG_INF("Capture started"); /* Grab video frames */ - vbuf->type = type; + vbuf->type = VIDEO_BUF_TYPE_OUTPUT; while (1) { err = video_dequeue(video_dev, &vbuf, K_FOREVER); if (err) { From 8e2ae55ce576f7809af81406c76141c3d20abec3 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Tue, 16 Sep 2025 10:14:02 +0000 Subject: [PATCH 03/10] samples: drivers: video: capture: change log verbosity Increase log verbosity to print one line per captured frames and make it like in the README by default. This avoids giving the impression that the capture sample does not work. Users desiring to get better performance can disable logging or reduce verbosity from INF to WRN. Signed-off-by: Josuah Demangeon --- samples/drivers/video/capture/src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index d1d0ed0efc3e2..92e11d7c7d5b2 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -288,7 +288,7 @@ int main(void) return 0; } - LOG_DBG("Got frame %u! size: %u; timestamp %u ms", + LOG_INF("Got frame %u! size: %u; timestamp %u ms", frame++, vbuf->bytesused, vbuf->timestamp); #ifdef CONFIG_TEST From 2f2a4ae38fde84b4abeea8f298bf7624c288dc20 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Mon, 15 Sep 2025 13:29:41 +0000 Subject: [PATCH 04/10] samples: drivers: video: capture: use if() instead of #ifdef When possible, make use of C if statements instead of preprocessor #ifdef. This still allow conditional compilation by letting the compiler garbage-collect unused code. Signed-off-by: Josuah Demangeon --- samples/drivers/video/capture/src/main.c | 72 ++++++++++++++++-------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index 92e11d7c7d5b2..2f1f2f379da68 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -96,16 +96,6 @@ int main(void) struct video_format fmt = { .type = VIDEO_BUF_TYPE_OUTPUT, }; -#if CONFIG_VIDEO_SOURCE_CROP_WIDTH && CONFIG_VIDEO_SOURCE_CROP_HEIGHT - struct video_selection 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 struct video_caps caps = { .type = VIDEO_BUF_TYPE_OUTPUT, }; @@ -155,24 +145,56 @@ int main(void) } /* Set the crop setting if necessary */ -#if CONFIG_VIDEO_SOURCE_CROP_WIDTH && CONFIG_VIDEO_SOURCE_CROP_HEIGHT - if (video_set_selection(video_dev, &crop_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_SOURCE_CROP_WIDTH > 0 || CONFIG_VIDEO_SOURCE_CROP_HEIGHT > 0) { + struct video_selection sel = { + .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, + .type = VIDEO_BUF_TYPE_OUTPUT, + }; + + if (video_set_selection(video_dev, &sel)) { + LOG_ERR("Unable to set selection crop"); + return 0; + } -#if CONFIG_VIDEO_FRAME_HEIGHT - fmt.height = CONFIG_VIDEO_FRAME_HEIGHT; -#endif + LOG_INF("Selection crop set to (%u,%u)/%ux%u", + sel.rect.left, sel.rect.top, sel.rect.width, sel.rect.height); -#if CONFIG_VIDEO_FRAME_WIDTH - 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 (strcmp(CONFIG_VIDEO_PIXEL_FORMAT, "")) { + 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; + } + } + } + + if (CONFIG_VIDEO_FRAME_HEIGHT > 0) { + fmt.height = CONFIG_VIDEO_FRAME_HEIGHT; + } + if (CONFIG_VIDEO_FRAME_WIDTH > 0) { + fmt.width = CONFIG_VIDEO_FRAME_WIDTH; + } + if (strcmp(CONFIG_VIDEO_PIXEL_FORMAT, "") != 0) { fmt.pixelformat = VIDEO_FOURCC_FROM_STR(CONFIG_VIDEO_PIXEL_FORMAT); } From 33c08f846f1968b87a40adc5d21bf6ac61de47ef Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Mon, 15 Sep 2025 18:15:13 +0000 Subject: [PATCH 05/10] samples: drivers: video: capture: use app_ prefix Use an "app_" prefix for all functions local to the application. This avoids accidentally using "display_" or "video_" prefix that are reserved for the respective area. Signed-off-by: Josuah Demangeon --- samples/drivers/video/capture/src/main.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index 2f1f2f379da68..1f5f0accca9c7 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -27,7 +27,7 @@ LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL); #endif #if DT_HAS_CHOSEN(zephyr_display) -static inline int display_setup(const struct device *const display_dev, const uint32_t pixfmt) +static inline int app_setup_display(const struct device *const display_dev, const uint32_t pixfmt) { struct display_capabilities capabilities; int ret = 0; @@ -74,9 +74,9 @@ static inline int display_setup(const struct device *const display_dev, const ui return ret; } -static inline void video_display_frame(const struct device *const display_dev, - const struct video_buffer *const vbuf, - const struct video_format fmt) +static inline void app_display_frame(const struct device *const display_dev, + const struct video_buffer *const vbuf, + const struct video_format fmt) { struct display_buffer_descriptor buf_desc = { .buf_size = vbuf->bytesused, @@ -264,7 +264,7 @@ int main(void) return 0; } - err = display_setup(display_dev, fmt.pixelformat); + err = app_setup_display(display_dev, fmt.pixelformat); if (err) { LOG_ERR("Unable to set up display"); return err; @@ -322,7 +322,7 @@ int main(void) #endif #if DT_HAS_CHOSEN(zephyr_display) - video_display_frame(display_dev, vbuf, fmt); + app_display_frame(display_dev, vbuf, fmt); #endif err = video_enqueue(video_dev, vbuf); From ca1bb30975b3d614988914da7ad886081fe2d381 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Mon, 15 Sep 2025 20:04:46 +0000 Subject: [PATCH 06/10] samples: drivers: video: capture: use the same error handling idiom Use the same error handling implementation as everywhere else in the video area and the majority of Zephyr: ret = error(); if (ret < 0) { return ret; } Signed-off-by: Josuah Demangeon --- samples/drivers/video/capture/src/main.c | 91 ++++++++++++++++-------- 1 file changed, 60 insertions(+), 31 deletions(-) diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index 1f5f0accca9c7..ae3f85a068fb9 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -58,8 +58,7 @@ static inline int app_setup_display(const struct device *const display_dev, cons default: return -ENOTSUP; } - - if (ret) { + if (ret < 0) { LOG_ERR("Unable to set display format"); return ret; } @@ -74,9 +73,9 @@ static inline int app_setup_display(const struct device *const display_dev, cons return ret; } -static inline void app_display_frame(const struct device *const display_dev, - const struct video_buffer *const vbuf, - const struct video_format fmt) +static int app_display_frame(const struct device *const display_dev, + const struct video_buffer *const vbuf, + const struct video_format fmt) { struct display_buffer_descriptor buf_desc = { .buf_size = vbuf->bytesused, @@ -85,7 +84,7 @@ static inline void app_display_frame(const struct device *const display_dev, .height = vbuf->bytesused / fmt.pitch, }; - display_write(display_dev, 0, vbuf->line_offset, &buf_desc, vbuf->buffer); + return display_write(display_dev, 0, vbuf->line_offset, &buf_desc, vbuf->buffer); } #endif @@ -105,7 +104,7 @@ int main(void) }; unsigned int frame = 0; int i = 0; - int err; + int ret; /* When the video shell is enabled, do not run the capture loop */ if (IS_ENABLED(CONFIG_VIDEO_SHELL)) { @@ -122,7 +121,8 @@ int main(void) LOG_INF("Video device: %s", video_dev->name); /* Get capabilities */ - if (video_get_caps(video_dev, &caps)) { + ret = video_get_caps(video_dev, &caps); + if (ret < 0) { LOG_ERR("Unable to retrieve video capabilities"); return 0; } @@ -139,7 +139,8 @@ int main(void) } /* Get default/native format */ - if (video_get_format(video_dev, &fmt)) { + ret = video_get_format(video_dev, &fmt); + if (ret < 0) { LOG_ERR("Unable to retrieve video format"); return 0; } @@ -155,7 +156,8 @@ int main(void) .type = VIDEO_BUF_TYPE_OUTPUT, }; - if (video_set_selection(video_dev, &sel)) { + ret = video_set_selection(video_dev, &sel); + if (ret < 0) { LOG_ERR("Unable to set selection crop"); return 0; } @@ -168,20 +170,20 @@ int main(void) * and if compose is necessary */ sel.target = VIDEO_SEL_TGT_CROP; - err = video_get_selection(video_dev, &sel); - if (err < 0 && err != -ENOSYS) { + ret = video_get_selection(video_dev, &sel); + if (ret < 0 && ret != -ENOSYS) { LOG_ERR("Unable to get selection crop"); return 0; } - if (err == 0 && (sel.rect.width != fmt.width || sel.rect.height != fmt.height)) { + if (ret == 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) { + ret = video_set_selection(video_dev, &sel); + if (ret < 0 && ret != -ENOSYS) { LOG_ERR("Unable to set selection compose"); return 0; } @@ -201,12 +203,19 @@ int main(void) LOG_INF("- Video format: %s %ux%u", VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height); - if (video_set_compose_format(video_dev, &fmt)) { + ret = video_set_compose_format(video_dev, &fmt); + if (ret < 0) { LOG_ERR("Unable to set format"); return 0; } - if (!video_get_frmival(video_dev, &frmival)) { + ret = video_get_frmival(video_dev, &frmival); + if (ret == -ENOTSUP || ret == -ENOSYS) { + LOG_WRN("The video source does not support frame rate control"); + } else if (ret < 0) { + LOG_ERR("Error while getting the frame interval"); + return ret; + } else { LOG_INF("- Default frame rate : %f fps", 1.0 * frmival.denominator / frmival.numerator); } @@ -229,7 +238,7 @@ int main(void) const struct device *last_dev = NULL; struct video_ctrl_query cq = {.dev = video_dev, .id = VIDEO_CTRL_FLAG_NEXT_CTRL}; - while (!video_query_ctrl(&cq)) { + while (video_query_ctrl(&cq) == 0) { if (cq.dev != last_dev) { last_dev = cq.dev; LOG_INF("\t\tdevice: %s", cq.dev->name); @@ -243,17 +252,29 @@ int main(void) int tp_set_ret = -ENOTSUP; if (IS_ENABLED(CONFIG_VIDEO_CTRL_HFLIP)) { - video_set_ctrl(video_dev, &ctrl); + ret = video_set_ctrl(video_dev, &ctrl); + if (ret < 0) { + LOG_ERR("Failed to set horizontal flip"); + return 0; + } } if (IS_ENABLED(CONFIG_VIDEO_CTRL_VFLIP)) { ctrl.id = VIDEO_CID_VFLIP; - video_set_ctrl(video_dev, &ctrl); + ret = video_set_ctrl(video_dev, &ctrl); + if (ret < 0) { + LOG_ERR("Failed to set vertical flip"); + return 0; + } } if (IS_ENABLED(CONFIG_TEST)) { ctrl.id = VIDEO_CID_TEST_PATTERN; - tp_set_ret = video_set_ctrl(video_dev, &ctrl); + ret = video_set_ctrl(video_dev, &ctrl); + if (ret < 0 && ret != -ENOTSUP) { + LOG_WRN("Failed to set the test pattern"); + } + tp_set_ret = ret; } #if DT_HAS_CHOSEN(zephyr_display) @@ -264,10 +285,10 @@ int main(void) return 0; } - err = app_setup_display(display_dev, fmt.pixelformat); - if (err) { + ret = app_setup_display(display_dev, fmt.pixelformat); + if (ret < 0) { LOG_ERR("Unable to set up display"); - return err; + return 0; } #endif @@ -290,11 +311,16 @@ int main(void) return 0; } vbuf->type = VIDEO_BUF_TYPE_OUTPUT; - video_enqueue(video_dev, vbuf); + ret = video_enqueue(video_dev, vbuf); + if (ret < 0) { + LOG_ERR("Failed to enqueue video buffer"); + return 0; + } } /* Start video capture */ - if (video_stream_start(video_dev, VIDEO_BUF_TYPE_OUTPUT)) { + ret = video_stream_start(video_dev, VIDEO_BUF_TYPE_OUTPUT); + if (ret < 0) { LOG_ERR("Unable to start capture (interface)"); return 0; } @@ -304,8 +330,8 @@ int main(void) /* Grab video frames */ vbuf->type = VIDEO_BUF_TYPE_OUTPUT; while (1) { - err = video_dequeue(video_dev, &vbuf, K_FOREVER); - if (err) { + ret = video_dequeue(video_dev, &vbuf, K_FOREVER); + if (ret < 0) { LOG_ERR("Unable to dequeue video buf"); return 0; } @@ -322,11 +348,14 @@ int main(void) #endif #if DT_HAS_CHOSEN(zephyr_display) - app_display_frame(display_dev, vbuf, fmt); + ret = app_display_frame(display_dev, vbuf, fmt); + if (ret != 0) { + LOG_WRN("Failed to display this frame"); + } #endif - err = video_enqueue(video_dev, vbuf); - if (err) { + ret = video_enqueue(video_dev, vbuf); + if (ret < 0) { LOG_ERR("Unable to requeue video buf"); return 0; } From c5cfe31ac1842fd33ce147f82733ee43bd21fcd9 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Tue, 16 Sep 2025 09:25:07 +0000 Subject: [PATCH 07/10] tests: drivers: video: test_pattern: new test from the sample Turn the test part of the sample code into a dedicated test into the tests/ directory instead of just samples/. Signed-off-by: Josuah Demangeon --- samples/drivers/video/capture/sample.yaml | 2 - .../video/capture/src/check_test_pattern.h | 154 ---------- samples/drivers/video/capture/src/main.c | 16 -- .../drivers/video/test_pattern/CMakeLists.txt | 8 + tests/drivers/video/test_pattern/Kconfig | 59 ++++ .../boards/native_sim_native_64.conf | 7 + tests/drivers/video/test_pattern/prj.conf | 4 + tests/drivers/video/test_pattern/src/main.c | 268 ++++++++++++++++++ .../drivers/video/test_pattern/testcase.yaml | 23 ++ 9 files changed, 369 insertions(+), 172 deletions(-) delete mode 100644 samples/drivers/video/capture/src/check_test_pattern.h create mode 100644 tests/drivers/video/test_pattern/CMakeLists.txt create mode 100644 tests/drivers/video/test_pattern/Kconfig create mode 100644 tests/drivers/video/test_pattern/boards/native_sim_native_64.conf create mode 100644 tests/drivers/video/test_pattern/prj.conf create mode 100644 tests/drivers/video/test_pattern/src/main.c create mode 100644 tests/drivers/video/test_pattern/testcase.yaml diff --git a/samples/drivers/video/capture/sample.yaml b/samples/drivers/video/capture/sample.yaml index 209411155eb75..1b843e1fcf26a 100644 --- a/samples/drivers/video/capture/sample.yaml +++ b/samples/drivers/video/capture/sample.yaml @@ -15,7 +15,6 @@ tests: - platform:stm32h7b3i_dk:SHIELD="st_b_cams_omv_mb1683" - platform:ek_ra8d1/r7fa8d1bhecbd:SHIELD="dvp_20pin_ov7670;rtkmipilcdb00000be" extra_configs: - - CONFIG_TEST=y - CONFIG_FPU=y harness: console harness_config: @@ -26,7 +25,6 @@ tests: - "Got frame \\d+" - "size: \\d+;" - "timestamp \\d+" - - "Pattern OK" platform_allow: - arduino_nicla_vision/stm32h747xx/m7 - mimxrt1064_evk/mimxrt1064 diff --git a/samples/drivers/video/capture/src/check_test_pattern.h b/samples/drivers/video/capture/src/check_test_pattern.h deleted file mode 100644 index 42dbe7c79e198..0000000000000 --- a/samples/drivers/video/capture/src/check_test_pattern.h +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2024 NXP - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef TEST_PATTERN_CHECK_H_ -#define TEST_PATTERN_CHECK_H_ - -#include - -#include - -#define LAB_THRESHOLD 10.0 - -#define BARS_NUM 8 -#define PIXELS_NUM 5 - -typedef struct { - double L; - double a; - double b; -} CIELAB; - -/* - * This is measured on a real 8-colorbar pattern generated by an ov5640 camera sensor. - * For other sensors, it can be slightly different. If it doesn't fit anymore, either - * this array or the LAB_THRESHOLD can be modified. - * - * {White, Yellow, Cyan, Green, Magenta, Red, Blue, Black} - */ -static const CIELAB colorbars_target[] = { - {100.0, 0.0053, -0.0104}, {97.1804, -21.2151, 91.3538}, {90.1352, -58.4675, 6.0570}, - {87.7630, -85.9469, 83.2128}, {56.6641, 95.0182, -66.9129}, {46.6937, 72.7494, 49.5801}, - {27.6487, 71.5662, -97.4712}, {1.3726, -2.8040, 2.0043}}; - -static inline CIELAB rgb888_to_lab(const uint8_t r, const uint8_t g, const uint8_t b) -{ - CIELAB lab; - - double r_lin = r / 255.0; - double g_lin = g / 255.0; - double b_lin = b / 255.0; - - r_lin = r_lin > 0.04045 ? pow((r_lin + 0.055) / 1.055, 2.4) : r_lin / 12.92; - g_lin = g_lin > 0.04045 ? pow((g_lin + 0.055) / 1.055, 2.4) : g_lin / 12.92; - b_lin = b_lin > 0.04045 ? pow((b_lin + 0.055) / 1.055, 2.4) : b_lin / 12.92; - - double x = r_lin * 0.4124 + g_lin * 0.3576 + b_lin * 0.1805; - double y = r_lin * 0.2126 + g_lin * 0.7152 + b_lin * 0.0722; - double z = r_lin * 0.0193 + g_lin * 0.1192 + b_lin * 0.9505; - - x /= 0.95047; - z /= 1.08883; - - x = x > 0.008856 ? pow(x, 1.0 / 3.0) : (7.787 * x) + (16.0 / 116.0); - y = y > 0.008856 ? pow(y, 1.0 / 3.0) : (7.787 * y) + (16.0 / 116.0); - z = z > 0.008856 ? pow(z, 1.0 / 3.0) : (7.787 * z) + (16.0 / 116.0); - - lab.L = 116.0 * y - 16.0; - lab.a = 500.0 * (x - y); - lab.b = 200.0 * (y - z); - - return lab; -} - -static inline CIELAB xrgb32_to_lab(const uint32_t color) -{ - uint8_t r = (color >> 16) & 0xFF; - uint8_t g = (color >> 8) & 0xFF; - uint8_t b = color & 0xFF; - - return rgb888_to_lab(r, g, b); -} - -static inline CIELAB rgb565_to_lab(const uint16_t color) -{ - uint8_t r5 = (color >> 11) & 0x1F; - uint8_t g6 = (color >> 5) & 0x3F; - uint8_t b5 = color & 0x1F; - - /* Convert RGB565 to RGB888 */ - uint8_t r = (r5 * 255) / 31; - uint8_t g = (g6 * 255) / 63; - uint8_t b = (b5 * 255) / 31; - - return rgb888_to_lab(r, g, b); -} - -static inline void sum_lab(CIELAB *sum, const CIELAB lab) -{ - sum->L += lab.L; - sum->a += lab.a; - sum->b += lab.b; -} - -static inline void average_lab(CIELAB *lab, const uint32_t count) -{ - if (count > 0) { - lab->L /= count; - lab->a /= count; - lab->b /= count; - } -} - -static inline double deltaE(const CIELAB lab1, const CIELAB lab2) -{ - return sqrt(pow(lab1.L - lab2.L, 2) + pow(lab1.a - lab2.a, 2) + pow(lab1.b - lab2.b, 2)); -} - -/* - * As color values may vary near the boundary of each bar and also, for computational - * efficiency, check only a small number of pixels (PIXELS_NUM) in the middle of each bar. - */ -static inline bool is_colorbar_ok(const uint8_t *const buf, const struct video_format fmt) -{ - int i; - int bw = fmt.width / BARS_NUM; - CIELAB colorbars[BARS_NUM] = {0}; - - for (int h = 0; h < fmt.height; h++) { - for (i = 0; i < BARS_NUM; i++) { - if (fmt.pixelformat == VIDEO_PIX_FMT_XRGB32) { - uint32_t *pixel = - (uint32_t *)&buf[4 * (h * fmt.width + bw / 2 + i * bw)]; - - for (int j = -PIXELS_NUM / 2; j <= PIXELS_NUM / 2; j++) { - sum_lab(&colorbars[i], xrgb32_to_lab(*(pixel + j))); - } - } else if (fmt.pixelformat == VIDEO_PIX_FMT_RGB565) { - uint16_t *pixel = - (uint16_t *)&buf[2 * (h * fmt.width + bw / 2 + i * bw)]; - - for (int j = -PIXELS_NUM / 2; j <= PIXELS_NUM / 2; j++) { - sum_lab(&colorbars[i], rgb565_to_lab(*(pixel + j))); - } - } else { - printk("Format %d is not supported", fmt.pixelformat); - return false; - } - } - } - - for (i = 0; i < BARS_NUM; i++) { - average_lab(&colorbars[i], PIXELS_NUM * fmt.height); - if (deltaE(colorbars[i], colorbars_target[i]) > LAB_THRESHOLD) { - return false; - } - } - - return true; -} - -#endif /* TEST_PATTERN_CHECK_H_ */ diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index ae3f85a068fb9..367c128c710d4 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -13,14 +13,7 @@ #include #include - -#ifdef CONFIG_TEST -#include "check_test_pattern.h" - -LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG); -#else LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL); -#endif #if !DT_HAS_CHOSEN(zephyr_camera) #error No camera chosen in devicetree. Missing "--shield" or "--snippet video-sw-generator" flag? @@ -249,7 +242,6 @@ int main(void) /* Set controls */ struct video_control ctrl = {.id = VIDEO_CID_HFLIP, .val = 1}; - int tp_set_ret = -ENOTSUP; if (IS_ENABLED(CONFIG_VIDEO_CTRL_HFLIP)) { ret = video_set_ctrl(video_dev, &ctrl); @@ -339,14 +331,6 @@ int main(void) LOG_INF("Got frame %u! size: %u; timestamp %u ms", frame++, vbuf->bytesused, vbuf->timestamp); -#ifdef CONFIG_TEST - if (tp_set_ret < 0) { - LOG_DBG("Test pattern control was not successful. Skip test"); - } else if (is_colorbar_ok(vbuf->buffer, fmt)) { - LOG_DBG("Pattern OK!\n"); - } -#endif - #if DT_HAS_CHOSEN(zephyr_display) ret = app_display_frame(display_dev, vbuf, fmt); if (ret != 0) { diff --git a/tests/drivers/video/test_pattern/CMakeLists.txt b/tests/drivers/video/test_pattern/CMakeLists.txt new file mode 100644 index 0000000000000..4cc11977f4df0 --- /dev/null +++ b/tests/drivers/video/test_pattern/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright The Zephyr Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(integration) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/drivers/video/test_pattern/Kconfig b/tests/drivers/video/test_pattern/Kconfig new file mode 100644 index 0000000000000..c791d7ac1bdea --- /dev/null +++ b/tests/drivers/video/test_pattern/Kconfig @@ -0,0 +1,59 @@ +# Copyright The Zephyr Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +mainmenu "Video test pattern testing" + +menu "Video capture configuration" + +config TEST_PIXEL_FORMAT + string "Pixel format of the video frame" + help + Pixel format of the video frame. If not set, the default pixel format is used. + +config TEST_FRAME_HEIGHT + int "Height of the video frame" + default 0 + help + Height of the video frame. If set to 0, the default height is used. + +config TEST_FRAME_WIDTH + int "Width of the video frame" + default 0 + help + Width of the video frame. If set to 0, the default width is used. + +config TEST_FRAMES_TOTAL + int "Number of frames to test in total" + default 1000 + help + Video hardware do not always produce valid frames immediately, at high FPS, there can + be a lot of frames completely black or otherwise invalid before the test pattern + to show-up, which would still be the expected behavior of the hardware. + +config TEST_PATTERN_CTRL + int "Value used for the test pattern menu control" + default 1 + help + Some drivers support different types of test patterns and/or in a different order. + Control the menu CID value to select the correct "vertical color bar" pattern. + +config TEST_FRAMES_VALID + int "Number of valid frames to expect" + default 10 + help + Number of frames after which consider the test successful. + A valid frame is a frame featuring the test pattern with colors close enough according + to CONFIG_TEST_LAB_THRESHOLD. + +config TEST_LAB_THRESHOLD + int "CIE LAB acceptance threshold" + default 10 + help + Margin value to consider the color similarity to be close enough. + The default is 10 to allow slight difference to be ignored, and complete swaps to + always be detected. This can be raised in case the colors are all slightly off but + correctly ordered. + +endmenu + +source "Kconfig.zephyr" diff --git a/tests/drivers/video/test_pattern/boards/native_sim_native_64.conf b/tests/drivers/video/test_pattern/boards/native_sim_native_64.conf new file mode 100644 index 0000000000000..bee94f1dd1daf --- /dev/null +++ b/tests/drivers/video/test_pattern/boards/native_sim_native_64.conf @@ -0,0 +1,7 @@ +CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=33000 +CONFIG_VIDEO_BUFFER_POOL_NUM_MAX=1 +CONFIG_TEST_FRAME_HEIGHT=64 +CONFIG_TEST_FRAME_WIDTH=256 +CONFIG_TEST_PIXEL_FORMAT="RGBP" +CONFIG_TEST_LAB_THRESHOLD=30 +CONFIG_TEST_PATTERN_CTRL=0 diff --git a/tests/drivers/video/test_pattern/prj.conf b/tests/drivers/video/test_pattern/prj.conf new file mode 100644 index 0000000000000..193d9535a724f --- /dev/null +++ b/tests/drivers/video/test_pattern/prj.conf @@ -0,0 +1,4 @@ +CONFIG_LOG=y +CONFIG_VIDEO=y +CONFIG_ZTEST=y +CONFIG_FPU=y diff --git a/tests/drivers/video/test_pattern/src/main.c b/tests/drivers/video/test_pattern/src/main.c new file mode 100644 index 0000000000000..c272546f74120 --- /dev/null +++ b/tests/drivers/video/test_pattern/src/main.c @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2019 Linaro Limited + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(test_pattern, LOG_LEVEL_INF); + +#define LAB_THRESHOLD ((double)CONFIG_TEST_LAB_THRESHOLD) + +#define BARS_NUM 8 +#define PIXELS_NUM 5 + +typedef struct { + double L; + double a; + double b; +} CIELAB; + +/* + * This is measured on a real 8-colorbar pattern generated by an ov5640 camera sensor. + * For other sensors, it can be slightly different. If it doesn't fit anymore, either + * this array or the LAB_THRESHOLD can be modified. + */ +static const CIELAB colorbars_target[] = { + {100.0, 0.0053, -0.0104}, /* White */ + {97.1804, -21.2151, 91.3538}, /* Yellow */ + {90.1352, -58.4675, 6.0570}, /* Cyan */ + {87.7630, -85.9469, 83.2128}, /* Green */ + {56.6641, 95.0182, -66.9129}, /* Magenta */ + {46.6937, 72.7494, 49.5801}, /* Red */ + {27.6487, 71.5662, -97.4712}, /* Blue */ + {1.3726, -2.8040, 2.0043}, /* Black */ +}; + +static inline CIELAB rgb888_to_lab(const uint8_t r, const uint8_t g, const uint8_t b) +{ + CIELAB lab; + + double r_lin = r / 255.0; + double g_lin = g / 255.0; + double b_lin = b / 255.0; + + r_lin = r_lin > 0.04045 ? pow((r_lin + 0.055) / 1.055, 2.4) : r_lin / 12.92; + g_lin = g_lin > 0.04045 ? pow((g_lin + 0.055) / 1.055, 2.4) : g_lin / 12.92; + b_lin = b_lin > 0.04045 ? pow((b_lin + 0.055) / 1.055, 2.4) : b_lin / 12.92; + + double x = r_lin * 0.4124 + g_lin * 0.3576 + b_lin * 0.1805; + double y = r_lin * 0.2126 + g_lin * 0.7152 + b_lin * 0.0722; + double z = r_lin * 0.0193 + g_lin * 0.1192 + b_lin * 0.9505; + + x /= 0.95047; + z /= 1.08883; + + x = x > 0.008856 ? pow(x, 1.0 / 3.0) : (7.787 * x) + (16.0 / 116.0); + y = y > 0.008856 ? pow(y, 1.0 / 3.0) : (7.787 * y) + (16.0 / 116.0); + z = z > 0.008856 ? pow(z, 1.0 / 3.0) : (7.787 * z) + (16.0 / 116.0); + + lab.L = 116.0 * y - 16.0; + lab.a = 500.0 * (x - y); + lab.b = 200.0 * (y - z); + + return lab; +} + +static inline CIELAB xrgb32_to_lab(const uint32_t color) +{ + uint8_t r = (color >> 16) & 0xFF; + uint8_t g = (color >> 8) & 0xFF; + uint8_t b = color & 0xFF; + + return rgb888_to_lab(r, g, b); +} + +static inline CIELAB rgb565_to_lab(const uint16_t color) +{ + uint8_t r5 = (color >> 11) & 0x1F; + uint8_t g6 = (color >> 5) & 0x3F; + uint8_t b5 = color & 0x1F; + + /* Convert RGB565 to RGB888 */ + uint8_t r = (r5 * 255) / 31; + uint8_t g = (g6 * 255) / 63; + uint8_t b = (b5 * 255) / 31; + + return rgb888_to_lab(r, g, b); +} + +static inline void sum_lab(CIELAB *sum, const CIELAB lab) +{ + sum->L += lab.L; + sum->a += lab.a; + sum->b += lab.b; +} + +static inline void average_lab(CIELAB *lab, const uint32_t count) +{ + if (count > 0) { + lab->L /= count; + lab->a /= count; + lab->b /= count; + } +} + +static inline double deltaE(const CIELAB lab1, const CIELAB lab2) +{ + return sqrt(pow(lab1.L - lab2.L, 2) + pow(lab1.a - lab2.a, 2) + pow(lab1.b - lab2.b, 2)); +} + +/* + * As color values may vary near the boundary of each bar and also, for computational + * efficiency, check only a small number of pixels (PIXELS_NUM) in the middle of each bar. + */ +static inline bool is_colorbar_ok(const uint8_t *const buf, const struct video_format *fmt) +{ + int i; + int bw = fmt->width / BARS_NUM; + CIELAB colorbars[BARS_NUM] = {0}; + + for (int h = 0; h < fmt->height; h++) { + for (i = 0; i < BARS_NUM; i++) { + if (fmt->pixelformat == VIDEO_PIX_FMT_XRGB32) { + uint32_t *pixel = + (uint32_t *)&buf[4 * (h * fmt->width + bw / 2 + i * bw)]; + + for (int j = -PIXELS_NUM / 2; j <= PIXELS_NUM / 2; j++) { + sum_lab(&colorbars[i], xrgb32_to_lab(*(pixel + j))); + } + } else if (fmt->pixelformat == VIDEO_PIX_FMT_RGB565) { + uint16_t *pixel = + (uint16_t *)&buf[2 * (h * fmt->width + bw / 2 + i * bw)]; + + for (int j = -PIXELS_NUM / 2; j <= PIXELS_NUM / 2; j++) { + sum_lab(&colorbars[i], rgb565_to_lab(*(pixel + j))); + } + } else { + printk("Format %d is not supported", fmt->pixelformat); + return false; + } + } + } + + for (i = 0; i < BARS_NUM; i++) { + average_lab(&colorbars[i], PIXELS_NUM * fmt->height); + if (deltaE(colorbars[i], colorbars_target[i]) > LAB_THRESHOLD) { + return false; + } + } + + return true; +} + +static const struct device *const video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera)); +struct video_format fmt; + +static void *test_pattern_setup(void) +{ + struct video_buffer *vbuf = &(struct video_buffer){}; + struct video_caps caps = { + .type = VIDEO_BUF_TYPE_OUTPUT, + }; + struct video_control ctrl = { + .id = VIDEO_CID_TEST_PATTERN, .val = CONFIG_TEST_PATTERN_CTRL, + }; + int ret; + + zassert(device_is_ready(video_dev), "device initialization failed"); + + ret = video_get_caps(video_dev, &caps); + zassert_ok(ret, "getting video capabilities failed"); + + fmt.type = VIDEO_BUF_TYPE_OUTPUT; + ret = video_get_format(video_dev, &fmt); + zassert_ok(ret, "getting default video format failed"); + + if (CONFIG_TEST_FRAME_HEIGHT > 0) { + fmt.height = CONFIG_TEST_FRAME_HEIGHT; + } + if (CONFIG_TEST_FRAME_WIDTH > 0) { + fmt.width = CONFIG_TEST_FRAME_WIDTH; + } + if (strcmp(CONFIG_TEST_PIXEL_FORMAT, "") != 0) { + fmt.pixelformat = VIDEO_FOURCC_FROM_STR(CONFIG_TEST_PIXEL_FORMAT); + } + + LOG_INF("Video format: %s %ux%u", + VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height); + + ret = video_set_format(video_dev, &fmt); + zassert_ok(ret, "setting video format failed"); + + ret = video_set_ctrl(video_dev, &ctrl); + zassert_ok(ret, "setting test pattern"); + + /* Alloc video buffers and enqueue for capture */ + zassert(caps.min_vbuf_count <= CONFIG_VIDEO_BUFFER_POOL_NUM_MAX, + "not enough buffers"); + zassert(fmt.size <= CONFIG_VIDEO_BUFFER_POOL_SZ_MAX, + "buffers too large"); + + for (int i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) { + vbuf = video_buffer_aligned_alloc(fmt.size, CONFIG_VIDEO_BUFFER_POOL_ALIGN, + K_NO_WAIT); + zassert_not_null(vbuf); + + vbuf->type = VIDEO_BUF_TYPE_OUTPUT; + + ret = video_enqueue(video_dev, vbuf); + zassert_ok(ret); + } + + LOG_INF("Device %s configured starting capture", video_dev->name); + + ret = video_stream_start(video_dev, VIDEO_BUF_TYPE_OUTPUT); + zassert_ok(ret); + + return NULL; +} + +void test_pattern_after(void *) +{ + int ret; + + ret = video_stream_stop(video_dev, VIDEO_BUF_TYPE_OUTPUT); + zassert_ok(ret); +} + +ZTEST(test_pattern, test_pattern_frames) +{ + struct video_buffer *vbuf = &(struct video_buffer){ + .type = VIDEO_BUF_TYPE_OUTPUT + }; + size_t valid = 0; + int ret; + + for (size_t i = 0; i < CONFIG_TEST_FRAMES_TOTAL; i++) { + ret = video_dequeue(video_dev, &vbuf, K_FOREVER); + zassert_ok(ret); + + LOG_INF("Got frame, testing color bars"); + + valid += is_colorbar_ok(vbuf->buffer, &fmt); + if (valid >= CONFIG_TEST_FRAMES_VALID) { + LOG_INF("Got %u valid frames out of %u, stopping the test", valid, i + 1); + break; + } + + ret = video_enqueue(video_dev, vbuf); + zassert_ok(ret); + } + + zassert_equal(valid, CONFIG_TEST_FRAMES_VALID, + "there should be at least %u valid frames out of %u", + CONFIG_TEST_FRAMES_VALID, CONFIG_TEST_FRAMES_TOTAL); +} + +ZTEST_SUITE(test_pattern, NULL, test_pattern_setup, NULL, test_pattern_after, NULL); diff --git a/tests/drivers/video/test_pattern/testcase.yaml b/tests/drivers/video/test_pattern/testcase.yaml new file mode 100644 index 0000000000000..37f69f9c2a624 --- /dev/null +++ b/tests/drivers/video/test_pattern/testcase.yaml @@ -0,0 +1,23 @@ +# Copyright The Zephyr Project Contributors +# SPDX-License-Identifier: Apache-2.0 + +common: + tags: + - drivers + - video + +tests: + + drivers.video.test_pattern.sw_generator: + platform_allow: + - native_sim/native/64 + integration_platforms: + - native_sim/native/64 + extra_args: + - SNIPPET="video-sw-generator" + + drivers.video.test_pattern.mimxrt1170_evk: + platform_allow: + - mimxrt1170_evk/mimxrt1176/cm7 + extra_args: + - SHIELD="nxp_btb44_ov5640;rk055hdmipi4ma0" From 764fcc9df6c08a70ff0b54ece353a2ccaff4e7b5 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Tue, 16 Sep 2025 10:06:56 +0000 Subject: [PATCH 08/10] samples: drivers: video: capture: move code to functions Move blocks of code into individual functions to make the sample more modular, component-based. Signed-off-by: Josuah Demangeon --- samples/drivers/video/capture/src/main.c | 321 ++++++++++++++--------- 1 file changed, 202 insertions(+), 119 deletions(-) diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index 367c128c710d4..671217a8227df 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -27,6 +27,11 @@ static inline int app_setup_display(const struct device *const display_dev, cons LOG_INF("Display device: %s", display_dev->name); + if (!device_is_ready(display_dev)) { + LOG_ERR("%s: display device not ready.", display_dev->name); + return -ENOSYS; + } + display_get_capabilities(display_dev, &capabilities); LOG_INF("- Capabilities:"); @@ -68,62 +73,100 @@ static inline int app_setup_display(const struct device *const display_dev, cons static int app_display_frame(const struct device *const display_dev, const struct video_buffer *const vbuf, - const struct video_format fmt) + const struct video_format *const fmt) { struct display_buffer_descriptor buf_desc = { .buf_size = vbuf->bytesused, - .width = fmt.width, + .width = fmt->width, .pitch = buf_desc.width, - .height = vbuf->bytesused / fmt.pitch, + .height = vbuf->bytesused / fmt->pitch, }; return display_write(display_dev, 0, vbuf->line_offset, &buf_desc, vbuf->buffer); } #endif -int main(void) +static int app_setup_video_selection(const struct device *const video_dev, + const struct video_format *const fmt) { - struct video_buffer *vbuf = &(struct video_buffer){}; - const struct device *video_dev; - struct video_format fmt = { - .type = VIDEO_BUF_TYPE_OUTPUT, - }; - struct video_caps caps = { + struct video_selection sel = { .type = VIDEO_BUF_TYPE_OUTPUT, }; - struct video_frmival frmival = {}; - struct video_frmival_enum fie = { - .format = &fmt, - }; - unsigned int frame = 0; - int i = 0; int ret; - /* When the video shell is enabled, do not run the capture loop */ - if (IS_ENABLED(CONFIG_VIDEO_SHELL)) { - LOG_INF("Letting the user control the device with the video shell"); - return 0; + /* Set the crop setting only if configured */ + if (CONFIG_VIDEO_SOURCE_CROP_WIDTH > 0) { + 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; + + ret = video_set_selection(video_dev, &sel); + if (ret < 0) { + LOG_ERR("Unable to set selection crop"); + return ret; + } + + LOG_INF("Crop window set to (%u,%u)/%ux%u", + sel.rect.left, sel.rect.top, sel.rect.width, sel.rect.height); } - video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera)); - if (!device_is_ready(video_dev)) { - LOG_ERR("%s: video device is not ready", video_dev->name); - return 0; + /* + * Check (if possible) if targeted size is same as crop + * and if compose is necessary + */ + sel.target = VIDEO_SEL_TGT_CROP; + ret = video_get_selection(video_dev, &sel); + if (ret < 0 && ret != -ENOSYS) { + LOG_ERR("Unable to get selection crop"); + return ret; + } + + if (ret == 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; + + ret = video_set_selection(video_dev, &sel); + if (ret < 0 && ret != -ENOSYS) { + LOG_ERR("Unable to set selection compose"); + return ret; + } + + LOG_INF("Compose window set to (%u,%u)/%ux%u", + sel.rect.left, sel.rect.top, sel.rect.width, sel.rect.height); } + return 0; +} + +static int app_query_video_info(const struct device *const video_dev, + struct video_caps *const caps, + struct video_format *const fmt) +{ + int ret; + LOG_INF("Video device: %s", video_dev->name); + if (!device_is_ready(video_dev)) { + LOG_ERR("%s: video device is not ready", video_dev->name); + return -ENOSYS; + } + /* Get capabilities */ - ret = video_get_caps(video_dev, &caps); + ret = video_get_caps(video_dev, caps); if (ret < 0) { LOG_ERR("Unable to retrieve video capabilities"); - return 0; + return ret; } LOG_INF("- Capabilities:"); - while (caps.format_caps[i].pixelformat) { - const struct video_format_cap *fcap = &caps.format_caps[i]; - /* fourcc to string */ + for (int i = 0; caps->format_caps[i].pixelformat; i++) { + const struct video_format_cap *fcap = &caps->format_caps[i]; + LOG_INF(" %s width [%u; %u; %u] height [%u; %u; %u]", VIDEO_FOURCC_TO_STR(fcap->pixelformat), fcap->width_min, fcap->width_max, fcap->width_step, @@ -132,88 +175,53 @@ int main(void) } /* Get default/native format */ - ret = video_get_format(video_dev, &fmt); + ret = video_get_format(video_dev, fmt); if (ret < 0) { LOG_ERR("Unable to retrieve video format"); - return 0; - } - - /* Set the crop setting if necessary */ - if (CONFIG_VIDEO_SOURCE_CROP_WIDTH > 0 || CONFIG_VIDEO_SOURCE_CROP_HEIGHT > 0) { - struct video_selection sel = { - .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, - .type = VIDEO_BUF_TYPE_OUTPUT, - }; - - ret = video_set_selection(video_dev, &sel); - if (ret < 0) { - 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); - - /* - * Check (if possible) if targeted size is same as crop - * and if compose is necessary - */ - sel.target = VIDEO_SEL_TGT_CROP; - ret = video_get_selection(video_dev, &sel); - if (ret < 0 && ret != -ENOSYS) { - LOG_ERR("Unable to get selection crop"); - return 0; - } - - if (ret == 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; - ret = video_set_selection(video_dev, &sel); - if (ret < 0 && ret != -ENOSYS) { - LOG_ERR("Unable to set selection compose"); - return 0; - } - } } + /* Adjust video format according to the configuration */ if (CONFIG_VIDEO_FRAME_HEIGHT > 0) { - fmt.height = CONFIG_VIDEO_FRAME_HEIGHT; + fmt->height = CONFIG_VIDEO_FRAME_HEIGHT; } if (CONFIG_VIDEO_FRAME_WIDTH > 0) { - fmt.width = CONFIG_VIDEO_FRAME_WIDTH; + fmt->width = CONFIG_VIDEO_FRAME_WIDTH; } if (strcmp(CONFIG_VIDEO_PIXEL_FORMAT, "") != 0) { - fmt.pixelformat = VIDEO_FOURCC_FROM_STR(CONFIG_VIDEO_PIXEL_FORMAT); + fmt->pixelformat = VIDEO_FOURCC_FROM_STR(CONFIG_VIDEO_PIXEL_FORMAT); } + return 0; +} + +static int app_setup_video_format(const struct device *const video_dev, + struct video_format *const fmt) +{ + int ret; + LOG_INF("- Video format: %s %ux%u", - VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height); + VIDEO_FOURCC_TO_STR(fmt->pixelformat), fmt->width, fmt->height); - ret = video_set_compose_format(video_dev, &fmt); + ret = video_set_compose_format(video_dev, fmt); if (ret < 0) { LOG_ERR("Unable to set format"); - return 0; - } - - ret = video_get_frmival(video_dev, &frmival); - if (ret == -ENOTSUP || ret == -ENOSYS) { - LOG_WRN("The video source does not support frame rate control"); - } else if (ret < 0) { - LOG_ERR("Error while getting the frame interval"); return ret; - } else { - LOG_INF("- Default frame rate : %f fps", - 1.0 * frmival.denominator / frmival.numerator); } + return 0; +} + +static int app_setup_video_frmival(const struct device *const video_dev, + struct video_format *const fmt) +{ + struct video_frmival frmival = {}; + struct video_frmival_enum fie = { + .format = fmt, + }; + int ret; + LOG_INF("- Supported frame intervals for the default format:"); + while (video_enum_frmival(video_dev, &fie) == 0) { if (fie.type == VIDEO_FRMIVAL_TYPE_DISCRETE) { LOG_INF(" %u/%u", fie.discrete.numerator, fie.discrete.denominator); @@ -226,6 +234,24 @@ int main(void) fie.index++; } + ret = video_get_frmival(video_dev, &frmival); + if (ret == -ENOTSUP || ret == -ENOSYS) { + LOG_WRN("The video source does not support frame rate control"); + } else if (ret < 0) { + LOG_ERR("Error while getting the frame interval"); + return ret; + } else { + LOG_INF("- Default frame rate : %f fps", + 1.0 * frmival.denominator / frmival.numerator); + } + + return 0; +} + +static int app_setup_video_controls(const struct device *const video_dev) +{ + int ret; + /* Get supported controls */ LOG_INF("- Supported controls:"); const struct device *last_dev = NULL; @@ -247,7 +273,7 @@ int main(void) ret = video_set_ctrl(video_dev, &ctrl); if (ret < 0) { LOG_ERR("Failed to set horizontal flip"); - return 0; + return ret; } } @@ -256,7 +282,7 @@ int main(void) ret = video_set_ctrl(video_dev, &ctrl); if (ret < 0) { LOG_ERR("Failed to set vertical flip"); - return 0; + return ret; } } @@ -266,51 +292,109 @@ int main(void) if (ret < 0 && ret != -ENOTSUP) { LOG_WRN("Failed to set the test pattern"); } - tp_set_ret = ret; } -#if DT_HAS_CHOSEN(zephyr_display) - const struct device *const display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)); - - if (!device_is_ready(display_dev)) { - LOG_ERR("%s: display device not ready.", display_dev->name); - return 0; - } + return 0; +} - ret = app_setup_display(display_dev, fmt.pixelformat); - if (ret < 0) { - LOG_ERR("Unable to set up display"); - return 0; - } -#endif +static int app_setup_video_buffers(const struct device *const video_dev, + struct video_caps *const caps, + struct video_format *const fmt) +{ + int ret; /* Alloc video buffers and enqueue for capture */ - if (caps.min_vbuf_count > CONFIG_VIDEO_BUFFER_POOL_NUM_MAX || - fmt.size > CONFIG_VIDEO_BUFFER_POOL_SZ_MAX) { + if (caps->min_vbuf_count > CONFIG_VIDEO_BUFFER_POOL_NUM_MAX || + fmt->size > CONFIG_VIDEO_BUFFER_POOL_SZ_MAX) { LOG_ERR("Not enough buffers or memory to start streaming"); - return 0; + return -EINVAL; } - for (i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) { + for (int i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) { + struct video_buffer *vbuf; + /* * For some hardwares, such as the PxP used on i.MX RT1170 to do image rotation, * buffer alignment is needed in order to achieve the best performance */ - vbuf = video_buffer_aligned_alloc(fmt.size, CONFIG_VIDEO_BUFFER_POOL_ALIGN, - K_NO_WAIT); + vbuf = video_buffer_aligned_alloc(fmt->size, CONFIG_VIDEO_BUFFER_POOL_ALIGN, + K_NO_WAIT); if (vbuf == NULL) { LOG_ERR("Unable to alloc video buffer"); - return 0; + return -ENOMEM; } + vbuf->type = VIDEO_BUF_TYPE_OUTPUT; + ret = video_enqueue(video_dev, vbuf); if (ret < 0) { LOG_ERR("Failed to enqueue video buffer"); - return 0; + return ret; } } - /* Start video capture */ + return 0; +} + +int main(void) +{ + const struct device *const video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera)); + struct video_buffer *vbuf = &(struct video_buffer){}; + struct video_format fmt = { + .type = VIDEO_BUF_TYPE_OUTPUT, + }; + struct video_caps caps = { + .type = VIDEO_BUF_TYPE_OUTPUT, + }; + unsigned int frame = 0; + int ret; + + /* When the video shell is enabled, do not run the capture loop */ + if (IS_ENABLED(CONFIG_VIDEO_SHELL)) { + LOG_INF("Letting the user control the device with the video shell"); + return 0; + } + + ret = app_query_video_info(video_dev, &caps, &fmt); + if (ret < 0) { + return 0; + } + + ret = app_setup_video_selection(video_dev, &fmt); + if (ret < 0) { + return 0; + } + + ret = app_setup_video_format(video_dev, &fmt); + if (ret < 0) { + return 0; + } + + ret = app_setup_video_frmival(video_dev, &fmt); + if (ret < 0) { + return 0; + } + + ret = app_setup_video_controls(video_dev); + if (ret < 0) { + return 0; + } + +#if DT_HAS_CHOSEN(zephyr_display) + const struct device *const display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)); + + ret = app_setup_display(display_dev, fmt.pixelformat); + if (ret < 0) { + LOG_ERR("Unable to set up display"); + return 0; + } +#endif + + ret = app_setup_video_buffers(video_dev, &caps, &fmt); + if (ret < 0) { + return 0; + } + ret = video_stream_start(video_dev, VIDEO_BUF_TYPE_OUTPUT); if (ret < 0) { LOG_ERR("Unable to start capture (interface)"); @@ -319,7 +403,6 @@ int main(void) LOG_INF("Capture started"); - /* Grab video frames */ vbuf->type = VIDEO_BUF_TYPE_OUTPUT; while (1) { ret = video_dequeue(video_dev, &vbuf, K_FOREVER); @@ -332,7 +415,7 @@ int main(void) frame++, vbuf->bytesused, vbuf->timestamp); #if DT_HAS_CHOSEN(zephyr_display) - ret = app_display_frame(display_dev, vbuf, fmt); + ret = app_display_frame(display_dev, vbuf, &fmt); if (ret != 0) { LOG_WRN("Failed to display this frame"); } From adb344566bcd2f5df1fc3ac0e18ca2a790aeaf4b Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Tue, 21 Oct 2025 21:14:36 +0000 Subject: [PATCH 09/10] samples: drivers: video: make capture sample failure more visible Centralize exit of all helper functions and display a common message that indicates clearly that the sample is not blocking on any function but instead stopped running. Signed-off-by: Josuah Demangeon --- samples/drivers/video/capture/src/main.c | 28 ++++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index 671217a8227df..220bdb444f4bc 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -54,6 +54,7 @@ static inline int app_setup_display(const struct device *const display_dev, cons } break; default: + LOG_ERR("Display pixel format not supported by this sample"); return -ENOTSUP; } if (ret < 0) { @@ -240,7 +241,7 @@ static int app_setup_video_frmival(const struct device *const video_dev, } else if (ret < 0) { LOG_ERR("Error while getting the frame interval"); return ret; - } else { + } else if (ret == 0) { LOG_INF("- Default frame rate : %f fps", 1.0 * frmival.denominator / frmival.numerator); } @@ -357,27 +358,27 @@ int main(void) ret = app_query_video_info(video_dev, &caps, &fmt); if (ret < 0) { - return 0; + goto err; } ret = app_setup_video_selection(video_dev, &fmt); if (ret < 0) { - return 0; + goto err; } ret = app_setup_video_format(video_dev, &fmt); if (ret < 0) { - return 0; + goto err; } ret = app_setup_video_frmival(video_dev, &fmt); if (ret < 0) { - return 0; + goto err; } ret = app_setup_video_controls(video_dev); if (ret < 0) { - return 0; + goto err; } #if DT_HAS_CHOSEN(zephyr_display) @@ -385,20 +386,19 @@ int main(void) ret = app_setup_display(display_dev, fmt.pixelformat); if (ret < 0) { - LOG_ERR("Unable to set up display"); - return 0; + goto err; } #endif ret = app_setup_video_buffers(video_dev, &caps, &fmt); if (ret < 0) { - return 0; + goto err; } ret = video_stream_start(video_dev, VIDEO_BUF_TYPE_OUTPUT); if (ret < 0) { LOG_ERR("Unable to start capture (interface)"); - return 0; + goto err; } LOG_INF("Capture started"); @@ -408,7 +408,7 @@ int main(void) ret = video_dequeue(video_dev, &vbuf, K_FOREVER); if (ret < 0) { LOG_ERR("Unable to dequeue video buf"); - return 0; + goto err; } LOG_INF("Got frame %u! size: %u; timestamp %u ms", @@ -424,7 +424,11 @@ int main(void) ret = video_enqueue(video_dev, vbuf); if (ret < 0) { LOG_ERR("Unable to requeue video buf"); - return 0; + goto err; } } + +err: + LOG_ERR("Aborting sample"); + return 0; } From 66ef2fae154d1ae505165e81a722995226b56dc9 Mon Sep 17 00:00:00 2001 From: Josuah Demangeon Date: Fri, 24 Oct 2025 12:33:57 +0000 Subject: [PATCH 10/10] samples: drivers: video: capture: use DEVICE_DT_GET_OR_NULL for display Allow the display to also use if() instead of #if by leveraging the DEVICE_DT_GET_OR_NULL() that permit display_dev to always be defined. The compiler will const-fold all the unused variables and functions. Signed-off-by: Josuah Demangeon --- samples/drivers/video/capture/src/main.c | 25 +++++++++++------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/samples/drivers/video/capture/src/main.c b/samples/drivers/video/capture/src/main.c index 220bdb444f4bc..1888d4ec292c6 100644 --- a/samples/drivers/video/capture/src/main.c +++ b/samples/drivers/video/capture/src/main.c @@ -19,7 +19,6 @@ LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL); #error No camera chosen in devicetree. Missing "--shield" or "--snippet video-sw-generator" flag? #endif -#if DT_HAS_CHOSEN(zephyr_display) static inline int app_setup_display(const struct device *const display_dev, const uint32_t pixfmt) { struct display_capabilities capabilities; @@ -85,7 +84,6 @@ static int app_display_frame(const struct device *const display_dev, return display_write(display_dev, 0, vbuf->line_offset, &buf_desc, vbuf->buffer); } -#endif static int app_setup_video_selection(const struct device *const video_dev, const struct video_format *const fmt) @@ -340,6 +338,7 @@ static int app_setup_video_buffers(const struct device *const video_dev, int main(void) { const struct device *const video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera)); + const struct device *const display_dev = DEVICE_DT_GET_OR_NULL(DT_CHOSEN(zephyr_display)); struct video_buffer *vbuf = &(struct video_buffer){}; struct video_format fmt = { .type = VIDEO_BUF_TYPE_OUTPUT, @@ -381,14 +380,12 @@ int main(void) goto err; } -#if DT_HAS_CHOSEN(zephyr_display) - const struct device *const display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)); - - ret = app_setup_display(display_dev, fmt.pixelformat); - if (ret < 0) { - goto err; + if (DT_HAS_CHOSEN(zephyr_display)) { + ret = app_setup_display(display_dev, fmt.pixelformat); + if (ret < 0) { + goto err; + } } -#endif ret = app_setup_video_buffers(video_dev, &caps, &fmt); if (ret < 0) { @@ -414,12 +411,12 @@ int main(void) LOG_INF("Got frame %u! size: %u; timestamp %u ms", frame++, vbuf->bytesused, vbuf->timestamp); -#if DT_HAS_CHOSEN(zephyr_display) - ret = app_display_frame(display_dev, vbuf, &fmt); - if (ret != 0) { - LOG_WRN("Failed to display this frame"); + if (DT_HAS_CHOSEN(zephyr_display)) { + ret = app_display_frame(display_dev, vbuf, &fmt); + if (ret != 0) { + LOG_WRN("Failed to display this frame"); + } } -#endif ret = video_enqueue(video_dev, vbuf); if (ret < 0) {