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