26#include <winpr/wtypes.h>
30#include <linux/uvcvideo.h>
31#include <linux/videodev2.h>
37static uint8_t GUID_UVCX_H264_XU[16] = { 0x41, 0x76, 0x9E, 0xA2, 0x04, 0xDE, 0xE3, 0x47,
38 0x8B, 0x2B, 0xF4, 0x34, 0x1A, 0xFF, 0x00, 0x3B };
40#define TAG CHANNELS_TAG("rdpecam-uvch264.client")
51static uint16_t get_length_xu_control(
CamV4lStream* stream, uint8_t unit, uint8_t selector)
54 WINPR_ASSERT(stream->fd > 0);
58 struct uvc_xu_control_query xu_ctrl_query = { .unit = unit,
61 .size =
sizeof(length),
62 .data = (uint8_t*)&length };
64 if (ioctl(stream->fd, UVCIOC_CTRL_QUERY, &xu_ctrl_query) < 0)
66 char ebuffer[256] = WINPR_C_ARRAY_INIT;
67 WLog_ERR(TAG,
"UVCIOC_CTRL_QUERY (GET_LEN) - Error: %s",
68 winpr_strerror(errno, ebuffer,
sizeof(ebuffer)));
86static int query_xu_control(
CamV4lStream* stream, uint8_t unit, uint8_t selector, uint8_t query,
90 uint16_t len = get_length_xu_control(stream, unit, selector);
92 struct uvc_xu_control_query xu_ctrl_query = {
93 .unit = unit, .selector = selector, .query = query, .size = len, .data = (uint8_t*)data
97 if ((err = ioctl(stream->fd, UVCIOC_CTRL_QUERY, &xu_ctrl_query)) < 0)
99 char ebuffer[256] = WINPR_C_ARRAY_INIT;
100 WLog_ERR(TAG,
"UVCIOC_CTRL_QUERY (%" PRIu8
") - Error: %s", query,
101 winpr_strerror(errno, ebuffer,
sizeof(ebuffer)));
114static int uvcx_video_encoder_reset(
CamV4lStream* stream)
116 WINPR_ASSERT(stream);
122 if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_ENCODER_RESET, UVC_SET_CUR,
123 &encoder_reset_req)) < 0)
125 char ebuffer[256] = WINPR_C_ARRAY_INIT;
126 WLog_ERR(TAG,
"UVCX_ENCODER_RESET error: %s",
127 winpr_strerror(errno, ebuffer,
sizeof(ebuffer)));
142static int uvcx_video_probe(
CamV4lStream* stream, uint8_t query,
145 WINPR_ASSERT(stream);
149 if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_VIDEO_CONFIG_PROBE, query,
150 uvcx_video_config)) < 0)
152 char ebuffer[256] = WINPR_C_ARRAY_INIT;
153 WLog_ERR(TAG,
"UVCX_VIDEO_CONFIG_PROBE error: %s",
154 winpr_strerror(errno, ebuffer,
sizeof(ebuffer)));
171 WINPR_ASSERT(stream);
175 if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_VIDEO_CONFIG_COMMIT, UVC_SET_CUR,
176 uvcx_video_config)) < 0)
178 char ebuffer[256] = WINPR_C_ARRAY_INIT;
179 WLog_ERR(TAG,
"UVCX_VIDEO_CONFIG_COMMIT error: %s",
180 winpr_strerror(errno, ebuffer,
sizeof(ebuffer)));
196 WINPR_ASSERT(stream);
197 WINPR_ASSERT(mediaType);
203 err = uvcx_video_encoder_reset(stream);
208 err = uvcx_video_probe(stream, UVC_GET_DEF, &config_probe_req);
213 config_probe_req.wWidth = WINPR_ASSERTING_INT_CAST(uint16_t, mediaType->Width);
214 config_probe_req.wHeight = WINPR_ASSERTING_INT_CAST(uint16_t, mediaType->Height);
217 uint32_t frame_interval =
218 (mediaType->FrameRateDenominator * 1000000000LL / mediaType->FrameRateNumerator) / 100;
219 config_probe_req.dwFrameInterval = frame_interval;
222 config_probe_req.wProfile = PROFILE_HIGH;
223 config_probe_req.bUsageType = USAGETYPE_REALTIME;
224 config_probe_req.bRateControlMode = RATECONTROL_VBR;
225 config_probe_req.dwBitRate = h264_get_max_bitrate(mediaType->Height);
226 config_probe_req.bEntropyCABAC = ENTROPY_CABAC;
227 config_probe_req.wIFramePeriod = 1000;
230 config_probe_req.bmHints = BMHINTS_RESOLUTION | BMHINTS_FRAME_INTERVAL | BMHINTS_PROFILE |
231 BMHINTS_USAGE | BMHINTS_RATECONTROL | BMHINTS_BITRATE |
232 BMHINTS_ENTROPY | BMHINTS_IFRAMEPERIOD;
235 config_probe_req.bStreamMuxOption = STREAMMUX_H264;
238 err = uvcx_video_probe(stream, UVC_SET_CUR, &config_probe_req);
242 err = uvcx_video_probe(stream, UVC_GET_CUR, &config_probe_req);
246 if (config_probe_req.wWidth != mediaType->Width)
248 WLog_ERR(TAG,
"Requested width %" PRIu16
" but got %" PRIu16, mediaType->Width,
249 config_probe_req.wWidth);
252 if (config_probe_req.wHeight != mediaType->Height)
254 WLog_ERR(TAG,
"Requested height %" PRIu16
" but got %" PRIu16, mediaType->Height,
255 config_probe_req.wHeight);
258 if (config_probe_req.dwFrameInterval != frame_interval)
260 WLog_ERR(TAG,
"Requested frame interval %" PRIu32
" but got %" PRIu32, frame_interval,
261 config_probe_req.dwFrameInterval);
266 err = uvcx_video_commit(stream, &config_probe_req);
283static BOOL get_devpath_from_device_id(
const char* deviceId,
char* path,
size_t size)
285 if (0 != strncmp(deviceId,
"usb-", 4))
289 const char* p = strchr(deviceId + 4,
'-');
295 strncpy(path, p, size - 1);
309static BOOL get_devpath_from_device(libusb_device* device,
char* path,
size_t size)
311 uint8_t ports[MAX_DEVPATH_DEPTH] = WINPR_C_ARRAY_INIT;
312 int nPorts = libusb_get_port_numbers(device, ports,
sizeof(ports));
317 for (
int i = 0; i < nPorts; i++)
319 int nChars = snprintf(path, size,
"%" PRIu8, ports[i]);
320 if ((nChars <= 0) || ((
size_t)nChars >= size))
323 size -= (size_t)nChars;
335static uint8_t get_guid_unit_id_from_config_descriptor(
struct libusb_config_descriptor* config,
337 const struct libusb_device_descriptor* ddesc)
339 WINPR_ASSERT(config);
343 for (uint8_t j = 0; j < config->bNumInterfaces; j++)
345 const struct libusb_interface* cfg = &config->interface[j];
346 for (
int k = 0; k < cfg->num_altsetting; k++)
348 const struct libusb_interface_descriptor*
interface = &cfg->altsetting[k];
349 if (interface->bInterfaceClass != LIBUSB_CLASS_VIDEO ||
350 interface->bInterfaceSubClass != USB_VIDEO_CONTROL)
352 if (interface->extra_length < 0)
356 const size_t extraLen = WINPR_ASSERTING_INT_CAST(
size_t, interface->extra_length);
357 while (offset < extraLen)
359 const size_t remaining = extraLen - offset;
365 const size_t bLength = WINPR_ASSERTING_INT_CAST(
size_t, desc->bLength);
366 if ((desc->bLength <= 0) || (bLength > remaining))
369 if ((desc->bDescriptorType == USB_VIDEO_CONTROL_INTERFACE) &&
370 (desc->bDescriptorSubType == USB_VIDEO_CONTROL_XU_TYPE) &&
373 if (memcmp(desc->guidExtensionCode, guid, 16) == 0)
375 int8_t unit_id = desc->bUnitID;
378 "For camera %04" PRIx16
":%04" PRIx16
379 " found UVCX H264 UnitID %" PRId8,
380 ddesc->idVendor, ddesc->idProduct, unit_id);
383 return WINPR_CXX_COMPAT_CAST(uint8_t, unit_id);
402static uint8_t get_guid_unit_id_from_device(libusb_device* device,
const uint8_t* guid)
404 struct libusb_device_descriptor ddesc = WINPR_C_ARRAY_INIT;
406 if (libusb_get_device_descriptor(device, &ddesc) != 0)
408 WLog_ERR(TAG,
"Couldn't get device descriptor");
412 for (uint8_t i = 0; i < ddesc.bNumConfigurations; ++i)
415 struct libusb_config_descriptor* config =
nullptr;
417 if (libusb_get_config_descriptor(device, i, &config) != 0)
420 "Couldn't get config descriptor for "
421 "configuration %" PRIu8,
425 rc = get_guid_unit_id_from_config_descriptor(config, guid, &ddesc);
427 libusb_free_config_descriptor(config);
445static uint8_t get_guid_unit_id(
const char* deviceId,
const uint8_t* guid)
447 char cam_devpath[MAX_DEVPATH_STR_SIZE] = WINPR_C_ARRAY_INIT;
448 libusb_context* usb_ctx =
nullptr;
449 libusb_device** device_list =
nullptr;
452 if (!get_devpath_from_device_id(deviceId, cam_devpath,
sizeof(cam_devpath)))
454 WLog_ERR(TAG,
"Unable to get devpath from deviceId %s", deviceId);
458 if (0 != libusb_init(&usb_ctx))
460 WLog_ERR(TAG,
"Unable to initialize libusb");
464 ssize_t cnt = libusb_get_device_list(usb_ctx, &device_list);
466 for (ssize_t i = 0; i < cnt; i++)
468 char path[MAX_DEVPATH_STR_SIZE] = WINPR_C_ARRAY_INIT;
469 libusb_device* device = device_list[i];
471 if (!device || !get_devpath_from_device(device, path,
sizeof(path)))
474 if (0 != strcmp(cam_devpath, path))
478 unit_id = get_guid_unit_id_from_device(device, guid);
486 libusb_free_device_list(device_list, TRUE);
487 libusb_exit(usb_ctx);
499uint8_t get_uvc_h624_unit_id(
const char* deviceId)
501 WINPR_ASSERT(deviceId);
503 WLog_DBG(TAG,
"Checking for UVCX H264 UnitID for %s", deviceId);
505 return get_guid_unit_id(deviceId, GUID_UVCX_H264_XU);