/*
   File: mpeg2video.cc

   By: Alex Theo de Jong
   Created: February 1996

   Description:
   Implementation of the MPEG 2 Video class
*/

#define RELEASE "1.2, November 1996"

#ifdef __GNUG__
#pragma implementation
#endif

#include "athread.hh"

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <String.h>
#include <fstream.h>
#ifdef USE_OPENPTC
#include <ptc/ptc.h>
#endif
#include "error.hh"
#include "debug.hh"
#include "util.hh"

#include "sync.hh"
#include "mpeg2const.hh"
#include "mpeg2buff.hh"

#include "videoconst.hh"
#include "display.hh"
#include "idct.hh"
#include "vstream.hh"
#include "layerdata.hh"
#define GLOBAL
#include "global.hh"

#include "mpeg2video.hh"


/*
 *
 * Mpeg2Video
 *
 */


int error=0;

Mpeg2Video::Mpeg2Video(Mpeg2Buffer* input_buffer, Synchronization* s, int c, char** v) :
  argc(c), terminate(0), terminated(0), blockreadsize(0), framerate(0), 
  skip_state(0), skip_count(0), skipped_frames(0) 
{
  if (argc) argv=v;
  else argv=0;

  if (options()){   // parse options

    // create layer data (including display)
    ::ld=new LayerData(displaytitle.chars(), s);
    
    // set or create video data buffer
    if (input_buffer) ld->input=new VideoStream(input_buffer, s);
    else ld->input=0;
    
    // start player thread
    if ((error=athr_create((void*(*)(void*))Mpeg2Video::player, this, &thread_id))<0){
      error("could not create video player");
      athr_exit(0);
    }
    
    /* Seem to slow things donw! */
    sched_param param;
    int policy;
    if (athr_getschedparam(thread_id, &policy , &param)<0){
      error("could not get thread priority");
    }
#ifdef LINUX
    param.sched_priority+=1;
//    policy = SCHED_RR;
    TRACER("VIDEOPRIORITY=" << param.sched_priority << "(" << param.sched_priority-2 << ")");
#else
    param.prio+=2;
    TRACER("VIDEOPRIORITY=" << param.prio << "(" << param.prio-2 << ")");
#endif
    if (athr_setschedparam(thread_id, policy, &param)<0){
      error("could not set thread priority");
    }
  }
}


Mpeg2Video::~Mpeg2Video(){
  terminate=1;
  if (!terminated){
    TRACER("waiting for video thread to terminate ... ");
    athr_join(thread_id);
  }

#if 0
  delete ::ld;
  delete llframe1[0];
  delete lltmp;
  for (int i=0; i<3; i++){
    delete auxframe[i];
    delete refframe[i];
    delete oldrefframe[i];
//    delete llframe1[0];
//    delete lltmp;
  }
#endif
  TRACER("video thread terminated");
}

void* Mpeg2Video::player(Mpeg2Video* base){
  base->terminate=0;
  base->terminated=0;

  base->play();
  base->terminated=1;
  athr_exit(0);
  return 0;
}

const int NBR_FRAMES = 100;

Mpeg2Video* mv=0;

int Mpeg2Video::play(){
  TRACER("int Mpeg2Video::play()");
  timeval tstart;  // Time start
  timeval sstart;  // Sequence of NBR_FRAMES start
  timeval fstart, fstop; // Frame start and stop
//verbose=2;
  framenum=0;
  skip_state=0;
  skip_count=0;
  skipped_frames=0;
  i_c=p_c=b_c=d_c=0;

  if (!ld->input) 
    ld->input=new VideoStream(filename.chars());	// read from file

  if (blockreadsize)
    ld->input->set_read_block_size(blockreadsize);

  ::vs=ld->input;

  while (!terminate && ld->getheader() && (!ld->sync || !ld->sync->done(1))){ 
    if (!framenum){
      initdecoder();
      gettimeofday(&tstart, 0);
      gettimeofday(&sstart, 0);
      gettimeofday(&fstart, 0);
    }
    playedlastframe=0;
    
    // Allow B-frames to be skipped if in "skip_state"
    switch(pict_type){
    case B_TYPE : b_c++; if (skip_state<=0) ld->getpicture(framenum); else skipped_frames++; break;
    case P_TYPE : p_c++; ld->getpicture(framenum); break;
    case I_TYPE : i_c++; ld->getpicture(framenum); break;
    case D_TYPE : d_c++; if (!skip_state) ld->getpicture(framenum); else skipped_frames++; break;
    default     :  error("unknown frame in video stream");
    }

    framenum++;  // cruzial to have this here!!
    athr_yield();

    if (!secondfield){
      if (ld->sync && !playedlastframe) 
        if (ld->sync->skip(1)<0) break;  // skip PTS, <0 end of file
      if (framerate!=0){
        gettimeofday(&fstop, 0);
        doframerate(fstart, fstop);
        gettimeofday(&fstart, 0);
      }
    }

    if (!quiet && framenum>1 && (framenum % NBR_FRAMES)==0){
      showframerate(sstart);
      gettimeofday(&sstart, 0);
    }

  }
  if (framenum!=0) ld->putlast();   // put last frame

  if (!quiet && framenum) showframerateend(tstart);

  return 0;
}


void Mpeg2Video::doframerate(timeval& fstart, timeval& fstop){
  int time_real_usec=
    (fstop.tv_sec-fstart.tv_sec)*1000000+(fstop.tv_usec-fstart.tv_usec);

  if (time_real_usec > time_interval_usec){
    if (++skip_count>0) skip_state=1;  // Running behind
    return;
  }
  else {                               // Running ahead
    if (--skip_count<-2){
      skip_state=0;
//      timer.waitcond(time_interval_usec - time_real_usec);
      timer.wait(time_interval_usec - time_real_usec);
    }
    return;
  }
}


int Mpeg2Video::showframerate(timeval& sstart){  // show frame rate every NBR_FRAMES frames
  static char txt[100];
  timeval tstop;
  gettimeofday(&tstop,(struct timezone *)NULL);
  unsigned int runtime=1000*(tstop.tv_sec-sstart.tv_sec)+(tstop.tv_usec-sstart.tv_usec)/1000;
  sprintf(txt, " %d.%02d fps ", (1000*NBR_FRAMES/runtime), ((100000*NBR_FRAMES/runtime)%100));
  msg(txt);
  return 1;
}


int Mpeg2Video::showframerateend(timeval& tstart){
  static char txt[100];
  timeval tstop;
  gettimeofday(&tstop,(struct timezone *)NULL);
  int runtime = 1000*(tstop.tv_sec-tstart.tv_sec)+(tstop.tv_usec-tstart.tv_usec)/1000;
/*
  sprintf(errortext, "\n%d.%02d seconds, %d frames, %d.%02d fps (%d B frames skipped)",
          runtime/100, runtime%100, framenum, ((10000*framenum+runtime/2)/runtime)/100,
          ((10000*framenum+runtime/2)/runtime)%100, skipped_frames);
*/
  sprintf(txt, "\n%d.%02d seconds, %d frames, %d.%02d fps (%d B frames skipped)",
          runtime/1000, (100*runtime/1000)%100, framenum, 1000*framenum/runtime,
          (100000*framenum/runtime)%100, skipped_frames);
  message(txt);
  sprintf(txt, "Frames: %d (I), %d (P), %d (B), %d (D)", i_c, p_c, b_c, d_c);
  message(txt);
  return 1;
}


void Mpeg2Video::usage(const char* name){
  msg("usage: "); msg(name); message(" <filename> [options]");
  message("options:");
  message("\t-vn\tverbose output (n: level)");
  message("\t-q\tquiet (no error output)");
  message("\t-ds\tdisplay title (s: title)");
  message("\t-fn\tframe rate (default = as fast as possible)"); 
  message("\t-cn\tChunk size for buffer block read (NOTE: be carefull when using sychronization)");
  message("");
  message("\tMPEG 2 Video Player\n");
  msg("\tversion "); message(RELEASE);
  message("\tAlex Theo de Jong (e-mail: alex.dejong@nist.gov)\n");
  message("\tMulti-Media and Digital Video Group");
  message("\tNational Institute of Standards and Technology");
  message("\tGaithersburg, Md, U.S.A.\n\n");
  message("");
  message("Original code by:");
  message("\tMPEG Software Simulation Group   &");
  message("\tStefan Eckart");
  message("\tFraunhofer-Institut fuer Festkoerpertechnologie, Germany");
}


int Mpeg2Video::options(){
  TRACER("int Mpeg2Video::options()");

  if (argc<2 || argv[1][0]=='-'){
    usage(argv[0]);
    return 0;
  }

  filename=argv[1];

  for (int i=2; i<argc; i++){
    if (argv[i]=='\0') break;

    switch (toupper(argv[i][1])){
    case 'F': framerate = atoi(&argv[i][2]);  ::framerate=framerate; break;
    case 'V': verbose = atoi(&argv[i][2]);    break;
#ifdef TRACE
    case 'T': trace=1;  break;
#endif
    case 'D': displaytitle=&argv[i][2]; break;
    case 'C': blockreadsize=atoi(&argv[i][2]); break;
    case 'Q': quiet = 1;   break;
    default : msg("undefined option ignored - "); message(argv[i]);
    }
  }

  if (framerate)
    time_interval_usec=1000000/framerate;  // in usec
  if (!displaytitle.length()) 
    displaytitle="Hi there!";

  return 1;
}


int Mpeg2Video::initdecoder(){
  TRACER("int Mpeg2Video::initdecoder()");
  int size;
  static int blk_cnt_tab[3] = {6,8,12};

  if (!ld->mpeg2){     // force MPEG-1 parameters
    prog_seq = 1;
    prog_frame = 1;
    pict_struct = FRAME_PICTURE;
    frame_pred_dct = 1;
    chroma_format = CHROMA420;
    matrix_coefficients = 5;
  }

  /* round to nearest multiple of coded macroblocks */
  mb_width = (horizontal_size+15)/16;
  mb_height = (ld->mpeg2 && !prog_seq) ? 2*((vertical_size+31)/32)
                                        : (vertical_size+15)/16;
  coded_picture_width = 16*mb_width;
  coded_picture_height = 16*mb_height;
  chrom_width = (chroma_format==CHROMA444) ? coded_picture_width
                                           : coded_picture_width>>1;
  chrom_height = (chroma_format!=CHROMA420) ? coded_picture_height
                                            : coded_picture_height>>1;
  blk_cnt = blk_cnt_tab[chroma_format-1];

  for (int cc=0; cc<3; cc++){
    if (cc==0) size = coded_picture_width*coded_picture_height;
    else       size = chrom_width*chrom_height;

    refframe[cc]=new unsigned char[size];
    oldrefframe[cc]=new unsigned char[size];
    auxframe[cc]=new unsigned char[size];

    if (ld->scalable_mode==SC_SPAT){   // this assumes lower layer is 4:2:0
      llframe0[cc]=new unsigned char[(llw*llh)/(cc?4:1)];
      llframe1[cc]=new unsigned char[(llw*llh)/(cc?4:1)];
    }
  }

  if (ld->scalable_mode==SC_SPAT) lltmp=new short[llw*((llh*vn)/vm)];

  if (!ld->display || (ld->display->init(horizontal_size, vertical_size)<0)){
    error("could not initialize X11 display");
    athr_exit(0);
  }
  return 1;
}
