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@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.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
main(int argc,char * argv[])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
usage(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
video_print_all(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
video_print_caps(const char * name)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
video_print_one_format(unsigned long fmtnum,unsigned long sizenum)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
video_print_formats(const char * name)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
video_print_inputs(const char * name)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
video_print_audios(const char * name)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
video_print_standards(const char * name)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
video_print_tuners(const char * name)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
video_print(const char * name)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
video_print_ctrl(uint32_t ctrl_id)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
video_set(const char * name)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
video_set_ctrl(uint32_t ctrl_id,int32_t value)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 *
video_cid2name(uint32_t id)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
video_name2cid(const char * name)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