1 /*
2 * Basic bringup for libuvc as part of the decode frameserver
3 * Setup derived from the example in libuvc.
4 *
5 * For this to work properly (and that goes with USB LEDs etc. as well), we
6 * need to extend the device- negotiation path - until then, make sure the user
7 * arcan is running as can get access to the device nodes themselves.
8 *
9 * The setup is also so we can get webcam decoding without a full libvlc build
10 * on more custom setups - and as a place for hooking in other webcam support,
11 * should it be needed.
12 *
13 * More interestingly is when we can negotiate other buffer formats for the
14 * shmpage, particularly MJPEG and H264 for with the network- proxy.
15 *
16 * Controls to expose:
17 * - autoexposure,
18 * - autofocus
19 * - focus range
20 * - zoom
21 * - pan/tilt/roll
22 * - privacy(?)
23 * - button callback
24 *
25 * These should probably be provided as labelhints
26 */
27 #include <arcan_shmif.h>
28 #include <libuvc/libuvc.h>
29 #include <libswscale/swscale.h>
30 #include <math.h>
31
32 static int video_buffer_count = 1;
33
34 /* should also have the option to convert to GPU texture here and pass
35 * onwards rather than paying the conversion proce like this */
clamp_u8(int v,int low,int high)36 static uint8_t clamp_u8(int v, int low, int high)
37 {
38 return
39 v < low ? low : (v > high ? high : v);
40 }
41
ycbcr(int y,int cb,int cr)42 static shmif_pixel ycbcr(int y, int cb, int cr)
43 {
44 double r = y + (1.4065 * (cr - 128));
45 double g = y - (0.3455 * (cb - 128)) - (0.7169 * (cr - 128));
46 double b = y + (1.7790 * (cb - 128));
47 return
48 SHMIF_RGBA(
49 clamp_u8(r, 0, 255),
50 clamp_u8(g, 0, 255),
51 clamp_u8(b, 0, 255),
52 0xff
53 );
54 }
frame_uyvy(uvc_frame_t * frame,struct arcan_shmif_cont * dst)55 static void frame_uyvy(uvc_frame_t* frame, struct arcan_shmif_cont* dst)
56 {
57 uint8_t* buf = frame->data;
58
59 for (size_t y = 0; y < frame->height; y++){
60 shmif_pixel* vidp = &dst->vidp[y * dst->pitch];
61 for (size_t x = 0; x < frame->width; x+=2){
62 vidp[x+0] = ycbcr(buf[1], buf[0], buf[2]);
63 vidp[x+1] = ycbcr(buf[3], buf[0], buf[2]);
64 buf += 4;
65 }
66 }
67
68 arcan_shmif_signal(dst, SHMIF_SIGVID);
69 }
70
run_swscale(uvc_frame_t * frame,struct arcan_shmif_cont * dst,int planes,int fmt)71 static void run_swscale(
72 uvc_frame_t* frame, struct arcan_shmif_cont* dst, int planes, int fmt)
73 {
74 static struct SwsContext* scaler;
75 static int old_fmt = -1;
76
77 if (fmt != old_fmt && scaler){
78 sws_freeContext(scaler);
79 scaler = NULL;
80 }
81
82 if (!scaler){
83 scaler = sws_getContext(
84 frame->width, frame->height, fmt,
85 dst->w, dst->h, AV_PIX_FMT_BGRA, SWS_BILINEAR, NULL, NULL, NULL);
86 }
87
88 if (!scaler)
89 return;
90
91 int dst_stride[] = {dst->stride};
92 uint8_t* const dst_buf[] = {dst->vidb};
93 const uint8_t* data[3] = {frame->data, NULL, NULL};
94 int lines[3] = {frame->width, 0, 0};
95 size_t bsz = frame->width * frame->height;
96
97 if (planes > 1){
98 size_t hw = (frame->width + 1) >> 1;
99 size_t hh = (frame->height + 1) >> 1;
100
101 data[1] = frame->data + bsz;
102 lines[1] = frame->width;
103
104 if (planes > 2){
105 lines[1] = hw;
106 data[2] = frame->data + bsz + hw;
107 lines[2] = hw;
108 }
109 }
110
111 sws_scale(scaler, data, lines, 0, frame->height, dst_buf, dst_stride);
112 arcan_shmif_signal(dst, SHMIF_SIGVID);
113 }
114
frame_rgb(uvc_frame_t * frame,struct arcan_shmif_cont * dst)115 static void frame_rgb(uvc_frame_t* frame, struct arcan_shmif_cont* dst)
116 {
117 uint8_t* buf = frame->data;
118 for (size_t y = 0; y < frame->height; y++){
119 shmif_pixel* vidp = &dst->vidp[y * dst->pitch];
120 for (size_t x = 0; x < frame->width; x++){
121 vidp[x] = SHMIF_RGBA(buf[0], buf[1], buf[2], 0xff);
122 buf += 3;
123 }
124 }
125 arcan_shmif_signal(dst, SHMIF_SIGVID);
126 }
127
callback(uvc_frame_t * frame,void * tag)128 static void callback(uvc_frame_t* frame, void* tag)
129 {
130 uvc_frame_t* bgr;
131 uvc_error_t ret;
132 struct arcan_shmif_cont* cont = tag;
133
134 /* guarantee dimensions */
135 if (cont->w != frame->width || cont->h != frame->height){
136 if (!arcan_shmif_resize_ext(cont, frame->width, frame->height,
137 (struct shmif_resize_ext){.vbuf_cnt = video_buffer_count})){
138 return;
139 }
140 }
141
142 /* conversion / repack */
143 switch(frame->frame_format){
144 /* 'actually YUY2 is also called YUYV which is YUV420' (what a mess)
145 * though at least the capture devices I have used this one had the
146 * YUYV frame format have the same output as NV12 /facepalm */
147 case UVC_FRAME_FORMAT_YUYV:
148 case UVC_FRAME_FORMAT_NV12:
149 run_swscale(frame, cont, 2, AV_PIX_FMT_NV12);
150 break;
151 case UVC_FRAME_FORMAT_UYVY:
152 run_swscale(frame, cont, 3, AV_PIX_FMT_UYVY422);
153 frame_uyvy(frame, cont);
154 break;
155 case UVC_FRAME_FORMAT_RGB:
156 frame_rgb(frame, cont);
157 break;
158 /* h264 and mjpeg should map into ffmpeg as well */
159 default:
160 LOG("unhandled frame format: %d\n", (int)frame->frame_format);
161 break;
162 }
163 }
164
fmt_score(const uint8_t fourcc[static4],int * out)165 static int fmt_score(const uint8_t fourcc[static 4], int* out)
166 {
167 static struct {
168 uint8_t fourcc[4];
169 int enumv;
170 int score;
171 }
172 fmts[] = {
173 {
174 .fourcc = {'Y', 'U', 'Y', '2'},
175 .enumv = UVC_FRAME_FORMAT_YUYV,
176 .score = 2
177 },
178 {
179 .fourcc = {'U', 'Y', 'V', 'Y'},
180 .enumv = UVC_FRAME_FORMAT_UYVY,
181 .score = 2
182 },
183 {
184 .fourcc = {'Y', '8', '0', '0'},
185 .enumv = UVC_FRAME_FORMAT_GRAY8,
186 .score = 1
187 },
188 {
189 .fourcc = {'N', 'V', '1', '2'},
190 .enumv = UVC_FRAME_FORMAT_NV12,
191 .score = 3
192 },
193 /* this does not seem to have the 'right' fourcc? */
194 {
195 .fourcc = {0x7d, 0xeb, 0x36, 0xe4},
196 .enumv = UVC_FRAME_FORMAT_BGR,
197 .score = 3
198 },
199 {
200 .enumv = UVC_FRAME_FORMAT_MJPEG,
201 .score = -1,
202 .fourcc = {'M', 'J', 'P', 'G'}
203 },
204 {
205 .enumv = UVC_FRAME_FORMAT_MJPEG,
206 .score = -1,
207 .fourcc = {'H', '2', '6', '4'}
208 },
209 };
210
211 for (size_t i = 0; i < sizeof(fmts) / sizeof(fmts[0]); i++){
212 if (memcmp(fmts[i].fourcc, fourcc, 4) == 0){
213 *out = fmts[i].enumv;
214 return fmts[i].score;
215 }
216 }
217
218 return -1;
219 }
220
221 /*
222 * preference order:
223 * fmt > dimensions
224 */
match_dev_pref_fmt(uvc_device_handle_t * devh,size_t * w,size_t * h,int * fmt_out)225 static bool match_dev_pref_fmt(
226 uvc_device_handle_t* devh, size_t* w, size_t* h, int* fmt_out)
227 {
228 const uvc_format_desc_t* fmt = uvc_get_format_descs(devh);
229 int fmtid = -1;
230 int best_score = -1;
231 int fw = 0;
232 int fh = 0;
233 float id = 0;
234 float best_dist = id;
235
236 if (*w || *h)
237 id = sqrtf(*w * *w + *h * *h);
238
239 /* ok the way formats are defined here is a special kind of ?!, there is an
240 * internal format description that you are supposed to use within API borders,
241 * then it is compared to the regular fourcc and GUIDs - but note that the
242 * fourcc is ALSO a GUID */
243 while (fmt){
244 int fmtscore;
245 int score = fmt_score(fmt->fourccFormat, &fmtscore);
246
247 if (score != -1 && (best_score == -1 || best_score < score)){
248 const uvc_frame_desc_t* ftype = fmt->frame_descs;
249 if (!ftype){
250 fmt = fmt->next;
251 continue;
252 }
253
254 /* new format, pick the first dimension, and if the caller set a preference,
255 * find the distance that best fits our wants */
256 best_score = score;
257 fw = ftype->wWidth;
258 fh = ftype->wHeight;
259 *fmt_out = fmtscore;
260
261 best_dist = sqrtf(fw * fw + fh * fh);
262 if (!*w || !*h){
263 fmt = fmt->next;
264 continue;
265 }
266
267 while (ftype){
268 float dist = sqrtf(
269 ftype->wWidth * ftype->wWidth + ftype->wHeight * ftype->wHeight);
270 if (dist < best_dist){
271 best_dist = dist;
272 fw = ftype->wWidth;
273 fh = ftype->wHeight;
274 }
275 ftype = ftype->next;
276 }
277 }
278
279 fmt = fmt->next;
280 }
281
282 if (best_score != -1){
283 *w = fw;
284 *h = fh;
285 return true;
286 }
287 else
288 return false;
289 }
290
291 #define DIE(C) do { arcan_shmif_drop(C); return true; } while(0)
292
uvc_support_activate(struct arcan_shmif_cont * cont,struct arg_arr * args)293 bool uvc_support_activate(
294 struct arcan_shmif_cont* cont, struct arg_arr* args)
295 {
296 int vendor_id = 0x00;
297 int product_id = 0x00;
298 size_t width = 0;
299 size_t height = 0;
300 int fps = 0;
301
302 /* we only return 'false' if uvc has explicitly been disabled, otherwise
303 * VLC might try to capture */
304 const char* serial = NULL;
305 if (arg_lookup(args, "no_uvc", 0, NULL))
306 return false;
307
308 /* capture is already set, otherwise we wouldn't be here */
309 uvc_context_t* uvctx;
310 uvc_device_t* dev;
311 uvc_device_handle_t* devh;
312 uvc_stream_ctrl_t ctrl;
313 uvc_error_t res;
314
315 if (uvc_init(&uvctx, NULL) < 0){
316 arcan_shmif_last_words(cont, "couldn't initialize UVC");
317 DIE(cont);
318 }
319
320 /* enumeration means that we won't really use the connection, but
321 * at least send as messages */
322 if (arg_lookup(args, "list", 0, NULL)){
323 uvc_device_t** devices;
324 uvc_get_device_list(uvctx, &devices);
325 for(size_t i = 0; devices[i]; i++){
326 uvc_device_descriptor_t* ddesc;
327 if (uvc_get_device_descriptor(devices[i], &ddesc) != UVC_SUCCESS)
328 continue;
329
330 struct arcan_event ev = {
331 .category = EVENT_EXTERNAL,
332 .ext.kind = ARCAN_EVENT(MESSAGE)
333 };
334
335 size_t nb = sizeof(ev.ext.message.data) / sizeof(ev.ext.message.data[0]);
336 if (!ddesc->serialNumber){
337 snprintf((char*) ev.ext.message.data, nb, "vid=%.4x:pid=%.4x:"
338 "status=EPERM, permission denied\n", ddesc->idVendor, ddesc->idProduct);
339 }
340 else{
341 snprintf((char*) ev.ext.message.data, nb, "vid=%.4x:pid=%.4x:serial=%s:product=%s\n",
342 ddesc->idVendor, ddesc->idProduct, ddesc->serialNumber, ddesc->product);
343 }
344
345 fputs((char*)ev.ext.message.data, stdout);
346 arcan_shmif_enqueue(cont, &ev);
347 }
348 uvc_free_device_list(devices, 1);
349 DIE(cont);
350 }
351
352 const char* val;
353 if (arg_lookup(args, "vid", 0, &val) && val)
354 vendor_id = strtoul(val, NULL, 16);
355
356 if (arg_lookup(args, "width", 0, &val) && val)
357 width = strtoul(val, NULL, 10);
358
359 if (arg_lookup(args, "height", 0, &val) && val)
360 height = strtoul(val, NULL, 10);
361
362 if (arg_lookup(args, "pid", 0, &val) && val)
363 product_id = strtoul(val, NULL, 16);
364
365 if (arg_lookup(args, "fps", 0, &val) && val)
366 fps = strtoul(val, NULL, 10);
367
368 if (arg_lookup(args, "vbufc", 0, &val)){
369 uint8_t bufc = strtoul(val, NULL, 10);
370 video_buffer_count = bufc > 0 && bufc <= 4 ? bufc : 1;
371 }
372
373 arg_lookup(args, "serial", 0, &serial);
374
375 if (uvc_find_device(uvctx, &dev, vendor_id, product_id, serial) < 0){
376 arcan_shmif_last_words(cont, "no matching device");
377 DIE(cont);
378 }
379
380 if (uvc_open(dev, &devh) < 0){
381 arcan_shmif_last_words(cont, "couldn't open device");
382 DIE(cont);
383 }
384
385 /* finding the right format is complicated -
386 * the formats for a device is a linked list (->next) where there is a
387 * frame_desc with the same restriction.
388 *
389 * scan for matching size (if defined) - otherwise pick based on format
390 * and format priority. This is what uvc_get_stream_ctrl_format_size does
391 * but it also requires explicit size description
392 */
393 int fmt = -1;
394
395 if (!match_dev_pref_fmt(devh, &width, &height, &fmt)){
396 arcan_shmif_last_words(cont, "no compatible frame-format for device");
397 DIE(cont);
398 }
399
400 enum uvc_frame_format frame_format;
401
402 /* will be redirected to log */
403 uvc_print_diag(devh, stderr);
404
405 /* so there are more options to negotiate here, and we can't really grok
406 * what is the 'preferred' format, normal tactic is
407 *
408 * uvc_stream_ctrl_t ctrl;
409 * uvc_get_stream_ctrl_format_size(
410 * devh, &ctrl, UVC_FRAME_FORMAT_YUYV, w, h, fps)
411 *
412 * so maybe we need to try a few and then pick what best match some user pref.
413 */
414
415 /* some other frame formats take even more special consideration, mainly
416 * FRAME_FORMAT_H264 (decode through vlc or openh264) | (attempt-passthrough)
417 * FRAME_FORMAT_MJPEG
418 */
419 if (uvc_get_stream_ctrl_format_size(
420 devh, &ctrl, fmt, width, height, fps) < 0){
421 fprintf(stderr, "kind=EINVAL:message="
422 "format request (%zu*%zu@%zu fps)@%d failed\n", width, height, fps, fmt);
423
424 if (uvc_get_stream_ctrl_format_size(
425 devh, &ctrl, UVC_FRAME_FORMAT_ANY, width, height, fps) < 0){
426 fprintf(stderr, "kind=EINVAL:message="
427 "format request (%zu*%zu@%zu fps)@ANY failed\n", width, height, fps);
428 }
429 goto out;
430 }
431
432 int rv = uvc_start_streaming(devh, &ctrl, callback, cont, 0);
433 if (rv < 0){
434 arcan_shmif_last_words(cont, "uvc- error when streaming");
435 goto out;
436 }
437
438 /* this one is a bit special, optimally we'd want to check cont and
439 * see if we have GPU access - if there is one, we should try and get
440 * the camera native format, upload that to a texture and repack /
441 * convert there - for now just set RGBX and hope that uvc can unpack
442 * without further conversion */
443 arcan_shmif_privsep(cont, "minimal", NULL, 0);
444
445 struct arcan_event ev;
446 while(arcan_shmif_wait(cont, &ev)){
447 if (ev.category != EVENT_TARGET)
448 continue;
449 }
450
451 out:
452 uvc_close(devh);
453 arcan_shmif_drop(cont);
454 return true;
455 }
456
uvc_append_help(FILE * out)457 void uvc_append_help(FILE* out)
458 {
459 fprintf(out, "\nUVC- based webcam arguments:\n"
460 " key \t value \t description\n"
461 "---------\t-----------\t----------------\n"
462 "no_uvc \t \t skip uvc in capture device processing chain\n"
463 "list \t \t enumerate valid devices then return\n"
464 "capture \t \t try and find a capture device\n"
465 "vid \t 0xUSBVID \t specify (hex) the vendor ID of the device\n"
466 "pid \t 0xUSBPID \t specify (hex) the product ID of the device\n"
467 "serial \t <string> \t specify the serial number of the device\n"
468 "width \t px \t preferred capture width (=0)\n"
469 "height \t px \t preferred capture height (=0)\n"
470 "fps \t nframes \t preferred capture framerate (=0)\n"
471 "vbufc \t nbuf \t preferred number of transfer buffers (=1)\n"
472 );
473 }
474