Skip to content

Expose video devices to user #88182

New issue

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

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

Already on GitHub? Sign in to your account

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
zephyr,sram = &sram0;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
zephyr,camera = &dcmi;
};

aliases {
Expand Down
1 change: 0 additions & 1 deletion boards/espressif/esp32s3_eye/esp32s3_eye_procpu.dts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
zephyr,code-partition = &slot0_partition;
zephyr,bt-hci = &esp32_bt_hci;
zephyr,display = &st7789v;
zephyr,camera = &lcd_cam;
};

buttons {
Expand Down
4 changes: 0 additions & 4 deletions boards/seeed/xiao_esp32s3/xiao_esp32s3_procpu_sense.dts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@
/ {
model = "Seeed Xiao ESP32S3 PROCPU Sense";
compatible = "seeed,xiao-esp32s3";

chosen {
zephyr,camera = &lcd_cam;
};
};

&i2c1 {
Expand Down
6 changes: 0 additions & 6 deletions boards/shields/dvp_fpc24_mt9m114/dvp_fpc24_mt9m114.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

/{
chosen {
zephyr,camera = &dvp_fpc24_interface;
};
};

&dvp_fpc24_i2c {
mt9m114: mt9m114@48 {
compatible = "aptina,mt9m114";
Expand Down
6 changes: 0 additions & 6 deletions boards/shields/nxp_btb44_ov5640/nxp_btb44_ov5640.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@

#include <zephyr/dt-bindings/video/video-interfaces.h>

/{
chosen {
zephyr,camera = &nxp_csi;
};
};

&nxp_cam_i2c {
status = "okay";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@

#include <zephyr/dt-bindings/video/video-interfaces.h>

/ {
chosen {
zephyr,camera = &st_cam_dvp;
};
};

&st_cam_i2c {

ov5640: ov5640@3c {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

/ {
chosen {
zephyr,camera = &zephyr_camera_dvp;
};
};

&zephyr_camera_i2c {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
Expand Down
1 change: 1 addition & 0 deletions cmake/linker_script/common/common-ram.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ endif()

if(CONFIG_VIDEO)
zephyr_iterable_section(NAME video_device GROUP DATA_REGION ${XIP_ALIGN_WITH_INPUT} SUBALIGN ${CONFIG_LINKER_ITERABLE_SUBALIGN})
zephyr_iterable_section(NAME video_mdev GROUP DATA_REGION ${XIP_ALIGN_WITH_INPUT} SUBALIGN ${CONFIG_LINKER_ITERABLE_SUBALIGN})
endif()

if(CONFIG_LOG)
Expand Down
3 changes: 0 additions & 3 deletions drivers/video/Kconfig.sw_generator
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
# Copyright (c) 2016 Linaro Limited
# SPDX-License-Identifier: Apache-2.0

DT_CHOSEN_ZEPHYR_CAMERA := zephyr,camera

config VIDEO_SW_GENERATOR
bool "Video Software Generator"
depends on !$(dt_chosen_enabled,$(DT_CHOSEN_ZEPHYR_CAMERA))
help
Enable video pattern generator (for testing purposes).
1 change: 1 addition & 0 deletions drivers/video/video.ld
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <zephyr/linker/iterable_sections.h>

ITERABLE_SECTION_RAM(video_device, Z_LINK_ITERABLE_SUBALIGN)
ITERABLE_SECTION_RAM(video_mdev, Z_LINK_ITERABLE_SUBALIGN)
20 changes: 20 additions & 0 deletions drivers/video/video_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/drivers/video.h>

#include "video_device.h"

struct video_device *video_find_vdev(const struct device *dev)
Expand All @@ -20,3 +22,21 @@ struct video_device *video_find_vdev(const struct device *dev)

return NULL;
}

const struct device *video_get_dev(uint8_t i)
{
const struct video_mdev *vmd = NULL;

STRUCT_SECTION_GET(video_mdev, i, &vmd);

return vmd->dev;
}

uint8_t video_get_devs_num(void)
{
uint8_t count = 0;

STRUCT_SECTION_COUNT(video_mdev, &count);

return count;
}
9 changes: 9 additions & 0 deletions drivers/video/video_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,22 @@ struct video_device {
sys_dlist_t ctrls;
};

struct video_mdev {
const struct device *dev;
};

Comment on lines +19 to +22
Copy link
Contributor

Choose a reason for hiding this comment

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

How about video_interface to communicate that this is the part of the video chain that an application interacts with. Hopefully reducing the jargon required to understand V4Z a little.

Copy link
Contributor

Choose a reason for hiding this comment

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

An alternative is to use STRUCT_SECTION_ITERABLE_ALTERNATE so that defining a wrapper struct is not required. Instead, a struct device is used, but with a dedicated section name such as video_mdev or video_interface:

/**
* @brief Defines a new element of alternate data type for an iterable section.
*
* @details
* Special variant of STRUCT_SECTION_ITERABLE(), for placing alternate
* data types within the iterable section of a specific data type. The
* data type sizes and semantics must be equivalent!
*/
#define STRUCT_SECTION_ITERABLE_ALTERNATE(secname, struct_type, varname) \
TYPE_SECTION_ITERABLE(struct struct_type, varname, secname, varname)

For instance here is how USB submits the same struct type but with different names for each USB speed:

static STRUCT_SECTION_ITERABLE_ALTERNATE( \
usbd_class_fs, usbd_class_node, class_name##_fs) = { \
.c_data = &class_name, \
}; \
static STRUCT_SECTION_ITERABLE_ALTERNATE( \
usbd_class_hs, usbd_class_node, class_name##_hs) = { \
.c_data = &class_name, \
}

Both approach work depending on your goals, i.e. plan extra fields in the future or not.

#define VIDEO_DEVICE_DEFINE(name, device, source) \
static STRUCT_SECTION_ITERABLE(video_device, name) = { \
.dev = device, \
.src_dev = source, \
.ctrls = SYS_DLIST_STATIC_INIT(&name.ctrls), \
}

#define VIDEO_MDEV_DEFINE(name, device) \
static STRUCT_SECTION_ITERABLE(video_mdev, name_##mdev) = { \
.dev = device, \
}

Comment on lines +30 to +34
Copy link
Contributor

Choose a reason for hiding this comment

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

To reduce the boilerplate, it might be possible to combine the two declarations in one:

Suggested change
#define VIDEO_MDEV_DEFINE(name, device) \
static STRUCT_SECTION_ITERABLE(video_mdev, name_##mdev) = { \
.dev = device, \
}
#define VIDEO_INTERFACE_DEFINE(name, device, source) \
VIDEO_DEVICE_DEFINE(name, device, source); \
static STRUCT_SECTION_ITERABLE(video_interface, name_##interface) = { \
.dev = device, \
}

Then the user can select VIDEO_DEVICE_DEFINE() if it is an intermediate device, and VIDEO_INTERFACE_DEFINE() if it is a media I/O device to define both at once.

This also prevents to define a VIDEO_INTERFACE_DEFINE() without a VIDEO_DEVICE_DEFINE() by mistake.

struct video_device *video_find_vdev(const struct device *dev);

#endif /* ZEPHYR_INCLUDE_DRIVERS_VIDEO_VIDEO_DEVICE_H_ */
4 changes: 3 additions & 1 deletion drivers/video/video_emul_rx.c
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ int emul_rx_init(const struct device *dev)
DEVICE_DT_INST_DEFINE(n, &emul_rx_init, NULL, &emul_rx_data_##n, &emul_rx_cfg_##n, \
POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &emul_rx_driver_api); \
\
VIDEO_DEVICE_DEFINE(emul_rx_##n, DEVICE_DT_INST_GET(n), emul_rx_cfg_##n.source_dev);
VIDEO_DEVICE_DEFINE(emul_rx_##n, DEVICE_DT_INST_GET(n), emul_rx_cfg_##n.source_dev); \
\
VIDEO_MDEV_DEFINE(emul_rx_##n, DEVICE_DT_INST_GET(n));

DT_INST_FOREACH_STATUS_OKAY(EMUL_RX_DEFINE)
2 changes: 2 additions & 0 deletions drivers/video/video_esp32_dvp.c
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,8 @@ DEVICE_DT_INST_DEFINE(0, video_esp32_init, NULL, &esp32_data, &esp32_config, POS

VIDEO_DEVICE_DEFINE(esp32, DEVICE_DT_INST_GET(0), esp32_config.source_dev);

VIDEO_MDEV_DEFINE(esp32, DEVICE_DT_INST_GET(0));

static int video_esp32_cam_init_master_clock(void)
{
int ret = 0;
Expand Down
2 changes: 2 additions & 0 deletions drivers/video/video_mcux_csi.c
Original file line number Diff line number Diff line change
Expand Up @@ -483,4 +483,6 @@ DEVICE_DT_INST_DEFINE(0, &video_mcux_csi_init_0, NULL, &video_mcux_csi_data_0,

VIDEO_DEVICE_DEFINE(csi, DEVICE_DT_INST_GET(0), video_mcux_csi_config_0.source_dev);

VIDEO_MDEV_DEFINE(csi, DEVICE_DT_INST_GET(0));

#endif
4 changes: 3 additions & 1 deletion drivers/video/video_mcux_smartdma.c
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,8 @@ static DEVICE_API(video, nxp_video_sdma_api) = {
&sdma_config_##inst, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &nxp_video_sdma_api); \
\
VIDEO_DEVICE_DEFINE(sdma_##inst, DEVICE_DT_INST_GET(inst), sdma_config_##inst.sensor_dev);
VIDEO_DEVICE_DEFINE(sdma_##inst, DEVICE_DT_INST_GET(inst), sdma_config_##inst.sensor_dev); \
\
VIDEO_MDEV_DEFINE(sdma_##inst, DEVICE_DT_INST_GET(inst));

DT_INST_FOREACH_STATUS_OKAY(NXP_VIDEO_SDMA_INIT)
2 changes: 2 additions & 0 deletions drivers/video/video_stm32_dcmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -501,3 +501,5 @@ DEVICE_DT_INST_DEFINE(0, &video_stm32_dcmi_init,
&video_stm32_dcmi_driver_api);

VIDEO_DEVICE_DEFINE(dcmi, DEVICE_DT_INST_GET(0), video_stm32_dcmi_config_0.sensor_dev);

VIDEO_MDEV_DEFINE(dcmi, DEVICE_DT_INST_GET(0));
2 changes: 2 additions & 0 deletions drivers/video/video_sw_generator.c
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,5 @@ DEVICE_DEFINE(video_sw_generator, "VIDEO_SW_GENERATOR", &video_sw_generator_init
&video_sw_generator_driver_api);

VIDEO_DEVICE_DEFINE(video_sw_generator, DEVICE_GET(video_sw_generator), NULL);

VIDEO_MDEV_DEFINE(video_sw_generator, DEVICE_GET(video_sw_generator));
20 changes: 20 additions & 0 deletions include/zephyr/drivers/video.h
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,26 @@ void video_closest_frmival_stepwise(const struct video_frmival_stepwise *stepwis
void video_closest_frmival(const struct device *dev, enum video_endpoint_id ep,
struct video_frmival_enum *match);

/**
* @brief Get a video device by index
*
* The video subsystem keeps a list of registered video devices that application
* can use for streaming. This function is to get a video device in this list by
* specifying an index.
*
* @param i Index of the video device to get
*/
const struct device *video_get_dev(uint8_t i);

/**
* @brief Get the number of registered video devices
*
* The video subsystem keeps a list of registered video devices that application
* can use for streaming. This function is to retrieve the size of this list.
*
*/
uint8_t video_get_devs_num(void);

/**
* @defgroup video_pixel_formats Video pixel formats
* The '|' characters separate the pixels or logical blocks, and spaces separate the bytes.
Expand Down
11 changes: 0 additions & 11 deletions samples/drivers/video/capture/boards/frdm_mcxn947_cpu0.overlay

This file was deleted.

26 changes: 14 additions & 12 deletions samples/drivers/video/capture/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,22 +92,24 @@ int main(void)
size_t bsize;
int i = 0;
int err;

#if DT_HAS_CHOSEN(zephyr_camera)
const struct device *const 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;
const struct device *video_dev = NULL;
const struct device *const vsg = device_get_binding(VIDEO_DEV_SW);
uint8_t vdevs_num = video_get_devs_num();
bool found_vdev = false;

/* Get the 1st video HW available, otherwise fallbacks to video sw generator */
for (int j = 0; j < vdevs_num; j++) {
video_dev = video_get_dev(j);
if (device_is_ready(video_dev) && (vdevs_num == 1 || video_dev != vsg)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Given that device_is_ready() == false when a device init() returns an error, how about a LOW_WRN("video device %s failed to initialize", video_dev->name) to help user notice the issue?

found_vdev = true;
break;
}
}
#else
const struct device *const video_dev = device_get_binding(VIDEO_DEV_SW);

if (video_dev == NULL) {
LOG_ERR("%s: video device not found or failed to initialized", VIDEO_DEV_SW);
if (!found_vdev) {
LOG_ERR("No video device ready!");
return 0;
}
#endif

LOG_INF("Video device: %s", video_dev->name);

Expand Down
23 changes: 13 additions & 10 deletions samples/drivers/video/capture_to_lvgl/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,29 @@ int main(void)
const struct device *video_dev;
size_t bsize;
int i = 0;
const struct device *const vsg = device_get_binding(VIDEO_DEV_SW);
uint8_t vdevs_num = video_get_devs_num();
bool found_vdev = false;

display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
if (!device_is_ready(display_dev)) {
LOG_ERR("Device not ready, aborting test");
return 0;
}

#if DT_HAS_CHOSEN(zephyr_camera)
video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera));
if (!device_is_ready(video_dev)) {
LOG_ERR("%s device is not ready", video_dev->name);
return 0;
/* Get the 1st video HW available, otherwise fallbacks to video sw generator */
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like this PR implements a fallback mechanism already, so is video_dev != vsg really needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

vsg is also registered and listed by the video framework. So, if it is listed at first place and in case we have multiple video devices, we should continue the loop to get the 1st real hw video device, otherwise we stop the loop because we found the real hw device or we only have one video device (whatever it is sw or hw, we have no choice).

Copy link
Contributor

@josuah josuah May 11, 2025

Choose a reason for hiding this comment

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

This makes sense. Great example of how to do filtering.

if it is listed at first place

One How does an user control which device is at the first place? I think it is alphabetical sorting.

Could there be 2 devices, with the first being the VIDEO_SW_GENERATOR, and then the VIDEO_SW_GENERATOR would be selected instead of the real camera?

for (int j = 0; j < vdevs_num; j++) {
video_dev = video_get_dev(j);
if (device_is_ready(video_dev) && (vdevs_num == 1 || video_dev != vsg)) {
found_vdev = true;
break;
}
}
#else
video_dev = device_get_binding(VIDEO_DEV_SW);
if (video_dev == NULL) {
LOG_ERR("%s device not found", VIDEO_DEV_SW);

if (!found_vdev) {
LOG_ERR("No video device ready!");
return 0;
}
#endif

LOG_INF("- Device name: %s", video_dev->name);

Expand Down
25 changes: 14 additions & 11 deletions samples/drivers/video/tcpserversink/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,24 @@ int main(void)
int i, ret, sock, client;
struct video_format fmt;
struct video_caps caps;
#if DT_HAS_CHOSEN(zephyr_camera)
const struct device *const video = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera));

if (!device_is_ready(video)) {
LOG_ERR("%s: video device not ready.", video->name);
return 0;
const struct device *video = NULL;
const struct device *const vsg = device_get_binding(VIDEO_DEV_SW);
uint8_t vdevs_num = video_get_devs_num();
bool found_vdev = false;

/* Get the 1st video HW available, otherwise fallbacks to video sw generator */
for (int j = 0; j < vdevs_num; j++) {
video = video_get_dev(j);
if (device_is_ready(video) && (vdevs_num == 1 || video != vsg)) {
found_vdev = true;
break;
}
}
#else
const struct device *const video = device_get_binding(VIDEO_DEV_SW);

if (video == NULL) {
LOG_ERR("%s: video device not found or failed to initialized.", VIDEO_DEV_SW);
if (!found_vdev) {
LOG_ERR("No video device ready!");
return 0;
}
#endif
/* Prepare Network */
(void)memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
Expand Down
Loading