Files
Megapixels/src/camera.c
ArenM f43fcdb241 Set bridge resolution when setting the mode (MR 31)
sun6i-csi-bridge will return EINVAL if the resolution it is configured
with is different than the resolution the camera is configured with. So
we have to set it's resolution when changing the camera resolution.
2023-05-28 13:42:20 -04:00

1226 lines
42 KiB
C

#include "camera.h"
#include "mode.h"
#include <assert.h>
#include <errno.h>
#include <glib.h>
#include <linux/v4l2-subdev.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>
#define MAX_VIDEO_BUFFERS 20
#define MAX_BG_TASKS 8
static void
errno_printerr(const char *s)
{
g_printerr("MPCamera: %s error %d, %s\n", s, errno, strerror(errno));
}
static int
xioctl(int fd, int request, void *arg)
{
int r;
do {
r = ioctl(fd, request, arg);
} while (r == -1 && errno == EINTR);
return r;
}
struct video_buffer {
uint32_t length;
uint8_t *data;
int fd;
};
struct _MPCamera {
int video_fd;
int subdev_fd;
int bridge_fd;
bool has_set_mode;
MPMode current_mode;
struct video_buffer buffers[MAX_VIDEO_BUFFERS];
uint32_t num_buffers;
// keeping track of background task child-PIDs for cleanup code
int child_bg_pids[MAX_BG_TASKS];
bool use_mplane;
};
MPCamera *
mp_camera_new(int video_fd, int subdev_fd, int bridge_fd)
{
g_return_val_if_fail(video_fd != -1, NULL);
// Query capabilities
struct v4l2_capability cap;
if (xioctl(video_fd, VIDIOC_QUERYCAP, &cap) == -1) {
return NULL;
}
// Check whether this is a video capture device
bool use_mplane;
if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) {
use_mplane = true;
} else if (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
use_mplane = false;
} else {
return NULL;
}
MPCamera *camera = malloc(sizeof(MPCamera));
camera->video_fd = video_fd;
camera->subdev_fd = subdev_fd;
camera->bridge_fd = bridge_fd;
camera->has_set_mode = false;
camera->num_buffers = 0;
camera->use_mplane = use_mplane;
memset(camera->child_bg_pids,
0,
sizeof(camera->child_bg_pids[0]) * MAX_BG_TASKS);
return camera;
}
void
mp_camera_free(MPCamera *camera)
{
mp_camera_wait_bg_tasks(camera);
g_warn_if_fail(camera->num_buffers == 0);
if (camera->num_buffers != 0) {
mp_camera_stop_capture(camera);
}
free(camera);
}
void
mp_camera_add_bg_task(MPCamera *camera, pid_t pid)
{
int status;
while (true) {
for (size_t i = 0; i < MAX_BG_TASKS; ++i) {
if (camera->child_bg_pids[i] == 0) {
camera->child_bg_pids[i] = pid;
return;
} else {
// error == -1, still running == 0
if (waitpid(camera->child_bg_pids[i],
&status,
WNOHANG) <= 0)
continue; // consider errored wait still
// running
if (WIFEXITED(status)) {
// replace exited
camera->child_bg_pids[i] = pid;
return;
}
}
}
// wait for any status change on child processes
pid_t changed = waitpid(-1, &status, 0);
if (WIFEXITED(status)) {
// some child exited
for (size_t i = 0; i < MAX_BG_TASKS; ++i) {
if (camera->child_bg_pids[i] == changed) {
camera->child_bg_pids[i] = pid;
return;
}
}
}
// no luck, repeat and check if something exited maybe
}
}
void
mp_camera_wait_bg_tasks(MPCamera *camera)
{
for (size_t i = 0; i < MAX_BG_TASKS; ++i) {
if (camera->child_bg_pids[i] != 0) {
// ignore errors
waitpid(camera->child_bg_pids[i], NULL, 0);
}
}
}
bool
mp_camera_check_task_complete(MPCamera *camera, pid_t pid)
{
// this method is potentially unsafe because pid could already be reused at
// this point, but extremely unlikely so we won't implement this.
int status;
if (pid == 0)
return true;
// ignore errors (-1), no exit == 0
int pidchange = waitpid(pid, &status, WNOHANG);
if (pidchange == -1) // error or exists and runs
return false;
if (WIFEXITED(status)) {
for (size_t i = 0; i < MAX_BG_TASKS; ++i) {
if (camera->child_bg_pids[i] == pid) {
camera->child_bg_pids[i] = 0;
break;
}
}
return true;
} else {
return false;
}
}
bool
mp_camera_is_subdev(MPCamera *camera)
{
return camera->subdev_fd != -1;
}
int
mp_camera_get_video_fd(MPCamera *camera)
{
return camera->video_fd;
}
int
mp_camera_get_subdev_fd(MPCamera *camera)
{
return camera->subdev_fd;
}
static enum v4l2_buf_type
get_buf_type(MPCamera *camera)
{
if (camera->use_mplane) {
return V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
}
return V4L2_BUF_TYPE_VIDEO_CAPTURE;
}
static bool
camera_mode_impl(MPCamera *camera, int request, MPMode *mode)
{
uint32_t pixfmt = mp_pixel_format_to_v4l_pixel_format(mode->pixel_format);
struct v4l2_format fmt = {};
if (camera->use_mplane) {
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
fmt.fmt.pix_mp.width = mode->width;
fmt.fmt.pix_mp.height = mode->height;
fmt.fmt.pix_mp.pixelformat = pixfmt;
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
} else {
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = mode->width;
fmt.fmt.pix.height = mode->height;
fmt.fmt.pix.pixelformat = pixfmt;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
}
if (xioctl(camera->video_fd, request, &fmt) == -1) {
return false;
}
if (camera->use_mplane) {
mode->width = fmt.fmt.pix_mp.width;
mode->height = fmt.fmt.pix_mp.height;
mode->pixel_format = mp_pixel_format_from_v4l_pixel_format(
fmt.fmt.pix_mp.pixelformat);
} else {
mode->width = fmt.fmt.pix.width;
mode->height = fmt.fmt.pix.height;
mode->pixel_format = mp_pixel_format_from_v4l_pixel_format(
fmt.fmt.pix.pixelformat);
}
return true;
}
bool
mp_camera_try_mode(MPCamera *camera, MPMode *mode)
{
if (!camera_mode_impl(camera, VIDIOC_TRY_FMT, mode)) {
errno_printerr("VIDIOC_S_FMT");
return false;
}
return true;
}
const MPMode *
mp_camera_get_mode(const MPCamera *camera)
{
return &camera->current_mode;
}
bool
mp_camera_set_mode(MPCamera *camera, MPMode *mode)
{
// Set the mode in the subdev the camera is one
if (mp_camera_is_subdev(camera)) {
struct v4l2_subdev_frame_interval interval = {};
interval.pad = 0;
interval.interval = mode->frame_interval;
if (xioctl(camera->subdev_fd,
VIDIOC_SUBDEV_S_FRAME_INTERVAL,
&interval) == -1) {
errno_printerr("VIDIOC_SUBDEV_S_FRAME_INTERVAL");
}
bool did_set_frame_rate = interval.interval.numerator ==
mode->frame_interval.numerator &&
interval.interval.denominator ==
mode->frame_interval.denominator;
struct v4l2_subdev_format fmt = {};
fmt.pad = 0;
fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
fmt.format.width = mode->width;
fmt.format.height = mode->height;
fmt.format.code =
mp_pixel_format_to_v4l_bus_code(mode->pixel_format);
fmt.format.field = V4L2_FIELD_ANY;
if (xioctl(camera->subdev_fd, VIDIOC_SUBDEV_S_FMT, &fmt) == -1) {
errno_printerr("VIDIOC_SUBDEV_S_FMT");
return false;
}
// sun6i-csi-bridge will return EINVAL when trying to read
// frames if the resolution it's configured with doesn't match
// the resolution of its input.
if (camera->bridge_fd > 0) {
struct v4l2_subdev_format bridge_fmt = fmt;
if (xioctl(camera->bridge_fd,
VIDIOC_SUBDEV_S_FMT,
&bridge_fmt) == -1) {
errno_printerr("VIDIOC_SUBDEV_S_FMT");
return false;
}
if (fmt.format.width != bridge_fmt.format.width ||
fmt.format.height != bridge_fmt.format.height) {
g_printerr("Bridge format resolution mismatch\n");
return false;
}
}
// Some drivers like ov5640 don't allow you to set the frame format
// with too high a frame-rate, but that means the frame-rate won't be
// set after the format change. So we need to try again here if we
// didn't succeed before. Ideally we'd be able to set both at once.
if (!did_set_frame_rate) {
interval.interval = mode->frame_interval;
if (xioctl(camera->subdev_fd,
VIDIOC_SUBDEV_S_FRAME_INTERVAL,
&interval) == -1) {
errno_printerr("VIDIOC_SUBDEV_S_FRAME_INTERVAL");
}
}
// Update the mode
mode->pixel_format =
mp_pixel_format_from_v4l_bus_code(fmt.format.code);
mode->frame_interval = interval.interval;
mode->width = fmt.format.width;
mode->height = fmt.format.height;
}
// Set the mode for the video device
{
if (!camera_mode_impl(camera, VIDIOC_S_FMT, mode)) {
errno_printerr("VIDIOC_S_FMT");
return false;
}
}
camera->has_set_mode = true;
camera->current_mode = *mode;
return true;
}
bool
mp_camera_start_capture(MPCamera *camera)
{
g_return_val_if_fail(camera->has_set_mode, false);
g_return_val_if_fail(camera->num_buffers == 0, false);
const enum v4l2_buf_type buftype = get_buf_type(camera);
// Start by requesting buffers
struct v4l2_requestbuffers req = {};
req.count = MAX_VIDEO_BUFFERS;
req.type = buftype;
req.memory = V4L2_MEMORY_MMAP;
if (xioctl(camera->video_fd, VIDIOC_REQBUFS, &req) == -1) {
errno_printerr("VIDIOC_REQBUFS");
return false;
}
if (req.count < 2) {
g_printerr(
"Insufficient buffer memory. Only %d buffers available.\n",
req.count);
goto error;
}
for (uint32_t i = 0; i < req.count; ++i) {
// Query each buffer and mmap it
struct v4l2_buffer buf = {
.type = buftype,
.memory = V4L2_MEMORY_MMAP,
.index = i,
};
struct v4l2_plane planes[1];
if (camera->use_mplane) {
buf.m.planes = planes;
buf.length = 1;
}
if (xioctl(camera->video_fd, VIDIOC_QUERYBUF, &buf) == -1) {
errno_printerr("VIDIOC_QUERYBUF");
break;
}
if (camera->use_mplane) {
camera->buffers[i].length = planes[0].length;
camera->buffers[i].data = mmap(NULL,
planes[0].length,
PROT_READ,
MAP_SHARED,
camera->video_fd,
planes[0].m.mem_offset);
} else {
camera->buffers[i].length = buf.length;
camera->buffers[i].data = mmap(NULL,
buf.length,
PROT_READ,
MAP_SHARED,
camera->video_fd,
buf.m.offset);
}
if (camera->buffers[i].data == MAP_FAILED) {
errno_printerr("mmap");
break;
}
struct v4l2_exportbuffer expbuf = {
.type = buftype,
.index = i,
};
if (xioctl(camera->video_fd, VIDIOC_EXPBUF, &expbuf) == -1) {
errno_printerr("VIDIOC_EXPBUF");
break;
}
camera->buffers[i].fd = expbuf.fd;
++camera->num_buffers;
}
if (camera->num_buffers != req.count) {
g_printerr("Unable to map all buffers\n");
goto error;
}
for (uint32_t i = 0; i < camera->num_buffers; ++i) {
struct v4l2_buffer buf = {
.type = buftype,
.memory = V4L2_MEMORY_MMAP,
.index = i,
};
struct v4l2_plane planes[1];
if (camera->use_mplane) {
buf.m.planes = planes;
buf.length = 1;
}
// Queue the buffer for capture
if (xioctl(camera->video_fd, VIDIOC_QBUF, &buf) == -1) {
errno_printerr("VIDIOC_QBUF");
goto error;
}
}
// Start capture
enum v4l2_buf_type type = buftype;
if (xioctl(camera->video_fd, VIDIOC_STREAMON, &type) == -1) {
errno_printerr("VIDIOC_STREAMON");
goto error;
}
return true;
error:
// Unmap any mapped buffers
assert(camera->num_buffers <= MAX_VIDEO_BUFFERS);
for (uint32_t i = 0; i < camera->num_buffers; ++i) {
if (munmap(camera->buffers[i].data, camera->buffers[i].length) ==
-1) {
errno_printerr("munmap");
}
if (close(camera->buffers[i].fd) == -1) {
errno_printerr("close");
}
}
// Reset allocated buffers
{
struct v4l2_requestbuffers req = {};
req.count = 0;
req.type = buftype;
req.memory = V4L2_MEMORY_MMAP;
if (xioctl(camera->video_fd, VIDIOC_REQBUFS, &req) == -1) {
errno_printerr("VIDIOC_REQBUFS");
}
}
return false;
}
bool
mp_camera_stop_capture(MPCamera *camera)
{
g_return_val_if_fail(camera->num_buffers > 0, false);
const enum v4l2_buf_type buftype = get_buf_type(camera);
enum v4l2_buf_type type = buftype;
if (xioctl(camera->video_fd, VIDIOC_STREAMOFF, &type) == -1) {
errno_printerr("VIDIOC_STREAMOFF");
}
assert(camera->num_buffers <= MAX_VIDEO_BUFFERS);
for (int i = 0; i < camera->num_buffers; ++i) {
if (munmap(camera->buffers[i].data, camera->buffers[i].length) ==
-1) {
errno_printerr("munmap");
}
if (close(camera->buffers[i].fd) == -1) {
errno_printerr("close");
}
}
camera->num_buffers = 0;
struct v4l2_requestbuffers req = {};
req.count = 0;
req.type = buftype;
req.memory = V4L2_MEMORY_MMAP;
if (xioctl(camera->video_fd, VIDIOC_REQBUFS, &req) == -1) {
errno_printerr("VIDIOC_REQBUFS");
}
return true;
}
bool
mp_camera_is_capturing(MPCamera *camera)
{
return camera->num_buffers > 0;
}
bool
mp_camera_capture_buffer(MPCamera *camera, MPBuffer *buffer)
{
const enum v4l2_buf_type buftype = get_buf_type(camera);
struct v4l2_buffer buf = {};
buf.type = buftype;
buf.memory = V4L2_MEMORY_MMAP;
struct v4l2_plane planes[1];
if (camera->use_mplane) {
buf.m.planes = planes;
buf.length = 1;
}
if (xioctl(camera->video_fd, VIDIOC_DQBUF, &buf) == -1) {
switch (errno) {
case EAGAIN:
return true;
case EIO:
/* Could ignore EIO, see spec. */
/* fallthrough */
default:
errno_printerr("VIDIOC_DQBUF");
exit(1);
return false;
}
}
uint32_t pixel_format = camera->current_mode.pixel_format;
uint32_t width = camera->current_mode.width;
uint32_t height = camera->current_mode.height;
uint32_t bytesused;
if (camera->use_mplane) {
bytesused = planes[0].bytesused;
} else {
bytesused = buf.bytesused;
}
assert(bytesused == (mp_pixel_format_width_to_bytes(pixel_format, width) +
mp_pixel_format_width_to_padding(pixel_format, width)) *
height);
assert(bytesused == camera->buffers[buf.index].length);
buffer->index = buf.index;
buffer->data = camera->buffers[buf.index].data;
buffer->fd = camera->buffers[buf.index].fd;
return true;
}
bool
mp_camera_release_buffer(MPCamera *camera, uint32_t buffer_index)
{
const enum v4l2_buf_type buftype = get_buf_type(camera);
struct v4l2_buffer buf = {};
buf.type = buftype;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = buffer_index;
struct v4l2_plane planes[1];
if (camera->use_mplane) {
buf.m.planes = planes;
buf.length = 1;
}
if (xioctl(camera->video_fd, VIDIOC_QBUF, &buf) == -1) {
errno_printerr("VIDIOC_QBUF");
return false;
}
return true;
}
static MPModeList *
get_subdev_modes(MPCamera *camera, bool (*check)(MPCamera *, MPMode *))
{
MPModeList *item = NULL;
for (uint32_t fmt_index = 0;; ++fmt_index) {
struct v4l2_subdev_mbus_code_enum fmt = {};
fmt.index = fmt_index;
fmt.pad = 0;
fmt.which = V4L2_SUBDEV_FORMAT_TRY;
if (xioctl(camera->subdev_fd, VIDIOC_SUBDEV_ENUM_MBUS_CODE, &fmt) ==
-1) {
if (errno != EINVAL) {
errno_printerr("VIDIOC_SUBDEV_ENUM_MBUS_CODE");
}
break;
}
// Skip unsupported formats
uint32_t format = mp_pixel_format_from_v4l_bus_code(fmt.code);
if (format == MP_PIXEL_FMT_UNSUPPORTED) {
continue;
}
for (uint32_t frame_index = 0;; ++frame_index) {
struct v4l2_subdev_frame_size_enum frame = {};
frame.index = frame_index;
frame.pad = 0;
frame.code = fmt.code;
frame.which = V4L2_SUBDEV_FORMAT_TRY;
if (xioctl(camera->subdev_fd,
VIDIOC_SUBDEV_ENUM_FRAME_SIZE,
&frame) == -1) {
if (errno != EINVAL) {
errno_printerr(
"VIDIOC_SUBDEV_ENUM_FRAME_SIZE");
}
break;
}
// TODO: Handle other types
if (frame.min_width != frame.max_width ||
frame.min_height != frame.max_height) {
break;
}
for (uint32_t interval_index = 0;; ++interval_index) {
struct v4l2_subdev_frame_interval_enum interval = {};
interval.index = interval_index;
interval.pad = 0;
interval.code = fmt.code;
interval.width = frame.max_width;
interval.height = frame.max_height;
interval.which = V4L2_SUBDEV_FORMAT_TRY;
if (xioctl(camera->subdev_fd,
VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL,
&interval) == -1) {
if (errno != EINVAL) {
errno_printerr(
"VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL");
}
break;
}
MPMode mode = {
.pixel_format = format,
.frame_interval = interval.interval,
.width = frame.max_width,
.height = frame.max_height,
};
if (!check(camera, &mode)) {
continue;
}
MPModeList *new_item = malloc(sizeof(MPModeList));
new_item->mode = mode;
new_item->next = item;
item = new_item;
}
}
}
return item;
}
static MPModeList *
get_video_modes(MPCamera *camera, bool (*check)(MPCamera *, MPMode *))
{
const enum v4l2_buf_type buftype = get_buf_type(camera);
MPModeList *item = NULL;
for (uint32_t fmt_index = 0;; ++fmt_index) {
struct v4l2_fmtdesc fmt = {};
fmt.index = fmt_index;
fmt.type = buftype;
if (xioctl(camera->video_fd, VIDIOC_ENUM_FMT, &fmt) == -1) {
if (errno != EINVAL) {
errno_printerr("VIDIOC_ENUM_FMT");
}
break;
}
// Skip unsupported formats
uint32_t format =
mp_pixel_format_from_v4l_pixel_format(fmt.pixelformat);
if (format == MP_PIXEL_FMT_UNSUPPORTED) {
continue;
}
for (uint32_t frame_index = 0;; ++frame_index) {
struct v4l2_frmsizeenum frame = {};
frame.index = frame_index;
frame.pixel_format = fmt.pixelformat;
if (xioctl(camera->video_fd,
VIDIOC_ENUM_FRAMESIZES,
&frame) == -1) {
if (errno != EINVAL) {
errno_printerr("VIDIOC_ENUM_FRAMESIZES");
}
break;
}
// TODO: Handle other types
if (frame.type != V4L2_FRMSIZE_TYPE_DISCRETE) {
break;
}
for (uint32_t interval_index = 0;; ++interval_index) {
struct v4l2_frmivalenum interval = {};
interval.index = interval_index;
interval.pixel_format = fmt.pixelformat;
interval.width = frame.discrete.width;
interval.height = frame.discrete.height;
if (xioctl(camera->video_fd,
VIDIOC_ENUM_FRAMEINTERVALS,
&interval) == -1) {
if (errno != EINVAL) {
errno_printerr(
"VIDIOC_ENUM_FRAMESIZES");
}
break;
}
// TODO: Handle other types
if (interval.type != V4L2_FRMIVAL_TYPE_DISCRETE) {
break;
}
MPMode mode = {
.pixel_format = format,
.frame_interval = interval.discrete,
.width = frame.discrete.width,
.height = frame.discrete.height,
};
if (!check(camera, &mode)) {
continue;
}
MPModeList *new_item = malloc(sizeof(MPModeList));
new_item->mode = mode;
new_item->next = item;
item = new_item;
}
}
}
return item;
}
static bool
all_modes(MPCamera *camera, MPMode *mode)
{
return true;
}
static bool
available_modes(MPCamera *camera, MPMode *mode)
{
MPMode attempt = *mode;
return mp_camera_try_mode(camera, &attempt) &&
mp_mode_is_equivalent(mode, &attempt);
}
MPModeList *
mp_camera_list_supported_modes(MPCamera *camera)
{
if (mp_camera_is_subdev(camera)) {
return get_subdev_modes(camera, all_modes);
} else {
return get_video_modes(camera, all_modes);
}
}
MPModeList *
mp_camera_list_available_modes(MPCamera *camera)
{
if (mp_camera_is_subdev(camera)) {
return get_subdev_modes(camera, available_modes);
} else {
return get_video_modes(camera, available_modes);
}
}
MPMode *
mp_camera_mode_list_get(MPModeList *list)
{
g_return_val_if_fail(list, NULL);
return &list->mode;
}
MPModeList *
mp_camera_mode_list_next(MPModeList *list)
{
g_return_val_if_fail(list, NULL);
return list->next;
}
void
mp_camera_mode_list_free(MPModeList *list)
{
while (list) {
MPModeList *tmp = list;
list = tmp->next;
free(tmp);
}
}
struct int_str_pair {
uint32_t value;
const char *str;
};
struct int_str_pair control_id_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_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" },
{ V4L2_CID_CHROMA_AGC, "CHROMA_AGC" },
{ V4L2_CID_COLOR_KILLER, "COLOR_KILLER" },
{ V4L2_CID_COLORFX, "COLORFX" },
{ V4L2_CID_AUTOBRIGHTNESS, "AUTOBRIGHTNESS" },
{ V4L2_CID_BAND_STOP_FILTER, "BAND_STOP_FILTER" },
{ V4L2_CID_ROTATE, "ROTATE" },
{ V4L2_CID_BG_COLOR, "BG_COLOR" },
{ V4L2_CID_CHROMA_GAIN, "CHROMA_GAIN" },
{ V4L2_CID_ILLUMINATORS_1, "ILLUMINATORS_1" },
{ V4L2_CID_ILLUMINATORS_2, "ILLUMINATORS_2" },
{ V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, "MIN_BUFFERS_FOR_CAPTURE" },
{ V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, "MIN_BUFFERS_FOR_OUTPUT" },
{ V4L2_CID_ALPHA_COMPONENT, "ALPHA_COMPONENT" },
{ V4L2_CID_COLORFX_CBCR, "COLORFX_CBCR" },
{ V4L2_CID_LASTP1, "LASTP1" },
{ V4L2_CID_USER_MEYE_BASE, "USER_MEYE_BASE" },
{ V4L2_CID_USER_BTTV_BASE, "USER_BTTV_BASE" },
{ V4L2_CID_USER_S2255_BASE, "USER_S2255_BASE" },
{ V4L2_CID_USER_SI476X_BASE, "USER_SI476X_BASE" },
{ V4L2_CID_USER_TI_VPE_BASE, "USER_TI_VPE_BASE" },
{ V4L2_CID_USER_SAA7134_BASE, "USER_SAA7134_BASE" },
{ V4L2_CID_USER_ADV7180_BASE, "USER_ADV7180_BASE" },
{ V4L2_CID_USER_TC358743_BASE, "USER_TC358743_BASE" },
{ V4L2_CID_USER_MAX217X_BASE, "USER_MAX217X_BASE" },
{ V4L2_CID_USER_IMX_BASE, "USER_IMX_BASE" },
// { V4L2_CID_USER_ATMEL_ISC_BASE, "USER_ATMEL_ISC_BASE" },
{ V4L2_CID_CAMERA_CLASS_BASE, "CAMERA_CLASS_BASE" },
{ V4L2_CID_CAMERA_CLASS, "CAMERA_CLASS" },
{ V4L2_CID_EXPOSURE_AUTO, "EXPOSURE_AUTO" },
{ V4L2_CID_EXPOSURE_ABSOLUTE, "EXPOSURE_ABSOLUTE" },
{ V4L2_CID_EXPOSURE_AUTO_PRIORITY, "EXPOSURE_AUTO_PRIORITY" },
{ V4L2_CID_PAN_RELATIVE, "PAN_RELATIVE" },
{ V4L2_CID_TILT_RELATIVE, "TILT_RELATIVE" },
{ V4L2_CID_PAN_RESET, "PAN_RESET" },
{ V4L2_CID_TILT_RESET, "TILT_RESET" },
{ V4L2_CID_PAN_ABSOLUTE, "PAN_ABSOLUTE" },
{ V4L2_CID_TILT_ABSOLUTE, "TILT_ABSOLUTE" },
{ V4L2_CID_FOCUS_ABSOLUTE, "FOCUS_ABSOLUTE" },
{ V4L2_CID_FOCUS_RELATIVE, "FOCUS_RELATIVE" },
{ V4L2_CID_FOCUS_AUTO, "FOCUS_AUTO" },
{ V4L2_CID_ZOOM_ABSOLUTE, "ZOOM_ABSOLUTE" },
{ V4L2_CID_ZOOM_RELATIVE, "ZOOM_RELATIVE" },
{ V4L2_CID_ZOOM_CONTINUOUS, "ZOOM_CONTINUOUS" },
{ V4L2_CID_PRIVACY, "PRIVACY" },
{ V4L2_CID_IRIS_ABSOLUTE, "IRIS_ABSOLUTE" },
{ V4L2_CID_IRIS_RELATIVE, "IRIS_RELATIVE" },
{ V4L2_CID_AUTO_EXPOSURE_BIAS, "AUTO_EXPOSURE_BIAS" },
{ V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, "AUTO_N_PRESET_WHITE_BALANCE" },
{ V4L2_CID_WIDE_DYNAMIC_RANGE, "WIDE_DYNAMIC_RANGE" },
{ V4L2_CID_IMAGE_STABILIZATION, "IMAGE_STABILIZATION" },
{ V4L2_CID_ISO_SENSITIVITY, "ISO_SENSITIVITY" },
{ V4L2_CID_ISO_SENSITIVITY_AUTO, "ISO_SENSITIVITY_AUTO" },
{ V4L2_CID_EXPOSURE_METERING, "EXPOSURE_METERING" },
{ V4L2_CID_SCENE_MODE, "SCENE_MODE" },
{ V4L2_CID_3A_LOCK, "3A_LOCK" },
{ V4L2_CID_AUTO_FOCUS_START, "AUTO_FOCUS_START" },
{ V4L2_CID_AUTO_FOCUS_STOP, "AUTO_FOCUS_STOP" },
{ V4L2_CID_AUTO_FOCUS_STATUS, "AUTO_FOCUS_STATUS" },
{ V4L2_CID_AUTO_FOCUS_RANGE, "AUTO_FOCUS_RANGE" },
{ V4L2_CID_PAN_SPEED, "PAN_SPEED" },
{ V4L2_CID_TILT_SPEED, "TILT_SPEED" },
// { V4L2_CID_CAMERA_ORIENTATION, "CAMERA_ORIENTATION" },
// { V4L2_CID_CAMERA_SENSOR_ROTATION, "CAMERA_SENSOR_ROTATION" },
{ V4L2_CID_FLASH_LED_MODE, "FLASH_LED_MODE" },
{ V4L2_CID_FLASH_STROBE_SOURCE, "FLASH_STROBE_SOURCE" },
{ V4L2_CID_FLASH_STROBE, "FLASH_STROBE" },
{ V4L2_CID_FLASH_STROBE_STOP, "FLASH_STROBE_STOP" },
{ V4L2_CID_FLASH_STROBE_STATUS, "FLASH_STROBE_STATUS" },
{ V4L2_CID_FLASH_TIMEOUT, "FLASH_TIMEOUT" },
{ V4L2_CID_FLASH_INTENSITY, "FLASH_INTENSITY" },
{ V4L2_CID_FLASH_TORCH_INTENSITY, "FLASH_TORCH_INTENSITY" },
{ V4L2_CID_FLASH_INDICATOR_INTENSITY, "FLASH_INDICATOR_INTENSITY" },
{ V4L2_CID_FLASH_FAULT, "FLASH_FAULT" },
{ V4L2_CID_FLASH_CHARGE, "FLASH_CHARGE" },
{ V4L2_CID_FLASH_READY, "FLASH_READY" },
};
const char *
mp_control_id_to_str(uint32_t id)
{
size_t size = sizeof(control_id_names) / sizeof(*control_id_names);
for (size_t i = 0; i < size; ++i) {
if (control_id_names[i].value == id) {
return control_id_names[i].str;
}
}
return "UNKNOWN";
}
struct int_str_pair control_type_names[] = {
{ V4L2_CTRL_TYPE_INTEGER, "INTEGER" },
{ V4L2_CTRL_TYPE_BOOLEAN, "BOOLEAN" },
{ V4L2_CTRL_TYPE_MENU, "MENU" },
{ V4L2_CTRL_TYPE_INTEGER_MENU, "INTEGER_MENU" },
{ V4L2_CTRL_TYPE_BITMASK, "BITMASK" },
{ V4L2_CTRL_TYPE_BUTTON, "BUTTON" },
{ V4L2_CTRL_TYPE_INTEGER64, "INTEGER64" },
{ V4L2_CTRL_TYPE_STRING, "STRING" },
{ V4L2_CTRL_TYPE_CTRL_CLASS, "CTRL_CLASS" },
{ V4L2_CTRL_TYPE_U8, "U8" },
{ V4L2_CTRL_TYPE_U16, "U16" },
{ V4L2_CTRL_TYPE_U32, "U32" },
// { V4L2_CTRL_TYPE_MPEG2_SLICE_PARAMS, "MPEG2_SLICE_PARAMS" },
// { V4L2_CTRL_TYPE_MPEG2_QUANTIZATION, "MPEG2_QUANTIZATION" },
// { V4L2_CTRL_TYPE_AREA, "AREA" },
// { V4L2_CTRL_TYPE_H264_SPS, "H264_SPS" },
// { V4L2_CTRL_TYPE_H264_PPS, "H264_PPS" },
// { V4L2_CTRL_TYPE_H264_SCALING_MATRIX, "H264_SCALING_MATRIX" },
// { V4L2_CTRL_TYPE_H264_SLICE_PARAMS, "H264_SLICE_PARAMS" },
// { V4L2_CTRL_TYPE_H264_DECODE_PARAMS, "H264_DECODE_PARAMS" },
// { V4L2_CTRL_TYPE_HEVC_SPS, "HEVC_SPS" },
// { V4L2_CTRL_TYPE_HEVC_PPS, "HEVC_PPS" },
// { V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS, "HEVC_SLICE_PARAMS" },
};
const char *
mp_control_type_to_str(uint32_t type)
{
size_t size = sizeof(control_type_names) / sizeof(*control_type_names);
for (size_t i = 0; i < size; ++i) {
if (control_type_names[i].value == type) {
return control_type_names[i].str;
}
}
return "UNKNOWN";
}
struct _MPControlList {
MPControl control;
MPControlList *next;
};
static int
control_fd(MPCamera *camera)
{
if (camera->subdev_fd != -1) {
return camera->subdev_fd;
}
return camera->video_fd;
}
MPControlList *
mp_camera_list_controls(MPCamera *camera)
{
MPControlList *item = NULL;
struct v4l2_query_ext_ctrl ctrl = {};
ctrl.id = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
while (true) {
if (xioctl(control_fd(camera), VIDIOC_QUERY_EXT_CTRL, &ctrl) == -1) {
if (errno != EINVAL) {
errno_printerr("VIDIOC_QUERY_EXT_CTRL");
}
break;
}
MPControl control = {
.id = ctrl.id,
.type = ctrl.type,
.name = {},
.min = ctrl.minimum,
.max = ctrl.maximum,
.step = ctrl.step,
.default_value = ctrl.default_value,
.flags = ctrl.flags,
.element_size = ctrl.elem_size,
.element_count = ctrl.elems,
.dimensions_count = ctrl.nr_of_dims,
.dimensions = {},
};
strcpy(control.name, ctrl.name);
memcpy(control.dimensions,
ctrl.dims,
sizeof(uint32_t) * V4L2_CTRL_MAX_DIMS);
MPControlList *new_item = malloc(sizeof(MPControlList));
new_item->control = control;
new_item->next = item;
item = new_item;
ctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
}
return item;
}
MPControl *
mp_control_list_get(MPControlList *list)
{
g_return_val_if_fail(list, NULL);
return &list->control;
}
MPControlList *
mp_control_list_next(MPControlList *list)
{
g_return_val_if_fail(list, NULL);
return list->next;
}
void
mp_control_list_free(MPControlList *list)
{
while (list) {
MPControlList *tmp = list;
list = tmp->next;
free(tmp);
}
}
bool
mp_camera_query_control(MPCamera *camera, uint32_t id, MPControl *control)
{
struct v4l2_query_ext_ctrl ctrl = {};
ctrl.id = id;
if (xioctl(control_fd(camera), VIDIOC_QUERY_EXT_CTRL, &ctrl) == -1) {
if (errno != EINVAL) {
errno_printerr("VIDIOC_QUERY_EXT_CTRL");
}
return false;
}
if (control) {
control->id = ctrl.id;
control->type = ctrl.type;
strcpy(control->name, ctrl.name);
control->min = ctrl.minimum;
control->max = ctrl.maximum;
control->step = ctrl.step;
control->default_value = ctrl.default_value;
control->flags = ctrl.flags;
control->element_size = ctrl.elem_size;
control->element_count = ctrl.elems;
control->dimensions_count = ctrl.nr_of_dims;
memcpy(control->dimensions,
ctrl.dims,
sizeof(uint32_t) * V4L2_CTRL_MAX_DIMS);
}
return true;
}
static bool
control_impl_int32(MPCamera *camera, uint32_t id, int request, int32_t *value)
{
struct v4l2_ext_control ctrl = {};
ctrl.id = id;
ctrl.value = *value;
struct v4l2_ext_controls ctrls = {
.ctrl_class = 0,
.which = V4L2_CTRL_WHICH_CUR_VAL,
.count = 1,
.controls = &ctrl,
};
if (xioctl(control_fd(camera), request, &ctrls) == -1) {
return false;
}
*value = ctrl.value;
return true;
}
pid_t
mp_camera_control_set_int32_bg(MPCamera *camera, uint32_t id, int32_t v)
{
struct v4l2_ext_control ctrl = {};
ctrl.id = id;
ctrl.value = v;
struct v4l2_ext_controls ctrls = {
.ctrl_class = 0,
.which = V4L2_CTRL_WHICH_CUR_VAL,
.count = 1,
.controls = &ctrl,
};
int fd = control_fd(camera);
// fork only after all the memory has been read
pid_t pid = fork();
if (pid == -1) {
return 0; // discard errors, nothing to do in parent process
} else if (pid != 0) {
// parent process adding pid to wait list (to clear zombie processes)
mp_camera_add_bg_task(camera, pid);
return pid;
}
// ignore errors
xioctl(fd, VIDIOC_S_EXT_CTRLS, &ctrls);
// exit without calling exit handlers
_exit(0);
}
bool
mp_camera_control_try_int32(MPCamera *camera, uint32_t id, int32_t *v)
{
return control_impl_int32(camera, id, VIDIOC_TRY_EXT_CTRLS, v);
}
bool
mp_camera_control_set_int32(MPCamera *camera, uint32_t id, int32_t v)
{
return control_impl_int32(camera, id, VIDIOC_S_EXT_CTRLS, &v);
}
int32_t
mp_camera_control_get_int32(MPCamera *camera, uint32_t id)
{
int32_t v = 0;
control_impl_int32(camera, id, VIDIOC_G_EXT_CTRLS, &v);
return v;
}
bool
mp_camera_control_try_boolean(MPCamera *camera, uint32_t id, bool *v)
{
int32_t value = *v;
bool s = control_impl_int32(camera, id, VIDIOC_TRY_EXT_CTRLS, &value);
*v = value;
return s;
}
bool
mp_camera_control_set_bool(MPCamera *camera, uint32_t id, bool v)
{
int32_t value = v;
return control_impl_int32(camera, id, VIDIOC_S_EXT_CTRLS, &value);
}
bool
mp_camera_control_get_bool(MPCamera *camera, uint32_t id)
{
int32_t v = false;
control_impl_int32(camera, id, VIDIOC_G_EXT_CTRLS, &v);
return v;
}
pid_t
mp_camera_control_set_bool_bg(MPCamera *camera, uint32_t id, bool v)
{
int32_t value = v;
return mp_camera_control_set_int32_bg(camera, id, value);
}