28#include <linux/uvcvideo.h>
29#include <linux/videodev2.h>
35static uint8_t GUID_UVCX_H264_XU[16] = { 0x41, 0x76, 0x9E, 0xA2, 0x04, 0xDE, 0xE3, 0x47,
36 0x8B, 0x2B, 0xF4, 0x34, 0x1A, 0xFF, 0x00, 0x3B };
38#define TAG CHANNELS_TAG("rdpecam-uvch264.client")
49static uint16_t get_length_xu_control(
CamV4lStream* stream, uint8_t unit, uint8_t selector)
52 WINPR_ASSERT(stream->fd > 0);
56 struct uvc_xu_control_query xu_ctrl_query = { .unit = unit,
59 .size =
sizeof(length),
60 .data = (uint8_t*)&length };
62 if (ioctl(stream->fd, UVCIOC_CTRL_QUERY, &xu_ctrl_query) < 0)
64 char ebuffer[256] = { 0 };
65 WLog_ERR(TAG,
"UVCIOC_CTRL_QUERY (GET_LEN) - Error: %s",
66 winpr_strerror(errno, ebuffer,
sizeof(ebuffer)));
84static int query_xu_control(
CamV4lStream* stream, uint8_t unit, uint8_t selector, uint8_t query,
88 uint16_t len = get_length_xu_control(stream, unit, selector);
90 struct uvc_xu_control_query xu_ctrl_query = {
91 .unit = unit, .selector = selector, .query = query, .size = len, .data = (uint8_t*)data
95 if ((err = ioctl(stream->fd, UVCIOC_CTRL_QUERY, &xu_ctrl_query)) < 0)
97 char ebuffer[256] = { 0 };
98 WLog_ERR(TAG,
"UVCIOC_CTRL_QUERY (%" PRIu8
") - Error: %s", query,
99 winpr_strerror(errno, ebuffer,
sizeof(ebuffer)));
112static int uvcx_video_encoder_reset(
CamV4lStream* stream)
114 WINPR_ASSERT(stream);
120 if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_ENCODER_RESET, UVC_SET_CUR,
121 &encoder_reset_req)) < 0)
123 char ebuffer[256] = { 0 };
124 WLog_ERR(TAG,
"UVCX_ENCODER_RESET error: %s",
125 winpr_strerror(errno, ebuffer,
sizeof(ebuffer)));
140static int uvcx_video_probe(
CamV4lStream* stream, uint8_t query,
143 WINPR_ASSERT(stream);
147 if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_VIDEO_CONFIG_PROBE, query,
148 uvcx_video_config)) < 0)
150 char ebuffer[256] = { 0 };
151 WLog_ERR(TAG,
"UVCX_VIDEO_CONFIG_PROBE error: %s",
152 winpr_strerror(errno, ebuffer,
sizeof(ebuffer)));
169 WINPR_ASSERT(stream);
173 if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_VIDEO_CONFIG_COMMIT, UVC_SET_CUR,
174 uvcx_video_config)) < 0)
176 char ebuffer[256] = { 0 };
177 WLog_ERR(TAG,
"UVCX_VIDEO_CONFIG_COMMIT error: %s",
178 winpr_strerror(errno, ebuffer,
sizeof(ebuffer)));
194 WINPR_ASSERT(stream);
195 WINPR_ASSERT(mediaType);
201 err = uvcx_video_encoder_reset(stream);
206 err = uvcx_video_probe(stream, UVC_GET_DEF, &config_probe_req);
211 config_probe_req.wWidth = WINPR_ASSERTING_INT_CAST(uint16_t, mediaType->Width);
212 config_probe_req.wHeight = WINPR_ASSERTING_INT_CAST(uint16_t, mediaType->Height);
215 uint32_t frame_interval =
216 (mediaType->FrameRateDenominator * 1000000000LL / mediaType->FrameRateNumerator) / 100;
217 config_probe_req.dwFrameInterval = frame_interval;
220 config_probe_req.wProfile = PROFILE_HIGH;
221 config_probe_req.bUsageType = USAGETYPE_REALTIME;
222 config_probe_req.bRateControlMode = RATECONTROL_VBR;
223 config_probe_req.dwBitRate = h264_get_max_bitrate(mediaType->Height);
224 config_probe_req.bEntropyCABAC = ENTROPY_CABAC;
225 config_probe_req.wIFramePeriod = 1000;
228 config_probe_req.bmHints = BMHINTS_RESOLUTION | BMHINTS_FRAME_INTERVAL | BMHINTS_PROFILE |
229 BMHINTS_USAGE | BMHINTS_RATECONTROL | BMHINTS_BITRATE |
230 BMHINTS_ENTROPY | BMHINTS_IFRAMEPERIOD;
233 config_probe_req.bStreamMuxOption = STREAMMUX_H264;
236 err = uvcx_video_probe(stream, UVC_SET_CUR, &config_probe_req);
240 err = uvcx_video_probe(stream, UVC_GET_CUR, &config_probe_req);
244 if (config_probe_req.wWidth != mediaType->Width)
246 WLog_ERR(TAG,
"Requested width %" PRIu16
" but got %" PRIu16, mediaType->Width,
247 config_probe_req.wWidth);
250 if (config_probe_req.wHeight != mediaType->Height)
252 WLog_ERR(TAG,
"Requested height %" PRIu16
" but got %" PRIu16, mediaType->Height,
253 config_probe_req.wHeight);
256 if (config_probe_req.dwFrameInterval != frame_interval)
258 WLog_ERR(TAG,
"Requested frame interval %" PRIu32
" but got %" PRIu32, frame_interval,
259 config_probe_req.dwFrameInterval);
264 err = uvcx_video_commit(stream, &config_probe_req);
284static BOOL get_devpath_from_device_id(
const char* deviceId,
char* path,
size_t size)
286 if (0 != strncmp(deviceId,
"usb-", 4))
290 const char* p = strchr(deviceId + 4,
'-');
296 strncpy(path, p, size - 1);
310static BOOL get_devpath_from_device(libusb_device* device,
char* path,
size_t size)
312 uint8_t ports[MAX_DEVPATH_DEPTH] = { 0 };
313 int nPorts = libusb_get_port_numbers(device, ports,
sizeof(ports));
318 for (
int i = 0; i < nPorts; i++)
320 int nChars = snprintf(path, size,
"%" PRIu8, ports[i]);
321 if ((nChars <= 0) || ((
size_t)nChars >= size))
324 size -= (size_t)nChars;
345static uint8_t get_guid_unit_id_from_device(libusb_device* device,
const uint8_t* guid)
347 struct libusb_device_descriptor ddesc = { 0 };
349 if (libusb_get_device_descriptor(device, &ddesc) != 0)
351 WLog_ERR(TAG,
"Couldn't get device descriptor");
355 for (uint8_t i = 0; i < ddesc.bNumConfigurations; ++i)
357 struct libusb_config_descriptor* config = NULL;
359 if (libusb_get_config_descriptor(device, i, &config) != 0)
362 "Couldn't get config descriptor for "
363 "configuration %" PRIu8,
368 for (uint8_t j = 0; j < config->bNumInterfaces; j++)
370 const struct libusb_interface* cfg = &config->interface[j];
371 for (
int k = 0; k < cfg->num_altsetting; k++)
373 const struct libusb_interface_descriptor*
interface = &cfg->altsetting[k];
374 if (interface->bInterfaceClass != LIBUSB_CLASS_VIDEO ||
375 interface->bInterfaceSubClass != USB_VIDEO_CONTROL)
378 const uint8_t* ptr = interface->extra;
379 while (ptr < interface->extra + interface->extra_length)
382 if (desc->bDescriptorType == USB_VIDEO_CONTROL_INTERFACE &&
383 desc->bDescriptorSubType == USB_VIDEO_CONTROL_XU_TYPE &&
384 memcmp(desc->guidExtensionCode, guid, 16) == 0)
386 int8_t unit_id = desc->bUnitID;
389 "For camera %04" PRIx16
":%04" PRIx16
390 " found UVCX H264 UnitID %" PRId8,
391 ddesc.idVendor, ddesc.idProduct, unit_id);
394 return WINPR_CXX_COMPAT_CAST(uint8_t, unit_id);
396 ptr += desc->bLength;
415static uint8_t get_guid_unit_id(
const char* deviceId,
const uint8_t* guid)
417 char cam_devpath[MAX_DEVPATH_STR_SIZE] = { 0 };
418 libusb_context* usb_ctx = NULL;
419 libusb_device** device_list = NULL;
422 if (!get_devpath_from_device_id(deviceId, cam_devpath,
sizeof(cam_devpath)))
424 WLog_ERR(TAG,
"Unable to get devpath from deviceId %s", deviceId);
428 if (0 != libusb_init(&usb_ctx))
430 WLog_ERR(TAG,
"Unable to initialize libusb");
434 ssize_t cnt = libusb_get_device_list(usb_ctx, &device_list);
436 for (ssize_t i = 0; i < cnt; i++)
438 char path[MAX_DEVPATH_STR_SIZE] = { 0 };
439 libusb_device* device = device_list[i];
441 if (!device || !get_devpath_from_device(device, path,
sizeof(path)))
444 if (0 != strcmp(cam_devpath, path))
448 unit_id = get_guid_unit_id_from_device(device, guid);
456 libusb_free_device_list(device_list, TRUE);
457 libusb_exit(usb_ctx);
469uint8_t get_uvc_h624_unit_id(
const char* deviceId)
471 WINPR_ASSERT(deviceId);
473 WLog_DBG(TAG,
"Checking for UVCX H264 UnitID for %s", deviceId);
475 return get_guid_unit_id(deviceId, GUID_UVCX_H264_XU);