FreeRDP
Loading...
Searching...
No Matches
uvc_h264.c
1
26#include <winpr/wtypes.h>
27
28#include <sys/ioctl.h>
29
30#include <linux/uvcvideo.h>
31#include <linux/videodev2.h>
32#include <libusb.h>
33
34#include "uvc_h264.h"
35
36/* UVC H.264 extension unit GUID: {A29E7641-DE04-47E3-8B2B-F4341AFF003B} */
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 };
39
40#define TAG CHANNELS_TAG("rdpecam-uvch264.client")
41
42/*
43 * get length of xu control defined by unit id and selector
44 * args:
45 * stream - pointer to video device data
46 * unit - unit id of xu control
47 * selector - selector for control
48 *
49 * returns: length of xu control
50 */
51static uint16_t get_length_xu_control(CamV4lStream* stream, uint8_t unit, uint8_t selector)
52{
53 WINPR_ASSERT(stream);
54 WINPR_ASSERT(stream->fd > 0);
55
56 uint16_t length = 0;
57
58 struct uvc_xu_control_query xu_ctrl_query = { .unit = unit,
59 .selector = selector,
60 .query = UVC_GET_LEN,
61 .size = sizeof(length),
62 .data = (uint8_t*)&length };
63
64 if (ioctl(stream->fd, UVCIOC_CTRL_QUERY, &xu_ctrl_query) < 0)
65 {
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)));
69 return 0;
70 }
71
72 return length;
73}
74
75/*
76 * runs a query on xu control defined by unit id and selector
77 * args:
78 * stream - pointer to video device data
79 * unit - unit id of xu control
80 * selector - selector for control
81 * query - query type
82 * data - pointer to query data
83 *
84 * returns: 0 if query succeeded or error code on fail
85 */
86static int query_xu_control(CamV4lStream* stream, uint8_t unit, uint8_t selector, uint8_t query,
87 void* data)
88{
89 int err = 0;
90 uint16_t len = get_length_xu_control(stream, unit, selector);
91
92 struct uvc_xu_control_query xu_ctrl_query = {
93 .unit = unit, .selector = selector, .query = query, .size = len, .data = (uint8_t*)data
94 };
95
96 /*get query data*/
97 if ((err = ioctl(stream->fd, UVCIOC_CTRL_QUERY, &xu_ctrl_query)) < 0)
98 {
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)));
102 }
103
104 return err;
105}
106
107/*
108 * resets the h264 encoder
109 * args:
110 * stream - pointer to video device data
111 *
112 * returns: 0 on success or error code on fail
113 */
114static int uvcx_video_encoder_reset(CamV4lStream* stream)
115{
116 WINPR_ASSERT(stream);
117
118 uvcx_encoder_reset encoder_reset_req = WINPR_C_ARRAY_INIT;
119
120 int err = 0;
121
122 if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_ENCODER_RESET, UVC_SET_CUR,
123 &encoder_reset_req)) < 0)
124 {
125 char ebuffer[256] = WINPR_C_ARRAY_INIT;
126 WLog_ERR(TAG, "UVCX_ENCODER_RESET error: %s",
127 winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
128 }
129
130 return err;
131}
132
133/*
134 * probes the h264 encoder config
135 * args:
136 * stream - pointer to video device data
137 * query - probe query
138 * uvcx_video_config - pointer to probe/commit config data
139 *
140 * returns: 0 on success or error code on fail
141 */
142static int uvcx_video_probe(CamV4lStream* stream, uint8_t query,
143 uvcx_video_config_probe_commit_t* uvcx_video_config)
144{
145 WINPR_ASSERT(stream);
146
147 int err = 0;
148
149 if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_VIDEO_CONFIG_PROBE, query,
150 uvcx_video_config)) < 0)
151 {
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)));
155 }
156
157 return err;
158}
159
160/*
161 * commits the h264 encoder config
162 * args:
163 * stream - pointer to video device data
164 * uvcx_video_config - pointer to probe/commit config data
165 *
166 * returns: 0 on success or error code on fail
167 */
168static int uvcx_video_commit(CamV4lStream* stream,
169 uvcx_video_config_probe_commit_t* uvcx_video_config)
170{
171 WINPR_ASSERT(stream);
172
173 int err = 0;
174
175 if ((err = query_xu_control(stream, stream->h264UnitId, UVCX_VIDEO_CONFIG_COMMIT, UVC_SET_CUR,
176 uvcx_video_config)) < 0)
177 {
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)));
181 }
182
183 return err;
184}
185
186/*
187 * sets h264 muxed format (must not be called while streaming)
188 * args:
189 * stream - pointer to video device data
190 * mediaType
191 *
192 * returns: TRUE on success or FALSE on fail
193 */
194BOOL set_h264_muxed_format(CamV4lStream* stream, const CAM_MEDIA_TYPE_DESCRIPTION* mediaType)
195{
196 WINPR_ASSERT(stream);
197 WINPR_ASSERT(mediaType);
198
199 uvcx_video_config_probe_commit_t config_probe_req = WINPR_C_ARRAY_INIT;
200 int err = 0;
201
202 /* reset the encoder */
203 err = uvcx_video_encoder_reset(stream);
204 if (err != 0)
205 return FALSE;
206
207 /* get default values */
208 err = uvcx_video_probe(stream, UVC_GET_DEF, &config_probe_req);
209 if (err != 0)
210 return FALSE;
211
212 /* set resolution */
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);
215
216 /* set frame rate in 100ns units */
217 uint32_t frame_interval =
218 (mediaType->FrameRateDenominator * 1000000000LL / mediaType->FrameRateNumerator) / 100;
219 config_probe_req.dwFrameInterval = frame_interval;
220
221 /* quality settings */
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; /* ms, 1 sec */
228
229 /* hints which parameters are configured */
230 config_probe_req.bmHints = BMHINTS_RESOLUTION | BMHINTS_FRAME_INTERVAL | BMHINTS_PROFILE |
231 BMHINTS_USAGE | BMHINTS_RATECONTROL | BMHINTS_BITRATE |
232 BMHINTS_ENTROPY | BMHINTS_IFRAMEPERIOD;
233
234 /* set the aux stream */
235 config_probe_req.bStreamMuxOption = STREAMMUX_H264;
236
237 /* probe the format */
238 err = uvcx_video_probe(stream, UVC_SET_CUR, &config_probe_req);
239 if (err != 0)
240 return FALSE;
241
242 err = uvcx_video_probe(stream, UVC_GET_CUR, &config_probe_req);
243 if (err != 0)
244 return FALSE;
245
246 if (config_probe_req.wWidth != mediaType->Width)
247 {
248 WLog_ERR(TAG, "Requested width %" PRIu16 " but got %" PRIu16, mediaType->Width,
249 config_probe_req.wWidth);
250 return FALSE;
251 }
252 if (config_probe_req.wHeight != mediaType->Height)
253 {
254 WLog_ERR(TAG, "Requested height %" PRIu16 " but got %" PRIu16, mediaType->Height,
255 config_probe_req.wHeight);
256 return FALSE;
257 }
258 if (config_probe_req.dwFrameInterval != frame_interval)
259 {
260 WLog_ERR(TAG, "Requested frame interval %" PRIu32 " but got %" PRIu32, frame_interval,
261 config_probe_req.dwFrameInterval);
262 return FALSE;
263 }
264
265 /* commit the format */
266 err = uvcx_video_commit(stream, &config_probe_req);
267 return (err == 0);
268}
269
270/*
271 * parses deviceId such as usb-0000:00:1a.0-1.2.2 to return devpath (1.2.2)
272 *
273 * deviceID format is: usb-<busname>-<devpath>
274 * see kernel's usb_make_path()
275 *
276 * args:
277 * deviceId
278 * path - buffer to return devpath
279 * size - buffer size
280 *
281 * returns: TRUE if success, FALSE otherwise
282 */
283static BOOL get_devpath_from_device_id(const char* deviceId, char* path, size_t size)
284{
285 if (0 != strncmp(deviceId, "usb-", 4))
286 return FALSE;
287
288 /* find second `-` */
289 const char* p = strchr(deviceId + 4, '-');
290 if (!p)
291 return FALSE;
292
293 p++; // now points to nullptr terminated devpath
294
295 strncpy(path, p, size - 1);
296 return TRUE;
297}
298
299/*
300 * return devpath of a given libusb_device as text string such as: 1.2.2 or 2.3
301 *
302 * args:
303 * device
304 * path - buffer to return devpath
305 * size - buffer size
306 *
307 * returns: TRUE if success, FALSE otherwise
308 */
309static BOOL get_devpath_from_device(libusb_device* device, char* path, size_t size)
310{
311 uint8_t ports[MAX_DEVPATH_DEPTH] = WINPR_C_ARRAY_INIT;
312 int nPorts = libusb_get_port_numbers(device, ports, sizeof(ports));
313
314 if (nPorts <= 0)
315 return FALSE;
316
317 for (int i = 0; i < nPorts; i++)
318 {
319 int nChars = snprintf(path, size, "%" PRIu8, ports[i]);
320 if ((nChars <= 0) || ((size_t)nChars >= size))
321 return FALSE;
322
323 size -= (size_t)nChars;
324 path += nChars;
325
326 if (i < nPorts - 1)
327 {
328 *path++ = '.';
329 size--;
330 }
331 }
332 return TRUE;
333}
334
335static uint8_t get_guid_unit_id_from_config_descriptor(struct libusb_config_descriptor* config,
336 const uint8_t* guid,
337 const struct libusb_device_descriptor* ddesc)
338{
339 WINPR_ASSERT(config);
340 WINPR_ASSERT(guid);
341 WINPR_ASSERT(ddesc);
342
343 for (uint8_t j = 0; j < config->bNumInterfaces; j++)
344 {
345 const struct libusb_interface* cfg = &config->interface[j];
346 for (int k = 0; k < cfg->num_altsetting; k++)
347 {
348 const struct libusb_interface_descriptor* interface = &cfg->altsetting[k];
349 if (interface->bInterfaceClass != LIBUSB_CLASS_VIDEO ||
350 interface->bInterfaceSubClass != USB_VIDEO_CONTROL)
351 continue;
352 if (interface->extra_length < 0)
353 continue;
354
355 size_t offset = 0;
356 const size_t extraLen = WINPR_ASSERTING_INT_CAST(size_t, interface->extra_length);
357 while (offset < extraLen)
358 {
359 const size_t remaining = extraLen - offset;
360 if (remaining < 3)
361 break;
362 const xu_descriptor* desc = (const xu_descriptor*)&interface->extra[offset];
363
364 WINPR_STATIC_ASSERT(sizeof(xu_descriptor) == 16 + 4);
365 const size_t bLength = WINPR_ASSERTING_INT_CAST(size_t, desc->bLength);
366 if ((desc->bLength <= 0) || (bLength > remaining))
367 break;
368
369 if ((desc->bDescriptorType == USB_VIDEO_CONTROL_INTERFACE) &&
370 (desc->bDescriptorSubType == USB_VIDEO_CONTROL_XU_TYPE) &&
371 (bLength >= sizeof(xu_descriptor)))
372 {
373 if (memcmp(desc->guidExtensionCode, guid, 16) == 0)
374 {
375 int8_t unit_id = desc->bUnitID;
376
377 WLog_DBG(TAG,
378 "For camera %04" PRIx16 ":%04" PRIx16
379 " found UVCX H264 UnitID %" PRId8,
380 ddesc->idVendor, ddesc->idProduct, unit_id);
381 if (unit_id < 0)
382 return 0;
383 return WINPR_CXX_COMPAT_CAST(uint8_t, unit_id);
384 }
385 }
386 offset += bLength;
387 }
388 }
389 }
390 return 0;
391}
392
393/*
394 * get GUID unit id from libusb_device, if any
395 *
396 * args:
397 * device
398 * guid - 16 byte xu GUID
399 *
400 * returns: unit id for the matching GUID or 0 if none
401 */
402static uint8_t get_guid_unit_id_from_device(libusb_device* device, const uint8_t* guid)
403{
404 struct libusb_device_descriptor ddesc = WINPR_C_ARRAY_INIT;
405
406 if (libusb_get_device_descriptor(device, &ddesc) != 0)
407 {
408 WLog_ERR(TAG, "Couldn't get device descriptor");
409 return 0;
410 }
411
412 for (uint8_t i = 0; i < ddesc.bNumConfigurations; ++i)
413 {
414 uint8_t rc = 0;
415 struct libusb_config_descriptor* config = nullptr;
416
417 if (libusb_get_config_descriptor(device, i, &config) != 0)
418 {
419 WLog_ERR(TAG,
420 "Couldn't get config descriptor for "
421 "configuration %" PRIu8,
422 i);
423 }
424 else
425 rc = get_guid_unit_id_from_config_descriptor(config, guid, &ddesc);
426
427 libusb_free_config_descriptor(config);
428 if (rc != 0)
429 return rc;
430 }
431
432 /* no match found */
433 return 0;
434}
435
436/*
437 * get GUID unit id, if any
438 *
439 * args:
440 * deviceId - camera deviceId such as: usb-0000:00:1a.0-1.2.2
441 * guid - 16 byte xu GUID
442 *
443 * returns: unit id for the matching GUID or 0 if none
444 */
445static uint8_t get_guid_unit_id(const char* deviceId, const uint8_t* guid)
446{
447 char cam_devpath[MAX_DEVPATH_STR_SIZE] = WINPR_C_ARRAY_INIT;
448 libusb_context* usb_ctx = nullptr;
449 libusb_device** device_list = nullptr;
450 uint8_t unit_id = 0;
451
452 if (!get_devpath_from_device_id(deviceId, cam_devpath, sizeof(cam_devpath)))
453 {
454 WLog_ERR(TAG, "Unable to get devpath from deviceId %s", deviceId);
455 return 0;
456 }
457
458 if (0 != libusb_init(&usb_ctx))
459 {
460 WLog_ERR(TAG, "Unable to initialize libusb");
461 return 0;
462 }
463
464 ssize_t cnt = libusb_get_device_list(usb_ctx, &device_list);
465
466 for (ssize_t i = 0; i < cnt; i++)
467 {
468 char path[MAX_DEVPATH_STR_SIZE] = WINPR_C_ARRAY_INIT;
469 libusb_device* device = device_list[i];
470
471 if (!device || !get_devpath_from_device(device, path, sizeof(path)))
472 continue;
473
474 if (0 != strcmp(cam_devpath, path))
475 continue;
476
477 /* found device with matching devpath, try to get guid unit id */
478 unit_id = get_guid_unit_id_from_device(device, guid);
479
480 if (unit_id > 0)
481 break; /* got it */
482
483 /* there's chance for another devpath match - continue */
484 }
485
486 libusb_free_device_list(device_list, TRUE);
487 libusb_exit(usb_ctx);
488 return unit_id;
489}
490
491/*
492 * gets the uvc h264 xu control unit id, if any
493 *
494 * args:
495 * deviceId - camera deviceId such as: usb-0000:00:1a.0-1.2.2
496 *
497 * returns: unit id or 0 if none
498 */
499uint8_t get_uvc_h624_unit_id(const char* deviceId)
500{
501 WINPR_ASSERT(deviceId);
502
503 WLog_DBG(TAG, "Checking for UVCX H264 UnitID for %s", deviceId);
504
505 return get_guid_unit_id(deviceId, GUID_UVCX_H264_XU);
506}