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