/* #define DEBUG */
/* subspic.c, Frame buffer substitution routines */

/* Copyright (C) 1996, MPEG Software Simulation Group. All Rights Reserved. */

/*
 * Disclaimer of Warranty
 *
 * These software programs are available to the user without any license fee or
 * royalty on an "as is" basis.  The MPEG Software Simulation Group disclaims
 * any and all warranties, whether express, implied, or statuary, including any
 * implied warranties or merchantability or of fitness for a particular
 * purpose.  In no event shall the copyright-holder be liable for any
 * incidental, punitive, or consequential damages of any kind whatsoever
 * arising from the use of these programs.
 *
 * This disclaimer of warranty extends to the user of these programs and user's
 * customers, employees, agents, transferees, successors, and assigns.
 *
 * The MPEG Software Simulation Group does not represent or warrant that the
 * programs furnished hereunder are free of infringement of any third-party
 * patents.
 *
 * Commercial implementations of MPEG-1 and MPEG-2 video, including shareware,
 * are subject to royalty fees to patent holders.  Many of these patents are
 * general enough such that they are unavoidable regardless of implementation
 * design.
 *
 */

#include "config.h"

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

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "mpeg-config.h"
#include "global.h"

/* private prototypes*/
static void Read_Frame _ANSI_ARGS_((struct mpeg2obj *m, const char *filename, 
  unsigned char *frame_buffer[], int framenum));
static void Copy_Frame _ANSI_ARGS_((struct mpeg2obj *m, unsigned char *src, unsigned char *dst, 
  int width, int height, int parity, int incr));
static int Read_Components _ANSI_ARGS_ ((struct mpeg2obj *m, const char *filename, 
  unsigned char *frame[3], int framenum));
static int Read_Component _ANSI_ARGS_ ((struct mpeg2obj *m, const char *fname, unsigned char *frame, 
  int width, int height));
static int Extract_Components _ANSI_ARGS_ ((struct mpeg2obj *m, const char *filename,
  unsigned char *frame[3], int framenum));


/* substitute frame buffer routine */
void _mpeg2Substitute_Frame_Buffer (m,bitstream_framenum, sequence_framenum)
  struct mpeg2obj *m;
int bitstream_framenum;
int sequence_framenum;
{
  /* static tracking variables */
  static int previous_temporal_reference;
  static int previous_bitstream_framenum;
  static int previous_anchor_temporal_reference;
  static int previous_anchor_bitstream_framenum;
  static int previous_picture_coding_type;
  static int bgate;
  
  /* local temporary variables */
  int substitute_display_framenum;


#ifdef DEBUG
  printf("SUB: seq fn(%d) bitfn(%d) tempref(%d) picstr(%d) type(%d)\n", 
    sequence_framenum, bitstream_framenum, temporal_reference, 
    picture_structure, picture_coding_type);
#endif

  /* we don't substitute at the first picture of a sequence */
  if((sequence_framenum!=0)||(m->Second_Field))
  {
    /* only at the start of the frame */
    if ((m->picture_structure==FRAME_PICTURE)||(!m->Second_Field))
    {
      if(m->picture_coding_type==P_TYPE)
      {
        /* the most recently decoded reference frame needs substituting */
        substitute_display_framenum = bitstream_framenum - 1;
        
        Read_Frame(m,m->Substitute_Picture_Filename, m->forward_reference_frame, 
          substitute_display_framenum);
      }
      /* only the first B frame in a consequitve set of B pictures
         loads a substitute backward_reference_frame since all subsequent
         B frames predict from the same reference pictures */
      else if((m->picture_coding_type==B_TYPE)&&(bgate!=1))
      {
        substitute_display_framenum = 
          (previous_temporal_reference - m->temporal_reference) 
            + bitstream_framenum - 1;

        Read_Frame(m,m->Substitute_Picture_Filename, m->backward_reference_frame, 
          substitute_display_framenum);
      }
    } /* P fields can predict from the two most recently decoded fields, even
         from the first field of the same frame being decoded */
    else if(m->Second_Field && (m->picture_coding_type==P_TYPE))
    {
      /* our favourite case: the IP field picture pair */
      if((previous_picture_coding_type==I_TYPE)&&(m->picture_coding_type==P_TYPE))
      {
        substitute_display_framenum = bitstream_framenum;
      }
      else /* our more generic P field picture pair */
      {
        substitute_display_framenum = 
          (m->temporal_reference - previous_anchor_temporal_reference) 
            + bitstream_framenum - 1;
      }

      Read_Frame(m,m->Substitute_Picture_Filename, m->current_frame, substitute_display_framenum);
    }
#ifdef DEBUG
    else if((picture_coding_type!=B_TYPE)||(picture_coding_type!=D_TYPE))
    {
      printf("NO SUBS FOR THIS PICTURE\n");
    }
#endif
  }


  /* set b gate so we don't redundantly load next time around */
  if(m->picture_coding_type==B_TYPE)
    bgate = 1;
  else
    bgate = 0;

  /* update general tracking variables */
  if((m->picture_structure==FRAME_PICTURE)||(!m->Second_Field))
  {
    previous_temporal_reference  = m->temporal_reference;
    previous_bitstream_framenum  = bitstream_framenum;
  }
  
  /* update reference frame tracking variables */
  if((m->picture_coding_type!=B_TYPE) && 
    ((m->picture_structure==FRAME_PICTURE)||m->Second_Field))
  {
    previous_anchor_temporal_reference  = m->temporal_reference;
    previous_anchor_bitstream_framenum  = bitstream_framenum;
  }

  previous_picture_coding_type = m->picture_coding_type;

}


/* Note: fields are only read to serve as the same-frame reference for 
   a second field */
static void Read_Frame(m,fname,frame,framenum)
  struct mpeg2obj *m;
  const char *fname;
  unsigned char *frame[];
  int framenum;
{
  int parity;
  int rerr = 0;
  int field_mode;

  if(framenum<0)
    printf("ERROR: framenum (%d) is less than zero\n", framenum);


  if(m->Big_Picture_Flag)
    rerr = Extract_Components(m, fname, m->substitute_frame, framenum);
  else
    rerr = Read_Components(m, fname, m->substitute_frame, framenum);

  if(rerr!=0)
  {
    printf("was unable to substitute frame\n");
  }

  /* now copy to the appropriate buffer */
  /* first field (which we are attempting to substitute) must be
     of opposite field parity to the current one */
  if((m->Second_Field)&&(m->picture_coding_type==P_TYPE))
  {
    parity      = (m->picture_structure==TOP_FIELD ? 1:0);      
    field_mode  = (m->picture_structure==FRAME_PICTURE ? 0:1);
  }
  else
  {
    /* Like frame structued pictures, B pictures only substitute an entire frame 
       since both fields always predict from the same frame (with respect 
       to forward/backwards directions) */
    parity = 0;
    field_mode = 0;
  }


  Copy_Frame(m,m->substitute_frame[0], frame[0], m->Coded_Picture_Width, 
	     m->Coded_Picture_Height, parity, field_mode);
  
  Copy_Frame(m,m->substitute_frame[1], frame[1], m->Chroma_Width, m->Chroma_Height, 
    parity, field_mode);
  
  Copy_Frame(m,m->substitute_frame[2], frame[2], m->Chroma_Width, m->Chroma_Height,
    parity, field_mode);

#ifdef VERBOSE
  if(Verbose_Flag > NO_LAYER)
    printf("substituted %s %d\n",
      (field_mode ? (parity?"bottom field":"bottom field"):"frame"), framenum);
#endif
}




static int Read_Components(m, filename, frame, framenum) 
  struct mpeg2obj *m;
const char *filename;
unsigned char *frame[3];
int framenum;
{
  int err = 0;
  char outname[FILENAME_LENGTH];
  char name[FILENAME_LENGTH];

  sprintf(outname,filename,framenum);


  sprintf(name,"%s.Y",outname);
  err += Read_Component(m, name, frame[0], m->Coded_Picture_Width, 
			m->Coded_Picture_Height);

  sprintf(name,"%s.U",outname);
  err += Read_Component(m, name, frame[1], m->Chroma_Width, m->Chroma_Height);

  sprintf(name,"%s.V",outname);
  err += Read_Component(m,name, frame[2], m->Chroma_Width, m->Chroma_Height);

  return(err);
}


static int Read_Component(m, Filename, Frame, Width, Height)
  struct mpeg2obj *m;
const char *Filename;
unsigned char *Frame;
int Width;
int Height;
{
  int Size;
  int Bytes_Read;
  int Infile;

  Size = Width*Height;

#ifdef DEBUG
  printf("SUBS: reading %s\n", filename);
#endif

  if(!(Infile=open(Filename,O_RDONLY|O_BINARY))<0)
  {
    printf("ERROR: unable to open reference filename (%s)\n", Filename);
	return(-1);
  }

  Bytes_Read = read(Infile, Frame, Size);
  
  if(Bytes_Read!=Size)
  {
    printf("was able to read only %d bytes of %d of file %s\n",
      Bytes_Read, Size, Filename);
  }
 
  close(Infile); 
  return(0);
}


/* optimization: do not open the big file each time. Open once at start
   of decoder, and close at the very last frame */

/* Note: "big" files were used in E-mail exchanges almost exclusively by the 
   MPEG Committee's syntax validation and conformance ad-hoc groups from 
   the year 1993 until 1995 */
static int Extract_Components(m, filename, frame, framenum) 
  struct mpeg2obj *m;
const char *filename;
unsigned char *frame[3];
int framenum;
{
/*  int err = 0; */
  FILE *fd;
  int line;
  int size, offset;


  if (!(fd = fopen(filename,"rb")))
  {
    sprintf(m->Error_Text,"Couldn't open %s\n",filename);
    return(-1);
  }

  /* compute size of each frame (in bytes) */
  size = (m->Coded_Picture_Width*m->Coded_Picture_Height);

  if(m->chroma_format==CHROMA444)
    size = (size * 3);
  else if(m->chroma_format==CHROMA422)
    size = (size * 2);
  else if(m->chroma_format==CHROMA420)
    size = ((size*3)>>1);
  else
    printf("ERROR: chroma_format (%d) not recognized\n", m->chroma_format);


  /* compute distance into "big" file */
  offset = size*framenum;

#ifdef DEBUG
  printf("EXTRACTING: frame(%d) offset(%d), size (%d) from %s\n", 
    framenum, offset, size, filename);
#endif

  /* seek to location in big file where desired frame begins */
  /* note: this offset cannot exceed a few billion bytes due to the */
  /*       obvious limitations of 32-bit integers */
  fseek(fd, offset, 0);

  /* Y  */
  for (line=0; line<m->Coded_Picture_Height; line++)
  {
    fread(frame[0]+(line*m->Coded_Picture_Width),1,m->Coded_Picture_Width,fd);
  }

  /* Cb */
  for (line=0; line<m->Chroma_Height; line++)
  {
    fread(frame[1]+(line*m->Chroma_Width),1,m->Chroma_Width,fd);
  }

  /* Cr */
  for (line=0; line<m->Chroma_Height; line++)
  {
    fread(frame[2]+(line*m->Chroma_Width),1,m->Chroma_Width,fd);
  }


  fclose(fd);
  return(0);
}


static void Copy_Frame(m, src, dst, width, height, parity, field_mode)
  struct mpeg2obj *m;
unsigned char *src;
unsigned char *dst;
int width;
int height;
int parity;        /* field parity (top or bottom) to overwrite */
int field_mode;    /* 0 = frame, 1 = field                      */
{
  int row, col;
  int s, d;
  int incr;

  s = d = 0;

#ifdef DEBUG
  printf("COPYING (w=%d, h=%d, parity=%d, field_mode=%d)\n",
    width,height,parity,field_mode);
#endif /* DEBUG */

  if(field_mode)
  {
    incr = 2;

    if(parity==0)
      s += width;
  }
  else
  {
    incr = 1;
  }

  for(row=0; row<height; row+=incr) 
  {
    for(col=0; col<width; col++)
    {
      dst[d+col] = src[s+col];
    }
    
    d += (width*incr);
    s += (width*incr);
  }

}

