/*
 *    video2.c
 *
 *    V4L2 interface with basically JPEG decompression support and even more ...
 *    Copyright 2006 Krzysztof Blaszkowski (kb@sysmikro.com.pl)
 *              2007 Angel Carpintero (motiondevelop@gmail.com)

 * Supported features and TODO
   - preferred palette is JPEG which seems to be very popular for many 640x480 usb cams
   - other supported palettes (NOT TESTED)
       V4L2_PIX_FMT_SN9C10X   (sonix)
       V4L2_PIX_FMT_SBGGR16,
       V4L2_PIX_FMT_SBGGR8,   (sonix)
       V4L2_PIX_FMT_SPCA561,
       V4L2_PIX_FMT_SGBRG8,
       V4L2_PIX_FMT_SGRBG8,
       V4L2_PIX_FMT_PAC207,
       V4L2_PIX_FMT_PJPG,
       V4L2_PIX_FMT_MJPEG,    (tested)
       V4L2_PIX_FMT_JPEG,     (tested)
       V4L2_PIX_FMT_RGB24,
       V4L2_PIX_FMT_SPCA501,
       V4L2_PIX_FMT_SPCA505,
       V4L2_PIX_FMT_SPCA508,
       V4L2_PIX_FMT_UYVY,     (tested)
       V4L2_PIX_FMT_YUV422P,
       V4L2_PIX_FMT_YUV420,   (tested)
       V4L2_PIX_FMT_YUYV      (tested)

 *  - setting tuner - NOT TESTED
 *  - access to V4L2 device controls is missing. Partially added but requires some improvements likely.
 *  - changing resolution at run-time may not work.
 *  - ucvideo svn r75 or above to work with MJPEG ( e.g. Logitech 5000 pro )

 * This work is inspired by fswebcam and current design of motion.
 * This interface has been tested with ZC0301 driver from kernel 2.6.17.3 and Labtec's usb camera (PAS202 sensor)

 * I'm very pleased by achieved image quality and cpu usage comparing to junky v4l1 spca5xx driver with
 * it nonsensical kernel messy jpeg decompressor.
 * Default sensor settings used by ZC0301 driver are very reasonable choosen.
 * apparently brigthness should be controlled automatically by motion still for light compensation.
 * it can be done by adjusting ADC gain and also exposure time.

 * Kernel 2.6.27

 V4L2_PIX_FMT_SPCA501 v4l2_fourcc('S', '5', '0', '1')  YUYV per line
 V4L2_PIX_FMT_SPCA505 v4l2_fourcc('S', '5', '0', '5')  YYUV per line
 V4L2_PIX_FMT_SPCA508 v4l2_fourcc('S', '5', '0', '8')  YUVY per line
 V4L2_PIX_FMT_SGBRG8  v4l2_fourcc('G', 'B', 'R', 'G')   8  GBGB.. RGRG..
 V4L2_PIX_FMT_SGRBG8  v4l2_fourcc('G', 'R', 'B', 'G')   8  GRGR.. BGBG..
 V4L2_PIX_FMT_SBGGR16 v4l2_fourcc('B', 'Y', 'R', '2')  16  BGBG.. GRGR..
 V4L2_PIX_FMT_SPCA561 v4l2_fourcc('S', '5', '6', '1')  compressed GBRG bayer
 V4L2_PIX_FMT_PJPG    v4l2_fourcc('P', 'J', 'P', 'G')  Pixart 73xx JPEG
 V4L2_PIX_FMT_PAC207  v4l2_fourcc('P', '2', '0', '7')  compressed BGGR bayer


 *
 * 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.

 let's go :)
*/

#if !defined(WITHOUT_V4L) && defined(MOTION_V4L2)

#include "motion.h"
#include "video.h"

#ifdef MOTION_V4L2_OLD
// Seems that is needed for some system
#include <linux/time.h>
#include <linux/videodev2.h>
#endif

#define u8 unsigned char
#define u16 unsigned short
#define u32 unsigned int
#define s32 signed int

#define MMAP_BUFFERS 4
#define MIN_MMAP_BUFFERS 2

#ifndef V4L2_PIX_FMT_SBGGR8
/* see http://www.siliconimaging.com/RGB%20Bayer.htm */
#define V4L2_PIX_FMT_SBGGR8  v4l2_fourcc('B','A','8','1')  /*  8  BGBG.. GRGR.. */
#endif

#ifndef V4L2_PIX_FMT_MJPEG
#define V4L2_PIX_FMT_MJPEG   v4l2_fourcc('M','J','P','G')  /* Motion-JPEG   */
#endif

#ifndef V4L2_PIX_FMT_SN9C10X
#define V4L2_PIX_FMT_SN9C10X v4l2_fourcc('S','9','1','0')  /* SN9C10x compression */
#endif

#ifndef V4L2_PIX_FMT_SGBRG8
#define V4L2_PIX_FMT_SGBRG8  v4l2_fourcc('G', 'B', 'R', 'G') /*  8  GBGB.. RGRG.. */
#endif

#ifndef V4L2_PIX_FMT_SGRBG8
#define V4L2_PIX_FMT_SGRBG8  v4l2_fourcc('G', 'R', 'B', 'G') /*  8  GRGR.. BGBG.. */
#endif

#ifndef V4L2_PIX_FMT_SBGGR16
#define V4L2_PIX_FMT_SBGGR16 v4l2_fourcc('B', 'Y', 'R', '2') /* 16  BGBG.. GRGR.. */
#endif

#ifndef V4L2_PIX_FMT_SPCA561
#define V4L2_PIX_FMT_SPCA561 v4l2_fourcc('S', '5', '6', '1') /* compressed GBRG bayer */
#endif

#ifndef V4L2_PIX_FMT_PJPG
#define V4L2_PIX_FMT_PJPG    v4l2_fourcc('P', 'J', 'P', 'G') /* Pixart 73xx JPEG */
#endif

#ifndef V4L2_PIX_FMT_PAC207
#define V4L2_PIX_FMT_PAC207  v4l2_fourcc('P', '2', '0', '7') /* compressed BGGR bayer */
#endif

#ifndef V4L2_PIX_FMT_SPCA501
#define V4L2_PIX_FMT_SPCA501 v4l2_fourcc('S', '5', '0', '1') /*  YUYV per line */
#endif

#ifndef V4L2_PIX_FMT_SPCA505
#define V4L2_PIX_FMT_SPCA505 v4l2_fourcc('S', '5', '0', '5') /* YYUV per line  */
#endif

#ifndef V4L2_PIX_FMT_SPCA508
#define V4L2_PIX_FMT_SPCA508 v4l2_fourcc('S', '5', '0', '8') /* YUVY per line  */
#endif

#define ZC301_V4L2_CID_DAC_MAGN       V4L2_CID_PRIVATE_BASE
#define ZC301_V4L2_CID_GREEN_BALANCE  (V4L2_CID_PRIVATE_BASE+1)

static const u32 queried_ctrls[] = {
    V4L2_CID_BRIGHTNESS,
    V4L2_CID_CONTRAST,
    V4L2_CID_SATURATION,
    V4L2_CID_HUE,

    V4L2_CID_RED_BALANCE,
    V4L2_CID_BLUE_BALANCE,
    V4L2_CID_GAMMA,
    V4L2_CID_EXPOSURE,
    V4L2_CID_AUTOGAIN,
    V4L2_CID_GAIN,

    ZC301_V4L2_CID_DAC_MAGN,
    ZC301_V4L2_CID_GREEN_BALANCE,
    0
};

typedef struct {
    int fd;
    u32 fps;

    struct v4l2_capability cap;
    struct v4l2_format src_fmt;
    struct v4l2_format dst_fmt;
    struct v4l2_requestbuffers req;
    struct v4l2_buffer buf;

    video_buff *buffers;

    s32 pframe;

    u32 ctrl_flags;
    struct v4l2_queryctrl *controls;

} src_v4l2_t;

/**
 * xioctl
 */
static int xioctl(int fd, int request, void *arg)
{
    int ret;

    do
        ret = ioctl(fd, request, arg);
    while (-1 == ret && EINTR == errno);

    return ret;
}

/**
 * v4l2_get_capability
 */
static int v4l2_get_capability(src_v4l2_t * vid_source)
{
    if (xioctl(vid_source->fd, VIDIOC_QUERYCAP, &vid_source->cap) < 0) {
        MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO, "%s: Not a V4L2 device?");
        return -1;
    }

    MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: \n------------------------\n"
               "cap.driver: \"%s\"\n"
               "cap.card: \"%s\"\n"
               "cap.bus_info: \"%s\"\n"
               "cap.capabilities=0x%08X\n------------------------",
               vid_source->cap.driver, vid_source->cap.card, vid_source->cap.bus_info,
               vid_source->cap.capabilities);

    if (vid_source->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - VIDEO_CAPTURE");
    if (vid_source->cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - VIDEO_OUTPUT");
    if (vid_source->cap.capabilities & V4L2_CAP_VIDEO_OVERLAY)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - VIDEO_OVERLAY");
    if (vid_source->cap.capabilities & V4L2_CAP_VBI_CAPTURE)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - VBI_CAPTURE");
    if (vid_source->cap.capabilities & V4L2_CAP_VBI_OUTPUT)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - VBI_OUTPUT");
    if (vid_source->cap.capabilities & V4L2_CAP_RDS_CAPTURE)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - RDS_CAPTURE");
    if (vid_source->cap.capabilities & V4L2_CAP_TUNER)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - TUNER");
    if (vid_source->cap.capabilities & V4L2_CAP_AUDIO)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - AUDIO");
    if (vid_source->cap.capabilities & V4L2_CAP_READWRITE)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - READWRITE");
    if (vid_source->cap.capabilities & V4L2_CAP_ASYNCIO)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - ASYNCIO");
    if (vid_source->cap.capabilities & V4L2_CAP_STREAMING)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - STREAMING");
    if (vid_source->cap.capabilities & V4L2_CAP_TIMEPERFRAME)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - TIMEPERFRAME");

    if (!(vid_source->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
        MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO, "%s: Device does not support capturing.");
        return -1;
    }

    return 0;
}

/**
 * v4l2_select_input
 */
static int v4l2_select_input(struct config *conf, struct video_dev *viddev,
                             src_v4l2_t * vid_source, int in, int norm,
                             unsigned long freq_, int tuner_number ATTRIBUTE_UNUSED)
{
    struct v4l2_input input;
    struct v4l2_standard standard;
    v4l2_std_id std_id;

    /* Set the input. */
    memset(&input, 0, sizeof (input));
    if (in == IN_DEFAULT)
        input.index = IN_TV;
    else input.index = in;

    if (xioctl(vid_source->fd, VIDIOC_ENUMINPUT, &input) == -1) {
        MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: Unable to query input %d."
                   " VIDIOC_ENUMINPUT, if you use a WEBCAM change input value in conf by -1", 
                   input.index);
        return -1;
    }

    MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: name = \"%s\", type 0x%08X,"
               " status %08x", input.name, input.type, input.status);

    if (input.type & V4L2_INPUT_TYPE_TUNER)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - TUNER");

    if (input.type & V4L2_INPUT_TYPE_CAMERA)
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - CAMERA");

    if (xioctl(vid_source->fd, VIDIOC_S_INPUT, &input.index) == -1) {
        MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: Error selecting input %d"
                   " VIDIOC_S_INPUT", input.index);
        return -1;
    }

    viddev->input = conf->input = in;

    /*
     * Set video standard usually webcams doesn't support the ioctl or
     * return V4L2_STD_UNKNOWN
     */
    if (xioctl(vid_source->fd, VIDIOC_G_STD, &std_id) == -1) {
        MOTION_LOG(WRN, TYPE_VIDEO, NO_ERRNO, "%s: Device doesn't support VIDIOC_G_STD");
        norm = std_id = 0;    // V4L2_STD_UNKNOWN = 0
    }

    if (std_id) {
        memset(&standard, 0, sizeof(standard));
        standard.index = 0;

        while (xioctl(vid_source->fd, VIDIOC_ENUMSTD, &standard) == 0) {
            if (standard.id & std_id)
                MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: - video standard %s",
                           standard.name);

            standard.index++;
        }

        switch (norm) {
        case 1:
            std_id = V4L2_STD_NTSC;
            break;
        case 2:
            std_id = V4L2_STD_SECAM;
            break;
        default:
            std_id = V4L2_STD_PAL;
        }

        if (xioctl(vid_source->fd, VIDIOC_S_STD, &std_id) == -1)
            MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: Error selecting standard"
                       " method %d VIDIOC_S_STD", (int)std_id);

        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: Set standard method %d",
                   (int)std_id);
    }

    viddev->norm = conf->norm = norm;

    /* If this input is attached to a tuner, set the frequency. */
    if (input.type & V4L2_INPUT_TYPE_TUNER) {
        struct v4l2_tuner tuner;
        struct v4l2_frequency freq;

        /* Query the tuners capabilities. */

        memset(&tuner, 0, sizeof(struct v4l2_tuner));
        tuner.index = input.tuner;

        if (xioctl(vid_source->fd, VIDIOC_G_TUNER, &tuner) == -1) {
            MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: tuner %d VIDIOC_G_TUNER",
                       tuner.index);
            return 0;
        }

        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: Set tuner %d",
                   tuner.index);

        /* Set the frequency. */
        memset(&freq, 0, sizeof(struct v4l2_frequency));
        freq.tuner = input.tuner;
        freq.type = V4L2_TUNER_ANALOG_TV;
        freq.frequency = (freq_ / 1000) * 16;

        if (xioctl(vid_source->fd, VIDIOC_S_FREQUENCY, &freq) == -1) {
            MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: freq %ul VIDIOC_S_FREQUENCY",
                       freq.frequency);
            return 0;
        }

        viddev->freq = conf->frequency = freq_;

        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: Set Frequency to %ul",
                   freq.frequency);
    } else {
        viddev->freq = conf->frequency = 0;
    }

    return 0;
}


/* *
 * v4l2_do_set_pix_format
 *
 *          This routine does the actual request to the driver
 *
 * Returns:  0  Ok
 *          -1  Problems setting palette or not supported
 *
 * Our algorithm for setting the picture format for the data which the
 * driver returns to us will be as follows:
 *
 * First, we request that the format be set to whatever is in the config
 * file (which is either the motion default, or a value chosen by the user).
 * If that request is successful, we are finished.
 *
 * If the driver responds that our request is not accepted, we then enumerate
 * the formats which the driver claims to be able to supply.  From this list,
 * we choose whichever format is "most efficient" for motion.  The enumerated
 * list is also printed to the motion log so that the user can consider
 * choosing a different value for the config file.
 *
 * We then request the driver to set the format we have chosen.  That request
 * should never fail, so if it does we log the fact and give up.
 */
static int v4l2_do_set_pix_format(u32 pixformat, src_v4l2_t * vid_source,
                                  int *width, int *height)
{
    CLEAR(vid_source->dst_fmt);
    vid_source->dst_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    vid_source->dst_fmt.fmt.pix.width = *width;
    vid_source->dst_fmt.fmt.pix.height = *height;
    vid_source->dst_fmt.fmt.pix.pixelformat = pixformat;
    vid_source->dst_fmt.fmt.pix.field = V4L2_FIELD_ANY;

    if (xioctl(vid_source->fd, VIDIOC_TRY_FMT, &vid_source->dst_fmt) != -1 &&
        vid_source->dst_fmt.fmt.pix.pixelformat == pixformat) {
        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: Testing palette %c%c%c%c (%dx%d)",
                   pixformat >> 0, pixformat >> 8,
                   pixformat >> 16, pixformat >> 24, *width, *height);

        if (vid_source->dst_fmt.fmt.pix.width != (unsigned int) *width ||
            vid_source->dst_fmt.fmt.pix.height != (unsigned int) *height) {

            MOTION_LOG(WRN, TYPE_VIDEO, NO_ERRNO, "%s: Adjusting resolution "
                       "from %ix%i to %ix%i.",
                       *width, *height, vid_source->dst_fmt.fmt.pix.width,
                       vid_source->dst_fmt.fmt.pix.height);

            *width = vid_source->dst_fmt.fmt.pix.width;
            *height = vid_source->dst_fmt.fmt.pix.height;
        }

        if (xioctl(vid_source->fd, VIDIOC_S_FMT, &vid_source->dst_fmt) == -1) {
            MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: Error setting pixel "
                       "format.\nVIDIOC_S_FMT: ");
            return -1;
        }

        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: Using palette %c%c%c%c (%dx%d)"
                   " bytesperlines %d sizeimage %d colorspace %08x", pixformat >> 0,
                   pixformat >> 8, pixformat >> 16, pixformat >> 24, *width,
                   *height, vid_source->dst_fmt.fmt.pix.bytesperline,
                   vid_source->dst_fmt.fmt.pix.sizeimage,
                   vid_source->dst_fmt.fmt.pix.colorspace);

        return 0;
    }

    return -1;
}

/**
 * v4l2_set_pix_format
 *
 * Returns:  0  Ok
 *          -1  Problems setting palette or not supported
 */
static int v4l2_set_pix_format(struct context *cnt, src_v4l2_t * vid_source,
                               int *width, int *height)
{
    struct v4l2_fmtdesc fmtd;
    int v4l2_pal;

    /*
     * Note that this array MUST exactly match the config file list.
     * A higher index means better chance to be used
     */
    static const u32 supported_formats[] = {
        V4L2_PIX_FMT_SN9C10X,
        V4L2_PIX_FMT_SBGGR16,
        V4L2_PIX_FMT_SBGGR8,
        V4L2_PIX_FMT_SPCA561,
        V4L2_PIX_FMT_SGBRG8,
        V4L2_PIX_FMT_SGRBG8,
        V4L2_PIX_FMT_PAC207,
        V4L2_PIX_FMT_PJPG,
        V4L2_PIX_FMT_MJPEG,
        V4L2_PIX_FMT_JPEG,
        V4L2_PIX_FMT_RGB24,
        V4L2_PIX_FMT_SPCA501,
        V4L2_PIX_FMT_SPCA505,
        V4L2_PIX_FMT_SPCA508,
        V4L2_PIX_FMT_UYVY,
        V4L2_PIX_FMT_YUYV,
        V4L2_PIX_FMT_YUV422P,
        V4L2_PIX_FMT_YUV420 /* most efficient for motion */
    };

    int array_size = sizeof(supported_formats) / sizeof(supported_formats[0]);
    int index_format = -1; /* -1 says not yet chosen */
    CLEAR(fmtd);
    fmtd.index = v4l2_pal = 0;
    fmtd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    /* First we try a shortcut of just setting the config file value */
    if (cnt->conf.v4l2_palette >= 0) {
        char name[5] = {supported_formats[cnt->conf.v4l2_palette] >>  0,
                        supported_formats[cnt->conf.v4l2_palette] >>  8,
                        supported_formats[cnt->conf.v4l2_palette] >>  16,
                        supported_formats[cnt->conf.v4l2_palette] >>  24, 0};

        if (v4l2_do_set_pix_format(supported_formats[cnt->conf.v4l2_palette],
                                   vid_source, width, height) >= 0)
            return 0;

        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: Config palette index %d (%s)"
                   " doesn't work.", cnt->conf.v4l2_palette, name);
    }

    MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: Supported palettes:");

    while (xioctl(vid_source->fd, VIDIOC_ENUM_FMT, &fmtd) != -1) {

        int i;

        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: (%i) %c%c%c%c (%s)",
                   v4l2_pal, fmtd.pixelformat >> 0,
                   fmtd.pixelformat >> 8, fmtd.pixelformat >> 16,
                   fmtd.pixelformat >> 24, fmtd.description);

        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: %d - %s (compressed : %d) (%#x)",
                   fmtd.index, fmtd.description, fmtd.flags, fmtd.pixelformat);

         /* Adjust index_format if larger value found */
        for (i = index_format + 1; i < array_size; i++)
            if (supported_formats[i] == fmtd.pixelformat)
                index_format = i;

        CLEAR(fmtd);
        fmtd.index = ++v4l2_pal;
        fmtd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    }

    if (index_format >= 0) {
        char name[5] = {supported_formats[index_format] >>  0,
                        supported_formats[index_format] >>  8,
                        supported_formats[index_format] >>  16,
                        supported_formats[index_format] >>  24, 0};

        MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s Selected palette %s", name);

        if (v4l2_do_set_pix_format(supported_formats[index_format],
                                   vid_source, width, height) >= 0)
            return 0;

        MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "VIDIOC_TRY_FMT failed for "
                   "format %s", name);
    }

    MOTION_LOG(ERR, TYPE_VIDEO, NO_ERRNO, "%s: Unable to find a compatible"
               " palette format.");

    return -1;
}

#if 0
static void v4l2_set_fps(src_v4l2_t * vid_source) {
    struct v4l2_streamparm* setfps;

    setfps = (struct v4l2_streamparm *) calloc(1, sizeof(struct v4l2_streamparm));
    memset(setfps, 0, sizeof(struct v4l2_streamparm));
    setfpvid_source->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    setfpvid_source->parm.capture.timeperframe.numerator = 1;
    setfpvid_source->parm.capture.timeperframe.denominator = vid_source->fps;

    if (xioctl(vid_source->fd, VIDIOC_S_PARM, setfps) == -1)
        MOTION_LOG(ERR, 1, "%s: v4l2_set_fps VIDIOC_S_PARM");


}
#endif

/**
 * v4l2_set_mmap
 */
static int v4l2_set_mmap(src_v4l2_t * vid_source)
{
    enum v4l2_buf_type type;
    u32 buffer_index;

    /* Does the device support streaming? */
    if (!(vid_source->cap.capabilities & V4L2_CAP_STREAMING))
        return -1;

    memset(&vid_source->req, 0, sizeof(struct v4l2_requestbuffers));

    vid_source->req.count = MMAP_BUFFERS;
    vid_source->req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    vid_source->req.memory = V4L2_MEMORY_MMAP;

    if (xioctl(vid_source->fd, VIDIOC_REQBUFS, &vid_source->req) == -1) {
        MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: Error requesting buffers"
                   " %d for memory map. VIDIOC_REQBUFS",
                   vid_source->req.count);
        return -1;
    }

    MOTION_LOG(DBG, TYPE_VIDEO, NO_ERRNO, "%s: mmap information: frames=%d",
               vid_source->req.count);

    if (vid_source->req.count < MIN_MMAP_BUFFERS) {
        MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: Insufficient buffer memory"
                   " %d < MIN_MMAP_BUFFERS.", vid_source->req.count);
        return -1;
    }

    vid_source->buffers = calloc(vid_source->req.count, sizeof(video_buff));

    if (!vid_source->buffers) {
        MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: Out of memory.");
        return -1;
    }

    for (buffer_index = 0; buffer_index < vid_source->req.count; buffer_index++) {
        struct v4l2_buffer buf;

        memset(&buf, 0, sizeof(struct v4l2_buffer));

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = buffer_index;

        if (xioctl(vid_source->fd, VIDIOC_QUERYBUF, &buf) == -1) {
            MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: Error querying buffer"
                       " %i\nVIDIOC_QUERYBUF: ", buffer_index);
            free(vid_source->buffers);
            return -1;
        }

        vid_source->buffers[buffer_index].size = buf.length;
        vid_source->buffers[buffer_index].ptr = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
                                                     MAP_SHARED, vid_source->fd, buf.m.offset);

        if (vid_source->buffers[buffer_index].ptr == MAP_FAILED) {
            MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: Error mapping buffer %i mmap",
                       buffer_index);
            free(vid_source->buffers);
            return -1;
        }

        MOTION_LOG(DBG, TYPE_VIDEO, NO_ERRNO, "%s: %i length=%d Address (%x)",
                   buffer_index, buf.length, vid_source->buffers[buffer_index].ptr);
    }

    for (buffer_index = 0; buffer_index < vid_source->req.count; buffer_index++) {
        memset(&vid_source->buf, 0, sizeof(struct v4l2_buffer));

        vid_source->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        vid_source->buf.memory = V4L2_MEMORY_MMAP;
        vid_source->buf.index = buffer_index;

        if (xioctl(vid_source->fd, VIDIOC_QBUF, &vid_source->buf) == -1) {
            MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: VIDIOC_QBUF");
            return -1;
        }
    }

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (xioctl(vid_source->fd, VIDIOC_STREAMON, &type) == -1) {
        MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: Error starting stream."
                   " VIDIOC_STREAMON");
        return -1;
    }

    return 0;
}

/**
 * v4l2_scan_controls
 */
static int v4l2_scan_controls(src_v4l2_t * vid_source)
{
    int count, i;
    struct v4l2_queryctrl queryctrl;

    memset(&queryctrl, 0, sizeof(struct v4l2_queryctrl));

    for (i = 0, count = 0; queried_ctrls[i]; i++) {
        queryctrl.id = queried_ctrls[i];
        if (xioctl(vid_source->fd, VIDIOC_QUERYCTRL, &queryctrl))
            continue;

        count++;
        vid_source->ctrl_flags |= 1 << i;
    }

    if (count) {
        struct v4l2_queryctrl *ctrl = vid_source->controls
                                    = calloc(count, sizeof(struct v4l2_queryctrl));

        if (!ctrl) {
            MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: Insufficient buffer memory.");
            return -1;
        }

        for (i = 0; queried_ctrls[i]; i++) {
            if (vid_source->ctrl_flags & (1 << i)) {
                struct v4l2_control control;

                queryctrl.id = queried_ctrls[i];
                if (xioctl(vid_source->fd, VIDIOC_QUERYCTRL, &queryctrl))
                    continue;

                memcpy(ctrl, &queryctrl, sizeof(struct v4l2_queryctrl));

                MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: found control 0x%08x, \"%s\","
                           " range %d,%d %s",
                           ctrl->id, ctrl->name, ctrl->minimum, ctrl->maximum,
                           ctrl->flags & V4L2_CTRL_FLAG_DISABLED ? "!DISABLED!" : "");

                memset(&control, 0, sizeof (control));
                control.id = queried_ctrls[i];
                xioctl(vid_source->fd, VIDIOC_G_CTRL, &control);
                MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: \t\"%s\", default %d, current %d",
                           ctrl->name, ctrl->default_value, control.value);

                ctrl++;
            }
        }
    }

    return 0;
}

/**
 * v4l2_set_control
 */
static int v4l2_set_control(src_v4l2_t * vid_source, u32 cid, int value)
{
    int i, count;

    if (!vid_source->controls)
        return -1;

    for (i = 0, count = 0; queried_ctrls[i]; i++) {
        if (vid_source->ctrl_flags & (1 << i)) {
            if (cid == queried_ctrls[i]) {
                struct v4l2_queryctrl *ctrl = vid_source->controls + count;
                struct v4l2_control control;
                int ret;

                memset(&control, 0, sizeof (control));
                control.id = queried_ctrls[i];

                switch (ctrl->type) {
                case V4L2_CTRL_TYPE_INTEGER:
                    value = control.value =
                            (value * (ctrl->maximum - ctrl->minimum) / 256) + ctrl->minimum;
                    ret = xioctl(vid_source->fd, VIDIOC_S_CTRL, &control);
                    break;

                case V4L2_CTRL_TYPE_BOOLEAN:
                    value = control.value = value ? 1 : 0;
                    ret = xioctl(vid_source->fd, VIDIOC_S_CTRL, &control);
                    break;

                default:
                    MOTION_LOG(WRN, TYPE_VIDEO, NO_ERRNO, "%s: control type not supported yet");
                    return -1;
                }

                MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: setting control \"%s\" to %d"
                           " (ret %d %s) %s", ctrl->name, value, ret, ret ? strerror(errno) : "",
                           ctrl->flags & V4L2_CTRL_FLAG_DISABLED ? "Control is DISABLED!" : "");

                return 0;
            }
            count++;
        }
    }

    return -1;
}

/**
 * v4l2_picture_controls
 */
static void v4l2_picture_controls(struct context *cnt, struct video_dev *viddev)
{
    src_v4l2_t *vid_source = (src_v4l2_t *) viddev->v4l2_private;

    if (cnt->conf.contrast && cnt->conf.contrast != viddev->contrast) {
        viddev->contrast = cnt->conf.contrast;
        v4l2_set_control(vid_source, V4L2_CID_CONTRAST, viddev->contrast);
    }

    if (cnt->conf.saturation && cnt->conf.saturation != viddev->saturation) {
        viddev->saturation = cnt->conf.saturation;
        v4l2_set_control(vid_source, V4L2_CID_SATURATION, viddev->saturation);
    }

    if (cnt->conf.hue && cnt->conf.hue != viddev->hue) {
        viddev->hue = cnt->conf.hue;
        v4l2_set_control(vid_source, V4L2_CID_HUE, viddev->hue);
    }

    if (cnt->conf.autobright) {
        if (vid_do_autobright(cnt, viddev)) {
            if (v4l2_set_control(vid_source, V4L2_CID_BRIGHTNESS, viddev->brightness))
                v4l2_set_control(vid_source, V4L2_CID_GAIN, viddev->brightness);
        }
    } else {
        if (cnt->conf.brightness && cnt->conf.brightness != viddev->brightness) {
            viddev->brightness = cnt->conf.brightness;
            if (v4l2_set_control(vid_source, V4L2_CID_BRIGHTNESS, viddev->brightness))
                v4l2_set_control(vid_source, V4L2_CID_GAIN, viddev->brightness);
        }
    }

}

/* public functions */
/**
 * v4l2_start
 */
unsigned char *v4l2_start(struct context *cnt, struct video_dev *viddev, int width, int height,
              int input, int norm, unsigned long freq, int tuner_number)
{
    src_v4l2_t *vid_source;

    /* Allocate memory for the state structure. */
    if (!(vid_source = calloc(sizeof(src_v4l2_t), 1))) {
        MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: Out of memory.");
        goto err;
    }

    viddev->v4l2_private = vid_source;
    vid_source->fd = viddev->fd;
    vid_source->fps = cnt->conf.frame_limit;
    vid_source->pframe = -1;
    struct config *conf = &cnt->conf;

    if (v4l2_get_capability(vid_source))
        goto err;

    if (v4l2_select_input(conf, viddev, vid_source, input, norm, freq, tuner_number))
        goto err;

    if (v4l2_set_pix_format(cnt, vid_source, &width, &height))
        goto err;

    if (v4l2_scan_controls(vid_source))
        goto err;

#if 0
    v4l2_set_fps(vid_source);
#endif
    if (v4l2_set_mmap(vid_source))
        goto err;

    viddev->size_map = 0;
    viddev->v4l_buffers[0] = NULL;
    viddev->v4l_maxbuffer = 1;
    viddev->v4l_curbuffer = 0;

    viddev->v4l_fmt = VIDEO_PALETTE_YUV420P;
    viddev->v4l_bufsize = (width * height * 3) / 2;


    /* Update width and height with supported values from camera driver */
    viddev->width = width;
    viddev->height = height;

    return (void *) 1;

err:
    if (vid_source)
        free(vid_source);

    viddev->v4l2_private = NULL;
    viddev->v4l2 = 0;
    return NULL;
}

/**
 * v4l2_set_input
 */
void v4l2_set_input(struct context *cnt, struct video_dev *viddev, unsigned char *map,
                    int width, int height, struct config *conf)
{
    int input = conf->input;
    int norm = conf->norm;
    unsigned long freq = conf->frequency;
    int tuner_number = conf->tuner_number;

    if (input != viddev->input || width != viddev->width || height != viddev->height ||
        freq != viddev->freq || tuner_number != viddev->tuner_number || norm != viddev->norm) {

        unsigned int i;
        struct timeval switchTime;
        unsigned int skip = conf->roundrobin_skip;

        if (conf->roundrobin_skip < 0)
            skip = 1;

        v4l2_select_input(conf, viddev, (src_v4l2_t *) viddev->v4l2_private,
                          input, norm, freq, tuner_number);

        gettimeofday(&switchTime, NULL);

        v4l2_picture_controls(cnt, viddev);

        viddev->width = width;
        viddev->height = height;

        /*
        viddev->input = input;
        viddev->norm = norm;
        viddev->width = width;
        viddev->height = height;
        viddev->freq = freq;
        viddev->tuner_number = tuner_number;
        */

        /* Skip all frames captured before switchtime, capture 1 after switchtime */
        {
            src_v4l2_t *vid_source = (src_v4l2_t *) viddev->v4l2_private;
            unsigned int counter = 0;

            MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: set_input_skip_frame "
                       "switch_time=%ld:%ld", switchTime.tv_sec, switchTime.tv_usec);

            /* Avoid hang using the number of mmap buffers */
            while(counter < vid_source->req.count) {
                counter++;
                if (v4l2_next(cnt, viddev, map, width, height))
                    break;

                if (vid_source->buf.timestamp.tv_sec > switchTime.tv_sec ||
                   (vid_source->buf.timestamp.tv_sec == switchTime.tv_sec &&
                    vid_source->buf.timestamp.tv_usec > switchTime.tv_usec))
                    break;

                MOTION_LOG(NTC, TYPE_VIDEO, NO_ERRNO, "%s: got frame before "
                           " switch timestamp=%ld:%ld",
                           vid_source->buf.timestamp.tv_sec,
                           vid_source->buf.timestamp.tv_usec);
            }
        }

        /* skip a few frames if needed */
        for (i = 1; i < skip; i++)
            v4l2_next(cnt, viddev, map, width, height);
    } else {
        /* No round robin - we only adjust picture controls */
        v4l2_picture_controls(cnt, viddev);
    }
}

/**
 * v4l2_next
 */
int v4l2_next(struct context *cnt, struct video_dev *viddev, unsigned char *map,
              int width, int height)
{
    sigset_t set, old;
    src_v4l2_t *vid_source = (src_v4l2_t *) viddev->v4l2_private;

    if (viddev->v4l_fmt != VIDEO_PALETTE_YUV420P)
        return V4L_FATAL_ERROR;

    /* Block signals during IOCTL */
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigaddset(&set, SIGALRM);
    sigaddset(&set, SIGUSR1);
    sigaddset(&set, SIGTERM);
    sigaddset(&set, SIGHUP);
    pthread_sigmask(SIG_BLOCK, &set, &old);

    MOTION_LOG(DBG, TYPE_VIDEO, NO_ERRNO, "%s: 1) vid_source->pframe %i",
               vid_source->pframe);

    if (vid_source->pframe >= 0) {
        if (xioctl(vid_source->fd, VIDIOC_QBUF, &vid_source->buf) == -1) {
            MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: VIDIOC_QBUF");
            pthread_sigmask(SIG_UNBLOCK, &old, NULL);
            return -1;
        }
    }

    memset(&vid_source->buf, 0, sizeof(struct v4l2_buffer));

    vid_source->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    vid_source->buf.memory = V4L2_MEMORY_MMAP;

    if (xioctl(vid_source->fd, VIDIOC_DQBUF, &vid_source->buf) == -1) {
        int ret;
        /*
         * Some drivers return EIO when there is no signal,
         * driver might dequeue an (empty) buffer despite
         * returning an error, or even stop capturing.
         */
        if (errno == EIO) {
            vid_source->pframe++;

            if ((u32)vid_source->pframe >= vid_source->req.count)
                vid_source->pframe = 0;

             vid_source->buf.index = vid_source->pframe;
             MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: VIDIOC_DQBUF: EIO "
                        "(vid_source->pframe %d)", vid_source->pframe);
             ret = 1;
        } else if (errno == EAGAIN) {
            MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: VIDIOC_DQBUF: EAGAIN"
                       " (vid_source->pframe %d)", vid_source->pframe);
            ret = 1;
        } else {
            MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: VIDIOC_DQBUF");
            ret = -1;
        }

        pthread_sigmask(SIG_UNBLOCK, &old, NULL);
        return ret;
    }

    MOTION_LOG(DBG, TYPE_VIDEO, NO_ERRNO, "%s: 2) vid_source->pframe %i",
               vid_source->pframe);

    vid_source->pframe = vid_source->buf.index;
    vid_source->buffers[vid_source->buf.index].used = vid_source->buf.bytesused;
    vid_source->buffers[vid_source->buf.index].content_length = vid_source->buf.bytesused;

    MOTION_LOG(DBG, TYPE_VIDEO, NO_ERRNO, "%s: 3) vid_source->pframe %i "
               "vid_source->buf.index %i", vid_source->pframe, vid_source->buf.index);
    MOTION_LOG(DBG, TYPE_VIDEO, NO_ERRNO, "%s: vid_source->buf.bytesused %i",
               vid_source->buf.bytesused);

    pthread_sigmask(SIG_UNBLOCK, &old, NULL);    /*undo the signal blocking */

    {
        video_buff *the_buffer = &vid_source->buffers[vid_source->buf.index];

        MOTION_LOG(DBG, TYPE_VIDEO, NO_ERRNO, "%s: the_buffer index %d Address (%x)",
                   vid_source->buf.index, the_buffer->ptr);

        switch (vid_source->dst_fmt.fmt.pix.pixelformat) {
        case V4L2_PIX_FMT_RGB24:
            conv_rgb24toyuv420p(map, the_buffer->ptr, width, height);
            return 0;

        case V4L2_PIX_FMT_UYVY:
            conv_uyvyto420p(map, the_buffer->ptr, (unsigned)width, (unsigned)height);
            return 0;

        case V4L2_PIX_FMT_YUYV:
        case V4L2_PIX_FMT_YUV422P:
            conv_yuv422to420p(map, the_buffer->ptr, width, height);
            return 0;

        case V4L2_PIX_FMT_YUV420:
            memcpy(map, the_buffer->ptr, viddev->v4l_bufsize);
            return 0;

        case V4L2_PIX_FMT_PJPG:
        case V4L2_PIX_FMT_JPEG:
        case V4L2_PIX_FMT_MJPEG:
            return mjpegtoyuv420p(map, the_buffer->ptr, width, height,
                                  vid_source->buffers[vid_source->buf.index].content_length);

        /* FIXME: quick hack to allow work all bayer formats */
        case V4L2_PIX_FMT_SBGGR16:
        case V4L2_PIX_FMT_SGBRG8:
        case V4L2_PIX_FMT_SGRBG8:
        /* case V4L2_PIX_FMT_SPCA561: */
        case V4L2_PIX_FMT_SBGGR8:    /* bayer */
            bayer2rgb24(cnt->imgs.common_buffer, the_buffer->ptr, width, height);
            conv_rgb24toyuv420p(map, cnt->imgs.common_buffer, width, height);
            return 0;

        case V4L2_PIX_FMT_SPCA561:
        case V4L2_PIX_FMT_SN9C10X:
            sonix_decompress(map, the_buffer->ptr, width, height);
            bayer2rgb24(cnt->imgs.common_buffer, map, width, height);
            conv_rgb24toyuv420p(map, cnt->imgs.common_buffer, width, height);
            return 0;
        }
    }

    return 1;
}

/**
 * v4l2_close
 */
void v4l2_close(struct video_dev *viddev)
{
    src_v4l2_t *vid_source = (src_v4l2_t *) viddev->v4l2_private;
    enum v4l2_buf_type type;

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    xioctl(vid_source->fd, VIDIOC_STREAMOFF, &type);
    close(vid_source->fd);
    vid_source->fd = -1;
}

/**
 * v4l2_cleanup
 */
void v4l2_cleanup(struct video_dev *viddev)
{
    src_v4l2_t *vid_source = (src_v4l2_t *) viddev->v4l2_private;

    if (vid_source->buffers) {
        unsigned int i;

        for (i = 0; i < vid_source->req.count; i++)
            munmap(vid_source->buffers[i].ptr, vid_source->buffers[i].size);

        free(vid_source->buffers);
        vid_source->buffers = NULL;
    }

    if (vid_source->controls) {
        free(vid_source->controls);
        vid_source->controls = NULL;
    }

    free(vid_source);
    viddev->v4l2_private = NULL;
}
#endif /* !WITHOUT_V4L && MOTION_V4L2 */
