/*===========================================================================*
 * combine.c								     *
 *									     *
 *	Procedures to combine frames or GOPS into an MPEG sequence	     *
 *									     *
 * EXPORTED PROCEDURES:							     *
 *	GOPStoMPEG							     *
 *	FramesToMPEG							     *
 *									     *
 *===========================================================================*/

/*
 * 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$
 *  $Log$
 *  Revision 1.4  2004/04/02 15:12:40  rwcox
 *  Cput
 *
 *  Revision 1.3  2003/12/23 13:50:08  rwcox
 *  Cput
 *
 *  Revision 1.2  2003/12/03 14:46:14  rwcox
 *  Cput
 *
 *  Revision 1.1  2001/12/17 16:11:53  rwcox
 *  Cadd
 *
 *  Revision 1.9  1995/08/07 21:42:38  smoot
 *  Sleeps when files do not exist.
 *  renamed index to idx
 *
 *  Revision 1.8  1995/06/21 22:20:45  smoot
 *  added a sleep for NFS to complete file writes
 *
 * Revision 1.7  1995/06/08  20:23:19  smoot
 * added "b"'s to fopen so PCs are happy
 *
 * Revision 1.6  1995/01/19  23:07:22  eyhung
 * Changed copyrights
 *
 * Revision 1.5  1995/01/16  07:53:55  eyhung
 * Added realQuiet
 *
 * Revision 1.4  1994/11/12  02:11:46  keving
 * nothing
 *
 * Revision 1.3  1994/03/15  00:27:11  keving
 * nothing
 *
 * Revision 1.2  1993/12/22  19:19:01  keving
 * nothing
 *
 * Revision 1.1  1993/07/22  22:23:43  keving
 * nothing
 *
 */


/*==============*
 * 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 "mheaders.h"
#include "fsize.h"
#include "combine.h"
#include <unistd.h>

/* note, remove() might not have a prototype in the standard header files,
 * but it really should -- it's not my fault!
 */


static int	currentGOP;

#define READ_ATTEMPTS 5 /* number of times (seconds) to retry an input file */

/*==================*
 * GLOBAL VARIABLES *
 *==================*/
extern int  yuvWidth, yuvHeight;
char	currentGOPPath[MAXPATHLEN];
char	currentFramePath[MAXPATHLEN];


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

static void	AppendFile _ANSI_ARGS_((FILE *outputFile, FILE *inputFile));


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

/*===========================================================================*
 *
 * GOPStoMPEG
 *
 *	convert some number of GOP files into a single MPEG sequence file
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
void
GOPStoMPEG(numGOPS, outputFileName, outputFilePtr)
    int numGOPS;
    char *outputFileName;
    FILE *outputFilePtr;
{
    register int ind;
    BitBucket *bb;
    char    fileName[1024];
    char    inputFileName[1024];
    FILE *inputFile;
    int q;

    {
      /* Why is this reset called? */
      int x=Fsize_x, y=Fsize_y;
      Fsize_Reset();
      Fsize_Note(0, yuvWidth, yuvHeight);
      if (Fsize_x == 0 || Fsize_y == 0) {
	Fsize_Note(0, x, y);
      }}
    
    bb = Bitio_New(outputFilePtr);

    Mhead_GenSequenceHeader(bb, Fsize_x, Fsize_y, /* pratio */ aspectRatio,
	       /* pict_rate */ frameRate, /* bit_rate */ -1,
	       /* buf_size */ -1, /*c_param_flag */ 1,
	       /* iq_matrix */ customQtable, /* niq_matrix */ customNIQtable,
	       /* ext_data */ NULL, /* ext_data_size */ 0,
	       /* user_data */ NULL, /* user_data_size */ 0);

    /* it's byte-padded, so we can dump it now */
    Bitio_Flush(bb);

    if ( numGOPS > 0 ) {
	for ( ind = 0; ind < numGOPS; ind++ ) {
	    GetNthInputFileName(inputFileName, ind);
	    sprintf(fileName, "%s/%s", currentGOPPath, inputFileName);

	    for (q = 0;   q < READ_ATTEMPTS;  ++q ) {
	      if ( (inputFile = fopen(fileName, "rb")) != NULL ) break;
	      fprintf(stderr, "ERROR:  Couldn't read (GOPStoMPEG):  %s retry %d\n", 
		      fileName, q);
	      fflush(stderr);
	      sleep(1);
	    }
	    if (q == READ_ATTEMPTS) {
	      fprintf(stderr, "Giving up (%d attepmts).\n", READ_ATTEMPTS);
	      exit(1);
	    }
	    
	    if (! realQuiet) {
	    	fprintf(stdout, "appending file:  %s\n", fileName);
	    }

	    AppendFile(outputFilePtr, inputFile);
	}
    } else {
	ind = 0;
	while ( TRUE ) {
	    sprintf(fileName, "%s.gop.%d", outputFileName, ind);

	    if ( (inputFile = fopen(fileName, "rb")) == NULL ) {
		break;
	    }

	    if (! realQuiet) {
	    	fprintf(stdout, "appending file:  %s\n", fileName);
	    }

	    AppendFile(outputFilePtr, inputFile);

	    ind++;
	}
    }

    bb = Bitio_New(outputFilePtr);

    /* SEQUENCE END CODE */
    Mhead_GenSequenceEnder(bb);

    Bitio_Flush(bb);

    fclose(outputFilePtr);
}


/*===========================================================================*
 *
 * FramestoMPEG
 *
 *	convert some number of frame files into a single MPEG sequence file
 *
 *	if parallel == TRUE, then when appending a file, blocks until that
 *	file is actually ready
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
void
FramesToMPEG(numFrames, outputFileName, outputFile, parallel)
    int numFrames;
    char *outputFileName;
    FILE *outputFile;
    boolean parallel;
{
    register int ind;
    BitBucket *bb;
    char    fileName[1024];
    char    inputFileName[1024];
    FILE *inputFile;
    int	pastRefNum = -1;
    int	futureRefNum = -1;
    int q;

    tc_hrs = 0;	tc_min = 0; tc_sec = 0; tc_pict = 0; tc_extra = 0;

    {
      /* Why is this reset called? */
      int x=Fsize_x, y=Fsize_y;
      Fsize_Reset();
      Fsize_Note(0, yuvWidth, yuvHeight);
      if (Fsize_x == 0 || Fsize_y == 0) {
	Fsize_Note(0, x, y);
      }}
    SetBlocksPerSlice();

    bb = Bitio_New(outputFile);
    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);
    /* it's byte-padded, so we can dump it now */
    Bitio_Flush(bb);

    /* need to do these in the right order!!! */
    /* also need to add GOP headers */

    currentGOP = gopSize;
    totalFramesSent = 0;

    if ( numFrames > 0 ) {
	for ( ind = 0; ind < numFrames; ind++ ) {
	    if ( FRAME_TYPE(ind) == 'b' ) {
		continue;
	    }

	    pastRefNum = futureRefNum;
	    futureRefNum = ind;

	    if ( (FRAME_TYPE(ind) == 'i') && (currentGOP >= gopSize) ) {
		int closed;

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

		if (! realQuiet) {
			fprintf(stdout, "Creating new GOP (closed = %d) after %d frames\n",
				closed, currentGOP);
		}

		/* new GOP */
		bb = Bitio_New(outputFile);
		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);
		Bitio_Flush(bb);
		SetGOPStartTime(ind);
		
		currentGOP -= gopSize;
	    }

	    if ( parallel ) {
		WaitForOutputFile(ind);
		sprintf(fileName, "%s.frame.%d", outputFileName, ind);
	    } else {
		GetNthInputFileName(inputFileName, ind);
		sprintf(fileName, "%s/%s", currentFramePath, inputFileName);
	    }

	    for (q = 0;   q < READ_ATTEMPTS;  ++q ) {
	      if ( (inputFile = fopen(fileName, "rb")) != NULL ) break;
	      fprintf(stderr, "ERROR:  Couldn't read 2:  %s retry %d\n", fileName, q);
	      fflush(stderr);
	      sleep(1);
	    }
	    if (q == READ_ATTEMPTS) {
	      fprintf(stderr, "Giving up (%d attepmts).\n", READ_ATTEMPTS);
	      exit(1);
	    }
	    
	    AppendFile(outputFile, inputFile);
	    if ( parallel ) {
		remove(fileName);
	    }

	    currentGOP++;
	    IncrementTCTime();

	    /* now, output the B-frames */
	    if ( pastRefNum != -1 ) {
		register int bNum;

		for ( bNum = pastRefNum+1; bNum < futureRefNum; bNum++ ) {
		    if ( parallel ) {
			WaitForOutputFile(bNum);
			sprintf(fileName, "%s.frame.%d", outputFileName, bNum);
		    } else {
			GetNthInputFileName(inputFileName, bNum);
			sprintf(fileName, "%s/%s", currentFramePath, inputFileName);
		    }


		    for (q = 0;   q < READ_ATTEMPTS;  ++q ) {
		      if ( (inputFile = fopen(fileName, "rb")) != NULL ) break;
		      fprintf(stderr, "ERROR:  Couldn't read (bNum=%d):  %s retry %d\n", 
			      bNum, fileName, q);
		      fflush(stderr);
		      sleep(1);
		    }
		    if (q == READ_ATTEMPTS) {
		      fprintf(stderr, "Giving up (%d attepmts).\n", READ_ATTEMPTS);
		      exit(1);
		    }
		    
		    AppendFile(outputFile, inputFile);
		    if ( parallel ) {
			remove(fileName);
		    }
			
		    currentGOP++;
		    IncrementTCTime();
		}
	    }
	}
    } else {
	if ( parallel ) {
	    fprintf(stderr, "ERROR:  PARALLEL COMBINE WITH 0 FRAMES\n");
	    fprintf(stderr, "(please send bug report!)\n");
	    exit(1);
	}

	ind = 0;
	while ( TRUE ) {
	    if ( FRAME_TYPE(ind) == 'b' ) {
		ind++;
		continue;
	    }

	    if ( (FRAME_TYPE(ind) == 'i') && (currentGOP >= gopSize) ) {
		int closed;

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

		if (! realQuiet) {
		fprintf(stdout, "Creating new GOP (closed = %d) before frame %d\n",
			closed, ind);
		}

		/* new GOP */
		bb = Bitio_New(outputFile);
		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);
		Bitio_Flush(bb);
		SetGOPStartTime(ind);

		currentGOP -= gopSize;
	    }

	    sprintf(fileName, "%s.frame.%d", outputFileName, ind);

	    if ( (inputFile = fopen(fileName, "rb")) == NULL ) {
		break;
	    }

	    AppendFile(outputFile, inputFile);
	    if ( parallel ) {
		remove(fileName);
	    }

	    currentGOP++;
	    IncrementTCTime();

	    /* now, output the B-frames */
	    if ( pastRefNum != -1 ) {
		register int bNum;

		for ( bNum = pastRefNum+1; bNum < futureRefNum; bNum++ ) {
		    sprintf(fileName, "%s.frame.%d", outputFileName, bNum);

		    for (q = 0;   q < READ_ATTEMPTS;  ++q ) {
		      if ( (inputFile = fopen(fileName, "rb")) != NULL ) break;
		      fprintf(stderr, "ERROR:  Couldn't read (FramestoMPEG):  %s retry %d\n", 
			      fileName, q);
		      fflush(stderr);
		      sleep(1);
		    }
		    if (q == READ_ATTEMPTS) {
		      fprintf(stderr, "Giving up (%d attepmts).\n", READ_ATTEMPTS);
		      exit(1);
		    }

		    AppendFile(outputFile, inputFile);
		    if ( parallel ) {
			remove(fileName);
		    }

		    currentGOP++;
		    IncrementTCTime();
		}
	    }

	    ind++;
	}
    }

    if (! realQuiet) {
	fprintf(stdout, "Wrote %d frames\n", totalFramesSent);
	fflush(stdout);
    }

    bb = Bitio_New(outputFile);

    /* SEQUENCE END CODE */
    Mhead_GenSequenceEnder(bb);

    Bitio_Flush(bb);

    fclose(outputFile);
}


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

/*===========================================================================*
 *
 * AppendFile
 *
 *	appends the output file with the contents of the given input file
 *
 * RETURNS:	nothing
 *
 * SIDE EFFECTS:    none
 *
 *===========================================================================*/
static void
AppendFile(outputFile, inputFile)
    FILE *outputFile;
    FILE *inputFile;
{
    uint8   data[9999];
    int	    readItems;

    readItems = 9999;
    while ( readItems == 9999 ) {
	readItems = fread(data, sizeof(uint8), 9999, inputFile);
	if ( readItems > 0 ) {
	    fwrite(data, sizeof(uint8), readItems, outputFile);
	}
    }

    fclose(inputFile);
}


