/*===========================================================================*
 * mpeg.c								     *
 *									     *
 *	Procedures to generate the MPEG sequence			     *
 *									     *
 * EXPORTED PROCEDURES:							     *
 *	GetMPEGStream							     *
 *	IncrementTCTime							     *
 *	SetStatFileName							     *
 *	SetGOPSize							     *
 *	PrintStartStats							     *
 *									     *
 *===========================================================================*/

/*
 * Copyright (c) 1995 The Regents of the University of California.
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without written agreement is
 * hereby granted, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 *
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
 * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */

/*  
 *  $Header: /n/picasso/project/mpeg/mpeg_dist/mpeg_encode/RCS/mpeg.c,v 1.24 1995/08/16 18:10:48 smoot Exp $
 *  $Log: mpeg.c,v $
 *  Revision 1.24  1995/08/16 18:10:48  smoot
 *  *** empty log message ***
 *
 *  Revision 1.23  1995/08/07 21:48:08  smoot
 *  stdin bugs fixed
 *
 *  Revision 1.22  1995/06/26 21:49:19  smoot
 *  added new frame ordering (hacks)^H^H^H^H^H code ;-)
 *
 *  Revision 1.21  1995/06/21 18:30:41  smoot
 *  changed time structure to be ANSI
 *  changed file access to be binary (DOS!)
 *  added time to userdata
 *  Added a sleep to remote reads (NFS delay)
 *
 * Revision 1.20  1995/05/02  01:49:21  eyhung
 * prints out true output bit rate and slightly untabified
 *
 * Revision 1.19  1995/05/02  00:45:35  eyhung
 * endstats now contain correct output fbit rate at the specified frame rate
 *
 * Revision 1.18  1995/03/27  23:43:20  smoot
 * killed printing long as int (compiler warning)
 *
 * Revision 1.17  1995/03/27  19:18:54  smoot
 * fixed divide by zero for very quick encodings
 *
 * Revision 1.16  1995/02/02  22:03:37  smoot
 * added types for MIPS
 *
 * Revision 1.15  1995/02/02  07:26:58  eyhung
 * removed unused tempframe
 *
 * Revision 1.14  1995/02/01  05:01:35  eyhung
 * Completed infinite coding-on-the-fly
 *
 * Revision 1.13  1995/02/01  02:34:02  eyhung
 * Added full coding-on-the-fly
 *
 * Revision 1.12  1995/01/31  23:05:14  eyhung
 * Added some stdin stuff
 *
 * Revision 1.11  1995/01/20  00:01:16  eyhung
 * Added output file to PrintEndStats
 *
 * Revision 1.10  1995/01/19  23:08:51  eyhung
 * Changed copyrights
 *
 * Revision 1.9  1995/01/17  18:55:54  smoot
 * added right version number, and error if no frames selected
 *
 * Revision 1.8  1995/01/16  08:12:54  eyhung
 * added realQuiet
 *
 * Revision 1.7  1994/12/07  00:40:36  smoot
 * Added seperate P and B search ranges
 *
 * Revision 1.6  1994/11/28  21:46:45  smoot
 * Added version printing
 *
 * Revision 1.5  1994/11/19  01:33:05  smoot
 * put in userdata
 *
 * Revision 1.4  1994/11/14  22:36:22  smoot
 * Merged specifics and rate control
 *
 * Revision 1.2  1994/03/15  00:27:11  keving
 * nothing
 *
 * Revision 1.1  1993/12/22  19:19:01  keving
 * nothing
 *
 * Revision 1.6  1993/07/22  22:23:43  keving
 * nothing
 *
 * Revision 1.5  1993/06/30  20:06:09  keving
 * nothing
 *
 * Revision 1.4  1993/06/03  21:08:08  keving
 * nothing
 *
 * Revision 1.3  1993/02/19  18:10:12  keving
 * nothing
 *
 * Revision 1.2  1993/02/17  23:18:20  dwallach
 * checkin prior to keving's joining the project
 *
 */


/*==============*
 * HEADER FILES *
 *==============*/

#include <string.h>
#include "all.h"
#include <time.h>
#include <errno.h>
//#include <unistd.h>
#include "mtypes.h"
#include "frames.h"
#include "search.h"
#include "mpeg.h"
#include "prototypes.h"
#include "parallel.h"
#include "param.h"
#include "readframe.h"
#include "fsize.h"
#include "mheaders.h"
#include "rate.h"
#ifdef MIPS
#include <sys/types.h>
#endif
#include <sys/stat.h>

/*===========*
 *  VERSION  *
 *===========*/

#define VERSION "1.5b"


/*===========*
 * CONSTANTS *
 *===========*/

#define	FPS_30	0x5   /* from MPEG standard sect. 2.4.3.2 */
#define ASPECT_1    0x1	/* aspect ratio, from MPEG standard sect. 2.4.3.2 */


/*==================*
 * STATIC VARIABLES *
 *==================*/

static int32   diffTime;
static int framesOutput;
static int	    realStart, realEnd;
static int	currentGOP;
static int	    timeMask;
static int	    numI, numP, numB;


/*==================*
 * GLOBAL VARIABLES *	
 *==================*/

/* important -- don't initialize anything here */
/* must be re-initted anyway in GenMPEGStream */

extern time_t  IOtime;
extern boolean	resizeFrame;
extern int outputWidth, outputHeight;
int	    gopSize = 100;  /* default */
int32	    tc_hrs, tc_min, tc_sec, tc_pict, tc_extra;
int	    totalFramesSent;
int	    yuvWidth, yuvHeight;
int	    realWidth, realHeight;
char	    currentPath[MAXPATHLEN];
char	    statFileName[256];
char	    bitRateFileName[256];
time_t	    timeStart, timeEnd;
FILE	   *statFile;
FILE	   *bitRateFile = NULL;
char	   *framePattern;
int	    framePatternLen;
int	    referenceFrame;
static int  framesRead;
MpegFrame  *pastRefFrame;
MpegFrame  *futureRefFrame;
int	    frameRate = FPS_30;
int	    frameRateRounded = 30;
boolean	    frameRateInteger = TRUE;
int	    aspectRatio = ASPECT_1;
extern unsigned char userDataFileName[];
extern int mult_seq_headers;

int32 bit_rate, buf_size;

/*===============================*
 * INTERNAL PROCEDURE prototypes *
 *===============================*/

static void	ShowRemainingTime _ANSI_ARGS_((void));
static void	ComputeDHMSTime _ANSI_ARGS_((int32 someTime, char *timeText));
static void	ComputeGOPFrames _ANSI_ARGS_((int whichGOP, int *firstFrame,
					      int *lastFrame, int numFrames));
static void	PrintEndStats _ANSI_ARGS_((int inputFrameBits, int32 totalBits));
static void	ProcessRefFrame _ANSI_ARGS_((MpegFrame *frame,
					      BitBucket *bb, int lastFrame,
					      char *outputFileName));
static void	OpenBitRateFile _ANSI_ARGS_((void));
static void	CloseBitRateFile _ANSI_ARGS_((void));


/*=====================*
 * EXPORTED PROCEDURES *
 *=====================*/

/*===========================================================================*
 *
 * SetReferenceFrameType
 *
 *	set the reference frame type to be original or decoded
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    referenceFrame
 *
 *===========================================================================*/
void
SetReferenceFrameType(char *type)
{
    if ( strcmp(type, "ORIGINAL") == 0 ) {
	referenceFrame = ORIGINAL_FRAME;
    } else if ( strcmp(type, "DECODED") == 0 ) {
	referenceFrame = DECODED_FRAME;
    } else {
      throw "Illegal reference frame type";
    }
}

void
SetBitRateFileName(char *fileName)
{
    strcpy(bitRateFileName, fileName);
}


/*===========================================================================*
 *
 * GenMPEGStream
 *
 *	generate an MPEG sequence stream (generally)
 *	if whichGOP == frameStart == -1 then does complete MPEG sequence
 *	if whichGOP != -1 then does numbered GOP only (without sequence
 *			       header)
 *	if frameStart != -1 then does numbered frames only (without any
 *				 sequence or GOP headers)		       
 *
 * RETURNS:	amount of time it took
 *
 * SIDE EFFECTS:    too numerous to mention
 *
 *===========================================================================*/
int32
GenMPEGStream(int whichGOP,
              int frameStart,
              int frameEnd,
              int32   qtable[],
              int32   niqtable[],
              int numFrames,
              FILE *ofp,
              char *outputFileName)
{
    extern void        PrintItoIBitRate _ANSI_ARGS_((int numBits, int frameNum));
    BitBucket *bb;
    int i;
    char frameType;
    MpegFrame        *frame = NULL;
    MpegFrame *tempFrame;
    int            firstFrame, lastFrame;
    int     inputFrameBits = 0;
    char    inputFileName[1024];
    time_t  tempTimeStart, tempTimeEnd;
    boolean firstFrameDone = FALSE;
    int numBits;
    int32 bitstreamMode, res;

    if ( (whichGOP == -1) && (frameStart == -1) &&
         (! stdinUsed) && (FType_Type(numFrames-1) == 'b') ) {
        fprintf(stderr, "\n");
        fprintf(stderr, "WARNING:  One or more B-frames at end will not be encoded.\n");
        fprintf(stderr, "          See FORCE_ENCODE_LAST_FRAME option in man page.\n");
        fprintf(stderr, "\n");
    }

    time(&timeStart);

    framesRead = 0;

    ResetIFrameStats();
    ResetPFrameStats();
    ResetBFrameStats();

    Fsize_Reset();

    framesOutput = 0;

    if ( childProcess && separateConversion ) {
        SetFileType(slaveConversion);
    } else {
        SetFileType(inputConversion);
    }

    if ( whichGOP != -1 ) {
        ComputeGOPFrames(whichGOP, &firstFrame, &lastFrame, numFrames);

        realStart = firstFrame;
        realEnd = lastFrame;

        if ( FType_Type(firstFrame) == 'b' ) {

            /* can't find the previous frame interactively */
            if ( stdinUsed ) {
                throw "Cannot encode GOP from stdin when first frame is a B-frame";
            }

            /* need to load in previous frame; call it an I frame */
            frame = Frame_New(firstFrame-1, 'i');

            time(&tempTimeStart);

            if ( (referenceFrame == DECODED_FRAME) &&
                 childProcess ) {
                WaitForDecodedFrame(firstFrame);

                if ( remoteIO ) {
                    GetRemoteDecodedRefFrame(frame, firstFrame-1);
                } else {
                    ReadDecodedRefFrame(frame, firstFrame-1);
                }
            } else {
                if ( remoteIO ) {
                    GetRemoteFrame(frame, firstFrame-1);
                } else {
                    GetNthInputFileName(inputFileName, firstFrame-1);

                    if ( childProcess && separateConversion ) {
                        ReadFrame(frame, inputFileName, slaveConversion, TRUE);
                    } else {
                        ReadFrame(frame, inputFileName, inputConversion, TRUE);
                    }
                }
            }

            framesRead++;

            time(&tempTimeEnd);
            IOtime += (tempTimeEnd-tempTimeStart);
        }
    } else if ( frameStart != -1 ) {
        if ( frameEnd > numFrames-1 ) {
            throw "Specified last frame is out of bounds";
        }

        realStart = frameStart;
        realEnd = frameEnd;

        firstFrame = frameStart;
        lastFrame = frameEnd;

        /* if first frame is P or B, need to read in P or I frame before it */
        if ( FType_Type(firstFrame) != 'i' ) {

            /* can't find the previous frame interactively */
            if ( stdinUsed ) {
                throw "Cannot encode frames from stdin when first frame is not an I-frame";
            }

            firstFrame = FType_PastRef(firstFrame);
        }

        /* if last frame is B, need to read in P or I frame after it */
        if ( (FType_Type(lastFrame) == 'b') && (lastFrame != numFrames-1) ) {

            /* can't find the next reference frame interactively */
            if ( stdinUsed ) {
                throw "Cannot encode frames from stdin when last frame is a B-frame";
            }

            lastFrame = FType_FutureRef(lastFrame);
        }

        if ( lastFrame > numFrames-1 ) {            /* can't go last frame! */
            lastFrame = numFrames-1;
        }

    } else {
        firstFrame = 0;
        lastFrame = numFrames-1;

        realStart = 0;
        realEnd = numFrames-1;
        if ( numFrames == 0 )  {
            throw "No frames selected!";
        }
    }

    /* count number of I, P, and B frames */
    numI = 0;        numP = 0;   numB = 0;
    timeMask = 0;
    if (stdinUsed) {
      numI = numP = numB = MAXINT/4;
    } else {
      for ( i = firstFrame; i <= lastFrame; i++ ) {
        frameType = FType_Type(i);
        switch(frameType) {
	case 'i':        numI++;            timeMask |= 0x1;    break;
	case 'p':        numP++;            timeMask |= 0x2;        break;
	case 'b':        numB++;            timeMask |= 0x4;        break;
        }
      }
    }

    if ( ! childProcess ) {
        if ( showBitRatePerFrame )
            OpenBitRateFile();
        PrintStartStats(realStart, realEnd);
    }

    if ( frameStart == -1 ) {
        bb = Bitio_New(ofp);
    } else {
        bb = NULL;
    }

    tc_hrs = 0;        tc_min = 0; tc_sec = 0; tc_pict = 0; tc_extra = 0;
    for ( i = 0; i < firstFrame; i++ ) {
        IncrementTCTime();
    }

    totalFramesSent = firstFrame;
    currentGOP = gopSize;        /* so first I-frame generates GOP Header */

    /* Rate Control Initialization  */
    bitstreamMode = getRateMode();
    if (bitstreamMode == FIXED_RATE) {
      res = initRateControl();
      /*
        SetFrameRate();
        */
         }
    
#ifdef BLEAH
fprintf(stdout, "firstFrame, lastFrame = %d, %d;  real = %d, %d\n",
        firstFrame, lastFrame, realStart, realEnd);
fflush(stdout);
#endif

    pastRefFrame = NULL;
    futureRefFrame = NULL;
    for ( i = firstFrame; i <= lastFrame; i++) {

        /* break out of the near-infinite loop if input from stdin is done */
#if 0 
      char eofcheck[1];
      if ( stdinUsed ) {
	if (scanf("%c", eofcheck) != EOF) {
	  ungetc(eofcheck[0], stdin);
	} else {
	  break;
	}
      }
#else
	/*
	 ** For some reason the above version of this stdin EOF check does not
	 ** work right with jpeg files, the ungetc() is not padding anything to
	 ** stdin, I have no idea why (perhaps because a char is passed instead
	 ** of an int?), and it drove me nuts, so I wrote my own, slightly
	 ** cleaner version, and this one seems to work.
	 ** Dave Scott (dhs), UofO, 7/19/95.
	 */
	if ( stdinUsed) {
	  int eofcheck_;
	  eofcheck_ = fgetc(stdin);
	  if ( eofcheck_ == EOF) 
	    break;
	  else 
	    ungetc(eofcheck_, stdin);
	} 
#endif
        frameType = FType_Type(i);
	
        time(&tempTimeStart);

        /* skip non-reference frames if non-interactive
         * read in non-reference frames if interactive */
        if ( frameType == 'b' ) {
          if ( stdinUsed ) {
            frame = Frame_New(i, frameType);
            ReadFrame(frame, (char*)"stdin", inputConversion, TRUE);

            framesRead++;

            time(&tempTimeEnd);
            IOtime += (tempTimeEnd-tempTimeStart);

            /* Add the B frame to the end of the queue of B-frames 
             * for later encoding
             */

            if (futureRefFrame != NULL) {
              tempFrame = futureRefFrame;
              while (tempFrame->next != NULL) {
		tempFrame = tempFrame->next;
              }
	      tempFrame->next = frame;
            } else {
	      fprintf(stderr, "Yow, something wrong in neverland! (hit bad code in mpeg.c\n");
	    }
          }
          continue;
        }

        frame = Frame_New(i, frameType);

        pastRefFrame = futureRefFrame;
        futureRefFrame = frame;

        if ( (referenceFrame == DECODED_FRAME) &&
             ((i < realStart) || (i > realEnd)) ) {
            WaitForDecodedFrame(i);

            if ( remoteIO ) {
                GetRemoteDecodedRefFrame(frame, i);
            } else {
                ReadDecodedRefFrame(frame, i);
            }
        } else {
            if ( remoteIO ) {
                GetRemoteFrame(frame, i);
            } else {
                GetNthInputFileName(inputFileName, i);
                if ( childProcess && separateConversion ) {
                    ReadFrame(frame, inputFileName, slaveConversion, TRUE);
                } else {
                    ReadFrame(frame, inputFileName, inputConversion, TRUE);
                }
            }
        }

        framesRead++;

        time(&tempTimeEnd);
        IOtime += (tempTimeEnd-tempTimeStart);

        if ( ! firstFrameDone ) {
          char *userData = (char *)NULL;
          int userDataSize = 0;

          inputFrameBits = 24*Fsize_x*Fsize_y;
          SetBlocksPerSlice();
          
          if ( (whichGOP == -1) && (frameStart == -1) ) {
            DBG_PRINT(("Generating sequence header\n"));
            bitstreamMode = getRateMode();
            if (bitstreamMode == FIXED_RATE) {
              bit_rate = getBitRate();
              buf_size = getBufferSize();
            }
            else {
	          bit_rate = -1;
	          buf_size = -1;
	        }
	    
	    if (strlen((char*)userDataFileName) != 0) {
	      struct stat statbuf;
	      FILE *fp;
	      
	      stat((char*)userDataFileName,&statbuf);
	      userDataSize = statbuf.st_size;
	      userData = (char*)malloc(userDataSize);
	      if ((fp = fopen((char*)userDataFileName,"rb")) == NULL) {
		fprintf(stderr,"Could not open userdata file-%s.\n",
			userDataFileName);
		userData = NULL;
		userDataSize = 0;
		goto write;
	      }
              if ((int)fread(userData,1,userDataSize,fp) != userDataSize) {
            fprintf(stderr,"Could not read %d bytes from userdata file-%s.\n",
                    userDataSize,userDataFileName);
            userData = NULL;
            userDataSize = 0;
            goto write;
          }
        } else { /* Put in our UserData Header */
	  time_t now;

	  time(&now);
          userData = (char*)malloc(100);
          sprintf(userData,"MPEG stream encoded by UCB Encoder (mpeg_encode) v%s on %s.",
		  VERSION, ctime(&now));
          userDataSize = strlen(userData);
        }
          write:
            Mhead_GenSequenceHeader(bb, Fsize_x, Fsize_y,
                                    /* pratio */ aspectRatio,
                                    /* pict_rate */ frameRate, /* bit_rate */ bit_rate,
                                    /* buf_size */ buf_size, /*c_param_flag */ 1,
                                    /* iq_matrix */ qtable, /* niq_matrix */ niqtable,
                                    /* ext_data */ NULL, /* ext_data_size */ 0,
                                    /* user_data */ (uint8*)userData, /* user_data_size */ userDataSize);
          }
          
          firstFrameDone = TRUE;
        }
        
        ProcessRefFrame(frame, bb, lastFrame, outputFileName);

    }

    if ( frame != NULL ) {
        Frame_Free(frame);
    }

    /* SEQUENCE END CODE */
    if ( (whichGOP == -1) && (frameStart == -1) ) {
        Mhead_GenSequenceEnder(bb);
    }

    if ( frameStart == -1 ) {
      /* I think this is right, since (bb == NULL) if (frameStart != -1).
         See above where "bb" is initialized  */
      numBits = bb->cumulativeBits;
    } else {
      /* What should the correct value be?  Most likely 1.  "numBits" is
         used below, so we need to make sure it's properly initialized 
       to somthing (anything).  */
      numBits = 1;
    }

    if ( frameStart == -1 ) {
        Bitio_Flush(bb);
        bb = NULL;
        fclose(ofp);

        time(&timeEnd);
        diffTime = (int32)(timeEnd-timeStart);

        if ( ! childProcess ) {
            PrintEndStats(inputFrameBits, numBits);
        }
    } else {
        time(&timeEnd);
        diffTime = (int32)(timeEnd-timeStart);

        if ( ! childProcess ) {
            PrintEndStats(inputFrameBits, 1);
        }
    }

    if ( FType_Type(realEnd) != 'i' ) {
        PrintItoIBitRate(numBits, realEnd+1);
    }

    if ( (! childProcess) && showBitRatePerFrame )
        CloseBitRateFile();

#ifdef BLEAH
    if ( childProcess ) {
        NoteFrameDone(frameStart, frameEnd);
    }
#endif

    if (! realQuiet) {
        fprintf(stdout, "======FRAMES READ:  %d\n", framesRead);
        fflush(stdout);
    }

    return diffTime;
}


/*===========================================================================*
 *
 * IncrementTCTime
 *
 *	increment the tc time by one second (and update min, hrs if necessary)
 *	also increments totalFramesSent
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    totalFramesSent, tc_pict, tc_sec, tc_min, tc_hrs, tc_extra
 *
 *===========================================================================*/
void
IncrementTCTime()
{
    /* if fps = an integer, then tc_extra = 0 and is ignored

       otherwise, it is the number of extra 1/1001 frames we've passed by

       so far; for example, if fps = 24000/1001, then 24 frames = 24024/24000
       seconds = 1 second + 24/24000 seconds = 1 + 1/1000 seconds; similary,
       if fps = 30000/1001, then 30 frames = 30030/30000 = 1 + 1/1000 seconds
       and if fps = 60000/1001, then 60 frames = 1 + 1/1000 seconds

       if fps = 24000/1001, then 1/1000 seconds = 24/1001 frames
       if fps = 30000/1001, then 1/1000 seconds = 30/1001 frames
       if fps = 60000/1001, then 1/1000 seconds = 60/1001 frames     
     */

    totalFramesSent++;
    tc_pict++;
    if ( tc_pict >= frameRateRounded ) {
	tc_pict = 0;
	tc_sec++;
	if ( tc_sec == 60 ) {
	    tc_sec = 0;
	    tc_min++;
	    if ( tc_min == 60 ) {
		tc_min = 0;
		tc_hrs++;
	    }
	}
	if ( ! frameRateInteger ) {
	    tc_extra += frameRateRounded;
	    if ( tc_extra >= 1001 ) {	/* a frame's worth */
		tc_pict++;
		tc_extra -= 1001;
	    }
	}
    }
}


/*===========================================================================*
 *
 * SetStatFileName
 *
 *	set the statistics file name
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    statFileName
 *
 *===========================================================================*/
void
SetStatFileName(char *fileName)
{
    strcpy(statFileName, fileName);
}


/*===========================================================================*
 *
 * SetGOPSize
 *
 *	set the GOP size (frames per GOP)
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    gopSize
 *
 *===========================================================================*/
void
SetGOPSize(int size)
{
    gopSize = size;
}


/*===========================================================================*
 *
 * PrintStartStats
 *
 *	print out the starting statistics (stuff from the param file)
 *	firstFrame, lastFrame represent the first, last frames to be
 *	encoded
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
void
PrintStartStats(int firstFrame,
                int lastFrame)
{
    FILE *fpointer;
    int i;
    char    inputFileName[1024];

    if ( statFileName[0] == '\0' ) {
	statFile = NULL;
    } else {
	statFile = fopen(statFileName, "a");	/* open for appending */
	if ( statFile == NULL ) {
	    fprintf(stderr, "ERROR:  Could not open stat file:  %s\n", statFileName);
	    fprintf(stderr, "        Sending statistics to stdout only.\n");
	    fprintf(stderr, "\n\n");
	} else if (! realQuiet) {
	    fprintf(stdout, "Appending statistics to file:  %s\n", statFileName);
	    fprintf(stdout, "\n\n");
	}
    }
	
    for ( i = 0; i < 2; i++ ) {
	if ( ( i == 0 ) && (! realQuiet) ) {
	    fpointer = stdout;
	} else if ( statFile != NULL ) {
	    fpointer = statFile;
	} else {
	    continue;
	}

	fprintf(fpointer, "MPEG ENCODER STATS (%s)\n",VERSION);
	fprintf(fpointer, "------------------------\n");
	fprintf(fpointer, "TIME STARTED:  %s", ctime(&timeStart));
	if ( getenv("HOST") != NULL ) {
	    fprintf(fpointer, "MACHINE:  %s\n", getenv("HOST"));
	} else {
	    fprintf(fpointer, "MACHINE:  unknown\n");
	}

	if ( stdinUsed ) {
	    fprintf(fpointer, "INPUT:  stdin\n");
	  }

	
	if ( firstFrame == -1 ) {
	    fprintf(fpointer, "OUTPUT:  %s\n", outputFileName);
	} else if ( ! stdinUsed ) {
	    GetNthInputFileName(inputFileName, firstFrame);
	    fprintf(fpointer, "FIRST FILE:  %s/%s\n", currentPath, inputFileName);
	    GetNthInputFileName(inputFileName, lastFrame);
	    fprintf(fpointer, "LAST FILE:  %s/%s\n", currentPath,
		    inputFileName);
	}
	if ( resizeFrame )
	    fprintf(fpointer, "RESIZED TO:  %dx%d\n",
		    outputWidth, outputHeight);
	fprintf(fpointer, "PATTERN:  %s\n", framePattern);
	fprintf(fpointer, "GOP_SIZE:  %d\n", gopSize);
	fprintf(fpointer, "SLICES PER FRAME:  %d\n", slicesPerFrame);
	if (searchRangeP==searchRangeB)
	  fprintf(fpointer, "RANGE:  +/-%d\n", searchRangeP/2);
	else fprintf(fpointer, "RANGES:  +/-%d %d\n", 
		     searchRangeP/2,searchRangeB/2);
	fprintf(fpointer, "PIXEL SEARCH:  %s\n", pixelFullSearch ? "FULL" : "HALF");
	fprintf(fpointer, "PSEARCH:  %s\n", PSearchName());
	fprintf(fpointer, "BSEARCH:  %s\n", BSearchName());
	fprintf(fpointer, "QSCALE:  %d %d %d\n", qscaleI, 
		GetPQScale(), GetBQScale());
	if (specificsOn) 
	  fprintf(fpointer, "(Except as modified by Specifics file)\n");
	if ( referenceFrame == DECODED_FRAME ) {
	    fprintf(fpointer, "REFERENCE FRAME:  DECODED\n");
	} else if ( referenceFrame == ORIGINAL_FRAME ) {
	    fprintf(fpointer, "REFERENCE FRAME:  ORIGINAL\n");
	} else {
	    throw "Illegal referenceFrame!!!";
	}
 	/*	For new Rate control parameters */
 	if (getRateMode() == FIXED_RATE) {
	  fprintf(fpointer, "PICTURE RATE:  %d\n", frameRateRounded);
	  if (getBitRate() != -1) {
	    fprintf(fpointer, "\nBIT RATE:  %d\n", getBitRate());
	  }
	  if (getBufferSize() != -1) {
	    fprintf(fpointer, "BUFFER SIZE:  %d\n", getBufferSize());
	  }
	}
      }
    if (! realQuiet) {
    	fprintf(stdout, "\n\n");
    }	
}


/*===========================================================================*
 *
 * NonLocalRefFrame
 *
 *	decides if this frame can be referenced from a non-local process
 *
 * RETURNS:	TRUE or FALSE
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
boolean
NonLocalRefFrame(int id)
{
    int	    lastIPid;
    int	    nextIPid;

    if ( ! childProcess ) {
	return FALSE;
    }

    lastIPid = FType_PastRef(id);

    /* might be accessed by B-frame */
    if ( lastIPid+1 < realStart ) {
	return TRUE;
    }

    /* if B-frame is out of range, then current frame can be ref'd by it */
    nextIPid = FType_FutureRef(id);

    /* might be accessed by B-frame */
    if ( nextIPid-1 > realEnd ) {
	return TRUE;
    }

    /* might be accessed by P-frame */
    if ( (nextIPid > realEnd) && (FType_Type(nextIPid) == 'p') ) {
	return TRUE;
    }

    return FALSE;
}


 
/*===========================================================================*
 *
 * SetFrameRate
 *
 *	sets global frame rate variables.  value passed is MPEG frame rate code.
 *
 * RETURNS:	TRUE or FALSE
 *
 * SIDE EFFECTS:    frameRateRounded, frameRateInteger
 *
 *===========================================================================*/
void
SetFrameRate()
{
    switch(frameRate) {
	case 1:
	    frameRateRounded = 24;
	    frameRateInteger = FALSE;
	    break;
	case 2:
	    frameRateRounded = 24;
	    frameRateInteger = TRUE;
	    break;
	case 3:
	    frameRateRounded = 25;
	    frameRateInteger = TRUE;
	    break;
	case 4:
	    frameRateRounded = 30;
	    frameRateInteger = FALSE;
	    break;
	case 5:
	    frameRateRounded = 30;
	    frameRateInteger = TRUE;
	    break;
	case 6:
	    frameRateRounded = 50;
	    frameRateInteger = TRUE;
	    break;
	case 7:
	    frameRateRounded = 60;
	    frameRateInteger = FALSE;
	    break;
	case 8:
	    frameRateRounded = 60;
	    frameRateInteger = TRUE;
	    break;
    }
	printf("frame rate(%d) set to %d\n", frameRate, frameRateRounded);
}


/*=====================*
 * INTERNAL PROCEDURES *
 *=====================*/

/*===========================================================================*
 *
 * ComputeDHMSTime
 *
 *	turn some number of seconds (someTime) into a string which
 *	summarizes that time according to scale (days, hours, minutes, or
 *	seconds)
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
static void
ComputeDHMSTime(int32 someTime,
                char *timeText)
{
    int	    days, hours, mins, secs;

    days = someTime / (24*60*60);
    someTime -= days*24*60*60;
    hours = someTime / (60*60);
    someTime -= hours*60*60;
    mins = someTime / 60;
    secs = someTime - mins*60;

    if ( days > 0 ) {
        sprintf(timeText, "Total time:  %d days and %d hours", days, hours);
    } else if ( hours > 0 ) {
        sprintf(timeText, "Total time:  %d hours and %d minutes", hours, mins);
    } else if ( mins > 0 ) {
        sprintf(timeText, "Total time:  %d minutes and %d seconds", mins, secs);
    } else {
	sprintf(timeText, "Total time:  %d seconds", secs);
    }
}


/*===========================================================================*
 *
 * ComputeGOPFrames
 *
 *	calculate the first, last frames of the numbered GOP
 *
 * RETURNS:	lastFrame, firstFrame changed
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
static void
ComputeGOPFrames(int whichGOP,
                 int *firstFrame,
                 int *lastFrame,
                 int numFrames)
{
    int	    passedB;
    int	    currGOP;
    int	    gopNum, frameNum;

    /* calculate first, last frames of whichGOP GOP */

    *firstFrame = -1;
    *lastFrame = -1;
    gopNum = 0;
    frameNum = 0;
    passedB = 0;
    currGOP = 0;
    while ( *lastFrame == -1 ) {
	if ( frameNum >= numFrames ) {
	    throw "There aren't that many GOPs!";
	}

#ifdef BLEAH
if (! realQuiet) {
fprintf(stdout, "GOP STARTS AT %d\n", frameNum-passedB);
}
#endif

	if ( gopNum == whichGOP ) {
	    *firstFrame = frameNum;
	}

	/* go past one gop */
	/* must go past at least one frame */
	do {
	    currGOP += (1 + passedB);

	    frameNum++;

	    passedB = 0;
	    while ( (frameNum < numFrames) && (FType_Type(frameNum) == 'b') ) {
		frameNum++;
		passedB++;
	    }
	} while ( (frameNum < numFrames) && 
		  ((FType_Type(frameNum) != 'i') || (currGOP < gopSize)) );

	currGOP -= gopSize;

	if ( gopNum == whichGOP ) {
	    *lastFrame = (frameNum-passedB-1);
	}

#ifdef BLEAH
if (! realQuiet) {
fprintf(stdout, "GOP ENDS at %d\n", frameNum-passedB-1);
}
#endif

	gopNum++;
    }
}


/*===========================================================================*
 *
 * PrintEndStats
 *
 *	print end statistics (summary, time information)
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
static void
PrintEndStats(int inputFrameBits,
              int32 totalBits)
{
    FILE *fpointer;
    int i;
    char    timeText[256];
    float   totalCPU;

    if (! realQuiet) {
    	fprintf(stdout, "\n\n");
    }

    ComputeDHMSTime(diffTime, timeText);

    for ( i = 0; i < 2; i++ ) {
	if ( ( i == 0 ) && (! realQuiet) ) {
	    fpointer = stdout;
	} else if ( statFile != NULL ) {
	    fpointer = statFile;
	} else {
	    continue;
	}

	fprintf(fpointer, "TIME COMPLETED:  %s", ctime(&timeEnd));
	fprintf(fpointer, "%s\n\n", timeText);

	totalCPU = 0.0;
	totalCPU += ShowIFrameSummary(inputFrameBits, totalBits, fpointer);
	totalCPU += ShowPFrameSummary(inputFrameBits, totalBits, fpointer);
	totalCPU += ShowBFrameSummary(inputFrameBits, totalBits, fpointer);
	fprintf(fpointer, "---------------------------------------------\n");
	fprintf(fpointer, "Total Compression:  %3d:1     (%9.4f bpp)\n",
		framesOutput*inputFrameBits/totalBits,
		24.0*(float)(totalBits)/(float)(framesOutput*inputFrameBits));
	if (diffTime > 0) {
	  fprintf(fpointer, "Total Frames Per Second:  %f (%ld mps)\n",
		  (float)framesOutput/(float)diffTime,
		  (long)((float)framesOutput*(float)inputFrameBits/(256.0*24.0*(float)diffTime)));
	} else {
	  fprintf(fpointer, "Total Frames Per Second:  Infinite!\n");
	}
	if ( totalCPU == 0.0 ) {
	    fprintf(fpointer, "CPU Time:  NONE!\n");
	} else {
	    fprintf(fpointer, "CPU Time:  %f fps     (%ld mps)\n",
		    (float)framesOutput/totalCPU,
		     (long)((float)framesOutput*(float)inputFrameBits/(256.0*24.0*totalCPU)));
	}
	fprintf(fpointer, "Total Output Bit Rate (%d fps):  %d bits/sec\n",
		frameRateRounded, frameRateRounded*totalBits/framesOutput);
	fprintf(fpointer, "MPEG file created in :  %s\n", outputFileName);
	fprintf(fpointer, "\n\n");

	if ( computeMVHist ) {
	    ShowPMVHistogram(fpointer);
	    ShowBBMVHistogram(fpointer);
	    ShowBFMVHistogram(fpointer);
	}
    }

    if ( statFile != NULL ) {
        fclose(statFile);
    }
}


/*===========================================================================*
 *
 * ProcessRefFrame
 *
 *	process an I or P frame -- encode it, and process any B frames that
 *	we can now
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    stuff appended to bb
 *
 *===========================================================================*/
static void
ProcessRefFrame(MpegFrame *frame,
                BitBucket *bb,
                int lastFrame,
                char *outputFileName)
{
  MpegFrame *bFrame = NULL;
  char    fileName[1024];
  char    inputFileName[1024];
  FILE    *fpointer = NULL;
  boolean separateFiles;
  int	    id;
  time_t  tempTimeStart, tempTimeEnd;
  
  separateFiles = (bb == NULL);
  
  if ( separateFiles && (frame->id >= realStart) &&
      (frame->id <= realEnd) ) {
    if ( remoteIO ) {
      bb = Bitio_New(NULL);
    } else {
      sprintf(fileName, "%s.frame.%d", outputFileName, frame->id);
      if ( (fpointer = fopen(fileName, "wb")) == NULL ) {
	throw "Could not open output file";
      }
      
      bb = Bitio_New(fpointer);
    }
  }
  
  /* nothing to do */
  if ( frame->id < realStart ) {
    return;
  }
  
  /* first, output this frame */
  if ( frame->type == TYPE_IFRAME ) {
      
#ifdef BLEAH
    fprintf(stdout, "I-frame %d, currentGOP = %d\n",
	    frame->id, currentGOP);
    fflush(stdout);
#endif
      
    /* only start a new GOP with I */
    /* don't start GOP if only doing frames */
    if ( (! separateFiles) && (currentGOP >= gopSize) ) {
      int closed;
      static int num_gop = 0;
	
      /* first, check to see if closed GOP */
      if ( totalFramesSent == frame->id || pastRefFrame == NULL) {
	closed = 1;
      } else {
	closed = 0;
      }
	
      /* new GOP */
      if (num_gop != 0 && mult_seq_headers && num_gop % mult_seq_headers == 0) {
	if (! realQuiet) {
	  fprintf(stdout, "Creating new Sequence before GOP %d\n", num_gop);
	  fflush(stdout);
	}
	  
	Mhead_GenSequenceHeader(bb, Fsize_x, Fsize_y,
				/* pratio */    aspectRatio,
				/* pict_rate */ frameRate, /* bit_rate */ bit_rate,
				/* buf_size */  buf_size,  /* c_param_flag */ 1,
				/* iq_matrix */ customQtable, /* niq_matrix */ customNIQtable,
				/* ext_data */ NULL,  /* ext_data_size */ 0,
				/* user_data */ NULL, /* user_data_size */ 0);
      }
	
      if (! realQuiet) {
	fprintf(stdout, "Creating new GOP (closed = %c) before frame %d\n",
		"FT"[closed], frame->id);
	fflush(stdout);
      }
	
      num_gop++;
      Mhead_GenGOPHeader(bb,	/* drop_frame_flag */ 0,
			 tc_hrs, tc_min, tc_sec, tc_pict,
			 closed, /* broken_link */ 0,
			 /* ext_data */ NULL, /* ext_data_size */ 0,
			 /* user_data */ NULL, /* user_data_size */ 0);
      currentGOP -= gopSize;
      if (pastRefFrame == NULL) {
	SetGOPStartTime(0);
      } else {
	SetGOPStartTime(pastRefFrame->id+1);
      }
    }
      
    if ( (frame->id >= realStart) && (frame->id <= realEnd) ) {
      GenIFrame(bb, frame);
	
      framesOutput++;
	
      if ( separateFiles ) {
	if ( remoteIO ) {
	  SendRemoteFrame(frame->id, bb);
	} else {
	  Bitio_Flush(bb);
	  fclose(fpointer);
	}
      }
    }
      
    numI--;
    timeMask &= 0x6;
      
    currentGOP++;
    IncrementTCTime();
  } else {
    if ( (frame->id >= realStart) && (frame->id <= realEnd) ) {
      GenPFrame(bb, frame, pastRefFrame);
	
      framesOutput++;
	
      if ( separateFiles ) {
	if ( remoteIO ) {
	  SendRemoteFrame(frame->id, bb);
	} else {
	  Bitio_Flush(bb);
	  fclose(fpointer);
	}
      }
    }
    
    numP--;
    timeMask &= 0x5;
    ShowRemainingTime();
    
    currentGOP++;
    IncrementTCTime();
  }
  
  /* now, output B-frames */
  if ( pastRefFrame != NULL ) {
    for ( id = pastRefFrame->id+1; id < futureRefFrame->id; id++ ) {
      if ( ! ((id >= realStart) && (id <= realEnd)) )
	continue;
	
      if ( ! stdinUsed ) {
	bFrame = Frame_New(id, 'b');
	  
	time(&tempTimeStart);
	  
	/* read B frame, output it */
	if ( remoteIO ) {
	  GetRemoteFrame(bFrame, bFrame->id);
	} else {
	  GetNthInputFileName(inputFileName, id);
	  if ( childProcess && separateConversion ) {
	    ReadFrame(bFrame, inputFileName, slaveConversion, TRUE);
	  } else {
	    ReadFrame(bFrame, inputFileName, inputConversion, TRUE);
	  }
	}
	  
	time(&tempTimeEnd);
	IOtime += (tempTimeEnd-tempTimeStart);
	  
	framesRead++;
      } else {
	  
	/* retrieve and remove B-frame from queue set up in 
	 * GenMPEGStream 
	 */
	bFrame = pastRefFrame->next;
	pastRefFrame->next = bFrame->next;
      }
	
	
      if ( separateFiles ) {
	if ( remoteIO ) {
	  bb = Bitio_New(NULL);
	} else {
	  sprintf(fileName, "%s.frame.%d", outputFileName, 
		  bFrame->id);
	  if ( (fpointer = fopen(fileName, "wb")) == NULL ) {
	    throw "Could not open output file";
	  }
	  bb = Bitio_New(fpointer);
	}
      }
	
      GenBFrame(bb, bFrame, pastRefFrame, futureRefFrame);
      framesOutput++;
	
      if ( separateFiles ) {
	if ( remoteIO ) {
	  SendRemoteFrame(bFrame->id, bb);
	} else {
	  Bitio_Flush(bb);
	  fclose(fpointer);
	}
      }
	
      /* free this B frame right away */
      Frame_Free(bFrame);
	
      numB--;
      timeMask &= 0x3;
      ShowRemainingTime();
	
      currentGOP++;
      IncrementTCTime();
    }
  } else {
    /* SRS replicated code */
    for ( id = 0; id < futureRefFrame->id; id++ ) {
      if ( ! ((id >= realStart) && (id <= realEnd)) )
	continue;

      if ( ! stdinUsed ) {
	bFrame = Frame_New(id, 'b');

	time(&tempTimeStart);

	/* read B frame, output it */
	if ( remoteIO ) {
	  GetRemoteFrame(bFrame, bFrame->id);
	} else {
	  GetNthInputFileName(inputFileName, id);
	  if ( childProcess && separateConversion ) {
	    ReadFrame(bFrame, inputFileName, slaveConversion, TRUE);
	  } else {
	    ReadFrame(bFrame, inputFileName, inputConversion, TRUE);
	  }
	}

	time(&tempTimeEnd);
	IOtime += (tempTimeEnd-tempTimeStart);

	framesRead++;
      } else {
	    
	/* retrieve and remove B-frame from queue set up in 
	 * GenMPEGStream 
	 */
	printf("Yow, I doubt this works!\n");
	bFrame = pastRefFrame->next;
	pastRefFrame->next = bFrame->next;
      }


      if ( separateFiles ) {
	if ( remoteIO ) {
	  bb = Bitio_New(NULL);
	} else {
	  sprintf(fileName, "%s.frame.%d", outputFileName, 
		  bFrame->id);
	  if ( (fpointer = fopen(fileName, "wb")) == NULL ) {
	    throw "Could not open output file";
	  }
	  bb = Bitio_New(fpointer);
	}
      }

      GenBFrame(bb, bFrame, (MpegFrame *)NULL, futureRefFrame);
      framesOutput++;

      if ( separateFiles ) {
	if ( remoteIO ) {
	  SendRemoteFrame(bFrame->id, bb);
	} else {
	  Bitio_Flush(bb);
	  fclose(fpointer);
	}
      }

      /* free this B frame right away */
      Frame_Free(bFrame);

      numB--;
      timeMask &= 0x3;
      ShowRemainingTime();

      currentGOP++;
      IncrementTCTime();
    }
    
  }
  
  /* now free previous frame, if there was one */
  if ( pastRefFrame != NULL ) {
    Frame_Free(pastRefFrame);
  }
  
  /* note, we may still not free last frame if lastFrame is incorrect
   * (if the last frames are B frames, they aren't output!)
   */
}


/*===========================================================================*
 *
 * ShowRemainingTime
 *
 *	print out an estimate of the time left to encode
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
static void
ShowRemainingTime()
{
    static int	lastTime = 0;
    float   total;
    time_t  nowTime;
    float   secondsPerFrame;

    if ( childProcess ) {
	return /* nothing */;
    }

    if ( numI + numP + numB == 0 ) {	/* no time left */
	return /* nothing */ ;
    }

    if ( timeMask != 0 ) {	    /* haven't encoded all types yet */
	return /* nothing */ ;
    }

    time(&nowTime);
    secondsPerFrame = (nowTime-timeStart)/(float)framesOutput;
    total = secondsPerFrame*(float)(numI+numP+numB);

#ifdef BLEAH
    float   timeI, timeP, timeB;

    timeI = EstimateSecondsPerIFrame();
    timeP = EstimateSecondsPerPFrame();
    timeB = EstimateSecondsPerBFrame();
    total = (float)numI*timeI + (float)numP*timeP + (float)numB*timeB;
#endif

    if ( (quietTime >= 0) && (! realQuiet) && (! stdinUsed) &&
	 ((lastTime < (int)total) || ((lastTime-(int)total) >= quietTime) ||
	  (lastTime == 0) || (quietTime == 0)) ) {
	if ( total > 270.0 ) {
	    fprintf(stdout, "ESTIMATED TIME OF COMPLETION:  %d minutes\n",
		    ((int)total+30)/60);
	} else {
	    fprintf(stdout, "ESTIMATED TIME OF COMPLETION:  %d seconds\n",
		    (int)total);
	}

	lastTime = (int)total;
    }
}


void
ReadDecodedRefFrame(MpegFrame *frame,
                    int frameNumber)
{
    FILE    *fpointer;
    char    fileName[256];
    int	width, height;
    int y;

    width = Fsize_x;
    height = Fsize_y;

    sprintf(fileName, "%s.decoded.%d", outputFileName, frameNumber);
    if (! realQuiet) {
	fprintf(stdout, "reading %s\n", fileName);
	fflush(stdout);
    }

    if ((fpointer = fopen(fileName, "rb")) == NULL) {
      //sleep(1);
      if ((fpointer = fopen(fileName, "rb")) == NULL) {
	throw "Cannot open file";
      }}

    Frame_AllocDecoded(frame, TRUE);
    
    for ( y = 0; y < height; y++ ) {
      if ((int)fread(frame->decoded_y[y], 1, width, fpointer) != width) {
	fprintf(stderr, "Could not read enough bytes from %s\n", fileName);
      }
    }
    
    for (y = 0; y < (height >> 1); y++) {			/* U */
      if ((int)fread(frame->decoded_cb[y], 1, width >> 1, fpointer) != (width>>1)) {
	fprintf(stderr, "Could not read enough bytes from %s\n", fileName);
      }
    }
    
    for (y = 0; y < (height >> 1); y++) {			/* V */
      if ((int)fread(frame->decoded_cr[y], 1, width >> 1, fpointer) != (width>>1)) {
	fprintf(stderr, "Could not read enough bytes from %s\n", fileName);
      }
    }
    
    fclose(fpointer);
}


static void
OpenBitRateFile()
{
    bitRateFile = fopen(bitRateFileName, "w");
    if ( bitRateFile == NULL ) {
	fprintf(stderr, "ERROR:  Could not open bit rate file:  %s\n", bitRateFileName);
	fprintf(stderr, "\n\n");
	showBitRatePerFrame = FALSE;
    }
}


static void
CloseBitRateFile()
{
#ifdef BLEAH
    char command[256];
#endif

    fclose(bitRateFile);
#ifdef BLEAH
    sprintf(command, "sort -n %s > /tmp/fubahr", bitRateFileName);
    system(command);
    sprintf(command, "mv /tmp/fubahr %s", bitRateFileName);
    system(command);
#endif
}
