blob: 9ddf5cb5229a8fab3c09fab0b0f9a49d88bb76de [file] [log] [blame]
/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#include "util.h"
static const char device_name[] = "/dev/video0";
struct buffer {
struct v4l2_buffer vbuf;
unsigned char* mmap_data;
};
static struct buffer buffers[4];
static size_t buffer_count;
static void no_v4l2(void) {
atomic_puts("EXIT-SUCCESS");
exit(0);
}
static int open_device(void) {
struct v4l2_capability cap;
int fd = open("/dev/video0", O_RDWR);
int ret;
if (fd < 0 && errno == ENOENT) {
atomic_printf("%s not found; aborting test\n", device_name);
no_v4l2();
}
if (fd < 0 && errno == EACCES) {
atomic_printf("%s not accessible; aborting test\n", device_name);
no_v4l2();
}
test_assert(fd >= 0);
ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
if (ret < 0 && errno == EINVAL) {
atomic_printf("%s is not a V4L2 device; aborting test\n", device_name);
no_v4l2();
}
if (ret < 0 && errno == EACCES) {
atomic_printf("%s is not accessible; aborting test\n", device_name);
no_v4l2();
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
atomic_printf("%s is not a V4L2 capture device; aborting test\n",
device_name);
no_v4l2();
}
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
atomic_printf("%s does not support streaming; aborting test\n",
device_name);
no_v4l2();
}
uint32_t input = 0xdeadbeef;
ret = ioctl(fd, VIDIOC_G_INPUT, &input);
if (ret < 0) {
atomic_printf("%s does not support VIDIOC_G_INPUT\n", device_name);
} else {
atomic_printf("%s VIDIOC_G_INPUT returns %d\n", device_name, input);
}
#ifdef VIDIOC_QUERY_EXT_CTRL
struct v4l2_query_ext_ctrl qec;
memset(&qec, 0, sizeof(qec));
qec.id = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
ret = ioctl(fd, VIDIOC_QUERY_EXT_CTRL, &qec);
if (ret < 0) {
atomic_printf("%s does not support VIDIOC_QUERY_EXT_CTRL\n", device_name);
} else {
atomic_printf("%s VIDIOC_QUERY_EXT_CTRL returns id=%d, type=%d, name=%s\n",
device_name, qec.id, qec.type, qec.name);
}
#endif
enum v4l2_priority prio = V4L2_PRIORITY_UNSET;
ret = ioctl(fd, VIDIOC_G_PRIORITY, &prio);
if (ret < 0) {
atomic_printf("%s does not support VIDIOC_G_PRIORITY\n", device_name);
} else {
atomic_printf("%s VIDIOC_G_PRIORITY returns prio=%d\n", device_name, prio);
}
struct v4l2_queryctrl qc;
memset(&qc, 0, sizeof(qc));
qc.id = V4L2_CTRL_FLAG_NEXT_CTRL;
ret = ioctl(fd, VIDIOC_QUERYCTRL, &qc);
if (ret < 0) {
atomic_printf("%s does not support VIDIOC_QUERYCTRL\n", device_name);
} else {
atomic_printf("%s VIDIOC_QUERYCTRL returns id=%d, type=%d, name=%s\n",
device_name, qc.id, qc.type, qc.name);
}
return fd;
}
static void init_device(int fd) {
struct v4l2_format fmt;
struct v4l2_requestbuffers req;
int ret;
size_t i;
enum v4l2_buf_type type;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = ioctl(fd, VIDIOC_G_FMT, &fmt);
if (ret < 0 && errno == EINVAL) {
// v4l2_loopback doesn't support G_FMT
atomic_printf("%s does not support G_FMT; aborting test\n",
device_name);
no_v4l2();
}
test_assert(0 == ret);
atomic_printf("%s returning %dx%d frames\n", device_name, fmt.fmt.pix.width,
fmt.fmt.pix.height);
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_REQBUFS, &req);
if (ret < 0 && errno == EINVAL) {
atomic_printf("%s does not support memory mapping; aborting test\n",
device_name);
no_v4l2();
}
if (ret < 0 && errno == EBUSY) {
atomic_printf("%s is busy; aborting test\n", device_name);
no_v4l2();
}
test_assert(0 == ret);
if (req.count < 2) {
atomic_printf("%s only supports one buffer; aborting test\n", device_name);
no_v4l2();
}
buffer_count = req.count;
for (i = 0; i < buffer_count; ++i) {
struct buffer* buf = buffers + i;
buf->vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf->vbuf.memory = V4L2_MEMORY_MMAP;
buf->vbuf.index = i;
test_assert(0 == ioctl(fd, VIDIOC_QUERYBUF, &buf->vbuf));
buf->mmap_data = mmap(NULL, buf->vbuf.length, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, buf->vbuf.m.offset);
test_assert(buf->mmap_data != MAP_FAILED);
atomic_printf("Buffer %d, addr %p, device offset 0x%llx, device len 0x%llx\n",
(int)i, buf->mmap_data, (long long)buf->vbuf.m.offset,
(long long)buf->vbuf.length);
test_assert(0 == ioctl(fd, VIDIOC_QBUF, &buf->vbuf));
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
test_assert(0 == ioctl(fd, VIDIOC_STREAMON, &type));
}
static void print_fourcc(int v) {
union {
int v;
char cs[4];
} u;
u.v = v;
atomic_printf("%c%c%c%c", u.cs[0], u.cs[1], u.cs[2], u.cs[3]);
}
static double fract_to_fps(struct v4l2_fract* f) {
return (double)f->denominator / f->numerator;
}
static void dump_sizes(int fd) {
struct v4l2_fmtdesc fmt;
fmt.index = 0;
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
while (1) {
struct v4l2_frmsizeenum size;
int ret = ioctl(fd, VIDIOC_ENUM_FMT, &fmt);
if (ret < 0) {
test_assert(errno == EINVAL);
break;
}
++fmt.index;
atomic_printf("Format %d fourcc ", fmt.index);
print_fourcc(fmt.pixelformat);
atomic_printf(" name '%s'\n", fmt.description);
size.index = 0;
size.pixel_format = fmt.pixelformat;
while (1) {
ret = ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &size);
if (ret < 0) {
test_assert(errno == EINVAL);
break;
}
++size.index;
if (size.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
struct v4l2_frmivalenum interval;
atomic_printf(" Frame size %dx%d\n", size.discrete.width,
size.discrete.height);
interval.index = 0;
interval.pixel_format = fmt.pixelformat;
interval.width = size.discrete.width;
interval.height = size.discrete.height;
while (1) {
ret = ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &interval);
if (ret < 0) {
test_assert(errno == EINVAL);
break;
}
++interval.index;
if (interval.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
atomic_printf(" %f fps\n", fract_to_fps(&interval.discrete));
} else if (interval.type == V4L2_FRMIVAL_TYPE_CONTINUOUS ||
interval.type == V4L2_FRMIVAL_TYPE_STEPWISE) {
atomic_printf(" %f-%f fps\n",
fract_to_fps(&interval.stepwise.min),
fract_to_fps(&interval.stepwise.max));
}
}
} else if (size.type == V4L2_FRMSIZE_TYPE_STEPWISE) {
atomic_printf(" Frame size %dx%d to %dx%d step %dx%d\n",
size.stepwise.min_width, size.stepwise.min_height,
size.stepwise.max_width, size.stepwise.max_height,
size.stepwise.step_width, size.stepwise.step_height);
}
}
}
}
static void read_frames(int fd) {
size_t i, j;
for (i = 0; i < buffer_count * 2; ++i) {
struct v4l2_buffer buf;
int ret;
size_t bytes;
struct buffer* buffer;
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_DQBUF, &buf);
test_assert(ret == 0);
test_assert(buf.index < buffer_count);
bytes = buf.length < 16 ? buf.length : 16;
buffer = &buffers[buf.index];
atomic_printf("Frame %d, buffer %d, addr %p: ", (int)i, (int)buf.index,
buffer->mmap_data);
for (j = 0; j < bytes; ++j) {
atomic_printf("%2x ", buffer->mmap_data[j]);
}
atomic_printf("...\n");
/* Reallocate the mmap data to check for bugs involving the length
of the shared memory area */
munmap(buffer->mmap_data, buffer->vbuf.length);
buffer->mmap_data =
mmap(NULL, buffer->vbuf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, buffer->vbuf.m.offset);
test_assert(buffer->mmap_data != MAP_FAILED);
test_assert(0 == ioctl(fd, VIDIOC_QBUF, &buf));
}
}
static void close_device(int fd) {
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
test_assert(0 == ioctl(fd, VIDIOC_STREAMOFF, &type));
}
int main(void) {
int fd = open_device();
init_device(fd);
dump_sizes(fd);
read_frames(fd);
close_device(fd);
atomic_puts("EXIT-SUCCESS");
return 0;
}