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

/*
 * Copyright (c) 1993 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/users/keving/encode/src/RCS/mpeg.c,v 1.1 1993/12/22 19:19:01 keving Exp keving $
 *  $Log: mpeg.c,v $
 * 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 "all.h"
#include <time.h>
#include <errno.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"


/*===========*
 * 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 int  IOtime;
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;


/*===============================*
 * 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(type)
    char *type;
{
    if ( strcmp(type, "ORIGINAL") == 0 ) {
	referenceFrame = ORIGINAL_FRAME;
    } else if ( strcmp(type, "DECODED") == 0 ) {
	referenceFrame = DECODED_FRAME;
    } else {
	fprintf(stderr, "ERROR:  Illegal reference frame type: '%s'\n",
		type);
	exit(1);
    }
}

void
SetBitRateFileName(fileName)
    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(whichGOP, frameStart, frameEnd, qtable, niqtable, numFrames,
	      ofp, outputFileName)
    int whichGOP;
    int frameStart;
    int frameEnd;
    int32   qtable[];
    int32   niqtable[];
    int numFrames;
    FILE *ofp;
    char *outputFileName;
{
    extern void	PrintItoIBitRate _ANSI_ARGS_((BitBucket *bb, int frameNum));
    BitBucket *bb;
    int i;
    char frameType;
    MpegFrame	*frame = NULL;
    int	    firstFrame, lastFrame;
    int  inputFrameBits = 0;
    char    inputFileName[1024];
    time_t  tempTimeStart, tempTimeEnd;
    boolean firstFrameDone = FALSE;

    if ( (whichGOP == -1) && (frameStart == -1)  &&
	 (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' ) {
	    /* 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 ) {
	    fprintf(stdout, "ERROR:  Specified last frame is out of bounds\n");
	    exit(1);
	}

	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' ) {
	    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) ) {
	    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;
    }

    /* count number of I, P, and B frames */
    numI = 0;	numP = 0;   numB = 0;
    timeMask = 0;
    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 */

#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++) {
	frameType = FType_Type(i);

	/* skip non-reference frames */
	if ( frameType == 'b' ) {
	    continue;
	}

	frame = Frame_New(i, frameType);

	pastRefFrame = futureRefFrame;
	futureRefFrame = frame;

	time(&tempTimeStart);

	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 ) {
	    inputFrameBits = 24*Fsize_x*Fsize_y;
	    SetBlocksPerSlice();

	    if ( (whichGOP == -1) && (frameStart == -1) ) {
		DBG_PRINT(("Generating sequence header\n"));
		Mhead_GenSequenceHeader(bb, Fsize_x, Fsize_y,
					/* pratio */ aspectRatio,
			   /* pict_rate */ frameRate, /* bit_rate */ -1,
			   /* buf_size */ -1, /*c_param_flag */ 1,
			   /* iq_matrix */ qtable, /* niq_matrix */ niqtable,
			   /* ext_data */ NULL, /* ext_data_size */ 0,
			   /* user_data */ NULL, /* user_data_size */ 0);
	    }

	    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 ) {
	Bitio_Flush(bb);
	fclose(ofp);

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

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

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

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

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

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

    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(fileName)
    char *fileName;
{
    strcpy(statFileName, fileName);
}


/*===========================================================================*
 *
 * SetGOPSize
 *
 *	set the GOP size (frames per GOP)
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    gopSize
 *
 *===========================================================================*/
void
SetGOPSize(size)
    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(firstFrame, lastFrame)
    int firstFrame;
    int lastFrame;
{
    FILE *fpointer;
    register 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 {
	    fprintf(stdout, "Appending statistics to file:  %s\n", statFileName);
	    fprintf(stdout, "\n\n");
	}
    }
	
    for ( i = 0; i < 2; i++ ) {
	if ( i == 0 ) {
	    fpointer = stdout;
	} else if ( statFile != NULL ) {
	    fpointer = statFile;
	} else {
	    continue;
	}

	fprintf(fpointer, "MPEG ENCODER STATS\n");
	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 ( firstFrame == -1 ) {
	    fprintf(fpointer, "OUTPUT:  %s\n", outputFileName);
	} else {
	    GetNthInputFileName(inputFileName, firstFrame);
	    fprintf(fpointer, "FIRST FILE:  %s/%s\n", currentPath, inputFileName);
	    GetNthInputFileName(inputFileName, lastFrame);
	    fprintf(fpointer, "LAST FILE:  %s/%s\n", currentPath,
		    inputFileName);
	}
	fprintf(fpointer, "PATTERN:  %s\n", framePattern);
	fprintf(fpointer, "GOP_SIZE:  %d\n", gopSize);
	fprintf(fpointer, "SLICES PER FRAME:  %d\n", slicesPerFrame);
	fprintf(fpointer, "RANGE:  +/-%d\n", searchRange/2);
	fprintf(fpointer, "FULL SEARCH:  %d\n", pixelFullSearch);
	fprintf(fpointer, "PSEARCH:  %s\n", PSearchName());
	fprintf(fpointer, "BSEARCH:  %s\n", BSearchName());
	fprintf(fpointer, "QSCALE:  %d %d %d\n", qscaleI, 
		GetPQScale(), GetBQScale());
	if ( referenceFrame == DECODED_FRAME ) {
	    fprintf(fpointer, "REFERENCE FRAME:  DECODED\n");
	} else if ( referenceFrame == ORIGINAL_FRAME ) {
	    fprintf(fpointer, "REFERENCE FRAME:  ORIGINAL\n");
	} else {
	    fprintf(stderr, "ERROR:  Illegal referenceFrame!!!\n");
	    exit(1);
	}
    }

    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(id)
    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;
}


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;
    }
}


/*=====================*
 * 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(someTime, timeText)
    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(whichGOP, firstFrame, lastFrame, numFrames)
    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 ) {
	    fprintf(stderr, "ERROR:  There aren't that many GOPs!\n");
	    exit(1);
	}

fprintf(stdout, "GOP STARTS AT %d\n", frameNum-passedB);

	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);
	}

fprintf(stdout, "GOP ENDS at %d\n", frameNum-passedB-1);

	gopNum++;
    }
}


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

    fprintf(stdout, "\n\n");

    ComputeDHMSTime(diffTime, timeText);

    for ( i = 0; i < 2; i++ ) {
	if ( i == 0 ) {
	    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));
	fprintf(fpointer, "Total Frames Per Second:  %f (%d mps)\n",
		(float)framesOutput/(float)diffTime,
    	         (long)((float)framesOutput*(float)inputFrameBits/(256.0*24.0*(float)diffTime)));
	if ( totalCPU == 0.0 ) {
	    fprintf(fpointer, "CPU Time:  NONE!\n");
	} else {
	    fprintf(fpointer, "CPU Time:  %f fps     (%d mps)\n",
		    (float)framesOutput/totalCPU,
		     (long)((float)framesOutput*(float)inputFrameBits/(256.0*24.0*totalCPU)));
	}
	fprintf(fpointer, "Total Output Bit Rate (30 fps):  %d bits/sec\n",
		30*totalBits/framesOutput);
	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(frame, bb, lastFrame, outputFileName)
    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, "w")) == NULL ) {
		fprintf(stderr, "ERROR:  Could not open output file:  %s\n",
			fileName);
		exit(1);
	    }

	    bb = Bitio_New(fpointer);
	}
    }

    /* nothing to do */
    if ( frame->id < realStart ) {
	return;
    }

    /* first, output this frame */
    if ( frame->type == TYPE_IFRAME ) {
	/* only start a new GOP with I */
	/* don't start GOP if only doing frames */

#ifdef BLEAH
fprintf(stdout, "I-frame %d, currentGOP = %d\n",
	frame->id, currentGOP);
fflush(stdout);
#endif

	if ( (! separateFiles) && (currentGOP >= gopSize) ) {
	    int closed;

	    /* first, check to see if closed GOP */
	    if ( totalFramesSent == frame->id ) {
		closed = 1;
	    } else {
		closed = 0;
	    }

	    fprintf(stdout, "Creating new GOP (closed = %d) before frame %d\n",
		    closed, frame->id);
fflush(stdout);

	    /* new 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;
	    SetGOPStartTime(frame->id);
	}

	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;

		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);
		    }
		}

		framesRead++;

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

		if ( separateFiles ) {
		    if ( remoteIO ) {
			bb = Bitio_New(NULL);
		    } else {
			sprintf(fileName, "%s.frame.%d", outputFileName, bFrame->id);
			if ( (fpointer = fopen(fileName, "w")) == NULL ) {
			fprintf(stderr, "ERROR:  Could not open output file:  %s\n",
			    fileName);
		    exit(1);
		}
		    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();
	}
    }

    /* 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) &&
	 ((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(frame, frameNumber)
    MpegFrame *frame;
    int frameNumber;
{
    FILE    *fpointer;
    char    fileName[256];
    int	width, height;
    register int y;

    width = Fsize_x;
    height = Fsize_y;

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

    fpointer = fopen(fileName, "r");

	Frame_AllocDecoded(frame, TRUE);

	for ( y = 0; y < height; y++ ) {
	    fread(frame->decoded_y[y], 1, width, fpointer);
	}

	for (y = 0; y < (height >> 1); y++) {			/* U */
	    fread(frame->decoded_cb[y], 1, width >> 1, fpointer);
	}

	for (y = 0; y < (height >> 1); y++) {			/* V */
	    fread(frame->decoded_cr[y], 1, width >> 1, fpointer);
	}

    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
}
