-
Notifications
You must be signed in to change notification settings - Fork 7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
usb: device_next: new USB Video Class (UVC) implementation #76798
base: main
Are you sure you want to change the base?
Conversation
I am grateful for the work on the USB and Video stacks of Zephyr, as well as the entire Zephyr tree, on the shoulder of which this is built. |
Force push:
|
Force-push:
|
.dwMinBitRate = sys_cpu_to_le32(15360000), \ | ||
.dwMaxBitRate = sys_cpu_to_le32(15360000), \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where are these values coming from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are chosen arbitrarily, and I need to think about what should I do here: pick reasonable defaults? Deduce from other values? Let the user figure out by exposing a devicetree option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In future work, it will be possible to ask the sensor for VIDEO_CID_PIXEL_RATE
in combination with video_bits_per_pixel()
to generate this field.
.bLength = sizeof(struct usb_ep_descriptor), \ | ||
.bDescriptorType = USB_DESC_ENDPOINT, \ | ||
.bEndpointAddress = 0x81, \ | ||
.bmAttributes = USB_EP_TYPE_BULK, \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe UVC can be BULK or ISO.... but I only see BULK supported here. Do you plan to have a way to select which type of EP?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to mention that only BULK is supported through this early version. I will need to spend more time investigating the device_next
APIs for how ISO is implemented.
I cannot guarantee ETAs as I would do this feature on free time, but will add it to the roadmap: this will be needed at some point.
Very grateful for your reviews @XenuIsWatching I will force-push with the changes as soon as I get a chance to do so. |
Force-push:
|
182ac7c
to
cec26cc
Compare
02c1087
to
4bdc1ea
Compare
force push: rebased on |
force push: changed the way descriptors are declared and introduce video controls at the descriptor level (no support for controls commands yet) |
force push: implemented the UVC controls for the supported Video class controls. I did not test this yet with the samples, but on the internal fork, we could get an IMX219 sensor with exposure and gain control from the host, format selection at startup (but not runtime, see [1]). I believe that the last step for me is to test the sample on several boards that support |
Known limitations:[1]: there is currently no [2]: [3]: [4]: [5]: There are missing implementations for selector unit, extensions unit, encoding unit. TODO. [6]: UVC introduces dependencies between some controls, i.e. auto-expopsure needs to be off for exposure to be accepted. TODO. [7]: [8]: [9]: [10]: Supports custom header size, but not passing custom header data yet. [11]: Still image capture (capturing one frame at full resolution) not supported. [12]: USB3CV compliance tests not all passing since last rebase. TODO. [13]: Announcing different resolutions/FPS for different connection speed not supported. [14]: Asynchronous controls (the host setting a control, and a notification interrupt alert of completion) not supported. Supported features:[A]: Class API and enumeration [B]: [C]: [D]: [E]: Handling of control commands end-to-end from host to Zephyr video device [F]: Zephyr native Video API that allows to enqueue/dequeue frames like any other Zephyr video device. [G]: Supports fragmented frames as discussed in #66994 and #72827 [H]: [I]: Supports querying the min/max/current values of every controls end-to-end from host to Zephyr video driver. |
a7cd6cb
to
ea60ec0
Compare
union uvc_stream_descriptor *desc = &strm->desc->if_vs_formats[*id]; | ||
uint32_t pixfmt = cap->pixelformat; | ||
|
||
if (*id >= CONFIG_USBD_VIDEO_MAX_VS_DESC) { | ||
LOG_ERR("Out of descriptors, consider increasing CONFIG_USBD_VIDEO_MAX_VS_DESC"); | ||
return -ENOMEM; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we are assigning desc based on id, i believe the if (*id >= CONFIG_USBD_VIDEO_MAX_VS_DESC) check should be prior to the desc assignment.
strm->desc->if_vs_header.bNumFormats + 1, | ||
pixfmt >> 0 & 0xff, pixfmt >> 8 & 0xff, pixfmt >> 16 & 0xff, pixfmt >> 24 & 0xff); | ||
|
||
memset(desc, 0, sizeof(*desc)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More of a suggestion, If desc is guaranteed to be Non NULL at this point, it is fine, otherwise it is good to keep NULL check before doing any memsets in order to avoid undefined behavior (memset on NULL pointer).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's desc = &array[*id];
, but I added a __ASSERT_NO_MSG()
to raise awareness i.e. in case of refactoring.
Thank you!
struct video_format fmt = {.pixelformat = cap->pixelformat, | ||
.width = w, .height = h, .pitch = p}; | ||
struct video_frmival_enum fie = {.format = &fmt}; | ||
union uvc_stream_descriptor *desc = &strm->desc->if_vs_formats[*id]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we are assigning desc based on id, i believe the if (*id >= CONFIG_USBD_VIDEO_MAX_VS_DESC) check should be prior to the desc assignment.
return -EINVAL; | ||
} | ||
strm->format_id = probe->bFormatIndex; | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to handle default case for request incase of invalid request?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I originally had one, but factorized it from all locations here:
5fbd6d7#diff-8741d252cf20802cf806752bfd74a7e120f74445aa181cfd85ff04c9e2dc8ec0R889-R893
However, this leads to loose coupling between code and associated checks, better have a default
!
return -EINVAL; | ||
} | ||
strm->frame_id = probe->bFrameIndex; | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to handle default case for request incase of invalid request?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as here. Thanks!
case SET_CUR: | ||
strm->video_frmival.numerator = sys_le32_to_cpu(probe->dwFrameInterval); | ||
break; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to handle default case for request incase of invalid request?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as here. Thanks!
sys_le32_to_cpu(probe->dwMaxVideoFrameSize) != max_frame_size) { | ||
LOG_WRN("probe: dwMaxVideoFrameSize is read-only"); | ||
} | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to handle default case for request incase of invalid request?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as here. Thanks!
force-push:
|
samples/subsys/usb/uvc/sample.yaml
Outdated
sample.subsys.usb.uvc.camera: | ||
depends_on: | ||
- usbd | ||
tags: usb video | ||
filter: dt_chosen_enabled("zephyr,camera") | ||
integration_platforms: | ||
- arduino_nicla_vision/stm32h747xx/m7 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that it depends on video-dcmi.overlay. I thought the source was abstracted by the video driver/subsystem. If there is no generic way to get the video source, then you cannot use filter: dt_chosen_enabled("zephyr,camera")
and integration_platform because it would fail on other platforms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the source was abstracted by the video driver/subsystem.
Not yet... so I removed the sample.subsys.usb.uvc.camera
hoping that it is the best thing to do until a long term solution solves it.
Thank you once again for the guidance.
force-push:
|
CI just finished. Thanks for waiting. Force-push:
|
#define SET_CUR 0x01 | ||
#define GET_CUR 0x81 | ||
#define GET_MIN 0x82 | ||
#define GET_MAX 0x83 | ||
#define GET_RES 0x84 | ||
#define GET_LEN 0x85 | ||
#define GET_INFO 0x86 | ||
#define GET_DEF 0x87 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest to prefix them with UVC_ to minimize the risk of colliding with whatever madness the vendor's HALs contain.
/** | ||
* @brief Get the currently selected format and frame descriptors. | ||
* | ||
* @param strm The VideoStreaming interface from which search the descriptors | ||
* @param format_desc Pointer to be set to a format descriptor pointer. | ||
* @param frame_desc Pointer to be set to a frame descriptor pointer. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please never use doxygen format in c or local header files.
case GET_RES: | ||
case GET_MIN: | ||
probe->bFormatIndex = 1; | ||
break; | ||
case GET_MAX: | ||
probe->bFormatIndex = max; | ||
break; | ||
case GET_CUR: | ||
probe->bFormatIndex = strm->format_id; | ||
break; | ||
case SET_CUR: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GET_/SET_ must not be mixed, but must be handled in separate handlers. See my comment below about net_buf pointer.
|
||
static void uvc_update(struct usbd_class_data *const c_data, uint8_t iface, uint8_t alternate) | ||
{ | ||
LOG_DBG("update"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LOG_DBG("Select alternate %u for interface %u", alternate, iface);
uint32_t max_size = MAX(p, w) * h; | ||
|
||
LOG_DBG("Adding frame descriptor #%u for %ux%u", | ||
format_desc->format.bNumFrameDescriptors + 1, (unsigned int)w, (unsigned int)h); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LOG_DBG("Adding frame descriptor #%u for %ux%u",
format_desc->format.bNumFrameDescriptors + 1, w, h);
@@ -11,4 +11,5 @@ supported: | |||
- gpio | |||
- spi | |||
- i2c | |||
- usbd |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move it to a separate commit "boards: nicla_vision: add usbd test feature"
Force-push:
This is an early preview that was not tested yet, only there to give an idea. |
Introduce a new USB Video Class (UVC) implementation from scratch. It exposes a native Zephyr Video driver interface, allowing to call the video_enqueue()/video_dequeue() interface. It will query the attached video device to learn about the pipeline capabilities, and use this to configure the USB descriptors. At runtime, this UVC implementation will send this device all the control requests, which it can then dispatch to the rest of the pipeline. The application can poll the format currently selected by the host, but will not be alerted when the host configures a new format, as there is no video.h API for it yet. Signed-off-by: Josuah Demangeon <[email protected]>
The Arduino Nicla Vision board supports the new device_next USB stack and can be used for testing USB Device features such as UVC. Signed-off-by: Josuah Demangeon <[email protected]>
Following the addition of USB Video Class, this adds a sample that makes use of the &zephyr,camera chosen node of any board to stream the video source to the host. A fallback video-emul.overlay is provided for test and debugging purpose for devices without a camera. Signed-off-by: Josuah Demangeon <[email protected]>
Force-push:
Current limitations (likely not due to this refactoring):
High-level outline:
|
Kind of sad this won't make it in 4.1 😢 |
How to test:
The method to test with any
device_next
board:The method to test with any device with a
dcmi
node:It will use a very small test pattern as video source, so no camera is required.
To connect a camera instead of a test pattern, connect a different video source through the devicetree.
The USB descriptors will be updated by querying the driver.
Dependencies:
drivers: video: introduce "GET" sub-operations #78603replaced by something else to comedrivers: video: allow allocation with a header preceding the buffer #79168not required anymoreThis is a preview of an USB Video Class implementation for Zephyr that was only tested on custom devices insofar.
Proper Zephyr samples will be provided on upcoming commits. The API is simply to submit frames to the UVC device like to any Zephyr video device.
There is an unsolved challenge around the Video API: there is no
set_format
because the Zephyr application cannot decide what the host uses, onlyget_format
for what the host does support. But there is a missing video API to allow the driver to warn the application about a forced format change requested by the host. I thought of maybe reusingset_signal()
to also warn about format changes and not just buffer events.I will now work on building examples for existing Zephyr devices, as this was built for a custom USB3 board.
Here is the devicetree configuration used insofar:
The sizes and FPS are to be selected by the developer. The USB descriptors get configured as described above, and the host will request a particular format. Once that is done, the USB Video Class driver can let the application know which of these was selected through the video.hget_format()
API.This is still a draft PR, but I am grateful for comments and suggestions. I am willing to do the work of refactoring this as much as needed.
[EDIT: see this comment for latest description]