/* v4l2-api: low-level wrapper around v4l2 devices
 *
 * Copyright (C) 2009 Hans Verkuil <hverkuil@xs4all.nl>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <errno.h>
#include <limits.h>
#include <libv4l2.h>
#include "v4l2-api.h"

bool v4l2::open(const QString &device, bool useWrapper)
{
	m_device = device;
	m_useWrapper = useWrapper;
	m_fd = ::open(device.toAscii(), O_RDWR | O_NONBLOCK);
	if (m_fd < 0) {
		error("Cannot open " + device);
		return false;
	}
	if (!querycap(m_capability)) {
		::close(m_fd);
		m_fd = -1;
		error(device + " is not a V4L2 device");
		return false;
	}

	if (m_useWrapper) {
		int fd = ::v4l2_fd_open(m_fd, V4L2_ENABLE_ENUM_FMT_EMULATION);

		if (fd < 0) {
			m_useWrapper = false;
			error("Cannot use libv4l2 wrapper for " + device);
		}
	}
	return true;
}

void v4l2::close()
{
	if (useWrapper())
		::v4l2_close(m_fd);
	else
		::close(m_fd);
	m_fd = -1;
}

int v4l2::ioctl(unsigned cmd, void *arg)
{
	if (useWrapper())
		return v4l2_ioctl(m_fd, cmd, arg);
	return ::ioctl(m_fd, cmd, arg);
}

bool v4l2::ioctl(const QString &descr, unsigned cmd, void *arg)
{
	clear();
	int err = ioctl(cmd, arg);

	if (err < 0) {
		QString s = strerror(errno);
		error(descr + ": " + s);
	}
	return err >= 0;
}

int v4l2::read(unsigned char *p, int size)
{
	if (useWrapper())
		return v4l2_read(m_fd, p, size);
	return ::read(m_fd, p, size);
}

void *v4l2::mmap(size_t length, __off64_t offset)
{
	if (useWrapper())
		return v4l2_mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, offset);
	return ::mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, offset);
}

int v4l2::munmap(void *start, size_t length)
{
	if (useWrapper())
		return v4l2_munmap(start, length);
	return ::munmap(start, length);
}

void v4l2::error(const QString &error)
{
	if (!error.isEmpty())
		fprintf(stderr, "%s\n", error.toAscii().data());
}

QString v4l2::pixfmt2s(unsigned id)
{
	QString pixfmt;

	pixfmt += (char)(id & 0xff);
	pixfmt += (char)((id >> 8) & 0xff);
	pixfmt += (char)((id >> 16) & 0xff);
	pixfmt += (char)((id >> 24) & 0xff);
	return pixfmt;
}

bool v4l2::querycap(v4l2_capability &cap)
{
	memset(&cap, 0, sizeof(cap));
	return ioctl(VIDIOC_QUERYCAP, &cap) >= 0;
}

bool v4l2::queryctrl(v4l2_queryctrl &qc)
{
	return ioctl(VIDIOC_QUERYCTRL, &qc) >= 0;
}

bool v4l2::querymenu(v4l2_querymenu &qm)
{
	return ioctl(VIDIOC_QUERYMENU, &qm) >= 0;
}

bool v4l2::g_tuner(v4l2_tuner &tuner)
{
	memset(&tuner, 0, sizeof(tuner));
	if (ioctl(VIDIOC_G_TUNER, &tuner) < 0)
		return false;
	if (tuner.rangehigh > INT_MAX)
		tuner.rangehigh = INT_MAX;
	return true;
}

bool v4l2::g_input(int &input)
{
	return ioctl(VIDIOC_G_INPUT, &input) >= 0;
}

bool v4l2::s_input(int input)
{
	return ioctl("Set Input", VIDIOC_S_INPUT, &input);
}

bool v4l2::g_output(int &output)
{
	return ioctl(VIDIOC_G_OUTPUT, &output) >= 0;
}

bool v4l2::s_output(int output)
{
	return ioctl("Set Output", VIDIOC_S_OUTPUT, &output);
}

bool v4l2::g_audio(v4l2_audio &audio)
{
	memset(&audio, 0, sizeof(audio));
	return ioctl(VIDIOC_G_AUDIO, &audio) >= 0;
}

bool v4l2::s_audio(int input)
{
	v4l2_audio audio;

	memset(&audio, 0, sizeof(audio));
	audio.index = input;
	return ioctl("Set Audio Input", VIDIOC_S_AUDIO, &audio);
}

bool v4l2::g_audout(v4l2_audioout &audout)
{
	memset(&audout, 0, sizeof(audout));
	return ioctl(VIDIOC_G_AUDOUT, &audout) >= 0;
}

bool v4l2::s_audout(int output)
{
	v4l2_audioout audout;

	memset(&audout, 0, sizeof(audout));
	audout.index = output;
	return ioctl("Set Audio Output", VIDIOC_S_AUDOUT, &audout);
}

bool v4l2::g_std(v4l2_std_id &std)
{
	return ioctl(VIDIOC_G_STD, &std) >= 0;
}

bool v4l2::s_std(v4l2_std_id std)
{
	return ioctl("Set TV Standard", VIDIOC_S_STD, &std);
}

bool v4l2::g_dv_preset(__u32 &preset)
{
	struct v4l2_dv_preset p;
	int err;

	memset(&p, 0, sizeof(p));
	err = ioctl(VIDIOC_G_DV_PRESET, &p);
	preset = p.preset;
	return err >= 0;
}

bool v4l2::s_dv_preset(__u32 preset)
{
	struct v4l2_dv_preset p;

	memset(&p, 0, sizeof(p));
	p.preset = preset;
	return ioctl("Set Preset", VIDIOC_S_DV_PRESET, &p);
}


bool v4l2::g_frequency(v4l2_frequency &freq)
{
	memset(&freq, 0, sizeof(freq));
	freq.type = V4L2_TUNER_ANALOG_TV;
	return ioctl(VIDIOC_G_FREQUENCY, &freq) >= 0;
}

bool v4l2::s_frequency(v4l2_frequency &freq)
{
	return ioctl("Set Frequency", VIDIOC_S_FREQUENCY, &freq);
}

bool v4l2::s_frequency(int val)
{
	v4l2_frequency f;

	memset(&f, 0, sizeof(f));
	f.type = V4L2_TUNER_ANALOG_TV;
	f.frequency = val;
	return s_frequency(f);
}

bool v4l2::g_fmt_cap(v4l2_format &fmt)
{
	memset(&fmt, 0, sizeof(fmt));
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	return ioctl(VIDIOC_G_FMT, &fmt) >= 0;
}

bool v4l2::g_fmt_out(v4l2_format &fmt)
{
	memset(&fmt, 0, sizeof(fmt));
	fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	return ioctl(VIDIOC_G_FMT, &fmt) >= 0;
}

bool v4l2::try_fmt(v4l2_format &fmt)
{
	fmt.fmt.pix.field = V4L2_FIELD_ANY;
	return ioctl("Try Capture Format", VIDIOC_TRY_FMT, &fmt);
}

bool v4l2::s_fmt(v4l2_format &fmt)
{
	return ioctl("Set Capture Format", VIDIOC_S_FMT, &fmt);
}

bool v4l2::enum_input(v4l2_input &in, bool init, int index)
{
	if (init) {
		memset(&in, 0, sizeof(in));
		in.index = index;
	} else {
		in.index++;
	}
	return ioctl(VIDIOC_ENUMINPUT, &in) >= 0;
}

bool v4l2::enum_output(v4l2_output &out, bool init, int index)
{
	if (init) {
		memset(&out, 0, sizeof(out));
		out.index = index;
	} else {
		out.index++;
	}
	return ioctl(VIDIOC_ENUMOUTPUT, &out) >= 0;
}

bool v4l2::enum_audio(v4l2_audio &audio, bool init)
{
	if (init)
		memset(&audio, 0, sizeof(audio));
	else
		audio.index++;
	return ioctl(VIDIOC_ENUMAUDIO, &audio) >= 0;
}

bool v4l2::enum_audout(v4l2_audioout &audout, bool init)
{
	if (init)
		memset(&audout, 0, sizeof(audout));
	else
		audout.index++;
	return ioctl(VIDIOC_ENUMAUDOUT, &audout) >= 0;
}

bool v4l2::enum_std(v4l2_standard &std, bool init, int index)
{
	if (init) {
		memset(&std, 0, sizeof(std));
		std.index = index;
	} else {
		std.index++;
	}
	return ioctl(VIDIOC_ENUMSTD, &std) >= 0;
}

bool v4l2::enum_dv_preset(v4l2_dv_enum_preset &preset, bool init, int index)
{
	if (init) {
		memset(&preset, 0, sizeof(preset));
		preset.index = index;
	} else {
		preset.index++;
	}
	return ioctl(VIDIOC_ENUM_DV_PRESETS, &preset) >= 0;
}

bool v4l2::enum_fmt_cap(v4l2_fmtdesc &fmt, bool init, int index)
{
	if (init) {
		memset(&fmt, 0, sizeof(fmt));
		fmt.index = index;
	} else {
		fmt.index++;
	}
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	return ioctl(VIDIOC_ENUM_FMT, &fmt) >= 0;
}

bool v4l2::enum_fmt_out(v4l2_fmtdesc &fmt, bool init, int index)
{
	if (init) {
		memset(&fmt, 0, sizeof(fmt));
		fmt.index = index;
	} else {
		fmt.index++;
	}
	fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	return ioctl(VIDIOC_ENUM_FMT, &fmt) >= 0;
}

bool v4l2::enum_framesizes(v4l2_frmsizeenum &frm, __u32 init_pixfmt, int index)
{
	if (init_pixfmt) {
		memset(&frm, 0, sizeof(frm));
		frm.pixel_format = init_pixfmt;
		frm.index = index;
	} else {
		frm.index++;
	}
	return ioctl(VIDIOC_ENUM_FRAMESIZES, &frm) >= 0;
}

bool v4l2::enum_frameintervals(v4l2_frmivalenum &frm, __u32 init_pixfmt, __u32 w, __u32 h, int index)
{
	if (init_pixfmt) {
		memset(&frm, 0, sizeof(frm));
		frm.pixel_format = init_pixfmt;
		frm.width = w;
		frm.height = h;
		frm.index = index;
	} else {
		frm.index++;
	}
	return ioctl(VIDIOC_ENUM_FRAMEINTERVALS, &frm) >= 0;
}

bool v4l2::reqbufs_user_cap(v4l2_requestbuffers &reqbuf, int count)
{
	memset(&reqbuf, 0, sizeof (reqbuf));
	reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	reqbuf.memory = V4L2_MEMORY_USERPTR;
	reqbuf.count = count;

	return ioctl(VIDIOC_REQBUFS, &reqbuf) >= 0;
}

bool v4l2::reqbufs_mmap_cap(v4l2_requestbuffers &reqbuf, int count)
{
	memset(&reqbuf, 0, sizeof (reqbuf));
	reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	reqbuf.memory = V4L2_MEMORY_MMAP;
	reqbuf.count = count;

	return ioctl(VIDIOC_REQBUFS, &reqbuf) >= 0;
}

bool v4l2::dqbuf_mmap_cap(v4l2_buffer &buf)
{
	memset(&buf, 0, sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;
	return ioctl(VIDIOC_DQBUF, &buf) >= 0;
}

bool v4l2::dqbuf_user_cap(v4l2_buffer &buf)
{
	memset(&buf, 0, sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_USERPTR;
	return ioctl(VIDIOC_DQBUF, &buf) >= 0;
}

bool v4l2::qbuf(v4l2_buffer &buf)
{
	return ioctl(VIDIOC_QBUF, &buf) >= 0;
}

bool v4l2::qbuf_mmap_cap(int index)
{
	v4l2_buffer buf;

	memset(&buf, 0, sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;
	buf.index = index;
	return qbuf(buf);
}

bool v4l2::qbuf_user_cap(int index, void *ptr, int length)
{
	v4l2_buffer buf;

	memset(&buf, 0, sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_USERPTR;
	buf.m.userptr = (unsigned long)ptr;
	buf.length = length;
	buf.index = index;
	return qbuf(buf);
}

bool v4l2::streamon_cap()
{
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	return ioctl("Start Capture", VIDIOC_STREAMON, &type);
}

bool v4l2::streamoff_cap()
{
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	return ioctl("Stop Capture", VIDIOC_STREAMOFF, &type);
}

bool v4l2::reqbufs_user_out(v4l2_requestbuffers &reqbuf)
{
	memset(&reqbuf, 0, sizeof (reqbuf));
	reqbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	reqbuf.memory = V4L2_MEMORY_USERPTR;

	return ioctl(VIDIOC_REQBUFS, &reqbuf) >= 0;
}

bool v4l2::reqbufs_mmap_out(v4l2_requestbuffers &reqbuf, int count)
{
	memset(&reqbuf, 0, sizeof (reqbuf));
	reqbuf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	reqbuf.memory = V4L2_MEMORY_MMAP;
	reqbuf.count = count;

	return ioctl(VIDIOC_REQBUFS, &reqbuf) >= 0;
}

bool v4l2::dqbuf_mmap_out(v4l2_buffer &buf)
{
	memset(&buf, 0, sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	buf.memory = V4L2_MEMORY_MMAP;
	return ioctl("dqbuf", VIDIOC_DQBUF, &buf);
}

bool v4l2::dqbuf_user_out(v4l2_buffer &buf)
{
	memset(&buf, 0, sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	buf.memory = V4L2_MEMORY_USERPTR;
	return ioctl(VIDIOC_DQBUF, &buf) >= 0;
}

bool v4l2::qbuf_mmap_out(int index, int bytesused)
{
	v4l2_buffer buf;

	memset(&buf, 0, sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	buf.memory = V4L2_MEMORY_MMAP;
	buf.index = index;
	buf.bytesused = bytesused;
	return qbuf(buf);
}

bool v4l2::qbuf_user_out(void *ptr, int length)
{
	v4l2_buffer buf;

	memset(&buf, 0, sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	buf.memory = V4L2_MEMORY_USERPTR;
	buf.m.userptr = (unsigned long)ptr;
	buf.length = length;
	return qbuf(buf);
}

bool v4l2::streamon_out()
{
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_OUTPUT;

	return ioctl("Start Output", VIDIOC_STREAMON, &type);
}

bool v4l2::streamoff_out()
{
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_OUTPUT;

	return ioctl("Stop Output", VIDIOC_STREAMOFF, &type);
}
