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