diff --git a/usr.bin/videoctl/Makefile b/usr.bin/videoctl/Makefile new file mode 100644 index 000000000000..21579d46200a --- /dev/null +++ b/usr.bin/videoctl/Makefile @@ -0,0 +1,8 @@ +# $NetBSD: Makefile,v 1.1 2010/12/26 10:37:14 jmcneill Exp $ + +PROG= videoctl +SRCS= videoctl.c +LDADD+= -lutil +DPADD+= ${LIBUTIL} + +.include diff --git a/usr.bin/videoctl/videoctl.1 b/usr.bin/videoctl/videoctl.1 new file mode 100644 index 000000000000..4d834d377268 --- /dev/null +++ b/usr.bin/videoctl/videoctl.1 @@ -0,0 +1,77 @@ +.\" $NetBSD: videoctl.1,v 1.1 2010/12/26 10:37:15 jmcneill Exp $ +.\" +.\" Copyright (c) 2010 Jared D. McNeill +.\" 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. +.\" +.Dd December 26, 2010 +.Dt VIDEOCTL 1 +.Os +.Sh NAME +.Nm videoctl +.Nd control video device +.Sh SYNOPSIS +.Nm +.Op Fl d Ar device +.Fl a +.Nm +.Op Fl d Ar device +.Ar name ... +.Nm +.Op Fl d Ar device +.Fl w +.Ar name=value ... +.Sh DESCRIPTION +The +.Nm +command displays or sets various video system driver variables. If a list of +variables is present on the command line, then +.Nm +prints the current value of those variables for the specified device. +If the +.Fl a +flag is specified, all variables for the device are printed. +If the +.Fl w +flag is specified +.Nm +attempts to set the specified variables to the given values. +The +.Fl d +flag can be used to specify an alternate video device. If +.Fl d +is not specified, +.Pa /dev/video0 +is used. +.Sh FILES +.Bl -tag -width /dev/video0 -compact +.It Pa /dev/video0 +video device +.El +.Sh SEE ALSO +.Xr video 4 +.Sh HISTORY +The +.Nm +command first appeared in +.Nx 6.0 . diff --git a/usr.bin/videoctl/videoctl.c b/usr.bin/videoctl/videoctl.c new file mode 100644 index 000000000000..ddecd509767c --- /dev/null +++ b/usr.bin/videoctl/videoctl.c @@ -0,0 +1,627 @@ +/* $NetBSD: videoctl.c,v 1.1 2010/12/26 10:37:15 jmcneill Exp $ */ + +/*- + * Copyright (c) 2010 Jared D. McNeill + * 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 +__COPYRIGHT("@(#) Copyright (c) 2010\ + Jared D. McNeill . All rights reserved."); +__RCSID("$NetBSD: videoctl.c,v 1.1 2010/12/26 10:37:15 jmcneill Exp $"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; + case 'h': + 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; +}