about summary refs log tree commit diff
path: root/converter/ppm/ppmtompeg/mpeg.c
diff options
context:
space:
mode:
Diffstat (limited to 'converter/ppm/ppmtompeg/mpeg.c')
-rw-r--r--converter/ppm/ppmtompeg/mpeg.c1717
1 files changed, 1717 insertions, 0 deletions
diff --git a/converter/ppm/ppmtompeg/mpeg.c b/converter/ppm/ppmtompeg/mpeg.c
new file mode 100644
index 00000000..a934ed09
--- /dev/null
+++ b/converter/ppm/ppmtompeg/mpeg.c
@@ -0,0 +1,1717 @@
+/*===========================================================================*
+ * mpeg.c
+ *
+ *  Procedures to generate the MPEG sequence
+ *===========================================================================*/
+
+/*
+ * 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 FILES *
+ *==============*/
+
+#define _BSD_SOURCE   /* Make sure strdup() is in string.h */
+
+#include "all.h"
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+#ifdef MIPS
+#include <sys/types.h>
+#endif
+#include <sys/stat.h>
+
+#include "ppm.h"
+#include "nstring.h"
+
+#include "mtypes.h"
+#include "frames.h"
+#include "motion_search.h"
+#include "prototypes.h"
+#include "parallel.h"
+#include "param.h"
+#include "readframe.h"
+#include "fsize.h"
+#include "mheaders.h"
+#include "rate.h"
+#include "input.h"
+#include "frametype.h"
+#include "mpeg.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 unsigned int framesOutput;
+static int      realStart, realEnd;
+static int  currentGOP;
+static int      timeMask;
+static int      numI, numP, numB;
+static boolean  frameCountsUnknown;
+
+
+/*==================*
+ * GLOBAL VARIABLES *   
+ *==================*/
+
+/* important -- don't initialize anything here */
+/* must be re-initted anyway in GenMPEGStream */
+
+extern int  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;
+int     frameRate = FPS_30;
+int     frameRateRounded = 30;
+boolean     frameRateInteger = TRUE;
+int     aspectRatio = ASPECT_1;
+extern char userDataFileName[];
+extern int mult_seq_headers;
+
+int32 bit_rate, buf_size;
+
+/*===============================*
+ * INTERNAL PROCEDURE prototypes *
+ *===============================*/
+
+static void ComputeDHMSTime _ANSI_ARGS_((int32 someTime, char *timeText));
+static void OpenBitRateFile _ANSI_ARGS_((void));
+static void CloseBitRateFile _ANSI_ARGS_((void));
+
+
+static void
+ShowRemainingTime(boolean const childProcess) {
+/*----------------------------------------------------------------------------
+   Print out an estimate of the time left to encode
+-----------------------------------------------------------------------------*/
+
+    if (childProcess) {
+        /* nothing */;
+    } else if ( numI + numP + numB == 0 ) {
+        /* no time left */
+    } else if ( timeMask != 0 ) {   
+        /* haven't encoded all types yet */
+    } else {
+        static int  lastTime = 0;
+        float   total;
+        time_t  nowTime;
+        float   secondsPerFrame;
+        
+        time(&nowTime);
+        secondsPerFrame = (nowTime-timeStart)/(float)framesOutput;
+        total = secondsPerFrame*(float)(numI+numP+numB);
+
+        if ((quietTime >= 0) && (!realQuiet) && (!frameCountsUnknown) &&
+            ((lastTime < (int)total) || ((lastTime-(int)total) >= quietTime) ||
+             (lastTime == 0) || (quietTime == 0))) {
+            if (total > 270.0)
+                pm_message("ESTIMATED TIME OF COMPLETION:  %d minutes",
+                           ((int)total+30)/60);
+            else
+                pm_message("ESTIMATED TIME OF COMPLETION:  %d seconds",
+                           (int)total);
+        }
+        lastTime = (int)total;
+    }
+}
+
+
+
+static void
+initTCTime(unsigned int const firstFrameNumber) {
+
+    unsigned int frameNumber;
+    
+    tc_hrs = 0; tc_min = 0; tc_sec = 0; tc_pict = 0; tc_extra = 0;
+    for (frameNumber = 0; frameNumber < firstFrameNumber; ++frameNumber)
+        IncrementTCTime();
+}
+
+
+
+/*===========================================================================*
+ *
+ * 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;
+            }
+        }
+    }
+}
+
+
+
+static void
+initializeRateControl(bool const wantUnderflowWarning,
+                      bool const wantOverflowWarning) {
+/*----------------------------------------------------------------------------
+   Initialize rate control
+-----------------------------------------------------------------------------*/
+    int32 const bitstreamMode = getRateMode();
+
+    if (bitstreamMode == FIXED_RATE) {
+        initRateControl(wantUnderflowWarning, wantOverflowWarning);
+        /*
+          SetFrameRate();
+        */
+    }
+}
+    
+
+
+/*===========================================================================*
+ *
+ * SetReferenceFrameType
+ *
+ *  set the reference frame type to be original or decoded
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    referenceFrame
+ *
+ *===========================================================================*/
+void
+SetReferenceFrameType(const char * const type) {
+
+    if (strcmp(type, "ORIGINAL") == 0)
+        referenceFrame = ORIGINAL_FRAME;
+    else if ( strcmp(type, "DECODED") == 0 )
+        referenceFrame = DECODED_FRAME;
+    else
+        pm_error("INTERNAL ERROR: Illegal reference frame type: '%s'", type);
+}
+
+
+
+void
+SetBitRateFileName(fileName)
+    char *fileName;
+{
+    strcpy(bitRateFileName, fileName);
+}
+
+
+
+
+static void
+finishFrameOutput(MpegFrame * const frameP,
+                  BitBucket * const bbP,
+                  boolean     const separateFiles,
+                  int         const referenceFrame,
+                  boolean     const childProcess,
+                  boolean     const remoteIO) {
+
+    if ((referenceFrame == DECODED_FRAME) && 
+        childProcess && NonLocalRefFrame(frameP->id)) {
+        if (remoteIO)
+            SendDecodedFrame(frameP);
+        else
+            WriteDecodedFrame(frameP);
+            
+        NotifyDecodeServerReady(frameP->id);
+    }
+    
+    if (separateFiles) {
+        if (remoteIO)
+            SendRemoteFrame(frameP->id, bbP);
+        else {
+            Bitio_Flush(bbP);
+            Bitio_Close(bbP);
+        }
+    }
+}
+
+    
+
+
+static void
+outputIFrame(MpegFrame * const frameP,
+             BitBucket * const bb,
+             int         const realStart,
+             int         const realEnd,
+             MpegFrame * const pastRefFrameP,
+             boolean     const separateFiles) {
+      
+    /* only start a new GOP with I */
+    /* don't start GOP if only doing frames */
+    if ((!separateFiles) && (currentGOP >= gopSize)) {
+        boolean const closed = 
+            (totalFramesSent == frameP->id || pastRefFrameP == NULL);
+
+        static int num_gop = 0;
+    
+        /* first, check to see if closed GOP */
+    
+        /* 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)
+            pm_message("Creating new GOP (closed = %s) before frame %d\n",
+                       closed ? "YES" : "NO", frameP->id);
+    
+        ++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 (pastRefFrameP == NULL)
+            SetGOPStartTime(0);
+        else
+            SetGOPStartTime(pastRefFrameP->id+1);
+    }
+      
+    if ((frameP->id >= realStart) && (frameP->id <= realEnd))
+        GenIFrame(bb, frameP);
+      
+    numI--;
+    timeMask &= 0x6;
+      
+    currentGOP++;
+    IncrementTCTime();
+}
+
+
+
+static void
+outputPFrame(MpegFrame * const frameP,
+             BitBucket * const bbP,
+             int         const realStart,
+             int         const realEnd,
+             MpegFrame * const pastRefFrameP) {
+
+    if ((frameP->id >= realStart) && (frameP->id <= realEnd))
+        GenPFrame(bbP, frameP, pastRefFrameP);
+
+    numP--;
+    timeMask &= 0x5;
+    
+    currentGOP++;
+    IncrementTCTime();
+}
+
+
+
+static BitBucket *
+bitioNew(const char * const outputFileName,
+         unsigned int const frameNumber,
+         boolean      const remoteIO) {
+
+    BitBucket * bbP;
+
+    if (remoteIO)
+        bbP = Bitio_New(NULL);
+    else {
+        const char * fileName;
+
+        asprintfN(&fileName, "%s.frame.%d", outputFileName, frameNumber);
+
+        bbP = Bitio_New_Filename(fileName);
+
+        strfree(fileName);
+    }
+    return bbP;
+}
+
+
+
+static void
+getBFrame(int                  const frameNum,
+          struct inputSource * const inputSourceP,
+          MpegFrame *          const pastRefFrameP,
+          boolean              const childProcess,
+          boolean              const remoteIO,
+          MpegFrame **         const bFramePP,
+          int *                const IOtimeP,
+          unsigned int *       const framesReadP) {
+/*----------------------------------------------------------------------------
+   Get Frame 'frameNum', which is a B frame related to previous reference
+   frame 'pastRefFrameP'.  Return it as *bFramePP.
+
+   We have various ways of getting the frame, corresponding to the
+   multitude of modes in which Ppmtompeg works.
+-----------------------------------------------------------------------------*/
+    if (!inputSourceP->stdinUsed) {
+        time_t tempTimeStart, tempTimeEnd;
+        MpegFrame * bFrameP;
+        bool endOfStream;
+
+        bFrameP = Frame_New(frameNum, 'b');
+
+        time(&tempTimeStart);
+
+        ReadNthFrame(inputSourceP, frameNum, remoteIO, childProcess,
+                     separateConversion, slaveConversion, inputConversion,
+                     bFrameP, &endOfStream);
+
+        assert(!endOfStream);  /* Because it's not a stream */
+
+        time(&tempTimeEnd);
+        *IOtimeP += (tempTimeEnd-tempTimeStart);
+
+        ++(*framesReadP);
+        *bFramePP = bFrameP;
+    } else {
+        /* As the frame input is serial, we can't read the B frame now.
+           Rather, Caller has already read it and chained it to 
+           the previous reference frame.  So we get that copy now.
+        */
+        *bFramePP = pastRefFrameP->next;
+        pastRefFrameP->next = (*bFramePP)->next;  /* unlink from list */
+    }
+}
+
+
+
+static void
+processBFrames(MpegFrame *          const pastRefFrameP,
+               MpegFrame *          const futureRefFrameP,
+               int                  const realStart,
+               int                  const realEnd,
+               struct inputSource * const inputSourceP,
+               boolean              const remoteIo,
+               boolean              const childProcess,
+               int *                const IOtimeP,
+               BitBucket *          const wholeStreamBbP,
+               const char *         const outputFileName,
+               unsigned int *       const framesReadP,
+               unsigned int *       const framesOutputP,
+               int *                const currentGopP) {
+/*----------------------------------------------------------------------------
+   Process the B frames that go between 'pastRefFrameP' and
+   'futureRefFrame' in the movie (but go after 'futureRefFrameP' in the
+   MPEG stream, so reader doesn't have to read ahead).
+
+   Remember that a B frame is one which is described by data in the
+   MPEG stream that describes the frame with respect to a frame somewhere
+   before it, and a frame somewhere after it (i.e. reference frames).
+
+   But do only those B frames whose frame numbers are within the range
+   'realStart' through 'realEnd'.
+-----------------------------------------------------------------------------*/
+    boolean const separateFiles = (wholeStreamBbP == NULL);
+    unsigned int const firstBFrameNum = pastRefFrameP->id + 1;
+
+    int frameNum;
+
+    assert(pastRefFrameP != NULL);
+    assert(futureRefFrameP != NULL);
+    
+    for (frameNum = MAX(realStart, firstBFrameNum); 
+         frameNum < MIN(realEnd, futureRefFrameP->id); 
+         ++frameNum) {
+
+        MpegFrame * bFrame;
+        BitBucket * bbP;
+
+        getBFrame(frameNum, inputSourceP, pastRefFrameP, childProcess, 
+                  remoteIO,
+                  &bFrame, IOtimeP, framesReadP);
+
+        if (separateFiles)
+            bbP = bitioNew(outputFileName, bFrame->id, remoteIO);
+        else
+            bbP = wholeStreamBbP;
+
+        GenBFrame(bbP, bFrame, pastRefFrameP, futureRefFrameP);
+        ++(*framesOutputP);
+
+        if (separateFiles) {
+            if (remoteIO)
+                SendRemoteFrame(bFrame->id, bbP);
+            else {
+                Bitio_Flush(bbP);
+                Bitio_Close(bbP);
+            }
+        }
+
+        /* free this B frame right away */
+        Frame_Free(bFrame);
+
+        numB--;
+        timeMask &= 0x3;
+        ShowRemainingTime(childProcess);
+
+        ++(*currentGopP);
+        IncrementTCTime();
+    }
+}
+
+
+
+static void
+processRefFrame(MpegFrame *    const frameP, 
+                BitBucket *    const bb_arg,
+                int            const realStart,
+                int            const realEnd,
+                MpegFrame *    const pastRefFrameP,
+                boolean        const childProcess,
+                const char *   const outputFileName,
+                unsigned int * const framesReadP,
+                unsigned int * const framesOutputP) {
+/*----------------------------------------------------------------------------
+   Process an I or P frame.  Encode and output it.
+
+   But only if its frame number is within the range 'realStart'
+   through 'realEnd'.
+-----------------------------------------------------------------------------*/
+    if (frameP->id >= realStart && frameP->id <= realEnd) {
+        boolean separateFiles;
+        BitBucket * bb;
+  
+        separateFiles = (bb_arg == NULL);
+  
+        if ( separateFiles )
+            bb = bitioNew(outputFileName, frameP->id, remoteIO);
+        else
+            bb = bb_arg;
+  
+        /* first, output this reference frame */
+        switch (frameP->type) {
+        case TYPE_IFRAME:
+            outputIFrame(frameP, bb, realStart, realEnd, pastRefFrameP, 
+                         separateFiles);
+            break;
+        case TYPE_PFRAME:
+            outputPFrame(frameP, bb, realStart, realEnd, pastRefFrameP);
+            ShowRemainingTime(childProcess);
+            break;
+        default:
+            pm_error("INTERNAL ERROR: non-reference frame passed to "
+                     "ProcessRefFrame()");
+        }  
+        
+        ++(*framesOutputP);
+        
+        finishFrameOutput(frameP, bb, separateFiles, referenceFrame,
+                          childProcess, remoteIO);
+    }
+}
+
+
+
+static void
+countFrames(unsigned int const firstFrame,
+            unsigned int const lastFrame,
+            boolean      const stdinUsed,
+            int *        const numIP,
+            int *        const numPP,
+            int *        const numBP,
+            int *        const timeMaskP,
+            boolean *    const frameCountsUnknownP) {
+/*----------------------------------------------------------------------------
+  Count number of I, P, and B frames
+-----------------------------------------------------------------------------*/
+    unsigned int numI, numP, numB;
+    unsigned int timeMask;
+            
+    numI = 0; numP = 0; numB = 0;
+    timeMask = 0;
+    if (stdinUsed) {
+        numI = numP = numB = MAXINT/4;
+        *frameCountsUnknownP = TRUE;
+    } else {
+        unsigned int i;
+        for (i = firstFrame; i <= lastFrame; ++i) {
+            char const frameType = FType_Type(i);
+            switch(frameType) {
+            case 'i':        numI++;            timeMask |= 0x1;    break;
+            case 'p':        numP++;            timeMask |= 0x2;    break;
+            case 'b':        numB++;            timeMask |= 0x4;    break;
+            }
+        }
+        *frameCountsUnknownP = FALSE;
+    }
+
+    *numIP     = numI;
+    *numPP     = numP;
+    *numBP     = numB;
+    *timeMaskP = timeMask;
+    *frameCountsUnknownP = frameCountsUnknown;
+}
+
+
+
+static void
+readAndSaveFrame(struct inputSource * const inputSourceP,
+                 unsigned int         const frameNumber,
+                 char                 const frameType,
+                 const char *         const inputConversion,
+                 MpegFrame *          const pastRefFrameP,
+                 unsigned int *       const framesReadP,
+                 int *                const ioTimeP,
+                 bool *               const endOfStreamP) {
+/*----------------------------------------------------------------------------
+   Read the next frame from Standard Input and add it to the linked list
+   at *pastRefFrameP.  Assume it is Frame Number 'frameNumber' and is of
+   type 'frameType'.
+
+   Increment *framesReadP.
+   
+   Add the time it took to read it, in seconds, to *iotimeP.
+
+   Iff we can't read because we hit end of file, return
+   *endOfStreamP == TRUE and *framesReadP and *iotimeP untouched.
+-----------------------------------------------------------------------------*/
+    /* This really should be part of ReadNthFrame.  The frame should be chained
+       to the input object, not the past reference frame.
+    */
+       
+    MpegFrame * p;
+    MpegFrame * frameP;
+    time_t ioTimeStart, ioTimeEnd;
+    
+    time(&ioTimeStart);
+
+    frameP = Frame_New(frameNumber, frameType);
+    ReadFrame(frameP, inputSourceP, frameNumber, inputConversion,
+              endOfStreamP);
+
+    if (*endOfStreamP)
+        Frame_Free(frameP);
+    else {
+        ++(*framesReadP);
+    
+        time(&ioTimeEnd);
+        *ioTimeP += (ioTimeEnd - ioTimeStart);
+
+        /* Add the B frame to the end of the queue of B-frames 
+           for later encoding.
+        */
+        assert(pastRefFrameP != NULL);
+        
+        p = pastRefFrameP;
+        while (p->next != NULL)
+            p = p->next;
+        p->next = frameP;
+    }
+}
+
+
+
+static void
+doFirstFrameStuff(enum frameContext const context,
+                  const char *      const userDataFileName,
+                  BitBucket *       const bb,
+                  int               const fsize_x,
+                  int               const fsize_y,
+                  int               const aspectRatio,
+                  int               const frameRate,
+                  int32             const qtable[],
+                  int32             const niqtable[],
+                  unsigned int *    const inputFrameBitsP) {
+/*----------------------------------------------------------------------------
+   Do stuff we have to do after reading the first frame in a sequence
+   of frames requested of GenMPEGStream().
+-----------------------------------------------------------------------------*/
+    *inputFrameBitsP = 24*Fsize_x*Fsize_y;
+    SetBlocksPerSlice();
+          
+    if (context == CONTEXT_WHOLESTREAM) {
+        int32 const bitstreamMode = getRateMode();
+        char * userData;
+        unsigned int userDataSize;
+
+        assert(bb != NULL);
+
+        DBG_PRINT(("Generating sequence header\n"));
+        if (bitstreamMode == FIXED_RATE) {
+            bit_rate = getBitRate();
+            buf_size = getBufferSize();
+        } else {
+            bit_rate = -1;
+            buf_size = -1;
+        }
+        
+        if (strlen(userDataFileName) != 0) {
+            struct stat statbuf;
+            FILE *fp;
+          
+            stat(userDataFileName,&statbuf);
+            userDataSize = statbuf.st_size;
+            userData = malloc(userDataSize);
+            fp = fopen(userDataFileName,"rb");
+            if (fp == NULL) {
+                pm_message("Could not open userdata file '%s'.",
+                           userDataFileName);
+                userData = NULL;
+                userDataSize = 0;
+            } else {
+                size_t bytesRead;
+
+                bytesRead = fread(userData,1,userDataSize,fp);
+                if (bytesRead != userDataSize) {
+                    pm_message("Could not read %d bytes from "
+                               "userdata file '%s'.",
+                               userDataSize,userDataFileName);
+                    userData = NULL;
+                    userDataSize = 0;
+                }
+            }
+        } else { /* Put in our UserData Header */
+            const char * userDataString;
+            time_t now;
+                    
+            time(&now);
+            asprintfN(&userDataString,"MPEG stream encoded by UCB Encoder "
+                      "(mpeg_encode) v%s on %s.",
+                      VERSION, ctime(&now));
+            userData = strdup(userDataString);
+            userDataSize = strlen(userData);
+            strfree(userDataString);
+        }
+        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);
+    }
+}
+
+
+
+static void
+getPreviousFrame(unsigned int         const frameStart,
+                 int                  const referenceFrame,
+                 struct inputSource * const inputSourceP,
+                 boolean              const childProcess,
+                 const char *         const slaveConversion,
+                 const char *         const inputConversion,
+                 MpegFrame **         const framePP,
+                 unsigned int *       const framesReadP,
+                 int *                const ioTimeP) {
+
+    /* This needs to be modularized.  It shouldn't issue messages about
+       encoding GOPs and B frames, since it knows nothing about those.
+       It should work for Standard Input too, through a generic Standard
+       Input reader that buffers stuff for backward reading.
+    */
+
+    MpegFrame * frameP;
+    time_t ioTimeStart, ioTimeEnd;
+
+    /* can't find the previous frame interactively */
+    if (inputSourceP->stdinUsed)
+        pm_error("Cannot encode GOP from stdin when "
+                 "first frame is a B-frame.");
+
+    if (frameStart < 1)
+        pm_error("Cannot encode GOP when first frame is a B-frame "
+                 "and is not preceded by anything.");
+
+    /* need to load in previous frame; call it an I frame */
+    frameP = Frame_New(frameStart-1, 'i');
+
+    time(&ioTimeStart);
+
+    if ((referenceFrame == DECODED_FRAME) && childProcess) {
+        WaitForDecodedFrame(frameStart);
+
+        if (remoteIO)
+            GetRemoteDecodedRefFrame(frameP, frameStart - 1);
+        else
+            ReadDecodedRefFrame(frameP, frameStart - 1);
+    } else {
+        bool endOfStream;
+        ReadNthFrame(inputSourceP, frameStart - 1, remoteIO, childProcess,
+                     separateConversion, slaveConversion, inputConversion,
+                     frameP, &endOfStream);
+        assert(!endOfStream);  /* Because Stdin causes failure above */
+    }            
+    ++(*framesReadP);
+    
+    time(&ioTimeEnd);
+    *ioTimeP += (ioTimeEnd-ioTimeStart);
+
+    *framePP = frameP;
+}
+
+
+
+static void
+computeFrameRange(unsigned int         const frameStart,
+                  unsigned int         const frameEnd,
+                  enum frameContext    const context, 
+                  struct inputSource * const inputSourceP,
+                  unsigned int *       const firstFrameP,
+                  unsigned int *       const lastFrameP) {
+
+    switch (context) {
+    case CONTEXT_GOP:
+        *firstFrameP = frameStart;
+        *lastFrameP  = frameEnd;
+        break;
+    case CONTEXT_JUSTFRAMES: {
+        /* if first frame is P or B, need to read in P or I frame before it */
+        if (FType_Type(frameStart) != 'i') {
+            /* can't find the previous frame interactively */
+            if (inputSourceP->stdinUsed)
+                pm_error("Cannot encode frames from Standard Input "
+                         "when first frame is not an I-frame.");
+
+            *firstFrameP = FType_PastRef(frameStart);
+        } else
+            *firstFrameP = frameStart;
+
+        /* if last frame is B, need to read in P or I frame after it */
+        if ((FType_Type(frameEnd) == 'b') && 
+            (frameEnd != inputSourceP->numInputFiles-1)) {
+            /* can't find the next reference frame interactively */
+            if (inputSourceP->stdinUsed)
+                pm_error("Cannot encode frames from Standard Input "
+                         "when last frame is a B-frame.");
+            
+            *lastFrameP = FType_FutureRef(frameEnd);
+        } else
+            *lastFrameP = frameEnd;
+    }
+    break;
+    case CONTEXT_WHOLESTREAM:
+        *firstFrameP = frameStart;
+        *lastFrameP  = frameEnd;
+    }
+}
+
+
+
+static void
+getFrame(MpegFrame **         const framePP,
+         struct inputSource * const inputSourceP,
+         unsigned int         const frameNumber,
+         char                 const frameType,
+         unsigned int         const realStart,
+         unsigned int         const realEnd,
+         int                  const referenceFrame,
+         boolean              const childProcess,
+         boolean              const remoteIo,
+         boolean              const separateConversion,
+         const char *         const slaveConversion,
+         const char *         const inputConversion,
+         unsigned int *       const framesReadP,
+         int *                const ioTimeP) {
+/*----------------------------------------------------------------------------
+   Get frame with number 'frameNumber' as *frameP.
+
+   Increment *framesReadP.
+
+   Add to *ioTimeP the time in seconds we spent reading it.
+
+   Iff we fail to get the frame because the stream ends, return
+   *frameP == NULL, don't increment *framesReadP, and leave
+   *ioTimeP unchanged.
+-----------------------------------------------------------------------------*/
+    time_t ioTimeStart, ioTimeEnd;
+    MpegFrame * frameP;
+    bool endOfStream;
+    
+    time(&ioTimeStart);
+
+    frameP = Frame_New(frameNumber, frameType);
+            
+    if ((referenceFrame == DECODED_FRAME) &&
+        ((frameNumber < realStart) || (frameNumber > realEnd)) ) {
+        WaitForDecodedFrame(frameNumber);
+
+        if (remoteIo)
+            GetRemoteDecodedRefFrame(frameP, frameNumber);
+        else
+            ReadDecodedRefFrame(frameP, frameNumber);
+
+        /* I don't know what this block of code does, so I don't know
+           what endOfStream should be.  Here's a guess:
+        */
+        endOfStream = FALSE;
+    } else
+        ReadNthFrame(inputSourceP, frameNumber, remoteIO, childProcess,
+                     separateConversion, slaveConversion, inputConversion,
+                     frameP, &endOfStream);
+    
+    if (endOfStream) {
+        Frame_Free(frameP);
+        *framePP = NULL;
+    } else {
+        ++(*framesReadP);
+            
+        time(&ioTimeEnd);
+        *ioTimeP += (ioTimeEnd - ioTimeStart);
+
+        *framePP = frameP;
+    }
+}
+
+
+
+static void
+handleBitRate(unsigned int const realEnd,
+              unsigned int const numBits,
+              boolean      const childProcess,
+              boolean      const showBitRatePerFrame) {
+
+    extern void PrintItoIBitRate (int numBits, int frameNum);
+
+    if (FType_Type(realEnd) != 'i')
+        PrintItoIBitRate(numBits, realEnd+1);
+
+    if ((!childProcess) && showBitRatePerFrame)
+        CloseBitRateFile();
+}
+
+
+
+static void
+doAFrame(unsigned int         const frameNumber,
+         struct inputSource * const inputSourceP,
+         enum frameContext    const context, 
+         unsigned int         const frameStart, 
+         unsigned int         const frameEnd, 
+         unsigned int         const realStart,
+         unsigned int         const realEnd,
+         bool                 const childProcess,
+         const char *         const outputFileName,
+         MpegFrame *          const pastRefFrameP,
+         MpegFrame **         const newPastRefFramePP,
+         unsigned int *       const framesReadP,
+         unsigned int *       const framesOutputP,
+         bool *               const firstFrameDoneP,
+         BitBucket *          const bbP,
+         unsigned int *       const inputFrameBitsP,
+         bool *               const endOfStreamP) {
+/*----------------------------------------------------------------------------
+   *endOfStreamP returned means we were unable to do a frame because
+   the input stream has ended.  In that case, none of the other outputs
+   are valid.
+-----------------------------------------------------------------------------*/
+    char const frameType = FType_Type(frameNumber);
+    
+    *endOfStreamP = FALSE;  /* initial assumption */
+
+    if (frameType == 'b') {
+        /* We'll process this non-reference frame later.  If reading
+           from stdin, we read it now and save it.  Otherwise, we can
+           just read it later.
+        */
+        *newPastRefFramePP = pastRefFrameP;
+        if (inputSourceP->stdinUsed) 
+            readAndSaveFrame(inputSourceP,
+                             frameNumber, frameType, inputConversion,
+                             pastRefFrameP, framesReadP, &IOtime,
+                             endOfStreamP);
+    } else {
+        MpegFrame * frameP;
+        
+        getFrame(&frameP, inputSourceP, frameNumber, frameType,
+                 realStart, realEnd, referenceFrame, childProcess,
+                 remoteIO,
+                 separateConversion, slaveConversion, inputConversion,
+                 framesReadP, &IOtime);
+        
+        if (frameP) {
+            *endOfStreamP = FALSE;
+
+            if (!*firstFrameDoneP) {
+                doFirstFrameStuff(context, userDataFileName,
+                                  bbP, Fsize_x, Fsize_y, aspectRatio,
+                                  frameRate, qtable, niqtable, 
+                                  inputFrameBitsP);
+            
+                *firstFrameDoneP = TRUE;
+            }
+            processRefFrame(frameP, bbP, frameStart, frameEnd,
+                            pastRefFrameP, childProcess, outputFileName, 
+                            framesReadP, framesOutputP);
+                
+            if (pastRefFrameP) {
+                processBFrames(pastRefFrameP, frameP, realStart, realEnd,
+                               inputSourceP, remoteIO, childProcess, 
+                               &IOtime, bbP, outputFileName,
+                               framesReadP, framesOutputP, &currentGOP);
+            }
+            if (pastRefFrameP != NULL)
+                Frame_Free(pastRefFrameP);
+        
+            *newPastRefFramePP = frameP;
+        } else
+            *endOfStreamP = TRUE;
+    }
+}
+
+
+
+void
+GenMPEGStream(struct inputSource * const inputSourceP,
+              enum frameContext    const context, 
+              unsigned int         const frameStart, 
+              unsigned int         const frameEnd, 
+              int32                const qtable[], 
+              int32                const niqtable[], 
+              bool                 const childProcess,
+              FILE *               const ofP, 
+              const char *         const outputFileName,
+              bool                 const wantVbvUnderflowWarning,
+              bool                 const wantVbvOverflowWarning,
+              unsigned int *       const inputFrameBitsP,
+              unsigned int *       const totalBitsP) {
+/*----------------------------------------------------------------------------
+   Encode a bunch of frames into an MPEG sequence stream or a part thereof.
+
+   'context' tells what in addition to the frames themselves must go into
+   the stream:
+
+      CONTEXT_JUSTFRAMES:  Nothing but the indicated frames
+      CONTEXT_GOP:         GOP header/trailer stuff to make a single GOP
+                           that contains the indicated frames
+      CONTEXT_WHOLESTREAM: A whole stream consisting of the indicated
+                           frames -- a sequence of whole GOPS, with stream
+                           header/trailer stuff as well.
+
+   'frameStart' and 'frameEnd' are the numbers of the first and last
+   frames we are to encode, except that if the input source is a stream,
+   we stop where the stream ends if that is before 'frameEnd'.
+
+-----------------------------------------------------------------------------*/
+    BitBucket * bbP;
+    unsigned int frameNumber;
+    bool endOfStream;
+    bool firstFrameDone;
+    int numBits;
+    unsigned int firstFrame, lastFrame;
+    /* Frame numbers of the first and last frames we look at.  This
+       could be more than the the frames we actually encode because
+       we may need context (i.e. to encode a B frame, we need the subsequent
+       I or P frame).
+    */
+    unsigned int framesRead;
+        /* Number of frames we have read; for statistical purposes */
+    MpegFrame * pastRefFrameP;
+        /* The frame that will be the past reference frame for the next
+           B or P frame that we put into the stream
+        */
+    if (frameEnd + 1 > inputSourceP->numInputFiles)
+        pm_error("Last frame (number %u) is beyond the end of the stream "
+                 "(%u frames)", frameEnd, inputSourceP->numInputFiles);
+
+    if (context == CONTEXT_WHOLESTREAM &&
+        !inputSourceP->stdinUsed && 
+        FType_Type(inputSourceP->numInputFiles-1) == 'b')
+        pm_message("WARNING:  "
+                   "One or more B-frames at end will not be encoded.  "
+                   "See FORCE_ENCODE_LAST_FRAME parameter file statement.");
+
+    time(&timeStart);
+
+    framesRead = 0;
+
+    ResetIFrameStats();
+    ResetPFrameStats();
+    ResetBFrameStats();
+
+    Fsize_Reset();
+
+    framesOutput = 0;
+
+    if (childProcess && separateConversion)
+        SetFileType(slaveConversion);
+    else
+        SetFileType(inputConversion);
+
+    realStart = frameStart;
+    realEnd   = frameEnd;
+
+    computeFrameRange(frameStart, frameEnd, context, inputSourceP,
+                      &firstFrame, &lastFrame);
+
+    if (context == CONTEXT_GOP && FType_Type(frameStart) == 'b')
+        getPreviousFrame(frameStart, referenceFrame, inputSourceP,
+                         childProcess, slaveConversion, inputConversion,
+                         &pastRefFrameP, &framesRead, &IOtime);
+    else
+        pastRefFrameP = NULL;
+
+    countFrames(firstFrame, lastFrame, inputSourceP->stdinUsed,
+                &numI, &numP, &numB, &timeMask, &frameCountsUnknown);
+
+    if (showBitRatePerFrame)
+        OpenBitRateFile();  /* May modify showBitRatePerFrame */
+
+    if (context == CONTEXT_WHOLESTREAM)
+        bbP = Bitio_New(ofP);
+    else
+        bbP = NULL;
+
+    initTCTime(firstFrame);
+
+    totalFramesSent = firstFrame;
+    currentGOP = gopSize;        /* so first I-frame generates GOP Header */
+
+    initializeRateControl(wantVbvUnderflowWarning, wantVbvOverflowWarning);
+
+    firstFrameDone = FALSE;
+    for (frameNumber = firstFrame, endOfStream = FALSE;
+         frameNumber <= lastFrame && !endOfStream;
+         ++frameNumber) {
+
+        doAFrame(frameNumber, inputSourceP, context, 
+                 frameStart, frameEnd, realStart, realEnd,
+                 childProcess, outputFileName,
+                 pastRefFrameP, &pastRefFrameP,
+                 &framesRead, &framesOutput, &firstFrameDone, bbP,
+                 inputFrameBitsP, &endOfStream);
+    }
+    
+    if (pastRefFrameP != NULL)
+        Frame_Free(pastRefFrameP);
+    
+    /* SEQUENCE END CODE */
+    if (context == CONTEXT_WHOLESTREAM)
+        Mhead_GenSequenceEnder(bbP);
+    
+    if (context == CONTEXT_WHOLESTREAM)
+        numBits = bbP->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 (context != CONTEXT_JUSTFRAMES) {
+        Bitio_Flush(bbP);
+        bbP = NULL;
+        fclose(ofP);
+    }
+    handleBitRate(realEnd, numBits, childProcess, showBitRatePerFrame);
+
+    *totalBitsP  = numBits;
+}
+
+
+
+/*===========================================================================*
+ *
+ * SetStatFileName
+ *
+ *  set the statistics file name
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    statFileName
+ *
+ *===========================================================================*/
+void
+SetStatFileName(const char * const 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)
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+PrintStartStats(time_t               const startTime, 
+                bool                 const specificFrames,
+                unsigned int         const firstFrame, 
+                unsigned int         const lastFrame,
+                struct inputSource * const inputSourceP) {
+
+    FILE *fpointer;
+    int i;
+
+    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(&startTime));
+        if (getenv("HOST") != NULL)
+            fprintf(fpointer, "MACHINE:  %s\n", getenv("HOST"));
+        else
+            fprintf(fpointer, "MACHINE:  unknown\n");
+
+        if (inputSourceP->stdinUsed)
+            fprintf(fpointer, "INPUT:  stdin\n");
+        else {
+            const char * inputFileName;
+
+            fprintf(fpointer, "INPUT FROM FILES:\n");
+
+            GetNthInputFileName(inputSourceP, 0, &inputFileName);
+            fprintf(fpointer, "FIRST FILE:  %s/%s\n", 
+                    currentPath, inputFileName);
+            strfree(inputFileName);
+            GetNthInputFileName(inputSourceP, inputSourceP->numInputFiles-1, 
+                                &inputFileName);
+            fprintf(fpointer, "LAST FILE:  %s/%s\n", 
+                    currentPath, inputFileName);
+            strfree(inputFileName);
+        }    
+        fprintf(fpointer, "OUTPUT:  %s\n", outputFileName);
+
+        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
+            pm_error("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");
+}
+
+
+
+boolean
+NonLocalRefFrame(int const id) {
+/*----------------------------------------------------------------------------
+   Return TRUE if frame number 'id' might be referenced from a non-local
+   process.  This is a conservative estimate.  We return FALSE iff there
+   is no way based on the information we have that the frame could be
+   referenced by a non-local process.
+-----------------------------------------------------------------------------*/
+    boolean retval;
+
+    int const lastIPid = FType_PastRef(id);
+    
+    /* might be accessed by B-frame */
+    
+    if (lastIPid+1 < realStart)
+        retval = TRUE;
+    else {
+        unsigned int const nextIPid = FType_FutureRef(id);
+        
+        /* if B-frame is out of range, then current frame can be
+           ref'd by it 
+        */
+        
+        /* might be accessed by B-frame */
+        if (nextIPid > realEnd+1)
+            retval = TRUE;
+        
+        /* might be accessed by P-frame */
+        if ((nextIPid > realEnd) && (FType_Type(nextIPid) == 'p'))
+            retval = TRUE;
+    }
+    return retval;
+}
+
+
+ 
+/*===========================================================================*
+ *
+ * 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(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);
+    }
+}
+
+
+
+void
+ComputeGOPFrames(int            const whichGOP, 
+                 unsigned int * const firstFrameP, 
+                 unsigned int * const lastFrameP, 
+                 unsigned int   const numFrames) {
+/*----------------------------------------------------------------------------
+   Figure out which frames are in GOP number 'whichGOP'.
+-----------------------------------------------------------------------------*/
+    unsigned int passedB;
+    unsigned int currGOP;
+    unsigned int gopNum;
+    unsigned int frameNum;
+    unsigned int firstFrame, lastFrame;
+    bool foundGop;
+
+    /* calculate first, last frames of whichGOP GOP */
+
+    gopNum = 0;
+    frameNum = 0;
+    passedB = 0;
+    currGOP = 0;
+    foundGop = FALSE;
+
+    while (!foundGop) {
+        if (frameNum >= numFrames)
+            pm_error("There aren't that many GOPs!");
+
+        if (gopNum == whichGOP) {
+            foundGop = TRUE;
+            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);
+
+        ++gopNum;
+    }
+    *firstFrameP = firstFrame;
+    *lastFrameP  = lastFrame;
+}
+
+
+
+static void
+doEndStats(FILE *       const fpointer,
+           time_t       const startTime,
+           time_t       const endTime,
+           unsigned int const inputFrameBits,
+           unsigned int const totalBits,
+           float        const totalCPU) {
+
+    int32 const diffTime = endTime - startTime;
+
+    char    timeText[256];
+
+    ComputeDHMSTime(diffTime, timeText);
+
+    fprintf(fpointer, "TIME COMPLETED:  %s", ctime(&endTime));
+    fprintf(fpointer, "%s\n\n", timeText);
+        
+    ShowIFrameSummary(inputFrameBits, totalBits, fpointer);
+    ShowPFrameSummary(inputFrameBits, totalBits, fpointer);
+    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 Sec Elapsed:  %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 Sec Elapsed:  Infinite!\n");
+    }
+    if ( totalCPU == 0.0 ) {
+        fprintf(fpointer, "CPU Time:  NONE!\n");
+    } else {
+        fprintf(fpointer, "Total Frames Per Sec CPU    :  %f (%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);
+    }
+}
+
+
+
+/*===========================================================================*
+ *
+ * PrintEndStats
+ *
+ *  print end statistics (summary, time information)
+ *
+ * RETURNS: nothing
+ *
+ * SIDE EFFECTS:    none
+ *
+ *===========================================================================*/
+void
+PrintEndStats(time_t       const startTime,
+              time_t       const endTime,
+              unsigned int const inputFrameBits, 
+              unsigned int const totalBits) {
+
+    float   totalCPU;
+
+    totalCPU = 0.0;
+    totalCPU += IFrameTotalTime();
+    totalCPU += PFrameTotalTime();
+    totalCPU += BFrameTotalTime();
+
+    if (!realQuiet) {
+        fprintf(stdout, "\n\n");
+        doEndStats(stdout, startTime, endTime, inputFrameBits,
+                   totalBits, totalCPU);
+    }
+    
+    if (statFile) {
+        doEndStats(statFile, startTime, endTime, inputFrameBits,
+                   totalBits, totalCPU);
+
+        fclose(statFile);
+    }
+}
+
+
+
+void
+ReadDecodedRefFrame(MpegFrame *  const frameP, 
+                    unsigned int const frameNumber) {
+
+    FILE    *fpointer;
+    char    fileName[256];
+    int width, height;
+    register int y;
+
+    width = Fsize_x;
+    height = Fsize_y;
+
+    sprintf(fileName, "%s.decoded.%u", 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) {
+            fprintf(stderr, "Cannot open %s\n", fileName);
+            exit(1);
+        }}
+
+    Frame_AllocDecoded(frameP, TRUE);
+    
+    for ( y = 0; y < height; y++ ) {
+        size_t bytesRead;
+
+        bytesRead = fread(frameP->decoded_y[y], 1, width, fpointer);
+        if (bytesRead != width)
+            pm_error("Could not read enough bytes from '%s;", fileName);
+    }
+    
+    for (y = 0; y < (height >> 1); y++) {           /* U */
+        size_t const bytesToRead = width/2;
+        size_t bytesRead;
+
+        bytesRead = fread(frameP->decoded_cb[y], 1, bytesToRead, fpointer);
+        if (bytesRead != bytesToRead)
+            pm_message("Could not read enough bytes from '%s'", fileName);
+    }
+    
+    for (y = 0; y < (height >> 1); y++) {           /* V */
+        size_t const bytesToRead = width/2;
+        size_t bytesRead;
+        bytesRead = fread(frameP->decoded_cr[y], 1, bytesToRead, fpointer);
+        if (bytesRead != bytesToRead)
+            pm_message("Could not read enough bytes from '%s'", fileName);
+    }
+    fclose(fpointer);
+}
+
+
+
+static void
+OpenBitRateFile() {
+    bitRateFile = fopen(bitRateFileName, "w");
+    if ( bitRateFile == NULL ) {
+        pm_message("ERROR:  Could not open bit rate file:  '%s'", 
+                   bitRateFileName);
+        showBitRatePerFrame = FALSE;
+    }
+}
+
+
+
+static void
+CloseBitRateFile() {
+    fclose(bitRateFile);
+}