/*  FreeJ
 *  (c) Copyright 2001-2002 Denis Rojo aka jaromil <jaromil@dyne.org>
 *
 */

#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>

#include <v4l_layer.h>
#include <tvfreq.h>
#include <jutils.h>
#include <config.h>

V4lGrabber::V4lGrabber()
  :Layer() {
  dev = -1;
  rgb_surface = NULL;
  buffer = NULL;
  have_tuner=false;
  setname("V4L");
}

V4lGrabber::~V4lGrabber() {
  close();
}

void V4lGrabber::close() {
  if(dev>0) notice("Closing video4linux grabber layer");

  if(buffer!=NULL) {
    act("unmapping address %p sized %u bytes",buffer,grab_map.size);
    munmap(buffer,grab_map.size);
  }

  if(dev>0) {
    act("closing video4linux device %u",dev);
    ::close(dev);
  }

  if(rgb_surface) jfree(rgb_surface);

}

bool V4lGrabber::detect(char *devfile) {
  int counter, res;
  char *capabilities[] = {
    "VID_TYPE_CAPTURE          can capture to memory",
    "VID_TYPE_TUNER            has a tuner of some form",
    "VID_TYPE_TELETEXT         has teletext capability",
    "VID_TYPE_OVERLAY          can overlay its image to video",
    "VID_TYPE_CHROMAKEY        overlay is chromakeyed",
    "VID_TYPE_CLIPPING         overlay clipping supported",
    "VID_TYPE_FRAMERAM         overlay overwrites video memory",
    "VID_TYPE_SCALES           supports image scaling",
    "VID_TYPE_MONOCHROME       image capture is grey scale only",
    "VID_TYPE_SUBCAPTURE       capture can be of only part of the image"
  };

  func("V4lGrabber::detect()");

  if (-1 == (dev = ::open(devfile,O_RDWR|O_NONBLOCK))) {
    error("capture device %s: %s",devfile,strerror(errno));
    return(false);
  } else {
    ::close(dev);
    dev = ::open(devfile,O_RDWR);
  }

  res = ioctl(dev,VIDIOCGCAP,&grab_cap);
  if(res<0) {
    error("error in VIDIOCGCAP ");
    return(false);
  }

  if(get_debug()>0) {

    notice("Device detected is %s",devfile);
    act("%s",grab_cap.name);
    act("%u channels detected",grab_cap.channels);
    act("max size w[%u] h[%u] - min size w[%u] h[%u]",grab_cap.maxwidth,grab_cap.maxheight,grab_cap.minwidth,grab_cap.minheight);
    act("Video capabilities:");
    for (counter=0;counter<11;counter++)
      if (grab_cap.type & (1 << counter)) act("%s",capabilities[counter]);

    if (-1 == ioctl(dev, VIDIOCGPICT, &grab_pic)) {
      error("ioctl VIDIOCGPICT ");
      exit(1);
    }

    if (grab_pic.palette & VIDEO_PALETTE_GREY)
      act("VIDEO_PALETTE_GREY        device is able to grab greyscale frames");
  }

  if(grab_cap.type & VID_TYPE_TUNER)
    /* if the device does'nt has any tuner, so we avoid some ioctl
       this should be a fix for many webcams, thanks to Ben Wilson */
    have_tuner = 1;

  /* set and check the minwidth and minheight */
  minw = grab_cap.minwidth;
  minh = grab_cap.minheight;
  maxw = grab_cap.maxwidth;
  maxh = grab_cap.maxheight;
  if( (minw>320) || (minh>240) || (maxw<320) || (maxh<240) ) {
    error("your device does'nt supports grabbing to right size");
    return(false);
  }

  if (ioctl (dev, VIDIOCGMBUF, &grab_map) == -1) {
    error("error in ioctl VIDIOCGMBUF");
    return(false);
  }
  /* print memory info */
  if(get_debug()>0) {
    act("memory map of %i frames: %i bytes",grab_map.frames,grab_map.size);
    for(counter=0;counter<grab_map.frames;counter++)
      act("Offset of frame %i: %i",counter,grab_map.offsets[counter]);
  }
  num_frame = grab_map.frames;
  channels = grab_cap.channels;
  set_filename(devfile);
  return(true);
}

bool V4lGrabber::init(Context *screen,int wdt, int hgt) {
  int i;
  func("V4lGrabber::init()");

  /* set image source and TV norm */
  grab_chan.channel = input = (channels>1) ? 1 : 0;

  if(have_tuner) { /* does this only if the device has a tuner */
    _band = 5; /* default band is europe west */
    _freq = 0;
    /* resets CHAN */
    if (-1 == ioctl(dev,VIDIOCGCHAN,&grab_chan)) {
      error("error in ioctl VIDIOCGCHAN ");
      return(false);
    }

    if (-1 == ioctl(dev,VIDIOCSCHAN,&grab_chan)) {
      error("error in ioctl VIDIOCSCHAN ");
      return(false);
    }

    /* get/set TUNER settings */
    if (-1 == ioctl(dev,VIDIOCGTUNER,&grab_tuner)) {
      error("error in ioctl VIDIOCGTUNER ");
      return(false);
    }
  }

  /* INIT from the LAYER CLASS */
  _init(screen,wdt,hgt);

  palette = VIDEO_PALETTE_YUV422P;
  /* choose best yuv2rgb routine (detecting cpu)
     supported: C, ASM-MMX, ASM-MMX+SSE */
  yuv2rgb = yuv2rgb_init(geo.bpp,0x1); /* arg2 is MODE_RGB */
  rgb_surface = jalloc(rgb_surface,geo.size);

  u = (geo.w*geo.h);
  v = u+(u/2);

  /* mmap (POSIX.4) buffer for grabber device */
  buffer = (unsigned char *) mmap(0,grab_map.size,PROT_READ|PROT_WRITE,MAP_SHARED,dev,0);
  if(MAP_FAILED == buffer) {
    error("cannot allocate v4lgrabber buffer ");
    return(false);
  }

  for(i=0; i<grab_map.frames; i++) {
    /* initialize frames geometry */
    grab_buf[i].format = palette;
    grab_buf[i].frame  = i;
    grab_buf[i].height = geo.h;
    grab_buf[i].width = geo.w;
  }

  /* feed up the mmapped frame */
  if (-1 == ioctl(dev,VIDIOCMCAPTURE,&grab_buf[0])) {
    func("V4lGrabber::init");
    error("error in ioctl VIDIOCMCAPTURE on buffer %p",&grab_buf[0]);
  }


  cur_frame = ok_frame = 0;

  notice("V4L layer :: w[%u] h[%u] bpp[%u] size[%u] grab_mmap[%u]",geo.w,geo.h,geo.bpp,geo.size,geo.size*num_frame);
  if(grab_cap.channels>1)
    act("using input channel %s",grab_chan.name);

  return(true);
}

void V4lGrabber::set_chan(int ch) {

  grab_chan.channel = input = ch;

  if (-1 == ioctl(dev,VIDIOCGCHAN,&grab_chan))
    error("error in ioctl VIDIOCGCHAN ");

  grab_chan.norm = VIDEO_MODE_PAL;

  if (-1 == ioctl(dev,VIDIOCSCHAN,&grab_chan))
    error("error in ioctl VIDIOCSCHAN ");

  act("V4L: input chan %u %s",ch,grab_chan.name);
  show_osd();
}

void V4lGrabber::set_band(int b) {
  _band = b;
  chanlist = chanlists[b].list;
  if(_freq>chanlists[b].count) _freq = chanlists[b].count;
  act("V4L: frequency table %u %s [%u]",b,chanlists[b].name,chanlists[b].count);
  show_osd();
}

void V4lGrabber::set_freq(int f) {
  _freq = f;

  unsigned long frequency = chanlist[f].freq*16/1000;
  float ffreq = (float) frequency/16;

  func("V4L: set frequency %u %.3f",frequency,ffreq);

  //  lock_feed();
  if (-1 == ioctl(dev,VIDIOCSFREQ,&frequency))
    error("error in ioctl VIDIOCSFREQ ");
  //  unlock_feed();
  act("V4L: frequency %s %.3f Mhz (%s)",chanlist[f].name,ffreq,chanlists[_band].name);
  show_osd();
}


/* here are defined the keys for this layer */
bool V4lGrabber::keypress(SDL_keysym *keysym) {
  int res = 1;
  if(keysym->mod & KMOD_CAPS) return false;

  switch(keysym->sym) {

  case SDLK_k:
    if(input<channels)
      set_chan(input+1);
    break;

  case SDLK_m:
    if(input>0)
      set_chan(input-1);
    break;

    if(have_tuner) {
    case SDLK_j:
      if(_band<bandcount)
   set_band(_band+1);
      break;

    case SDLK_n:
      if(_band>0)
   set_band(_band-1);
      break;

    case SDLK_h:
      if(_freq<chanlists[_band].count)
   set_freq(_freq+1);
      else
   set_freq(0);
      break;

    case SDLK_b:
      if(_freq>0)
   set_freq(_freq-1);
      else
   set_freq(chanlists[_band].count);
      break;

    } /* if (have_tuner) */

  default:
    res = 0;
  }
  return res;
}

void *V4lGrabber::get_buffer() {
  return(rgb_surface);
}

void *V4lGrabber::feed() {

  ok_frame = cur_frame;

  cur_frame = (cur_frame>=num_frame) ? 0 : cur_frame++;
  // cur_frame = ((cur_frame+1)%num_frame); 10x luka@ljudmila

  grab_buf[ok_frame].format = palette;
  if (-1 == ioctl(dev,VIDIOCSYNC,&grab_buf[ok_frame])) {
    func("V4lGrabber::feed");
    error("error in ioctl VIDIOCSYNC on buffer %p",&grab_buf[ok_frame]);
  }

  grab_buf[cur_frame].format = palette;
  if (-1 == ioctl(dev,VIDIOCMCAPTURE,&grab_buf[cur_frame])) {
    func("V4lGrabber::feed");
    error("error in ioctl VIDIOCMCAPTURE on buffer %p",&grab_buf[cur_frame]);
  }

  (*yuv2rgb)((uint8_t *) rgb_surface,
        (uint8_t *) &buffer[grab_map.offsets[ok_frame]],
        (uint8_t *) &buffer[grab_map.offsets[ok_frame]+u],
        (uint8_t *) &buffer[grab_map.offsets[ok_frame]+v],
        geo.w, geo.h, geo.pitch, geo.w, geo.w);

  return rgb_surface;
}


Select source code:
back to 'READ ME' (main page)
'layer.cpp'(code that handles the printing on the screen)
'linklist.cpp'(code that keeps layers and effects inside dynamic arrays)
'layer.h'(class declaration for the layer)