Skip to content

Commit 208f6c8

Browse files
authored
Merge pull request #366 from adafruit/enable-uvc-rp2040
Add Video class support
2 parents 693f7d4 + 70fd45a commit 208f6c8

File tree

9 files changed

+475
-8
lines changed

9 files changed

+475
-8
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2020 Jerzy Kasenbreg
5+
* Copyright (c) 2021 Koji KITAYAMA
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*
25+
*/
26+
27+
#ifndef _USB_DESCRIPTORS_H_
28+
#define _USB_DESCRIPTORS_H_
29+
30+
/* Time stamp base clock. It is a deprecated parameter. */
31+
#define UVC_CLOCK_FREQUENCY 27000000
32+
/* video capture path */
33+
#define UVC_ENTITY_CAP_INPUT_TERMINAL 0x01
34+
#define UVC_ENTITY_CAP_OUTPUT_TERMINAL 0x02
35+
36+
enum {
37+
ITF_NUM_VIDEO_CONTROL,
38+
ITF_NUM_VIDEO_STREAMING,
39+
ITF_NUM_TOTAL
40+
};
41+
42+
#define TUD_VIDEO_CAPTURE_DESC_UNCOMPR_LEN (\
43+
TUD_VIDEO_DESC_IAD_LEN\
44+
/* control */\
45+
+ TUD_VIDEO_DESC_STD_VC_LEN\
46+
+ (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\
47+
+ TUD_VIDEO_DESC_CAMERA_TERM_LEN\
48+
+ TUD_VIDEO_DESC_OUTPUT_TERM_LEN\
49+
/* Interface 1, Alternate 0 */\
50+
+ TUD_VIDEO_DESC_STD_VS_LEN\
51+
+ (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\
52+
+ TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN\
53+
+ TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN\
54+
+ TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\
55+
/* Interface 1, Alternate 1 */\
56+
+ TUD_VIDEO_DESC_STD_VS_LEN\
57+
+ 7/* Endpoint */\
58+
)
59+
60+
#define TUD_VIDEO_CAPTURE_DESC_MJPEG_LEN (\
61+
TUD_VIDEO_DESC_IAD_LEN\
62+
/* control */\
63+
+ TUD_VIDEO_DESC_STD_VC_LEN\
64+
+ (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\
65+
+ TUD_VIDEO_DESC_CAMERA_TERM_LEN\
66+
+ TUD_VIDEO_DESC_OUTPUT_TERM_LEN\
67+
/* Interface 1, Alternate 0 */\
68+
+ TUD_VIDEO_DESC_STD_VS_LEN\
69+
+ (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\
70+
+ TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN\
71+
+ TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN\
72+
+ TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\
73+
/* Interface 1, Alternate 1 */\
74+
+ TUD_VIDEO_DESC_STD_VS_LEN\
75+
+ 7/* Endpoint */\
76+
)
77+
78+
#define TUD_VIDEO_CAPTURE_DESC_UNCOMPR_BULK_LEN (\
79+
TUD_VIDEO_DESC_IAD_LEN\
80+
/* control */\
81+
+ TUD_VIDEO_DESC_STD_VC_LEN\
82+
+ (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\
83+
+ TUD_VIDEO_DESC_CAMERA_TERM_LEN\
84+
+ TUD_VIDEO_DESC_OUTPUT_TERM_LEN\
85+
/* Interface 1, Alternate 0 */\
86+
+ TUD_VIDEO_DESC_STD_VS_LEN\
87+
+ (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\
88+
+ TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN\
89+
+ TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN\
90+
+ TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\
91+
+ 7/* Endpoint */\
92+
)
93+
94+
#define TUD_VIDEO_CAPTURE_DESC_MJPEG_BULK_LEN (\
95+
TUD_VIDEO_DESC_IAD_LEN\
96+
/* control */\
97+
+ TUD_VIDEO_DESC_STD_VC_LEN\
98+
+ (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\
99+
+ TUD_VIDEO_DESC_CAMERA_TERM_LEN\
100+
+ TUD_VIDEO_DESC_OUTPUT_TERM_LEN\
101+
/* Interface 1, Alternate 0 */\
102+
+ TUD_VIDEO_DESC_STD_VS_LEN\
103+
+ (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\
104+
+ TUD_VIDEO_DESC_CS_VS_FMT_MJPEG_LEN\
105+
+ TUD_VIDEO_DESC_CS_VS_FRM_MJPEG_CONT_LEN\
106+
+ TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\
107+
+ 7/* Endpoint */\
108+
)
109+
110+
/* Windows support YUY2 and NV12
111+
* https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/usb-video-class-driver-overview */
112+
113+
#define TUD_VIDEO_DESC_CS_VS_FMT_YUY2(_fmtidx, _numfmtdesc, _frmidx, _asrx, _asry, _interlace, _cp) \
114+
TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_YUY2, 16, _frmidx, _asrx, _asry, _interlace, _cp)
115+
#define TUD_VIDEO_DESC_CS_VS_FMT_NV12(_fmtidx, _numfmtdesc, _frmidx, _asrx, _asry, _interlace, _cp) \
116+
TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_NV12, 12, _frmidx, _asrx, _asry, _interlace, _cp)
117+
#define TUD_VIDEO_DESC_CS_VS_FMT_M420(_fmtidx, _numfmtdesc, _frmidx, _asrx, _asry, _interlace, _cp) \
118+
TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_M420, 12, _frmidx, _asrx, _asry, _interlace, _cp)
119+
#define TUD_VIDEO_DESC_CS_VS_FMT_I420(_fmtidx, _numfmtdesc, _frmidx, _asrx, _asry, _interlace, _cp) \
120+
TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_I420, 12, _frmidx, _asrx, _asry, _interlace, _cp)
121+
122+
123+
#define TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR_BULK(_stridx, _epin, _width, _height, _fps, _epsize) \
124+
TUD_VIDEO_DESC_IAD(ITF_NUM_VIDEO_CONTROL, /* 2 Interfaces */ 0x02, _stridx), \
125+
/* Video control 0 */ \
126+
TUD_VIDEO_DESC_STD_VC(ITF_NUM_VIDEO_CONTROL, 0, _stridx), \
127+
/* Header: UVC 1.5, wTotalLength - bLength */ \
128+
TUD_VIDEO_DESC_CS_VC(0x0150, TUD_VIDEO_DESC_CAMERA_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, UVC_CLOCK_FREQUENCY, ITF_NUM_VIDEO_STREAMING), \
129+
/* Camera Terminal: ID, bAssocTerminal, iTerminal, focal min, max, length, bmControl */ \
130+
TUD_VIDEO_DESC_CAMERA_TERM(UVC_ENTITY_CAP_INPUT_TERMINAL, 0, 0, 0, 0, 0, 0), \
131+
TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, VIDEO_TT_STREAMING, 0, 1, 0), \
132+
/* Video stream alt. 0 */ \
133+
TUD_VIDEO_DESC_STD_VS(ITF_NUM_VIDEO_STREAMING, 0, 1, _stridx), \
134+
/* Video stream header for without still image capture */ \
135+
TUD_VIDEO_DESC_CS_VS_INPUT( /*bNumFormats*/1, \
136+
/*wTotalLength - bLength */\
137+
TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN,\
138+
_epin, /*bmInfo*/0, /*bTerminalLink*/UVC_ENTITY_CAP_OUTPUT_TERMINAL, \
139+
/*bStillCaptureMethod*/0, /*bTriggerSupport*/0, /*bTriggerUsage*/0, \
140+
/*bmaControls(1)*/0), \
141+
/* Video stream format */ \
142+
TUD_VIDEO_DESC_CS_VS_FMT_YUY2(/*bFormatIndex*/1, /*bNumFrameDescriptors*/1, \
143+
/*bDefaultFrameIndex*/1, 0, 0, 0, /*bCopyProtect*/0), \
144+
/* Video stream frame format */ \
145+
TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT(/*bFrameIndex */1, 0, _width, _height, \
146+
_width * _height * 16, _width * _height * 16 * _fps, \
147+
_width * _height * 16, \
148+
(10000000/_fps), (10000000/_fps), (10000000/_fps)*_fps, (10000000/_fps)), \
149+
TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(VIDEO_COLOR_PRIMARIES_BT709, VIDEO_COLOR_XFER_CH_BT709, VIDEO_COLOR_COEF_SMPTE170M), \
150+
TUD_VIDEO_DESC_EP_BULK(_epin, _epsize, 1)
151+
152+
#endif
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*********************************************************************
2+
Adafruit invests time and resources providing this open source code,
3+
please support Adafruit and open-source hardware by purchasing
4+
products from Adafruit!
5+
6+
MIT license, check LICENSE for more information
7+
Copyright (c) 2019 Ha Thach for Adafruit Industries
8+
All text above, and the splash screen below must be included in
9+
any redistribution
10+
*********************************************************************/
11+
12+
#include "Adafruit_TinyUSB.h"
13+
#include "usb_descriptors.h"
14+
15+
//--------------------------------------------------------------------+
16+
//
17+
//--------------------------------------------------------------------+
18+
#define FRAME_WIDTH 128
19+
#define FRAME_HEIGHT 96
20+
#define FRAME_RATE 30
21+
22+
uint8_t const desc_video[] = {
23+
TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR_BULK(0, 0x80, FRAME_WIDTH, FRAME_HEIGHT, FRAME_RATE, 64)
24+
};
25+
26+
Adafruit_USBD_Video usb_video(desc_video, sizeof(desc_video));
27+
28+
// YUY2 frame buffer
29+
static uint8_t frame_buffer[FRAME_WIDTH * FRAME_HEIGHT * 16 / 8];
30+
31+
static unsigned frame_num = 0;
32+
static unsigned tx_busy = 0;
33+
static unsigned interval_ms = 1000 / FRAME_RATE;
34+
static unsigned start_ms = 0;
35+
static unsigned already_sent = 0;
36+
37+
//--------------------------------------------------------------------+
38+
//
39+
//--------------------------------------------------------------------+
40+
static void fill_color_bar(uint8_t* buffer, unsigned start_position);
41+
42+
void setup() {
43+
Serial.begin(115200);
44+
usb_video.begin();
45+
}
46+
47+
void loop() {
48+
if (!tud_video_n_streaming(0, 0)) {
49+
already_sent = 0;
50+
frame_num = 0;
51+
return;
52+
}
53+
54+
if (!already_sent) {
55+
already_sent = 1;
56+
start_ms = millis();
57+
fill_color_bar(frame_buffer, frame_num);
58+
tud_video_n_frame_xfer(0, 0, (void*) frame_buffer, FRAME_WIDTH * FRAME_HEIGHT * 16 / 8);
59+
}
60+
61+
unsigned cur = millis();
62+
if (cur - start_ms < interval_ms) return; // not enough time
63+
if (tx_busy) return;
64+
start_ms += interval_ms;
65+
66+
fill_color_bar(frame_buffer, frame_num);
67+
tud_video_n_frame_xfer(0, 0, (void*) frame_buffer, FRAME_WIDTH * FRAME_HEIGHT * 16 / 8);
68+
}
69+
70+
void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx) {
71+
(void) ctl_idx;
72+
(void) stm_idx;
73+
tx_busy = 0;
74+
/* flip buffer */
75+
++frame_num;
76+
}
77+
78+
int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx,
79+
video_probe_and_commit_control_t const* parameters) {
80+
(void) ctl_idx;
81+
(void) stm_idx;
82+
/* convert unit to ms from 100 ns */
83+
interval_ms = parameters->dwFrameInterval / 10000;
84+
return VIDEO_ERROR_NONE;
85+
}
86+
87+
//------------- Helper -------------//
88+
static void fill_color_bar(uint8_t* buffer, unsigned start_position) {
89+
/* EBU color bars
90+
* See also https://stackoverflow.com/questions/6939422 */
91+
static uint8_t const bar_color[8][4] = {
92+
/* Y, U, Y, V */
93+
{ 235, 128, 235, 128}, /* 100% White */
94+
{ 219, 16, 219, 138}, /* Yellow */
95+
{ 188, 154, 188, 16}, /* Cyan */
96+
{ 173, 42, 173, 26}, /* Green */
97+
{ 78, 214, 78, 230}, /* Magenta */
98+
{ 63, 102, 63, 240}, /* Red */
99+
{ 32, 240, 32, 118}, /* Blue */
100+
{ 16, 128, 16, 128}, /* Black */
101+
};
102+
uint8_t* p;
103+
104+
/* Generate the 1st line */
105+
uint8_t* end = &buffer[FRAME_WIDTH * 2];
106+
unsigned idx = (FRAME_WIDTH / 2 - 1) - (start_position % (FRAME_WIDTH / 2));
107+
p = &buffer[idx * 4];
108+
for (unsigned i = 0; i < 8; ++i) {
109+
for (int j = 0; j < FRAME_WIDTH / (2 * 8); ++j) {
110+
memcpy(p, &bar_color[i], 4);
111+
p += 4;
112+
if (end <= p) {
113+
p = buffer;
114+
}
115+
}
116+
}
117+
118+
/* Duplicate the 1st line to the others */
119+
p = &buffer[FRAME_WIDTH * 2];
120+
for (unsigned i = 1; i < FRAME_HEIGHT; ++i) {
121+
memcpy(p, buffer, FRAME_WIDTH * 2);
122+
p += FRAME_WIDTH * 2;
123+
}
124+
}

src/Adafruit_TinyUSB.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@
6464
#include "arduino/webusb/Adafruit_USBD_WebUSB.h"
6565
#endif
6666

67+
#if CFG_TUD_VIDEO
68+
#include "arduino/video/Adafruit_USBD_Video.h"
69+
#endif
70+
6771
// Initialize device hardware, stack, also Serial as CDC
6872
// Wrapper for TinyUSBDevice.begin(rhport)
6973
void TinyUSB_Device_Init(uint8_t rhport);

src/arduino/Adafruit_USBD_Device.cpp

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,49 @@ bool Adafruit_USBD_Device::addInterface(Adafruit_USBD_Interface &itf) {
244244
}
245245

246246
// Parse interface descriptor to update
247-
// - Interface Number & string descriptor
248-
// - Endpoint address
247+
// - IAD: interface number
248+
// - Interface: number & string descriptor
249+
// - Endpoint: address
249250
while (desc < desc_end) {
250-
if (tu_desc_type(desc) == TUSB_DESC_INTERFACE) {
251+
switch (tu_desc_type(desc)) {
252+
case TUSB_DESC_INTERFACE_ASSOCIATION: {
253+
tusb_desc_interface_assoc_t *desc_iad =
254+
(tusb_desc_interface_assoc_t *)desc;
255+
desc_iad->bFirstInterface = _itf_count;
256+
break;
257+
}
258+
259+
case TUSB_DESC_INTERFACE: {
251260
tusb_desc_interface_t *desc_itf = (tusb_desc_interface_t *)desc;
261+
desc_itf->bInterfaceNumber = _itf_count;
262+
263+
#if CFG_TUD_VIDEO && CFG_TUD_VIDEO_STREAMING
264+
if (TUSB_CLASS_VIDEO == desc_itf->bInterfaceClass) {
265+
desc += tu_desc_len(desc); // next to CS Interface
266+
267+
if (TUSB_DESC_CS_INTERFACE == tu_desc_type(desc)) {
268+
uint8_t const subtype = desc[2];
269+
270+
if (VIDEO_SUBCLASS_CONTROL == desc_itf->bInterfaceSubClass) {
271+
// Adjust stream interface number in VC Header
272+
if (subtype == VIDEO_CS_ITF_VC_HEADER) {
273+
uint8_t const vs_count = desc[11];
274+
for (uint8_t i = 0; i < vs_count; i++) {
275+
desc[12 + i] += _itf_count;
276+
}
277+
}
278+
} else if (VIDEO_SUBCLASS_STREAMING == desc_itf->bInterfaceSubClass) {
279+
// Adjust the endpoint address in CS VS Input/Output Header
280+
if (subtype == VIDEO_CS_ITF_VS_INPUT_HEADER) {
281+
desc[6] = 0x80 | _epin_count;
282+
} else if (subtype == VIDEO_CS_ITF_VS_OUTPUT_HEADER) {
283+
desc[6] = _epout_count;
284+
}
285+
}
286+
}
287+
}
288+
#endif
289+
252290
if (desc_itf->bAlternateSetting == 0) {
253291
_itf_count++;
254292
if (desc_str && (_desc_str_count < STRING_DESCRIPTOR_MAX)) {
@@ -258,12 +296,23 @@ bool Adafruit_USBD_Device::addInterface(Adafruit_USBD_Interface &itf) {
258296

259297
// only assign string index to first interface
260298
desc_str = NULL;
299+
} else {
300+
desc_itf->iInterface = 0;
261301
}
262302
}
263-
} else if (tu_desc_type(desc) == TUSB_DESC_ENDPOINT) {
303+
304+
break;
305+
}
306+
307+
case TUSB_DESC_ENDPOINT: {
264308
tusb_desc_endpoint_t *desc_ep = (tusb_desc_endpoint_t *)desc;
265309
desc_ep->bEndpointAddress |=
266310
(desc_ep->bEndpointAddress & 0x80) ? _epin_count++ : _epout_count++;
311+
break;
312+
}
313+
314+
default:
315+
break;
267316
}
268317

269318
if (desc[0] == 0) {

0 commit comments

Comments
 (0)