1 /*
2 * Copyright (c) 2019 Linaro Limited
3 * Copyright 2025 NXP
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <zephyr/kernel.h>
9 #include <zephyr/device.h>
10
11 #include <zephyr/drivers/display.h>
12 #include <zephyr/drivers/video.h>
13 #include <zephyr/drivers/video-controls.h>
14
15 #include <zephyr/logging/log.h>
16
17 #ifdef CONFIG_TEST
18 #include "check_test_pattern.h"
19
20 LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG);
21 #else
22 LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL);
23 #endif
24
25 #if !DT_HAS_CHOSEN(zephyr_camera)
26 #error No camera chosen in devicetree. Missing "--shield" or "--snippet video-sw-generator" flag?
27 #endif
28
29 #if DT_HAS_CHOSEN(zephyr_display)
display_setup(const struct device * const display_dev,const uint32_t pixfmt)30 static inline int display_setup(const struct device *const display_dev, const uint32_t pixfmt)
31 {
32 struct display_capabilities capabilities;
33 int ret = 0;
34
35 LOG_INF("Display device: %s", display_dev->name);
36
37 display_get_capabilities(display_dev, &capabilities);
38
39 LOG_INF("- Capabilities:");
40 LOG_INF(" x_resolution = %u, y_resolution = %u, supported_pixel_formats = %u"
41 " current_pixel_format = %u, current_orientation = %u",
42 capabilities.x_resolution, capabilities.y_resolution,
43 capabilities.supported_pixel_formats, capabilities.current_pixel_format,
44 capabilities.current_orientation);
45
46 /* Set display pixel format to match the one in use by the camera */
47 switch (pixfmt) {
48 case VIDEO_PIX_FMT_RGB565:
49 if (capabilities.current_pixel_format != PIXEL_FORMAT_RGB_565) {
50 ret = display_set_pixel_format(display_dev, PIXEL_FORMAT_RGB_565);
51 }
52 break;
53 case VIDEO_PIX_FMT_XRGB32:
54 if (capabilities.current_pixel_format != PIXEL_FORMAT_ARGB_8888) {
55 ret = display_set_pixel_format(display_dev, PIXEL_FORMAT_ARGB_8888);
56 }
57 break;
58 default:
59 return -ENOTSUP;
60 }
61
62 if (ret) {
63 LOG_ERR("Unable to set display format");
64 return ret;
65 }
66
67 /* Turn off blanking if driver supports it */
68 ret = display_blanking_off(display_dev);
69 if (ret == -ENOSYS) {
70 LOG_DBG("Display blanking off not available");
71 ret = 0;
72 }
73
74 return ret;
75 }
76
video_display_frame(const struct device * const display_dev,const struct video_buffer * const vbuf,const struct video_format fmt)77 static inline void video_display_frame(const struct device *const display_dev,
78 const struct video_buffer *const vbuf,
79 const struct video_format fmt)
80 {
81 struct display_buffer_descriptor buf_desc = {
82 .buf_size = vbuf->bytesused,
83 .width = fmt.width,
84 .pitch = buf_desc.width,
85 .height = vbuf->bytesused / fmt.pitch,
86 };
87
88 display_write(display_dev, 0, vbuf->line_offset, &buf_desc, vbuf->buffer);
89 }
90 #endif
91
main(void)92 int main(void)
93 {
94 struct video_buffer *vbuf = &(struct video_buffer){};
95 const struct device *video_dev;
96 struct video_format fmt;
97 struct video_caps caps;
98 struct video_frmival frmival;
99 struct video_frmival_enum fie;
100 enum video_buf_type type = VIDEO_BUF_TYPE_OUTPUT;
101 #if (CONFIG_VIDEO_SOURCE_CROP_WIDTH && CONFIG_VIDEO_SOURCE_CROP_HEIGHT) || \
102 CONFIG_VIDEO_FRAME_HEIGHT || CONFIG_VIDEO_FRAME_WIDTH
103 struct video_selection sel = {
104 .type = VIDEO_BUF_TYPE_OUTPUT,
105 };
106 #endif
107 unsigned int frame = 0;
108 size_t bsize;
109 int i = 0;
110 int err;
111
112 /* When the video shell is enabled, do not run the capture loop */
113 if (IS_ENABLED(CONFIG_VIDEO_SHELL)) {
114 LOG_INF("Letting the user control the device with the video shell");
115 return 0;
116 }
117
118 video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera));
119 if (!device_is_ready(video_dev)) {
120 LOG_ERR("%s: video device is not ready", video_dev->name);
121 return 0;
122 }
123
124 LOG_INF("Video device: %s", video_dev->name);
125
126 /* Get capabilities */
127 caps.type = type;
128 if (video_get_caps(video_dev, &caps)) {
129 LOG_ERR("Unable to retrieve video capabilities");
130 return 0;
131 }
132
133 LOG_INF("- Capabilities:");
134 while (caps.format_caps[i].pixelformat) {
135 const struct video_format_cap *fcap = &caps.format_caps[i];
136 /* fourcc to string */
137 LOG_INF(" %s width [%u; %u; %u] height [%u; %u; %u]",
138 VIDEO_FOURCC_TO_STR(fcap->pixelformat),
139 fcap->width_min, fcap->width_max, fcap->width_step,
140 fcap->height_min, fcap->height_max, fcap->height_step);
141 i++;
142 }
143
144 /* Get default/native format */
145 fmt.type = type;
146 if (video_get_format(video_dev, &fmt)) {
147 LOG_ERR("Unable to retrieve video format");
148 return 0;
149 }
150
151 /* Set the crop setting if necessary */
152 #if CONFIG_VIDEO_SOURCE_CROP_WIDTH && CONFIG_VIDEO_SOURCE_CROP_HEIGHT
153 sel.target = VIDEO_SEL_TGT_CROP;
154 sel.rect.left = CONFIG_VIDEO_SOURCE_CROP_LEFT;
155 sel.rect.top = CONFIG_VIDEO_SOURCE_CROP_TOP;
156 sel.rect.width = CONFIG_VIDEO_SOURCE_CROP_WIDTH;
157 sel.rect.height = CONFIG_VIDEO_SOURCE_CROP_HEIGHT;
158 if (video_set_selection(video_dev, &sel)) {
159 LOG_ERR("Unable to set selection crop");
160 return 0;
161 }
162 LOG_INF("Selection crop set to (%u,%u)/%ux%u",
163 sel.rect.left, sel.rect.top, sel.rect.width, sel.rect.height);
164 #endif
165
166 #if CONFIG_VIDEO_FRAME_HEIGHT || CONFIG_VIDEO_FRAME_WIDTH
167 #if CONFIG_VIDEO_FRAME_HEIGHT
168 fmt.height = CONFIG_VIDEO_FRAME_HEIGHT;
169 #endif
170
171 #if CONFIG_VIDEO_FRAME_WIDTH
172 fmt.width = CONFIG_VIDEO_FRAME_WIDTH;
173 #endif
174
175 /*
176 * Check (if possible) if targeted size is same as crop
177 * and if compose is necessary
178 */
179 sel.target = VIDEO_SEL_TGT_CROP;
180 err = video_get_selection(video_dev, &sel);
181 if (err < 0 && err != -ENOSYS) {
182 LOG_ERR("Unable to get selection crop");
183 return 0;
184 }
185
186 if (err == 0 && (sel.rect.width != fmt.width || sel.rect.height != fmt.height)) {
187 sel.target = VIDEO_SEL_TGT_COMPOSE;
188 sel.rect.left = 0;
189 sel.rect.top = 0;
190 sel.rect.width = fmt.width;
191 sel.rect.height = fmt.height;
192 err = video_set_selection(video_dev, &sel);
193 if (err < 0 && err != -ENOSYS) {
194 LOG_ERR("Unable to set selection compose");
195 return 0;
196 }
197 }
198 #endif
199
200 if (strcmp(CONFIG_VIDEO_PIXEL_FORMAT, "")) {
201 fmt.pixelformat = VIDEO_FOURCC_FROM_STR(CONFIG_VIDEO_PIXEL_FORMAT);
202 }
203
204 LOG_INF("- Video format: %s %ux%u",
205 VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height);
206
207 if (video_set_format(video_dev, &fmt)) {
208 LOG_ERR("Unable to set format");
209 return 0;
210 }
211
212 if (!video_get_frmival(video_dev, &frmival)) {
213 LOG_INF("- Default frame rate : %f fps",
214 1.0 * frmival.denominator / frmival.numerator);
215 }
216
217 LOG_INF("- Supported frame intervals for the default format:");
218 memset(&fie, 0, sizeof(fie));
219 fie.format = &fmt;
220 while (video_enum_frmival(video_dev, &fie) == 0) {
221 if (fie.type == VIDEO_FRMIVAL_TYPE_DISCRETE) {
222 LOG_INF(" %u/%u", fie.discrete.numerator, fie.discrete.denominator);
223 } else {
224 LOG_INF(" [min = %u/%u; max = %u/%u; step = %u/%u]",
225 fie.stepwise.min.numerator, fie.stepwise.min.denominator,
226 fie.stepwise.max.numerator, fie.stepwise.max.denominator,
227 fie.stepwise.step.numerator, fie.stepwise.step.denominator);
228 }
229 fie.index++;
230 }
231
232 /* Get supported controls */
233 LOG_INF("- Supported controls:");
234 const struct device *last_dev = NULL;
235 struct video_ctrl_query cq = {.dev = video_dev, .id = VIDEO_CTRL_FLAG_NEXT_CTRL};
236
237 while (!video_query_ctrl(&cq)) {
238 if (cq.dev != last_dev) {
239 last_dev = cq.dev;
240 LOG_INF("\t\tdevice: %s", cq.dev->name);
241 }
242 video_print_ctrl(&cq);
243 cq.id |= VIDEO_CTRL_FLAG_NEXT_CTRL;
244 }
245
246 /* Set controls */
247 struct video_control ctrl = {.id = VIDEO_CID_HFLIP, .val = 1};
248 int tp_set_ret = -ENOTSUP;
249
250 if (IS_ENABLED(CONFIG_VIDEO_CTRL_HFLIP)) {
251 video_set_ctrl(video_dev, &ctrl);
252 }
253
254 if (IS_ENABLED(CONFIG_VIDEO_CTRL_VFLIP)) {
255 ctrl.id = VIDEO_CID_VFLIP;
256 video_set_ctrl(video_dev, &ctrl);
257 }
258
259 if (IS_ENABLED(CONFIG_TEST)) {
260 ctrl.id = VIDEO_CID_TEST_PATTERN;
261 tp_set_ret = video_set_ctrl(video_dev, &ctrl);
262 }
263
264 #if DT_HAS_CHOSEN(zephyr_display)
265 const struct device *const display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
266
267 if (!device_is_ready(display_dev)) {
268 LOG_ERR("%s: display device not ready.", display_dev->name);
269 return 0;
270 }
271
272 err = display_setup(display_dev, fmt.pixelformat);
273 if (err) {
274 LOG_ERR("Unable to set up display");
275 return err;
276 }
277 #endif
278
279 /* Size to allocate for each buffer */
280 if (caps.min_line_count == LINE_COUNT_HEIGHT) {
281 bsize = fmt.pitch * fmt.height;
282 } else {
283 bsize = fmt.pitch * caps.min_line_count;
284 }
285
286 /* Alloc video buffers and enqueue for capture */
287 if (caps.min_vbuf_count > CONFIG_VIDEO_BUFFER_POOL_NUM_MAX ||
288 bsize > CONFIG_VIDEO_BUFFER_POOL_SZ_MAX) {
289 LOG_ERR("Not enough buffers or memory to start streaming");
290 return 0;
291 }
292
293 for (i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) {
294 /*
295 * For some hardwares, such as the PxP used on i.MX RT1170 to do image rotation,
296 * buffer alignment is needed in order to achieve the best performance
297 */
298 vbuf = video_buffer_aligned_alloc(bsize, CONFIG_VIDEO_BUFFER_POOL_ALIGN,
299 K_FOREVER);
300 if (vbuf == NULL) {
301 LOG_ERR("Unable to alloc video buffer");
302 return 0;
303 }
304 vbuf->type = type;
305 video_enqueue(video_dev, vbuf);
306 }
307
308 /* Start video capture */
309 if (video_stream_start(video_dev, type)) {
310 LOG_ERR("Unable to start capture (interface)");
311 return 0;
312 }
313
314 LOG_INF("Capture started");
315
316 /* Grab video frames */
317 vbuf->type = type;
318 while (1) {
319 err = video_dequeue(video_dev, &vbuf, K_FOREVER);
320 if (err) {
321 LOG_ERR("Unable to dequeue video buf");
322 return 0;
323 }
324
325 LOG_DBG("Got frame %u! size: %u; timestamp %u ms",
326 frame++, vbuf->bytesused, vbuf->timestamp);
327
328 #ifdef CONFIG_TEST
329 if (tp_set_ret < 0) {
330 LOG_DBG("Test pattern control was not successful. Skip test");
331 } else if (is_colorbar_ok(vbuf->buffer, fmt)) {
332 LOG_DBG("Pattern OK!\n");
333 }
334 #endif
335
336 #if DT_HAS_CHOSEN(zephyr_display)
337 video_display_frame(display_dev, vbuf, fmt);
338 #endif
339
340 err = video_enqueue(video_dev, vbuf);
341 if (err) {
342 LOG_ERR("Unable to requeue video buf");
343 return 0;
344 }
345 }
346 }
347