/* $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;
}