/*
  v4l.cc
  By Wayne Piekarski - wayne@cs.unisa.edu.au

  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.
  
  See the file LICENSE included in this directory for more information.
*/


/* This line is required to fix a current bug in Debian unstable headers */
#define __KERNEL_STRICT_NAMES


/* Standard includes */
#include <linux/videodev.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

#include "v4l.h"


/* V4L data structures */
struct video_capability dev_info;
struct video_channel    channel_info;
struct video_picture    picture_info;
struct video_mbuf       mbuf_info;
struct video_mmap       mmap_info;


/* Actual memory map of video data */
char *memory_map;


/* Configuration variables */
int v4l_width, v4l_height, channel, fd;
double brightness = 128;
double contrast = 128;
double color = 128;




int open_v4l (char *device, int *out_width, int *out_height)
{
  /* Open up the video device */
  fd = open (device, O_RDWR);
  if (fd < 0)
    {    
      fprintf (stderr, "Could not open video device %s for read/write\n", device);
      return (-1);
    }
  
  /* Get the capabilities of the video source */
  ioctl (fd, VIDIOCGCAP, &dev_info);
  fprintf (stderr,  "Device = %s, Name = %s\n", device, dev_info.name);
  fprintf (stderr,  "Channels = %d, Width = %d..%d, Height = %d..%d\n",
           dev_info.channels, dev_info.minwidth, dev_info.maxwidth, dev_info.minheight, dev_info.maxheight);
  
  /* Check the values to make sure they are sane */
  v4l_width  = dev_info.maxwidth;
  v4l_height = dev_info.maxheight;
  *out_width = v4l_width;
  *out_height = v4l_height;
  fprintf (stderr,  "Setting resolution to maximum X=%d, Y=%d\n", v4l_width, v4l_height);
  if ((v4l_width <= 0) || (v4l_height <= 0))
    {
      fprintf (stderr, "Device %s width/height values (%d, %d) are not valid, must be positive\n", device, v4l_width, v4l_height);
      exit (1);
    }
  
  /* Set the channel to the A/V inputs */
  channel_info.channel = channel;
  if (channel_info.channel >= dev_info.channels)
    {
      fprintf (stderr,  "Adjusting channel value from %d to %d to put it within the valid range\n", channel_info.channel, dev_info.channels - 1);
      channel_info.channel = dev_info.channels - 1;
    }

  /* Grab the information for the above selected channel */
  ioctl (fd, VIDIOCGCHAN, &channel_info);

  /* Set the mode to PAL and print out debugging */
  channel_info.norm = 0; /* ----- Was 0 for PAL ----- */
  fprintf (stderr,  "Channel %d, Name = %s, Tuners = %d, Mode = %d\n",
           channel_info.channel, channel_info.name, channel_info.tuners, channel_info.norm);

  /* Set the channel to the one we want */
  ioctl (fd, VIDIOCSCHAN, &channel_info);

  /* Set the picture parameters */
  picture_info.brightness = int (32767 * 2.0 * brightness);
  picture_info.hue        = 32767;
  picture_info.colour     = int (32767 * 2.0 * color);
  picture_info.contrast   = int (32767 * 2.0 * contrast);
  picture_info.whiteness  = 32767;
  picture_info.depth      = 24;
  picture_info.palette    = VIDEO_PALETTE_RGB24;
  fprintf (stderr,  "Bright = %d, Hue = %d, Colour = %d, Contrast = %d, White = %d, Depth = %d\n",
           picture_info.brightness, picture_info.hue, picture_info.colour, picture_info.contrast,
           picture_info.whiteness, picture_info.depth);
  ioctl (fd, VIDIOCSPICT, &picture_info);

  /* Get memory map information */
  ioctl (fd, VIDIOCGMBUF, &mbuf_info);
  fprintf (stderr,  "Memory Size = %d, Frames = %d\n", mbuf_info.size, mbuf_info.frames);

  /* We need at least two frames for double buffering */
  if (mbuf_info.frames < 2)
    {
      fprintf (stderr, "%d frames is not enough to support double buffering, at least 2 is required\n", mbuf_info.frames);
      exit (1);
    }
  
  /* Open up the memory map so we can use it */
  memory_map = (char *)mmap (0, mbuf_info.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (memory_map == MAP_FAILED)
    {
      fprintf (stderr, "Could not mmap() %d bytes from the device %s\n", mbuf_info.size, device);
      exit (1);
    }
  
  /* Setup structure so the capture calls can use it */
  mmap_info.frame  = 0;
  mmap_info.width  = v4l_width;
  mmap_info.height = v4l_height;
  mmap_info.format = picture_info.palette;
  
  /* Success */
  return (0);
}




void start_v4l (void)
{
  /* Set to the first frame */
  mmap_info.frame = 0;

  /* Start the capture for the first frame */
  ioctl (fd, VIDIOCMCAPTURE, &mmap_info);

  /* Start the second frame as well */
  mmap_info.frame = 1;

  /* Start the capture for the second frame */
  ioctl (fd, VIDIOCMCAPTURE, &mmap_info);

  /* We will use frame zero as the start now */
  mmap_info.frame = 0;
}




void capture_v4l (void)
{
  /* Flip the frame values, the next operation will be on the next frame */
  mmap_info.frame = 1 - mmap_info.frame;

  /* Release the previously used frame returned in getFrame() */
  ioctl (fd, VIDIOCSYNC, &mmap_info.frame);

  /* Start this frame off for capturing the next frame */
  ioctl (fd, VIDIOCMCAPTURE, &mmap_info);
}




char *get_v4l (void)
{
  /* Return back a pointer to the current memory buffer */
  return (memory_map + mbuf_info.offsets [mmap_info.frame]);
}
