Devices typically have a number of user-settable controls such as brightness, saturation and so on, which would be presented to the user on a graphical user interface. But, different devices will have different controls available, and furthermore, the range of possible values, and the default value will vary from device to device. The control ioctls provide the information and a mechanism to create a nice user interface for these controls that will work correctly with any device.
All controls are accessed using an ID value. V4L2 defines several IDs for specific purposes. Drivers can also implement their own custom controls using V4L2_CID_PRIVATE_BASE [1] and higher values. The pre-defined control IDs have the prefix V4L2_CID_, and are listed in Control IDs. The ID is used when querying the attributes of a control, and when getting or setting the current value.
Generally applications should present controls to the user without assumptions about their purpose. Each control comes with a name string the user is supposed to understand. When the purpose is non-intuitive the driver writer should provide a user manual, a user interface plug-in or a driver specific panel application. Predefined IDs were introduced to change a few controls programmatically, for example to mute a device during a channel switch.
Drivers may enumerate different controls after switching the current video input or output, tuner or modulator, or audio input or output. Different in the sense of other bounds, another default and current value, step size or other menu items. A control with a certain custom ID can also change name and type.
If a control is not applicable to the current configuration of the device (for example, it doesn’t apply to the current video input) drivers set the V4L2_CTRL_FLAG_INACTIVE flag.
Control values are stored globally, they do not change when switching except to stay within the reported bounds. They also do not change e. g. when the device is opened or closed, when the tuner radio frequency is changed or generally never without application request.
V4L2 specifies an event mechanism to notify applications when controls change value (see ioctl VIDIOC_SUBSCRIBE_EVENT, VIDIOC_UNSUBSCRIBE_EVENT, event V4L2_EVENT_CTRL), panel applications might want to make use of that in order to always reflect the correct control value.
All controls use machine endianness.
V4L2_COLORFX_NONE | Color effect is disabled. |
V4L2_COLORFX_ANTIQUE | An aging (old photo) effect. |
V4L2_COLORFX_ART_FREEZE | Frost color effect. |
V4L2_COLORFX_AQUA | Water color, cool tone. |
V4L2_COLORFX_BW | Black and white. |
V4L2_COLORFX_EMBOSS | Emboss, the highlights and shadows replace light/dark boundaries and low contrast areas are set to a gray background. |
V4L2_COLORFX_GRASS_GREEN | Grass green. |
V4L2_COLORFX_NEGATIVE | Negative. |
V4L2_COLORFX_SEPIA | Sepia tone. |
V4L2_COLORFX_SKETCH | Sketch. |
V4L2_COLORFX_SKIN_WHITEN | Skin whiten. |
V4L2_COLORFX_SKY_BLUE | Sky blue. |
V4L2_COLORFX_SOLARIZATION | Solarization, the image is partially reversed in tone, only color values above or below a certain threshold are inverted. |
V4L2_COLORFX_SILHOUETTE | Silhouette (outline). |
V4L2_COLORFX_VIVID | Vivid colors. |
V4L2_COLORFX_SET_CBCR | The Cb and Cr chroma components are replaced by fixed coefficients determined by V4L2_CID_COLORFX_CBCR control. |
Applications can enumerate the available controls with the ioctls VIDIOC_QUERYCTRL, VIDIOC_QUERY_EXT_CTRL and VIDIOC_QUERYMENU and VIDIOC_QUERYMENU ioctls, get and set a control value with the VIDIOC_G_CTRL and VIDIOC_S_CTRL ioctls. Drivers must implement VIDIOC_QUERYCTRL, VIDIOC_G_CTRL and VIDIOC_S_CTRL when the device has one or more controls, VIDIOC_QUERYMENU when it has one or more menu type controls.
struct v4l2_queryctrl queryctrl;
struct v4l2_querymenu querymenu;
static void enumerate_menu(__u32 id)
{
printf(" Menu items:\\n");
memset(&querymenu, 0, sizeof(querymenu));
querymenu.id = id;
for (querymenu.index = queryctrl.minimum;
querymenu.index <= queryctrl.maximum;
querymenu.index++) {
if (0 == ioctl(fd, VIDIOC_QUERYMENU, &querymenu)) {
printf(" %s\\n", querymenu.name);
}
}
}
memset(&queryctrl, 0, sizeof(queryctrl));
queryctrl.id = V4L2_CTRL_FLAG_NEXT_CTRL;
while (0 == ioctl(fd, VIDIOC_QUERYCTRL, &queryctrl)) {
if (!(queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)) {
printf("Control %s\\n", queryctrl.name);
if (queryctrl.type == V4L2_CTRL_TYPE_MENU)
enumerate_menu(queryctrl.id);
}
queryctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
}
if (errno != EINVAL) {
perror("VIDIOC_QUERYCTRL");
exit(EXIT_FAILURE);
}
struct v4l2_query_ext_ctrl query_ext_ctrl;
memset(&query_ext_ctrl, 0, sizeof(query_ext_ctrl));
query_ext_ctrl.id = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
while (0 == ioctl(fd, VIDIOC_QUERY_EXT_CTRL, &query_ext_ctrl)) {
if (!(query_ext_ctrl.flags & V4L2_CTRL_FLAG_DISABLED)) {
printf("Control %s\\n", query_ext_ctrl.name);
if (query_ext_ctrl.type == V4L2_CTRL_TYPE_MENU)
enumerate_menu(query_ext_ctrl.id);
}
query_ext_ctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
}
if (errno != EINVAL) {
perror("VIDIOC_QUERY_EXT_CTRL");
exit(EXIT_FAILURE);
}
memset(&queryctrl, 0, sizeof(queryctrl));
for (queryctrl.id = V4L2_CID_BASE;
queryctrl.id < V4L2_CID_LASTP1;
queryctrl.id++) {
if (0 == ioctl(fd, VIDIOC_QUERYCTRL, &queryctrl)) {
if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)
continue;
printf("Control %s\\n", queryctrl.name);
if (queryctrl.type == V4L2_CTRL_TYPE_MENU)
enumerate_menu(queryctrl.id);
} else {
if (errno == EINVAL)
continue;
perror("VIDIOC_QUERYCTRL");
exit(EXIT_FAILURE);
}
}
for (queryctrl.id = V4L2_CID_PRIVATE_BASE;;
queryctrl.id++) {
if (0 == ioctl(fd, VIDIOC_QUERYCTRL, &queryctrl)) {
if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED)
continue;
printf("Control %s\\n", queryctrl.name);
if (queryctrl.type == V4L2_CTRL_TYPE_MENU)
enumerate_menu(queryctrl.id);
} else {
if (errno == EINVAL)
break;
perror("VIDIOC_QUERYCTRL");
exit(EXIT_FAILURE);
}
}
struct v4l2_queryctrl queryctrl;
struct v4l2_control control;
memset(&queryctrl, 0, sizeof(queryctrl));
queryctrl.id = V4L2_CID_BRIGHTNESS;
if (-1 == ioctl(fd, VIDIOC_QUERYCTRL, &queryctrl)) {
if (errno != EINVAL) {
perror("VIDIOC_QUERYCTRL");
exit(EXIT_FAILURE);
} else {
printf("V4L2_CID_BRIGHTNESS is not supportedn");
}
} else if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) {
printf("V4L2_CID_BRIGHTNESS is not supportedn");
} else {
memset(&control, 0, sizeof (control));
control.id = V4L2_CID_BRIGHTNESS;
control.value = queryctrl.default_value;
if (-1 == ioctl(fd, VIDIOC_S_CTRL, &control)) {
perror("VIDIOC_S_CTRL");
exit(EXIT_FAILURE);
}
}
memset(&control, 0, sizeof(control));
control.id = V4L2_CID_CONTRAST;
if (0 == ioctl(fd, VIDIOC_G_CTRL, &control)) {
control.value += 1;
/* The driver may clamp the value or return ERANGE, ignored here */
if (-1 == ioctl(fd, VIDIOC_S_CTRL, &control)
&& errno != ERANGE) {
perror("VIDIOC_S_CTRL");
exit(EXIT_FAILURE);
}
/* Ignore if V4L2_CID_CONTRAST is unsupported */
} else if (errno != EINVAL) {
perror("VIDIOC_G_CTRL");
exit(EXIT_FAILURE);
}
control.id = V4L2_CID_AUDIO_MUTE;
control.value = 1; /* silence */
/* Errors ignored */
ioctl(fd, VIDIOC_S_CTRL, &control);
[1] | The use of V4L2_CID_PRIVATE_BASE is problematic because different drivers may use the same V4L2_CID_PRIVATE_BASE ID for different controls. This makes it hard to programatically set such controls since the meaning of the control with that ID is driver dependent. In order to resolve this drivers use unique IDs and the V4L2_CID_PRIVATE_BASE IDs are mapped to those unique IDs by the kernel. Consider these V4L2_CID_PRIVATE_BASE IDs as aliases to the real IDs. Many applications today still use the V4L2_CID_PRIVATE_BASE IDs instead of using ioctls VIDIOC_QUERYCTRL, VIDIOC_QUERY_EXT_CTRL and VIDIOC_QUERYMENU with the V4L2_CTRL_FLAG_NEXT_CTRL flag to enumerate all IDs, so support for V4L2_CID_PRIVATE_BASE is still around. |