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