/*------------------------------------------------------------------------
 *
 *  File:	o1k_video.cc
 *  Revision:	1.28
 *  Last Mod:	15:46:42, 02/11/98
 *
 *  Description:
 *     Implements the O1kAudio object.
 *     
 *     
 *----------------------------------------------------------------------*/
#pragma ident	"@(#)o1k_video.cc	1.28\t98/02/11  "

#include <stdio.h>
#include <errno.h>
#include <thread.h>
#include <synch.h>
#include <string.h>
#include <math.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/file.h>
#include <sys/filio.h>
#include <sys/stat.h>
#include <sys/signal.h>
#include <sys/ioctl.h>
#include <stropts.h>
#include <xil/xil.h>

#include "o1k_conf.h"
#include "o1k_misc.h"
#include "window_state.h"
#include "cellb_util.h"
#include "packet.h"
#include "blank261.h"

#include "o1k_timer.h"

extern void* tVidThrd(void* vp);
extern void* rVidThrd(void* vp);
extern char dev_name[];

/*
 * We keep this out of the class so that user's don't
 * need to know that video class is implemented on top
 * of XIL
 */
XilSystemState xil_sys_state;  
Boolean bAudioInitWait;
XilImage o1k_image;

/*
 * Useful strings
 */
char* VideoTypeStr[] = { "H261",  "CELLB", "JPEG", "VID_NONE" };
char* VideoPortStr[] = { "SVIDEO", "COMP1"  };
char* VideoSizeStr[] = { "CIF",   "QCIF" };

/*------------------------------------------------------------------------
 *
 *  Function:	O1kVideo::O1kVideo()
 *
 *  Description:
 *     Init O1kVideo to default state.
 *     
 *  Notes:
 *     Failure of create is not checked.
 *     
 *----------------------------------------------------------------------*/

O1kVideo::O1kVideo()
{

  /*
   * The current XIL video drivers require that it be the
   * first thing to initialize the Osprey-1x00 device. the 
   * bAudioInitWait flag is used to tell the audio oject
   * that it is ok to start.
   */
  bAudioInitWait = TRUE;
  
  /* Init video info */
  type       = H261;
  port       = COMP1;
  output     = VID_WINDOW;
  brightness = -1;
  contrast   = -1;
  hue        = -1;
  gainu      = -1;
  gainv      = -1;  
  
  h261.framerate     = 10;
  h261.bitrate       = 256;
  h261.maxquant      = 10;
  h261.refreshperiod = 3000;
  h261.size          = SIZE_QCIF;
  h261.pip_on        = TRUE;

  cellb.framerate    = 10;
  cellb.size         = SIZE_QCIF;

  jpeg.framerate     = 10;
  jpeg.quality       = 50;
  jpeg.size          = SIZE_QCIF;

  view_local = TRUE;
  view_far   = TRUE;

  bitrate_t   = 0;
  bitrate_r   = 0;  
  framerate_t = 0;
  framerate_r = 0;
  
  if (createXilLock()<0){
    fprintf(stderr, "O1kVideo(): createXilLock failed\n");
  }

  thread_t = new O1kThread("Video Transmit");
  if (!thread_t)
    fprintf(stderr,"O1kVideo() could not allocate thread\n");

  thread_r = new O1kThread("Video Receive");
  if (!thread_r)
    fprintf(stderr,"O1kVideo() could not allocate thread\n");
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kVideo::~O1kVideo()
 *
 *  Description:
 *     Delete allocated O1kAudio data
 *     
 *----------------------------------------------------------------------*/

O1kVideo::~O1kVideo()
{
  if (thread_t)
    delete thread_t;
  if (thread_r)
    delete thread_r;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kVideo::settings()
 *
 *  Description:
 *	
 *     Debug print out of O1kAudio settings.
 *	
 *----------------------------------------------------------------------*/

void O1kVideo::settings()
{
  printf("Video Codec Settings\n");
  printf("--------------------\n");
  if (type == VID_NONE)
    printf("  non-video conference\n");
  else {
    printf("  video codec   %s\n",VideoTypeStr[type]);
    if (type==H261){
      printf("  framerate     %d\n",h261.framerate);
      printf("  bitrate       %d\n",h261.bitrate);
      printf("  maxquant      %d\n",h261.maxquant);
      printf("  refreshperiod %d\n",h261.refreshperiod);
      printf("  size          %d\n",VideoSizeStr[h261.size]);
      printf("  pip_on        %d\n",h261.pip_on);
    } else if (type==CELLB){
      printf("  framerate %d\n",cellb.framerate);
      printf("  size      %d\n",VideoSizeStr[cellb.size]);
    } else if (type==JPEG){
      printf("  framerate %d\n",jpeg.framerate);
      printf("  quality   %d\n",jpeg.quality);
      printf("  size      %d\n",VideoSizeStr[jpeg.size]);
    }
  }

  if (type != VID_NONE){
    printf("General Video Settings\n");
    printf("----------------------\n");
    printf("  video port  %s\n",VideoPortStr[port]);
    printf("  brigtness   %d\n",brightness);
    printf("  contrast    %d\n",contrast);
    printf("  hue         %d\n",hue);
    printf("  gainu       %d\n",gainu);
    printf("  gainv       %d\n",gainv);
  }
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kVideo::setFormat()
 *
 *  Description:
 *     Sets the desired video format. If video format is
 *     VID_NONE then audio does not need to wait on video
 *     to start up.
 *     
 *  Parameters:
 *     VideoType t : video format
 *     
 *----------------------------------------------------------------------*/

void O1kVideo::setFormat(VideoType t)
{
  type = t;
  if (type == VID_NONE)
    bAudioInitWait = FALSE;
  else
    bAudioInitWait = TRUE;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kVideo::createXilLock()
 *
 *  Description:
 *     create XIL mutex
 *	
 *----------------------------------------------------------------------*/

int O1kVideo::createXilLock(void)
{
  int rval;
    
  if ((rval = mutex_init(&xil_mutex, USYNC_THREAD, NULL)) != 0){
    fprintf(stderr, "CreateXilLocks failed: 0x%lX\n", rval);
    return -1;
  }
  return 0;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kVideo::lockXil()
 *
 *  Description:
 *     lock XIL mutex
 *	
 *----------------------------------------------------------------------*/

/* see readme */
int lock_return = 1;

extern int use_xil_locks;

int O1kVideo::lockXil()
{
  int rval;

  if (use_xil_locks){
    if ((rval = mutex_lock(&xil_mutex)) != 0){
      fprintf(stderr, "Error In LockXil: 0x%08lX\n", rval);
      return -2;
    }
    
    /* see readme */
    while (!lock_return);
    lock_return = 0;
  }

  return 0;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kVideo::releaseXil()
 *
 *  Description:
 *     lock XIL mutex
 *	
 *----------------------------------------------------------------------*/

int O1kVideo::releaseXil()
{
  int rval;

  if (use_xil_locks){
    if ((rval = mutex_unlock(&xil_mutex)) != 0){
      fprintf(stderr, "Error In ReleaseXil: 0x%08lX\n", rval);
      return -2;
    }
    
    /* see readme */
    lock_return = 1;
  }

  return 0;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kVideo::getFrameRate()
 *
 *  Description:
 *     Return video framerate
 *	
 *----------------------------------------------------------------------*/

int O1kVideo::getFrameRate()
{
  if (type == H261)
    return h261.framerate;
  if (type == CELLB)
    return cellb.framerate;
  if (type == JPEG)
    return jpeg.framerate;
  fprintf(stderr,"o1k_conf_get_video_framerate: no video format\n");
  return 30;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kVideo::getSize()
 *
 *  Description:
 *     Return video size
 *	
 *----------------------------------------------------------------------*/

VideoSize O1kVideo::getSize()
{
  if (type == H261)
    return h261.size;
  if (type == CELLB)
    return cellb.size;
  if (type == JPEG)
    return jpeg.size;
  fprintf(stderr,"o1k_conf_get_video_size: no video format\n");
  return SIZE_QCIF;
}

/*-------------------------------------------------------------------------
 *
 * Function: O1kVideo::start()
 *
 * Description:
 *      Start video device and threads
 *
 *-----------------------------------------------------------------------*/

int O1kVideo::start()
{
  /* create xil system state */
  xil_sys_state = xil_open();
  if (xil_sys_state == NULL) {
    fprintf(stderr, "unable to open xil library\n");
    return 0; 
  }

  /*
   * Need to start transmitter first: the MMACo1k io device
   * needs to be created before a decompress cis is created.
   */
  thread_t->start(tVidThrd, (void*)this);  
  thread_r->start(rVidThrd, (void*)this);

  return 0;
}

/*-------------------------------------------------------------------------
 *
 * Function: O1kVideo::stop()
 *
 * Description:
 *     Stop video device and threads
 *
 *-----------------------------------------------------------------------*/
int O1kVideo::stop()
{
  if (verbose) printf("\n---------------- O1kVideo::stop() ----------------\n");

  /* stop threads */
  thread_t->stop();  
  thread_r->stop();
 
  xil_destroy(o1k_image); 
  xil_close(xil_sys_state);

  return 0;
}

/*------------------------------------------------------------------------
 *
 *  Function:	O1kVideo::getStatus
 *
 *  Description:
 *     Returns a string represent the interger value of the
 *     specified request status type.
 *	
 *  Parameters:
 *     VideoStatus ns  : desired status info requested
 *	
 *  Returns:
 *     char* : pointer to status info
 *	
 *  Notes:
 *     The caller should copy the returned status info for it 
 *     is only valid until the next call to this routine.
 *	
 *----------------------------------------------------------------------*/

char* O1kVideo::getStatus(VideoStatus vs)
{
  static char status[10];

  switch (vs)
  {
    case VS_BITRATE_T:
    {
      sprintf(status,"%d",bitrate_t);
      break;
    }
    case VS_BITRATE_R:
    {
      sprintf(status,"%d",bitrate_r);
      break;
    }
    case VS_FRAMERATE_T:
    {
      sprintf(status,"%d",framerate_t);
      break;
    }
    case VS_FRAMERATE_R:
    {
      sprintf(status,"%d",framerate_r);      
      break;
    }
    default:
    {
      fprintf(stderr,"Unknown video status query %d\n",vs);
      strcpy(status,"");
    }
  }
  return status;
}

/*------------------------------------------------------------------------
 *
 *  Function:	o1k_video_dynamic_update
 *
 *  Description:
 *    Checks for changes in video parameters while conf is in
 *    progress and makes XIL calls to change these settings.
 *     
 *     
 *  Parameters:
 *     int init        : init flag
 *     O1kVideo* video : main O1kVideo object
 *     XilCis cis      : cis being used for compress
 *     
 *----------------------------------------------------------------------*/

void  o1k_video_dynamic_update(int init, O1kVideo* video, XilCis cis)
{
  /* video only */
  static VideoPort port;
  static int brightness;
  static int contrast;
  static int hue;
  static int gainu;
  static int gainv;
  
  /* cellb, h261, jpeg */
  static unsigned int  framerate;
  
  /* jpeg only */
  static unsigned int  quality;

  /* h261 only */
  static unsigned int  bitrate;
  static unsigned int  maxquant;
  static unsigned int  refreshperiod;
  static unsigned char pip_on;

  if (init){
    port       = video->getInputPort();
    brightness = video->getBrightness();
    contrast   = video->getContrast();
    hue        = video->getHue();
    gainu      = video->getGainU();
    gainv      = video->getGainV();

    framerate = video->getFrameRate();
  
    quality   = video->getJpegQuality();

    bitrate   = video->getH261BitRate();
    maxquant  = video->getH261MaxQuant();
    refreshperiod = video->getH261RefreshPeriod();
    pip_on    = video->getH261Pip();

    return;
  }

  video->lockXil();
  if (port != video->getInputPort()){
    port = video->getInputPort();
    xil_set_device_attribute(o1k_image, "PORT_V", (void *) port);
  }
  
  if (brightness != video->getBrightness()){
    brightness = video->getBrightness();
    xil_set_device_attribute(o1k_image, "BRIGHTNESS", (void *) brightness);
  }
  
  if (contrast != video->getContrast()){
    contrast = video->getContrast();
    xil_set_device_attribute(o1k_image, "CONTRAST", (void *) contrast);
  }
  
  if (hue != video->getHue()){
    hue = video->getHue();
    xil_set_device_attribute(o1k_image, "HUE", (void *) hue);
  }
  
  if (gainu != video->getGainU()){
    gainu = video->getGainU();
    xil_set_device_attribute(o1k_image, "CHROMA_GAIN_U", (void *) gainu);
  }
  
  if (gainv != video->getGainV()){
    gainv = video->getGainV();
    xil_set_device_attribute(o1k_image, "CHROMA_GAIN_V", (void *) gainv);
  }

  if (framerate != video->getFrameRate()){
    int image_skip;
    framerate = video->getFrameRate();
    if (framerate)
      image_skip = (30/framerate) - 1;
    else
      image_skip = 30;
    xil_set_device_attribute(o1k_image, "IMAGE_SKIP", (void *) image_skip);
  }

  if (quality != video->getJpegQuality()){
    quality = video->getJpegQuality();
    xil_cis_set_attribute(cis, "COMPRESSION_QUALITY", (void *) quality);    
  }

  if (bitrate != video->getH261BitRate()){
    bitrate = video->getH261BitRate();
    xil_set_device_attribute(o1k_image, "H261_BIT_RATE", (void *) bitrate);
  }

  if (maxquant != video->getH261MaxQuant()){
    maxquant = video->getH261MaxQuant();
    xil_set_device_attribute(o1k_image, "H261_MAX_QUANT", (void *) maxquant);
  }

  if (refreshperiod != video->getH261RefreshPeriod()){
    refreshperiod = video->getH261RefreshPeriod();
    xil_set_device_attribute(o1k_image, "H261_IBLOCK_REFRESH", (void *) refreshperiod);
  }

  if (pip_on != video->getH261Pip()){
    pip_on = video->getH261Pip();
    xil_set_device_attribute(o1k_image, "H261_PIP", (void *) pip_on);
  }
  
  video->releaseXil();

}

/*------------------------------------------------------------------------
 *
 *  Function:	tVidThrd
 *
 *  Description:
 *      This is the worker thread that handles video transmit. 
 *      
 *  Parameters:
 *	void* vp : should be the single O1kVideo* object
 *	
 *  Returns: 0
 *  
 *------------------------------------------------------------------------*/

void* tVidThrd(void* vp)
{
  O1kVideo* video   = (O1kVideo*) vp;
  O1kThread* thread = video->thread_t;
  
  int scalex = 1, scaley = 1;
  XilImage image;
  XilDataType datatype;
  XilImage scaled_image;
  XilCis cis;
  XilCis cis2;    
  Xil_unsigned8 *data;
  
  WindowState *wstate;
  
  unsigned int width, height, nbands;
  double tval;
  struct timeval t0, t1;
  int nframes = 0;
  int nbytes;
  int multiple_frames = 0;
  const char *video_format;
  int bytes_for_first_frame;
  int first_frame = 1;
  int format;
  XilDevice device;
  int image_skip;
  int tmp;
  int rc;
  int win_pre_state;
  int win_cur_state;  

  int s_bytes = 0;
  int s_frames = 0;

  O1kTimer* timer0 = new O1kTimer("vtthrd",10);
  double    compress_wait;

  if (register_trap)
    asm("ta 6");

  video->lockXil();
  
  if (!(device = xil_device_create(xil_sys_state, "MMACo1k"))) {
    fprintf(stderr, "Unable to create a device object\n");
    return 0; 
  }
  
  xil_device_set_value(device, "DEVICE_NAME", (void *) dev_name);

  /*
   * Auto detect of video format is performed during xil_create_from_device.
   * If AUTO is seleted for the video format in the control panel, it is
   * best to set the port here prior to calling xil_create_from_device().
   */
  xil_device_set_value(device, "PORT_V", (void *) video->getInputPort());

  /*
   * The "video out" attribute also needs to be set prior to calling
   * xil_create_from_device(). 
   */
  
  if (video->getFormat() == H261)
    xil_device_set_value(device, "VIDEO_OUT", (void *) video->getOutput());
  else
    xil_device_set_value(device, "VIDEO_OUT", (void *) 0);
    
  if (!(image = xil_create_from_device(xil_sys_state, "MMACo1k", device))) {
    fprintf(stderr, "ERROR: failed to open MMACo1k device\n");
    return 0;
  }
  xil_device_destroy(device);
  
  o1k_image = image;
    
  xil_get_device_attribute(o1k_image, "FORMAT_V", (void **) &format);
  if (format == 0) {
    fprintf(stderr, "Unknown video format, exiting.\n");
    return 0;
  }
  

  tmp = video->getFrameRate();
  if (tmp)
    image_skip = (30/tmp) - 1;
  else
    image_skip = 30;
  compress_wait = 1.0/(tmp + 2);
  
  if (verbose) printf("image_skip = %d compress_wait = %g\n",image_skip, compress_wait);
  
  xil_set_device_attribute(image, "IMAGE_SKIP", (void *) image_skip);
  xil_set_device_attribute(image, "MAX_BUFFERS", (void *) 2);
  xil_get_device_attribute(image, "FORMAT", (void **) &video_format);

  /*
   * The HW_DECOMPRESS_CIS is set to -1 here. This prevent the the
   * h/w decode molecules from accelerating a decompress on a cis
   * unless a cis is explicitly set using this attribute.
   */
  xil_set_device_attribute(image, "HW_DECOMPRESS_CIS", (void *)-1);
  
  xil_get_info(image, &width, &height, &nbands, &datatype);
  
  tmp = video->getSize();
  if (tmp == SIZE_CIF){
    scalex = scaley = 2;
  } else  if (tmp == SIZE_QCIF){
    scalex = scaley = 4;
  } else {
    fprintf(stderr,"Unsupported video size %d\n",tmp);
    return 0;
  }
    
  /* CellB compression will produce 0.25 bytes/pixel for the first frame */
  bytes_for_first_frame = width * height / (scalex * scaley * 4);
  
  if ((bytes_for_first_frame > PACKETDATASIZE * MAXPACKETS)) {
    fprintf(stderr, " Warning: Max number of packets can be exceeded \n");
    fprintf(stderr, "   given this size frame. No packets will be trans-\n");
    fprintf(stderr, "   mitted on any frame in which the maximum number \n");
    fprintf(stderr, "   of packets is exceeded.\n\n");
    fprintf(stderr, "   You can increase -s or reduce -w or -h\n");
  }
  
  /*
   * H261 CIF is defined as 352x288 not 320x240. We set the width
   * and height here and create a scaled_image which will be used
   * in the capture/scale8nearest/compress_H261 molecule
   */
  if (video->getFormat() == H261) {
    if (verbose) printf("h261 bitrate %d maxquant %d\n",video->getH261BitRate(), video->getH261MaxQuant());
    xil_set_device_attribute(o1k_image, "H261_BIT_RATE", (void *) video->getH261BitRate());
    xil_set_device_attribute(o1k_image, "H261_MAX_QUANT", (void *) video->getH261MaxQuant());
    xil_set_device_attribute(o1k_image, "H261_PIP", (void *) video->getH261Pip());
    if (scalex == 2){
      width = 352;
      height = 288;
    } else if (scalex == 4) {
      width = 352/2;
      height = 288/2;
    } else {
      fprintf(stderr,"Invalid scale factor for H261 Compression: only 2 and 4 legal\n");
      return 0;
    }
    if (verbose) printf("h261: scaled_image w: %d h: %d b: %d\n", width, height, nbands);
    scaled_image = xil_create(xil_sys_state, width, height, nbands, XIL_BYTE);
  } else {
    if (scalex || scaley) {
      width = (int) ((float) width / (float) scalex + 0.5);
      height = (int) ((float) height / (float) scaley + 0.5);
      scaled_image = xil_create(xil_sys_state, width, height, nbands, XIL_BYTE);
    }
  }


  wstate = initialize_window(xil_sys_state, width, height, "Local View");

  
  if (video->getFormat() == H261)
    cis  = xil_cis_create(xil_sys_state, "H261");
  else if (video->getFormat() == CELLB)
    cis  = xil_cis_create(xil_sys_state, "CellB");
  else if (video->getFormat() == JPEG)
    cis  = xil_cis_create(xil_sys_state, "Jpeg");
  
  /*
   * set the buffers to make sure that you don't keep information you do
   * not need, but H261 currently needs to keep more frames around do
   * to XIL CIS seeking problems.
   */
  
  if (video->getFormat() == H261) {
    
    /*
     * H261 needs to keep more than one frame in the CIS
     * because of the non-byte aligned nature of H261. XIL
     * has chosen to mark the beginning of a H261 frame by
     * the bytes that encompass the H261 PSC. These bytes,
     * however, may contain bits from the previous frame.
     * Thus for a given H261 frame, you need at least that
     * frame and the next frame in the CIS.
     */
    
    xil_cis_set_keep_frames(cis, 3);
    xil_cis_set_max_frames(cis, 3);
      
    /*
     * The XIL H261 CIS is not seekable. This causes a problem
     * if one tries to both read data from a CIS (to transmit)
     * and also display from the CIS (for local preview). The
     * way this is normally done is to retrieve data from the
     * CIS (with a get_bits call) and then seek back a frame
     * and the call decompress on this frame to display it.
     * With the seek problem this is not possible. The solution
     * to the problem is to create a second CIS. When data is
     * retrieved from the CIS it is added to the second CIS as
     * well as being transmitted. Decompression is out of the
     * second CIS.
     */
    
    cis2 =xil_cis_create(xil_sys_state, "H261");
    
    xil_cis_set_keep_frames(cis2, 3);
    xil_cis_set_max_frames(cis2, 3);
    
  } else {
    xil_cis_set_keep_frames(cis, 1);
    xil_cis_set_max_frames(cis, 1);
  }
  
  /*
   * We need to be able to seek backwards to both get-bits, and decompress
   */
  if (video->getFormat() == CELLB || video->getFormat() == JPEG)
    xil_cis_set_attribute(cis, "IGNORE_HISTORY", (void *) TRUE);

  if (video->getFormat() == JPEG)
    xil_cis_set_attribute(cis, "ENCODE_411_INTERLEAVED", (void *) TRUE);

  win_pre_state = win_cur_state = check_events(wstate);
  
  video->releaseXil();
  
  gettimeofday(&t0, NULL);

  if (verbose) printf("***** Video Transmit tVidThrd Entering\n");
  
  timer0->timeStart();

  o1k_video_dynamic_update(1, video, cis);
  
  while (thread->isEnabled()){

    o1k_video_dynamic_update(0, video, cis);

    /*
     * Due to the single threadedness of XIL 1.2, we need
     * to use a mutex lock/release between xil calls. Since
     * the compression operation has a built in image skip
     * (frame rate) param, the compress molecule will stall
     * until the compress completes. We'd rather not do this
     * inside a mutex lock because this would keep our
     * decompress far view thread from decoding. Thus we'll
     * wait out here until we approximately know when a call
     * to compress will produce a frame right away.
     */

    tmp = video->getFrameRate();
    if (tmp)
      image_skip = (30/tmp) - 1;
    else
      image_skip = 30;
    compress_wait = 1.0/(tmp + 2);

    if (first_frame){
      first_frame = 0;
    } else {
      if (video->getFormat() == CELLB){
        /* FOR S/W Based CELLB */
        timer0->timeEnd();
        while (timer0->timeDiff() < compress_wait){
          poll(0,0,5);
          timer0->timeEnd();
        }
      } else {
        /* H261 or JPEG */
        int full_buffers;
        
        video->lockXil();
        xil_get_device_attribute(o1k_image, "FULL_BUFFERS", (void **) &full_buffers);
        video->releaseXil();
        
        while (full_buffers == 0 && thread->isEnabled()){
          poll(0,0,5);
          video->lockXil();
          xil_get_device_attribute(o1k_image, "FULL_BUFFERS", (void **) &full_buffers);
          video->releaseXil();
        }
      }
    }
    
    if (!thread->isEnabled())
      break;
    
    video->lockXil();

    if (video->getFormat() == H261){
      /*
       * This is the special scale molecule to handle the
       * 352x288 CIF H261 specification.
       */
      if (format == 2) /* NTSC */
        xil_scale(image, scaled_image, "nearest",
                  width/640.0, height/480.0);
      else /* PAL */
        xil_scale(image, scaled_image, "nearest",
                  width/768.0, height/576.0);
    } else if (scalex || scaley) {
      xil_scale(image, scaled_image, "nearest",
                1.0 / (float) scalex, 1.0 / (float) scaley);
    }
    else {
      scaled_image = image;
    }
    
    xil_compress(scaled_image, cis);
    xil_cis_sync(cis);
    xil_toss(scaled_image);
    timer0->timeStart();
    
    video->releaseXil(); 

    thread->setStopped(FALSE);
    
    /* check for X events like a `quit' from the pulldown menu */

    video->lockXil(); 
    win_cur_state = check_events(wstate);
    if (win_cur_state != win_pre_state){
      /* user adjusted iconfication state of window */
      win_pre_state = win_cur_state;
      video->setViewLocal(win_cur_state);
    } else {
      /* user has not adjusted iconfication state of window */
      if (video->getViewLocal() != win_cur_state){
        /* user adjusted desired view state */
        if (video->getViewLocal()){
          deiconify_window(wstate);
        } else {
          iconify_window(wstate);
        }
        win_cur_state = check_events(wstate);
        if (win_cur_state != video->getViewLocal())
          fprintf(stderr,"Error: adjust of window state failed\n");
        else {
          win_pre_state = win_cur_state;
        }
      } 
    }
    video->releaseXil(); 
    
    video->lockXil(); 
    if (xil_cis_has_frame(cis)) {
      
      /* 
       * If we're not in the midst of handling multiple frames, retrieve
       * as many as we can via xil_cis_get_bits_ptr().
       */
      
      if (!multiple_frames) {
        
        /*
         * There are many options one can use to packetize H261. Some
         * methods are easier if the decoder can take zero stuffing.
         * H261 can not handle zero stuffing but it can handle macro
         * block stuffing. More documentation on this is in the
         * receiver program. In any rate, an H261 frame in XIL is
         * completed by possible bits in the following frame. These
         * bits need to be transmitted as part of the frame in case
         * the next frame gets dropped by the network. On the
         * receiver end, a valid bitstream can be constructed even
         * if a frame is dropped.
         *
         * In sum, the above is the reason we are double buffering the
         * data retrieved from the H261 CIS. This way, we can send to
         * the transmit routine the current frame and the last bits for
         * the frame which are actually in the first bits of the next
         * frame.
         */
        
        data = (Xil_unsigned8*)xil_cis_get_bits_ptr(cis, &nbytes, &nframes);
        s_bytes  += nbytes;
        s_frames += nframes;

        /* only one frame should be retrieved from get bits if everything */
        /* is working right. If > 1 frame returned, some sort of XIL error */
        /* has occured */
           
        if (nframes != 1){
          fprintf(stderr, "video transmit more than one frame %d\n",nframes);
        }
        
        bAudioInitWait = FALSE;
        
        if (video->getFormat() == H261){
          if (video->getViewLocal()){
            /*
             * Since an XIL H261 frame is non-byte aligned and because XIL
             * marks the beginning of a frame by the bytes that make up the 
             * PSC of the H261 frame, the data retrieved from get bits will
             * not be a complete frame. Thus the 0 parameter in the call to
             * xil_cis_put_bits below
             */
            
            xil_cis_put_bits(cis2, nbytes, 0, data);
          }
        }
        video->releaseXil(); 
        
        /*printf("transmit compressed video data nbytes: %d\n",nbytes);*/

        rc = o1k_conf_network_transmit((unsigned char*)data,  nbytes, 1000, NET_TYPE_VIDEO);
        if (rc != 0){
          fprintf(stderr,"Video Transmit Thread Error: timeout %d\n",rc);
        }
      }
      
      if (video->getViewLocal()) {
        video->lockXil(); 
        if (video->getFormat() == H261){
          /*
           * There must be at least two frames in an XIL H261 CIS
           * in order to decompress from it.
           */
          if (xil_cis_number_of_frames(cis2)>1){
            display(wstate, cis2);
          }
        } else {
          /*
           * xil_cis_get_bits() moved the CIS read frame forward by
           * nframes. To decompress these, seek back nframes. If we're
           * in the midst of handling multiple frames, no seek is
           * needed.
           */
          
          if (!multiple_frames)
            xil_cis_seek(cis, -nframes, 1);
          display(wstate, cis);
        }
        video->releaseXil(); 
      } else {
        poll(0,0,5);
      }
    } else {
      video->releaseXil(); 
    }
    
    
    if (nframes > 0) {
      
      /*
       * If we're in the midst of handling multiple frames, decrement
       * the count.  Else, set multiple_frames if nframes is more than
       * 1.
       */
      if (multiple_frames)
        multiple_frames--;
      else
        multiple_frames = nframes - 1;
    }

    gettimeofday(&t1, NULL);
    if (t1.tv_sec - t0.tv_sec >= 1){
      tval = (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec) * 1e-6;
      if (verbose) printf("transmitted video %d frames %d bytes in %g seconds, %g frames/sec %g bits/second\n", s_frames, s_bytes, tval, s_frames/tval, (s_bytes*8.0) / tval);
      video->bitrate_t   = (int)((s_bytes*8.0)/tval);
      video->framerate_t = (int)(s_frames/tval);
      s_bytes  = 0;
      s_frames = 0;
      gettimeofday(&t0, NULL);      
    }

  }

  video->bitrate_t   = 0;
  video->framerate_t = 0;

  bAudioInitWait = TRUE;
  
  video->lockXil(); 

  close_window(wstate);  
  xil_cis_destroy(cis);
  
  if (video->getFormat() == H261)
    xil_cis_destroy(cis2);    
  video->releaseXil(); 

  
  if (verbose) printf("***** Video Transmit tVidThrd Exiting *****\n");
  thread->setStopped(TRUE);
  return 0;
}

/*------------------------------------------------------------------------
 *
 *  Function:	rVidThrd
 *
 *  Description:
 *      This is the worker thread that handles video receive. 
 *      
 *  Parameters:
 *	void* vp : should be the single O1kVideo* object
 *	
 *  Returns: 0
 *  
 *------------------------------------------------------------------------*/

extern int receive_pal_video;  /* see note in o1k_conf_stubs.cc  */


void* rVidThrd(void* vp)
{
  O1kVideo* video   = (O1kVideo*) vp;
  O1kThread* thread = video->thread_r;

  WindowState *wstate;
  int rc;
  XilCis cis;
  unsigned int width, height;
  int size;
  
  unsigned char* data;
  unsigned int nbytes;
  int type = NET_TYPE_VIDEO; /* thread index type */  

  struct timeval t0, t1;
  double tval;
  int s_bytes = 0;
  int s_frames = 0;
  
  int win_pre_state;
  int win_cur_state;

  XilImage tmpYcc;

  VideoOutput composite_out;

  if (video->getFormat() == H261)
    composite_out = video->getOutput();
  else
    composite_out = 0;
  
  data = (unsigned char*) new char[MAX_FRAME_SIZE];
  if (!data){
    fprintf(stderr, "Error rVidThrd could not allocate store buffer\n");
    return 0;
  }

  if (register_trap)
    asm("ta 6");
  
  video->lockXil(); 
  
  if (video->getFormat() == H261){
    cis  = xil_cis_create(xil_sys_state, "H261");
    /*
     * The cis to h/w decode is explicitly set here using the
     * HW_DECOMPRESS_CIS attribute. Note that the "o1k_image" used
     * here is the device image created transmit video thread. 
     * Thus that thread needs to start before this thread.
     */
    xil_set_device_attribute(o1k_image, "HW_DECOMPRESS_CIS", (void *)cis);
  } else if (video->getFormat() == CELLB) {
    cis  = xil_cis_create(xil_sys_state, "CellB");
  } else if (video->getFormat() == JPEG) {
    cis  = xil_cis_create(xil_sys_state, "Jpeg");
  }

  size = video->getSize();

  switch (video->getFormat()){
    case H261:
    {
      if (size == SIZE_CIF){
        width = 352;
        height = 288;
      } else if (size == SIZE_QCIF) {
        width = 352/2;
        height = 288/2;
      }
      break;
    }
    case CELLB:
    case JPEG:
    {
      if (receive_pal_video){
        if (size == SIZE_CIF){
          width = 384;
          height = 288;
        } else if (size == SIZE_QCIF) {
          width = 384/2;
          height = 288/2;
        }
      } else {
        if (size == SIZE_CIF){
          width = 320;
          height = 240;
        } else if (size == SIZE_QCIF) {
          width = 320/2;
          height = 240/2;
        }
      }
      break;
    }
  }

  if (verbose) printf("VIDEO RECEIVE w %d h %d\n",width, height);
  
  /*
   * If CellB set attributes since width & height are
   * "out-of-band" info
   */
  if (video->getFormat() ==  CELLB){
    xil_cis_set_attribute(cis, "WIDTH", (void *) width);
    xil_cis_set_attribute(cis, "HEIGHT", (void *) height);
  }
  
 /*
  * If JPEG, compress and decompress a dummy frame to load all the
  * necessary tables for the decompressor.  This allows us to decompress
  * received frames created with COMPRESSED_DATA_FORMAT set to
  * ABBREVIATED_FORMAT even if we miss the first frame. NOTE: this
  * technique assumes that the compressed bitstream was created using the
  * default JPEG CIS attributes.
  */
  if (video->getFormat() == JPEG) {
    XilImage dummy = xil_create(xil_sys_state, width, height, 3, XIL_BYTE);
    
    xil_compress(dummy, cis);
    xil_decompress(cis, dummy);
    xil_sync(dummy);
    xil_destroy(dummy);
  }

  if (video->getFormat() == H261) 
    xil_cis_set_max_frames(cis, 4);
  else
    xil_cis_set_max_frames(cis, 1);    

  if (composite_out){
    tmpYcc = xil_create(xil_sys_state, width, height, 3, XIL_BYTE);
  } else {
    if ((wstate = initialize_window(xil_sys_state, width, height, "Far View")) == NULL){
      fprintf(stderr, "ERROR: Unable to create window. ");
      return 0;
    }
  } 
  
  /*
   * Because of the non-byte aligned nature of H261 and because
   * the XIL H261 CIS can not tolerate zero stuffing, the first
   * frame added to an H261 CIS must begin with a perfect PSC.
   * Below we add a dummy blank (a black H261 frame) that starts
   * with a perfect PSC. Since the XIL H261 CIS needs possibly at
   * least two frames in order to decompress a given frame, we
   * add the blank frame twice.
   */
  
  if (video->getFormat() == H261) {
    if (size == SIZE_CIF)
      xil_cis_put_bits(cis, sizeof(blank_261_frame_cif), 0, blank_261_frame_cif);
    else
      xil_cis_put_bits(cis, sizeof(blank_261_frame_qcif), 0, blank_261_frame_qcif);
  }

  if (!composite_out)
    win_pre_state = win_cur_state = check_events(wstate);

  video->releaseXil(); 

  gettimeofday(&t0, NULL);
  
  if (verbose) printf("***** Video Receive rVidThrd Entering\n");
  
  thread->setStopped(FALSE);
  while (thread->isEnabled()){

    /* get data from network, timeout if thread disabled */

    do {
      /* sleep for a little */
      poll(0,0,5);
      rc = o1k_conf_network_receive(data, &nbytes, 0, type);
    } while (rc != 0 && thread->isEnabled() && conf->network->vid_thread_t->isEnabled());

    if (rc != 0)
      break;

    if (!composite_out){
      video->lockXil(); 
      
      win_cur_state = check_events(wstate);
      if (win_cur_state != win_pre_state){
        /* user adjusted iconfication state of window */
        win_pre_state = win_cur_state;
        video->setViewFar(win_cur_state);
      } else {
        /* user has not adjusted iconfication state of window */
        if (video->getViewFar() != win_cur_state){
          /* user adjusted desired view state */
          if (video->getViewFar()){
            deiconify_window(wstate);
          } else {
            iconify_window(wstate);
          }
          win_cur_state = check_events(wstate);
          if (win_cur_state != video->getViewFar())
            fprintf(stderr,"Error: adjust of window state failed\n");
          else {
            win_pre_state = win_cur_state;
          }
        } 
      }
      video->releaseXil(); 
    }
    
    s_bytes  += nbytes;
    s_frames += 1;

    /*printf("video receive put bits nbytes %d\n",nbytes); fflush(stdout);*/

    if (composite_out){
      video->lockXil(); 
      xil_cis_put_bits(cis, nbytes, 1, data);
      xil_decompress(cis, tmpYcc);
      xil_sync(tmpYcc);
      video->releaseXil(); 
    } else {
      if (video->getViewFar()){
        video->lockXil(); 
        xil_cis_put_bits(cis, nbytes, 1, data); 
        display(wstate, cis);
        video->releaseXil(); 
      }
    }

    gettimeofday(&t1, NULL);
    if (t1.tv_sec - t0.tv_sec >= 1){
      tval = (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec) * 1e-6;
      if (verbose) printf("received video %d frames %d bytes in %g seconds, %g frames/sec %g bits/second\n", s_frames, s_bytes, tval, s_frames/tval, (s_bytes*8.0) / tval);

      video->bitrate_r   = (int)((s_bytes*8.0)/tval);
      video->framerate_r = (int)(s_frames/tval);
      s_bytes  = 0;
      s_frames = 0;
      gettimeofday(&t0, NULL);      
    }
  }

  video->bitrate_r   = 0;
  video->framerate_r = 0;
  
  video->lockXil();
  if (!composite_out)
    close_window(wstate);
  xil_cis_destroy(cis);
  video->releaseXil(); 

  delete data;
  
  if (verbose) printf("***** Video Receive rVidThrd Exiting *****\n"); fflush(stdout);
  thread->setStopped(TRUE);
  return 0;
}
