/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
/*
 * V4L2 C helper header providing wrappers to simplify access to the various
 * v4l2 functions.
 *
 * Copyright 2014-2016 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 */

#ifndef _V4L_HELPERS_H_
#define _V4L_HELPERS_H_

#include <linux/v4l2-subdev.h>
#include <linux/videodev2.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

struct v4l_fd {
	int fd;
	struct v4l2_capability cap;
	char devname[128];
	__u32 type;
	__u32 caps;
	unsigned int trace;
	bool direct;
	bool have_query_ext_ctrl;
	bool have_ext_ctrls;
	bool have_next_ctrl;
	bool have_selection;
	bool is_subdev;
	bool is_media;
	bool have_streams;
	bool ival_uses_which;

	int (*open)(struct v4l_fd *f, const char *file, int oflag, ...);
	int (*close)(struct v4l_fd *f);
	int (*ioctl)(struct v4l_fd *f, unsigned long cmd, ...);
	ssize_t (*read)(struct v4l_fd *f, void *buffer, size_t n);
	ssize_t (*write)(struct v4l_fd *f, const void *buffer, size_t n);
	void *(*mmap)(void *addr, size_t length, int prot, int flags,
		      struct v4l_fd *f, off_t offset);
	int (*munmap)(struct v4l_fd *f, void *addr, size_t length);
};

#ifdef __LIBV4L2_H

static inline int v4l_wrap_open(struct v4l_fd *f, const char *file, int oflag, ...)
{
 	return f->direct ? open(file, oflag) : v4l2_open(file, oflag);
}

static inline int v4l_wrap_close(struct v4l_fd *f)
{
	int ret = f->direct ? close(f->fd) : v4l2_close(f->fd);

	f->fd = -1;
	return ret;
}

static inline ssize_t v4l_wrap_read(struct v4l_fd *f, void *buffer, size_t n)
{
	return f->direct ? read(f->fd, buffer, n) : v4l2_read(f->fd, buffer, n);
}

static inline ssize_t v4l_wrap_write(struct v4l_fd *f, const void *buffer, size_t n)
{
	return f->direct ? write(f->fd, buffer, n) : v4l2_write(f->fd, buffer, n);
}

static inline int v4l_wrap_ioctl(struct v4l_fd *f, unsigned long cmd, ...)
{
	void *arg;
	va_list ap;

	va_start(ap, cmd);
	arg = va_arg(ap, void *);
	va_end(ap);
	return f->direct ? ioctl(f->fd, cmd, arg) : v4l2_ioctl(f->fd, cmd, arg);
}

static inline void *v4l_wrap_mmap(void *start, size_t length, int prot, int flags,
		struct v4l_fd *f, off_t offset)
{
 	return f->direct ? mmap(start, length, prot, flags, f->fd, offset) :
		v4l2_mmap(start, length, prot, flags, f->fd, offset);
}

static inline int v4l_wrap_munmap(struct v4l_fd *f, void *start, size_t length)
{
 	return f->direct ? munmap(start, length) : v4l2_munmap(start, length);
}

static inline bool v4l_fd_g_direct(const struct v4l_fd *f)
{
	return f->direct;
}

static inline void v4l_fd_s_direct(struct v4l_fd *f, bool direct)
{
	if (!f->is_subdev && !f->is_media)
		f->direct = direct;
}

#else

static inline int v4l_wrap_open(struct v4l_fd *f, const char *file, int oflag, ...)
{
 	return open(file, oflag);
}

static inline int v4l_wrap_close(struct v4l_fd *f)
{
	int ret = close(f->fd);

	f->fd = -1;
	return ret;
}

static inline ssize_t v4l_wrap_read(struct v4l_fd *f, void *buffer, size_t n)
{
	return read(f->fd, buffer, n);
}

static inline ssize_t v4l_wrap_write(struct v4l_fd *f, const void *buffer, size_t n)
{
	return write(f->fd, buffer, n);
}

static inline int v4l_wrap_ioctl(struct v4l_fd *f, unsigned long cmd, ...)
{
	void *arg;
	va_list ap;

	va_start(ap, cmd);
	arg = va_arg(ap, void *);
	va_end(ap);
	return ioctl(f->fd, cmd, arg);
}

static inline void *v4l_wrap_mmap(void *start, size_t length, int prot, int flags,
		struct v4l_fd *f, off_t offset)
{
 	return mmap(start, length, prot, flags, f->fd, offset);
}

static inline int v4l_wrap_munmap(struct v4l_fd *f, void *start, size_t length)
{
 	return munmap(start, length);
}

static inline bool v4l_fd_g_direct(const struct v4l_fd *f)
{
	return true;
}

static inline void v4l_fd_s_direct(struct v4l_fd *f, bool direct)
{
}

#endif

static inline void v4l_fd_init(struct v4l_fd *f)
{
	memset(f, 0, sizeof(*f));
	f->fd = -1;
	f->is_subdev = false;
	f->is_media = false;
	f->open = v4l_wrap_open;
	f->close = v4l_wrap_close;
	f->ioctl = v4l_wrap_ioctl;
	f->read = v4l_wrap_read;
	f->write = v4l_wrap_write;
	f->mmap = v4l_wrap_mmap;
	f->munmap = v4l_wrap_munmap;
}

static inline bool v4l_fd_is_subdev(const struct v4l_fd *f)
{
	return f->is_subdev;
}

static inline bool v4l_fd_is_media(const struct v4l_fd *f)
{
	return f->is_media;
}

static inline bool v4l_fd_is_v4l2(const struct v4l_fd *f)
{
	return !f->is_subdev && !f->is_media;
}

static inline unsigned int v4l_fd_g_trace(const struct v4l_fd *f)
{
	return f->trace;
}

static inline void v4l_fd_s_trace(struct v4l_fd *f, unsigned int trace)
{
	f->trace = trace;
}

static inline int v4l_named_ioctl(struct v4l_fd *f,
		const char *cmd_name, unsigned long cmd, void *arg)
{
	int retval;
	int e;

	retval = f->ioctl(f, cmd, arg);
	e = retval == 0 ? 0 : errno;
	if (f->trace >= (e ? 1 : 2))
		fprintf(stderr, "\t\t%s returned %d (%s)\n",
				cmd_name, retval, strerror(e));
	return retval == -1 ? e : (retval ? -1 : 0);
}

#define v4l_ioctl(f, cmd, arg) v4l_named_ioctl(f, #cmd, cmd, arg)

static inline void *v4l_mmap(struct v4l_fd *f, size_t length, off_t offset)
{
	return f->mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, f, offset);
}

static inline int v4l_munmap(struct v4l_fd *f, void *start, size_t length)
{
	return f->munmap(f, start, length);
}

static inline ssize_t v4l_read(struct v4l_fd *f, void *buffer, size_t n)
{
	return f->read(f, buffer, n);
}

static inline ssize_t v4l_write(struct v4l_fd *f, const void *buffer, size_t n)
{
	return f->write(f, buffer, n);
}

static inline int v4l_close(struct v4l_fd *f)
{
	int res = f->close(f);

	f->caps = f->type = 0;
	f->fd = -1;
	return res;
}

static inline int v4l_querycap(struct v4l_fd *f, struct v4l2_capability *cap)
{
	return v4l_ioctl(f, VIDIOC_QUERYCAP, cap);
}

static inline __u32 v4l_capability_g_caps(const struct v4l2_capability *cap)
{
	return (cap->capabilities & V4L2_CAP_DEVICE_CAPS) ?
			cap->device_caps : cap->capabilities;
}

static inline __u32 v4l_g_type(const struct v4l_fd *f)
{
	return f->type;
}

static inline void v4l_s_type(struct v4l_fd *f, __u32 type)
{
	f->type = type;
}

static inline __u32 v4l_g_selection_type(const struct v4l_fd *f)
{
	if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
		return V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
		return V4L2_BUF_TYPE_VIDEO_OUTPUT;
	return f->type;
}

static inline __u32 v4l_g_caps(const struct v4l_fd *f)
{
	return f->caps;
}

static inline bool v4l_has_vid_cap(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE |
				V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE);
}

static inline bool v4l_has_vid_out(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & (V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE |
				V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE);
}

static inline bool v4l_has_vid_m2m(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & (V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE);
}

static inline bool v4l_has_vid_mplane(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & (V4L2_CAP_VIDEO_CAPTURE_MPLANE |
				V4L2_CAP_VIDEO_OUTPUT_MPLANE |
				V4L2_CAP_VIDEO_M2M_MPLANE);
}

static inline bool v4l_has_overlay_cap(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_VIDEO_OVERLAY;
}

static inline bool v4l_has_overlay_out(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_VIDEO_OUTPUT_OVERLAY;
}

static inline bool v4l_has_raw_vbi_cap(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_VBI_CAPTURE;
}

static inline bool v4l_has_sliced_vbi_cap(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_SLICED_VBI_CAPTURE;
}

static inline bool v4l_has_vbi_cap(const struct v4l_fd *f)
{
	return v4l_has_raw_vbi_cap(f) || v4l_has_sliced_vbi_cap(f);
}

static inline bool v4l_has_raw_vbi_out(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_VBI_OUTPUT;
}

static inline bool v4l_has_sliced_vbi_out(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_SLICED_VBI_OUTPUT;
}

static inline bool v4l_has_vbi_out(const struct v4l_fd *f)
{
	return v4l_has_raw_vbi_out(f) || v4l_has_sliced_vbi_out(f);
}

static inline bool v4l_has_vbi(const struct v4l_fd *f)
{
	return v4l_has_vbi_cap(f) || v4l_has_vbi_out(f);
}

static inline bool v4l_has_radio_rx(const struct v4l_fd *f)
{
	return (v4l_g_caps(f) & V4L2_CAP_RADIO) &&
	       (v4l_g_caps(f) & V4L2_CAP_TUNER);
}

static inline bool v4l_has_radio_tx(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_MODULATOR;
}

static inline bool v4l_has_rds_cap(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_RDS_CAPTURE;
}

static inline bool v4l_has_rds_out(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_RDS_OUTPUT;
}

static inline bool v4l_has_sdr_cap(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_SDR_CAPTURE;
}

static inline bool v4l_has_sdr_out(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_SDR_OUTPUT;
}

static inline bool v4l_has_meta_cap(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_META_CAPTURE;
}

static inline bool v4l_has_meta_out(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_META_OUTPUT;
}

static inline bool v4l_has_touch(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_TOUCH;
}

static inline bool v4l_has_hwseek(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_HW_FREQ_SEEK;
}

static inline bool v4l_has_rw(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_READWRITE;
}

static inline bool v4l_has_streaming(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_STREAMING;
}

static inline bool v4l_has_ext_pix_format(const struct v4l_fd *f)
{
	return v4l_g_caps(f) & V4L2_CAP_EXT_PIX_FORMAT;
}

static inline __u32 v4l_determine_type(const struct v4l_fd *f)
{
	if (v4l_has_vid_mplane(f))
		return v4l_has_vid_cap(f) ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
					    V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
	if (v4l_has_vid_cap(f))
		return V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (v4l_has_vid_out(f))
		return V4L2_BUF_TYPE_VIDEO_OUTPUT;
	if (v4l_has_raw_vbi_cap(f))
		return V4L2_BUF_TYPE_VBI_CAPTURE;
	if (v4l_has_sliced_vbi_cap(f))
		return V4L2_BUF_TYPE_SLICED_VBI_CAPTURE;
	if (v4l_has_raw_vbi_out(f))
		return V4L2_BUF_TYPE_VBI_OUTPUT;
	if (v4l_has_sliced_vbi_out(f))
		return V4L2_BUF_TYPE_SLICED_VBI_OUTPUT;
	if (v4l_has_sdr_cap(f))
		return V4L2_BUF_TYPE_SDR_CAPTURE;
	if (v4l_has_sdr_out(f))
		return V4L2_BUF_TYPE_SDR_OUTPUT;
	if (v4l_has_meta_cap(f))
		return V4L2_BUF_TYPE_META_CAPTURE;
	if (v4l_has_meta_out(f))
		return V4L2_BUF_TYPE_META_OUTPUT;

	return 0;
}

static inline int v4l_s_fd(struct v4l_fd *f, int fd, const char *devname, bool direct)
{
	struct v4l2_query_ext_ctrl qec;
	struct v4l2_ext_controls ec;
	struct v4l2_queryctrl qc;
	struct v4l2_selection sel;

	if (f->fd >= 0)
		f->close(f);

	f->fd = fd;
	f->direct = direct;
	if (fd < 0)
		return fd;

	memset(&qec, 0, sizeof(qec));
	qec.id = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND;
	memset(&ec, 0, sizeof(ec));
	memset(&qc, 0, sizeof(qc));
	qc.id = V4L2_CTRL_FLAG_NEXT_CTRL;
	memset(&sel, 0, sizeof(sel));

	if (f->devname != devname)
		strncpy(f->devname, devname, sizeof(f->devname));
	f->devname[sizeof(f->devname) - 1] = '\0';

	memset(&f->cap, 0, sizeof(f->cap));
	if (v4l_querycap(f, &f->cap)) {
		v4l_close(f);
		return -1;
	}
	f->is_subdev = false;
	f->is_media = false;
	f->caps = v4l_capability_g_caps(&f->cap);
	f->type = v4l_determine_type(f);

	f->have_query_ext_ctrl = v4l_ioctl(f, VIDIOC_QUERY_EXT_CTRL, &qec) == 0;
	f->have_ext_ctrls = v4l_ioctl(f, VIDIOC_TRY_EXT_CTRLS, &ec) == 0;
	f->have_next_ctrl = v4l_ioctl(f, VIDIOC_QUERYCTRL, &qc) == 0;
	sel.type = v4l_g_selection_type(f);
	sel.target = sel.type == V4L2_BUF_TYPE_VIDEO_CAPTURE ?
			V4L2_SEL_TGT_CROP : V4L2_SEL_TGT_COMPOSE;
	f->have_selection = v4l_ioctl(f, VIDIOC_G_SELECTION, &sel) != ENOTTY;

	return f->fd;
}

static inline int v4l_open(struct v4l_fd *f, const char *devname, bool non_blocking)
{
	int fd = f->open(f, devname, O_RDWR | (non_blocking ? O_NONBLOCK : 0));

	return v4l_s_fd(f, fd, devname, f->direct);
}

static inline int v4l_subdev_s_fd(struct v4l_fd *f, int fd, const char *devname)
{
	struct v4l2_subdev_client_capability clientcap = {};
	struct v4l2_subdev_capability subdevcap = {};
	bool subdev_streams;
	bool client_streams;
	int ret;

	if (f->fd >= 0)
		f->close(f);

	f->fd = fd;
	f->direct = true;
	if (fd < 0)
		return fd;

	if (f->devname != devname)
		strncpy(f->devname, devname, sizeof(f->devname));
	f->devname[sizeof(f->devname) - 1] = '\0';

	memset(&f->cap, 0, sizeof(f->cap));
	f->is_subdev = true;
	f->is_media = false;
	f->type = 0;
	f->have_query_ext_ctrl = false;
	f->have_ext_ctrls = false;
	f->have_next_ctrl = false;
	f->have_selection = false;

	ret = ioctl(f->fd, VIDIOC_SUBDEV_QUERYCAP, &subdevcap);
	subdev_streams = !ret && (subdevcap.capabilities & V4L2_SUBDEV_CAP_STREAMS);

	clientcap.capabilities = V4L2_SUBDEV_CLIENT_CAP_STREAMS |
				 V4L2_SUBDEV_CLIENT_CAP_INTERVAL_USES_WHICH;

	ret = ioctl(f->fd, VIDIOC_SUBDEV_S_CLIENT_CAP, &clientcap);
	client_streams = !ret && (clientcap.capabilities & V4L2_SUBDEV_CLIENT_CAP_STREAMS);
	f->ival_uses_which = !ret && (clientcap.capabilities & V4L2_SUBDEV_CLIENT_CAP_INTERVAL_USES_WHICH);

	f->have_streams = subdev_streams && client_streams;

	return f->fd;
}

static inline int v4l_subdev_open(struct v4l_fd *f, const char *devname, bool non_blocking)
{
	int fd = f->open(f, devname, O_RDWR | (non_blocking ? O_NONBLOCK : 0));

	return v4l_subdev_s_fd(f, fd, devname);
}

static inline int v4l_media_s_fd(struct v4l_fd *f, int fd, const char *devname)
{
	if (f->fd >= 0)
		f->close(f);

	f->fd = fd;
	f->direct = true;
	if (fd < 0)
		return fd;

	if (f->devname != devname)
		strncpy(f->devname, devname, sizeof(f->devname));
	f->devname[sizeof(f->devname) - 1] = '\0';

	memset(&f->cap, 0, sizeof(f->cap));
	f->is_subdev = false;
	f->is_media = true;
	f->type = 0;
	f->have_query_ext_ctrl = false;
	f->have_ext_ctrls = false;
	f->have_next_ctrl = false;
	f->have_selection = false;

	return f->fd;
}

static inline int v4l_media_open(struct v4l_fd *f, const char *devname, bool non_blocking)
{
	int fd = f->open(f, devname, O_RDWR | (non_blocking ? O_NONBLOCK : 0));

	return v4l_media_s_fd(f, fd, devname);
}

static inline int v4l_reopen(struct v4l_fd *f, bool non_blocking)
{
	f->close(f);
	if (f->is_subdev)
		return v4l_subdev_open(f, f->devname, non_blocking);
	if (f->is_media)
		return v4l_media_open(f, f->devname, non_blocking);
	return v4l_open(f, f->devname, non_blocking);
}

static inline void v4l_format_init(struct v4l2_format *fmt, unsigned type)
{
	memset(fmt, 0, sizeof(*fmt));
	fmt->type = type;
	if (fmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE ||
	    fmt->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
		fmt->fmt.pix.priv = V4L2_PIX_FMT_PRIV_MAGIC;
}

static inline void v4l_format_s_width(struct v4l2_format *fmt, __u32 width)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		fmt->fmt.pix.width = width;
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		fmt->fmt.pix_mp.width = width;
		break;
	case V4L2_BUF_TYPE_VIDEO_OVERLAY:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
		fmt->fmt.win.w.width = width;
		break;
	}
}

static inline __u32 v4l_format_g_width(const struct v4l2_format *fmt)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		return fmt->fmt.pix.width;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		return fmt->fmt.pix_mp.width;
	case V4L2_BUF_TYPE_VIDEO_OVERLAY:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
		return fmt->fmt.win.w.width;
	default:
		return 0;
	}
}

static inline void v4l_format_s_height(struct v4l2_format *fmt, __u32 height)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		fmt->fmt.pix.height = height;
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		fmt->fmt.pix_mp.height = height;
		break;
	case V4L2_BUF_TYPE_VIDEO_OVERLAY:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
		fmt->fmt.win.w.height = height;
		break;
	}
}

static inline __u32 v4l_format_g_height(const struct v4l2_format *fmt)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		return fmt->fmt.pix.height;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		return fmt->fmt.pix_mp.height;
	case V4L2_BUF_TYPE_VIDEO_OVERLAY:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
		return fmt->fmt.win.w.height;
	default:
		return 0;
	}
}

static inline void v4l_format_s_pixelformat(struct v4l2_format *fmt, __u32 pixelformat)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		fmt->fmt.pix.pixelformat = pixelformat;
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		fmt->fmt.pix_mp.pixelformat = pixelformat;
		break;
	case V4L2_BUF_TYPE_SDR_CAPTURE:
	case V4L2_BUF_TYPE_SDR_OUTPUT:
		fmt->fmt.sdr.pixelformat = pixelformat;
		break;
	case V4L2_BUF_TYPE_VBI_CAPTURE:
	case V4L2_BUF_TYPE_VBI_OUTPUT:
		fmt->fmt.vbi.sample_format = pixelformat;
		break;
	case V4L2_BUF_TYPE_META_CAPTURE:
	case V4L2_BUF_TYPE_META_OUTPUT:
		fmt->fmt.meta.dataformat = pixelformat;
		break;
	}
}

static inline __u32 v4l_format_g_pixelformat(const struct v4l2_format *fmt)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		return fmt->fmt.pix.pixelformat;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		return fmt->fmt.pix_mp.pixelformat;
	case V4L2_BUF_TYPE_SDR_CAPTURE:
	case V4L2_BUF_TYPE_SDR_OUTPUT:
		return fmt->fmt.sdr.pixelformat;
	case V4L2_BUF_TYPE_VBI_CAPTURE:
	case V4L2_BUF_TYPE_VBI_OUTPUT:
		return fmt->fmt.vbi.sample_format;
	case V4L2_BUF_TYPE_META_CAPTURE:
	case V4L2_BUF_TYPE_META_OUTPUT:
		return fmt->fmt.meta.dataformat;
	default:
		return 0;
	}
}

static inline void v4l_format_s_field(struct v4l2_format *fmt, unsigned field)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		fmt->fmt.pix.field = field;
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		fmt->fmt.pix_mp.field = field;
		break;
	case V4L2_BUF_TYPE_VIDEO_OVERLAY:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
		fmt->fmt.win.field = field;
		break;
	}
}

static inline unsigned v4l_format_g_field(const struct v4l2_format *fmt)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		return fmt->fmt.pix.field;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		return fmt->fmt.pix_mp.field;
	case V4L2_BUF_TYPE_VIDEO_OVERLAY:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY:
		return fmt->fmt.win.field;
	default:
		return V4L2_FIELD_NONE;
	}
}

static inline unsigned v4l_format_g_first_field(const struct v4l2_format *fmt,
						v4l2_std_id std)
{
	unsigned field = v4l_format_g_field(fmt);

	if (field != V4L2_FIELD_ALTERNATE)
		return field;
	if (std & V4L2_STD_525_60)
		return V4L2_FIELD_BOTTOM;
	return V4L2_FIELD_TOP;
}

static inline unsigned v4l_format_g_flds_per_frm(const struct v4l2_format *fmt)
{
	unsigned field = v4l_format_g_field(fmt);

	if (field == V4L2_FIELD_ALTERNATE ||
	    field == V4L2_FIELD_TOP || field == V4L2_FIELD_BOTTOM)
		return 2;
	return 1;
}

static inline void v4l_format_s_frame_height(struct v4l2_format *fmt, __u32 height)
{
	if (V4L2_FIELD_HAS_T_OR_B(v4l_format_g_field(fmt)))
		height /= 2;
	v4l_format_s_height(fmt, height);
}

static inline __u32 v4l_format_g_frame_height(const struct v4l2_format *fmt)
{
	__u32 height = v4l_format_g_height(fmt);

	if (V4L2_FIELD_HAS_T_OR_B(v4l_format_g_field(fmt)))
		return height * 2;
	return height;
}

static inline void v4l_format_s_colorspace(struct v4l2_format *fmt,
					       unsigned colorspace)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		fmt->fmt.pix.colorspace = colorspace;
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		fmt->fmt.pix_mp.colorspace = colorspace;
		break;
	}
}

static inline unsigned
v4l_format_g_colorspace(const struct v4l2_format *fmt)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		return fmt->fmt.pix.colorspace;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		return fmt->fmt.pix_mp.colorspace;
	default:
		return 0;
	}
}

static inline void v4l_format_s_xfer_func(struct v4l2_format *fmt,
					       unsigned xfer_func)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		fmt->fmt.pix.xfer_func = xfer_func;
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		fmt->fmt.pix_mp.xfer_func = xfer_func;
		break;
	}
}

static inline unsigned
v4l_format_g_xfer_func(const struct v4l2_format *fmt)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		return fmt->fmt.pix.xfer_func;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		return fmt->fmt.pix_mp.xfer_func;
	default:
		return 0;
	}
}

static inline void v4l_format_s_ycbcr_enc(struct v4l2_format *fmt,
					       unsigned ycbcr_enc)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		fmt->fmt.pix.ycbcr_enc = ycbcr_enc;
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		fmt->fmt.pix_mp.ycbcr_enc = ycbcr_enc;
		break;
	}
}

static inline unsigned
v4l_format_g_ycbcr_enc(const struct v4l2_format *fmt)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		return fmt->fmt.pix.ycbcr_enc;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		return fmt->fmt.pix_mp.ycbcr_enc;
	default:
		return 0;
	}
}

static inline unsigned
v4l_format_g_hsv_enc(const struct v4l2_format *fmt)
{
	unsigned hsv_enc = v4l_format_g_ycbcr_enc(fmt);
	if (hsv_enc == V4L2_HSV_ENC_180)
		return V4L2_HSV_ENC_180;

	return hsv_enc;
}

static inline void v4l_format_s_quantization(struct v4l2_format *fmt,
					       unsigned quantization)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		fmt->fmt.pix.quantization = quantization;
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		fmt->fmt.pix_mp.quantization = quantization;
		break;
	}
}

static inline unsigned
v4l_format_g_quantization(const struct v4l2_format *fmt)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		return fmt->fmt.pix.quantization;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		return fmt->fmt.pix_mp.quantization;
	default:
		return 0;
	}
}

static inline void v4l_format_s_flags(struct v4l2_format *fmt,
					       unsigned flags)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		fmt->fmt.pix.flags = flags;
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		fmt->fmt.pix_mp.flags = flags;
		break;
	}
}

static inline unsigned
v4l_format_g_flags(const struct v4l2_format *fmt)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		return fmt->fmt.pix.flags;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		return fmt->fmt.pix_mp.flags;
	default:
		return 0;
	}
}

static inline void v4l_format_s_num_planes(struct v4l2_format *fmt, __u8 num_planes)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		fmt->fmt.pix_mp.num_planes = num_planes;
		break;
	}
}

static inline __u8
v4l_format_g_num_planes(const struct v4l2_format *fmt)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		return fmt->fmt.pix_mp.num_planes;
	default:
		return 1;
	}
}

static inline void v4l_format_s_bytesperline(struct v4l2_format *fmt,
					     unsigned plane, __u32 bytesperline)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		if (plane == 0)
			fmt->fmt.pix.bytesperline = bytesperline;
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		fmt->fmt.pix_mp.plane_fmt[plane].bytesperline = bytesperline;
		break;
	case V4L2_BUF_TYPE_VBI_CAPTURE:
	case V4L2_BUF_TYPE_VBI_OUTPUT:
		/* This assumes V4L2_PIX_FMT_GREY which is always the case */
		if (plane == 0)
			fmt->fmt.vbi.samples_per_line = bytesperline;
		break;
	}
}

static inline __u32
v4l_format_g_bytesperline(const struct v4l2_format *fmt, unsigned plane)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		return plane ? 0 : fmt->fmt.pix.bytesperline;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		return fmt->fmt.pix_mp.plane_fmt[plane].bytesperline;
	case V4L2_BUF_TYPE_VBI_CAPTURE:
	case V4L2_BUF_TYPE_VBI_OUTPUT:
		/* This assumes V4L2_PIX_FMT_GREY which is always the case */
		return plane ? 0 : fmt->fmt.vbi.samples_per_line;
	default:
		return 0;
	}
}

static inline void v4l_format_s_sizeimage(struct v4l2_format *fmt,
					  unsigned plane, __u32 sizeimage)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		if (plane == 0)
			fmt->fmt.pix.sizeimage = sizeimage;
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		fmt->fmt.pix_mp.plane_fmt[plane].sizeimage = sizeimage;
		break;
	case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
	case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:
		if (plane == 0)
			fmt->fmt.sliced.io_size = sizeimage;
		break;
	case V4L2_BUF_TYPE_SDR_CAPTURE:
	case V4L2_BUF_TYPE_SDR_OUTPUT:
		if (plane == 0)
			fmt->fmt.sdr.buffersize = sizeimage;
		break;
	case V4L2_BUF_TYPE_META_CAPTURE:
	case V4L2_BUF_TYPE_META_OUTPUT:
		if (plane == 0)
			fmt->fmt.meta.buffersize = sizeimage;
		break;
	}
}

static inline __u32
v4l_format_g_sizeimage(const struct v4l2_format *fmt, unsigned plane)
{
	switch (fmt->type) {
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		return plane ? 0 : fmt->fmt.pix.sizeimage;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
		return fmt->fmt.pix_mp.plane_fmt[plane].sizeimage;
	case V4L2_BUF_TYPE_VBI_CAPTURE:
	case V4L2_BUF_TYPE_VBI_OUTPUT:
		/* This assumes V4L2_PIX_FMT_GREY which is always the case */
		return plane ? 0 : fmt->fmt.vbi.samples_per_line *
			(fmt->fmt.vbi.count[0] + fmt->fmt.vbi.count[1]);
	case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
	case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:
		return plane ? 0 : fmt->fmt.sliced.io_size;
	case V4L2_BUF_TYPE_SDR_CAPTURE:
	case V4L2_BUF_TYPE_SDR_OUTPUT:
		return plane ? 0 : fmt->fmt.sdr.buffersize;
	case V4L2_BUF_TYPE_META_CAPTURE:
	case V4L2_BUF_TYPE_META_OUTPUT:
		return plane ? 0 : fmt->fmt.meta.buffersize;
	default:
		return 0;
	}
}

static inline int v4l_g_fmt(struct v4l_fd *f, struct v4l2_format *fmt, unsigned type)
{
	v4l_format_init(fmt, type ? type : f->type);
	return v4l_ioctl(f, VIDIOC_G_FMT, fmt);
}

static inline int v4l_try_fmt(struct v4l_fd *f, struct v4l2_format *fmt, bool zero_bpl)
{
	/*
	 * Some drivers allow applications to set bytesperline to a larger value.
	 * In most cases you just want the driver to fill in the bytesperline field
	 * and so you have to zero bytesperline first.
	 */
	if (zero_bpl) {
		__u8 p;

		for (p = 0; p < v4l_format_g_num_planes(fmt); p++)
			v4l_format_s_bytesperline(fmt, p, 0);
	}
	return v4l_ioctl(f, VIDIOC_TRY_FMT, fmt);
}

static inline int v4l_s_fmt(struct v4l_fd *f, struct v4l2_format *fmt, bool zero_bpl)
{
	/*
	 * Some drivers allow applications to set bytesperline to a larger value.
	 * In most cases you just want the driver to fill in the bytesperline field
	 * and so you have to zero bytesperline first.
	 */
	if (zero_bpl) {
		__u8 p;

		for (p = 0; p < v4l_format_g_num_planes(fmt); p++)
			v4l_format_s_bytesperline(fmt, p, 0);
	}
	return v4l_ioctl(f, VIDIOC_S_FMT, fmt);
}

struct v4l_buffer {
	struct v4l2_plane planes[VIDEO_MAX_PLANES];
	struct v4l2_buffer buf;
};

static inline void v4l_buffer_init(struct v4l_buffer *buf,
		unsigned type, unsigned memory, unsigned index)
{
	memset(buf, 0, sizeof(*buf));
	buf->buf.type = type;
	buf->buf.memory = memory;
	buf->buf.index = index;
	if (V4L2_TYPE_IS_MULTIPLANAR(type)) {
		buf->buf.m.planes = buf->planes;
		buf->buf.length = VIDEO_MAX_PLANES;
	}
}

static inline bool v4l_type_is_planar(unsigned type)
{
       return V4L2_TYPE_IS_MULTIPLANAR(type);
}

static inline bool v4l_type_is_output(unsigned type)
{
       return V4L2_TYPE_IS_OUTPUT(type);
}

static inline bool v4l_type_is_capture(unsigned type)
{
       return V4L2_TYPE_IS_CAPTURE(type);
}

static inline bool v4l_type_is_video(unsigned type)
{
       switch (type) {
       case V4L2_BUF_TYPE_VIDEO_CAPTURE:
       case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
       case V4L2_BUF_TYPE_VIDEO_OUTPUT:
       case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
               return true;
       default:
               return false;
       }
}

static inline bool v4l_type_is_raw_vbi(unsigned type)
{
       return type == V4L2_BUF_TYPE_VBI_CAPTURE ||
              type == V4L2_BUF_TYPE_VBI_OUTPUT;
}

static inline bool v4l_type_is_sliced_vbi(unsigned type)
{
       return type == V4L2_BUF_TYPE_SLICED_VBI_CAPTURE ||
              type == V4L2_BUF_TYPE_SLICED_VBI_OUTPUT;
}

static inline bool v4l_type_is_vbi(unsigned type)
{
       return v4l_type_is_raw_vbi(type) || v4l_type_is_sliced_vbi(type);
}

static inline bool v4l_type_is_overlay(unsigned type)
{
       return type == V4L2_BUF_TYPE_VIDEO_OVERLAY ||
              type == V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY;
}

static inline bool v4l_type_is_sdr(unsigned type)
{
       return type == V4L2_BUF_TYPE_SDR_CAPTURE ||
	      type == V4L2_BUF_TYPE_SDR_OUTPUT;
}

static inline bool v4l_type_is_meta(unsigned type)
{
	return type == V4L2_BUF_TYPE_META_CAPTURE ||
	       type == V4L2_BUF_TYPE_META_OUTPUT;
}

static inline unsigned v4l_type_invert(unsigned type)
{
	if (v4l_type_is_planar(type))
		return v4l_type_is_output(type) ?
			V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
			V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
	return v4l_type_is_output(type) ?
		V4L2_BUF_TYPE_VIDEO_CAPTURE :
		V4L2_BUF_TYPE_VIDEO_OUTPUT;
}

static inline unsigned v4l_buffer_g_num_planes(const struct v4l_buffer *buf)
{
	if (v4l_type_is_planar(buf->buf.type))
		return buf->buf.length;
	return 1;
}

static inline __u32 v4l_buffer_g_index(const struct v4l_buffer *buf)
{
	return buf->buf.index;
}

static inline void v4l_buffer_s_index(struct v4l_buffer *buf, __u32 index)
{
	buf->buf.index = index;
}

static inline __s32 v4l_buffer_g_request_fd(const struct v4l_buffer *buf)
{
	return buf->buf.request_fd;
}

static inline void v4l_buffer_s_request_fd(struct v4l_buffer *buf, __s32 request_fd)
{
	buf->buf.request_fd = request_fd;
}


static inline unsigned v4l_buffer_g_type(const struct v4l_buffer *buf)
{
	return buf->buf.type;
}

static inline unsigned v4l_buffer_g_memory(const struct v4l_buffer *buf)
{
	return buf->buf.memory;
}

static inline __u32 v4l_buffer_g_flags(const struct v4l_buffer *buf)
{
	return buf->buf.flags;
}

static inline void v4l_buffer_s_flags(struct v4l_buffer *buf, __u32 flags)
{
	buf->buf.flags = flags;
}

static inline void v4l_buffer_or_flags(struct v4l_buffer *buf, __u32 flags)
{
	buf->buf.flags |= flags;
}

static inline unsigned v4l_buffer_g_field(const struct v4l_buffer *buf)
{
	return buf->buf.field;
}

static inline void v4l_buffer_s_field(struct v4l_buffer *buf, unsigned field)
{
	buf->buf.field = field;
}

static inline __u32 v4l_buffer_g_sequence(const struct v4l_buffer *buf)
{
	return buf->buf.sequence;
}

static inline const struct timeval *v4l_buffer_g_timestamp(const struct v4l_buffer *buf)
{
	return &buf->buf.timestamp;
}

static inline void v4l_buffer_s_timestamp(struct v4l_buffer *buf, const struct timeval *tv)
{
	buf->buf.timestamp = *tv;
}

static inline void v4l_buffer_s_timestamp_ts(struct v4l_buffer *buf, const struct timespec *ts)
{
	buf->buf.timestamp.tv_sec = ts->tv_sec;
	buf->buf.timestamp.tv_usec = ts->tv_nsec / 1000;
}

static inline void v4l_buffer_s_timestamp_clock(struct v4l_buffer *buf)
{
	struct timespec ts;

	clock_gettime(CLOCK_MONOTONIC, &ts);
	v4l_buffer_s_timestamp_ts(buf, &ts);
}

static inline const struct v4l2_timecode *v4l_buffer_g_timecode(const struct v4l_buffer *buf)
{
	return &buf->buf.timecode;
}

static inline void v4l_buffer_s_timecode(struct v4l_buffer *buf, const struct v4l2_timecode *tc)
{
	buf->buf.timecode = *tc;
}

static inline __u32 v4l_buffer_g_timestamp_type(const struct v4l_buffer *buf)
{
	return buf->buf.flags & V4L2_BUF_FLAG_TIMESTAMP_MASK;
}

static inline bool v4l_buffer_is_copy(const struct v4l_buffer *buf)
{
	return v4l_buffer_g_timestamp_type(buf) == V4L2_BUF_FLAG_TIMESTAMP_COPY;
}

static inline __u32 v4l_buffer_g_timestamp_src(const struct v4l_buffer *buf)
{
	return buf->buf.flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
}

static inline void v4l_buffer_s_timestamp_src(struct v4l_buffer *buf, __u32 src)
{
	buf->buf.flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
	buf->buf.flags |= src & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
}

static inline unsigned v4l_buffer_g_length(const struct v4l_buffer *buf, unsigned plane)
{
	if (v4l_type_is_planar(buf->buf.type))
		return buf->planes[plane].length;
	return plane ? 0 : buf->buf.length;
}

static inline void v4l_buffer_s_length(struct v4l_buffer *buf, unsigned plane, unsigned length)
{
	if (v4l_type_is_planar(buf->buf.type))
		buf->planes[plane].length = length;
	else if (plane == 0)
		buf->buf.length = length;
}

static inline unsigned v4l_buffer_g_bytesused(const struct v4l_buffer *buf, unsigned plane)
{
	if (v4l_type_is_planar(buf->buf.type))
		return buf->planes[plane].bytesused;
	return plane ? 0 : buf->buf.bytesused;
}

static inline void v4l_buffer_s_bytesused(struct v4l_buffer *buf, unsigned plane, __u32 bytesused)
{
	if (v4l_type_is_planar(buf->buf.type))
		buf->planes[plane].bytesused = bytesused;
	else if (plane == 0)
		buf->buf.bytesused = bytesused;
}

static inline unsigned v4l_buffer_g_data_offset(const struct v4l_buffer *buf, unsigned plane)
{
	if (v4l_type_is_planar(buf->buf.type))
		return buf->planes[plane].data_offset;
	return 0;
}

static inline void v4l_buffer_s_data_offset(struct v4l_buffer *buf, unsigned plane, __u32 data_offset)
{
	if (v4l_type_is_planar(buf->buf.type))
		buf->planes[plane].data_offset = data_offset;
}

static inline __u32 v4l_buffer_g_mem_offset(const struct v4l_buffer *buf, unsigned plane)
{
	if (v4l_type_is_planar(buf->buf.type))
		return buf->planes[plane].m.mem_offset;
	return plane ? 0 : buf->buf.m.offset;
}

static inline void v4l_buffer_s_userptr(struct v4l_buffer *buf, unsigned plane, void *userptr)
{
	if (v4l_type_is_planar(buf->buf.type))
		buf->planes[plane].m.userptr = (unsigned long)userptr;
	else if (plane == 0)
		buf->buf.m.userptr = (unsigned long)userptr;
}

static inline void *v4l_buffer_g_userptr(const struct v4l_buffer *buf, unsigned plane)
{
	if (v4l_type_is_planar(buf->buf.type))
		return (void *)buf->planes[plane].m.userptr;
	return plane ? NULL : (void *)buf->buf.m.userptr;
}

static inline void v4l_buffer_s_fd(struct v4l_buffer *buf, unsigned plane, int fd)
{
	if (v4l_type_is_planar(buf->buf.type))
		buf->planes[plane].m.fd = fd;
	else if (plane == 0)
		buf->buf.m.fd = fd;
}

static inline int v4l_buffer_g_fd(const struct v4l_buffer *buf, unsigned plane)
{
	if (v4l_type_is_planar(buf->buf.type))
		return buf->planes[plane].m.fd;
	return plane ? -1 : buf->buf.m.fd;
}

static inline int v4l_buffer_prepare_buf(struct v4l_fd *f, struct v4l_buffer *buf)
{
	return v4l_ioctl(f, VIDIOC_PREPARE_BUF, &buf->buf);
}

static inline int v4l_buffer_qbuf(struct v4l_fd *f, struct v4l_buffer *buf)
{
	return v4l_ioctl(f, VIDIOC_QBUF, &buf->buf);
}

static inline int v4l_buffer_dqbuf(struct v4l_fd *f, struct v4l_buffer *buf)
{
	return v4l_ioctl(f, VIDIOC_DQBUF, &buf->buf);
}

static inline int v4l_buffer_querybuf(struct v4l_fd *f, struct v4l_buffer *buf, unsigned index)
{
	v4l_buffer_s_index(buf, index);
	return v4l_ioctl(f, VIDIOC_QUERYBUF, &buf->buf);
}

struct v4l_queue_buf_info {
	__u32 mem_offsets[VIDEO_MAX_PLANES];
	void *mmappings[VIDEO_MAX_PLANES];
	unsigned long userptrs[VIDEO_MAX_PLANES];
	int fds[VIDEO_MAX_PLANES];
};

struct v4l_queue {
	unsigned type;
	unsigned memory;
	unsigned buffers;
	unsigned mappings;
	unsigned num_planes;
	unsigned capabilities;
	unsigned max_num_buffers;

	__u32 lengths[VIDEO_MAX_PLANES];
	struct v4l_queue_buf_info _bufs_info[VIDEO_MAX_FRAME];
	struct v4l_queue_buf_info *bufs_info;
};

static inline void v4l_queue_init(struct v4l_queue *q,
		unsigned type, unsigned memory)
{
	unsigned i, p;

	memset(q, 0, sizeof(*q));
	q->type = type;
	q->memory = memory;
	q->max_num_buffers = VIDEO_MAX_FRAME;
	q->bufs_info = q->_bufs_info;

	for (i = 0; i < VIDEO_MAX_FRAME; i++)
		for (p = 0; p < VIDEO_MAX_PLANES; p++)
			q->bufs_info[i].fds[p] = -1;
}

static inline int v4l_queue_alloc_bufs_info(struct v4l_queue *q)
{
	struct v4l_queue_buf_info *bi;
	unsigned i, p;

	if (q->max_num_buffers <= VIDEO_MAX_FRAME)
		return 0;
	bi = (struct v4l_queue_buf_info *)calloc(q->max_num_buffers, sizeof(*bi));
	if (!bi)
		return -ENOMEM;
	for (i = 0; i < VIDEO_MAX_FRAME; i++)
		bi[i] = q->bufs_info[i];
	for (i = VIDEO_MAX_FRAME; i < q->max_num_buffers; i++)
		for (p = 0; p < VIDEO_MAX_PLANES; p++)
			bi[i].fds[p] = -1;
	q->bufs_info = bi;
	return 0;
}

static inline void v4l_queue_free_bufs_info(struct v4l_queue *q)
{
	if (q->bufs_info != q->_bufs_info)
		free(q->bufs_info);
	q->bufs_info = q->_bufs_info;
}

static inline unsigned v4l_queue_g_type(const struct v4l_queue *q) { return q->type; }
static inline unsigned v4l_queue_g_memory(const struct v4l_queue *q) { return q->memory; }
static inline unsigned v4l_queue_g_buffers(const struct v4l_queue *q) { return q->buffers; }
static inline unsigned v4l_queue_g_max_num_buffers(const struct v4l_queue *q) { return q->max_num_buffers; }
static inline unsigned v4l_queue_g_mappings(const struct v4l_queue *q) { return q->mappings; }
static inline unsigned v4l_queue_g_num_planes(const struct v4l_queue *q) { return q->num_planes; }
static inline unsigned v4l_queue_g_capabilities(const struct v4l_queue *q) { return q->capabilities; }

static inline __u32 v4l_queue_g_length(const struct v4l_queue *q, unsigned plane)
{
	return q->lengths[plane];
}

static inline __u32 v4l_queue_g_mem_offset(const struct v4l_queue *q, unsigned index, unsigned plane)
{
	return q->bufs_info[index].mem_offsets[plane];
}

static inline void v4l_queue_s_mmapping(struct v4l_queue *q, unsigned index, unsigned plane, void *m)
{
	q->bufs_info[index].mmappings[plane] = m;
}

static inline void *v4l_queue_g_mmapping(const struct v4l_queue *q, unsigned index, unsigned plane)
{
	if (index >= v4l_queue_g_mappings(q) || plane >= v4l_queue_g_num_planes(q))
		return NULL;
	return q->bufs_info[index].mmappings[plane];
}

static inline void v4l_queue_s_userptr(struct v4l_queue *q, unsigned index, unsigned plane, void *m)
{
	q->bufs_info[index].userptrs[plane] = (unsigned long)m;
}

static inline void *v4l_queue_g_userptr(const struct v4l_queue *q, unsigned index, unsigned plane)
{
	if (index >= v4l_queue_g_buffers(q) || plane >= v4l_queue_g_num_planes(q))
		return NULL;
	return (void *)q->bufs_info[index].userptrs[plane];
}

static inline void v4l_queue_s_fd(struct v4l_queue *q, unsigned index, unsigned plane, int fd)
{
	q->bufs_info[index].fds[plane] = fd;
}

static inline int v4l_queue_g_fd(const struct v4l_queue *q, unsigned index, unsigned plane)
{
	return q->bufs_info[index].fds[plane];
}

static inline void *v4l_queue_g_dataptr(const struct v4l_queue *q, unsigned index, unsigned plane)
{
	if (q->memory == V4L2_MEMORY_USERPTR)
		return v4l_queue_g_userptr(q, index, plane);
	return v4l_queue_g_mmapping(q, index, plane);
}

static inline int v4l_queue_remove_bufs(struct v4l_fd *f, struct v4l_queue *q, unsigned index, unsigned count)
{
	struct v4l2_remove_buffers removebufs;
	int ret;

	memset(&removebufs, 0, sizeof(removebufs));
	removebufs.type = q->type;
	removebufs.index = index;
	removebufs.count = count;

	ret = v4l_ioctl(f, VIDIOC_REMOVE_BUFS, &removebufs);
	if (!ret)
		q->buffers -= removebufs.count;

	return ret;
}

static inline int v4l_queue_querybufs(struct v4l_fd *f, struct v4l_queue *q, unsigned from, unsigned count)
{
	unsigned b, p, max = from + count;
	int ret;

	for (b = from; b < max; b++) {
		struct v4l_buffer buf;

		v4l_buffer_init(&buf, v4l_queue_g_type(q), v4l_queue_g_memory(q), b);
		ret = v4l_ioctl(f, VIDIOC_QUERYBUF, &buf.buf);
		if (ret)
			return ret;
		if (b == 0) {
			q->num_planes = v4l_buffer_g_num_planes(&buf);
			for (p = 0; p < v4l_queue_g_num_planes(q); p++)
				q->lengths[p] = v4l_buffer_g_length(&buf, p);
		}
		if (q->memory == V4L2_MEMORY_MMAP)
			for (p = 0; p < q->num_planes; p++)
				q->bufs_info[b].mem_offsets[p] = v4l_buffer_g_mem_offset(&buf, p);
	}
	return 0;
}

static inline int v4l_queue_reqbufs(struct v4l_fd *f,
		struct v4l_queue *q, unsigned count, unsigned int flags = 0)
{
	struct v4l2_create_buffers createbufs;
	struct v4l2_requestbuffers reqbufs;
	int ret;

	reqbufs.type = q->type;
	reqbufs.memory = q->memory;
	reqbufs.count = count;
	reqbufs.flags = flags;
	/*
	 * Problem: if REQBUFS returns an error, did it free any old
	 * buffers or not?
	 */
	ret = v4l_ioctl(f, VIDIOC_REQBUFS, &reqbufs);
	if (ret)
		return ret;
	q->buffers = reqbufs.count;
	q->capabilities = reqbufs.capabilities;

	if (q->buffers) {
		memset(&createbufs, 0, sizeof(createbufs));
		createbufs.format.type = q->type;
		createbufs.memory = q->memory;
		if (!v4l_ioctl(f, VIDIOC_CREATE_BUFS, &createbufs)) {
			q->capabilities = createbufs.capabilities;
			if (q->bufs_info == q->_bufs_info &&
			    (q->capabilities & V4L2_BUF_CAP_SUPPORTS_MAX_NUM_BUFFERS)) {
				q->max_num_buffers = createbufs.max_num_buffers;
				v4l_queue_alloc_bufs_info(q);
			}
		}
	}
	return v4l_queue_querybufs(f, q, 0, reqbufs.count);
}

static inline bool v4l_queue_has_create_bufs(struct v4l_fd *f, const struct v4l_queue *q)
{
	struct v4l2_create_buffers createbufs;

	memset(&createbufs, 0, sizeof(createbufs));
	createbufs.format.type = q->type;
	createbufs.memory = q->memory;
	return v4l_ioctl(f, VIDIOC_CREATE_BUFS, &createbufs) == 0;
}

static inline int v4l_queue_create_bufs(struct v4l_fd *f,
		struct v4l_queue *q, unsigned count,
		const struct v4l2_format *fmt, unsigned int flags = 0)
{
	struct v4l2_create_buffers createbufs;
	int ret;

	createbufs.format.type = q->type;
	createbufs.memory = q->memory;
	createbufs.count = count;
	createbufs.flags = flags;
	if (fmt) {
		createbufs.format = *fmt;
	} else {
		ret = v4l_g_fmt(f, &createbufs.format, q->type);
		if (ret)
			return ret;
	}
	memset(createbufs.reserved, 0, sizeof(createbufs.reserved));
	ret = v4l_ioctl(f, VIDIOC_CREATE_BUFS, &createbufs);
	if (ret)
		return ret;
	if (q->buffers == 0) {
		q->capabilities = createbufs.capabilities;
		if (q->bufs_info == q->_bufs_info &&
		    (q->capabilities & V4L2_BUF_CAP_SUPPORTS_MAX_NUM_BUFFERS)) {
			q->max_num_buffers = createbufs.max_num_buffers;
			v4l_queue_alloc_bufs_info(q);
		}
	}
	q->buffers += createbufs.count;
	return v4l_queue_querybufs(f, q, createbufs.index, createbufs.count);
}

static inline int v4l_queue_mmap_bufs(struct v4l_fd *f,
		struct v4l_queue *q, unsigned from)
{
	unsigned b, p;

	if (q->memory != V4L2_MEMORY_MMAP && q->memory != V4L2_MEMORY_DMABUF)
		return 0;

	for (b = from; b < v4l_queue_g_buffers(q); b++) {
		for (p = 0; p < v4l_queue_g_num_planes(q); p++) {
			void *m = MAP_FAILED;

			if (q->memory == V4L2_MEMORY_MMAP)
				m = v4l_mmap(f, v4l_queue_g_length(q, p), v4l_queue_g_mem_offset(q, b, p));
			else if (q->memory == V4L2_MEMORY_DMABUF)
				m = mmap(NULL, v4l_queue_g_length(q, p),
						PROT_READ | PROT_WRITE, MAP_SHARED,
						v4l_queue_g_fd(q, b, p), 0);

			if (m == MAP_FAILED)
				return errno;
			v4l_queue_s_mmapping(q, b, p, m);
		}
	}
	q->mappings = b;
	return 0;
}
static inline int v4l_queue_munmap_bufs(struct v4l_fd *f, struct v4l_queue *q,
					unsigned from)
{
	unsigned b, p;
	int ret = 0;

	if (q->memory != V4L2_MEMORY_MMAP && q->memory != V4L2_MEMORY_DMABUF)
		return 0;

	for (b = from; b < v4l_queue_g_mappings(q); b++) {
		for (p = 0; p < v4l_queue_g_num_planes(q); p++) {
			void *m = v4l_queue_g_mmapping(q, b, p);

			if (m == NULL)
				continue;

			if (q->memory == V4L2_MEMORY_MMAP)
				ret = v4l_munmap(f, m, v4l_queue_g_length(q, p));
			else if (q->memory == V4L2_MEMORY_DMABUF)
				ret = munmap(m, v4l_queue_g_length(q, p)) ? errno : 0;
			if (ret)
				return ret;
			v4l_queue_s_mmapping(q, b, p, NULL);
		}
	}
	q->mappings = from;
	return 0;
}

static inline int v4l_queue_alloc_bufs(struct v4l_fd *f,
		struct v4l_queue *q, unsigned from)
{
	unsigned b, p;

	if (q->memory != V4L2_MEMORY_USERPTR)
		return 0;
	for (b = from; b < v4l_queue_g_buffers(q); b++) {
		for (p = 0; p < v4l_queue_g_num_planes(q); p++) {
			void *m = malloc(v4l_queue_g_length(q, p));

			if (m == NULL)
				return errno;
			v4l_queue_s_userptr(q, b, p, m);
		}
	}
	return 0;
}

static inline int v4l_queue_free_bufs(struct v4l_queue *q, unsigned from)
{
	unsigned b, p;

	if (q->memory != V4L2_MEMORY_USERPTR)
		return 0;
	for (b = from; b < v4l_queue_g_buffers(q); b++) {
		for (p = 0; p < v4l_queue_g_num_planes(q); p++) {
			free(v4l_queue_g_userptr(q, b, p));
			v4l_queue_s_userptr(q, b, p, NULL);
		}
	}
	return 0;
}

static inline int v4l_queue_obtain_bufs(struct v4l_fd *f,
		struct v4l_queue *q, unsigned from)
{
	if (q->memory == V4L2_MEMORY_USERPTR)
		return v4l_queue_alloc_bufs(f, q, from);
	return v4l_queue_mmap_bufs(f, q, from);
}

static inline int v4l_queue_release_bufs(struct v4l_fd *f, struct v4l_queue *q,
					 unsigned from)
{
	if (q->memory == V4L2_MEMORY_USERPTR)
		return v4l_queue_free_bufs(q, from);
	return v4l_queue_munmap_bufs(f, q, from);
}


static inline bool v4l_queue_has_expbuf(struct v4l_fd *f)
{
	struct v4l2_exportbuffer expbuf;

	memset(&expbuf, 0, sizeof(expbuf));
	return v4l_ioctl(f, VIDIOC_EXPBUF, &expbuf) != ENOTTY;
}

static inline int v4l_queue_export_bufs(struct v4l_fd *f, struct v4l_queue *q,
					unsigned exp_type)
{
	struct v4l2_exportbuffer expbuf;
	unsigned b, p;
	int ret = 0;

	expbuf.type = exp_type ? : f->type;
	expbuf.flags = O_RDWR;
	memset(expbuf.reserved, 0, sizeof(expbuf.reserved));
	for (b = 0; b < v4l_queue_g_buffers(q); b++) {
		expbuf.index = b;
		for (p = 0; p < v4l_queue_g_num_planes(q); p++) {
			expbuf.plane = p;
			ret = v4l_ioctl(f, VIDIOC_EXPBUF, &expbuf);
			if (ret)
				return ret;
			v4l_queue_s_fd(q, b, p, expbuf.fd);
		}
	}
	return 0;
}

static inline void v4l_queue_close_exported_fds(struct v4l_queue *q)
{
	unsigned b, p;

	if (q->memory != V4L2_MEMORY_MMAP)
		return;

	for (b = 0; b < v4l_queue_g_buffers(q); b++) {
		for (p = 0; p < v4l_queue_g_num_planes(q); p++) {
			int fd = v4l_queue_g_fd(q, b, p);

			if (fd != -1) {
				close(fd);
				v4l_queue_s_fd(q, b, p, -1);
			}
		}
	}
}

static inline void v4l_queue_free(struct v4l_fd *f, struct v4l_queue *q)
{
	v4l_ioctl(f, VIDIOC_STREAMOFF, &q->type);
	v4l_queue_release_bufs(f, q, 0);
	v4l_queue_close_exported_fds(q);
	v4l_queue_reqbufs(f, q, 0, 0);
	v4l_queue_free_bufs_info(q);
}

static inline void v4l_queue_buffer_update(const struct v4l_queue *q,
					   struct v4l_buffer *buf, unsigned index)
{
	unsigned p;

	if (v4l_type_is_planar(q->type)) {
		buf->buf.length = v4l_queue_g_num_planes(q);
		buf->buf.m.planes = buf->planes;
	}
	switch (q->memory) {
	case V4L2_MEMORY_USERPTR:
		for (p = 0; p < v4l_queue_g_num_planes(q); p++) {
			v4l_buffer_s_userptr(buf, p, v4l_queue_g_userptr(q, index, p));
			v4l_buffer_s_length(buf, p, v4l_queue_g_length(q, p));
		}
		break;
	case V4L2_MEMORY_DMABUF:
		for (p = 0; p < v4l_queue_g_num_planes(q); p++)
			v4l_buffer_s_fd(buf, p, v4l_queue_g_fd(q, index, p));
		break;
	default:
		break;
	}
}

static inline void v4l_queue_buffer_init(const struct v4l_queue *q, struct v4l_buffer *buf, unsigned index)
{
	v4l_buffer_init(buf, v4l_queue_g_type(q), v4l_queue_g_memory(q), index);
	v4l_queue_buffer_update(q, buf, index);
}

static inline int v4l_query_ext_ctrl(v4l_fd *f, struct v4l2_query_ext_ctrl *qec,
		bool next_ctrl, bool next_compound)
{
	struct v4l2_queryctrl qc;
	int ret;

	if (next_compound && !f->have_query_ext_ctrl) {
		if (!next_ctrl)
			return -EINVAL;
		next_compound = false;
	}
	if (next_compound)
		qec->id |= V4L2_CTRL_FLAG_NEXT_COMPOUND;
	if (next_ctrl) {
		if (f->have_next_ctrl)
			qec->id |= V4L2_CTRL_FLAG_NEXT_CTRL;
		else
			qec->id = qec->id ? qec->id + 1 : V4L2_CID_BASE;
	}
	if (f->have_query_ext_ctrl)
		return v4l_ioctl(f, VIDIOC_QUERY_EXT_CTRL, qec);

	for (;;) {
		if (qec->id == V4L2_CID_LASTP1 && next_ctrl)
			qec->id = V4L2_CID_PRIVATE_BASE;
		qc.id = qec->id;
		ret = v4l_ioctl(f, VIDIOC_QUERYCTRL, &qc);
		if (!ret)
			break;
		if (ret != EINVAL)
			return ret;
		if (!next_ctrl || f->have_next_ctrl)
			return ret;
		if (qec->id >= V4L2_CID_PRIVATE_BASE)
			return ret;
		qec->id++;
	}
	qec->id = qc.id;
	qec->type = qc.type;
	memcpy(qec->name, qc.name, sizeof(qec->name));
	qec->minimum = qc.minimum;
	if (qc.type == V4L2_CTRL_TYPE_BITMASK) {
		qec->maximum = (__u32)qc.maximum;
		qec->default_value = (__u32)qc.default_value;
	} else {
		qec->maximum = qc.maximum;
		qec->default_value = qc.default_value;
	}
	qec->step = qc.step;
	qec->flags = qc.flags;
	qec->elems = 1;
	qec->nr_of_dims = 0;
	memset(qec->dims, 0, sizeof(qec->dims));
	switch (qec->type) {
	case V4L2_CTRL_TYPE_INTEGER64:
		qec->elem_size = sizeof(__s64);
		qec->minimum = 0x8000000000000000ULL;
		qec->maximum = 0x7fffffffffffffffULL;
		qec->step = 1;
		break;
	case V4L2_CTRL_TYPE_STRING:
		qec->elem_size = qc.maximum + 1;
		qec->flags |= V4L2_CTRL_FLAG_HAS_PAYLOAD;
		break;
	default:
		qec->elem_size = sizeof(__s32);
		break;
	}
	memset(qec->reserved, 0, sizeof(qec->reserved));
	return 0;
}

static inline int v4l_g_ext_ctrls(v4l_fd *f, struct v4l2_ext_controls *ec)
{
	unsigned i;

	if (f->have_ext_ctrls)
		return v4l_ioctl(f, VIDIOC_G_EXT_CTRLS, ec);
	if (ec->count == 0)
		return 0;
	for (i = 0; i < ec->count; i++) {
		struct v4l2_control c = { ec->controls[i].id, 0 };
		int ret = v4l_ioctl(f, VIDIOC_G_CTRL, &c);

		if (ret) {
			ec->error_idx = i;
			return ret;
		}
		ec->controls[i].value = c.value;
	}
	return 0;
}

static inline int v4l_s_ext_ctrls(v4l_fd *f, struct v4l2_ext_controls *ec)
{
	unsigned i;

	if (f->have_ext_ctrls)
		return v4l_ioctl(f, VIDIOC_S_EXT_CTRLS, ec);
	if (ec->count == 0)
		return 0;
	for (i = 0; i < ec->count; i++) {
		struct v4l2_control c = { ec->controls[i].id, ec->controls[i].value };
		int ret = v4l_ioctl(f, VIDIOC_S_CTRL, &c);

		if (ret) {
			ec->error_idx = i;
			return ret;
		}
	}
	return 0;
}

static inline int v4l_try_ext_ctrls(v4l_fd *f, struct v4l2_ext_controls *ec)
{
	unsigned i;

	if (f->have_ext_ctrls)
		return v4l_ioctl(f, VIDIOC_TRY_EXT_CTRLS, ec);
	if (ec->count == 0)
		return 0;
	for (i = 0; i < ec->count; i++) {
		struct v4l2_queryctrl qc;
		int ret;

		memset(&qc, 0, sizeof(qc));
		qc.id = ec->controls[i].id;
		ret = v4l_ioctl(f, VIDIOC_QUERYCTRL, &qc);

		if (ret || qc.type == V4L2_CTRL_TYPE_STRING ||
			   qc.type == V4L2_CTRL_TYPE_INTEGER64) {
			ec->error_idx = i;
			return ret ? ret : EINVAL;
		}
	}
	return 0;
}

static inline int v4l_g_selection(v4l_fd *f, struct v4l2_selection *sel)
{
	struct v4l2_cropcap cc;
	struct v4l2_crop crop;
	int ret;

	if (f->have_selection)
		return v4l_ioctl(f, VIDIOC_G_SELECTION, sel);
	crop.type = sel->type;
	cc.type = sel->type;
	ret = v4l_ioctl(f, VIDIOC_CROPCAP, &cc);
	if (ret)
		return ret;
	ret = v4l_ioctl(f, VIDIOC_G_CROP, &crop);
	if (ret)
		return ret;
	if (sel->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
		switch (sel->target) {
		case V4L2_SEL_TGT_CROP:
			sel->r = crop.c;
			return 0;
		case V4L2_SEL_TGT_CROP_DEFAULT:
			sel->r = cc.defrect;
			return 0;
		case V4L2_SEL_TGT_CROP_BOUNDS:
			sel->r = cc.bounds;
			return 0;
		default:
			return EINVAL;
		}
	}
	switch (sel->target) {
	case V4L2_SEL_TGT_COMPOSE:
		sel->r = crop.c;
		return 0;
	case V4L2_SEL_TGT_COMPOSE_DEFAULT:
		sel->r = cc.defrect;
		return 0;
	case V4L2_SEL_TGT_COMPOSE_BOUNDS:
		sel->r = cc.bounds;
		return 0;
	default:
		return EINVAL;
	}
}

static inline int v4l_s_selection(v4l_fd *f, struct v4l2_selection *sel)
{
	struct v4l2_crop crop;
	int ret;

	if (f->have_selection)
		return v4l_ioctl(f, VIDIOC_S_SELECTION, sel);
	crop.type = sel->type;
	ret = v4l_ioctl(f, VIDIOC_G_CROP, &crop);
	if (ret)
		return ret;
	if (sel->type == V4L2_BUF_TYPE_VIDEO_CAPTURE &&
	    sel->target == V4L2_SEL_TGT_CROP) {
		crop.c = sel->r;
		return v4l_ioctl(f, VIDIOC_S_CROP, &crop);
	}
	if (sel->type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
	    sel->target == V4L2_SEL_TGT_COMPOSE) {
		crop.c = sel->r;
		return v4l_ioctl(f, VIDIOC_S_CROP, &crop);
	}
	return EINVAL;
}

static inline void v4l_frame_selection(struct v4l2_selection *sel, bool to_frame)
{
	switch (sel->target) {
	case V4L2_SEL_TGT_CROP:
	case V4L2_SEL_TGT_CROP_DEFAULT:
	case V4L2_SEL_TGT_CROP_BOUNDS:
		if (V4L2_TYPE_IS_CAPTURE(sel->type))
			return;
		break;
	case V4L2_SEL_TGT_COMPOSE:
	case V4L2_SEL_TGT_COMPOSE_DEFAULT:
	case V4L2_SEL_TGT_COMPOSE_BOUNDS:
	case V4L2_SEL_TGT_COMPOSE_PADDED:
		if (V4L2_TYPE_IS_OUTPUT(sel->type))
			return;
		break;
	default:
		return;
	}
	if (to_frame) {
		sel->r.top *= 2;
		sel->r.height *= 2;
	} else {
		sel->r.top /= 2;
		sel->r.height /= 2;
	}
}

static inline int v4l_g_frame_selection(v4l_fd *f, struct v4l2_selection *sel, __u32 field)
{
	int ret = v4l_g_selection(f, sel);

	if (V4L2_FIELD_HAS_T_OR_B(field))
		v4l_frame_selection(sel, true);
	return ret;
}

static inline int v4l_s_frame_selection(v4l_fd *f, struct v4l2_selection *sel, __u32 field)
{
	int ret;

	if (V4L2_FIELD_HAS_T_OR_B(field))
		v4l_frame_selection(sel, false);
	ret = v4l_s_selection(f, sel);
	if (V4L2_FIELD_HAS_T_OR_B(field))
		v4l_frame_selection(sel, true);
	return ret;
}

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif
