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