1/* $NetBSD: videoctl.c,v 1.3 2021/02/19 11:39:11 rillig Exp $ */
2
3/*-
4 * Copyright (c) 2010 Jared D. McNeill <jmcneill@invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__COPYRIGHT("@(#) Copyright (c) 2010\
31 Jared D. McNeill <jmcneill@invisible.ca>. All rights reserved.");
32__RCSID("$NetBSD: videoctl.c,v 1.3 2021/02/19 11:39:11 rillig Exp $");
33
34#include <sys/types.h>
35#include <sys/ioctl.h>
36#include <sys/videoio.h>
37
38#include <err.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <limits.h>
42#include <paths.h>
43#include <stdio.h>
44#include <string.h>
45#include <stdbool.h>
46#include <stdlib.h>
47#include <unistd.h>
48#include <util.h>
49
50__dead static void	usage(void);
51static void		video_print(const char *);
52static void		video_print_all(void);
53static bool		video_print_caps(const char *);
54static bool		video_print_formats(const char *);
55static bool		video_print_inputs(const char *);
56static bool		video_print_audios(const char *);
57static bool		video_print_standards(const char *);
58static bool		video_print_tuners(const char *);
59static bool		video_print_ctrl(uint32_t);
60static void		video_set(const char *);
61static bool		video_set_ctrl(uint32_t, int32_t);
62static const char *	video_cid2name(uint32_t);
63static uint32_t		video_name2cid(const char *);
64
65static const char *video_dev = NULL;
66static int video_fd = -1;
67static bool aflag = false;
68static bool wflag = false;
69
70static const struct {
71	uint32_t	id;
72	const char	*name;
73} videoctl_cid_names[] = {
74	{ V4L2_CID_BRIGHTNESS,		"brightness" },
75	{ V4L2_CID_CONTRAST,		"contrast" },
76	{ V4L2_CID_SATURATION,		"saturation" },
77	{ V4L2_CID_HUE,			"hue" },
78	{ V4L2_CID_AUDIO_VOLUME,	"audio_volume" },
79	{ V4L2_CID_AUDIO_BALANCE,	"audio_balance" },
80	{ V4L2_CID_AUDIO_BASS,		"audio_bass" },
81	{ V4L2_CID_AUDIO_TREBLE,	"audio_treble" },
82	{ V4L2_CID_AUDIO_MUTE,		"audio_mute" },
83	{ V4L2_CID_AUDIO_LOUDNESS,	"audio_loudness" },
84	{ V4L2_CID_BLACK_LEVEL,		"black_level" },
85	{ V4L2_CID_AUTO_WHITE_BALANCE,	"auto_white_balance" },
86	{ V4L2_CID_DO_WHITE_BALANCE,	"do_white_balance" },
87	{ V4L2_CID_RED_BALANCE,		"red_balance" },
88	{ V4L2_CID_BLUE_BALANCE,	"blue_balance" },
89	{ V4L2_CID_GAMMA,		"gamma" },
90	{ V4L2_CID_WHITENESS,		"whiteness" },
91	{ V4L2_CID_EXPOSURE,		"exposure" },
92	{ V4L2_CID_AUTOGAIN,		"autogain" },
93	{ V4L2_CID_GAIN,		"gain" },
94	{ V4L2_CID_HFLIP,		"hflip" },
95	{ V4L2_CID_VFLIP,		"vflip" },
96	{ V4L2_CID_HCENTER,		"hcenter" },
97	{ V4L2_CID_VCENTER,		"vcenter" },
98	{ V4L2_CID_POWER_LINE_FREQUENCY, "power_line_frequency" },
99	{ V4L2_CID_HUE_AUTO,		"hue_auto" },
100	{ V4L2_CID_WHITE_BALANCE_TEMPERATURE, "white_balance_temperature" },
101	{ V4L2_CID_SHARPNESS,		"sharpness" },
102	{ V4L2_CID_BACKLIGHT_COMPENSATION, "backlight_compensation" },
103};
104
105int
106main(int argc, char *argv[])
107{
108	int ch;
109
110	setprogname(argv[0]);
111
112	while ((ch = getopt(argc, argv, "ad:w")) != -1) {
113		switch (ch) {
114		case 'a':
115			aflag = true;
116			break;
117		case 'd':
118			video_dev = strdup(optarg);
119			break;
120		case 'w':
121			wflag = true;
122			break;
123		default:
124			usage();
125			/* NOTREACHED */
126		}
127	}
128	argc -= optind;
129	argv += optind;
130
131	if (wflag && aflag)
132		usage();
133		/* NOTREACHED */
134	if (wflag && argc == 0)
135		usage();
136		/* NOTREACHED */
137	if (aflag && argc > 0)
138		usage();
139		/* NOTREACHED */
140	if (!wflag && !aflag && argc == 0)
141		usage();
142		/* NOTREACHED */
143
144	if (video_dev == NULL)
145		video_dev = _PATH_VIDEO0;
146
147	video_fd = open(video_dev, wflag ? O_RDWR : O_RDONLY);
148	if (video_fd == -1)
149		err(EXIT_FAILURE, "couldn't open '%s'", video_dev);
150
151	if (aflag) {
152		video_print_all();
153	} else if (wflag) {
154		while (argc > 0) {
155			video_set(argv[0]);
156			--argc;
157			++argv;
158		}
159	} else {
160		while (argc > 0) {
161			video_print(argv[0]);
162			--argc;
163			++argv;
164		}
165	}
166
167	close(video_fd);
168
169	return EXIT_SUCCESS;
170}
171
172static void
173usage(void)
174{
175	fprintf(stderr, "usage: %s [-d file] name ...\n", getprogname());
176	fprintf(stderr, "usage: %s [-d file] -w name=value ...\n",
177	    getprogname());
178	fprintf(stderr, "usage: %s [-d file] -a\n", getprogname());
179	exit(EXIT_FAILURE);
180}
181
182static void
183video_print_all(void)
184{
185	video_print_caps(NULL);
186	video_print_formats(NULL);
187	video_print_inputs(NULL);
188	video_print_audios(NULL);
189	video_print_standards(NULL);
190	video_print_tuners(NULL);
191	video_print_ctrl(0);
192}
193
194static bool
195video_print_caps(const char *name)
196{
197	struct v4l2_capability cap;
198	char capbuf[128];
199	int error;
200	bool found = false;
201
202	if (strtok(NULL, ".") != NULL)
203		return false;
204
205	/* query capabilities */
206	error = ioctl(video_fd, VIDIOC_QUERYCAP, &cap);
207	if (error == -1)
208		err(EXIT_FAILURE, "VIDIOC_QUERYCAP failed");
209
210	if (!name || strcmp(name, "card") == 0) {
211		printf("info.cap.card=%s\n", cap.card);
212		found = true;
213	}
214	if (!name || strcmp(name, "driver") == 0) {
215		printf("info.cap.driver=%s\n", cap.driver);
216		found = true;
217	}
218	if (!name || strcmp(name, "bus_info") == 0) {
219		printf("info.cap.bus_info=%s\n", cap.bus_info);
220		found = true;
221	}
222	if (!name || strcmp(name, "version") == 0) {
223		printf("info.cap.version=%u.%u.%u\n",
224		    (cap.version >> 16) & 0xff,
225		    (cap.version >> 8) & 0xff,
226		    cap.version & 0xff);
227		found = true;
228	}
229	if (!name || strcmp(name, "capabilities") == 0) {
230		snprintb(capbuf, sizeof(capbuf), V4L2_CAP_BITMASK,
231		    cap.capabilities);
232		printf("info.cap.capabilities=%s\n", capbuf);
233		found = true;
234	}
235
236	return found;
237}
238
239static bool
240video_print_formats(const char *name)
241{
242	struct v4l2_fmtdesc fmtdesc;
243	int error;
244
245	fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
246	if (name == NULL) {
247		/* enumerate formats */
248		for (fmtdesc.index = 0; ; fmtdesc.index++) {
249			error = ioctl(video_fd, VIDIOC_ENUM_FMT, &fmtdesc);
250			if (error)
251				break;
252			printf("info.format.%u=%s\n", fmtdesc.index,
253			    fmtdesc.description);
254		}
255	} else {
256		unsigned long n;
257
258		if (strtok(NULL, ".") != NULL)
259			return false;
260
261		n = strtoul(name, NULL, 10);
262		if (n == ULONG_MAX)
263			return false;
264		fmtdesc.index = n;
265		error = ioctl(video_fd, VIDIOC_ENUM_FMT, &fmtdesc);
266		if (error)
267			return false;
268		printf("info.format.%u=%s\n", fmtdesc.index,
269		    fmtdesc.description);
270	}
271
272	return true;
273}
274
275static bool
276video_print_inputs(const char *name)
277{
278	struct v4l2_input input;
279	int error;
280
281	if (name == NULL) {
282		/* enumerate inputs */
283		for (input.index = 0; ; input.index++) {
284			error = ioctl(video_fd, VIDIOC_ENUMINPUT, &input);
285			if (error)
286				break;
287			printf("info.input.%u=%s\n", input.index, input.name);
288			printf("info.input.%u.type=", input.index);
289			switch (input.type) {
290			case V4L2_INPUT_TYPE_TUNER:
291				printf("tuner\n");
292				break;
293			case V4L2_INPUT_TYPE_CAMERA:
294				printf("baseband\n");
295				break;
296			default:
297				printf("unknown (%d)\n", input.type);
298				break;
299			}
300		}
301	} else {
302		unsigned long n;
303		char *s;
304
305		n = strtoul(name, NULL, 10);
306		if (n == ULONG_MAX)
307			return false;
308		input.index = n;
309		error = ioctl(video_fd, VIDIOC_ENUMINPUT, &input);
310		if (error)
311			return false;
312
313		s = strtok(NULL, ".");
314		if (s == NULL) {
315			printf("info.input.%u=%s\n", input.index, input.name);
316		} else if (strcmp(s, "type") == 0) {
317			if (strtok(NULL, ".") != NULL)
318				return false;
319			printf("info.input.%u.type=", input.index);
320			switch (input.type) {
321			case V4L2_INPUT_TYPE_TUNER:
322				printf("tuner\n");
323				break;
324			case V4L2_INPUT_TYPE_CAMERA:
325				printf("baseband\n");
326				break;
327			default:
328				printf("unknown (%d)\n", input.type);
329				break;
330			}
331		} else
332			return false;
333	}
334
335	return true;
336}
337
338static bool
339video_print_audios(const char *name)
340{
341	struct v4l2_audio audio;
342	int error;
343
344	if (name == NULL) {
345		/* enumerate audio */
346		for (audio.index = 0; ; audio.index++) {
347			error = ioctl(video_fd, VIDIOC_ENUMAUDIO, &audio);
348			if (error)
349				break;
350			printf("info.audio.%u=%s\n", audio.index, audio.name);
351			printf("info.audio.%u.stereo=%d\n", audio.index,
352			    audio.capability & V4L2_AUDCAP_STEREO ? 1 : 0);
353			printf("info.audio.%u.avl=%d\n", audio.index,
354			    audio.capability & V4L2_AUDCAP_AVL ? 1 : 0);
355		}
356	} else {
357		unsigned long n;
358		char *s;
359
360		n = strtoul(name, NULL, 10);
361		if (n == ULONG_MAX)
362			return false;
363		audio.index = n;
364		error = ioctl(video_fd, VIDIOC_ENUMAUDIO, &audio);
365		if (error)
366			return false;
367
368		s = strtok(NULL, ".");
369		if (s == NULL) {
370			printf("info.audio.%u=%s\n", audio.index, audio.name);
371		} else if (strcmp(s, "stereo") == 0) {
372			if (strtok(NULL, ".") != NULL)
373				return false;
374			printf("info.audio.%u.stereo=%d\n", audio.index,
375			    audio.capability & V4L2_AUDCAP_STEREO ? 1 : 0);
376		} else if (strcmp(s, "avl") == 0) {
377			if (strtok(NULL, ".") != NULL)
378				return false;
379			printf("info.audio.%u.avl=%d\n", audio.index,
380			    audio.capability & V4L2_AUDCAP_AVL ? 1 : 0);
381		} else
382			return false;
383	}
384
385	return true;
386}
387
388static bool
389video_print_standards(const char *name)
390{
391	struct v4l2_standard std;
392	int error;
393
394	if (name == NULL) {
395		/* enumerate standards */
396		for (std.index = 0; ; std.index++) {
397			error = ioctl(video_fd, VIDIOC_ENUMSTD, &std);
398			if (error)
399				break;
400			printf("info.standard.%u=%s\n", std.index, std.name);
401		}
402	} else {
403		unsigned long n;
404
405		if (strtok(NULL, ".") != NULL)
406			return false;
407
408		n = strtoul(name, NULL, 10);
409		if (n == ULONG_MAX)
410			return false;
411		std.index = n;
412		error = ioctl(video_fd, VIDIOC_ENUMSTD, &std);
413		if (error)
414			return false;
415		printf("info.standard.%u=%s\n", std.index, std.name);
416	}
417
418	return true;
419}
420
421static bool
422video_print_tuners(const char *name)
423{
424	struct v4l2_tuner tuner;
425	int error;
426
427	if (name == NULL) {
428		/* enumerate tuners */
429		for (tuner.index = 0; ; tuner.index++) {
430			error = ioctl(video_fd, VIDIOC_G_TUNER, &tuner);
431			if (error)
432				break;
433			printf("info.tuner.%u=%s\n", tuner.index, tuner.name);
434		}
435	} else {
436		unsigned long n;
437
438		if (strtok(NULL, ".") != NULL)
439			return false;
440
441		n = strtoul(name, NULL, 10);
442		if (n == ULONG_MAX)
443			return false;
444		tuner.index = n;
445		error = ioctl(video_fd, VIDIOC_G_TUNER, &tuner);
446		if (error)
447			return false;
448		printf("info.tuner.%u=%s\n", tuner.index, tuner.name);
449	}
450
451	return true;
452}
453
454static void
455video_print(const char *name)
456{
457	char *buf, *s, *s2 = NULL;
458	bool found = false;
459
460	buf = strdup(name);
461	s = strtok(buf, ".");
462	if (s == NULL)
463		return;
464
465	if (strcmp(s, "info") == 0) {
466		s = strtok(NULL, ".");
467		if (s)
468			s2 = strtok(NULL, ".");
469		if (s == NULL || strcmp(s, "cap") == 0) {
470			found = video_print_caps(s2);
471		}
472		if (s == NULL || strcmp(s, "format") == 0) {
473			found = video_print_formats(s2);
474		}
475		if (s == NULL || strcmp(s, "input") == 0) {
476			found = video_print_inputs(s2);
477		}
478		if (s == NULL || strcmp(s, "audio") == 0) {
479			found = video_print_audios(s2);
480		}
481		if (s == NULL || strcmp(s, "standard") == 0) {
482			found = video_print_standards(s2);
483		}
484		if (s == NULL || strcmp(s, "tuner") == 0) {
485			found = video_print_tuners(s2);
486		}
487	} else if (strcmp(s, "ctrl") == 0) {
488		s = strtok(NULL, ".");
489		if (s)
490			s2 = strtok(NULL, ".");
491
492		if (s == NULL)
493			found = video_print_ctrl(0);
494		else if (s && !s2)
495			found = video_print_ctrl(video_name2cid(s));
496	}
497
498	free(buf);
499	if (!found)
500		fprintf(stderr, "%s: field %s does not exist\n",
501		    getprogname(), name);
502}
503
504static bool
505video_print_ctrl(uint32_t ctrl_id)
506{
507	struct v4l2_control ctrl;
508	const char *ctrlname;
509	bool found = false;
510	int error;
511
512	for (ctrl.id = V4L2_CID_BASE; ctrl.id != V4L2_CID_LASTP1; ctrl.id++) {
513		if (ctrl_id != 0 && ctrl_id != ctrl.id)
514			continue;
515		error = ioctl(video_fd, VIDIOC_G_CTRL, &ctrl);
516		if (error)
517			continue;
518		ctrlname = video_cid2name(ctrl.id);
519		if (ctrlname)
520			printf("ctrl.%s=%d\n", ctrlname, ctrl.value);
521		else
522			printf("ctrl.%08x=%d\n", ctrl.id, ctrl.value);
523		found = true;
524	}
525
526	return found;
527}
528
529static void
530video_set(const char *name)
531{
532	char *buf, *key, *value;
533	bool found = false;
534	long n;
535
536	if (strchr(name, '=') == NULL) {
537		fprintf(stderr, "%s: No '=' in %s\n", getprogname(), name);
538		exit(EXIT_FAILURE);
539	}
540
541	buf = strdup(name);
542	key = strtok(buf, "=");
543	if (key == NULL)
544		usage();
545		/* NOTREACHED */
546	value = strtok(NULL, "");
547	if (value == NULL)
548		usage();
549		/* NOTREACHED */
550
551	if (strncmp(key, "info.", strlen("info.")) == 0) {
552		fprintf(stderr, "'info' subtree read-only\n");
553		found = true;
554		goto done;
555	}
556	if (strncmp(key, "ctrl.", strlen("ctrl.")) == 0) {
557		char *ctrlname = key + strlen("ctrl.");
558		uint32_t ctrl_id = video_name2cid(ctrlname);
559
560		n = strtol(value, NULL, 0);
561		if (n == LONG_MIN || n == LONG_MAX)
562			goto done;
563		found = video_set_ctrl(ctrl_id, n);
564	}
565
566done:
567	free(buf);
568	if (!found)
569		fprintf(stderr, "%s: field %s does not exist\n",
570		    getprogname(), name);
571}
572
573static bool
574video_set_ctrl(uint32_t ctrl_id, int32_t value)
575{
576	struct v4l2_control ctrl;
577	const char *ctrlname;
578	int32_t ovalue;
579	int error;
580
581	ctrlname = video_cid2name(ctrl_id);
582
583	ctrl.id = ctrl_id;
584	error = ioctl(video_fd, VIDIOC_G_CTRL, &ctrl);
585	if (error)
586		return false;
587	ovalue = ctrl.value;
588	ctrl.value = value;
589	error = ioctl(video_fd, VIDIOC_S_CTRL, &ctrl);
590	if (error)
591		err(EXIT_FAILURE, "VIDIOC_S_CTRL failed for '%s'", ctrlname);
592	error = ioctl(video_fd, VIDIOC_G_CTRL, &ctrl);
593	if (error)
594		err(EXIT_FAILURE, "VIDIOC_G_CTRL failed for '%s'", ctrlname);
595
596	if (ctrlname)
597		printf("ctrl.%s: %d -> %d\n", ctrlname, ovalue, ctrl.value);
598	else
599		printf("ctrl.%08x: %d -> %d\n", ctrl.id, ovalue, ctrl.value);
600
601	return true;
602}
603
604static const char *
605video_cid2name(uint32_t id)
606{
607	unsigned int i;
608
609	for (i = 0; i < __arraycount(videoctl_cid_names); i++)
610		if (videoctl_cid_names[i].id == id)
611			return videoctl_cid_names[i].name;
612
613	return NULL;
614}
615
616static uint32_t
617video_name2cid(const char *name)
618{
619	unsigned int i;
620
621	for (i = 0; i < __arraycount(videoctl_cid_names); i++)
622		if (strcmp(name, videoctl_cid_names[i].name) == 0)
623			return videoctl_cid_names[i].id;
624
625	return (uint32_t)-1;
626}
627