Skip to content

Commit ec673a6

Browse files
committed
usb: uvc: remove application decisions from the UVC class
The UVC class was deciding the format list for itself, which does not allow the application to convert the format, or filter which formats to expose to thoe host. Remove this logic out of the UVC class and expose an extra API to add formats to the list. Signed-off-by: Josuah Demangeon <me@josuah.net>
1 parent f30af78 commit ec673a6

File tree

3 files changed

+198
-138
lines changed

3 files changed

+198
-138
lines changed

include/zephyr/usb/class/usbd_uvc.h

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,38 @@
2626
*/
2727

2828
/**
29-
* @brief Set the video device that a UVC instance will use.
29+
* @brief Set the video device that a UVC instance will use for control requests.
3030
*
31-
* It will query its supported controls, formats and frame rates, and use this information to
32-
* generate USB descriptors sent to the host.
33-
*
34-
* At runtime, it will forward all USB controls from the host to this device.
31+
* It will query its supported video controls and frame intervals and use this information to
32+
* generate the USB descriptors presented to the host. At runtime, it will forward all USB controls
33+
* from the host to this device.
3534
*
3635
* @note This function must be called before @ref usbd_enable.
3736
*
3837
* @param uvc_dev The UVC device
39-
* @param video_dev The video device that this UVC instance controls
38+
* @param video_dev The video device that this UVC instance send controls requests to
4039
*/
4140
void uvc_set_video_dev(const struct device *uvc_dev, const struct device *video_dev);
4241

42+
/**
43+
* @brief Set the video format capabilities that a UVC instance will present to the host.
44+
*
45+
* This information will be used to generate USB descriptors.
46+
* The minimum and maximum values are considered as discrete values, as width and height ranges
47+
* are not supported by the UVC protocol, but only discrete values.
48+
*
49+
* The format selected by the host can then be queried through the @ref video_get_format API.
50+
*
51+
* @note This function must be called before @ref usbd_enable.
52+
*
53+
* @note The @p fmts memory pointed should remain valid for the entire lifetime of @p uvc_dev.
54+
*
55+
* @param uvc_dev The UVC device
56+
* @param fmt The video format to add
57+
* @return 0 on success, negative error code otherwise
58+
*/
59+
int uvc_add_format(const struct device *const dev, const struct video_format *const fmt);
60+
4361
/**
4462
* @}
4563
*/

samples/subsys/usb/uvc/src/main.c

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,66 @@
1717

1818
LOG_MODULE_REGISTER(uvc_sample, LOG_LEVEL_INF);
1919

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));
20+
const static struct device *const uvc_dev = DEVICE_DT_GET(DT_NODELABEL(uvc));
21+
const static struct device *const video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera));
22+
23+
/* Format capabilities of video_dev, usd everywhere through the sampel */
24+
static struct video_caps video_caps = {.type = VIDEO_BUF_TYPE_OUTPUT};
25+
26+
static size_t app_get_min_buf_size(const struct video_format *const fmt)
27+
{
28+
if (video_caps.min_line_count == LINE_COUNT_HEIGHT) {
29+
return fmt->pitch * fmt->height;
30+
} else {
31+
return fmt->pitch * video_caps.min_line_count;
32+
}
33+
}
34+
35+
static void app_add_format(uint32_t pixfmt, uint16_t width, uint16_t height)
36+
{
37+
struct video_format fmt = {
38+
.pixelformat = pixfmt,
39+
.width = width,
40+
.height = height,
41+
.type = VIDEO_BUF_TYPE_OUTPUT,
42+
};
43+
int ret;
44+
45+
/* Set the format to get the pitch */
46+
ret = video_set_format(video_dev, &fmt);
47+
if (ret != 0) {
48+
LOG_ERR("Could not set the format of %s", video_dev->name);
49+
return;
50+
}
51+
52+
if (app_get_min_buf_size(&fmt) > CONFIG_VIDEO_BUFFER_POOL_SZ_MAX) {
53+
LOG_WRN("Skipping format %ux%u", fmt.width, fmt.height);
54+
return;
55+
}
56+
57+
uvc_add_format(uvc_dev, &fmt);
58+
}
59+
60+
/* Submit to UVC only the formats expected to be working (enough memory for the size, etc.) */
61+
static void app_add_filtered_formats(void)
62+
{
63+
for (int i = 0; video_caps.format_caps[i].pixelformat != 0; i++) {
64+
const struct video_format_cap *vcap = &video_caps.format_caps[i];
65+
66+
app_add_format(vcap->pixelformat, vcap->width_min, vcap->height_min);
67+
68+
if (vcap->width_min != vcap->width_max || vcap->height_min != vcap->height_max) {
69+
app_add_format(vcap->pixelformat, vcap->width_max, vcap->height_max);
70+
}
71+
}
72+
}
2273

2374
int main(void)
2475
{
2576
struct usbd_context *sample_usbd;
2677
struct video_buffer *vbuf;
2778
struct video_format fmt = {0};
28-
struct video_caps caps;
79+
struct video_frmival frmival = {0};
2980
struct k_poll_signal sig;
3081
struct k_poll_event evt[1];
3182
k_timeout_t timeout = K_FOREVER;
@@ -37,16 +88,18 @@ int main(void)
3788
return -ENODEV;
3889
}
3990

40-
caps.type = VIDEO_BUF_TYPE_OUTPUT;
41-
42-
if (video_get_caps(video_dev, &caps)) {
91+
ret = video_get_caps(video_dev, &video_caps);
92+
if (ret != 0) {
4393
LOG_ERR("Unable to retrieve video capabilities");
4494
return 0;
4595
}
4696

47-
/* Must be done before initializing USB */
97+
/* Must be called before usb_enable() */
4898
uvc_set_video_dev(uvc_dev, video_dev);
4999

100+
/* Must be called before uvc_set_video_dev() */
101+
app_add_filtered_formats();
102+
50103
sample_usbd = sample_usbd_init_device(NULL);
51104
if (sample_usbd == NULL) {
52105
return -ENODEV;
@@ -59,7 +112,6 @@ int main(void)
59112

60113
LOG_INF("Waiting the host to select the video format");
61114

62-
/* Get the video format once it is selected by the host */
63115
while (true) {
64116
fmt.type = VIDEO_BUF_TYPE_INPUT;
65117

@@ -75,17 +127,32 @@ int main(void)
75127
k_sleep(K_MSEC(10));
76128
}
77129

78-
LOG_INF("The host selected format '%s' %ux%u, preparing %u buffers of %u bytes",
130+
ret = video_get_frmival(uvc_dev, &frmival);
131+
if (ret != 0) {
132+
LOG_ERR("Failed to get the video frame interval");
133+
return ret;
134+
}
135+
136+
LOG_INF("The host selected format '%s' %ux%u at frame interval %u/%u",
79137
VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height,
80-
CONFIG_VIDEO_BUFFER_POOL_NUM_MAX, fmt.pitch * fmt.height);
138+
frmival.numerator, frmival.denominator);
81139

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;
140+
fmt.type = VIDEO_BUF_TYPE_OUTPUT;
141+
142+
ret = video_set_format(video_dev, &fmt);
143+
if (ret != 0) {
144+
LOG_WRN("Could not set the format of %s", video_dev->name);
87145
}
88146

147+
ret = video_set_frmival(video_dev, &frmival);
148+
if (ret != 0) {
149+
LOG_WRN("Could not set the framerate of %s", video_dev->name);
150+
}
151+
152+
bsize = app_get_min_buf_size(&fmt);
153+
154+
LOG_INF("Preparing %u buffers of %u bytes", CONFIG_VIDEO_BUFFER_POOL_NUM_MAX, bsize);
155+
89156
for (int i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) {
90157
vbuf = video_buffer_alloc(bsize, K_NO_WAIT);
91158
if (vbuf == NULL) {

0 commit comments

Comments
 (0)