/* parsempeg.c 
 *   by Nathan Laredo (laredo@gnu.org)
 *
 * Stradis 4:2:2 MPEG-2 Decoder Driver
 * Copyright (C) 1999 Nathan Laredo <laredo@gnu.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <linux/types.h>
#include <linux/videodev.h>

/* debugging */
#define show_hexbin(m, a) \
	fprintf(stderr, "%9d: 0x%02x (%c%c%c%c%c%c%c%c)\n", (m), (a), \
		((a) & 128) ? 49 : 48, ((a) & 64) ? 49 : 48, \
		((a) &  32) ? 49 : 48, ((a) & 16) ? 49 : 48, \
		((a) &   8) ? 49 : 48, ((a) &  4) ? 49 : 48, \
		((a) &   2) ? 49 : 48, ((a) &  1) ? 49 : 48)
#define dump_current(a) \
	fprintf(stderr, "%9d: %02x %02x %02x %02x %02x %02x %02x %02x\n", \
		a - mpegdata, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]);

unsigned char *mpegdata = NULL;
unsigned char *current = NULL;
unsigned char fluff[32768];
unsigned char sequence_error[4] = { 0x00, 0x00, 0x01, 0xb4 };
int mpegsize;
int videofd;

void loadmpeg(int f)
{
	struct stat st;
	if (fstat(f, &st) < 0) {
		perror("fstat");
		exit(1);
	}
	mpegsize = st.st_size - 4;

	if ((mpegdata = mmap(0, mpegsize, PROT_READ, MAP_SHARED, f, 0))
	    == MAP_FAILED) {
		perror("mmap");
		exit(1);
	}
}

static lastwritemode = VID_WRITE_MPEG_VID;

void decodedata(char *buf, int length, int mode)
{

	if (mode != lastwritemode) {
		ioctl(videofd, VIDIOCSWRITEMODE, &mode);
		lastwritemode = mode;
	}
#if 0
	if (mode == VID_WRITE_MPEG_VID)
#endif
	write(videofd, buf, length);
}
void parsempeg(int vidpid, int audpid)
{
	int pid = 0, i, pesbytes;
	int ac3flag = 0, offset;
	current = mpegdata;
	if (strncmp(current, "RIFF", 4) == 0)
		current += 0x11d14;
    while(current - mpegdata < mpegsize) {
	if (*mpegdata == 0x47) {
		unsigned int pidstat[65536];
		memset(pidstat, 0, sizeof(pidstat));
		/* transport stream */
		fprintf(stderr, "sending transport stream\n");
		while (current - mpegdata < mpegsize) {
			while((*current != 0x47 || (current[1] & 0x80)) &&
				current - mpegdata < mpegsize)
				current++;
			pid = ((current[1] & 0x1f)<<8) | current[2];
			if (pid == audpid || pid == vidpid) {
				offset = 4;
				switch(current[3]&0x30) {
				case 0x00:
					goto badfile;
				case 0x30:
					offset = 5 + current[4];
				case 0x10:
					pesbytes = 188 - offset;
					if (pid == vidpid)
						decodedata(&current[offset],
							pesbytes,
							VID_WRITE_MPEG_VID);
					if (pid == audpid)
						decodedata(&current[offset],
							pesbytes,
							VID_WRITE_MPEG_AUD);
				}
			}
			if (!pidstat[pid]) {
				fprintf(stderr, "Found PID %d\n", pid);
			}
			pidstat[pid]++;
			current += 188;
		}
		fprintf(stderr, "\nTransport Stream Stats\n  PID Packets\n");
		for (i=0; i < 65536; i++)
			if (pidstat[i])
				fprintf(stderr, "%5d %-10d\n", i, pidstat[i]);
	} else if (current[0] == 0 && current[1] == 0 && current[2] == 1 &&
		(current[3] == 0xb3 || (current[3] & 0xf0) == 0xe0)) {
		/* video elementary stream */
		fprintf(stderr, "sending elementary stream\n");
		decodedata(mpegdata, mpegsize, VID_WRITE_MPEG_VID);
	} else if (current[0] == 0 && current[1] == 0 && current[2] == 1 &&
		current[3] == 0xba && (current[4] & 0xc0) == 0x40) {
		/* program stream */
		fprintf(stderr, "sending program stream\n");
		while (current - mpegdata < mpegsize) {
			if (current[0] == 0 && current[1] == 0 &&
				current[2] == 1 && current[3] > 0xba) {
				pesbytes = (current[4]<<8) + current[5] + 6;
			} else {
				current++;
				continue;
			}
			pid = current[3];
			if (pid >= 0xbc) {
				if ((pid & 0xff) == 0xe0) {
					decodedata(current, pesbytes,
						VID_WRITE_MPEG_VID);
				} else if ((pid & 0xe0) == 0xc0) {
					decodedata(current, pesbytes,
						VID_WRITE_MPEG_AUD);
				} else if (pid == 0xbd) {
					if (!ac3flag)
						fprintf(stderr,
							"AC-3\n");
					ac3flag = 1;
				}
			}
			current += pesbytes;
		}
	} else if (current[0] == 0 && current[1] == 0 && current[2] == 1 &&
		current[3] == 0xba && (current[4] & 0xf0) == 0x20) {
		/* system stream */
		fprintf(stderr, "sending system stream\n");
		while (current - mpegdata < mpegsize) {
lookagain:
			while (!(current[0] == 0 && current[1] == 0 &&
				current[2] == 1) &&
				current - mpegdata < mpegsize) 
				current++;
			if (current[0] == 0 && current[1] == 0 &&
				current[2] == 1 && current[3] >= 0xbc)
				goto nosystemheader;
			current += 4;
			while (!(current[0] == 0 && current[1] == 0 &&
				current[2] == 1 && current[3] == 0xba) &&
				current - mpegdata < mpegsize) 
				current++;
processpack:
			current += 12;
			while (!(current[0] == 0 && current[1] == 0 &&
				current[2] == 1) &&
				current - mpegdata < mpegsize) 
				current++;
			if (current[0] == 0 && current[1] == 0 &&
				current[2] == 1 && current[3] == 0xbb) {
				pesbytes = current[4] + 5;
				current += pesbytes;
			}
			while (current - mpegdata < mpegsize) {
nosystemheader:
				while (!(current[0] == 0 && current[1] == 0 &&
					current[2] == 1) &&
					current - mpegdata < mpegsize) 
					current++;
				pid = current[3];
				if (pid == 0xba) {
					goto processpack;
				}
				if (pid == 0xb9)
					current = mpegdata + mpegsize;
				pesbytes = (current[4]<<8) + current[5] + 6;
				if (pid >= 0xbc) {
					if ((pid & 0xf0) == 0xe0) {
						decodedata(current, pesbytes,
							VID_WRITE_MPEG_VID);
					} else if ((pid & 0xe0) == 0xc0) {
						decodedata(current, pesbytes,
							VID_WRITE_MPEG_AUD);
					} else if (pid == 0xbd) {
						if (!ac3flag)
							fprintf(stderr,
								"AC-3\n");
						ac3flag = 1;
					}
					current += pesbytes;
					continue;
				} else
					goto lookagain;
			}
		}
	}
	current++;
    }
    return;
      badfile:
	fprintf(stderr, "bad mpeg stream\n");
	dump_current(current);
	return;
}

int main(int argc, char **argv)
{
	int f, a = -1, v = -1, d = 0, s = 0;
	char outf[25];
	struct video_play_mode p;

	if (argc < 2) {
		fprintf(stderr, "usage: %s mpegfile\n", argv[0]);
		exit(1);
	}
	if ((f = open(argv[1], O_RDONLY)) < 0) {
		perror(argv[1]);
		exit(1);
	}
	if (argc > 4)
		d = atoi(argv[4]);
	if (argc > 5)
		s = atoi(argv[5]);
	sprintf(outf, "/dev/video%d", d);
	if ((videofd = open(outf, O_RDWR)) < 0) {
		perror(outf);
		exit(1);
	}
	v = -1;
	if (argc > 2)
		v = atoi(argv[2]);
	if (argc > 3)
		a = atoi(argv[3]);
	p.mode = VID_PLAY_VID_OUT_MODE;
	p.p1 = VIDEO_MODE_NTSC;
	if (s == 1)
		p.p1 = VIDEO_MODE_PAL;
	ioctl(videofd, VIDIOCSPLAYMODE, &p);
	loadmpeg(f);
	parsempeg(v, a);
	memset(fluff, 0x00, 32768);
	memcpy(fluff, sequence_error, 4);
	/* flush buffers */
	decodedata(fluff, 4096, VID_WRITE_MPEG_AUD);
	decodedata(fluff, 32768, VID_WRITE_MPEG_VID);
        p.mode = VID_PLAY_END_MARK;
        ioctl(videofd, VIDIOCSPLAYMODE, &p);
	close(f);
	exit(0);
}				/* main */
