/*
 * This example captures a set of frames from MMACo1k and saves the captured
 * XIL compressed image seqence (CIS) into a file.
 */

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

/* - - - - - - - - - - - - - - - - - - - - - - */

#define GETOPTARGS "B:Q:o:C:s:I:f:i:D:R:ET"

void
usage(char *name, char *badparam)
{
  if (badparam)
    fprintf(stderr, "ERROR: bad parameter [%s]\n", badparam);
  
  fprintf(stderr, "\
Usage: %s [options]\n\n\
  Legal Options:                               Default\n\
        -o %%s  - save bitstream in file         NULL \n\
        -C %%s  - Compression type               CellB\n\
                    [ CellB | Jpeg | H261 | Mpeg1 ]\n\
        -s %%d  - shrink factor                  2\n\
        -I %%d  - select input channel           1\n\
        -f %%d  - frames to capture              100\n\
        -i %%d  - frames to skip between capture 0\n\
        -D %%s  - name of o1k device            /dev/o1k0\n\
  H261 Options:                                 \n\
        -B %%d  - bitrate in kbits/sec          256\n\
        -Q %%d  - max quantization              10\n\
  Mpeg1 Options:                                 \n\
        -B %%d  - bitrate in kbits/sec          2000\n\
        -R %%d  - rate control flag             1\n\
        -Q %%d  - fixed quantization value      10\n\
  Legal Switches:\n\
        -E      - exit on first error\n\
        -T      - display timestamp and frame numbers \n\
", name);

  exit(1);
}

/* - - - - - - - - - - - - - - - - - - - - - - */
/* Error handler installed iff -E specified */
Xil_boolean
error_handler(XilError error)
{
  xil_call_next_error_handler(error);
  
  fprintf(stderr, "\n***ERROR received; example exiting\n");
  exit(1);
}


/* - - - - - - - - - - - - - - - - - - - - - - */
void
write_frames(FILE * df, XilCis cis)
{
  Xil_unsigned8 *data;
  int nframes, nbytes;
  
  while (xil_cis_has_frame(cis)) {
    data = (void *) xil_cis_get_bits_ptr(cis, &nbytes, &nframes);
    
    if (df)
      fwrite(data, 1, nbytes, df);
  }
}

/* - - - - - - - - - - - - - - - - - - - - - - */

main(int argc, char **argv)
{
  
  /* XIL variables */
  XilSystemState state;
  XilImage o1k_image;
  XilImage scaled_image;
  XilDataType datatype;
  Xil_boolean error_out = FALSE;
  XilCis cis;

  char pattern[32];
  unsigned int width, height, nbands;
  int frames;
  int total_frames = 100;
  int image_skip = 0;
  int doscale = 2;
  int i;
  double t;
  struct timeval t0, t1;
  int port = 1;
  int c;
  int h261_bitrate = 256;
  int h261_mquant  = 10;  
  
  int mpeg1_bitrate = 2000;
  int mpeg1_rate_control = 1;
  int mpeg1_fixed_quant = 10;

  int format;
  
  char *movie_file = NULL;
  char *devname = NULL;
  
  FILE *df;
  
  enum compressor_type {
    JPEG, CELLB, H261, MPEG
  };
  enum compressor_type do_cis = CELLB;
  char *cis_type = "CellB";
  
  Xil_boolean timestampts_wanted = FALSE;
  hrtime_t* timeStamp_ptr = NULL;
  hrtime_t  timeStamp_last;
  hrtime_t  timeStamp_cur;
  unsigned int frame_number = 0;

  strcpy(pattern, "IIII");
                       
  while ((c = getopt(argc, argv, GETOPTARGS)) != EOF)
    switch (c) {
      case 's':
        doscale = atoi(optarg);
        break;
      case 'I':
        port = atoi(optarg);
        break;
      case 'D':
        devname = optarg;
        break;
      case 'i':
        image_skip = atoi(optarg);
        break;
      case 'f':
        total_frames = atoi(optarg);
        break;
      case 'B':
        mpeg1_bitrate = h261_bitrate = atoi(optarg);
        break;
      case 'Q':
        mpeg1_fixed_quant = h261_mquant = atoi(optarg);
        break;
     case 'R':
        mpeg1_rate_control = atoi(optarg);
        break;
      case 'C':
        if (!strcasecmp(optarg, "CellB")) {
          do_cis = CELLB;
          cis_type = "CellB";
        }
        else if (!strcasecmp(optarg, "Jpeg")) {
          do_cis = JPEG;
          cis_type = "Jpeg";
        }
        else if (!strcasecmp(optarg, "H261")) {
          do_cis = H261;
          cis_type = "H261";
        }
        else if (!strcasecmp(optarg, "Mpeg1")) {
          do_cis = MPEG;
          cis_type = "Mpeg1";
        }
        else
          usage(argv[0], optarg);
        break;
      case 'o':
        movie_file = optarg;
        break;
      case 'E':
        error_out = TRUE;
        break;
      case 'T':
        timestampts_wanted = TRUE;
        break;
      case '?':
      default:
        usage(argv[0], NULL);
      }
  
  /* expect no args left over */
  if (argv[optind]) {
    usage(argv[0], argv[optind]);
  }
  
  if (!movie_file) {
    fprintf(stderr, "\n***ERROR: must set `-o' option for output movie filename.\n\n");
    usage(argv[0], NULL);
  }
  
  df = fopen(movie_file, "w");
  if (df == NULL) {
    fprintf(stderr, "failed to open output movie file.\n");
    exit(1);
  }
  
  state = xil_open();
  if (state == NULL) {
    fprintf(stderr, "unable to open xil library\n");
    exit(1);
    }
  
  if (error_out)
    xil_install_error_handler(state, error_handler);
  
  {
    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 *) devname);
    xil_device_set_value(device, "PORT_V", (void *) port);
    
    o1k_image = xil_create_from_device(state, "MMACo1k", device);
    xil_device_destroy(device);
  }

  if (o1k_image == NULL) {
    fprintf(stderr, "failed to open MMACo1k device\n");
    xil_close(state);
    exit(1);
  }
  
  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);
  }

  if (do_cis == H261) {
    xil_set_device_attribute(o1k_image, "H261_BIT_RATE", (void *) h261_bitrate);
    xil_set_device_attribute(o1k_image, "H261_MAX_QUANT", (void *) h261_mquant);
  }
  
  if (do_cis == MPEG) {
    xil_set_device_attribute(o1k_image, "MPEG_RATE_CONTROL", (void *) mpeg1_rate_control);
    xil_set_device_attribute(o1k_image, "MPEG_FIXED_QUANT",  (void *) mpeg1_fixed_quant);
  }

  if (image_skip)
    xil_set_device_attribute(o1k_image, "IMAGE_SKIP",
                             (void *) image_skip);
  
  xil_get_info(o1k_image, &width, &height, &nbands, &datatype);
  
  if (do_cis == H261) {
    if (doscale == 2){
      width = 352;
      height = 288;
    } else if (doscale == 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);
    }
    scaled_image = xil_create(state, width, height, nbands, XIL_BYTE);
  } else {
    if (doscale) {
      width = (int) (((float) width / doscale) + 0.5);
      height = (int) (((float) height / doscale) + 0.5);
      scaled_image = xil_create(state, width, height, nbands, XIL_BYTE);
    }
  }
  
  /* MPEG requires CIF (352 width) compression */
  if (do_cis == MPEG) {
      if (doscale != 2) {
          fprintf(stderr, "ERROR: scalefactor is: %d, should be 2 for Mpeg1\n",
                  doscale);
          xil_close(state);
          exit(1);
      }
      width  = 352; /* Osprey Mpeg1 compression is 601 not square */
      height = 240;
      scaled_image = xil_create(state, width, height, nbands, XIL_BYTE);
  }

  /* Create a cis to put the movie frames in. */
  cis = xil_cis_create(state, cis_type);
  
  /*
   * set the buffers to make sure that you don't keep information you
   * do not need.  H261 needs more frames.
   */
  if (do_cis == H261) {
    xil_cis_set_keep_frames(cis, 3);
    xil_cis_set_max_frames(cis, 3);
  } else if (do_cis == MPEG) {
      xil_cis_set_keep_frames(cis, 6);
      xil_cis_set_max_frames(cis, 6);
  } else {
    xil_cis_set_keep_frames(cis, 1);
    xil_cis_set_max_frames(cis, 1);
  }

  if (do_cis == MPEG) {
      int bitrate = 1000 * mpeg1_bitrate;
      xil_cis_set_attribute(cis, "COMPRESSOR_BITS_PER_SECOND", (void*)bitrate);
  }

  /*
   * If JPEG, set an attribute needed for fast decompression of the
   * resulting movie.
   */
  if (do_cis == JPEG)
      xil_cis_set_attribute(cis, "ENCODE_411_INTERLEAVED", (void *) TRUE);
    
  xil_get_device_attribute(o1k_image, "TIMESTAMP", (void*)&timeStamp_ptr);

  for (frames = 0; frames < total_frames; frames++) {
    if (do_cis == H261 || do_cis == MPEG){
      if (format == 2) /* NTSC */
        xil_scale(o1k_image, scaled_image, "nearest",
                  width/640.0, height/480.0);
      else
        xil_scale(o1k_image, scaled_image, "nearest",
                  width/768.0, height/576.0);        
    } else if (doscale) {
      xil_scale(o1k_image, scaled_image, "nearest",
                1.0 / (float) doscale, 1.0 / (float) doscale);
    }
    else {
      scaled_image = o1k_image;
    }
    
    xil_compress(scaled_image, cis);
    xil_cis_sync(cis);
    xil_toss(scaled_image);
    
    if (timestampts_wanted){
        timeStamp_cur  = *timeStamp_ptr;
        xil_get_device_attribute(o1k_image, "FRAME_NUMBER", (void*)&frame_number);
        printf("frame[%.4d] cur stamp: %llx from last: %lld msec\n",
               frame_number,
               timeStamp_cur,
               (timeStamp_cur - timeStamp_last)/1000000);
        timeStamp_last = timeStamp_cur;
    }

    write_frames(df, cis);
    
    /* don't include overhead of first frame in frame rate calculation */
    if (frames == 0) {
      gettimeofday(&t0, NULL);
    }
  }
  
  /* done, empty buffer if any frames remain */
  xil_cis_flush(cis);
  write_frames(df, cis);
  
  /* calculate frame rate */
  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);
  
  xil_close(state);
  
  return 0;
}
