blob: 89a9553db46682d90cb3a206d4643675a1b11c5c [file] [log] [blame]
#include <algorithm>
#include <cctype>
#include <cstring>
#include <list>
#include <map>
#include <vector>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <linux/media.h>
#include "v4l2-ctl.h"
#ifdef HAVE_SYS_KLOG_H
#include <sys/klog.h>
#endif
struct ctrl_subset {
unsigned offset[V4L2_CTRL_MAX_DIMS];
unsigned size[V4L2_CTRL_MAX_DIMS];
};
using class2ctrls_map = std::map<unsigned int, std::vector<struct v4l2_ext_control> >;
using ctrl_qmap = std::map<std::string, struct v4l2_query_ext_ctrl>;
static ctrl_qmap ctrl_str2q;
using ctrl_idmap = std::map<unsigned int, std::string>;
static ctrl_idmap ctrl_id2str;
using ctrl_subset_map = std::map<std::string, ctrl_subset>;
static ctrl_subset_map ctrl_subsets;
using ctrl_get_list = std::list<std::string>;
static ctrl_get_list get_ctrls;
using ctrl_set_pair = std::pair<std::string, std::string>;
using ctrl_set_list = std::list<ctrl_set_pair>;
static ctrl_set_list set_ctrls;
using dev_vec = std::vector<std::string>;
using dev_map = std::map<std::string, std::string>;
static enum v4l2_priority prio = V4L2_PRIORITY_UNSET;
static bool have_query_ext_ctrl;
void common_usage()
{
printf("\nGeneral/Common options:\n"
" --all display all information available\n"
" -C, --get-ctrl <ctrl>[,<ctrl>...]\n"
" get the value of the controls [VIDIOC_G_EXT_CTRLS]\n"
" -c, --set-ctrl <ctrl>=<val>[,<ctrl>=<val>...]\n"
" set the value of the controls [VIDIOC_S_EXT_CTRLS]\n"
" -D, --info show driver info [VIDIOC_QUERYCAP]\n"
" -d, --device <dev> use device <dev> instead of /dev/video0\n"
" if <dev> starts with a digit, then /dev/video<dev> is used\n"
" Otherwise if -z was specified earlier, then <dev> is the entity name\n"
" or interface ID (if prefixed with 0x) as found in the topology of the\n"
" media device with the bus info string as specified by the -z option.\n"
" -e, --out-device <dev> use device <dev> for output streams instead of the\n"
" default device as set with --device\n"
" if <dev> starts with a digit, then /dev/video<dev> is used\n"
" Otherwise if -z was specified earlier, then <dev> is the entity name\n"
" or interface ID (if prefixed with 0x) as found in the topology of the\n"
" media device with the bus info string as specified by the -z option.\n"
" -E, --export-device <dev> use device <dev> for exporting DMA buffers\n"
" if <dev> starts with a digit, then /dev/video<dev> is used\n"
" Otherwise if -z was specified earlier, then <dev> is the entity name\n"
" or interface ID (if prefixed with 0x) as found in the topology of the\n"
" media device with the bus info string as specified by the -z option.\n"
" -z, --media-bus-info <bus-info>\n"
" find the media device with the given bus info string. If set, then\n"
" -d, -e and -E options can use the entity name or interface ID to refer\n"
" to the device nodes.\n"
" -h, --help display this help message\n"
" --help-all all options\n"
" --help-io input/output options\n"
" --help-meta metadata format options\n"
" --help-misc miscellaneous options\n"
" --help-overlay overlay format options\n"
" --help-sdr SDR format options\n"
" --help-selection crop/selection options\n"
" --help-stds standards and other video timings options\n"
" --help-streaming streaming options\n"
" --help-subdev sub-device options\n"
" --help-tuner tuner/modulator options\n"
" --help-vbi VBI format options\n"
" --help-vidcap video capture format options\n"
" --help-vidout vidout output format options\n"
" --help-edid edid handling options\n"
" -k, --concise be more concise if possible.\n"
" -l, --list-ctrls display all controls and their values [VIDIOC_QUERYCTRL]\n"
" -L, --list-ctrls-menus\n"
" display all controls and their menus [VIDIOC_QUERYMENU]\n"
" -r, --subset <ctrl>[,<offset>,<size>]+\n"
" the subset of the N-dimensional array to get/set for control <ctrl>,\n"
" for every dimension an (<offset>, <size>) tuple is given.\n"
#ifndef NO_LIBV4L2
" -w, --wrapper use the libv4l2 wrapper library.\n"
#endif
" --list-devices list all v4l devices. If -z was given, then list just the\n"
" devices of the media device with the bus info string as\n"
" specified by the -z option.\n"
" --log-status log the board status in the kernel log [VIDIOC_LOG_STATUS]\n"
" --get-priority query the current access priority [VIDIOC_G_PRIORITY]\n"
" --set-priority <prio>\n"
" set the new access priority [VIDIOC_S_PRIORITY]\n"
" <prio> is 1 (background), 2 (interactive) or 3 (record)\n"
" --silent only set the result code, do not print any messages\n"
" --sleep <secs> sleep <secs>, call QUERYCAP and close the file handle\n"
" --verbose turn on verbose ioctl status reporting\n"
" --version show version information\n"
);
}
static const char *prefixes[] = {
"video",
"radio",
"vbi",
"swradio",
"v4l-subdev",
"v4l-touch",
"media",
nullptr
};
static bool is_v4l_dev(const char *name)
{
for (unsigned i = 0; prefixes[i]; i++) {
unsigned l = strlen(prefixes[i]);
if (!memcmp(name, prefixes[i], l)) {
if (isdigit(name[l]))
return true;
}
}
return false;
}
static int calc_node_val(const char *s)
{
int n = 0;
s = std::strrchr(s, '/') + 1;
for (unsigned i = 0; prefixes[i]; i++) {
unsigned l = strlen(prefixes[i]);
if (!memcmp(s, prefixes[i], l)) {
n = i << 8;
n += atol(s + l);
return n;
}
}
return 0;
}
static bool sort_on_device_name(const std::string &s1, const std::string &s2)
{
int n1 = calc_node_val(s1.c_str());
int n2 = calc_node_val(s2.c_str());
return n1 < n2;
}
static void list_media_devices(const std::string &media_bus_info)
{
DIR *dp;
struct dirent *ep;
int media_fd = -1;
std::map<dev_t, std::string> devices;
dp = opendir("/dev");
if (dp == nullptr) {
perror ("Couldn't open the directory");
return;
}
while ((ep = readdir(dp))) {
std::string s("/dev/");
s += ep->d_name;
if (memcmp(ep->d_name, "media", 5)) {
if (!is_v4l_dev(ep->d_name))
continue;
struct stat st;
if (stat(s.c_str(), &st))
continue;
devices[st.st_rdev] = s;
continue;
}
int fd = open(s.c_str(), O_RDWR);
if (fd < 0)
continue;
struct media_device_info mdi;
if (!ioctl(fd, MEDIA_IOC_DEVICE_INFO, &mdi) &&
media_bus_info == mdi.bus_info) {
media_fd = fd;
printf("%s\n", s.c_str());
} else {
close(fd);
}
}
closedir(dp);
if (media_fd < 0)
return;
media_v2_topology topology;
memset(&topology, 0, sizeof(topology));
if (ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology)) {
close(media_fd);
return;
}
auto ifaces = new media_v2_interface[topology.num_interfaces];
topology.ptr_interfaces = (uintptr_t)ifaces;
if (!ioctl(media_fd, MEDIA_IOC_G_TOPOLOGY, &topology))
for (unsigned i = 0; i < topology.num_interfaces; i++) {
dev_t dev = makedev(ifaces[i].devnode.major,
ifaces[i].devnode.minor);
if (devices.find(dev) != devices.end())
printf("%s\n", devices[dev].c_str());
}
close(media_fd);
}
static void list_devices()
{
DIR *dp;
struct dirent *ep;
dev_vec files;
dev_map links;
dev_map cards;
struct v4l2_capability vcap;
dp = opendir("/dev");
if (dp == nullptr) {
perror ("Couldn't open the directory");
return;
}
while ((ep = readdir(dp)))
if (is_v4l_dev(ep->d_name))
files.push_back(std::string("/dev/") + ep->d_name);
closedir(dp);
/* Find device nodes which are links to other device nodes */
for (auto iter = files.begin();
iter != files.end(); ) {
char link[64+1];
int link_len;
std::string target;
link_len = readlink(iter->c_str(), link, 64);
if (link_len < 0) { /* Not a link or error */
iter++;
continue;
}
link[link_len] = '\0';
/* Only remove from files list if target itself is in list */
if (link[0] != '/') /* Relative link */
target = std::string("/dev/");
target += link;
if (find(files.begin(), files.end(), target) == files.end()) {
iter++;
continue;
}
/* Move the device node from files to links */
if (links[target].empty())
links[target] = *iter;
else
links[target] += ", " + *iter;
iter = files.erase(iter);
}
std::sort(files.begin(), files.end(), sort_on_device_name);
for (const auto &file : files) {
int fd = open(file.c_str(), O_RDWR);
std::string bus_info;
std::string card;
if (fd < 0)
continue;
int err = ioctl(fd, VIDIOC_QUERYCAP, &vcap);
if (err) {
struct media_device_info mdi;
err = ioctl(fd, MEDIA_IOC_DEVICE_INFO, &mdi);
if (!err) {
if (mdi.bus_info[0])
bus_info = mdi.bus_info;
else
bus_info = std::string("platform:") + mdi.driver;
if (mdi.model[0])
card = mdi.model;
else
card = mdi.driver;
}
} else {
bus_info = reinterpret_cast<const char *>(vcap.bus_info);
card = reinterpret_cast<const char *>(vcap.card);
}
close(fd);
if (err)
continue;
if (cards[bus_info].empty())
cards[bus_info] += card + " (" + bus_info + "):\n";
cards[bus_info] += "\t" + file;
if (!(links[file].empty()))
cards[bus_info] += " <- " + links[file];
cards[bus_info] += "\n";
}
for (const auto &card : cards) {
printf("%s\n", card.second.c_str());
}
}
static std::string name2var(const char *name)
{
std::string s;
int add_underscore = 0;
while (*name) {
if (isalnum(*name)) {
if (add_underscore)
s += '_';
add_underscore = 0;
s += std::string(1, tolower(*name));
}
else if (s.length()) add_underscore = 1;
name++;
}
return s;
}
static std::string safename(const unsigned char *name)
{
std::string s;
while (*name) {
if (*name == '\n') {
s += "\\n";
}
else if (*name == '\r') {
s += "\\r";
}
else if (*name == '\f') {
s += "\\f";
}
else if (*name == '\\') {
s += "\\\\";
}
else if ((*name & 0x7f) < 0x20) {
char buf[3];
sprintf(buf, "%02x", *name);
s += "\\x";
s += buf;
}
else {
s += *name;
}
name++;
}
return s;
}
static std::string safename(const char *name)
{
return safename(reinterpret_cast<const unsigned char *>(name));
}
static bool fill_subset(const struct v4l2_query_ext_ctrl &qc, ctrl_subset &subset)
{
unsigned d;
if (qc.nr_of_dims == 0)
return false;
for (d = 0; d < qc.nr_of_dims; d++) {
subset.offset[d] = 0;
subset.size[d] = qc.dims[d];
}
std::string s = name2var(qc.name);
if (ctrl_subsets.find(s) != ctrl_subsets.end()) {
unsigned ss_dims;
subset = ctrl_subsets[s];
for (ss_dims = 0; ss_dims < V4L2_CTRL_MAX_DIMS && subset.size[ss_dims]; ss_dims++) ;
if (ss_dims != qc.nr_of_dims) {
fprintf(stderr, "expected %d dimensions but --subset specified %d\n",
qc.nr_of_dims, ss_dims);
return true;
}
for (d = 0; d < qc.nr_of_dims; d++) {
if (subset.offset[d] + subset.size[d] > qc.dims[d]) {
fprintf(stderr, "the subset offset+size for dimension %d is out of range\n", d);
return true;
}
}
}
return false;
}
static void print_array(const v4l2_query_ext_ctrl &qc, const v4l2_ext_control &ctrl,
const ctrl_subset &subset)
{
std::string &name = ctrl_id2str[qc.id];
unsigned divide[V4L2_CTRL_MAX_DIMS] = { 0 };
unsigned from, to;
unsigned d, i;
divide[qc.nr_of_dims - 1] = 1;
for (d = 0; d < qc.nr_of_dims - 1; d++) {
divide[d] = qc.dims[d + 1];
for (i = 0; i < d; i++)
divide[i] *= divide[d];
}
from = subset.offset[qc.nr_of_dims - 1];
to = subset.offset[qc.nr_of_dims - 1] + subset.size[qc.nr_of_dims - 1] - 1;
for (unsigned idx = 0; idx < qc.elems; idx += qc.dims[qc.nr_of_dims - 1]) {
for (d = 0; d < qc.nr_of_dims - 1; d++) {
unsigned i = (idx / divide[d]) % qc.dims[d];
if (i < subset.offset[d] || i >= subset.offset[d] + subset.size[d])
break;
}
if (d < qc.nr_of_dims - 1)
continue;
printf("%s", name.c_str());
for (d = 0; d < qc.nr_of_dims - 1; d++)
printf("[%u]", (idx / divide[d]) % qc.dims[d]);
printf(": ");
switch (qc.type) {
case V4L2_CTRL_TYPE_U8:
for (i = from; i <= to; i++) {
printf("%4u", ctrl.p_u8[idx + i]);
if (i < to)
printf(", ");
}
printf("\n");
break;
case V4L2_CTRL_TYPE_U16:
for (i = from; i <= to; i++) {
printf("%6u", ctrl.p_u16[idx + i]);
if (i < to)
printf(", ");
}
printf("\n");
break;
case V4L2_CTRL_TYPE_U32:
for (i = from; i <= to; i++) {
printf("%10u", ctrl.p_u32[idx + i]);
if (i < to)
printf(", ");
}
printf("\n");
break;
}
}
}
static void print_value(int fd, const v4l2_query_ext_ctrl &qc, const v4l2_ext_control &ctrl,
bool show_payload, bool fill_in_subset)
{
v4l2_querymenu qmenu;
if (qc.nr_of_dims) {
if (!show_payload)
return;
ctrl_subset subset;
if (fill_in_subset) {
if (fill_subset(qc, subset))
return;
} else {
memset(&subset, 0, sizeof(subset));
for (unsigned i = 0; i < qc.nr_of_dims; i++)
subset.size[i] = qc.dims[i];
}
print_array(qc, ctrl, subset);
return;
}
if (qc.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
switch (qc.type) {
case V4L2_CTRL_TYPE_U8:
printf("%u", *ctrl.p_u8);
break;
case V4L2_CTRL_TYPE_U16:
printf("%u", *ctrl.p_u16);
break;
case V4L2_CTRL_TYPE_U32:
printf("%u", *ctrl.p_u32);
break;
case V4L2_CTRL_TYPE_STRING:
printf("'%s'", safename(ctrl.string).c_str());
break;
case V4L2_CTRL_TYPE_AREA:
printf("%dx%d", ctrl.p_area->width, ctrl.p_area->height);
break;
default:
printf("unsupported payload type");
break;
}
return;
}
switch (qc.type) {
case V4L2_CTRL_TYPE_INTEGER64:
printf("%lld", ctrl.value64);
break;
case V4L2_CTRL_TYPE_MENU:
case V4L2_CTRL_TYPE_INTEGER_MENU:
memset(&qmenu, 0, sizeof(qmenu));
qmenu.id = ctrl.id;
qmenu.index = ctrl.value;
if (test_ioctl(fd, VIDIOC_QUERYMENU, &qmenu))
printf("%d", ctrl.value);
else if (qc.type == V4L2_CTRL_TYPE_MENU)
printf("%d (%s)", ctrl.value, qmenu.name);
else
printf("%d (%lld 0x%llx)", ctrl.value, qmenu.value, qmenu.value);
break;
default:
printf("%d", ctrl.value);
break;
}
}
static void print_qctrl(int fd, const v4l2_query_ext_ctrl &qc,
const v4l2_ext_control &ctrl, bool show_menus)
{
std::string s = name2var(qc.name);
unsigned i;
switch (qc.type) {
case V4L2_CTRL_TYPE_INTEGER:
printf("%31s %#8.8x (int) : min=%lld max=%lld step=%lld default=%lld",
s.c_str(), qc.id, qc.minimum, qc.maximum,
qc.step, qc.default_value);
break;
case V4L2_CTRL_TYPE_INTEGER64:
printf("%31s %#8.8x (int64) : min=%lld max=%lld step=%lld default=%lld",
s.c_str(), qc.id, qc.minimum, qc.maximum,
qc.step, qc.default_value);
break;
case V4L2_CTRL_TYPE_STRING:
printf("%31s %#8.8x (str) : min=%lld max=%lld step=%lld",
s.c_str(), qc.id, qc.minimum, qc.maximum, qc.step);
break;
case V4L2_CTRL_TYPE_BOOLEAN:
printf("%31s %#8.8x (bool) : default=%lld",
s.c_str(), qc.id, qc.default_value);
break;
case V4L2_CTRL_TYPE_MENU:
printf("%31s %#8.8x (menu) : min=%lld max=%lld default=%lld",
s.c_str(), qc.id, qc.minimum, qc.maximum,
qc.default_value);
break;
case V4L2_CTRL_TYPE_INTEGER_MENU:
printf("%31s %#8.8x (intmenu): min=%lld max=%lld default=%lld",
s.c_str(), qc.id, qc.minimum, qc.maximum,
qc.default_value);
break;
case V4L2_CTRL_TYPE_BUTTON:
printf("%31s %#8.8x (button) :", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_BITMASK:
printf("%31s %#8.8x (bitmask): max=0x%08llx default=0x%08llx",
s.c_str(), qc.id, qc.maximum, qc.default_value);
break;
case V4L2_CTRL_TYPE_U8:
printf("%31s %#8.8x (u8) : min=%lld max=%lld step=%lld default=%lld",
s.c_str(), qc.id, qc.minimum, qc.maximum,
qc.step, qc.default_value);
break;
case V4L2_CTRL_TYPE_U16:
printf("%31s %#8.8x (u16) : min=%lld max=%lld step=%lld default=%lld",
s.c_str(), qc.id, qc.minimum, qc.maximum,
qc.step, qc.default_value);
break;
case V4L2_CTRL_TYPE_U32:
printf("%31s %#8.8x (u32) : min=%lld max=%lld step=%lld default=%lld",
s.c_str(), qc.id, qc.minimum, qc.maximum,
qc.step, qc.default_value);
break;
case V4L2_CTRL_TYPE_AREA:
printf("%31s %#8.8x (area) :", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_HDR10_CLL_INFO:
printf("%31s %#8.8x (hdr10-cll-info):", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_HDR10_MASTERING_DISPLAY:
printf("%31s %#8.8x (hdr10-mastering-display):", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_H264_SPS:
printf("%31s %#8.8x (h264-sps):", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_H264_PPS:
printf("%31s %#8.8x (h264-pps):", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_H264_SCALING_MATRIX:
printf("%31s %#8.8x (h264-scaling-matrix):", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_H264_SLICE_PARAMS:
printf("%31s %#8.8x (h264-slice-params):", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_H264_DECODE_PARAMS:
printf("%31s %#8.8x (h264-decode-params):", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_H264_PRED_WEIGHTS:
printf("%31s %#8.8x (h264-pred-weights):", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_VP8_FRAME:
printf("%31s %#8.8x (vp8-frame):", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_VP9_FRAME:
printf("%31s %#8.8x (vp9-frame):", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_MPEG2_QUANTISATION:
printf("%31s %#8.8x (mpeg2-quantisation):", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_MPEG2_SEQUENCE:
printf("%31s %#8.8x (mpeg2-sequence):", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_MPEG2_PICTURE:
printf("%31s %#8.8x (mpeg2-picture):", s.c_str(), qc.id);
break;
case V4L2_CTRL_TYPE_FWHT_PARAMS:
printf("%31s %#8.8x (fwht-params):", s.c_str(), qc.id);
break;
default:
printf("%31s %#8.8x (unknown): type=%x",
s.c_str(), qc.id, qc.type);
break;
}
if (qc.nr_of_dims == 0) {
printf(" value=");
print_value(fd, qc, ctrl, false, false);
} else {
printf(" dims=");
for (i = 0; i < qc.nr_of_dims; i++)
printf("[%u]", qc.dims[i]);
}
if (qc.flags)
printf(" flags=%s", ctrlflags2s(qc.flags).c_str());
printf("\n");
if ((qc.type == V4L2_CTRL_TYPE_MENU ||
qc.type == V4L2_CTRL_TYPE_INTEGER_MENU) && show_menus) {
v4l2_querymenu qmenu;
memset(&qmenu, 0, sizeof(qmenu));
qmenu.id = ctrl.id;
for (i = qc.minimum; i <= qc.maximum; i++) {
qmenu.index = i;
if (test_ioctl(fd, VIDIOC_QUERYMENU, &qmenu))
continue;
if (qc.type == V4L2_CTRL_TYPE_MENU)
printf("\t\t\t\t%d: %s\n", i, qmenu.name);
else
printf("\t\t\t\t%d: %lld (0x%llx)\n", i, qmenu.value, qmenu.value);
}
}
}
static int print_control(int fd, struct v4l2_query_ext_ctrl &qctrl, bool show_menus)
{
struct v4l2_control ctrl;
struct v4l2_ext_control ext_ctrl;
struct v4l2_ext_controls ctrls;
memset(&ctrl, 0, sizeof(ctrl));
memset(&ext_ctrl, 0, sizeof(ext_ctrl));
memset(&ctrls, 0, sizeof(ctrls));
if (qctrl.flags & V4L2_CTRL_FLAG_DISABLED)
return 1;
if (qctrl.type == V4L2_CTRL_TYPE_CTRL_CLASS) {
printf("\n%s\n\n", qctrl.name);
return 1;
}
ext_ctrl.id = qctrl.id;
if ((qctrl.flags & V4L2_CTRL_FLAG_WRITE_ONLY) ||
qctrl.type == V4L2_CTRL_TYPE_BUTTON) {
print_qctrl(fd, qctrl, ext_ctrl, show_menus);
return 1;
}
ctrls.which = V4L2_CTRL_ID2WHICH(qctrl.id);
ctrls.count = 1;
ctrls.controls = &ext_ctrl;
if (qctrl.type == V4L2_CTRL_TYPE_INTEGER64 ||
qctrl.type == V4L2_CTRL_TYPE_STRING ||
qctrl.type >= V4L2_CTRL_COMPOUND_TYPES ||
(V4L2_CTRL_ID2WHICH(qctrl.id) != V4L2_CTRL_CLASS_USER &&
qctrl.id < V4L2_CID_PRIVATE_BASE)) {
if (qctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
ext_ctrl.size = qctrl.elems * qctrl.elem_size;
ext_ctrl.ptr = calloc(1, ext_ctrl.size);
}
if (test_ioctl(fd, VIDIOC_G_EXT_CTRLS, &ctrls)) {
printf("error %d getting ext_ctrl %s\n",
errno, qctrl.name);
return 0;
}
}
else {
ctrl.id = qctrl.id;
if (test_ioctl(fd, VIDIOC_G_CTRL, &ctrl)) {
printf("error %d getting ctrl %s\n",
errno, qctrl.name);
return 0;
}
ext_ctrl.value = ctrl.value;
}
print_qctrl(fd, qctrl, ext_ctrl, show_menus);
if (qctrl.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD)
free(ext_ctrl.ptr);
return 1;
}
static int query_ext_ctrl_ioctl(int fd, struct v4l2_query_ext_ctrl &qctrl)
{
struct v4l2_queryctrl qc;
int rc;
if (have_query_ext_ctrl) {
rc = test_ioctl(fd, VIDIOC_QUERY_EXT_CTRL, &qctrl);
if (rc != ENOTTY)
return rc;
}
qc.id = qctrl.id;
rc = test_ioctl(fd, VIDIOC_QUERYCTRL, &qc);
if (rc == 0) {
qctrl.type = qc.type;
memcpy(qctrl.name, qc.name, sizeof(qctrl.name));
qctrl.minimum = qc.minimum;
if (qc.type == V4L2_CTRL_TYPE_BITMASK) {
qctrl.maximum = static_cast<__u32>(qc.maximum);
qctrl.default_value = static_cast<__u32>(qc.default_value);
} else {
qctrl.maximum = qc.maximum;
qctrl.default_value = qc.default_value;
}
qctrl.step = qc.step;
qctrl.flags = qc.flags;
qctrl.elems = 1;
qctrl.nr_of_dims = 0;
memset(qctrl.dims, 0, sizeof(qctrl.dims));
switch (qctrl.type) {
case V4L2_CTRL_TYPE_INTEGER64:
qctrl.elem_size = sizeof(__s64);
break;
case V4L2_CTRL_TYPE_STRING:
qctrl.elem_size = qc.maximum + 1;
qctrl.flags |= V4L2_CTRL_FLAG_HAS_PAYLOAD;
break;
default:
qctrl.elem_size = sizeof(__s32);
break;
}
memset(qctrl.reserved, 0, sizeof(qctrl.reserved));
}
qctrl.id = qc.id;
return rc;
}
static void list_controls(int fd, bool show_menus)
{
const unsigned next_fl = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
struct v4l2_query_ext_ctrl qctrl;
int id;
memset(&qctrl, 0, sizeof(qctrl));
qctrl.id = next_fl;
while (query_ext_ctrl_ioctl(fd, qctrl) == 0) {
print_control(fd, qctrl, show_menus);
qctrl.id |= next_fl;
}
if (qctrl.id != next_fl)
return;
for (id = V4L2_CID_USER_BASE; id < V4L2_CID_LASTP1; id++) {
qctrl.id = id;
if (query_ext_ctrl_ioctl(fd, qctrl) == 0)
print_control(fd, qctrl, show_menus);
}
for (qctrl.id = V4L2_CID_PRIVATE_BASE;
query_ext_ctrl_ioctl(fd, qctrl) == 0; qctrl.id++) {
print_control(fd, qctrl, show_menus);
}
}
static void find_controls(cv4l_fd &_fd)
{
const unsigned next_fl = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
struct v4l2_query_ext_ctrl qctrl;
int fd = _fd.g_fd();
int id;
memset(&qctrl, 0, sizeof(qctrl));
qctrl.id = next_fl;
while (query_ext_ctrl_ioctl(fd, qctrl) == 0) {
if (qctrl.type != V4L2_CTRL_TYPE_CTRL_CLASS &&
!(qctrl.flags & V4L2_CTRL_FLAG_DISABLED)) {
ctrl_str2q[name2var(qctrl.name)] = qctrl;
ctrl_id2str[qctrl.id] = name2var(qctrl.name);
}
qctrl.id |= next_fl;
}
if (qctrl.id != next_fl)
return;
for (id = V4L2_CID_USER_BASE; id < V4L2_CID_LASTP1; id++) {
qctrl.id = id;
if (query_ext_ctrl_ioctl(fd, qctrl) == 0 &&
!(qctrl.flags & V4L2_CTRL_FLAG_DISABLED)) {
ctrl_str2q[name2var(qctrl.name)] = qctrl;
ctrl_id2str[qctrl.id] = name2var(qctrl.name);
}
}
for (qctrl.id = V4L2_CID_PRIVATE_BASE;
query_ext_ctrl_ioctl(fd, qctrl) == 0; qctrl.id++) {
if (!(qctrl.flags & V4L2_CTRL_FLAG_DISABLED)) {
ctrl_str2q[name2var(qctrl.name)] = qctrl;
ctrl_id2str[qctrl.id] = name2var(qctrl.name);
}
}
}
int common_find_ctrl_id(const char *name)
{
if (ctrl_str2q.find(name) == ctrl_str2q.end())
return 0;
return ctrl_str2q[name].id;
}
void common_process_controls(cv4l_fd &fd)
{
struct v4l2_query_ext_ctrl qc = {
V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND
};
int rc;
rc = test_ioctl(fd.g_fd(), VIDIOC_QUERY_EXT_CTRL, &qc);
have_query_ext_ctrl = rc == 0;
find_controls(fd);
for (const auto &get_ctrl : get_ctrls) {
std::string s = get_ctrl;
if (isdigit(s[0])) {
__u32 id = strtoul(s.c_str(), nullptr, 0);
if (ctrl_id2str.find(id) != ctrl_id2str.end())
s = ctrl_id2str[id];
}
if (ctrl_str2q.find(s) == ctrl_str2q.end()) {
fprintf(stderr, "unknown control '%s'\n", s.c_str());
std::exit(EXIT_FAILURE);
}
}
for (const auto &set_ctrl : set_ctrls) {
std::string s = set_ctrl.first;
if (isdigit(s[0])) {
__u32 id = strtoul(s.c_str(), nullptr, 0);
if (ctrl_id2str.find(id) != ctrl_id2str.end())
s = ctrl_id2str[id];
}
if (ctrl_str2q.find(s) == ctrl_str2q.end()) {
fprintf(stderr, "unknown control '%s'\n", s.c_str());
std::exit(EXIT_FAILURE);
}
}
}
void common_control_event(const struct v4l2_event *ev)
{
const struct v4l2_event_ctrl *ctrl;
ctrl = &ev->u.ctrl;
printf("ctrl: %s\n", ctrl_id2str[ev->id].c_str());
if (ctrl->changes & V4L2_EVENT_CTRL_CH_VALUE) {
if (ctrl->type == V4L2_CTRL_TYPE_INTEGER64)
printf("\tvalue: %lld 0x%llx\n", ctrl->value64, ctrl->value64);
else if (ctrl->type == V4L2_CTRL_TYPE_BITMASK)
printf("\tvalue: %u 0x%08x\n", ctrl->value, ctrl->value);
else
printf("\tvalue: %d 0x%x\n", ctrl->value, ctrl->value);
}
if (ctrl->changes & V4L2_EVENT_CTRL_CH_FLAGS)
printf("\tflags: %s\n", ctrlflags2s(ctrl->flags).c_str());
if (ctrl->changes & V4L2_EVENT_CTRL_CH_RANGE) {
if (ctrl->type == V4L2_CTRL_TYPE_BITMASK)
printf("\trange: max=0x%08x default=0x%08x\n",
ctrl->maximum, ctrl->default_value);
else
printf("\trange: min=%d max=%d step=%d default=%d\n",
ctrl->minimum, ctrl->maximum, ctrl->step, ctrl->default_value);
}
}
static bool parse_subset(char *optarg)
{
struct ctrl_subset subset;
std::string ctrl_name;
unsigned idx = 0;
char *p;
memset(&subset, 0, sizeof(subset));
while (*optarg) {
p = std::strchr(optarg, ',');
if (p)
*p = 0;
if (optarg[0] == 0) {
fprintf(stderr, "empty string\n");
return true;
}
if (idx == 0) {
ctrl_name = optarg;
} else {
if (idx > V4L2_CTRL_MAX_DIMS * 2) {
fprintf(stderr, "too many dimensions\n");
return true;
}
if (idx & 1)
subset.offset[idx / 2] = strtoul(optarg, nullptr, 0);
else {
subset.size[idx / 2 - 1] = strtoul(optarg, nullptr, 0);
if (subset.size[idx / 2 - 1] == 0) {
fprintf(stderr, "<size> cannot be 0\n");
return true;
}
}
}
idx++;
if (p == nullptr)
break;
optarg = p + 1;
}
if (idx == 1) {
fprintf(stderr, "no <offset>, <size> tuples given\n");
return true;
}
if ((idx & 1) == 0) {
fprintf(stderr, "<offset> without <size>\n");
return true;
}
ctrl_subsets[ctrl_name] = subset;
return false;
}
static bool parse_next_subopt(char **subs, char **value)
{
static char *const subopts[] = {
nullptr
};
int opt = v4l_getsubopt(subs, subopts, value);
if (opt < 0 || *value)
return false;
fprintf(stderr, "Missing suboption value\n");
return true;
}
void common_cmd(const std::string &media_bus_info, int ch, char *optarg)
{
char *value, *subs;
switch (ch) {
case OptGetCtrl:
subs = optarg;
while (*subs != '\0') {
if (parse_next_subopt(&subs, &value)) {
common_usage();
std::exit(EXIT_FAILURE);
}
if (std::strchr(value, '=')) {
common_usage();
std::exit(EXIT_FAILURE);
}
else {
get_ctrls.push_back(value);
}
}
break;
case OptSetCtrl:
subs = optarg;
while (*subs != '\0') {
if (parse_next_subopt(&subs, &value)) {
common_usage();
std::exit(EXIT_FAILURE);
}
if (const char *equal = std::strchr(value, '=')) {
set_ctrls.emplace_back(std::string(value, (equal - value)),
std::string(equal + 1));
}
else {
fprintf(stderr, "control '%s' without '='\n", value);
std::exit(EXIT_FAILURE);
}
}
break;
case OptSubset:
if (parse_subset(optarg)) {
common_usage();
std::exit(EXIT_FAILURE);
}
break;
case OptSetPriority:
prio = static_cast<enum v4l2_priority>(strtoul(optarg, nullptr, 0));
break;
case OptListDevices:
if (media_bus_info.empty())
list_devices();
else
list_media_devices(media_bus_info);
break;
}
}
static bool idx_in_subset(const struct v4l2_query_ext_ctrl &qc, const ctrl_subset &subset,
const unsigned *divide, unsigned idx)
{
for (unsigned d = 0; d < qc.nr_of_dims; d++) {
unsigned i = (idx / divide[d]) % qc.dims[d];
if (i < subset.offset[d] || i >= subset.offset[d] + subset.size[d])
return false;
}
return true;
}
void common_set(cv4l_fd &_fd)
{
int fd = _fd.g_fd();
if (options[OptSetPriority]) {
if (doioctl(fd, VIDIOC_S_PRIORITY, &prio) >= 0) {
printf("Priority set: %d\n", prio);
}
}
if (options[OptSetCtrl] && !set_ctrls.empty()) {
struct v4l2_ext_controls ctrls;
class2ctrls_map class2ctrls;
bool use_ext_ctrls = false;
memset(&ctrls, 0, sizeof(ctrls));
for (const auto &set_ctrl : set_ctrls) {
std::string s = set_ctrl.first;
if (isdigit(s[0])) {
__u32 id = strtoul(s.c_str(), nullptr, 0);
s = ctrl_id2str[id];
}
struct v4l2_ext_control ctrl;
struct v4l2_query_ext_ctrl &qc = ctrl_str2q[s];
memset(&ctrl, 0, sizeof(ctrl));
ctrl.id = qc.id;
if (qc.type == V4L2_CTRL_TYPE_INTEGER64 ||
qc.flags & V4L2_CTRL_FLAG_UPDATE)
use_ext_ctrls = true;
if (qc.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
struct v4l2_ext_controls ctrls = { { 0 }, 1 };
unsigned divide[V4L2_CTRL_MAX_DIMS] = { 0 };
ctrl_subset subset;
long long v;
unsigned d, i;
use_ext_ctrls = true;
ctrl.size = qc.elems * qc.elem_size;
ctrl.ptr = malloc(ctrl.size);
ctrls.controls = &ctrl;
ioctl(fd, VIDIOC_G_EXT_CTRLS, &ctrls);
if (fill_subset(qc, subset))
return;
if (qc.nr_of_dims) {
divide[qc.nr_of_dims - 1] = 1;
for (d = 0; d < qc.nr_of_dims - 1; d++) {
divide[d] = qc.dims[d + 1];
for (i = 0; i < d; i++)
divide[i] *= divide[d];
}
}
switch (qc.type) {
case V4L2_CTRL_TYPE_U8:
v = strtoul(set_ctrl.second.c_str(), nullptr, 0);
for (i = 0; i < qc.elems; i++)
if (idx_in_subset(qc, subset, divide, i))
ctrl.p_u8[i] = v;
break;
case V4L2_CTRL_TYPE_U16:
v = strtoul(set_ctrl.second.c_str(), nullptr, 0);
for (i = 0; i < qc.elems; i++)
if (idx_in_subset(qc, subset, divide, i))
ctrl.p_u16[i] = v;
break;
case V4L2_CTRL_TYPE_U32:
v = strtoul(set_ctrl.second.c_str(), nullptr, 0);
for (i = 0; i < qc.elems; i++)
if (idx_in_subset(qc, subset, divide, i))
ctrl.p_u32[i] = v;
break;
case V4L2_CTRL_TYPE_STRING:
strncpy(ctrl.string, set_ctrl.second.c_str(), qc.maximum);
ctrl.string[qc.maximum] = 0;
break;
case V4L2_CTRL_TYPE_AREA:
sscanf(set_ctrl.second.c_str(), "%ux%u",
&ctrl.p_area->width, &ctrl.p_area->height);
break;
default:
fprintf(stderr, "%s: unsupported payload type\n",
qc.name);
break;
}
} else {
if (V4L2_CTRL_DRIVER_PRIV(ctrl.id))
use_ext_ctrls = true;
if (qc.type == V4L2_CTRL_TYPE_INTEGER64)
ctrl.value64 = strtoll(set_ctrl.second.c_str(), nullptr, 0);
else
ctrl.value = strtol(set_ctrl.second.c_str(), nullptr, 0);
}
class2ctrls[V4L2_CTRL_ID2WHICH(ctrl.id)].push_back(ctrl);
}
for (auto &class2ctrl : class2ctrls) {
if (!use_ext_ctrls &&
(class2ctrl.first == V4L2_CTRL_CLASS_USER ||
class2ctrl.first == V4L2_CID_PRIVATE_BASE)) {
for (const auto &i : class2ctrl.second) {
struct v4l2_control ctrl;
ctrl.id = i.id;
ctrl.value = i.value;
if (doioctl(fd, VIDIOC_S_CTRL, &ctrl)) {
fprintf(stderr, "%s: %s\n",
ctrl_id2str[ctrl.id].c_str(),
strerror(errno));
}
}
continue;
}
if (!class2ctrl.second.empty()) {
ctrls.which = class2ctrl.first;
ctrls.count = class2ctrl.second.size();
ctrls.controls = &class2ctrl.second[0];
if (doioctl(fd, VIDIOC_S_EXT_CTRLS, &ctrls)) {
if (ctrls.error_idx >= ctrls.count) {
fprintf(stderr, "Error setting controls: %s\n",
strerror(errno));
}
else {
fprintf(stderr, "%s: %s\n",
ctrl_id2str[class2ctrl.second[ctrls.error_idx].id].c_str(),
strerror(errno));
}
}
}
}
}
}
void common_get(cv4l_fd &_fd)
{
int fd = _fd.g_fd();
if (options[OptGetCtrl] && !get_ctrls.empty()) {
struct v4l2_ext_controls ctrls;
class2ctrls_map class2ctrls;
bool use_ext_ctrls = false;
memset(&ctrls, 0, sizeof(ctrls));
for (const auto &get_ctrl : get_ctrls) {
std::string s = get_ctrl;
if (isdigit(s[0])) {
__u32 id = strtoul(s.c_str(), nullptr, 0);
s = ctrl_id2str[id];
}
struct v4l2_ext_control ctrl;
struct v4l2_query_ext_ctrl &qc = ctrl_str2q[s];
memset(&ctrl, 0, sizeof(ctrl));
ctrl.id = qc.id;
if (qc.type == V4L2_CTRL_TYPE_INTEGER64 ||
qc.flags & V4L2_CTRL_FLAG_UPDATE)
use_ext_ctrls = true;
if (qc.flags & V4L2_CTRL_FLAG_HAS_PAYLOAD) {
use_ext_ctrls = true;
ctrl.size = qc.elems * qc.elem_size;
ctrl.ptr = calloc(1, ctrl.size);
}
if (V4L2_CTRL_DRIVER_PRIV(ctrl.id))
use_ext_ctrls = true;
class2ctrls[V4L2_CTRL_ID2WHICH(ctrl.id)].push_back(ctrl);
}
for (auto &class2ctrl : class2ctrls) {
if (!use_ext_ctrls &&
(class2ctrl.first == V4L2_CTRL_CLASS_USER ||
class2ctrl.first == V4L2_CID_PRIVATE_BASE)) {
for (const auto &i : class2ctrl.second) {
struct v4l2_control ctrl;
ctrl.id = i.id;
doioctl(fd, VIDIOC_G_CTRL, &ctrl);
printf("%s: %d\n", ctrl_id2str[ctrl.id].c_str(), ctrl.value);
}
continue;
}
if (!class2ctrl.second.empty()) {
ctrls.which = class2ctrl.first;
ctrls.count = class2ctrl.second.size();
ctrls.controls = &class2ctrl.second[0];
doioctl(fd, VIDIOC_G_EXT_CTRLS, &ctrls);
for (auto ctrl : class2ctrl.second) {
std::string &name = ctrl_id2str[ctrl.id];
struct v4l2_query_ext_ctrl &qc = ctrl_str2q[name];
if (qc.nr_of_dims) {
print_value(fd, qc, ctrl, true, true);
return;
}
printf("%s: ", name.c_str());
print_value(fd, qc, ctrl, true, false);
printf("\n");
}
}
}
}
if (options[OptGetPriority]) {
if (doioctl(fd, VIDIOC_G_PRIORITY, &prio) == 0)
printf("Priority: %d\n", prio);
}
if (options[OptLogStatus]) {
static char buf[40960];
int len = -1;
if (doioctl(fd, VIDIOC_LOG_STATUS, nullptr) == 0) {
printf("\nStatus Log:\n\n");
#ifdef HAVE_KLOGCTL
len = klogctl(3, buf, sizeof(buf) - 1);
#endif
if (len >= 0) {
char *p = buf;
char *q;
buf[len] = 0;
while ((q = strstr(p, "START STATUS"))) {
p = q + 1;
}
if (p) {
while (p > buf && *p != '<') p--;
q = p;
while ((q = strstr(q, "<6>"))) {
memcpy(q, " ", 3);
}
printf("%s", p);
}
}
}
}
}
void common_list(cv4l_fd &fd)
{
if (options[OptListCtrls] || options[OptListCtrlsMenus]) {
list_controls(fd.g_fd(), options[OptListCtrlsMenus]);
}
}