1 /*
2  * Copyright (c) 2025 tinyVision.ai Inc.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stdint.h>
8 #include <stdlib.h>
9 
10 #include <sample_usbd.h>
11 
12 #include <zephyr/device.h>
13 #include <zephyr/usb/usbd.h>
14 #include <zephyr/drivers/video.h>
15 #include <zephyr/logging/log.h>
16 #include <zephyr/usb/class/usbd_uvc.h>
17 
18 LOG_MODULE_REGISTER(uvc_sample, LOG_LEVEL_INF);
19 
20 const struct device *const uvc_dev = DEVICE_DT_GET(DT_NODELABEL(uvc));
21 const struct device *const video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera));
22 
main(void)23 int main(void)
24 {
25 	struct usbd_context *sample_usbd;
26 	struct video_buffer *vbuf;
27 	struct video_format fmt = {0};
28 	struct video_caps caps;
29 	struct k_poll_signal sig;
30 	struct k_poll_event evt[1];
31 	k_timeout_t timeout = K_FOREVER;
32 	size_t bsize;
33 	int ret;
34 
35 	if (!device_is_ready(video_dev)) {
36 		LOG_ERR("video source %s failed to initialize", video_dev->name);
37 		return -ENODEV;
38 	}
39 
40 	caps.type = VIDEO_BUF_TYPE_OUTPUT;
41 
42 	if (video_get_caps(video_dev, &caps)) {
43 		LOG_ERR("Unable to retrieve video capabilities");
44 		return 0;
45 	}
46 
47 	/* Must be done before initializing USB */
48 	uvc_set_video_dev(uvc_dev, video_dev);
49 
50 	sample_usbd = sample_usbd_init_device(NULL);
51 	if (sample_usbd == NULL) {
52 		return -ENODEV;
53 	}
54 
55 	ret = usbd_enable(sample_usbd);
56 	if (ret != 0) {
57 		return ret;
58 	}
59 
60 	LOG_INF("Waiting the host to select the video format");
61 
62 	/* Get the video format once it is selected by the host */
63 	while (true) {
64 		fmt.type = VIDEO_BUF_TYPE_INPUT;
65 
66 		ret = video_get_format(uvc_dev, &fmt);
67 		if (ret == 0) {
68 			break;
69 		}
70 		if (ret != -EAGAIN) {
71 			LOG_ERR("Failed to get the video format");
72 			return ret;
73 		}
74 
75 		k_sleep(K_MSEC(10));
76 	}
77 
78 	LOG_INF("The host selected format '%s' %ux%u, preparing %u buffers of %u bytes",
79 		VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height,
80 		CONFIG_VIDEO_BUFFER_POOL_NUM_MAX, fmt.pitch * fmt.height);
81 
82 	/* Size to allocate for each buffer */
83 	if (caps.min_line_count == LINE_COUNT_HEIGHT) {
84 		bsize = fmt.pitch * fmt.height;
85 	} else {
86 		bsize = fmt.pitch * caps.min_line_count;
87 	}
88 
89 	for (int i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) {
90 		vbuf = video_buffer_alloc(bsize, K_NO_WAIT);
91 		if (vbuf == NULL) {
92 			LOG_ERR("Could not allocate the video buffer");
93 			return -ENOMEM;
94 		}
95 
96 		vbuf->type = VIDEO_BUF_TYPE_OUTPUT;
97 
98 		ret = video_enqueue(video_dev, vbuf);
99 		if (ret != 0) {
100 			LOG_ERR("Could not enqueue video buffer");
101 			return ret;
102 		}
103 	}
104 
105 	LOG_DBG("Preparing signaling for %s input/output", video_dev->name);
106 
107 	k_poll_signal_init(&sig);
108 	k_poll_event_init(&evt[0], K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, &sig);
109 
110 	ret = video_set_signal(video_dev, &sig);
111 	if (ret != 0) {
112 		LOG_WRN("Failed to setup the signal on %s output endpoint", video_dev->name);
113 		timeout = K_MSEC(1);
114 	}
115 
116 	ret = video_set_signal(uvc_dev, &sig);
117 	if (ret != 0) {
118 		LOG_ERR("Failed to setup the signal on %s input endpoint", uvc_dev->name);
119 		return ret;
120 	}
121 
122 	LOG_INF("Starting the video transfer");
123 
124 	ret = video_stream_start(video_dev, VIDEO_BUF_TYPE_OUTPUT);
125 	if (ret != 0) {
126 		LOG_ERR("Failed to start %s", video_dev->name);
127 		return ret;
128 	}
129 
130 	while (true) {
131 		ret = k_poll(evt, ARRAY_SIZE(evt), timeout);
132 		if (ret != 0 && ret != -EAGAIN) {
133 			LOG_ERR("Poll exited with status %d", ret);
134 			return ret;
135 		}
136 
137 		vbuf = &(struct video_buffer){.type = VIDEO_BUF_TYPE_OUTPUT};
138 
139 		if (video_dequeue(video_dev, &vbuf, K_NO_WAIT) == 0) {
140 			LOG_DBG("Dequeued %p from %s, enqueueing to %s",
141 				(void *)vbuf, video_dev->name, uvc_dev->name);
142 
143 			vbuf->type = VIDEO_BUF_TYPE_INPUT;
144 
145 			ret = video_enqueue(uvc_dev, vbuf);
146 			if (ret != 0) {
147 				LOG_ERR("Could not enqueue video buffer to %s", uvc_dev->name);
148 				return ret;
149 			}
150 		}
151 
152 		vbuf = &(struct video_buffer){.type = VIDEO_BUF_TYPE_INPUT};
153 
154 		if (video_dequeue(uvc_dev, &vbuf, K_NO_WAIT) == 0) {
155 			LOG_DBG("Dequeued %p from %s, enqueueing to %s",
156 				(void *)vbuf, uvc_dev->name, video_dev->name);
157 
158 			vbuf->type = VIDEO_BUF_TYPE_OUTPUT;
159 
160 			ret = video_enqueue(video_dev, vbuf);
161 			if (ret != 0) {
162 				LOG_ERR("Could not enqueue video buffer to %s", video_dev->name);
163 				return ret;
164 			}
165 		}
166 
167 		k_poll_signal_reset(&sig);
168 	}
169 
170 	return 0;
171 }
172