videoctl.c revision 1.4 1 /* $NetBSD: videoctl.c,v 1.4 2025/04/12 07:54:39 mlelstv Exp $ */
2
3 /*-
4 * Copyright (c) 2010 Jared D. McNeill <jmcneill (at) 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 (at) invisible.ca>. All rights reserved.");
32 __RCSID("$NetBSD: videoctl.c,v 1.4 2025/04/12 07:54:39 mlelstv Exp $");
33
34 #include <sys/types.h>
35 #include <sys/endian.h>
36 #include <sys/ioctl.h>
37 #include <sys/videoio.h>
38
39 #include <err.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <limits.h>
43 #include <paths.h>
44 #include <stdio.h>
45 #include <string.h>
46 #include <stdbool.h>
47 #include <stdlib.h>
48 #include <unistd.h>
49 #include <util.h>
50 #include <vis.h>
51
52 __dead static void usage(void);
53 static void video_print(const char *);
54 static void video_print_all(void);
55 static bool video_print_caps(const char *);
56 static bool video_print_formats(const char *);
57 static bool video_print_inputs(const char *);
58 static bool video_print_audios(const char *);
59 static bool video_print_standards(const char *);
60 static bool video_print_tuners(const char *);
61 static bool video_print_ctrl(uint32_t);
62 static void video_set(const char *);
63 static bool video_set_ctrl(uint32_t, int32_t);
64 static const char * video_cid2name(uint32_t);
65 static uint32_t video_name2cid(const char *);
66
67 static const char *video_dev = NULL;
68 static int video_fd = -1;
69 static bool aflag = false;
70 static bool wflag = false;
71
72 static const struct {
73 uint32_t id;
74 const char *name;
75 } videoctl_cid_names[] = {
76 { V4L2_CID_BRIGHTNESS, "brightness" },
77 { V4L2_CID_CONTRAST, "contrast" },
78 { V4L2_CID_SATURATION, "saturation" },
79 { V4L2_CID_HUE, "hue" },
80 { V4L2_CID_AUDIO_VOLUME, "audio_volume" },
81 { V4L2_CID_AUDIO_BALANCE, "audio_balance" },
82 { V4L2_CID_AUDIO_BASS, "audio_bass" },
83 { V4L2_CID_AUDIO_TREBLE, "audio_treble" },
84 { V4L2_CID_AUDIO_MUTE, "audio_mute" },
85 { V4L2_CID_AUDIO_LOUDNESS, "audio_loudness" },
86 { V4L2_CID_BLACK_LEVEL, "black_level" },
87 { V4L2_CID_AUTO_WHITE_BALANCE, "auto_white_balance" },
88 { V4L2_CID_DO_WHITE_BALANCE, "do_white_balance" },
89 { V4L2_CID_RED_BALANCE, "red_balance" },
90 { V4L2_CID_BLUE_BALANCE, "blue_balance" },
91 { V4L2_CID_GAMMA, "gamma" },
92 { V4L2_CID_WHITENESS, "whiteness" },
93 { V4L2_CID_EXPOSURE, "exposure" },
94 { V4L2_CID_AUTOGAIN, "autogain" },
95 { V4L2_CID_GAIN, "gain" },
96 { V4L2_CID_HFLIP, "hflip" },
97 { V4L2_CID_VFLIP, "vflip" },
98 { V4L2_CID_HCENTER, "hcenter" },
99 { V4L2_CID_VCENTER, "vcenter" },
100 { V4L2_CID_POWER_LINE_FREQUENCY, "power_line_frequency" },
101 { V4L2_CID_HUE_AUTO, "hue_auto" },
102 { V4L2_CID_WHITE_BALANCE_TEMPERATURE, "white_balance_temperature" },
103 { V4L2_CID_SHARPNESS, "sharpness" },
104 { V4L2_CID_BACKLIGHT_COMPENSATION, "backlight_compensation" },
105 };
106
107 int
108 main(int argc, char *argv[])
109 {
110 int ch;
111
112 setprogname(argv[0]);
113
114 while ((ch = getopt(argc, argv, "ad:w")) != -1) {
115 switch (ch) {
116 case 'a':
117 aflag = true;
118 break;
119 case 'd':
120 video_dev = strdup(optarg);
121 break;
122 case 'w':
123 wflag = true;
124 break;
125 default:
126 usage();
127 /* NOTREACHED */
128 }
129 }
130 argc -= optind;
131 argv += optind;
132
133 if (wflag && aflag)
134 usage();
135 /* NOTREACHED */
136 if (wflag && argc == 0)
137 usage();
138 /* NOTREACHED */
139 if (aflag && argc > 0)
140 usage();
141 /* NOTREACHED */
142 if (!wflag && !aflag && argc == 0)
143 usage();
144 /* NOTREACHED */
145
146 if (video_dev == NULL)
147 video_dev = _PATH_VIDEO0;
148
149 video_fd = open(video_dev, wflag ? O_RDWR : O_RDONLY);
150 if (video_fd == -1)
151 err(EXIT_FAILURE, "couldn't open '%s'", video_dev);
152
153 if (aflag) {
154 video_print_all();
155 } else if (wflag) {
156 while (argc > 0) {
157 video_set(argv[0]);
158 --argc;
159 ++argv;
160 }
161 } else {
162 while (argc > 0) {
163 video_print(argv[0]);
164 --argc;
165 ++argv;
166 }
167 }
168
169 close(video_fd);
170
171 return EXIT_SUCCESS;
172 }
173
174 static void
175 usage(void)
176 {
177 fprintf(stderr, "usage: %s [-d file] name ...\n", getprogname());
178 fprintf(stderr, "usage: %s [-d file] -w name=value ...\n",
179 getprogname());
180 fprintf(stderr, "usage: %s [-d file] -a\n", getprogname());
181 exit(EXIT_FAILURE);
182 }
183
184 static void
185 video_print_all(void)
186 {
187 video_print_caps(NULL);
188 video_print_formats(NULL);
189 video_print_inputs(NULL);
190 video_print_audios(NULL);
191 video_print_standards(NULL);
192 video_print_tuners(NULL);
193 video_print_ctrl(0);
194 }
195
196 static bool
197 video_print_caps(const char *name)
198 {
199 struct v4l2_capability cap;
200 char capbuf[128];
201 int error;
202 bool found = false;
203
204 if (strtok(NULL, ".") != NULL)
205 return false;
206
207 /* query capabilities */
208 error = ioctl(video_fd, VIDIOC_QUERYCAP, &cap);
209 if (error == -1)
210 err(EXIT_FAILURE, "VIDIOC_QUERYCAP failed");
211
212 if (!name || strcmp(name, "card") == 0) {
213 printf("info.cap.card=%s\n", cap.card);
214 found = true;
215 }
216 if (!name || strcmp(name, "driver") == 0) {
217 printf("info.cap.driver=%s\n", cap.driver);
218 found = true;
219 }
220 if (!name || strcmp(name, "bus_info") == 0) {
221 printf("info.cap.bus_info=%s\n", cap.bus_info);
222 found = true;
223 }
224 if (!name || strcmp(name, "version") == 0) {
225 printf("info.cap.version=%u.%u.%u\n",
226 (cap.version >> 16) & 0xff,
227 (cap.version >> 8) & 0xff,
228 cap.version & 0xff);
229 found = true;
230 }
231 if (!name || strcmp(name, "capabilities") == 0) {
232 snprintb(capbuf, sizeof(capbuf), V4L2_CAP_BITMASK,
233 cap.capabilities);
234 printf("info.cap.capabilities=%s\n", capbuf);
235 found = true;
236 }
237
238 return found;
239 }
240
241 static bool
242 video_print_one_format(unsigned long fmtnum, unsigned long sizenum)
243 {
244 struct v4l2_fmtdesc fmtdesc;
245 struct v4l2_frmsizeenum framesize;
246 unsigned long n;
247 int error;
248
249 fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
250 fmtdesc.index = fmtnum;
251 error = ioctl(video_fd, VIDIOC_ENUM_FMT, &fmtdesc);
252 if (error)
253 return false;
254
255 printf("info.format.%u=%s\n", fmtdesc.index,
256 fmtdesc.description);
257
258 if (sizenum == ULONG_MAX)
259 n = 0;
260 else
261 n = sizenum;
262
263 while (n <= sizenum) {
264 framesize.index = n++;
265 framesize.pixel_format = fmtdesc.pixelformat;
266 error = ioctl(video_fd, VIDIOC_ENUM_FRAMESIZES, &framesize);
267 if (error)
268 break;
269
270 switch (framesize.type) {
271 case V4L2_FRMIVAL_TYPE_DISCRETE:
272 case V4L2_FRMIVAL_TYPE_CONTINUOUS:
273 printf("info.format.%u.size.%u=%ux%u\n",
274 fmtdesc.index, framesize.index,
275 framesize.discrete.width,
276 framesize.discrete.height);
277 break;
278 case V4L2_FRMIVAL_TYPE_STEPWISE:
279 printf("info.format.%u.size.%u=(%u,%u,%u)x(%u,%u,%u)\n",
280 fmtdesc.index, framesize.index,
281 framesize.stepwise.min_width,
282 framesize.stepwise.max_width,
283 framesize.stepwise.step_width,
284 framesize.stepwise.min_height,
285 framesize.stepwise.max_height,
286 framesize.stepwise.step_height);
287 break;
288 default:
289 printf("info.format.%u.size.%u=type %u\n",
290 fmtdesc.index, framesize.index,
291 framesize.type);
292 }
293 }
294
295 return true;
296 }
297
298 static bool
299 video_print_formats(const char *name)
300 {
301 unsigned long n, m = ULONG_MAX;
302 const char *p;
303
304 if (name == NULL) {
305 /* enumerate formats */
306 for (n = 0; ; n++) {
307 if (!video_print_one_format(n, m))
308 break;
309 }
310 } else {
311 p = strtok(NULL, ".");
312 n = strtoul(name, NULL, 10);
313 if (n == ULONG_MAX)
314 return false;
315
316 if (p != NULL) {
317 if (strcmp(p, "size") == 0) {
318 p = strtok(NULL, ".");
319 if (p != NULL) {
320 m = strtoul(p, NULL, 10);
321 if (m == ULONG_MAX)
322 return false;
323 }
324 } else
325 return false;
326 }
327
328
329 video_print_one_format(n, m);
330 }
331
332 return true;
333 }
334
335 static bool
336 video_print_inputs(const char *name)
337 {
338 struct v4l2_input input;
339 int error;
340
341 if (name == NULL) {
342 /* enumerate inputs */
343 for (input.index = 0; ; input.index++) {
344 error = ioctl(video_fd, VIDIOC_ENUMINPUT, &input);
345 if (error)
346 break;
347 printf("info.input.%u=%s\n", input.index, input.name);
348 printf("info.input.%u.type=", input.index);
349 switch (input.type) {
350 case V4L2_INPUT_TYPE_TUNER:
351 printf("tuner\n");
352 break;
353 case V4L2_INPUT_TYPE_CAMERA:
354 printf("baseband\n");
355 break;
356 default:
357 printf("unknown (%d)\n", input.type);
358 break;
359 }
360 }
361 } else {
362 unsigned long n;
363 char *s;
364
365 n = strtoul(name, NULL, 10);
366 if (n == ULONG_MAX)
367 return false;
368 input.index = n;
369 error = ioctl(video_fd, VIDIOC_ENUMINPUT, &input);
370 if (error)
371 return false;
372
373 s = strtok(NULL, ".");
374 if (s == NULL) {
375 printf("info.input.%u=%s\n", input.index, input.name);
376 } else if (strcmp(s, "type") == 0) {
377 if (strtok(NULL, ".") != NULL)
378 return false;
379 printf("info.input.%u.type=", input.index);
380 switch (input.type) {
381 case V4L2_INPUT_TYPE_TUNER:
382 printf("tuner\n");
383 break;
384 case V4L2_INPUT_TYPE_CAMERA:
385 printf("baseband\n");
386 break;
387 default:
388 printf("unknown (%d)\n", input.type);
389 break;
390 }
391 } else
392 return false;
393 }
394
395 return true;
396 }
397
398 static bool
399 video_print_audios(const char *name)
400 {
401 struct v4l2_audio audio;
402 int error;
403
404 if (name == NULL) {
405 /* enumerate audio */
406 for (audio.index = 0; ; audio.index++) {
407 error = ioctl(video_fd, VIDIOC_ENUMAUDIO, &audio);
408 if (error)
409 break;
410 printf("info.audio.%u=%s\n", audio.index, audio.name);
411 printf("info.audio.%u.stereo=%d\n", audio.index,
412 audio.capability & V4L2_AUDCAP_STEREO ? 1 : 0);
413 printf("info.audio.%u.avl=%d\n", audio.index,
414 audio.capability & V4L2_AUDCAP_AVL ? 1 : 0);
415 }
416 } else {
417 unsigned long n;
418 char *s;
419
420 n = strtoul(name, NULL, 10);
421 if (n == ULONG_MAX)
422 return false;
423 audio.index = n;
424 error = ioctl(video_fd, VIDIOC_ENUMAUDIO, &audio);
425 if (error)
426 return false;
427
428 s = strtok(NULL, ".");
429 if (s == NULL) {
430 printf("info.audio.%u=%s\n", audio.index, audio.name);
431 } else if (strcmp(s, "stereo") == 0) {
432 if (strtok(NULL, ".") != NULL)
433 return false;
434 printf("info.audio.%u.stereo=%d\n", audio.index,
435 audio.capability & V4L2_AUDCAP_STEREO ? 1 : 0);
436 } else if (strcmp(s, "avl") == 0) {
437 if (strtok(NULL, ".") != NULL)
438 return false;
439 printf("info.audio.%u.avl=%d\n", audio.index,
440 audio.capability & V4L2_AUDCAP_AVL ? 1 : 0);
441 } else
442 return false;
443 }
444
445 return true;
446 }
447
448 static bool
449 video_print_standards(const char *name)
450 {
451 struct v4l2_standard std;
452 int error;
453
454 if (name == NULL) {
455 /* enumerate standards */
456 for (std.index = 0; ; std.index++) {
457 error = ioctl(video_fd, VIDIOC_ENUMSTD, &std);
458 if (error)
459 break;
460 printf("info.standard.%u=%s\n", std.index, std.name);
461 }
462 } else {
463 unsigned long n;
464
465 if (strtok(NULL, ".") != NULL)
466 return false;
467
468 n = strtoul(name, NULL, 10);
469 if (n == ULONG_MAX)
470 return false;
471 std.index = n;
472 error = ioctl(video_fd, VIDIOC_ENUMSTD, &std);
473 if (error)
474 return false;
475 printf("info.standard.%u=%s\n", std.index, std.name);
476 }
477
478 return true;
479 }
480
481 static bool
482 video_print_tuners(const char *name)
483 {
484 struct v4l2_tuner tuner;
485 int error;
486
487 if (name == NULL) {
488 /* enumerate tuners */
489 for (tuner.index = 0; ; tuner.index++) {
490 error = ioctl(video_fd, VIDIOC_G_TUNER, &tuner);
491 if (error)
492 break;
493 printf("info.tuner.%u=%s\n", tuner.index, tuner.name);
494 }
495 } else {
496 unsigned long n;
497
498 if (strtok(NULL, ".") != NULL)
499 return false;
500
501 n = strtoul(name, NULL, 10);
502 if (n == ULONG_MAX)
503 return false;
504 tuner.index = n;
505 error = ioctl(video_fd, VIDIOC_G_TUNER, &tuner);
506 if (error)
507 return false;
508 printf("info.tuner.%u=%s\n", tuner.index, tuner.name);
509 }
510
511 return true;
512 }
513
514 static void
515 video_print(const char *name)
516 {
517 char *buf, *s, *s2 = NULL;
518 bool found = false;
519
520 buf = strdup(name);
521 s = strtok(buf, ".");
522 if (s == NULL)
523 return;
524
525 if (strcmp(s, "info") == 0) {
526 s = strtok(NULL, ".");
527 if (s)
528 s2 = strtok(NULL, ".");
529 if (s == NULL || strcmp(s, "cap") == 0) {
530 found = video_print_caps(s2);
531 }
532 if (s == NULL || strcmp(s, "format") == 0) {
533 found = video_print_formats(s2);
534 }
535 if (s == NULL || strcmp(s, "input") == 0) {
536 found = video_print_inputs(s2);
537 }
538 if (s == NULL || strcmp(s, "audio") == 0) {
539 found = video_print_audios(s2);
540 }
541 if (s == NULL || strcmp(s, "standard") == 0) {
542 found = video_print_standards(s2);
543 }
544 if (s == NULL || strcmp(s, "tuner") == 0) {
545 found = video_print_tuners(s2);
546 }
547 } else if (strcmp(s, "ctrl") == 0) {
548 s = strtok(NULL, ".");
549 if (s)
550 s2 = strtok(NULL, ".");
551
552 if (s == NULL)
553 found = video_print_ctrl(0);
554 else if (s && !s2)
555 found = video_print_ctrl(video_name2cid(s));
556 }
557
558 free(buf);
559 if (!found)
560 fprintf(stderr, "%s: field %s does not exist\n",
561 getprogname(), name);
562 }
563
564 static bool
565 video_print_ctrl(uint32_t ctrl_id)
566 {
567 struct v4l2_control ctrl;
568 const char *ctrlname;
569 bool found = false;
570 int error;
571
572 for (ctrl.id = V4L2_CID_BASE; ctrl.id != V4L2_CID_LASTP1; ctrl.id++) {
573 if (ctrl_id != 0 && ctrl_id != ctrl.id)
574 continue;
575 error = ioctl(video_fd, VIDIOC_G_CTRL, &ctrl);
576 if (error)
577 continue;
578 ctrlname = video_cid2name(ctrl.id);
579 if (ctrlname)
580 printf("ctrl.%s=%d\n", ctrlname, ctrl.value);
581 else
582 printf("ctrl.%08x=%d\n", ctrl.id, ctrl.value);
583 found = true;
584 }
585
586 return found;
587 }
588
589 static void
590 video_set(const char *name)
591 {
592 char *buf, *key, *value;
593 bool found = false;
594 long n;
595
596 if (strchr(name, '=') == NULL) {
597 fprintf(stderr, "%s: No '=' in %s\n", getprogname(), name);
598 exit(EXIT_FAILURE);
599 }
600
601 buf = strdup(name);
602 key = strtok(buf, "=");
603 if (key == NULL)
604 usage();
605 /* NOTREACHED */
606 value = strtok(NULL, "");
607 if (value == NULL)
608 usage();
609 /* NOTREACHED */
610
611 if (strncmp(key, "info.", strlen("info.")) == 0) {
612 fprintf(stderr, "'info' subtree read-only\n");
613 found = true;
614 goto done;
615 }
616 if (strncmp(key, "ctrl.", strlen("ctrl.")) == 0) {
617 char *ctrlname = key + strlen("ctrl.");
618 uint32_t ctrl_id = video_name2cid(ctrlname);
619
620 n = strtol(value, NULL, 0);
621 if (n == LONG_MIN || n == LONG_MAX)
622 goto done;
623 found = video_set_ctrl(ctrl_id, n);
624 }
625
626 done:
627 free(buf);
628 if (!found)
629 fprintf(stderr, "%s: field %s does not exist\n",
630 getprogname(), name);
631 }
632
633 static bool
634 video_set_ctrl(uint32_t ctrl_id, int32_t value)
635 {
636 struct v4l2_control ctrl;
637 const char *ctrlname;
638 int32_t ovalue;
639 int error;
640
641 ctrlname = video_cid2name(ctrl_id);
642
643 ctrl.id = ctrl_id;
644 error = ioctl(video_fd, VIDIOC_G_CTRL, &ctrl);
645 if (error)
646 return false;
647 ovalue = ctrl.value;
648 ctrl.value = value;
649 error = ioctl(video_fd, VIDIOC_S_CTRL, &ctrl);
650 if (error)
651 err(EXIT_FAILURE, "VIDIOC_S_CTRL failed for '%s'", ctrlname);
652 error = ioctl(video_fd, VIDIOC_G_CTRL, &ctrl);
653 if (error)
654 err(EXIT_FAILURE, "VIDIOC_G_CTRL failed for '%s'", ctrlname);
655
656 if (ctrlname)
657 printf("ctrl.%s: %d -> %d\n", ctrlname, ovalue, ctrl.value);
658 else
659 printf("ctrl.%08x: %d -> %d\n", ctrl.id, ovalue, ctrl.value);
660
661 return true;
662 }
663
664 static const char *
665 video_cid2name(uint32_t id)
666 {
667 unsigned int i;
668
669 for (i = 0; i < __arraycount(videoctl_cid_names); i++)
670 if (videoctl_cid_names[i].id == id)
671 return videoctl_cid_names[i].name;
672
673 return NULL;
674 }
675
676 static uint32_t
677 video_name2cid(const char *name)
678 {
679 unsigned int i;
680
681 for (i = 0; i < __arraycount(videoctl_cid_names); i++)
682 if (strcmp(name, videoctl_cid_names[i].name) == 0)
683 return videoctl_cid_names[i].id;
684
685 return (uint32_t)-1;
686 }
687