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