


/* Simple OV511 video capture program, version 1.0.1
 *
 * Copyright 2000 Peter S. Housel.
 *
 * Portions of this program were modeled after or adapted from the
 * OV511 dirver for Linux by Mark W. McClelland; see
 * http://alpha.dyndns.org/ov511/ for more information.
 *
 * This program is free software; you can redistribute it and/or modify
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <sys/param.h>

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#include <string.h>

#include <sys/param.h>
#include <sys/types.h>
#include <sys/ioctl.h>

#include <dev/usb/usb.h>

#include <pnm.h>

#include "camio.h"

#define VERSION			"1.0.1 (2000-05-07)"

int
camIO::ov511_reg_read(int fd, int reg) {
  struct usb_ctl_request ur;
  unsigned char data[1024];
  
#if (__FreeBSD_version > 500000 && __FreeBSD_version < 500031) || (__FreeBSD_version < 450001)
  ur.request.bmRequestType = UT_READ_VENDOR_INTERFACE;
  ur.request.bRequest = 2;
  
  USETW(ur.request.wValue, 0);	/* unused */
  USETW(ur.request.wIndex, reg); /* index */
  USETW(ur.request.wLength, 1);	/* payload len in bytes */
  ur.data = data;
  ur.flags = 0;
  ur.actlen = 0;
#else
  ur.ucr_request.bmRequestType = UT_READ_VENDOR_INTERFACE;
  ur.ucr_request.bRequest = 2;
  
  USETW(ur.ucr_request.wValue, 0);	/* unused */
  USETW(ur.ucr_request.wIndex, reg); /* index */
  USETW(ur.ucr_request.wLength, 1);	/* payload len in bytes */
  ur.ucr_data = data;
  ur.ucr_flags = 0;
  ur.ucr_actlen = 0;
#endif
  
  if(ioctl(fd, USB_DO_REQUEST, &ur) < 0) {
    return -1;
  }

  return data[0] & 0xFF;
}

int
camIO::ov511_reg_write(int fd, int reg, int val) {
  struct usb_ctl_request ur;
  unsigned char data[1024];

  data[0] = val;
  
#if (__FreeBSD_version > 500000 && __FreeBSD_version < 500031) || (__FreeBSD_version < 450001)
  ur.request.bmRequestType = UT_WRITE_VENDOR_INTERFACE;
  ur.request.bRequest = 2;
  
  USETW(ur.request.wValue, 0);	/* unused */
  USETW(ur.request.wIndex, reg); /* index */
  USETW(ur.request.wLength, 1);	/* payload len in bytes */
  ur.data = data;
  ur.flags = 0;
  ur.actlen = 0;
#else
  ur.ucr_request.bmRequestType = UT_WRITE_VENDOR_INTERFACE;
  ur.ucr_request.bRequest = 2;
  
  USETW(ur.ucr_request.wValue, 0);	/* unused */
  USETW(ur.ucr_request.wIndex, reg); /* index */
  USETW(ur.ucr_request.wLength, 1);	/* payload len in bytes */
  ur.ucr_data = data;
  ur.ucr_flags = 0;
  ur.ucr_actlen = 0;
#endif
  
  if(ioctl(fd, USB_DO_REQUEST, &ur) < 0) {
    return -1;
  }

  return 0;
}

int
camIO::ov511_i2c_read(int fd, int reg) {
  int status = 0;
  int val = 0;
  int retries = OV7610_I2C_RETRIES;

  while(--retries >= 0) {
    /* wait until bus idle */
    do {
      if((status = ov511_reg_read(fd, OV511_REG_I2C_CONTROL)) < 0)
	return -1;
    } while((status & 0x01) == 0);

    /* perform a dummy write cycle to set the register */
    if(ov511_reg_write(fd, OV511_REG_SMA, reg) < 0)
      return -1;

    /* initiate the dummy write */
    if(ov511_reg_write(fd, OV511_REG_I2C_CONTROL, 0x03) < 0)
      return -1;

    /* wait until bus idle */
    do {
      if((status = ov511_reg_read(fd, OV511_REG_I2C_CONTROL)) < 0)
	return -1;
    } while((status & 0x01) == 0);

    if((status & 0x2) == 0)
      break;
  }

  if(retries < 0)
    return -1;

  retries = OV7610_I2C_RETRIES;
  while(--retries >= 0) {
    /* initiate read */
    if(ov511_reg_write(fd, OV511_REG_I2C_CONTROL, 0x05) < 0)
      return -1;

    /* wait until bus idle */
    do {
      if((status = ov511_reg_read(fd, OV511_REG_I2C_CONTROL)) < 0)
	return -1;
    } while ((status & 0x01) == 0);
    
    if((status & 0x2) == 0)
      break;

    /* abort I2C bus before retrying */
    if(ov511_reg_write(fd, OV511_REG_I2C_CONTROL, 0x10) < 0)
      return -1;
  }
  if(retries < 0)
    return -1;
  
  /* retrieve data */
  val = ov511_reg_read(fd, OV511_REG_SDA);

  /* issue another read for some weird reason */
  if(ov511_reg_write(fd, OV511_REG_I2C_CONTROL, 0x05) < 0)
    return -1;

  return val;
}

int
camIO::ov511_i2c_write(int fd, int reg, int val) {
  int status = 0;
  int retries = OV7610_I2C_RETRIES;

  while(--retries >= 0) {
    if(ov511_reg_write(fd, OV511_REG_SWA, reg) < 0)
      return -1;
    
    if(ov511_reg_write(fd, OV511_REG_SDA, val) < 0)
      return -1;
    
    if(ov511_reg_write(fd, OV511_REG_I2C_CONTROL, 0x1) < 0)
      return -1;

    /* wait until bus idle */
    do {
      if((status = ov511_reg_read(fd, OV511_REG_I2C_CONTROL)) < 0)
	return -1;
    } while((status & 0x01) == 0);

    /* OK if ACK */
    if((status & 0x02) == 0)
      return 0;
  }

  return -1;
}
/*
struct vidstate {
  enum {SKIPPING, READING, DONE} state;
  int width, height;
  xel **xels;

  int segsize;

  int iY, jY;
  int iUV, jUV;

  u_char buf[2048];		/
  int residue;
};
*/
//static void procdata(struct vidstate *vsp, u_char *data, size_t len);
//static void progsegment(struct vidstate *vsp, u_char *data);

//static void postproc(struct vidstate *vsp);

int
camIO::runMain(int argc, char *argv[]) {
  int fd = -1, isoc = -1;	/* control and isochronous input fds */
  struct usb_device_info udi;	/* device info */
  struct usb_alt_interface alt;	/* interface/alternate selection */
  int cid = -1;			/* OV511 camera ID */
  u_char buf[1024];		/* isochronous input read buffer */
  char dev[FILENAME_MAX];	/* for constructing device names */
  char isocdev[FILENAME_MAX];	/* for constructing endpoint 1 device names */
  char *devname = NULL;		/* device name */
  int len = -1;			/* isochronous input read length */
  struct vidstate vs;		/* current read state */
  int small = 0;		/* use 320x240 */
  int frmnm = 0;		/* cyclic frame number key */
  int isplus;			/* bridge is OV511+ if true, else OV511 */
  int is20;			/* sensor is OV7620 if true, else OV7610 */
  int bufsize;			/* size of packet buffer */

  /* pnm_init(&argc, argv); */	/* required for PNM programs? */

  while(++argv, --argc) {
    if(strcmp(*argv, "--version") == 0) {
      fprintf(stderr, "OV511/OV511+ capture program version " VERSION
	      "\nCopyright 2000 Peter S. Housel"
	      "\nThis program is free software; "
	      "you may redistribute it under the terms of"
	      "\nthe GNU General Public License.  "
	      "This program has absolutely no warranty.\n");
      exit(0);
    } else if(strcmp(*argv, "--usage") == 0) {
      fprintf(stderr, "usage: vid [--version] [--usage] [--help] [--small]\n"
	              "           [-d device] [--device-name=device]\n");
      exit(0);
    } else if(strcmp(*argv, "--help") == 0) {
      fprintf(stderr, "usage: vid [options]\n"
	      "Capture an image frame from an OV511/OV511+ based USB video camera\n"
	      "and write image data to standard output in PNM format\n\n"
	      "--version              print program version information\n"
	      "--usage                summarize command line options\n"
	      "--help                 print this description\n"
	      "--small                capture as 320x240 image (default is 640x480)\n"
	      "-d device, --device-name=device\n"
	      "                       specify OV511 ugen device\n");
      exit(0);
    } else if(strcmp(*argv, "--small") == 0) {
      small = 1;
    } else if(strncmp(*argv, "--device-name", strlen("--device-name")) == 0) {
      if((*argv)[strlen("--device-name")] == '=') {
	devname = *argv + strlen("--device-name") + 1;
      } else if((*argv)[strlen("--device-name")] == '\0' && argc > 0) {
	devname = *++argv;
	--argc;
      } else {
	fprintf(stderr, "usage: vid [--version] [--usage] [--help] [--small]\n"
		        "           [-d device] [--device-name=device]\n");
	exit(1);
      }
    } else if((*argv)[0] == '-' && (*argv)[1] == 'd') {
      if((*argv)[2] != '\0') {
	devname = *argv + 2;
      } else if(argc > 0) {
	devname = *++argv;
	--argc;
      } else {
	fprintf(stderr, "usage: vid [--version] [--usage] [--help] [--small]\n"
		        "           [-d device] [--device-name=device]\n");
	exit(1);
      }
    } else {
      fprintf(stderr, "usage: vid [--version] [--usage] [--help] [--small]\n"
	              "           [-d device] [--device-name=device]\n");
      exit(1);
    }
  }

  if(devname) {
    if((fd = open(devname, O_RDWR)) < 0) {
      perror(devname);
      exit(1);
    }

    /* make sure it's an OV511 */
    if(ioctl(fd, USB_GET_DEVICEINFO, &udi) < 0) {
      perror("USB_GET_DEVICEINFO");
      exit(1);
    }

#if (__FreeBSD_version > 500000 && __FreeBSD_version < 500031) || (__FreeBSD_version < 450001)
    if(udi.vendorNo != 0x05A9 || (udi.productNo != 0x0511 &&
				  udi.productNo != 0xa511)) {
#else
    if(udi.udi_vendorNo != 0x05A9 || (udi.udi_productNo != 0x0511 &&
				  udi.udi_productNo != 0xa511)) {
#endif
      fprintf(stderr, "device %s is not an OmniVision OV511 or OV511+\n", devname);
      exit(1);
    }
  } else {
    int i = 0;
    for(i = 0; i < 15; ++i) {
      sprintf(dev, "/dev/ugen%d", i);
      if((fd = open(dev, O_RDWR)) < 0)
	continue;
#if (__FreeBSD_version > 500000 && __FreeBSD_version < 500031) || (__FreeBSD_version < 450001)
      if(ioctl(fd, USB_GET_DEVICEINFO, &udi) < 0
	 || udi.vendorNo != 0x05A9 || (udi.productNo != 0x0511 &&
				       udi.productNo != 0xa511)) {
#else
      if(ioctl(fd, USB_GET_DEVICEINFO, &udi) < 0
	 || udi.udi_vendorNo != 0x05A9 || (udi.udi_productNo != 0x0511 &&
				       udi.udi_productNo != 0xa511)) {
#endif
	close(fd);
	fd = -1;
	continue;
      } else {
	break;
      }
    }

    if(fd < 0) {
      fprintf(stderr, "vid: couldn't locate an OV511 or OV511+ device\n");
      exit(1);
    }

    devname = dev;
  }

#if (__FreeBSD_version > 500000 && __FreeBSD_version < 500031) || (__FreeBSD_version < 450001)
  isplus = udi.productNo == 0xa511;
#else
  isplus = udi.udi_productNo == 0xa511;
#endif
  bufsize = (isplus ? 961 : 993);
  
  /* reset the OV511 */
  if(ov511_reg_write(fd, OV511_REG_RST, 0x7f) < 0)
    exit(1);

  if(ov511_reg_write(fd, OV511_REG_RST, 0) < 0)
    exit(1);

  /* initialize system */
  if(ov511_reg_write(fd, OV511_REG_EN_SYS, 0x1) < 0)
    exit(1);

  /* determine the camera model */
  if((cid = ov511_reg_read(fd, OV511_REG_CID)) < 0)
    exit(1);

  switch(cid) {
  case 0: /* This also means that no custom ID was set */
    /* fprintf(stderr, "vid: MediaForte MV300\n"); */
    break;
  case 3:
    /* fprintf(stderr, "vid: D-Link DSB-C300\n"); */
    break;
  case 4:
    /* fprintf(stderr, "vid: Generic OV511/OV7610\n"); */
    break;
  case 5:
    /* fprintf(stderr, "vid: Puretek PT-6007\n"); */
    break;
  case 21:
    /* fprintf(stderr, "vid: Creative Labs WebCam 3\n"); */
    break;
  case 36:
    /* fprintf(stderr, "vid: Koala-Cam\n"); */
    break;
  case 100:
    /* fprintf(stderr, "vid: Lifeview RoboCam\n"); */
    break;
  case 102:
    /* fprintf(stderr, "vid: AverMedia InterCam Elite\n"); */
    break;
  case 112: /* The OmniVision OV7110 evaluation kit uses this too */
    /* fprintf(stderr, "vid: MediaForte MV300\n"); */
    break;
  default:
    fprintf(stderr, "vid: Camera custom ID %d not recognized\n", cid);
    exit(1);
  }
  
  /* set I2C write slave ID for OV7601 */
  if(ov511_reg_write(fd, OV511_REG_SID, OV7610_I2C_WRITE_ID) < 0)
    exit(1);

  /* set I2C read slave ID for OV7601 */
  if(ov511_reg_write(fd, OV511_REG_SRA, OV7610_I2C_READ_ID) < 0)
    exit(1);

  if(ov511_reg_write(fd, OV511_REG_PKSZ, 0x1) < 0)
    exit(1);
  if(ov511_reg_write(fd, OV511_REG_PKFMT, 0x0) < 0)
    exit(1);

  if(ov511_reg_write(fd, OV511_REG_RST, 0x3d) < 0)
    exit(1);

  if(ov511_reg_write(fd, OV511_REG_RST, 0x0) < 0)
    exit(1);

  if(ov511_i2c_read(fd, 0x00) < 0)
    exit(1);

  /* set YUV 4:2:0 format, Y channel LPF */
  if(ov511_reg_write(fd, OV511_REG_M400, 0x01) < 0)
    exit(1);
  if(ov511_reg_write(fd, OV511_REG_M420_YFIR, 0x03) < 0)
    exit(1);

  /* disable snapshot */
  if(ov511_reg_write(fd, OV511_REG_SNAP, 0x0) < 0)
    exit(1);
  /* disable compression */
  if(ov511_reg_write(fd, OV511_REG_CE_EN, 0x0) < 0)
    exit(1);

  /* This returns 0 if we have an OV7620 sensor */
  if((is20 = ov511_i2c_read(fd, OV7610_REG_COMI)) < 0)
    exit(1);
  is20 = !is20;

  /* set up the OV7610/OV7620 */
  if(is20) {
    ov511_i2c_write(fd, OV7610_REG_EC,      0xff);
    ov511_i2c_write(fd, OV7610_REG_FD,      0x06);
    ov511_i2c_write(fd, OV7610_REG_COMH,    0x24);
    ov511_i2c_write(fd, OV7610_REG_EHSL,    0xac);
    ov511_i2c_write(fd, OV7610_REG_COMA,    0x00);
    ov511_i2c_write(fd, OV7610_REG_COMH,    0x24);
    ov511_i2c_write(fd, OV7610_REG_RWB,     0x85);
    ov511_i2c_write(fd, OV7610_REG_COMD,    0x01);
    ov511_i2c_write(fd, 0x23,               0x00);
    ov511_i2c_write(fd, OV7610_REG_ECW,     0x10);
    ov511_i2c_write(fd, OV7610_REG_ECB,     0x8a);
    ov511_i2c_write(fd, OV7610_REG_COMG,    0xe2);
    ov511_i2c_write(fd, OV7610_REG_EHSH,    0x00);
    ov511_i2c_write(fd, OV7610_REG_EXBK,    0xfe);
    ov511_i2c_write(fd, 0x30,               0x71);
    ov511_i2c_write(fd, 0x31,               0x60);
    ov511_i2c_write(fd, 0x32,               0x26);
    ov511_i2c_write(fd, OV7610_REG_YGAM,    0x20);
    ov511_i2c_write(fd, OV7610_REG_BADJ,    0x48);
    ov511_i2c_write(fd, OV7610_REG_COMA,    0x24);
    ov511_i2c_write(fd, OV7610_REG_SYN_CLK, 0x01);
    ov511_i2c_write(fd, OV7610_REG_BBS,     0x24);
    ov511_i2c_write(fd, OV7610_REG_RBS,     0x24);
  } else {
    ov511_i2c_write(fd, OV7610_REG_RWB, 0x5);
    ov511_i2c_write(fd, OV7610_REG_EC, 0xFF);
    ov511_i2c_write(fd, OV7610_REG_COMB, 0x01);
    ov511_i2c_write(fd, OV7610_REG_FD, 0x06);
    ov511_i2c_write(fd, OV7610_REG_COME, 0x1c);
    ov511_i2c_write(fd, OV7610_REG_COMF, 0x90);
    ov511_i2c_write(fd, OV7610_REG_ECW, 0x2e);
    ov511_i2c_write(fd, OV7610_REG_ECB, 0x7C);
    ov511_i2c_write(fd, OV7610_REG_COMH, 0x24);
    ov511_i2c_write(fd, OV7610_REG_EHSH, 0x04);
    ov511_i2c_write(fd, OV7610_REG_EHSL, 0xAC);
    ov511_i2c_write(fd, OV7610_REG_EXBK, 0xFE);
    ov511_i2c_write(fd, OV7610_REG_COMJ, 0x93);
    ov511_i2c_write(fd, OV7610_REG_BADJ, 0x48);
    ov511_i2c_write(fd, OV7610_REG_COMK, 0x81);

    ov511_i2c_write(fd, OV7610_REG_GAM, 0x04);
  }

  if(small) {
    vs.width = 320;
    vs.height = 240;
    ov511_reg_write(fd, OV511_REG_PXCNT, 0x27);
    ov511_reg_write(fd, OV511_REG_LNCNT, 0x1D);
    ov511_i2c_write(fd, OV7610_REG_SYN_CLK, 0x01);
    ov511_i2c_write(fd, OV7610_REG_COMA, 0x04);
    ov511_i2c_write(fd, OV7610_REG_COMC, 0x24);
    ov511_i2c_write(fd, OV7610_REG_COML, 0x9e);
  } else {
    vs.width = 640;
    vs.height = 480;
    ov511_reg_write(fd, OV511_REG_PXCNT, 0x4F);
    ov511_reg_write(fd, OV511_REG_LNCNT, 0x3D);
    ov511_i2c_write(fd, OV7610_REG_SYN_CLK, 0x06);
    ov511_i2c_write(fd, OV7610_REG_HE, 0x3a + (640>>2));
    ov511_i2c_write(fd, OV7610_REG_VE, 5 + (480>>1));
    ov511_i2c_write(fd, OV7610_REG_COMA, 0x24);
    ov511_i2c_write(fd, OV7610_REG_COMC, 0x04);
    ov511_i2c_write(fd, OV7610_REG_COML, 0x1e);
  }

  ov511_reg_write(fd, OV511_REG_PXDV, 0x00);
  ov511_reg_write(fd, OV511_REG_LNDV, 0x00);

  /* set FIFO format (993-byte packets) */
  if(ov511_reg_write(fd, OV511_REG_PKSZ, bufsize/32) < 0)
    exit(1);
  if(ov511_reg_write(fd, OV511_REG_PKFMT, 0x03) < 0)
    exit(1);

  /* select the 993-byte alternative */
#if (__FreeBSD_version > 500000 && __FreeBSD_version < 500031) || (__FreeBSD_version < 450001)
  alt.interface_index = 0;
  alt.alt_no = (isplus ? 7 : 1);
#else
  alt.uai_interface_index = 0;
  alt.uai_alt_no = (isplus ? 7 : 1);
#endif
  if(ioctl(fd, USB_SET_ALTINTERFACE, &alt) < 0) {
    perror("USB_SET_ALTINTERFACE");
    exit(1);
  }

  /* reset the device again */

  if(ov511_reg_write(fd, OV511_REG_RST, 0x3F) < 0)
    exit(1);

  if(ov511_reg_write(fd, OV511_REG_RST, 0x00) < 0)
    exit(1);

  vs.state = vidstate::SKIPPING;
  vs.segsize = 384;
  vs.xels = pnm_allocarray(vs.width, vs.height);

  /* open the isochronous endpoint (endpoint 1) */
  sprintf(isocdev, "%s.1", devname);
  if((isoc = open(isocdev, O_RDONLY)) < 0) {
    perror(isocdev);
    exit(1);
  }

  /* read, looking for start and end frames */
  while(vs.state != vidstate::DONE && (len = read(isoc, &buf, bufsize)) >= 0) {
    if(buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] == 0
       && buf[4] == 0 && buf[5] == 0 && buf[6] == 0 && buf[7] == 0
       && (buf[8] & 0x80) == 0 && buf[bufsize-1] == 0 && vs.state == vidstate::SKIPPING) {
	vs.state = vidstate::READING;
	vs.iY = vs.jY = vs.iUV = vs.jUV = 0;
	vs.residue = 0;
	procdata(&vs, buf + 9, bufsize - 10);
    } else if(buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] == 0
	      && buf[4] == 0 && buf[5] == 0 && buf[6] == 0 && buf[7] == 0
	      && (buf[8] & 0x80) == 0x80 && buf[bufsize-1] == 0
	      && vs.state == vidstate::READING) {
      vs.state = vidstate::DONE;
    } else if(vs.state == vidstate::READING) {
      procdata(&vs, buf, bufsize - 1);

      /* abort the capture and start over if packets come in out-of-order */
      if(buf[bufsize-1] != frmnm && buf[bufsize-1] != 1) {
	vs.state = vidstate::SKIPPING;
      }
      frmnm = buf[bufsize-1] + 1;
      if(frmnm == 256)
	frmnm = 1;
    } else if(buf[bufsize-1] != 0) {
      frmnm = buf[bufsize-1] + 1;
      if(frmnm == 256)
	frmnm = 1;
    }
  }

  /* reset and close the OV511 */
  ov511_reg_write(fd, OV511_REG_RST, 0x7f);
  close(isoc);
  close(fd);

  /* convert from CIE YCbCr color to RGB */
  postproc(&vs);

  /* write out as PPM */
  pnm_writepnm(stdout, vs.xels, vs.width, vs.height, 255, PPM_TYPE, 0);

  exit(0);
  }


void
camIO::procdata(struct vidstate *vsp, u_char *data, size_t datalen) {
  size_t len = vsp->residue + datalen;
  int offset = 0;

  memcpy(vsp->buf + vsp->residue, data, datalen);

  while(len - offset >= vsp->segsize) {
    if(vsp->jY >= vsp->height && vsp->jUV >= vsp->height) {
      vsp->state = vidstate::DONE;
      return;
    } else if(vsp->jY >= vsp->height || vsp->jUV >= vsp->height) {
      vsp->state = vidstate::SKIPPING;
      return;
    }

    progsegment(vsp, vsp->buf + offset);

    vsp->iY += 32;		/* each segment contains 32x8 Y pixels */
    if(vsp->iY >= vsp->width) {
      vsp->iY = 0;
      vsp->jY += 8;
    }

    vsp->iUV += 16;		/* and 16x16 downsampled U and V pixels */
    if(vsp->iUV >= vsp->width) {
      vsp->iUV = 0;
      vsp->jUV += 16;
    }

    offset += vsp->segsize;
  }
  vsp->residue = len - offset;
  memmove(vsp->buf, vsp->buf + offset, len - offset);
}

/* The first 64 bytes of each segment are U, the next 64 are V.  The U
   and V are arranged as follows:

  0  1 ...  7
  8  9 ... 15
       ...   
 56 57 ... 63

 U and V are shipped at half resolution (1 U,V sample -> one 2x2 block).

 The next 256 bytes are full resolution Y data and represent 4 
 squares of 8x8 pixels as follows:

  0  1 ...  7    64  65 ...  71   ...  192 193 ... 199
  8  9 ... 15    72  73 ...  79        200 201 ... 207
       ...              ...                    ...
 56 57 ... 63   120 121     127        248 249 ... 255

 Note that the U and V data in one segment represents a 16 x 16 pixel
 area, but the Y data represents a 32 x 8 pixel area.
*/

void camIO::progsegment(struct vidstate *vsp, u_char *data) {
  int i, j, k;
  u_char *Up = data, *Vp = data + 64, *Yp = data + 128;
  xel *xp;

  for(j = 0; j < 8; ++j) {
    xp = vsp->xels[vsp->jUV + j * 2] + vsp->iUV;

    for(i = 0; i < 8; ++i) {
      PPM_ASSIGN(*xp, *Up, PPM_GETG(*xp), *Vp);
      Up++;
      Vp++;
      xp += 2;
    }
  }

  for(j = 0; j < 8; ++j) {
    xp = vsp->xels[vsp->jY + j] + vsp->iY;
    
    for(i = 0; i < 8; ++i) {
      PPM_ASSIGN(xp[0], PPM_GETR(xp[0]), Yp[0], PPM_GETB(xp[0]));
      PPM_ASSIGN(xp[8], PPM_GETR(xp[8]), Yp[64], PPM_GETB(xp[8]));
      PPM_ASSIGN(xp[16], PPM_GETR(xp[16]), Yp[128], PPM_GETB(xp[16]));
      PPM_ASSIGN(xp[24], PPM_GETR(xp[24]), Yp[192], PPM_GETB(xp[24]));
      Yp++;
      xp++;
    }
  }
}

#define LIMIT(x) ((x)>0xffffff?0xff: ((x)<=0xffff?0:((x)>>16)))

void
camIO::postproc(struct vidstate *vsp) {
  int i, j;
  xel *xp, *xq;

  const double brightness = 1.0; /* 0->black; 1->full scale */
  const double saturation = 1.0; /* 0->greyscale; 1->full color */
  const double fixScale = brightness * 256 * 256;
  const int rvScale = (int)(1.402 * saturation * fixScale);
  const int guScale = (int)(-0.344136 * saturation * fixScale);
  const int gvScale = (int)(-0.714136 * saturation * fixScale);
  const int buScale = (int)(1.772 * saturation * fixScale);
  const int yScale = (int)(fixScale);	

  for(j = 0; j < vsp->height; j += 2) {
    xp = vsp->xels[j];
    xq = vsp->xels[j+1];
    for(i = 0; i < vsp->width; i += 2) {

      int u = PPM_GETR(xp[0]) - 128;
      int v = PPM_GETB(xp[0]) - 128;

      int yTL = PPM_GETG(xp[0]) * yScale;
      int yTR = PPM_GETG(xp[1]) * yScale;
      int yBL = PPM_GETG(xq[0]) * yScale;
      int yBR = PPM_GETG(xq[1]) * yScale;

      int r =               rvScale * v;
      int g = guScale * u + gvScale * v;
      int b = buScale * u;

      PPM_ASSIGN(xp[0], LIMIT(r+yTL), LIMIT(g+yTL), LIMIT(b+yTL));
      PPM_ASSIGN(xp[1], LIMIT(r+yTR), LIMIT(g+yTR), LIMIT(b+yTR));
      PPM_ASSIGN(xq[0], LIMIT(r+yBL), LIMIT(g+yBL), LIMIT(b+yBL));
      PPM_ASSIGN(xq[1], LIMIT(r+yBR), LIMIT(g+yBR), LIMIT(b+yBR));

      xp += 2;
      xq += 2;
    }
  }

  if(vsp->width > 400) {
    for(j = 0; j < vsp->height - 1; ++j) {
      xp = vsp->xels[j];
      xq = vsp->xels[j+1];
      for(i = 0; i < vsp->width; ++i) {
	PPM_PUTR(*xp, PPM_GETR(*xq));
	++xp;
	++xq;
      }
    }
    for(j = vsp->height - 1; j >= 1; --j) {
      xp = vsp->xels[j-1];
      xq = vsp->xels[j];
      for(i = 0; i < vsp->width; ++i) {
	PPM_PUTB(*xq, PPM_GETB(*xp));
	++xp;
	++xq;
      }
    }
  }
}

int main()
{
return 0;
}


