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