|
| 1 | +// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | +#include "USBVideo.h" |
| 15 | + |
| 16 | +#if CONFIG_TINYUSB_VIDEO_ENABLED |
| 17 | + |
| 18 | +#include "esp32-hal-tinyusb.h" |
| 19 | + |
| 20 | +/* Time stamp base clock. It is a deprecated parameter. */ |
| 21 | +#define UVC_CLOCK_FREQUENCY 27000000 |
| 22 | +/* video capture path */ |
| 23 | +#define UVC_ENTITY_CAP_INPUT_TERMINAL 0x01 |
| 24 | +#define UVC_ENTITY_CAP_OUTPUT_TERMINAL 0x02 |
| 25 | + |
| 26 | +#define TUD_VIDEO_CAPTURE_DESC_LEN (\ |
| 27 | + TUD_VIDEO_DESC_IAD_LEN\ |
| 28 | + /* control */\ |
| 29 | + + TUD_VIDEO_DESC_STD_VC_LEN\ |
| 30 | + + (TUD_VIDEO_DESC_CS_VC_LEN + 1/*bInCollection*/)\ |
| 31 | + + TUD_VIDEO_DESC_INPUT_TERM_LEN\ |
| 32 | + + TUD_VIDEO_DESC_OUTPUT_TERM_LEN\ |
| 33 | + /* Interface 1, Alternate 0 */\ |
| 34 | + + TUD_VIDEO_DESC_STD_VS_LEN\ |
| 35 | + + (TUD_VIDEO_DESC_CS_VS_IN_LEN + 1/*bNumFormats x bControlSize*/)\ |
| 36 | + + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN\ |
| 37 | + + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN\ |
| 38 | + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN\ |
| 39 | + /* Interface 1, Alternate 1 */\ |
| 40 | + + TUD_VIDEO_DESC_STD_VS_LEN\ |
| 41 | + + 7/* Endpoint */\ |
| 42 | + ) |
| 43 | + |
| 44 | +/* Windows support YUY2 and NV12 |
| 45 | + * https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/usb-video-class-driver-overview */ |
| 46 | + |
| 47 | +#define TUD_VIDEO_DESC_CS_VS_FMT_YUY2(_fmtidx, _numfmtdesc, _frmidx, _asrx, _asry, _interlace, _cp) \ |
| 48 | + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_YUY2, 16, _frmidx, _asrx, _asry, _interlace, _cp) |
| 49 | +#define TUD_VIDEO_DESC_CS_VS_FMT_NV12(_fmtidx, _numfmtdesc, _frmidx, _asrx, _asry, _interlace, _cp) \ |
| 50 | + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_NV12, 12, _frmidx, _asrx, _asry, _interlace, _cp) |
| 51 | +#define TUD_VIDEO_DESC_CS_VS_FMT_M420(_fmtidx, _numfmtdesc, _frmidx, _asrx, _asry, _interlace, _cp) \ |
| 52 | + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_M420, 12, _frmidx, _asrx, _asry, _interlace, _cp) |
| 53 | +#define TUD_VIDEO_DESC_CS_VS_FMT_I420(_fmtidx, _numfmtdesc, _frmidx, _asrx, _asry, _interlace, _cp) \ |
| 54 | + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR(_fmtidx, _numfmtdesc, TUD_VIDEO_GUID_I420, 12, _frmidx, _asrx, _asry, _interlace, _cp) |
| 55 | + |
| 56 | +#define TUD_VIDEO_CAPTURE_DESCRIPTOR(_itf, _stridx, _epin, _width, _height, _fps, _epsize) \ |
| 57 | + TUD_VIDEO_DESC_IAD(_itf, (_itf+2), _stridx), \ |
| 58 | + /* Video control 0 */ \ |
| 59 | + TUD_VIDEO_DESC_STD_VC(_itf, 0, _stridx), \ |
| 60 | + TUD_VIDEO_DESC_CS_VC( /* UVC 1.5*/ 0x0150, \ |
| 61 | + /* wTotalLength - bLength */ \ |
| 62 | + TUD_VIDEO_DESC_INPUT_TERM_LEN + TUD_VIDEO_DESC_OUTPUT_TERM_LEN, \ |
| 63 | + UVC_CLOCK_FREQUENCY, 1), \ |
| 64 | + TUD_VIDEO_DESC_INPUT_TERM(UVC_ENTITY_CAP_INPUT_TERMINAL, VIDEO_ETT_COMPOSITE_CONNECTOR, 0, 0), \ |
| 65 | + TUD_VIDEO_DESC_OUTPUT_TERM(UVC_ENTITY_CAP_OUTPUT_TERMINAL, VIDEO_TT_STREAMING, 0, 1, 0), \ |
| 66 | + /* Video stream alt. 0 */ \ |
| 67 | + TUD_VIDEO_DESC_STD_VS( (_itf+1), 0, 0, 0), \ |
| 68 | + /* Video stream header for without still image capture */ \ |
| 69 | + TUD_VIDEO_DESC_CS_VS_INPUT( /*bNumFormats*/1, \ |
| 70 | + /*wTotalLength - bLength */\ |
| 71 | + TUD_VIDEO_DESC_CS_VS_FMT_UNCOMPR_LEN\ |
| 72 | + + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT_LEN\ |
| 73 | + + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING_LEN,\ |
| 74 | + _epin, /*bmInfo*/0, /*bTerminalLink*/UVC_ENTITY_CAP_OUTPUT_TERMINAL, \ |
| 75 | + /*bStillCaptureMethod*/0, /*bTriggerSupport*/0, /*bTriggerUsage*/0, \ |
| 76 | + /*bmaControls(1)*/0), \ |
| 77 | + /* Video stream format */ \ |
| 78 | + TUD_VIDEO_DESC_CS_VS_FMT_YUY2(/*bFormatIndex*/1, /*bNumFrameDescriptors*/1, \ |
| 79 | + /*bDefaultFrameIndex*/1, 0, 0, 0, /*bCopyProtect*/0), \ |
| 80 | + /* Video stream frame format */ \ |
| 81 | + TUD_VIDEO_DESC_CS_VS_FRM_UNCOMPR_CONT(/*bFrameIndex */1, 0, _width, _height, \ |
| 82 | + _width * _height * 16, _width * _height * 16 * _fps, \ |
| 83 | + _width * _height * 16, \ |
| 84 | + (10000000/_fps), (10000000/_fps), 10000000, 100000), \ |
| 85 | + TUD_VIDEO_DESC_CS_VS_COLOR_MATCHING(VIDEO_COLOR_PRIMARIES_BT709, VIDEO_COLOR_XFER_CH_BT709, VIDEO_COLOR_COEF_SMPTE170M), \ |
| 86 | + /* VS alt 1 */\ |
| 87 | + TUD_VIDEO_DESC_STD_VS((_itf+1), 1, 1, 0), \ |
| 88 | + /* EP */ \ |
| 89 | + TUD_VIDEO_DESC_EP_ISO(_epin, _epsize, 1) |
| 90 | + |
| 91 | + |
| 92 | +ESP_EVENT_DEFINE_BASE(ARDUINO_USB_VIDEO_EVENTS); |
| 93 | +esp_err_t arduino_usb_event_post(esp_event_base_t event_base, int32_t event_id, void *event_data, size_t event_data_size, TickType_t ticks_to_wait); |
| 94 | +esp_err_t arduino_usb_event_handler_register_with(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg); |
| 95 | + |
| 96 | +#define FRAME_WIDTH 80 |
| 97 | +#define FRAME_HEIGHT 60 |
| 98 | +#define FRAME_RATE 10 |
| 99 | + |
| 100 | +uint16_t tusb_video_load_descriptor(uint8_t * dst, uint8_t * itf) |
| 101 | +{ |
| 102 | + uint8_t str_index = tinyusb_add_string_descriptor("TinyUSB Video"); |
| 103 | + uint8_t ep_num = tinyusb_get_free_in_endpoint(); |
| 104 | + TU_VERIFY (ep_num != 0); |
| 105 | + uint8_t descriptor[TUD_VIDEO_CAPTURE_DESC_LEN] = { |
| 106 | + // Interface number, string index, EP Out & IN address, EP size |
| 107 | + TUD_VIDEO_CAPTURE_DESCRIPTOR(*itf, str_index, (uint8_t)(0x80 | ep_num), FRAME_WIDTH, FRAME_HEIGHT, FRAME_RATE, 64) |
| 108 | + }; |
| 109 | + *itf+=2; |
| 110 | + memcpy(dst, descriptor, TUD_VIDEO_CAPTURE_DESC_LEN); |
| 111 | + return TUD_VIDEO_CAPTURE_DESC_LEN; |
| 112 | + // size_t desc_len = sizeof(UVCConfigurationDescriptor); |
| 113 | + // memcpy(dst, UVCConfigurationDescriptor, desc_len); |
| 114 | + // return desc_len; |
| 115 | +} |
| 116 | + |
| 117 | +static unsigned frame_num = 0; |
| 118 | +static unsigned tx_busy = 0; |
| 119 | +static unsigned interval_ms = 1000 / FRAME_RATE; |
| 120 | +static uint8_t frame_buffer[FRAME_WIDTH * FRAME_HEIGHT * 16 / 8]; |
| 121 | + |
| 122 | +static void fill_color_bar(uint8_t *buffer, unsigned start_position) |
| 123 | +{ |
| 124 | + /* EBU color bars |
| 125 | + * See also https://stackoverflow.com/questions/6939422 */ |
| 126 | + static uint8_t const bar_color[8][4] = { |
| 127 | + /* Y, U, Y, V */ |
| 128 | + { 235, 128, 235, 128}, /* 100% White */ |
| 129 | + { 219, 16, 219, 138}, /* Yellow */ |
| 130 | + { 188, 154, 188, 16}, /* Cyan */ |
| 131 | + { 173, 42, 173, 26}, /* Green */ |
| 132 | + { 78, 214, 78, 230}, /* Magenta */ |
| 133 | + { 63, 102, 63, 240}, /* Red */ |
| 134 | + { 32, 240, 32, 118}, /* Blue */ |
| 135 | + { 16, 128, 16, 128}, /* Black */ |
| 136 | + }; |
| 137 | + uint8_t *p; |
| 138 | + |
| 139 | + /* Generate the 1st line */ |
| 140 | + uint8_t *end = &buffer[FRAME_WIDTH * 2]; |
| 141 | + unsigned idx = (FRAME_WIDTH / 2 - 1) - (start_position % (FRAME_WIDTH / 2)); |
| 142 | + p = &buffer[idx * 4]; |
| 143 | + for (unsigned i = 0; i < 8; ++i) { |
| 144 | + for (int j = 0; j < FRAME_WIDTH / (2 * 8); ++j) { |
| 145 | + memcpy(p, &bar_color[i], 4); |
| 146 | + p += 4; |
| 147 | + if (end <= p) { |
| 148 | + p = buffer; |
| 149 | + } |
| 150 | + } |
| 151 | + } |
| 152 | + /* Duplicate the 1st line to the others */ |
| 153 | + p = &buffer[FRAME_WIDTH * 2]; |
| 154 | + for (unsigned i = 1; i < FRAME_HEIGHT; ++i) { |
| 155 | + memcpy(p, buffer, FRAME_WIDTH * 2); |
| 156 | + p += FRAME_WIDTH * 2; |
| 157 | + } |
| 158 | +} |
| 159 | + |
| 160 | +void video_task(void) |
| 161 | +{ |
| 162 | + static unsigned start_ms = 0; |
| 163 | + static unsigned already_sent = 0; |
| 164 | + |
| 165 | + if (!tud_video_n_streaming(0, 0)) { |
| 166 | + already_sent = 0; |
| 167 | + frame_num = 0; |
| 168 | + return; |
| 169 | + } |
| 170 | + |
| 171 | + if (!already_sent) { |
| 172 | + already_sent = 1; |
| 173 | + start_ms = millis(); |
| 174 | + fill_color_bar(frame_buffer, frame_num); |
| 175 | + tud_video_n_frame_xfer(0, 0, (void*)frame_buffer, FRAME_WIDTH * FRAME_HEIGHT * 16/8); |
| 176 | + } |
| 177 | + |
| 178 | + unsigned cur = millis(); |
| 179 | + if (cur - start_ms < interval_ms) return; // not enough time |
| 180 | + if (tx_busy) return; |
| 181 | + start_ms += interval_ms; |
| 182 | + fill_color_bar(frame_buffer, frame_num); |
| 183 | + tud_video_n_frame_xfer(0, 0, (void*)frame_buffer, FRAME_WIDTH * FRAME_HEIGHT * 16/8); |
| 184 | +} |
| 185 | + |
| 186 | + |
| 187 | + |
| 188 | +/** Invoked when compeletion of a frame transfer |
| 189 | + * |
| 190 | + * @param[in] ctl_idx Destination control interface index |
| 191 | + * @param[in] stm_idx Destination streaming interface index */ |
| 192 | +extern "C" void tud_video_frame_xfer_complete_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx){ |
| 193 | + log_d(""); |
| 194 | + tx_busy = 0; |
| 195 | + ++frame_num; |
| 196 | +} |
| 197 | + |
| 198 | +/** Invoked when VS_COMMIT_CONTROL(SET_CUR) request received |
| 199 | + * |
| 200 | + * @param[in] ctl_idx Destination control interface index |
| 201 | + * @param[in] stm_idx Destination streaming interface index |
| 202 | + * @param[in] parameters Video streaming parameters |
| 203 | + * @return video_error_code_t */ |
| 204 | +extern "C" int tud_video_commit_cb(uint_fast8_t ctl_idx, uint_fast8_t stm_idx, video_probe_and_commit_control_t const *parameters){ |
| 205 | + log_d("[%u][%u]:", ctl_idx, stm_idx); |
| 206 | + /* convert unit to ms from 100 ns */ |
| 207 | + interval_ms = parameters->dwFrameInterval / 10000; |
| 208 | + return VIDEO_ERROR_NONE; |
| 209 | +} |
| 210 | + |
| 211 | +/** Invoked when SET_POWER_MODE request received |
| 212 | + * |
| 213 | + * @param[in] ctl_idx Destination control interface index |
| 214 | + * @param[in] stm_idx Destination streaming interface index |
| 215 | + * @return video_error_code_t */ |
| 216 | +extern "C" int tud_video_power_mode_cb(uint_fast8_t ctl_idx, uint8_t power_mod){ |
| 217 | + log_d("[%u]: %u", ctl_idx, power_mod); |
| 218 | + return VIDEO_ERROR_NONE; |
| 219 | +} |
| 220 | + |
| 221 | +USBVideo::USBVideo(uint8_t ctl, uint8_t stm):_ctl(ctl), _stm(stm){ |
| 222 | + tinyusb_enable_interface(USB_INTERFACE_VIDEO, TUD_VIDEO_CAPTURE_DESC_LEN, tusb_video_load_descriptor); |
| 223 | + //tinyusb_enable_interface(USB_INTERFACE_VIDEO, sizeof(UVCConfigurationDescriptor), tusb_video_load_descriptor); |
| 224 | +} |
| 225 | + |
| 226 | +void USBVideo::begin(){ |
| 227 | + |
| 228 | +} |
| 229 | + |
| 230 | +void USBVideo::end(){ |
| 231 | + |
| 232 | +} |
| 233 | + |
| 234 | +void USBVideo::onEvent(esp_event_handler_t callback){ |
| 235 | + onEvent(ARDUINO_USB_VIDEO_ANY_EVENT, callback); |
| 236 | +} |
| 237 | + |
| 238 | +void USBVideo::onEvent(arduino_usb_video_event_t event, esp_event_handler_t callback){ |
| 239 | + arduino_usb_event_handler_register_with(ARDUINO_USB_VIDEO_EVENTS, event, callback, this); |
| 240 | +} |
| 241 | + |
| 242 | +bool USBVideo::streaming(){ |
| 243 | + return tud_video_n_streaming(_ctl, _stm); |
| 244 | +} |
| 245 | + |
| 246 | +bool USBVideo::sendFrame(void *buffer, size_t len){ |
| 247 | + return tud_video_n_frame_xfer(_ctl, _stm, buffer, len); |
| 248 | +} |
| 249 | + |
| 250 | +#endif /* CONFIG_TINYUSB_VIDEO_ENABLED */ |
0 commit comments