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