Skip to content

Commit 8d6d559

Browse files
committed
uvc: make width, height boot configurable; disable by default
1 parent 6b74263 commit 8d6d559

5 files changed

Lines changed: 172 additions & 94 deletions

File tree

locale/circuitpython.pot

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,7 @@ msgstr ""
737737

738738
#: shared-bindings/storage/__init__.c shared-bindings/usb_cdc/__init__.c
739739
#: shared-bindings/usb_hid/__init__.c shared-bindings/usb_midi/__init__.c
740+
#: shared-bindings/uvc/__init__.c
740741
msgid "Cannot change USB devices now"
741742
msgstr ""
742743

@@ -1971,14 +1972,6 @@ msgstr ""
19711972
msgid "Specify exactly one of data0 or data_pins"
19721973
msgstr ""
19731974

1974-
#: ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c
1975-
msgid "Stereo left must be on PWM channel A"
1976-
msgstr ""
1977-
1978-
#: ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c
1979-
msgid "Stereo right must be on PWM channel B"
1980-
msgstr ""
1981-
19821975
#: shared-bindings/alarm/time/TimeAlarm.c
19831976
msgid "Supply one of monotonic_time or epoch_time"
19841977
msgstr ""

shared-bindings/uvc/__init__.c

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,64 @@
4646
//| for the first pixel to be red and the second pixel to be green. However, they can both
4747
//| be different shades of red.
4848
//| """
49+
//|
50+
51+
//| def enable_framebuffer(width: int, height: int) -> None:
52+
//| """Enable the UVC gadget, setting the given width & height
53+
//|
54+
//| During ``boot.py``, this function may be used to enable the UVC gadget.
55+
//|
56+
//| Width is rounded up to a multiple of 2.
57+
//|
58+
//| After boot.py completes, the framebuffer will be allocated. Total storage
59+
//| of 4×``width``×``height`` bytes is required, reducing the amount available
60+
//| for Python objects. If the allocation fails, a note will be written in boot.py
61+
//| and UVC will be disabled"""
62+
//|
63+
64+
STATIC mp_obj_t uvc_enable_framebuffer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
65+
enum { ARG_width, ARG_height };
66+
static const mp_arg_t allowed_args[] = {
67+
{ MP_QSTR_width, MP_ARG_REQUIRED | MP_ARG_INT, { .u_int = 0 } },
68+
{ MP_QSTR_height, MP_ARG_REQUIRED | MP_ARG_INT, { .u_int = 0 } },
69+
};
70+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
71+
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
72+
73+
// (but note that most devices will not be able to allocate this much memory.
74+
uint32_t width = mp_arg_validate_int_range(args[ARG_width].u_int, 0, 32767, MP_QSTR_width);
75+
uint32_t height = mp_arg_validate_int_range(args[ARG_height].u_int, 0, 32767, MP_QSTR_height);
76+
if (!shared_module_uvc_enable(width, height)) {
77+
mp_raise_RuntimeError(MP_ERROR_TEXT("Cannot change USB devices now"));
78+
}
79+
80+
return mp_const_none;
81+
};
82+
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(uvc_enable_framebuffer_obj, 0, uvc_enable_framebuffer);
83+
84+
//|
85+
//| def disable() -> None:
86+
//| """Disable the UVC gadget
87+
//|
88+
//| During ``boot.py``, this function may be used to enable the UVC gadget.
89+
//|
90+
//| This function is provided for completeness. The default state of UVC is disabled."""
91+
//|
92+
STATIC mp_obj_t uvc_disable(void) {
93+
if (!shared_module_uvc_disable()) {
94+
mp_raise_RuntimeError(MP_ERROR_TEXT("Cannot change USB devices now"));
95+
}
96+
97+
return mp_const_none;
98+
};
99+
STATIC MP_DEFINE_CONST_FUN_OBJ_0(uvc_disable_obj, uvc_disable);
100+
49101

50-
static const mp_map_elem_t uvc_module_globals_table[] = {
102+
static const mp_rom_map_elem_t uvc_module_globals_table[] = {
51103
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uvc) },
52104
{ MP_ROM_QSTR(MP_QSTR_bitmap), MP_ROM_PTR(&uvc_bitmap_obj) },
105+
{ MP_ROM_QSTR(MP_QSTR_disable), MP_ROM_PTR(&uvc_disable_obj) },
106+
{ MP_ROM_QSTR(MP_QSTR_enable_framebuffer), MP_ROM_PTR(&uvc_enable_framebuffer_obj) },
53107
};
54108

55109
static MP_DEFINE_CONST_DICT(uvc_module_globals, uvc_module_globals_table);

shared-bindings/uvc/__init__.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@
2828

2929
#include "shared-module/displayio/Bitmap.h"
3030
extern displayio_bitmap_t uvc_bitmap_obj;
31+
bool shared_module_uvc_enable(mp_int_t frame_width, mp_int_t frame_height);
32+
bool shared_module_uvc_disable(void);

shared-module/uvc/__init__.c

Lines changed: 91 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,119 @@
11
#include "py/runtime.h"
22
#include <stdint.h>
3-
#include "shared-module/uvc/__init__.h"
4-
#include "shared-module/uvc/uvc_usb_descriptors.h"
3+
#include "class/video/video_device.h"
54
#include "shared-bindings/displayio/Bitmap.h"
5+
#include "shared-bindings/uvc/__init__.h"
66
#include "shared-module/bitmapfilter/macros.h"
7-
#include "class/video/video_device.h"
8-
#include "supervisor/shared/tick.h"
7+
#include "shared-module/uvc/__init__.h"
8+
#include "shared-module/uvc/uvc_usb_descriptors.h"
99
#include "supervisor/background_callback.h"
10-
11-
#if CFG_TUD_VIDEO_STREAMING_BULK
12-
static const uint8_t usb_uvc_descriptor_template[] = {
13-
TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR_BULK(0xaa, 0x80, FRAME_WIDTH, FRAME_HEIGHT, 10, 64)
14-
};
15-
16-
// Use the "descriptor.c" file to generate/verify these offsets
17-
const uint8_t interface_indices[] = {2, 10, 29, 59};
18-
const uint8_t string_indices[] = {7, 16, 65};
19-
const uint8_t endpoint_indices[] = {72, 153};
20-
21-
#else
22-
static const uint8_t usb_uvc_descriptor_template[] = {
23-
TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR(0xaa, 0x80, FRAME_WIDTH, FRAME_HEIGHT, 10, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE)
24-
};
25-
26-
// Use the "descriptor.c" file to generate/verify these offsets
27-
const uint8_t interface_indices[] = {2, 10, 29, 59, 153};
28-
const uint8_t string_indices[] = {7, 16, 65, 159};
29-
const uint8_t endpoint_indices[] = {72, 162};
30-
#endif
10+
#include "supervisor/shared/tick.h"
11+
#include "device/usbd.h"
3112

3213
static unsigned frame_num = 0;
3314
static unsigned tx_busy = 0;
34-
static unsigned interval_ms = 1000 / FRAME_RATE;
15+
static unsigned interval_ms = 1000 / DEFAULT_FRAME_RATE;
3516

3617
// TODO must dynamically allocate this, otherwise everyone pays for it
37-
static uint8_t frame_buffer_yuyv[FRAME_WIDTH * FRAME_HEIGHT * 16 / 8];
38-
static uint16_t frame_buffer_rgb565[FRAME_WIDTH * FRAME_HEIGHT];
18+
static uint8_t *frame_buffer_yuyv;
19+
static uint16_t *frame_buffer_rgb565;
3920

4021
displayio_bitmap_t uvc_bitmap_obj = {
4122
.base = {.type = &displayio_bitmap_type },
42-
.width = FRAME_WIDTH,
43-
.height = FRAME_HEIGHT,
44-
.data = (uint32_t *)frame_buffer_rgb565,
45-
.stride = FRAME_WIDTH,
4623
.bits_per_value = 16,
4724
.x_shift = 1,
4825
.x_mask = 1,
4926
.bitmask = 0xffff,
27+
// (other fields set when enabling uvc)
5028
};
5129

30+
static bool uvc_is_enabled = false;
31+
static mp_int_t uvc_frame_width, uvc_frame_height;
32+
33+
bool shared_module_uvc_enable(mp_int_t frame_width, mp_int_t frame_height) {
34+
if (tud_connected()) {
35+
return false;
36+
}
37+
38+
// this will free any previously allocated framebuffer as a side-effect
39+
shared_module_uvc_disable();
40+
41+
if (frame_width & 1) {
42+
// frame_width must be even, round it up
43+
frame_width++;
44+
}
45+
46+
uvc_frame_width = frame_width;
47+
uvc_frame_height = frame_height;
48+
49+
size_t framebuffer_size = uvc_frame_width * uvc_frame_height * 2;
50+
frame_buffer_yuyv = port_malloc(framebuffer_size, false);
51+
frame_buffer_rgb565 = port_malloc(framebuffer_size, false);
52+
if (!frame_buffer_yuyv || !frame_buffer_rgb565) {
53+
// this will free either of the buffers allocated just above, in
54+
// case one succeeded and the other failed.
55+
shared_module_uvc_disable();
56+
m_malloc_fail(2 * framebuffer_size);
57+
}
58+
memset(frame_buffer_yuyv, 0, framebuffer_size);
59+
memset(frame_buffer_rgb565, 0, framebuffer_size);
60+
61+
uvc_bitmap_obj.data = (uint32_t *)frame_buffer_rgb565;
62+
uvc_bitmap_obj.width = uvc_frame_width;
63+
uvc_bitmap_obj.height = uvc_frame_height;
64+
uvc_bitmap_obj.stride = uvc_frame_width / 2; /* in uint32_t units */
65+
66+
uvc_is_enabled = true;
67+
68+
return true;
69+
}
70+
71+
bool shared_module_uvc_disable(void) {
72+
if (tud_connected()) {
73+
return false;
74+
}
75+
uvc_bitmap_obj.data = NULL; // should be redundant
76+
uvc_is_enabled = false;
77+
port_free(frame_buffer_yuyv);
78+
port_free(frame_buffer_rgb565);
79+
frame_buffer_yuyv = NULL;
80+
frame_buffer_rgb565 = NULL;
81+
return true;
82+
}
83+
5284
bool usb_uvc_enabled(void) {
53-
return true; // TODO
85+
return uvc_is_enabled;
5486
}
5587

5688
size_t usb_uvc_descriptor_length(void) {
57-
return sizeof(usb_uvc_descriptor_template);
89+
#if CFG_TUD_VIDEO_STREAMING_BULK
90+
return sizeof((char[]) {TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR_BULK(0, 0, DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT, DEFAULT_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE, 0, 0)});
91+
#else
92+
return sizeof((char[]) {TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR(0, 0, DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT, DEFAULT_FRAME_RATE, 64, 0, 0)});
93+
#endif
5894
}
5995

6096
static void convert_framebuffer(void) {
6197
uint8_t *dest = frame_buffer_yuyv;
6298
uint16_t *src = frame_buffer_rgb565;
6399

64-
for (int i = 0; i < FRAME_WIDTH * FRAME_HEIGHT / 2; i++) {
100+
static int i = 0;
101+
if ((i++) % 100 == 0) {
102+
mp_printf(&mp_plat_print, "convert_framebuffer width=%d height=%d total pixel pairs = %d\n",
103+
uvc_frame_width, uvc_frame_height, uvc_frame_width * uvc_frame_height / 2);
104+
}
105+
for (int i = 0; i < uvc_frame_width * uvc_frame_height / 2; i++) {
65106
uint16_t p1 = IMAGE_GET_RGB565_PIXEL_FAST(src, 0);
66107
uint16_t p2 = IMAGE_GET_RGB565_PIXEL_FAST(src, 1);
67108
src += 2;
68109

69110
int y1 = COLOR_RGB565_TO_Y(p1);
111+
int y2 = COLOR_RGB565_TO_Y(p2);
112+
if (y2 > y1) {
113+
p1 = p2; /* Use UV value of the brighter pixel */
114+
}
70115
int u = COLOR_RGB565_TO_U(p1) + 128; // openmv UV are signed in the range [-127,128]
71116
int v = COLOR_RGB565_TO_V(p1) + 128;
72-
int y2 = COLOR_RGB565_TO_Y(p2);
73117

74118
*dest++ = y1;
75119
*dest++ = u;
@@ -80,31 +124,22 @@ static void convert_framebuffer(void) {
80124

81125
size_t usb_uvc_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *descriptor_counts, uint8_t *current_interface_string) {
82126
usb_add_interface_string(*current_interface_string, "CircuitPython UVC");
83-
84-
memcpy(descriptor_buf, usb_uvc_descriptor_template, sizeof(usb_uvc_descriptor_template));
85-
86-
for (size_t i = 0; i < TU_ARRAY_SIZE(string_indices); i++) {
87-
descriptor_buf[string_indices[i]] = *current_interface_string;
88-
}
89-
127+
const uint8_t usb_uvc_descriptor[] = {
128+
#if CFG_TUD_VIDEO_STREAMING_BULK
129+
TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR_BULK(*current_interface_string, descriptor_counts->current_endpoint | 0x80, uvc_frame_width, uvc_frame_height, DEFAULT_FRAME_RATE, 64, descriptor_counts->current_interface, descriptor_counts->current_interface + 1)
130+
#else
131+
TUD_VIDEO_CAPTURE_DESCRIPTOR_UNCOMPR(*current_interface_string, descriptor_counts->current_endpoint | 0x80, uvc_frame_width, uvc_frame_height, DEFAULT_FRAME_RATE, CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE, descriptor_counts->current_interface, descriptor_counts->current_interface + 1)
132+
#endif
133+
};
90134
(*current_interface_string)++;
91-
92-
for (size_t i = 0; i < TU_ARRAY_SIZE(interface_indices); i++) {
93-
descriptor_buf[interface_indices[i]] += descriptor_counts->current_interface;
94-
}
95135
descriptor_counts->current_interface += 2;
96-
97-
for (size_t i = 0; i < TU_ARRAY_SIZE(endpoint_indices); i++) {
98-
// the only endpoint is an IN endpoint so set the 0x80 bit accordingly
99-
descriptor_buf[endpoint_indices[i]] = descriptor_counts->current_endpoint | 0x80;
100-
}
101136
descriptor_counts->num_out_endpoints++;
102137
descriptor_counts->current_endpoint++;
103138
descriptor_counts->current_interface++;
104139

105-
supervisor_enable_tick();
140+
memcpy(descriptor_buf, usb_uvc_descriptor, sizeof(usb_uvc_descriptor));
106141

107-
return sizeof(usb_uvc_descriptor_template);
142+
return sizeof(usb_uvc_descriptor);
108143
}
109144

110145
background_callback_t uvc_cb;
@@ -135,7 +170,7 @@ STATIC void uvc_cb_fun(void *unused) {
135170
already_sent = 1;
136171
start_ms = supervisor_ticks_ms32();
137172
convert_framebuffer();
138-
bool result = tud_video_n_frame_xfer(0, 0, (void *)frame_buffer_yuyv, FRAME_WIDTH * FRAME_HEIGHT * 16 / 8);
173+
bool result = tud_video_n_frame_xfer(0, 0, (void *)frame_buffer_yuyv, uvc_frame_width * uvc_frame_height * 16 / 8);
139174
(void)result;
140175
// printf("(!already_sent) frame_xfer -> %d\n", (int)result);
141176
}
@@ -152,7 +187,7 @@ STATIC void uvc_cb_fun(void *unused) {
152187
start_ms += interval_ms;
153188

154189
convert_framebuffer();
155-
bool result = tud_video_n_frame_xfer(0, 0, (void *)frame_buffer_yuyv, FRAME_WIDTH * FRAME_HEIGHT * 16 / 8);
190+
bool result = tud_video_n_frame_xfer(0, 0, (void *)frame_buffer_yuyv, uvc_frame_width * uvc_frame_height * 16 / 8);
156191
(void)result;
157192
// printf("frame_xfer -> %d\n", (int)result);
158193
}

0 commit comments

Comments
 (0)