1 /*
2  * Copyright (c) 2024 Charles Dias <charlesdias.cd@outlook.com>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/kernel.h>
8 #include <zephyr/device.h>
9 #include <zephyr/drivers/display.h>
10 #include <zephyr/drivers/video.h>
11 #include <zephyr/drivers/video-controls.h>
12 #include <zephyr/logging/log.h>
13 #include <lvgl.h>
14 
15 LOG_MODULE_REGISTER(main, CONFIG_LOG_DEFAULT_LEVEL);
16 
17 #if !DT_HAS_CHOSEN(zephyr_camera)
18 #error No camera chosen in devicetree. Missing "--shield" or "--snippet video-sw-generator" flag?
19 #endif
20 
21 #if !DT_HAS_CHOSEN(zephyr_display)
22 #error No display chosen in devicetree. Missing "--shield" flag?
23 #endif
24 
main(void)25 int main(void)
26 {
27 	struct video_buffer *buffers[2];
28 	struct video_buffer *vbuf = &(struct video_buffer){};
29 	const struct device *display_dev;
30 	const struct device *video_dev;
31 	struct video_format fmt;
32 	struct video_caps caps;
33 	enum video_buf_type type = VIDEO_BUF_TYPE_OUTPUT;
34 	struct video_selection sel = {
35 		.type = VIDEO_BUF_TYPE_OUTPUT,
36 	};
37 	size_t bsize;
38 	int i = 0;
39 	int err;
40 
41 	display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
42 	if (!device_is_ready(display_dev)) {
43 		LOG_ERR("Device not ready, aborting test");
44 		return 0;
45 	}
46 
47 	video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera));
48 	if (!device_is_ready(video_dev)) {
49 		LOG_ERR("%s device is not ready", video_dev->name);
50 		return 0;
51 	}
52 
53 	LOG_INF("- Device name: %s", video_dev->name);
54 
55 	/* Get capabilities */
56 	caps.type = type;
57 	if (video_get_caps(video_dev, &caps)) {
58 		LOG_ERR("Unable to retrieve video capabilities");
59 		return 0;
60 	}
61 
62 	LOG_INF("- Capabilities:");
63 	while (caps.format_caps[i].pixelformat) {
64 		const struct video_format_cap *fcap = &caps.format_caps[i];
65 		/* four %c to string */
66 		LOG_INF("  %c%c%c%c width [%u; %u; %u] height [%u; %u; %u]",
67 			(char)fcap->pixelformat, (char)(fcap->pixelformat >> 8),
68 			(char)(fcap->pixelformat >> 16), (char)(fcap->pixelformat >> 24),
69 			fcap->width_min, fcap->width_max, fcap->width_step, fcap->height_min,
70 			fcap->height_max, fcap->height_step);
71 		i++;
72 	}
73 
74 	/* Get default/native format */
75 	fmt.type = type;
76 	if (video_get_format(video_dev, &fmt)) {
77 		LOG_ERR("Unable to retrieve video format");
78 		return 0;
79 	}
80 
81 	/* Set the crop setting if necessary */
82 #if CONFIG_VIDEO_SOURCE_CROP_WIDTH && CONFIG_VIDEO_SOURCE_CROP_HEIGHT
83 	sel.target = VIDEO_SEL_TGT_CROP;
84 	sel.rect.left = CONFIG_VIDEO_SOURCE_CROP_LEFT;
85 	sel.rect.top = CONFIG_VIDEO_SOURCE_CROP_TOP;
86 	sel.rect.width = CONFIG_VIDEO_SOURCE_CROP_WIDTH;
87 	sel.rect.height = CONFIG_VIDEO_SOURCE_CROP_HEIGHT;
88 	if (video_set_selection(video_dev, &sel)) {
89 		LOG_ERR("Unable to set selection crop");
90 		return 0;
91 	}
92 	LOG_INF("Selection crop set to (%u,%u)/%ux%u",
93 		sel.rect.left, sel.rect.top, sel.rect.width, sel.rect.height);
94 #endif
95 
96 	/* Set format */
97 	fmt.width = CONFIG_VIDEO_WIDTH;
98 	fmt.height = CONFIG_VIDEO_HEIGHT;
99 	fmt.pixelformat = VIDEO_PIX_FMT_RGB565;
100 
101 	/*
102 	 * Check (if possible) if targeted size is same as crop
103 	 * and if compose is necessary
104 	 */
105 	sel.target = VIDEO_SEL_TGT_CROP;
106 	err = video_get_selection(video_dev, &sel);
107 	if (err < 0 && err != -ENOSYS) {
108 		LOG_ERR("Unable to get selection crop");
109 		return 0;
110 	}
111 
112 	if (err == 0 && (sel.rect.width != fmt.width || sel.rect.height != fmt.height)) {
113 		sel.target = VIDEO_SEL_TGT_COMPOSE;
114 		sel.rect.left = 0;
115 		sel.rect.top = 0;
116 		sel.rect.width = fmt.width;
117 		sel.rect.height = fmt.height;
118 		err = video_set_selection(video_dev, &sel);
119 		if (err < 0 && err != -ENOSYS) {
120 			LOG_ERR("Unable to set selection compose");
121 			return 0;
122 		}
123 	}
124 
125 	if (video_set_format(video_dev, &fmt)) {
126 		LOG_ERR("Unable to set up video format");
127 		return 0;
128 	}
129 
130 	LOG_INF("- Format: %c%c%c%c %ux%u %u", (char)fmt.pixelformat, (char)(fmt.pixelformat >> 8),
131 		(char)(fmt.pixelformat >> 16), (char)(fmt.pixelformat >> 24), fmt.width, fmt.height,
132 		fmt.pitch);
133 
134 	if (caps.min_line_count != LINE_COUNT_HEIGHT) {
135 		LOG_ERR("Partial framebuffers not supported by this sample");
136 		return 0;
137 	}
138 	/* Size to allocate for each buffer */
139 	bsize = fmt.pitch * fmt.height;
140 
141 	/* Alloc video buffers and enqueue for capture */
142 	for (i = 0; i < ARRAY_SIZE(buffers); i++) {
143 		buffers[i] = video_buffer_aligned_alloc(bsize, CONFIG_VIDEO_BUFFER_POOL_ALIGN,
144 							K_FOREVER);
145 		if (buffers[i] == NULL) {
146 			LOG_ERR("Unable to alloc video buffer");
147 			return 0;
148 		}
149 		buffers[i]->type = type;
150 		video_enqueue(video_dev, buffers[i]);
151 	}
152 
153 	/* Set controls */
154 	struct video_control ctrl = {.id = VIDEO_CID_HFLIP, .val = 1};
155 
156 	if (IS_ENABLED(CONFIG_VIDEO_HFLIP)) {
157 		video_set_ctrl(video_dev, &ctrl);
158 	}
159 
160 	if (IS_ENABLED(CONFIG_VIDEO_VFLIP)) {
161 		ctrl.id = VIDEO_CID_VFLIP;
162 		video_set_ctrl(video_dev, &ctrl);
163 	}
164 
165 	/* Start video capture */
166 	if (video_stream_start(video_dev, type)) {
167 		LOG_ERR("Unable to start capture (interface)");
168 		return 0;
169 	}
170 
171 	display_blanking_off(display_dev);
172 
173 	const lv_img_dsc_t video_img = {
174 		.header.w = CONFIG_VIDEO_WIDTH,
175 		.header.h = CONFIG_VIDEO_HEIGHT,
176 		.data_size = CONFIG_VIDEO_WIDTH * CONFIG_VIDEO_HEIGHT * sizeof(lv_color_t),
177 		.header.cf = LV_COLOR_FORMAT_NATIVE,
178 		.data = (const uint8_t *)buffers[0]->buffer,
179 	};
180 
181 	lv_obj_t *screen = lv_img_create(lv_scr_act());
182 
183 	LOG_INF("- Capture started");
184 
185 	/* Grab video frames */
186 	vbuf->type = type;
187 	while (1) {
188 		err = video_dequeue(video_dev, &vbuf, K_FOREVER);
189 		if (err) {
190 			LOG_ERR("Unable to dequeue video buf");
191 			return 0;
192 		}
193 
194 		lv_img_set_src(screen, &video_img);
195 		lv_obj_align(screen, LV_ALIGN_BOTTOM_LEFT, 0, 0);
196 
197 		lv_task_handler();
198 
199 		err = video_enqueue(video_dev, vbuf);
200 		if (err) {
201 			LOG_ERR("Unable to requeue video buf");
202 			return 0;
203 		}
204 	}
205 }
206