/* @(#)xilbroadcast.c	1.67 94/05/18  */

/*
 * This example reads an image from MMACo1k, transmits compressed video over
 * the local network, and optionally displays the image in a Xwindow.
 */

#include <stdio.h>
#include <stdlib.h>
#include <xil/xil.h>
#include <sys/time.h>

#include "params.h"
#include "packet.h"
#include "transmit.h"
#include "window_state.h"
#include "cellb_util.h"

void
transmit_data(Xil_unsigned8 * data, int nbytes, int width, int height,
	      TransmitState * tstate, Parameters * param, Xil_unsigned8 * data2, int hflag)
{
  PacketType packet;
  static int frame = 0;
  int count = 0;
  
  packet.header.frame = frame++;
  packet.header.session = tstate->session | (VIDEO_PACKET << 28);
  packet.header.x = width;
  packet.header.y = height;
  if (nbytes > PACKETDATASIZE * MAXPACKETS) {
    if (verbose)
      fprintf(stderr, "Not sending frame %d due to size (%d) > maximum\n",
              frame, nbytes);
  }
  else {
    int x = 0;
    int y = 0;
    
    while (nbytes > 0) {
      int curbytes = 0;
      int cellwidth = width / 4;
      
      if (nbytes <= PACKETDATASIZE) {
        curbytes = nbytes;
        if (!hflag)
          packet.header.flags = ((1 << count) - 1);
        else
          packet.header.flags = ~(1 << count);
        if (count) {
          packet.header.x = x * 4;
          packet.header.y = y * 4;
        }
      }
      else {
        curbytes = PACKETDATASIZE;
        if (param->cellb_xy_addr) {
          if (count) {
            packet.header.x = x * 4;
            packet.header.y = y * 4;
          }
          x += num_cells(data, &curbytes);
          y += x / cellwidth;
          x = x % cellwidth;
        }
        packet.header.flags = ~(1 << count);
      }
      
      packet.header.size = curbytes;
      count++;
      nbytes -= curbytes;
      memcpy(packet.data, data, curbytes);
      data += curbytes;
      
      if (verbose)
        printf("transmitting: %d bytes left: %d\n",packet.header.size, nbytes);
      
      transmit_packet(tstate, &packet);
      if (param->polling)
        poll(0, 0, param->polling);
    }

    /*
     * For H261, we also transmit a final packet for this frame
     * which includes the first four bytes of the next frame (
     * the first byte of which may contain bits from this frame.
     */
   
    if (hflag){
      int curbytes = 0;
      int nbytes = 4;
      
      curbytes = nbytes;
      packet.header.flags = ((1 << count) - 1);
      if (count) {
        packet.header.x = x * 4;
        packet.header.y = y * 4;
      }
      
      packet.header.size = curbytes;
      count++;
      nbytes -= curbytes;
      memcpy(packet.data, data2, curbytes);
      data2 += curbytes;
      
      if (verbose)
        printf("transmitting: %d bytes left: %d\n",packet.header.size, nbytes);
      
      transmit_packet(tstate, &packet);
      if (param->polling)
        poll(0, 0, param->polling);
    }
  }
}


main(int argc, char **argv)
{
    int scalex = 1, scaley = 1;
    XilSystemState state;
    XilImage image, o1k_image;
    XilDataType datatype;
    XilImage scaled_image;
    XilCis cis;
    XilCis cis2;    
    Xil_unsigned8 *data[2];
    unsigned int di  = 0;
    unsigned int dip = 0;    
    
    Parameters *param;
    TransmitState *tstate;
    WindowState *wstate;

    unsigned int width, height, nbands;
    int frames;
    double t;
    struct timeval t0, t1;
    int nframes[2] = { 0, 0};
    int nbytes[2];
    int multiple_frames = 0;
    const char *video_format;
    int bytes_for_first_frame;
    FILE *df;
    int first_frame = 1;
    int format;
    
    param = initialize_parameters(argc, argv);

    if (param->transmit)
	tstate = initialize_transmit(param);

    state = xil_open();
    if (state == NULL) {
	fprintf(stderr, "unable to open xil library\n");
	exit(1);
    }


    {
	XilDevice device;

	if (!(device = xil_device_create(state, "MMACo1k"))) {
	    fprintf(stderr, "Unable to create a device object\n");
	    xil_close(state);
	    exit(1);
	}
	xil_device_set_value(device, "DEVICE_NAME", (void *) param->devname);
	if (!(image = xil_create_from_device(state, "MMACo1k", device))) {
	    fprintf(stderr, "ERROR: failed to open MMACo1k device\n");
	    xil_close(state);
	    exit(1);
	}
	xil_device_destroy(device);
    }
    o1k_image = image;

    xil_set_device_attribute(image, "PORT_V", (void *) param->whichinput);

    xil_get_device_attribute(o1k_image, "FORMAT_V", (void **) &format);
    if (format == 0) {
      fprintf(stderr, "Unknown video format, exiting.\n");
      xil_close(state);
      exit(1);
    }

    xil_set_device_attribute(image, "IMAGE_SKIP", (void *) param->image_skip);
    xil_set_device_attribute(image, "MAX_BUFFERS", (void *) param->max_buffer);
    xil_get_device_attribute(image, "FORMAT", (void **) &video_format);

    xil_get_info(image, &width, &height, &nbands, &datatype);

    /*
     * If the user has not changed the default to something else, reset the
     * default capture width and height
     */
    if (param->width == -1)
	param->width = width;
    if (param->height == -1)
	param->height = height;

    /* check for width or height too big */
    if ((param->width > width) || (param->height > height)) {
	fprintf(stderr, "ERROR: Maximum width & height are %d & %d\n",
		width, height);
	xil_close(state);
	exit(1);
    }

    /*
     * if user specifies '-s 0' for no scaling, set local 'scalefactor' to
     * 1.0
     */
    scalex = (param->scalex ? param->scalex : 1);
    scaley = (param->scaley ? param->scaley : 1);

    /* CellB compression will produce 0.25 bytes/pixel for the first frame */
    bytes_for_first_frame = param->width * param->height / (scalex * scaley * 4);

    if (param->transmit &&
	    (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");
    }

    if (param->do_cis == CELLB) {

	/*
	 * CellB requires that the dimensions of the image used with
	 * xil_compress each be a multiple of 4
	 */
	if ((int) (((float) param->width / (float) scalex) + 0.5) % 4) {
          param->width = ((param->width / scalex) & ~3) * scalex;
          if (verbose)
	    fprintf(stderr, "capture width adjusted to %d\n", param->width);
	}

	if ((int) (((float) param->height / (float) scaley) + 0.5) % 4) {
          param->height = ((param->height / scaley) & ~3) * scaley;
          if (verbose)
	    fprintf(stderr, "capture height adjusted to %d\n", param->height);
	}
    }

    if (param->do_cis == UYVY) {

	/*
	 * UYVY requires that the width of the image used with xil_compress
	 * each be a multiple of 2
	 */
	if ((int) (((float) param->width / (float) scalex) + 0.5) % 2) {
          param->width = ((param->width / scalex) & ~1) * scalex;
          if (verbose)
	    fprintf(stderr, "capture width adjusted to %d\n", param->width);
	}

    }
    
    if ((param->width < width) || (param->height < height)) {
	image = xil_create_child(o1k_image,
				 (width - param->width) / 2,
				 (height - param->height) / 2,
				 param->width, param->height,
				 0, nbands);
	if (image == NULL) {
	    fprintf(stderr, "ERROR: failed to open child image on MMACo1k\n");
	    xil_close(state);
	    exit(1);
	}
	xil_get_info(image, &width, &height, &nbands, &datatype);
    }

    /*
     * 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 (param->do_cis == H261) {
      xil_set_device_attribute(o1k_image, "H261_BIT_RATE", (void *) param->h261_bitrate);
      xil_set_device_attribute(o1k_image, "H261_MAX_QUANT", (void *) param->h261_maxquant);
      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");
        xil_close(state);
        exit(1);
      }
      if (verbose)
        printf("h261: scaled_image w: %d h: %d b: %d\n",
               width, height, nbands);
      scaled_image = xil_create(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(state, width, height, nbands, XIL_BYTE);
      }
    }
    
    if (param->preview)
	wstate = initialize_window(param, state);

    cis  = xil_cis_create(state, param->cis_type);

    /*
     * 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 (param->do_cis == 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(state, param->cis_type);
      
      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 ((param->do_cis == JPEG) || (param->do_cis == CELLB))
	xil_cis_set_attribute(cis, "IGNORE_HISTORY", (void *) TRUE);

    if (param->do_cis == JPEG) {
	/* this attribute must be TRUE for acceleration */
	xil_cis_set_attribute(cis, "ENCODE_411_INTERLEAVED", (void *) TRUE);
    }


    if (param->datafile)
	df = fopen(param->datafile, "w");
    else
	df = (FILE *) NULL;
    
    for (frames = 0; frames < param->frames; frames++) {
      
      /* check for X events like a `quit' from the pulldown menu */
      if (param->preview)
        check_events(wstate);
      
      if (param->do_cis == 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);
      
      
      /* don't include overhead of first frame in frame rate calculation */
      if (frames == 0)
        gettimeofday(&t0, NULL);
      
      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[di] = (void *) xil_cis_get_bits_ptr(cis, &nbytes[di], &nframes[di]);
          
          if (param->do_cis == H261){

            /*
             * 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[di], 0, data[di]);
            
          }
          
          if (df)
            fwrite(data[di], 1, nbytes[di], df);

          if (param->transmit){
            if (param->do_cis != H261)
              transmit_data(data[di], nbytes[di], width, height, tstate, param, NULL, 0);
            else if (!first_frame){

              /*
               * We need to have at least two frames buffered before we
               * transmit an XIL H261 frame. See reasons in comments
               * above. Thus we skip the first frame retrieved from the
               * XIL CIS (first_frame flag above). Here, we are calling
               * transmit_data() with the previous retrieved frame and
               * the first bytes of the current retrieved frame.
               */
              transmit_data(data[dip], nbytes[dip], width, height, tstate, param, data[di], 1);
            }
          }
          
          if (param->do_cis == H261){
            /* double buffer H261 CIS frame retrieval */
            dip = di;
            di = (di+1)&0x1;
          }
          
          first_frame = 0;
        }
        
        
        if (param->preview) {
          
          if (param->do_cis == 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)>2)
              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[dip], 1);
            display(wstate, cis);
          }
        }
      }
      
      if (nframes[dip] > 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[dip] - 1;
      }

    }

    gettimeofday(&t1, NULL);

    t = (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec) * 1e-6;
    printf("%d frames in %g seconds, %g frames/second\n",
	   frames - 1, t, (frames - 1) / t);

    /*
     * cleanup
     */
    if (df) {
	/* flush any oustanding compresses */
	xil_cis_flush(cis);
	while (xil_cis_has_frame(cis)) {
	    data[di] = (void *) xil_cis_get_bits_ptr(cis, &nbytes[di], &nframes[di]);
	    fwrite(data[di], 1, nbytes[di], df);
	}

	fclose(df);
    }

    if (param->transmit)
	close_transmit(tstate);

    if (param->preview)
	close_window(wstate);

    xil_destroy(o1k_image);
    xil_cis_destroy(cis);

    if (param->do_cis == H261)
      xil_cis_destroy(cis2);    

    /*
     * shutdown
     */
    xil_close(state);

    return 0;
}
