/*------------------------------------------------------------------------
 *
 * Copyright (c) 1997-1998 by Cornell University.
 * 
 * See the file "license.txt" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * mpgtoppm.c
 * 
 * Wei Tsang Ooi weitsang@cs.cornell.edu
 *
 * Usage : mpgtoppm inputMPG
 *
 * Decodes an MPEG video into a series of PPM files.
 *
 *------------------------------------------------------------------------
 */
#include <sys/types.h>
#include <sys/stat.h>
#include "dvmbasic.h"
#include "dvmmpeg.h"
#include "dvmcolor.h"
#include "dvmpnm.h"

/*
 * This procedure encode 3 byte image into a bitstream bs,
 * using bitparser bp, and output it to a tcl channel called
 * name.  Assumes that the header is already encoded in the 
 * bitstream.  (This is an improvement over the routines in pnmlib.tcl
 * since it reuse the same header and bitstream)
 */
void WritePPM (r, g, b, bs, bp, name) 
    ByteImage *r;
    ByteImage *g;
    ByteImage *b;
    BitStream *bs;
    BitParser *bp;
    char *name;
{
    FILE *chan;
    int curr;

    chan = fopen(name, "w");
    if (chan == NULL) {
	fprintf(stderr, "unable to open %s for reading.\n", name);
	exit(1);
    }
    curr = BitParserTell(bp);
    PpmEncode(r, g, b, bp);
    BitStreamFileWrite(bs, chan, 0);
    BitParserSeek(bp, curr);
    fclose(chan);
}


void swap (ByteImage **x, ByteImage **y)
{
    ByteImage *temp;
    temp = *x;
    *x = *y;
    *y = temp;
}


/*
 * This proc make sure that there are at least size bytes of 
 * data in the bitstream bs, which is attached to bitparser bp.  
 * If there is not enough data, fill up the bitstream by reading 
 * from tcl channel chan.
 */
void CheckBitStreamUnderflow (bs, bp, chan, size)
    BitStream *bs;
    BitParser *bp;
    FILE *chan;
    int size;
{
    int off  = BitParserTell(bp);
    int left = BitStreamBytesLeft(bs, off);
    if (left < size) {
        BitStreamShift (bs, off);
        BitStreamFileRead (bs, chan, left);
        BitParserSeek (bp, 0);
    }
}


int main(int argc, char *argv[])
{
    BitParser *bp = BitParserNew();
    BitStream *bs = BitStreamNew(65536);
    BitParser *outbp;
    BitStream *outbs;
    MpegSeqHdr *sh;
    MpegPicHdr *fh;
    ByteImage *r, *g, *b, *y, *u, *v;
    ByteImage *prevy, *prevu, *prevv, *futurey, *futureu, *futurev;
    ScImage *scy, *scu, *scv;
    VectorImage *fwdmv, *bwdmv;
    PnmHdr *pnmhdr;
    FILE *file;
    int currCode, len;
    int halfw, halfh, w, h, seqw, seqh, picSize, remw, remh, type;
    int counter, gopSize = 0, gopStart = 0;
    char outname[100];

    /*
     * Check arguments, open file, and initialize BitStream.
     */
    if (argc < 1) {
	fprintf(stderr, "usage : %s input\n", argv[0]);
	exit(1);
    }
    file = fopen(argv[1], "rb");
    if (file == NULL) {
	fprintf(stderr, "unable to open %s for reading.\n", argv[1]);
	exit(1);
    }
    BitStreamFileRead(bs, file, 0);
    BitParserWrap(bp, bs);

    /*
     * Allocate a new sequence header, skips the initial garbage (if any)
     * in the input file, and read in the sequence header.
     */
    sh = MpegSeqHdrNew();
    MpegSeqHdrFind(bp);
    MpegSeqHdrParse(bp, sh);

    /*
     * Find the width and height of the video frames.  If the width
     * and height are not multiple of 16, round it up to the next 
     * multiple of 16.
     */
    seqw = MpegSeqHdrGetWidth(sh);
    seqh = MpegSeqHdrGetHeight(sh);
    picSize = MpegSeqHdrGetBufferSize(sh);
    remw = seqw % 16;
    remh = seqh % 16;
    if (remw != 0) {
	w = seqw + 16 - remw;
    } else {
	w = seqw;
    }
    if (remh != 0) {
	h = seqh + 16 - remh;
    } else {
	h = seqh;
    }
    halfw = w/2;
    halfh = h/2;

    /*
     * Allocates all the ByteImages and ScImages that we need.
     * y, u, v for decoded frame in YUV color space, r, g, b for
     * decoded frame in RGB color space. prevy, prevu, prevv are
     * past frames in YUV color space, futurey, futureu and futurev
     * are future frames in YUV color space.  scy, scu and scv
     * are the DCT coded images from the bitstream.  fwdmv and bwdmv
     * are the forward and backward motion vectors respectively.
     */
    y       = ByteNew (w, h);
    prevy   = ByteNew (w, h);
    futurey = ByteNew (w, h);
    r       = ByteNew (seqw, seqh);
    g       = ByteNew (seqw, seqh);
    b       = ByteNew (seqw, seqh);
    u       = ByteNew (halfw, halfh);
    prevu   = ByteNew (halfw, halfh);
    futureu = ByteNew (halfw, halfh);
    v       = ByteNew (halfw, halfh);
    prevv   = ByteNew (halfw, halfh);
    futurev = ByteNew (halfw, halfh);
    fwdmv   = VectorNew (w/16, h/16);
    bwdmv   = VectorNew (w/16, h/16);
    scy     = ScNew (w/8, h/8);
    scu     = ScNew (w/16, h/16);
    scv     = ScNew (w/16, h/16);

    /*
     * Create a new PnmHdr and encode it to the BitStream.  We only do
     * this once, since all frames have the same header.
     */
    pnmhdr = PnmHdrNew();
    PnmHdrSetType(pnmhdr, PPM_BIN);
    PnmHdrSetWidth(pnmhdr, seqw);
    PnmHdrSetHeight(pnmhdr, seqh);
    PnmHdrSetMaxVal(pnmhdr, 255);
    outbs = BitStreamNew(3*seqw*seqh + 20);
    outbp = BitParserNew();
    BitParserWrap(outbp, outbs);
    PnmHdrEncode(pnmhdr, outbp);
    PnmHdrFree(pnmhdr);

    /*
     * Create a new Pic header, and advance the cursor to the next
     * pic header. (We are not interested in GOP header here).
     */
    fh  = MpegPicHdrNew();
    len = MpegPicHdrFind(bp);

    while (1) {
	/*
	 * Reads the pic header, and perform decoding according to the
	 * type.
	 */
	CheckBitStreamUnderflow(bs, bp, file, picSize);
	MpegPicHdrParse(bp, fh);
	type = MpegPicHdrGetType(fh);
	counter = gopStart + MpegPicHdrGetTemporalRef(fh);
	gopSize++;
	if (type == I_FRAME) {
	    swap(&futurey, &prevy);
	    swap(&futureu, &prevu);
	    swap(&futurev, &prevv);
	    MpegPicIParse(bp, sh, fh, scy, scu, scv);
	    ScIToByte(scy, y);
	    ScIToByte(scu, u);
	    ScIToByte(scv, v);
	    YuvToRgb420(y, u, v, r, g, b);
	    sprintf(outname, "%03di.ppm", counter);
	    WritePPM(r, g, b, outbs, outbp, outname);
	    swap(&y, &futurey);
	    swap(&u, &futureu);
	    swap(&v, &futurev);
	} else if (type == P_FRAME) {
	    swap(&futurey, &prevy);
	    swap(&futureu, &prevu);
	    swap(&futurev, &prevv);
	    MpegPicPParse(bp, sh, fh, scy, scu, scv, fwdmv);
	    ScPToY(scy, fwdmv, prevy, y);
	    ScPToUV(scu, fwdmv, prevu, u);
	    ScPToUV(scv, fwdmv, prevv, v);
	    YuvToRgb420(y, u, v, r, g, b);
	    sprintf(outname, "%03dp.ppm", counter);
	    WritePPM(r, g, b, outbs, outbp, outname);
	    swap(&y, &futurey);
	    swap(&u, &futureu);
	    swap(&v, &futurev);
	} else {
	    MpegPicBParse(bp, sh, fh, scy, scu, scv, fwdmv, bwdmv);
	    ScBToY(scy, fwdmv, bwdmv, prevy, futurey, y);
	    ScBToUV(scu, fwdmv, bwdmv, prevu, futureu, u);
	    ScBToUV(scv, fwdmv, bwdmv, prevv, futurev, v);
	    YuvToRgb420(y, u, v, r, g, b);
	    sprintf(outname, "%03db.ppm", counter);
	    WritePPM(r, g, b, outbs, outbp, outname);
	}
	currCode = MpegGetCurrStartCode(bp);
        if (currCode == GOP_START_CODE) {
            gopStart += gopSize;
            gopSize = 0;
        }
        MpegPicHdrFind(bp);
	if (currCode == SEQ_END_CODE) {
	    break;
	}
    }

    /*
     * Clean up the stuffs.
     */
    MpegPicHdrFree(fh);
    MpegSeqHdrFree(sh);
    BitStreamFree(bs);
    BitParserFree(bp);
    ByteFree(r);
    ByteFree(g);
    ByteFree(b);
    ByteFree(y);
    ByteFree(u);
    ByteFree(v);
    ByteFree(prevy);
    ByteFree(prevu);
    ByteFree(prevv);
    ByteFree(futurey);
    ByteFree(futureu);
    ByteFree(futurev);
    ScFree(scy);
    ScFree(scu);
    ScFree(scv);
    VectorFree(fwdmv);
    VectorFree(bwdmv);

    return 0;
}