/*
 * Copyright (C) 2002-2003 George Staikos <staikos@kde.org>
 *
 * 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.
 */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <endian.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/mman.h>
#include <assert.h>
#include <signal.h>

#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
#define _LINUX_TIME_H
#define _DEVICE_H_
#include "kdetvv4lsetup/videodev.h"

#include <qimage.h>
#include <kdebug.h>
#include <kprocess.h>

#include "v4ldev.h"
#include "v4ldevtuner.h"
#include "v4ldevcamera.h"

#include <sys/time.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>

/* FIXME:
 *     - Colourspaces are not done.
 */

///////////////////////////////////////////////////////////////////////////////////////
// SIGALM signal handler
///////////////////////////////////////////////////////////////////////////////////////

static int _sigs = 0;

static void sigalarm(int)
{
    _sigs++;
    fprintf(stderr, "v4ldev: V4L timeout %d\n", _sigs);
}

class V4LSigInit
{
public:
    V4LSigInit()
    {
        kDebug() << "V4LDev: Installing SIGALARM handler.";
        _old = signal(SIGALRM, sigalarm);
    }

    ~V4LSigInit()
    {
        kDebug() << "V4LDev: Uninstalling SIGALARM handler.";
        signal(SIGALRM, _old);
    }

    void (*_old)(int);
};

static V4LSigInit _initSignals;

///////////////////////////////////////////////////////////////////////////////////////
// Device factory
///////////////////////////////////////////////////////////////////////////////////////

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

    /* get the capture properties */
    struct video_capability vcap;
    memset(&vcap, 0, sizeof(vcap));
    rc = ioctl(fd, VIDIOCGCAP, &vcap);
    if (rc < 0) {
        ::close(fd);
        return NULL;
    }

    kDebug() << "v4ldev: Grabber Name: " << vcap.name;
    kDebug() << "Type: " << vcap.type;
    kDebug() << "Input Channels: " << vcap.channels;
    kDebug() << "minw=" << vcap.minwidth << ", minh=" << vcap.minheight
              << ", maxw=" << vcap.maxwidth << ", maxh=" << vcap.maxheight << endl;

    if (vcap.type&VID_TYPE_CAPTURE) {
        kDebug() << "  Supports: capture to memory";
    }
    if (vcap.type&VID_TYPE_OVERLAY) {
        kDebug() << "  Supports: video overlay";
    }
    if (vcap.type&VID_TYPE_CLIPPING) {
        kDebug() << "  Supports: clipping";
    }
    if (vcap.type&VID_TYPE_CHROMAKEY) {
        kDebug() << "  Requires: chromakey";
    }
    if (vcap.type&VID_TYPE_SCALES) {
        kDebug() << "  Supports: scaling";
    }
    if (vcap.type&VID_TYPE_FRAMERAM) {
        kDebug() << "  Requires: overwriting frame buffer";
    }
    if (vcap.type&VID_TYPE_SUBCAPTURE) {
        kDebug() << "  Supports: capture of parts to memory";
    }

    if (vcap.type&VID_TYPE_OVERLAY) {
        // v4l1 does not support multiple fd opening
        ::close(fd);

        kDebug() << "v4ldev: The device supports overlay. Running kdetvv4lsetup.";

        KProcess p;
        QStringList args;
        args << "-c" << dev;
        if (p.execute("kdetvv4lsetup", args) != 0) {
            kWarning() << "v4ldev: kdetvv4lsetup had some trouble. Trying to continue anyway.";
        }

        // reopen device
        fd = ::open(dev.toLocal8Bit(), O_RDWR);
        if (fd < 0)
            return NULL;
    }

    V4LDev *nd = NULL;
    if (vcap.type & VID_TYPE_TUNER) {   /* it must be a TV tuner */
    nd = new V4LTuner(fd, vcap.name, vcap.channels, vcap.type,
                      vcap.minwidth, vcap.minheight,
                      vcap.maxwidth, vcap.maxheight);
    /* see if it's a camera */
    } else if (vcap.channels == 1) {  // hack for now   FIXME
        nd =  new V4LCamera(fd, vcap.name, vcap.channels,
                            vcap.type, vcap.minwidth, vcap.minheight,
                            vcap.maxwidth, vcap.maxheight);
    } else {
        ::close(fd);
    }

    return nd;
}

///////////////////////////////////////////////////////////////////////////////////////
// Constructor / destructor
///////////////////////////////////////////////////////////////////////////////////////

V4LDev::V4LDev(int fd, const QString &name, int channels, int type, int minw, int minh, int maxw, int maxh)
    : _fd(fd),
      _name(name),
      _minWidth(minw),
      _minHeight(minh),
      _maxWidth(maxw),
      _maxHeight(maxh),
      _type(type)
{
    int rc;

    _source = 0;
    _sources.clear();
    _encodings.clear();

    _isTuner = false;
    _isCamera = false;

    _aspectRatio = (float)maxw/(float)maxh;
    _overlaid = false;

    int displayrc, depth;
    unsigned int bitspp;
    displayrc = findDisplayProperties(_qvideofmt, depth, bitspp, _bpp);
    _v4lfmt = qvideoformat2v4lformat(_qvideofmt);

    _mmapBuf = 0;
    _readBuf = 0;
    _mmapCurrentFrame = 0;
    _grabNeedsInit = true;
    _mmapData = 0;
    _grabW = maxw;
    _grabH = maxh;
    _mbuf = new struct video_mbuf;
    _cliprecs = new struct video_clip[MAX_CLIP_RECTS];

    setImageSize(maxw, maxh);

    /* Get the channels */
    _channels = new video_channel[channels];
    memset(_channels, 0, sizeof(_channels));

    for (int i = 0; i < channels; i++) {
        _channels[i].channel = i;
        rc = ioctl(_fd, VIDIOCGCHAN, &_channels[i]);
        if (rc >= 0) {
            _sources << QString(_channels[i].name).toLower();

            /**** DEBUG ****/
            kDebug() << "v4ldev: Channel " << i+1 << ": " << _channels[i].name;

            QString type;
            if ( _channels[i].type & VIDEO_TYPE_TV )
                type = "TV";
            else if ( _channels[i].type & VIDEO_TYPE_CAMERA )
                type = "Camera";
            else
                type ="Unknown Type";

            QString msg( "%1" );
            if  ( _channels[i].flags & VIDEO_VC_TUNER )
                msg = msg + QString(" - tuner(%1) ").arg(_channels[i].tuners);
            if ( _channels[i].flags & VIDEO_VC_AUDIO )
                msg = msg + " - audio ";

            kDebug() << msg.arg(type);
            /**** END DEBUG ****/
        }
    }

    // Overlay stuff
    _disableOverlay = true;
    if (_type&VID_TYPE_OVERLAY) {
        struct video_buffer vb;
        memset(&vb, 0, sizeof(vb));
        rc = ioctl(_fd, VIDIOCGFBUF, &vb);
        if (rc < 0) {
            kWarning() << "v4ldev: Unable to get frame buffer info from v4l. Overlay disabled.";
            return;
        }

        if (!displayrc) {
            kWarning() << "v4ldev: Unable to figure out display properties. Overlay disabled.";
            return;
        }

        /* bttv confuses color depth and bits/pixel */
        if (depth == 15) {
            bitspp = 15;
        }

        if (vb.depth != bitspp) {
            kWarning() << "v4ldev: V4L and KVIDEOIO disagree about the depth of the "
                        << "display. Is kdetvv4lsetup installed suid root? Overlay disabled." << endl;
            kWarning() << "(I think it should be " << bitspp << ", v4l says: " << vb.depth << ")";
            return;
        }

        struct video_picture vp;
        memset(&vp, 0, sizeof(vp));
        rc = ioctl(_fd, VIDIOCGPICT, &vp);
        if (rc >= 0) {
            vp.palette = _v4lfmt;
            vp.depth   = bitspp;
            rc = ioctl(_fd, VIDIOCSPICT, &vp);
            if (rc < 0) {
                kWarning() << "v4ldev: VIDIOCSPICT failed: " << rc << ". Overlay disabled.";
                return;
            }
        }

        _disableOverlay = false;
        kDebug() << "v4ldev: Overlay video display is possible.";
    }
}

V4LDev::~V4LDev()
{
    delete[] _channels;
    int zero = 0;

    if (_mmapBuf)
        munmap(_mmapBuf, _mbuf->size);

    delete[] _readBuf;
    delete[] _mmapData;
    delete[] _mbuf;
    delete[] _cliprecs;

    ioctl(_fd, VIDIOCCAPTURE, &zero);
    ::close(_fd);
}

///////////////////////////////////////////////////////////////////////////////////////
// Video Source
///////////////////////////////////////////////////////////////////////////////////////

int V4LDev::setSource(const QString &source)
{
    kDebug() << "V4LDev::setSource(..) Source is " << source;

    syncCurrentFrame();

    // Empty source means don't touch
    if (source.isEmpty()) return 0;

    int idx = _sources.indexOf(source);

    if (idx < 0) return -1;

    struct video_channel vc;

    memset(&vc, 0, sizeof(vc));
    vc.channel = idx;

    int rc = ioctl(_fd, VIDIOCGCHAN, &vc);
    if (rc < 0)
        return -1;

    rc = ioctl(_fd, VIDIOCSCHAN, &vc);
    if (rc < 0) {
        kWarning() << "v4ldev: Error setting source to " << idx;
    }

    _source = idx;
    return 0;
}

///////////////////////////////////////////////////////////////////////////////////////
// Grabbing
///////////////////////////////////////////////////////////////////////////////////////

bool V4LDev::canGrab() const
{
    if (_type&VID_TYPE_CAPTURE)
        return true;
    return false;
}

int V4LDev::initGrabbing()
{
    if (_mmapBuf) {
        munmap(_mmapBuf, _mbuf->size);
        _mmapBuf = 0;
    }

    if (_mmapData) {
        delete[] _mmapData;
        _mmapData = 0;
    }

    _mmapCurrentFrame = 0;

    if (_type & VID_TYPE_CAPTURE) {
        int rc = ioctl(_fd, VIDIOCGMBUF, _mbuf);
        if (rc == 0) {
            void *rc2 = mmap(0, _mbuf->size, PROT_READ|PROT_WRITE, MAP_SHARED, _fd, 0);
            if ((long)rc2 != -1 && rc2 != 0) {
                _mmapBuf = static_cast<uchar*>(rc2);
            } else {
                kWarning() << "v4ldev: MMAP error.";
                return -1;
            }
        } else {
            return -1;
        }

        kDebug() << "Card supports " << _mbuf->frames << " grab frames.";
        _mmapData = new struct video_mmap[_mbuf->frames];
        memset(_mmapData, 0, _mbuf->frames*sizeof(struct video_mmap));
        for (int i = 0; i < _mbuf->frames; i++) {
            _mmapData[i].width  = _grabW;
            _mmapData[i].height = _grabH;
            _mmapData[i].frame  = i;
            _mmapData[i].format = _v4lfmt;
        }

        rc = ioctl(_fd, VIDIOCMCAPTURE, _mmapData);
        if (rc != 0) {
            fprintf(stderr, "v4ldev: VIDIOCMCAPTURE failed. Format may not be supported by grabber.\n");
            return -1;
        }
    }
    _grabNeedsInit = false;

    return 0;
}

bool V4LDev::setInputFormat(ImageFormat fmt)
{
    syncCurrentFrame();

    int ig = bytesppForFormat(fmt);
    if (ig < 0) {
        _qvideofmt = FORMAT_NONE;
        _v4lfmt = VIDEO_PALETTE_YUV422;
        return FORMAT_NONE;
    }

    _bpp = ig;
    _qvideofmt = fmt;
    _v4lfmt = qvideoformat2v4lformat(fmt);
    return (initGrabbing() == 0);
}

QVideo::ImageFormat V4LDev::inputFormat() const
{
    return _qvideofmt;
}

void V4LDev::syncCurrentFrame() const
{
    if (_mmapBuf) {
        while (ioctl(_fd, VIDIOCSYNC, &_mmapCurrentFrame) == -1
               && errno == EINTR)
            ;
    }
}

QSize V4LDev::grab(unsigned char* img)
{
    if (_grabNeedsInit) {
        initGrabbing();
    }

    if (_mmapBuf) {  ////////////////////////////////////   mmap() method
        int rc;
        int nextFrame = (_mmapCurrentFrame+1) % _mbuf->frames;

        rc = ioctl(_fd, VIDIOCMCAPTURE, &(_mmapData[nextFrame]));
        if (rc != 0) {
            fprintf(stderr, "v4ldev: VIDIOCMCAPTURE failed.\n");
            if (errno == EBUSY) {
                // on EBUSY, somehow the frame is busy.  This should never
                // happen as far as I know.  Hack: try to release the frame.
                rc = ioctl(_fd, VIDIOCSYNC, &nextFrame);
                if (rc != 0) {
                    fprintf(stderr, "v4ldev: VIDIOCSYNC failed too.\n");
                }
            }
            return QSize();
        }

        syncCurrentFrame();

        if (img) {
            memcpy(img, _mmapBuf + _mbuf->offsets[_mmapCurrentFrame], _grabW*_grabH*_bpp);
        }

        _mmapCurrentFrame = nextFrame;
        if (rc != 0) {
            fprintf(stderr, "v4ldev::grab(): VIDIOCSYNC failed.\n");
        }
    } else {  /////////////////////////////////////////   read() method
        if (!img) {
            int sz = _grabW*_grabH*_bpp;
            if (!_readBuf)
                _readBuf = new uchar[sz];
            int rc = read(_fd, _readBuf, sz);
            if (rc != sz) {
                fprintf(stderr, "v4ldev::grab() [mmap]: error: wanted %d, got rc = %d\n", sz, rc);

                return QSize();
            }
        } else {
            int sz = _grabW*_grabH*_bpp;

            int rc = read(_fd, img, sz);
            if (rc != sz) {
                fprintf(stderr, "v4ldev::grab() [read]: error: wanted %d, got rc = %d\n", sz, rc);
                return QSize();
            }
        }
    }

    return QSize(_grabW, _grabH);
}

int V4LDev::setImageSize(int w, int h)
{
    syncCurrentFrame();

    if (w < _minWidth)
        w = _minWidth;

    if (h >= 0 && h < _minHeight)
        h = _minHeight;

    if (w > _maxWidth)
        w = _maxWidth;

    if (h > _maxHeight)
        h = _maxHeight;

    if (h == -1) {
        h = int(w/_aspectRatio);
    }

    while (w%4 != 0 && w > _minWidth)
        w--;

    while (h%4 != 0 && h > _minHeight)
        h--;

    struct video_window vw;
    memset(&vw, 0, sizeof(vw));
    int rc = ioctl(_fd, VIDIOCGWIN, &vw);
    if (rc < 0)
        return -1;

    vw.width  = w;
    vw.height = h;
    vw.flags  = 0;
    if (_type&VID_TYPE_CHROMAKEY) {
        kDebug() << "v4ldev: Enabling chromakey for V4L overlay.";
        vw.flags |= VIDEO_WINDOW_CHROMAKEY;
    }
    rc = ioctl(_fd, VIDIOCSWIN, &vw);
    if (rc < 0)
        return -1;

    memset(&vw, 0, sizeof(vw));
    rc = ioctl(_fd, VIDIOCGWIN, &vw);
    if (rc < 0)
        return -1;

    if (vw.width != (unsigned)w || vw.height != (unsigned)h)
        return -1;

    if (_overlaid) {
        stopCapture();
        startCapture(vw.x, vw.y);
    }

    _grabW = w;
    _grabH = h;

    _grabNeedsInit = true;
    return 0;
}

///////////////////////////////////////////////////////////////////////////////////////
// Picture parameter accessors
///////////////////////////////////////////////////////////////////////////////////////

#define CreateParmFunc(X,Y)                 \
                                            \
int V4LDev::set##X(int x) {                 \
    struct video_picture vp;                \
                                            \
    memset(&vp, 0, sizeof(vp));             \
    int rc = ioctl(_fd, VIDIOCGPICT, &vp);  \
                                            \
    if (rc >= 0) {                          \
        vp.Y = x;                           \
        rc = ioctl(_fd, VIDIOCSPICT, &vp);  \
        if (rc < 0)                         \
            return -1;                      \
    } else return -1;                       \
                                            \
    return 0;                               \
}                                           \
                                            \
int V4LDev::Y() const {                     \
    struct video_picture vp;                \
                                            \
    memset(&vp, 0, sizeof(vp));             \
    int rc = ioctl(_fd, VIDIOCGPICT, &vp);  \
                                            \
    if (rc >= 0) {                          \
        return vp.Y;                        \
    }                                       \
                                            \
    return -1;                              \
}

CreateParmFunc(Brightness,brightness)
CreateParmFunc(Hue,hue)
CreateParmFunc(Colour,colour)
CreateParmFunc(Contrast,contrast)
CreateParmFunc(Whiteness,whiteness)

#undef CreateParmFunc


int V4LDev::setColourKey(unsigned long x)
{
    struct video_window vw;
    memset(&vw, 0, sizeof(vw));
    int rc = ioctl(_fd, VIDIOCGWIN, &vw);
    if (rc < 0)
        return -1;
    vw.chromakey = x;
    vw.flags = 0;
    if (_type&VID_TYPE_CHROMAKEY) {
        kDebug() << "v4ldev: Enabling chromakey for V4L overlay.";
        vw.flags |= VIDEO_WINDOW_CHROMAKEY;
    }

    return ioctl(_fd, VIDIOCSWIN, &vw);
}


unsigned long V4LDev::colourKey() const
{
    struct video_window vw;

    memset(&vw, 0, sizeof(vw));
    int rc = ioctl(_fd, VIDIOCGWIN, &vw);
    if (rc < 0)
        return 0;

    return vw.chromakey;
}

///////////////////////////////////////////////////////////////////////////////////////
// Audio
///////////////////////////////////////////////////////////////////////////////////////

int V4LDev::setVolume(int val)
{
    int rc;
    struct video_audio va;

    memset(&va, 0, sizeof(va));
    rc = ioctl(_fd, VIDIOCGAUDIO, &va);

    if (rc < 0) {
        perror("v4ldev: VIDIOCGAUDIO");
        return -1;
    }

    if((va.flags & VIDEO_AUDIO_VOLUME) == 0) {
        return -1;
    }

    va.volume = val;
    rc = ioctl(_fd, VIDIOCSAUDIO, &va);
    if (rc < 0) {
        perror("v4ldev: VIDIOCSAUDIO");
        return -1;
    }

    return 0;
}

int V4LDev::volume() const
{
    int rc;
    struct video_audio va;

    memset(&va, 0, sizeof(va));
    rc = ioctl(_fd, VIDIOCGAUDIO, &va);

    if (rc < 0) {
        perror("v4ldev: VIDIOCGAUDIO");
        return -1;
    }

    if((va.flags & VIDEO_AUDIO_VOLUME) == 0) {
        return -1;
    }

    return va.volume;
}

int V4LDev::setBass(int val)
{
    int rc;
    struct video_audio va;

    memset(&va, 0, sizeof(va));
    rc = ioctl(_fd, VIDIOCGAUDIO, &va);

    if (rc < 0) {
        perror("v4ldev: VIDIOCGAUDIO");
        return -1;
    }

    if((va.flags & VIDEO_AUDIO_BASS) == 0) {
        return -1;
    }

    va.bass = val;
    rc = ioctl(_fd, VIDIOCSAUDIO, &va);
    if (rc < 0) {
        perror("v4ldev: VIDIOCSAUDIO");
        return -1;
    }

    return 0;
}

int V4LDev::bass() const
{
    int rc;
    struct video_audio va;

    memset(&va, 0, sizeof(va));
    rc = ioctl(_fd, VIDIOCGAUDIO, &va);

    if (rc < 0) {
        perror("v4ldev: VIDIOCGAUDIO");
        return -1;
    }

    if((va.flags & VIDEO_AUDIO_BASS) == 0) {
        return -1;
    }

    return va.bass;
}

int V4LDev::setTreble(int val)
{
    int rc;
    struct video_audio va;

    memset(&va, 0, sizeof(va));
    rc = ioctl(_fd, VIDIOCGAUDIO, &va);

    if (rc < 0) {
        perror("v4ldev: VIDIOCGAUDIO");
        return -1;
    }

    if((va.flags & VIDEO_AUDIO_TREBLE) == 0) {
        return -1;
    }

    va.treble = val;
    rc = ioctl(_fd, VIDIOCSAUDIO, &va);
    if (rc < 0) {
        perror("v4ldev: VIDIOCSAUDIO");
        return -1;
    }

    return 0;
}

int V4LDev::treble() const
{
    int rc;
    struct video_audio va;

    memset(&va, 0, sizeof(va));
    rc = ioctl(_fd, VIDIOCGAUDIO, &va);

    if (rc < 0) {
        perror("v4ldev: VIDIOCGAUDIO");
        return -1;
    }

    if((va.flags & VIDEO_AUDIO_TREBLE) == 0) {
        return -1;
    }

    return va.treble;
}

int V4LDev::enableAudio()
{
    int rc;
    struct video_audio va;

    memset(&va, 0, sizeof(va));
    rc = ioctl(_fd, VIDIOCGAUDIO, &va);

    if (rc < 0) {
        perror("v4ldev: VIDIOCGAUDIO");
        return -1;
    }

    if((va.flags & VIDEO_AUDIO_MUTABLE) == 0) {
        return -1;
    }

    va.flags &= ~VIDEO_AUDIO_MUTE;
    rc = ioctl(_fd, VIDIOCSAUDIO, &va);
    if (rc < 0) {
        perror("v4ldev: VIDIOCSAUDIO");
        return -1;
    }

    return 0;
}

bool V4LDev::audioEnabled() const
{
    int rc;
    struct video_audio va;

    memset(&va, 0, sizeof(va));
    rc = ioctl(_fd, VIDIOCGAUDIO, &va);

    if (rc < 0) {
        perror("v4ldev: VIDIOCGAUDIO");
        return false;
    }

    return (va.flags & VIDEO_AUDIO_MUTE);
}

int V4LDev::disableAudio()
{
    int rc;
    struct video_audio va;

    memset(&va, 0, sizeof(va));
    rc = ioctl(_fd, VIDIOCGAUDIO, &va);

    if (rc < 0) {
        perror("v4ldev: VIDIOCGAUDIO");
        return -1;
    }

    if((va.flags & VIDEO_AUDIO_MUTABLE) == 0) {
        return -1;
    }

    va.flags |= VIDEO_AUDIO_MUTE;
    rc = ioctl(_fd, VIDIOCSAUDIO, &va);
    if (rc < 0) {
        perror("v4ldev: VIDIOCSAUDIO");
        return -1;
    }

    return 0;
}

int V4LDev::setAudioMode(const QString& mode)
{
    if (!_audioMap.contains(mode)) return -1;

    int rc;
    struct video_audio va;

    memset(&va, 0, sizeof(va));
    rc = ioctl(_fd, VIDIOCGAUDIO, &va);

    if (rc < 0) {
        perror("v4ldev: VIDIOCGAUDIO");
        return -1;
    }

    va.mode = _audioMap[mode];
    rc = ioctl(_fd, VIDIOCSAUDIO, &va);
    if (rc < 0) {
        perror("v4ldev: VIDIOCSAUDIO");
        return -1;
    }

    return 0;
}

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

    int rc;
    struct video_audio va;

    memset(&va, 0, sizeof(va));
    rc = ioctl(_fd, VIDIOCGAUDIO, &va);

    if (rc < 0) {
        perror("v4ldev: VIDIOCGAUDIO");
        return _broadcastedAudioModes;
    }

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

    return _broadcastedAudioModes;
}

///////////////////////////////////////////////////////////////////////////////////////
// Overlay
///////////////////////////////////////////////////////////////////////////////////////

bool V4LDev::canOverlay() const
{
    if (_disableOverlay)
        return false;

    if (_type&VID_TYPE_OVERLAY)
        return true;

    return false;
}

bool V4LDev::overlayOn() const
{
    return _overlaid;
}


void V4LDev::addClip(const QRect& clip)
{
    if (_clips.count() < MAX_CLIP_RECTS)
        _clips.append(clip);
}

void V4LDev::clearClips()
{
    _clips.clear();
}

void V4LDev::reClip()
{
    struct video_window vw;
    memset(&vw, 0, sizeof(vw));
    int rc = ioctl(_fd, VIDIOCGWIN, &vw);

    if (rc == 0) {
        for (int i = 0; i < _clips.count(); i++) {
            _cliprecs[i].x = _clips[i].x() - vw.x;
            _cliprecs[i].y = _clips[i].y() - vw.y;
            _cliprecs[i].width = _clips[i].width();
            _cliprecs[i].height = _clips[i].height();
        }

        vw.clips = _cliprecs;
        vw.clipcount = _clips.count();
        vw.flags = 0;
        if (_type&VID_TYPE_CHROMAKEY) {
            kDebug() << "Enabling chromakey for V4L overlay.";
            vw.flags |= VIDEO_WINDOW_CHROMAKEY;
        }
        ioctl(_fd, VIDIOCSWIN, &vw);
        if (_overlaid) {
            int one = 1;
            ioctl(_fd, VIDIOCCAPTURE, &one);
        }
    }
}

int V4LDev::setCaptureGeometry(const QRect& geom)
{
    if (!canOverlay())
        return -1;

    struct video_window vw;

    memset(&vw, 0, sizeof(vw));
    int rc = ioctl(_fd, VIDIOCGWIN, &vw);
    if (rc < 0) {
        perror("v4ldev: VIDIOCGWIN");
        return -1;
    }

    vw.x = geom.x();
    vw.y = geom.y();
    vw.flags = 0;
    if (_type&VID_TYPE_CHROMAKEY) {
        vw.flags |= VIDEO_WINDOW_CHROMAKEY;
    }
    rc = ioctl(_fd, VIDIOCSWIN, &vw);
    if (rc < 0) {
        perror("v4ldev: VIDIOCSWIN");
        return -1;
    }

    return setImageSize(geom.width(), geom.height());
}

int V4LDev::startCapture(int x, int y)
{
    if (!canOverlay())
        return -1;

    if (_overlaid)
        return -1;

    int on = 1;

    struct video_window vw;
    memset(&vw, 0, sizeof(vw));
    int rc = ioctl(_fd, VIDIOCGWIN, &vw);
    if (rc < 0) {
        perror("VIDIOCGWIN");
        return -1;
    }

    vw.x = x;
    vw.y = y;
    vw.flags = 0;
    vw.width = _grabW;
    vw.height = _grabH;
    //kDebug() << "x=" << x << " y=" << y << " w=" << vw.width << " h=" << vw.height;
    vw.flags = 0;
    if (_type&VID_TYPE_CHROMAKEY) {
        kDebug() << "v4ldev: Enabling chromakey for V4L overlay.";
        vw.flags |= VIDEO_WINDOW_CHROMAKEY;
    }
    vw.clipcount = 0;
    rc = ioctl(_fd, VIDIOCSWIN, &vw);
    if (rc < 0) {
        perror("v4ldev: VIDIOCSWIN");
        return -1;
    }

    rc = ioctl(_fd, VIDIOCCAPTURE, &on);
    if (rc < 0) {
        perror("v4ldev: VIDIOCCAPTURE");
        return -1;
    }

    _overlaid = true;
    return 0;
}

int V4LDev::stopCapture()
{
    if (!_overlaid)
        return -1;

    int on = 0;

    int rc = ioctl(_fd, VIDIOCCAPTURE, &on);
    if (rc < 0) return -1;

    _overlaid = false;
    return 0;
}

///////////////////////////////////////////////////////////////////////////////////////
// Format conversion
///////////////////////////////////////////////////////////////////////////////////////

int V4LDev::qvideoformat2v4lformat(ImageFormat f)
{
    if(f & FORMAT_YUYV) {
        return VIDEO_PALETTE_YUV422;
    }
    if(f & FORMAT_BGR24) {
        return VIDEO_PALETTE_RGB24;
    }
    if(f & FORMAT_BGR32) {
        return VIDEO_PALETTE_RGB32;
    }
    if(f & FORMAT_RGB15_LE) {
        return VIDEO_PALETTE_RGB555;
    }
    if(f & FORMAT_RGB16_LE) {
        return VIDEO_PALETTE_RGB565;
    }
    if(f & FORMAT_YUYV) {
        return VIDEO_PALETTE_YUV422;
    }
    if(f & FORMAT_UYVY) {
        return VIDEO_PALETTE_UYVY;
    }
    if(f & FORMAT_YUV422P) {
        return VIDEO_PALETTE_YUV422P;
    }
    if(f & FORMAT_YUV420P) {
        return VIDEO_PALETTE_YUV420P;
    }
    if(f & FORMAT_GREY) {
        return VIDEO_PALETTE_GREY;
    }
    if(f & FORMAT_HI240) {
        return VIDEO_PALETTE_HI240;
    }

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