/***************************************************************************
                           v4l2dev.cpp
                           -----------
    begin                : Sat Oct 24 2004
    copyright            : (C) 2004 by Dirk Ziegelmeier
    email                : dziegel@gmx.de
***************************************************************************/

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifdef __STRICT_ANSI__
#define FOO__STRICT_ANSI__
#undef __STRICT_ANSI__
#endif
#include <asm/types.h>
#ifdef FOO__STRICT_ANSI__
#define __STRICT_ANSI__
#undef FOO__STRICT_ANSI__
#endif

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <strings.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/mman.h>

#include <kdebug.h>
#include <klocale.h>

#include "kdetvv4lsetup/videodev2.h"

#include "v4l2dev.h"

#define _DEBUG

//#define DEBUG kDebug()
#define DEBUG kDebugDevNull()

// static void printBuffer(struct v4l2_buffer& desc);

V4L2Dev* V4L2Dev::getDevice( const QString &dev )
{
    int fd = ::open(dev.toLocal8Bit(), O_RDWR);
    if(fd < 0) {
        return NULL;
    }

    // ----------- Query capabilities
    kDebug() << "V4L2Dev: device \"" << dev << "\" capabilities: ";
    struct v4l2_capability desc;
    memset(&desc, 0, sizeof(desc));
    int rc = ::ioctl(fd, VIDIOC_QUERYCAP, &desc);
    if(rc >= 0) {
        kDebug() << "  Driver: " << (const char*)desc.driver << " "
              << ((desc.version>>16) & 0xFF) << "."
              << ((desc.version>> 8) & 0xFF) << "."
              << ((desc.version    ) & 0xFF) << endl;
        kDebug() << "  Card: " << (const char*)desc.card;
        kDebug() << "  Bus info: " << (const char*)desc.card;

        kDebug() << "  Capabilities:";
        if(desc.capabilities & V4L2_CAP_VIDEO_CAPTURE)
            kDebug() << "    Video capture";
        if(desc.capabilities & V4L2_CAP_VIDEO_OUTPUT)
            kDebug() << "    Video output";
        if(desc.capabilities & V4L2_CAP_VIDEO_OVERLAY)
            kDebug() << "    Video overlay";
        if(desc.capabilities & V4L2_CAP_VBI_CAPTURE)
            kDebug() << "    VBI capture";
        if(desc.capabilities & V4L2_CAP_VBI_OUTPUT)
            kDebug() << "    VBI output";
        if(desc.capabilities & V4L2_CAP_RDS_CAPTURE)
            kDebug() << "    RDS capture";
        if(desc.capabilities & V4L2_CAP_TUNER)
            kDebug() << "    Tuner IO";
        if(desc.capabilities & V4L2_CAP_AUDIO)
            kDebug() << "    Audio IO";
        if(desc.capabilities & V4L2_CAP_READWRITE)
            kDebug() << "    Read/Write interface";
        if(desc.capabilities & V4L2_CAP_ASYNCIO)
            kDebug() << "    Async IO interface";
        if(desc.capabilities & V4L2_CAP_STREAMING)
            kDebug() << "    Streaming interface";

        if(desc.capabilities & V4L2_CAP_STREAMING) {
            return new V4L2Dev(fd,
                               (const char*)desc.card,
                               (desc.capabilities & V4L2_CAP_READWRITE) != 0);
        }
    }

    ::close(fd);
    kWarning() << "Device does not support streaming interface or is not a V4L2 device.";
    return NULL;
}

V4L2Dev::V4L2Dev(int fd, const QString& name, bool supportsReadWrite)
    : _fd(fd),
      _streamingBufferCount(0),
      _streaming(false),
      _name(name),
      _isTuner(false),
      _supportsReadWrite(supportsReadWrite)
{
    bool ok;

    // Set priority
    v4l2_priority pdesc = V4L2_PRIORITY_DEFAULT;
    xioctl(VIDIOC_S_PRIORITY, &pdesc, EINVAL);

    // ----------- Enumerate grab formats
    DEBUG << "Enumerating grab formats: " << endl;
    ok = true;
    for(int i=0; ok; i++) {
        struct v4l2_fmtdesc desc;
        memset(&desc, 0, sizeof(desc));
        desc.index = i;
        desc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        ok = xioctl(VIDIOC_ENUM_FMT, &desc, EINVAL);

        if(ok) {
            DEBUG << "  " << (const char*)desc.description << endl;
            ImageFormat f = v4l2format2qvideoformat(desc.pixelformat);
            if(f != FORMAT_NONE) {
                _grabFormats = (ImageFormat)(_grabFormats | f);
            }
        }
    }

    // ----------- Enumerate video inputs
    DEBUG << "Enumerating video inputs: " << endl;
    ok = true;
    for(int i=0; ok; i++) {
        struct v4l2_input desc;
        memset(&desc, 0, sizeof(desc));
        desc.index = i;
        ok = xioctl(VIDIOC_ENUMINPUT, &desc, EINVAL);

        if(ok) {
            QString name = QString((const char*)desc.name).toLower();
            DEBUG << "  " << name << " (tuner: " << ((desc.type & V4L2_INPUT_TYPE_TUNER) != 0) << ")" << endl;
            if((desc.type & V4L2_INPUT_TYPE_TUNER) != 0) {
                _tunerForInput[name] = desc.tuner;
                _isTuner = true;
            } else {
                _tunerForInput[name] = -1;
            }
            _videoInputs[name] = i;
        }
    }

    // ----------- Enumerate video standarts
    DEBUG << "Enumerating video standarts: " << endl;
    ok = true;
    for(int i=0; ok; i++) {
        struct v4l2_standard desc;
        memset(&desc, 0, sizeof(desc));
        desc.index = i;
        ok = xioctl(VIDIOC_ENUMSTD, &desc, EINVAL);

        if(ok) {
            QString name = QString((const char*)desc.name).toLower();
            DEBUG << "  " << name << endl;
            _videoStandarts[name] = desc.id;
        }
    }

    // ----------- Enumerate controls
    DEBUG << "Enumerating public controls: " << endl;
    ok = true;
    for(int i=V4L2_CID_BASE;
        i < V4L2_CID_LASTP1;
        i++) {
        struct v4l2_queryctrl desc;
        memset(&desc, 0, sizeof(desc));
        desc.id = i;
        ok = xioctl(VIDIOC_QUERYCTRL, &desc, EINVAL);

        if( ok && !(desc.flags & V4L2_CTRL_FLAG_DISABLED)) {
            _controls[(const char*)desc.name] = parseControl(desc);
            _controls[(const char*)desc.name]->advanced = false;
        }
    }
    DEBUG << "Enumerating private controls: " << endl;
    ok = true;
    for(int i=V4L2_CID_PRIVATE_BASE; ok; i++) {
        struct v4l2_queryctrl desc;
        memset(&desc, 0, sizeof(desc));
        desc.id = i;
        ok = xioctl(VIDIOC_QUERYCTRL, &desc, EINVAL);

        if( ok && !(desc.flags & V4L2_CTRL_FLAG_DISABLED)) {
            _controls[(const char*)desc.name] = parseControl(desc);
            _controls[(const char*)desc.name]->advanced = true;
        }
    }

    // Init input dependent attributes
    setSource(source());

    _audioMap[i18n("Mono")]       = V4L2_TUNER_SUB_MONO;
    _audioMap[i18n("Stereo")]     = V4L2_TUNER_SUB_STEREO;
    _audioMap[i18n("Language 1")] = V4L2_TUNER_SUB_LANG1;
    _audioMap[i18n("Language 2")] = V4L2_TUNER_SUB_LANG2;

    _audioModes += _audioMap.keys();
}

V4L2Dev::~V4L2Dev()
{
    cleanup();
    ::close(_fd);
}

const QString& V4L2Dev::name()
{
    return _name;
}

// -----------------------------------------------------------------------

V4L2Dev::controlDescriptor* V4L2Dev::parseControl(const v4l2_queryctrl& desc)
{
    controlDescriptor* d = new controlDescriptor();
    d->id         = desc.id;
    d->type       = translateV4L2ControlType(desc.type);
    d->name       = QString((const char*)desc.name);
    d->minVal     = desc.minimum;
    d->maxVal     = desc.maximum;
    d->step       = desc.step;
    d->defaultVal = desc.default_value;

    switch(d->type) {
    case ControlType_Int:
        DEBUG << "  " << (const char*)desc.name
              << " - type: Integer; range: [" << d->minVal << ".." << d->maxVal << "]; step: " << d->step
              << "; default: " << d->defaultVal << "." << endl;
        break;
    case ControlType_Boolean:
        DEBUG << "  " << (const char*)desc.name
              << " - type: Boolean; default: " << d->defaultVal << "." << endl;
        break;
    case ControlType_Menu:
        // Options are enumerated below
        break;
    case ControlType_Button:
        DEBUG << "  " << (const char*)desc.name << " - type: Button." << endl;
        break;
    default:
        DEBUG << "  " << (const char*)desc.name << " - type: Unknown." << endl;
        break;
    }

    if(desc.type == V4L2_CTRL_TYPE_MENU) {
        DEBUG << "  " << (const char*)desc.name << " - type: Menu. Getting options:" << endl;
        bool ok = true;
        for(int i=0; ok; i++) {
            struct v4l2_querymenu mdesc;
            memset(&mdesc, 0, sizeof(mdesc));
            mdesc.id = desc.id;
            mdesc.index = i;

            ok = xioctl(VIDIOC_QUERYMENU, &mdesc);
            if(ok) {
                DEBUG << "    " << (const char*)mdesc.name << endl;
                d->choices.append((const char*)mdesc.name);
            }
        }
    }

    return d;
}

QVariant V4L2Dev::control(const QString& name) const
{
    const controlDescriptor* d;

    if(_controls.contains(name)) {
        d = _controls[name];
    } else {
        kDebug() << "V4L2Dev::control(): " << name << ": no such control.";
        return QVariant();
    }

    struct v4l2_control desc;
    desc.id = d->id;
    desc.value = 0;

    if(xioctl(VIDIOC_G_CTRL, &desc)) {
        switch(_controls[name]->type) {
        case ControlType_Int:
            DEBUG << "V4L2Dev::control(): " << name << ": " << (int)desc.value << endl;
            return QVariant((int)desc.value);
            break;
        case ControlType_Boolean:
            DEBUG << "V4L2Dev::control(): " << name << ": " << (bool)desc.value << endl;
            return QVariant((bool)desc.value);
            break;
        case ControlType_Menu:
            DEBUG << "V4L2Dev::control(): " << name << ": " << d->choices[desc.value] << endl;
            return QVariant(d->choices[desc.value]);
            break;
        default:
            break;
        }
    }
    kDebug() << "V4L2Dev::control(): IOCTL failed or unknown control type.";
    return QVariant();
}

bool V4L2Dev::setControl(const QString& name, const QVariant& value) const
{
    const controlDescriptor* d;

    if(_controls.contains(name)) {
        d = _controls[name];
    } else {
        kDebug() << "V4L2Dev::setControl(): " << name << ": no such control.";
        return false;
    }

    DEBUG << "V4L2Dev::setControl(): " << name << ": " << value.toString() << endl;

    struct v4l2_control desc;
    desc.id = d->id;

    switch(_controls[name]->type) {
    case ControlType_Int:
        if(value.type() != QVariant::Int) {
            return false;
        }
        desc.value = value.toInt();
        break;
    case ControlType_Boolean:
        if(value.type() != QVariant::Bool) {
            return false;
        }
        desc.value = value.toBool();
        break;
    case ControlType_Menu:
        {
            if(value.type() != QVariant::String) {
                return false;
            }
            int index = d->choices.indexOf(value.toString());
            if(index == -1) {
                return false;
            }
            desc.value = index;
            break;
        }
    case ControlType_Button:
        desc.value = 0;
        break;
    default:
        return false;
    }

    return xioctl(VIDIOC_S_CTRL, &desc);
}

// -----------------------------------------------------------------------

bool V4L2Dev::setSource(const QString& name)
{
    DEBUG << "V4L2Dev::setSource(): " << name << endl;

    if(_videoInputs.contains(name)) {
        int id = _videoInputs[name];
        if(xioctl(VIDIOC_S_INPUT, &id)) {
            _currentTuner = _tunerForInput[name];

            if(_currentTuner != -1) {
                struct v4l2_tuner desc;
                memset(&desc, 0, sizeof(desc));
                desc.index = _currentTuner;

                if(xioctl(VIDIOC_G_TUNER, &desc)) {
                    _currentTunerType = desc.type;
                    if(desc.capability & V4L2_TUNER_CAP_LOW) {
                        _frequencyScale = 62.5;
                    } else {
                        _frequencyScale = 62500;
                    }
                }
            }
            return true;
        }
    }
    _currentTuner = -1;
    return false;
}

const QString V4L2Dev::source() const
{
    int id;

    if(xioctl(VIDIOC_G_INPUT, &id)) {
        for(QMap<QString, int>::const_iterator it = _videoInputs.constBegin();
            it != _videoInputs.constEnd();
            ++it) {
            if(it.value() == id) {
                DEBUG << "V4L2Dev::source(): " << it.key() << endl;
                return it.key();
            }
        }
    }

    return QString();
}

// -----------------------------------------------------------------------

bool V4L2Dev::setEncoding(const QString& name)
{
    DEBUG << "V4L2Dev::setEncoding(): " << name << endl;

    if(!_videoStandarts.contains(name)) {
        return false;
    }

    v4l2_std_id id = _videoStandarts[name];
    return xioctl(VIDIOC_S_STD, &id);
}

const QString V4L2Dev::encoding() const
{
    v4l2_std_id id;

    if(xioctl(VIDIOC_G_STD, &id)) {
        for(QMap<QString, qulonglong>::const_iterator it = _videoStandarts.constBegin();
            it != _videoStandarts.constEnd();
            ++it) {
            if(it.value() == id) {
                DEBUG << "V4L2Dev::encoding(): " << it.key() << endl;
                return it.key();
            }
        }
    }

    return QString();
}

// -----------------------------------------------------------------------

bool V4L2Dev::setAudioMode(const QString& mode)
{
    DEBUG << "V4L2Dev::setAudioMode(): " << mode << endl;

    if(_currentTuner == -1) {
        return false;
    }

    struct v4l2_tuner desc;
    memset(&desc, 0, sizeof(desc));
    desc.index = _currentTuner;

    if(mode == i18n("Mono")) {
        desc.audmode = V4L2_TUNER_MODE_MONO;
    } else if (mode == i18n("Stereo")) {
        desc.audmode = V4L2_TUNER_MODE_STEREO;
    } else if (mode == i18n("Language 1")) {
        desc.audmode = V4L2_TUNER_MODE_LANG1;
    } else if (mode == i18n("Language 2")) {
        desc.audmode = V4L2_TUNER_MODE_LANG2;
    } else {
        return false;
    }

    return xioctl(VIDIOC_S_TUNER, &desc);
}

const QStringList& V4L2Dev::broadcastedAudioModes()
{
    _broadcastedAudioModes.clear();

    if(_currentTuner != -1) {
        struct v4l2_tuner desc;
        memset(&desc, 0, sizeof(desc));
        desc.index = _currentTuner;

        if (xioctl(VIDIOC_G_TUNER, &desc)) {
            for (QMap<QString, int>::const_iterator it(_audioMap.constBegin());
                 it != _audioMap.constEnd();
                 ++it) {
                if (it.value() & desc.rxsubchans) {
                    _broadcastedAudioModes.append(it.key());
                }
            }
        }
    }

    return _broadcastedAudioModes;
}

// -----------------------------------------------------------------------

QSize V4L2Dev::setInputProperties(ImageFormat fmt, const QSize& sz)
{
    DEBUG << "V4L2Dev::setInputProperties(): qvideo format: " << fmt << ", size: " << sz << endl;

    if(_streaming) {
        stopStreaming();
    }

    // Fixup size - YUV formats only support multiples of 2
    QSize size = QSize(sz.width() & 0xFFFFFFFE, sz.height());

    struct v4l2_format desc;
    memset(&desc, 0, sizeof(desc));
    desc.type                 = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    desc.fmt.pix.width        = size.width();
    desc.fmt.pix.height       = size.height();
    desc.fmt.pix.pixelformat  = qvideoformat2v4l2format(fmt);
    desc.fmt.pix.field        = V4L2_FIELD_INTERLACED;
    desc.fmt.pix.bytesperline = bytesppForFormat(fmt) * size.width();

    if(xioctl(VIDIOC_S_FMT, &desc) && (desc.fmt.pix.pixelformat == qvideoformat2v4l2format(fmt))) {
        DEBUG << "V4L2Dev::setInputProperties(): ok (got v4l2 format: " << qvideoformat2v4l2format(fmt)
              << ", size: " << QSize(desc.fmt.pix.width, desc.fmt.pix.height) << ")" << endl;
        return QSize(desc.fmt.pix.width, desc.fmt.pix.height);
    } else {
        kWarning() << "V4L2Dev::setInputProperties(): failed";
        return QSize();
    }
}

QVideo::ImageFormat V4L2Dev::inputFormat() const
{
    struct v4l2_format desc;
    memset(&desc, 0, sizeof(desc));
    desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if(xioctl(VIDIOC_G_FMT, &desc)) {
        DEBUG << "V4L2Dev::inputFormat(): " << v4l2format2qvideoformat(desc.fmt.pix.pixelformat) << endl;
        return v4l2format2qvideoformat(desc.fmt.pix.pixelformat);
    } else {
        return FORMAT_NONE;
    }
}

QSize V4L2Dev::inputSize() const
{
    struct v4l2_format desc;
    memset(&desc, 0, sizeof(desc));
    desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if(xioctl(VIDIOC_G_FMT, &desc)) {
        //        DEBUG << "V4L2Dev::inputSize(): " << QSize(desc.fmt.pix.width, desc.fmt.pix.height) << endl;
        return QSize(desc.fmt.pix.width, desc.fmt.pix.height);
    } else {
        return QSize();
    }
}

// -----------------------------------------------------------------------

double V4L2Dev::frequency() const
{
    if(_currentTuner == -1) {
        return false;
    }

    struct v4l2_frequency desc;
    memset(&desc, 0, sizeof(desc));
    desc.tuner = _currentTuner;

    if(xioctl(VIDIOC_G_FREQUENCY, &desc)) {
        DEBUG << "V4L2Dev::frequency(): " << (((double)desc.frequency) * _frequencyScale) << endl;
        return ((double)desc.frequency) * _frequencyScale;
    }
    return 0;
}

double V4L2Dev::setFrequency(double freq)
{
    DEBUG << "V4L2Dev::setFrequency(): " << freq << "Hz" << endl;

    if(_currentTuner == -1) {
        return false;
    }

    struct v4l2_frequency desc;
    memset(&desc, 0, sizeof(desc));
    desc.tuner = _currentTuner;
    desc.type = (v4l2_tuner_type)_currentTunerType;
    desc.frequency = (__u32)(freq / _frequencyScale);

    if(xioctl(VIDIOC_S_FREQUENCY, &desc)) {
        return frequency();
    } else {
        return 0;
    }
}

int V4L2Dev::signal() const
{
    if(_currentTuner == -1) {
        return -1;
    }

    struct v4l2_tuner desc;
    memset(&desc, 0, sizeof(desc));
    desc.index = _currentTuner;

    if(xioctl(VIDIOC_G_TUNER, &desc)) {
        DEBUG << "V4L2Dev::signal(): " << desc.signal << endl;
        return desc.signal;
    } else {
        return -1;
    }
}

// -----------------------------------------------------------------------

bool V4L2Dev::startStreaming(unsigned int numBuffers)
{
    DEBUG << "V4L2Dev::startStreaming()" << endl;

    if(_streaming) {
        return true;
    }

    if((0 == _streamingBufferCount) && (0 == setupStreaming(numBuffers))) {
        return false;
    }

    for(unsigned int i=0; i<_streamingBufferCount; i++) {
        enqueueBuffer(i);
    }

    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    _streaming = xioctl(VIDIOC_STREAMON, &type);
    return _streaming;
}

bool V4L2Dev::stopStreaming()
{
    DEBUG << "V4L2Dev::stopStreaming()" << endl;

    if(!_streaming) {
        return true;
    }

    _streaming = false;

    for(unsigned int i=0; i<_streamingBufferCount; i++) {
        _streamingBuffers[i].queued = false;
    }

    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    xioctl(VIDIOC_STREAMOFF, &type);

    cleanup();

    return true;
}

void V4L2Dev::cleanup()
{
    DEBUG << "V4L2Dev::cleanup()" << endl;

    if(_streaming) {
        stopStreaming();
    }

    for(unsigned int i=0; i<_streamingBufferCount; i++) {
        if(_streamingBuffers[i].mmapped) {
            munmap(_streamingBuffers[i].start, _streamingBuffers[i].length);
        } else {
            free(_streamingBuffers[i].start);
        }
    }

    _streamingBufferCount = 0;
}

int V4L2Dev::setupStreaming(unsigned int numBuffers)
{
    DEBUG << "V4L2Dev::setupStreaming(): " << numBuffers << endl;

    if(numBuffers > V4L2_MAX_STREAMING_BUFFERS) {
        numBuffers = V4L2_MAX_STREAMING_BUFFERS;
    }

    if(_streamingBufferCount != 0) {
        cleanup();
    }

    _streamingBufferCount = setupStreamingMMAP(numBuffers);

    if(_streamingBufferCount == 0) {
        _streamingBufferCount = setupStreamingUser(numBuffers);
    }

    return _streamingBufferCount;
}

int V4L2Dev::setupStreamingMMAP(unsigned int numBuffers)
{
    struct v4l2_requestbuffers desc;
    memset(&desc, 0, sizeof(desc));
    desc.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    desc.memory = V4L2_MEMORY_MMAP;
    desc.count  = numBuffers;

    if(xioctl(VIDIOC_REQBUFS, &desc)) {
        if (desc.count == 0) {
            kWarning() << "V4L2Dev::setupStreamingMMAP(): driver failed to allocate buffers.";
            return 0;
        }
        kDebug() << "V4L2Dev::setupStreamingMMAP(): driver allocated " << desc.count << " mmapped buffers.";

        for(_streamingBufferCount=0; _streamingBufferCount<desc.count; _streamingBufferCount++) {
            struct v4l2_buffer bdesc;
            memset(&bdesc, 0, sizeof(bdesc));
            bdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            bdesc.index = _streamingBufferCount;

            if(xioctl(VIDIOC_QUERYBUF, &bdesc)) {
                _streamingBuffers[_streamingBufferCount].queued  = false;
                _streamingBuffers[_streamingBufferCount].mmapped = true;
                _streamingBuffers[_streamingBufferCount].length  = bdesc.length;
                _streamingBuffers[_streamingBufferCount].start   = mmap(NULL, bdesc.length,
                                                                        PROT_READ | PROT_WRITE,
                                                                        MAP_SHARED,
                                                                        _fd, bdesc.m.offset);
                if (_streamingBuffers[_streamingBufferCount].start == MAP_FAILED) {
                    cleanup();
                    return 0;
                }
            } else {
                cleanup();
                return 0;
            }
        }
        _streamtype = V4L2_MEMORY_MMAP;
        return _streamingBufferCount;
    }

    kWarning() << "V4L2Dev::setupStreamingMMAP(): driver cannot handle mmap buffers.";
    return 0;
}

int V4L2Dev::setupStreamingUser(unsigned int numBuffers)
{
    DEBUG << "V4L2Dev::setupStreamingUser(): " << numBuffers << endl;

    struct v4l2_requestbuffers desc;
    memset(&desc, 0, sizeof(desc));
    desc.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    desc.memory = V4L2_MEMORY_USERPTR;
    desc.count  = numBuffers;

    QSize sz = inputSize();
    int bufSize = sz.width() * sz.height() * bytesppForFormat(inputFormat());

    if(xioctl(VIDIOC_REQBUFS, &desc) && (desc.count != 0)) {
        kDebug() << "V4L2Dev::setupStreamingUser(): driver supports " << desc.count << " user buffers.";

        for(_streamingBufferCount=0; _streamingBufferCount<numBuffers; _streamingBufferCount++) {
            _streamingBuffers[_streamingBufferCount].queued  = false;
            _streamingBuffers[_streamingBufferCount].mmapped = false;
            _streamingBuffers[_streamingBufferCount].start   = malloc(bufSize);
            _streamingBuffers[_streamingBufferCount].length  = bufSize;
        }
        _streamtype = V4L2_MEMORY_USERPTR;
        return numBuffers;
    }

    kWarning() << "V4L2Dev::setupStreamingUser(): driver cannot handle user buffers.";
    return 0;
}

const unsigned char* V4L2Dev::getStreamingBuffer(unsigned int id) const
{
    return (const unsigned char*)_streamingBuffers[id].start;
}

bool V4L2Dev::enqueueBuffer(unsigned int id)
{
    if(id > _streamingBufferCount) {
        kWarning() << "V4L2Dev::enqueueBuffer(): broken id: " << id;
        return false;
    }

    if(_streamingBuffers[id].queued) {
        kWarning() << "V4L2Dev::enqueueBuffer(): buffer already queued: " << id;
        return false;
    }

    struct v4l2_buffer desc;
    memset(&desc, 0, sizeof(desc));
    desc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    desc.index = id;

    if(_streamingBuffers[id].mmapped) {
        desc.memory    = V4L2_MEMORY_MMAP;
    } else {
        desc.memory    = V4L2_MEMORY_USERPTR;
        desc.m.userptr = (unsigned long)_streamingBuffers[id].start;
        desc.length    = _streamingBuffers[id].length;
    }

    if(xioctl(VIDIOC_QBUF, &desc)) {
        _streamingBuffers[id].queued = true;
        return true;
    } else {
        _streamingBuffers[id].queued = false;
        return false;
    }
}

bool V4L2Dev::waitBuffer(unsigned int* id)
{
    struct v4l2_buffer desc;
    memset(&desc, 0, sizeof(desc));
    desc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    desc.memory = (v4l2_memory)_streamtype;

    if(xioctl(VIDIOC_DQBUF, &desc)) {
        if(desc.index > _streamingBufferCount) {
            kWarning() << "V4L2Dev::dequeueBuffer(): dequeued broken index: " << id;
            return false;
        }

        *id = desc.index;
        _streamingBuffers[desc.index].queued = false;
        //        printBuffer(desc);
        return true;
    } else {
        if(errno == EIO) {
            kWarning() << "IO error - requeuing buffer";
            *id = desc.index;
            _streamingBuffers[desc.index].queued = false;
            enqueueBuffer(*id);
        }

        return false;
    }
}

// -----------------------------------------------------------------------

QSize V4L2Dev::snapshot(unsigned char* buf, const QSize& desiredSize, QVideo::ImageFormat format)
{
    stopStreaming();

    if(_supportsReadWrite) {
        QSize sz = setInputProperties(format, desiredSize);

        if(sz.isValid()) {
            ssize_t count = ::read(_fd, buf,
                                   sz.width() * sz.height() * QVideo::bytesppForFormat(format));

            kDebug() << "V4LDev2::snapshot(): Grab returned " << count << " bytes.";

            if(count > 0) {
                return sz;
            } else {
                kWarning() << "V4L2Dev::snapshot(): error: read() returned: " << strerror(errno);
            }
        }
    } else {
        kWarning() << "V4L2Dev::snapshot(): error: device does not support read()";
    }

    return QSize();
}

// -----------------------------------------------------------------------

bool V4L2Dev::xioctl(int cmd, void* buf, int mayFail) const
{
    static const char* v4l2ioctls[] = {
        "VIDIOC_QUERYCAP",
        "VIDIOC_RESERVED",
        "VIDIOC_ENUM_FMT", "", "VIDIOC_G_FMT", "VIDIOC_S_FMT",
        "VIDIOC_G_COMP", "VIDIOC_S_COMP",
        "VIDIOC_REQBUFS", "VIDIOC_QUERYBUF", "VIDIOC_G_FBUF", "VIDIOC_S_FBUF",
        "", "",
        "VIDIOC_OVERLAY",
        "VIDIOC_QBUF", "", "VIDIOC_DQBUF", "VIDIOC_STREAMON", "VIDIOC_STREAMOFF",
        "",
        "VIDIOC_G_PARM", "VIDIOC_S_PARM",
        "VIDIOC_G_STD", "VIDIOC_S_STD", "VIDIOC_ENUMSTD",
        "VIDIOC_ENUMINPUT",
        "VIDIOC_G_CTRL", "VIDIOC_S_CTRL",
        "VIDIOC_G_TUNER", "VIDIOC_S_TUNER",
        "", "",
        "VIDIOC_G_AUDIO", "VIDIOC_S_AUDIO", "",
        "VIDIOC_QUERYCTRL", "VIDIOC_QUERYMENU",
        "VIDIOC_G_INPUT", "VIDIOC_S_INPUT",
        "", "", "", "", "", "",
        "VIDIOC_G_OUTPUT", "VIDIOC_S_OUTPUT", "VIDIOC_ENUMOUTPUT",
        "VIDIOC_G_AUDOUT", "VIDIOC_S_AUDOUT",
        "", "", "",
        "VIDIOC_G_MODULATOR", "VIDIOC_S_MODULATOR",
        "VIDIOC_G_FREQUENCY", "VIDIOC_S_FREQUENCY",
        "VIDIOC_CROPCAP", "VIDIOC_G_CROP", "VIDIOC_S_CROP",
        "VIDIOC_G_JPEGCOMP", "VIDIOC_S_JPEGCOMP",
        "VIDIOC_QUERYSTD",
        "VIDIOC_TRY_FMT",
        "VIDIOC_ENUMAUDIO", "VIDIOC_ENUMAUDOUT",
        "VIDIOC_G_PRIORITY", "VIDIOC_S_PRIORITY"
    };

    int rc = ::ioctl(_fd, cmd, buf);
    if( (rc < 0) && (errno != mayFail)) {
        kWarning() << "V4L2Dev: " << v4l2ioctls[_IOC_NR(cmd)] << " failed: " << strerror(errno);
    } else {
        //        DEBUG << "V4L2Dev: " << v4l2ioctls[_IOC_NR(cmd)] << " ok. (" << rc << ")" << endl;
    }
    return (rc >= 0);
}

#if 0
static void printBuffer(struct v4l2_buffer& desc)
{
    switch(desc.type) {
    case V4L2_BUF_TYPE_VIDEO_CAPTURE:
        DEBUG << "  type: capture buffer" << endl;
        break;
    case V4L2_BUF_TYPE_VIDEO_OVERLAY:
        DEBUG << "  type: overlay buffer" << endl;
        break;
    case V4L2_BUF_TYPE_VIDEO_OUTPUT:
        DEBUG << "  type: output buffer" << endl;
        break;
    case V4L2_BUF_TYPE_VBI_CAPTURE:
        DEBUG << "  type: vbi capture buffer" << endl;
        break;
    case V4L2_BUF_TYPE_VBI_OUTPUT:
        DEBUG << "  type: vbi output buffer" << endl;
        break;
    default:
        DEBUG << "  type: unknown" << endl;
        break;
    }

    switch(desc.field) {
    case V4L2_FIELD_ANY:
        DEBUG << "  field: any" << endl;
        break;
    case V4L2_FIELD_NONE:
        DEBUG << "  field: none" << endl;
        break;
    case V4L2_FIELD_TOP:
        DEBUG << "  field: top" << endl;
        break;
    case V4L2_FIELD_BOTTOM:
        DEBUG << "  field: bottom" << endl;
        break;
    case V4L2_FIELD_INTERLACED:
        DEBUG << "  field: interlaced" << endl;
        break;
    case V4L2_FIELD_SEQ_TB:
        DEBUG << "  field: seq tb" << endl;
        break;
    case V4L2_FIELD_SEQ_BT:
        DEBUG << "  field: seq bt" << endl;
        break;
    case V4L2_FIELD_ALTERNATE:
        DEBUG << "  field: alternate" << endl;
        break;
    default:
        DEBUG << "  field: unknown" << endl;
        break;
    }

    DEBUG << "  bytes: "    << desc.bytesused                        << endl;
    DEBUG << "  sequence: " << desc.sequence                         << endl;
    DEBUG << "  mapped: "   << (desc.flags & V4L2_BUF_FLAG_MAPPED)   << endl;
    DEBUG << "  queued: "   << (desc.flags & V4L2_BUF_FLAG_QUEUED)   << endl;
    DEBUG << "  done: "     << (desc.flags & V4L2_BUF_FLAG_DONE)     << endl;
    DEBUG << "  keyframe: " << (desc.flags & V4L2_BUF_FLAG_KEYFRAME) << endl;
    DEBUG << "  pframe: "   << (desc.flags & V4L2_BUF_FLAG_PFRAME)   << endl;
    DEBUG << "  bframe: "   << (desc.flags & V4L2_BUF_FLAG_BFRAME)   << endl;
    DEBUG << "  timecode: " << (desc.flags & V4L2_BUF_FLAG_TIMECODE) << endl;
}
#endif

// -----------------------------------------------------------------------

V4L2Dev::ControlType V4L2Dev::translateV4L2ControlType(int t)
{
    switch(t) {
    case V4L2_CTRL_TYPE_INTEGER:
        return ControlType_Int;
    case V4L2_CTRL_TYPE_BOOLEAN:
        return ControlType_Boolean;
    case V4L2_CTRL_TYPE_MENU:
        return ControlType_Menu;
    case V4L2_CTRL_TYPE_BUTTON:
        return ControlType_Button;
    default:
        kWarning() << "V4LDev::translateV4L2ControlType(): unknown control type: " << t;
        return ControlType_Int;
    }
}

// -----------------------------------------------------------------------

unsigned int V4L2Dev::qvideoformat2v4l2format(ImageFormat f)
{
    if(f & FORMAT_GREY) {
        return V4L2_PIX_FMT_GREY;
    }
    if(f & FORMAT_HI240) {
        return V4L2_PIX_FMT_HI240;
    }
    if(f & FORMAT_RGB15_LE) {
        return V4L2_PIX_FMT_RGB555;
    }
    if(f & FORMAT_RGB15_BE) {
        return V4L2_PIX_FMT_RGB555X;
    }
    if(f & FORMAT_RGB16_LE) {
        return V4L2_PIX_FMT_RGB565;
    }
    if(f & FORMAT_RGB16_BE) {
        return V4L2_PIX_FMT_RGB565X;
    }
    if(f & FORMAT_RGB24) {
        return V4L2_PIX_FMT_RGB24;
    }
    if(f & FORMAT_RGB32) {
        return V4L2_PIX_FMT_RGB32;
    }
    if(f & FORMAT_BGR24) {
        return V4L2_PIX_FMT_BGR24;
    }
    if(f & FORMAT_BGR32) {
        return V4L2_PIX_FMT_BGR32;
    }
    if(f & FORMAT_YUYV) {
        return V4L2_PIX_FMT_YUYV;
    }
    if(f & FORMAT_UYVY) {
        return V4L2_PIX_FMT_UYVY;
    }
    if(f & FORMAT_YUV422P) {
        return V4L2_PIX_FMT_YUV422P;
    }
    if(f & FORMAT_YUV420P) {
        return V4L2_PIX_FMT_YUV420;
    }

    kWarning() << "V4LDev: Unable to figure out a grab format for the desired QImageFormat: " << f;
    return V4L2_PIX_FMT_YUYV;
}

QVideo::ImageFormat V4L2Dev::v4l2format2qvideoformat(unsigned int f)
{
    if(f == V4L2_PIX_FMT_GREY) {
        return FORMAT_GREY;
    }
    if(f == V4L2_PIX_FMT_HI240) {
        return FORMAT_HI240;
    }
    if(f == V4L2_PIX_FMT_YUYV) {
        return FORMAT_YUYV;
    }
    if(f == V4L2_PIX_FMT_UYVY) {
        return FORMAT_UYVY;
    }
    if(f == V4L2_PIX_FMT_YUV420) {
        return FORMAT_YUV420P;
    }
    if(f == V4L2_PIX_FMT_YUV422P) {
        return FORMAT_YUV422P;
    }
    if(f == V4L2_PIX_FMT_RGB32) {
        return FORMAT_RGB32;
    }
    if(f == V4L2_PIX_FMT_BGR32) {
        return FORMAT_BGR32;
    }
    if(f == V4L2_PIX_FMT_RGB24) {
        return FORMAT_RGB24;
    }
    if(f == V4L2_PIX_FMT_BGR24) {
        return FORMAT_BGR24;
    }
    if(f == V4L2_PIX_FMT_RGB555) {
        return FORMAT_RGB15_LE;
    }
    if(f == V4L2_PIX_FMT_RGB555X) {
        return FORMAT_RGB15_BE;
    }
    if(f == V4L2_PIX_FMT_RGB565) {
        return FORMAT_RGB16_LE;
    }
    if(f == V4L2_PIX_FMT_RGB565X) {
        return FORMAT_RGB16_BE;
    }

    return FORMAT_NONE;
}
