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

int videofd;
int f;

static lastwritemode = VID_WRITE_MPEG_VID;

int curpos = 0;
int lastpos = 0;
unsigned char inbuf[8192];
unsigned char fluff[32768];
unsigned char sequence_error[4] = { 0x00, 0x00, 0x01, 0xb4 };

#define dump_current(a) \
	fprintf(stderr, "%02x %02x %02x %02x %02x %02x %02x %02x " \
		"%02x %02x %02x %02x %02x %02x %02x %02x\n", \
		a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], \
		a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]);

/* count = 0 will peek at current byte without throwing it */
unsigned char *nextbytes(int count)
{
	unsigned char *tmp = NULL;
	int left;

	if (count > 8192)
		return NULL;
	if (lastpos - curpos > count) {
		tmp = &inbuf[curpos];
		curpos += count;
		if (curpos == lastpos)
			curpos = lastpos = 0;
		return tmp;
	}
	if (curpos > 0 && 8192 - curpos < count) {
		/* hopefully this won't happen TOO much */
		memcpy(inbuf, &inbuf[curpos], lastpos - curpos);
		lastpos -= curpos;
		curpos = 0;
	}
	tmp = &inbuf[curpos];
	curpos += count;

	/* only load n*1024 bytes */
	if ((left = (8192 - lastpos) & 0xffc00))
		lastpos += read(f, &inbuf[lastpos], left);
	if (curpos == lastpos)
		curpos = lastpos = 0;
	if (curpos <= lastpos)
		return tmp;

	/* should VERY RARELY get to this point, maybe near EOF only */
	if (8192 - lastpos > 0)
		lastpos += read(f, &inbuf[lastpos], 8192 - lastpos);
	if (curpos == lastpos)
		curpos = lastpos = 0;
	if (curpos <= lastpos)
		return tmp;
	return NULL;
}

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;
	unsigned char *tmp, *t2;
	if (*nextbytes(0) == 0x47) {
		unsigned int pidstat[65536];
		memset(pidstat, 0, sizeof(pidstat));
		/* transport stream */
		fprintf(stderr, "sending transport stream\n");
		while (nextbytes(0)) {
			while (tmp = nextbytes(1)) {
				if ((t2 = nextbytes(0)) == NULL)
					break;	/* EOF */
				if (*tmp == 0x47 && !(*t2 & 0x80))
					break;
			}
			if ((tmp = nextbytes(187)) == NULL)
				break;	/* EOF */
			pid = ((tmp[0] & 0x1f)<<8) | tmp[1];
			if (pid == audpid || pid == vidpid) {
				offset = 3;
				switch(tmp[2]&0x30) {
				case 0x00:
					break;
				case 0x30:
					offset = 4 + tmp[3];
				case 0x10:
					pesbytes = 187 - offset;
					if (pid == vidpid)
						decodedata(&tmp[offset],
							pesbytes,
							VID_WRITE_MPEG_VID);
					if (pid == audpid)
						decodedata(&tmp[offset],
							pesbytes,
							VID_WRITE_MPEG_AUD);
				}
			}
			if (!pidstat[pid]) {
				fprintf(stderr, "Found PID %d\n", pid);
			}
			pidstat[pid]++;
		} if (nextbytes(0)) {
			unsigned char *tmp;
			if(tmp = nextbytes(16)) {
				/* DUMP SEMI-USEFUL DEBUGGING INFO */
				fprintf(stderr, "Parse error at location:\n");
				dump_current(tmp);
				if (tmp = nextbytes(16))
					dump_current(tmp);
				if (tmp = nextbytes(16))
					dump_current(tmp);
				if (tmp = nextbytes(16))
					dump_current(tmp);
				if (tmp = nextbytes(16))
					dump_current(tmp);
				if (tmp = nextbytes(16))
					dump_current(tmp);
				if (tmp = nextbytes(16))
					dump_current(tmp);
				if (tmp = nextbytes(16))
					dump_current(tmp);
			}
		}
		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
		fprintf(stderr, "Transport sync not found\n");
	
}

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

	if (argc < 5) {
		fprintf(stderr,
			"usage: %s mpegfile videopid"
			" audiopid device [dopal]\n", argv[0]);
		exit(1);
	}
	if ((f = open(argv[1], O_RDONLY)) < 0) {
		perror(argv[1]);
		exit(1);
	}
	v = atoi(argv[2]);
	a = atoi(argv[3]);
	d = atoi(argv[4]);
	if (argc > 5)
		s = atoi(argv[5]);

	/* prime the input file buffer */
	lastpos = read(f, inbuf, 8192);
	if (lastpos < 0) {
		perror(argv[1]);
		exit(1);
	}

	memset(fluff, 0x00, 32768);
	memcpy(fluff, sequence_error, 4);
	//sprintf(outf, "/dev/video%d", d);
	sprintf(outf, "/dev/null");
	if ((videofd = open(outf, O_RDWR)) < 0) {
		perror(outf);
		exit(1);
	}
	p.mode = VID_PLAY_VID_OUT_MODE;
	p.p1 = VIDEO_MODE_NTSC;
	if (s == 1)
		p.p1 = VIDEO_MODE_PAL;
	ioctl(videofd, VIDIOCSPLAYMODE, &p);
	parsempeg(v, a);
	/* 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 */
