/* $NetBSD: videoctl.c,v 1.3 2021/02/19 11:39:11 rillig Exp $ */ /*- * Copyright (c) 2010 Jared D. McNeill <jmcneill@invisible.ca> * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include <sys/cdefs.h> __COPYRIGHT("@(#) Copyright (c) 2010\ Jared D. McNeill <jmcneill@invisible.ca>. All rights reserved."); __RCSID("$NetBSD: videoctl.c,v 1.3 2021/02/19 11:39:11 rillig Exp $"); #include <sys/types.h> #include <sys/ioctl.h> #include <sys/videoio.h> #include <err.h> #include <errno.h> #include <fcntl.h> #include <limits.h> #include <paths.h> #include <stdio.h> #include <string.h> #include <stdbool.h> #include <stdlib.h> #include <unistd.h> #include <util.h> __dead static void usage(void); static void video_print(const char *); static void video_print_all(void); static bool video_print_caps(const char *); static bool video_print_formats(const char *); static bool video_print_inputs(const char *); static bool video_print_audios(const char *); static bool video_print_standards(const char *); static bool video_print_tuners(const char *); static bool video_print_ctrl(uint32_t); static void video_set(const char *); static bool video_set_ctrl(uint32_t, int32_t); static const char * video_cid2name(uint32_t); static uint32_t video_name2cid(const char *); static const char *video_dev = NULL; static int video_fd = -1; static bool aflag = false; static bool wflag = false; static const struct { uint32_t id; const char *name; } videoctl_cid_names[] = { { V4L2_CID_BRIGHTNESS, "brightness" }, { V4L2_CID_CONTRAST, "contrast" }, { V4L2_CID_SATURATION, "saturation" }, { V4L2_CID_HUE, "hue" }, { V4L2_CID_AUDIO_VOLUME, "audio_volume" }, { V4L2_CID_AUDIO_BALANCE, "audio_balance" }, { V4L2_CID_AUDIO_BASS, "audio_bass" }, { V4L2_CID_AUDIO_TREBLE, "audio_treble" }, { V4L2_CID_AUDIO_MUTE, "audio_mute" }, { V4L2_CID_AUDIO_LOUDNESS, "audio_loudness" }, { V4L2_CID_BLACK_LEVEL, "black_level" }, { V4L2_CID_AUTO_WHITE_BALANCE, "auto_white_balance" }, { V4L2_CID_DO_WHITE_BALANCE, "do_white_balance" }, { V4L2_CID_RED_BALANCE, "red_balance" }, { V4L2_CID_BLUE_BALANCE, "blue_balance" }, { V4L2_CID_GAMMA, "gamma" }, { V4L2_CID_WHITENESS, "whiteness" }, { V4L2_CID_EXPOSURE, "exposure" }, { V4L2_CID_AUTOGAIN, "autogain" }, { V4L2_CID_GAIN, "gain" }, { V4L2_CID_HFLIP, "hflip" }, { V4L2_CID_VFLIP, "vflip" }, { V4L2_CID_HCENTER, "hcenter" }, { V4L2_CID_VCENTER, "vcenter" }, { V4L2_CID_POWER_LINE_FREQUENCY, "power_line_frequency" }, { V4L2_CID_HUE_AUTO, "hue_auto" }, { V4L2_CID_WHITE_BALANCE_TEMPERATURE, "white_balance_temperature" }, { V4L2_CID_SHARPNESS, "sharpness" }, { V4L2_CID_BACKLIGHT_COMPENSATION, "backlight_compensation" }, }; int main(int argc, char *argv[]) { int ch; setprogname(argv[0]); while ((ch = getopt(argc, argv, "ad:w")) != -1) { switch (ch) { case 'a': aflag = true; break; case 'd': video_dev = strdup(optarg); break; case 'w': wflag = true; break; default: usage(); /* NOTREACHED */ } } argc -= optind; argv += optind; if (wflag && aflag) usage(); /* NOTREACHED */ if (wflag && argc == 0) usage(); /* NOTREACHED */ if (aflag && argc > 0) usage(); /* NOTREACHED */ if (!wflag && !aflag && argc == 0) usage(); /* NOTREACHED */ if (video_dev == NULL) video_dev = _PATH_VIDEO0; video_fd = open(video_dev, wflag ? O_RDWR : O_RDONLY); if (video_fd == -1) err(EXIT_FAILURE, "couldn't open '%s'", video_dev); if (aflag) { video_print_all(); } else if (wflag) { while (argc > 0) { video_set(argv[0]); --argc; ++argv; } } else { while (argc > 0) { video_print(argv[0]); --argc; ++argv; } } close(video_fd); return EXIT_SUCCESS; } static void usage(void) { fprintf(stderr, "usage: %s [-d file] name ...\n", getprogname()); fprintf(stderr, "usage: %s [-d file] -w name=value ...\n", getprogname()); fprintf(stderr, "usage: %s [-d file] -a\n", getprogname()); exit(EXIT_FAILURE); } static void video_print_all(void) { video_print_caps(NULL); video_print_formats(NULL); video_print_inputs(NULL); video_print_audios(NULL); video_print_standards(NULL); video_print_tuners(NULL); video_print_ctrl(0); } static bool video_print_caps(const char *name) { struct v4l2_capability cap; char capbuf[128]; int error; bool found = false; if (strtok(NULL, ".") != NULL) return false; /* query capabilities */ error = ioctl(video_fd, VIDIOC_QUERYCAP, &cap); if (error == -1) err(EXIT_FAILURE, "VIDIOC_QUERYCAP failed"); if (!name || strcmp(name, "card") == 0) { printf("info.cap.card=%s\n", cap.card); found = true; } if (!name || strcmp(name, "driver") == 0) { printf("info.cap.driver=%s\n", cap.driver); found = true; } if (!name || strcmp(name, "bus_info") == 0) { printf("info.cap.bus_info=%s\n", cap.bus_info); found = true; } if (!name || strcmp(name, "version") == 0) { printf("info.cap.version=%u.%u.%u\n", (cap.version >> 16) & 0xff, (cap.version >> 8) & 0xff, cap.version & 0xff); found = true; } if (!name || strcmp(name, "capabilities") == 0) { snprintb(capbuf, sizeof(capbuf), V4L2_CAP_BITMASK, cap.capabilities); printf("info.cap.capabilities=%s\n", capbuf); found = true; } return found; } static bool video_print_formats(const char *name) { struct v4l2_fmtdesc fmtdesc; int error; fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (name == NULL) { /* enumerate formats */ for (fmtdesc.index = 0; ; fmtdesc.index++) { error = ioctl(video_fd, VIDIOC_ENUM_FMT, &fmtdesc); if (error) break; printf("info.format.%u=%s\n", fmtdesc.index, fmtdesc.description); } } else { unsigned long n; if (strtok(NULL, ".") != NULL) return false; n = strtoul(name, NULL, 10); if (n == ULONG_MAX) return false; fmtdesc.index = n; error = ioctl(video_fd, VIDIOC_ENUM_FMT, &fmtdesc); if (error) return false; printf("info.format.%u=%s\n", fmtdesc.index, fmtdesc.description); } return true; } static bool video_print_inputs(const char *name) { struct v4l2_input input; int error; if (name == NULL) { /* enumerate inputs */ for (input.index = 0; ; input.index++) { error = ioctl(video_fd, VIDIOC_ENUMINPUT, &input); if (error) break; printf("info.input.%u=%s\n", input.index, input.name); printf("info.input.%u.type=", input.index); switch (input.type) { case V4L2_INPUT_TYPE_TUNER: printf("tuner\n"); break; case V4L2_INPUT_TYPE_CAMERA: printf("baseband\n"); break; default: printf("unknown (%d)\n", input.type); break; } } } else { unsigned long n; char *s; n = strtoul(name, NULL, 10); if (n == ULONG_MAX) return false; input.index = n; error = ioctl(video_fd, VIDIOC_ENUMINPUT, &input); if (error) return false; s = strtok(NULL, "."); if (s == NULL) { printf("info.input.%u=%s\n", input.index, input.name); } else if (strcmp(s, "type") == 0) { if (strtok(NULL, ".") != NULL) return false; printf("info.input.%u.type=", input.index); switch (input.type) { case V4L2_INPUT_TYPE_TUNER: printf("tuner\n"); break; case V4L2_INPUT_TYPE_CAMERA: printf("baseband\n"); break; default: printf("unknown (%d)\n", input.type); break; } } else return false; } return true; } static bool video_print_audios(const char *name) { struct v4l2_audio audio; int error; if (name == NULL) { /* enumerate audio */ for (audio.index = 0; ; audio.index++) { error = ioctl(video_fd, VIDIOC_ENUMAUDIO, &audio); if (error) break; printf("info.audio.%u=%s\n", audio.index, audio.name); printf("info.audio.%u.stereo=%d\n", audio.index, audio.capability & V4L2_AUDCAP_STEREO ? 1 : 0); printf("info.audio.%u.avl=%d\n", audio.index, audio.capability & V4L2_AUDCAP_AVL ? 1 : 0); } } else { unsigned long n; char *s; n = strtoul(name, NULL, 10); if (n == ULONG_MAX) return false; audio.index = n; error = ioctl(video_fd, VIDIOC_ENUMAUDIO, &audio); if (error) return false; s = strtok(NULL, "."); if (s == NULL) { printf("info.audio.%u=%s\n", audio.index, audio.name); } else if (strcmp(s, "stereo") == 0) { if (strtok(NULL, ".") != NULL) return false; printf("info.audio.%u.stereo=%d\n", audio.index, audio.capability & V4L2_AUDCAP_STEREO ? 1 : 0); } else if (strcmp(s, "avl") == 0) { if (strtok(NULL, ".") != NULL) return false; printf("info.audio.%u.avl=%d\n", audio.index, audio.capability & V4L2_AUDCAP_AVL ? 1 : 0); } else return false; } return true; } static bool video_print_standards(const char *name) { struct v4l2_standard std; int error; if (name == NULL) { /* enumerate standards */ for (std.index = 0; ; std.index++) { error = ioctl(video_fd, VIDIOC_ENUMSTD, &std); if (error) break; printf("info.standard.%u=%s\n", std.index, std.name); } } else { unsigned long n; if (strtok(NULL, ".") != NULL) return false; n = strtoul(name, NULL, 10); if (n == ULONG_MAX) return false; std.index = n; error = ioctl(video_fd, VIDIOC_ENUMSTD, &std); if (error) return false; printf("info.standard.%u=%s\n", std.index, std.name); } return true; } static bool video_print_tuners(const char *name) { struct v4l2_tuner tuner; int error; if (name == NULL) { /* enumerate tuners */ for (tuner.index = 0; ; tuner.index++) { error = ioctl(video_fd, VIDIOC_G_TUNER, &tuner); if (error) break; printf("info.tuner.%u=%s\n", tuner.index, tuner.name); } } else { unsigned long n; if (strtok(NULL, ".") != NULL) return false; n = strtoul(name, NULL, 10); if (n == ULONG_MAX) return false; tuner.index = n; error = ioctl(video_fd, VIDIOC_G_TUNER, &tuner); if (error) return false; printf("info.tuner.%u=%s\n", tuner.index, tuner.name); } return true; } static void video_print(const char *name) { char *buf, *s, *s2 = NULL; bool found = false; buf = strdup(name); s = strtok(buf, "."); if (s == NULL) return; if (strcmp(s, "info") == 0) { s = strtok(NULL, "."); if (s) s2 = strtok(NULL, "."); if (s == NULL || strcmp(s, "cap") == 0) { found = video_print_caps(s2); } if (s == NULL || strcmp(s, "format") == 0) { found = video_print_formats(s2); } if (s == NULL || strcmp(s, "input") == 0) { found = video_print_inputs(s2); } if (s == NULL || strcmp(s, "audio") == 0) { found = video_print_audios(s2); } if (s == NULL || strcmp(s, "standard") == 0) { found = video_print_standards(s2); } if (s == NULL || strcmp(s, "tuner") == 0) { found = video_print_tuners(s2); } } else if (strcmp(s, "ctrl") == 0) { s = strtok(NULL, "."); if (s) s2 = strtok(NULL, "."); if (s == NULL) found = video_print_ctrl(0); else if (s && !s2) found = video_print_ctrl(video_name2cid(s)); } free(buf); if (!found) fprintf(stderr, "%s: field %s does not exist\n", getprogname(), name); } static bool video_print_ctrl(uint32_t ctrl_id) { struct v4l2_control ctrl; const char *ctrlname; bool found = false; int error; for (ctrl.id = V4L2_CID_BASE; ctrl.id != V4L2_CID_LASTP1; ctrl.id++) { if (ctrl_id != 0 && ctrl_id != ctrl.id) continue; error = ioctl(video_fd, VIDIOC_G_CTRL, &ctrl); if (error) continue; ctrlname = video_cid2name(ctrl.id); if (ctrlname) printf("ctrl.%s=%d\n", ctrlname, ctrl.value); else printf("ctrl.%08x=%d\n", ctrl.id, ctrl.value); found = true; } return found; } static void video_set(const char *name) { char *buf, *key, *value; bool found = false; long n; if (strchr(name, '=') == NULL) { fprintf(stderr, "%s: No '=' in %s\n", getprogname(), name); exit(EXIT_FAILURE); } buf = strdup(name); key = strtok(buf, "="); if (key == NULL) usage(); /* NOTREACHED */ value = strtok(NULL, ""); if (value == NULL) usage(); /* NOTREACHED */ if (strncmp(key, "info.", strlen("info.")) == 0) { fprintf(stderr, "'info' subtree read-only\n"); found = true; goto done; } if (strncmp(key, "ctrl.", strlen("ctrl.")) == 0) { char *ctrlname = key + strlen("ctrl."); uint32_t ctrl_id = video_name2cid(ctrlname); n = strtol(value, NULL, 0); if (n == LONG_MIN || n == LONG_MAX) goto done; found = video_set_ctrl(ctrl_id, n); } done: free(buf); if (!found) fprintf(stderr, "%s: field %s does not exist\n", getprogname(), name); } static bool video_set_ctrl(uint32_t ctrl_id, int32_t value) { struct v4l2_control ctrl; const char *ctrlname; int32_t ovalue; int error; ctrlname = video_cid2name(ctrl_id); ctrl.id = ctrl_id; error = ioctl(video_fd, VIDIOC_G_CTRL, &ctrl); if (error) return false; ovalue = ctrl.value; ctrl.value = value; error = ioctl(video_fd, VIDIOC_S_CTRL, &ctrl); if (error) err(EXIT_FAILURE, "VIDIOC_S_CTRL failed for '%s'", ctrlname); error = ioctl(video_fd, VIDIOC_G_CTRL, &ctrl); if (error) err(EXIT_FAILURE, "VIDIOC_G_CTRL failed for '%s'", ctrlname); if (ctrlname) printf("ctrl.%s: %d -> %d\n", ctrlname, ovalue, ctrl.value); else printf("ctrl.%08x: %d -> %d\n", ctrl.id, ovalue, ctrl.value); return true; } static const char * video_cid2name(uint32_t id) { unsigned int i; for (i = 0; i < __arraycount(videoctl_cid_names); i++) if (videoctl_cid_names[i].id == id) return videoctl_cid_names[i].name; return NULL; } static uint32_t video_name2cid(const char *name) { unsigned int i; for (i = 0; i < __arraycount(videoctl_cid_names); i++) if (strcmp(name, videoctl_cid_names[i].name) == 0) return videoctl_cid_names[i].id; return (uint32_t)-1; }