//++MTY use e.g., mpegcat -dV /video/1011_20030808233000_20030809000000.nuv

/*
#
# MPEG2Parser class testing tool
#
# $Id: mpegindex.c,v 1.2.2.1 2004/04/01 03:46:13 knup Exp $
#
# Copyright (C) 2001-2003 Kees Cook
# kees@outflux.net, http://outflux.net/
# 
# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
# http://www.gnu.org/copyleft/gpl.html
#
*/

/*
 * buffer management inspired by mplayer
 */
#define _FILE_OFFSET_BITS 64
#include <stdio.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/uio.h>
#include <string.h>
#include <errno.h>
#include "mpeg2structs.h"

// general stuff
int debug = 0;
int dvd_names = 0;

// file position options
loff_t begin_at = 0;
loff_t num_bytes = 0;

typedef enum {
	PACK_NONE,
	PACK_SPECIAL,
	PACK_PES_SIMPLE,	// packet length == data length
	PACK_PES_COMPLEX,	// crazy headers need skipping
} packet_type;

typedef enum {
	SUBID_NONE,		// no sub id (sid=0)
	SUBID_DATA,		// first PES data byte
} subid_type;

// FIXME: 
//   - add "valid scope" flags (eg some markers only valid in Video stream)
//   - add "struct size" field for auto-forwarding past each marker
typedef struct {
	// the byte value match for the packet tags
	uint8_t code_match_lo;	// low end of the range of matches
	uint8_t code_match_hi;	// high end of the range of matches

	// what kind of PES is it?
	packet_type packet;

	// how do we find the stream sub id
	subid_type subid_method;

	// misc stuff... sorting types?
} packet_tag_info;

packet_tag_info packet_tags[] = {
	{0x00, 0x00, PACK_SPECIAL, SUBID_NONE},	// pic start
	{0x01, 0xAF, PACK_SPECIAL, SUBID_NONE},	// video slices
	{0xB0, 0xB1, PACK_SPECIAL, SUBID_NONE},	// reserved
	{0xB2, 0xB5, PACK_SPECIAL, SUBID_NONE},	// user data, sequences
	{0xB6, 0xB6, PACK_SPECIAL, SUBID_NONE},	// reserved
	{0xB7, 0xB9, PACK_SPECIAL, SUBID_NONE},	// sequence, gop, end
	{0xBA, 0xBA, PACK_SPECIAL, SUBID_NONE},	// pack
	{0xBB, 0xBB, PACK_PES_SIMPLE, SUBID_NONE},	// system: same len as PES
	{0xBC, 0xBC, PACK_PES_SIMPLE, SUBID_NONE},	// PES: prog stream map
	{0xBD, 0xBD, PACK_PES_COMPLEX, SUBID_DATA},	// PES: priv 1
	{0xBE, 0xBF, PACK_PES_SIMPLE, SUBID_NONE},	// PES: padding, priv 2
	{0xC0, 0xDF, PACK_PES_COMPLEX, SUBID_NONE},	// PES: Audio
	{0xE0, 0xEF, PACK_PES_COMPLEX, SUBID_NONE},	// PES: Video
	{0xF0, 0xF2, PACK_PES_SIMPLE, SUBID_NONE},	// PES: ecm, emm, dsmcc
	{0xF3, 0xF7, PACK_PES_COMPLEX, SUBID_NONE},	// PES: iso 13522/h2221a-d
	{0xF8, 0xF8, PACK_PES_SIMPLE, SUBID_NONE},	// PES: h2221e
	{0xF9, 0xF9, PACK_PES_COMPLEX, SUBID_NONE},	// PES: ancillary
	{0xFA, 0xFE, PACK_PES_SIMPLE, SUBID_NONE},	// PES: reserved
	{0xFF, 0xFF, PACK_PES_SIMPLE, SUBID_NONE},	// PES: prog stream dir
	{0, 0, PACK_NONE, SUBID_NONE}	// end of list
};

#define BUFFER_SIZE	(1024*512)
FILE *fp = NULL;
uint8_t buffer[BUFFER_SIZE];
loff_t offset = 0;
size_t buffer_min = 0;
size_t buffer_max = 0;

loff_t buffer_tell()
{
	return offset - buffer_max + buffer_min;
}

// can point to a file offset to refill from
void buffer_refill()
{
	size_t got;

	//fprintf(stderr,"filling...\n");

	if (buffer_min < buffer_max) {
		memmove(buffer, buffer + buffer_min, buffer_max - buffer_min);
	}
	buffer_max -= buffer_min;
	buffer_min = 0;

	got =
	    fread(buffer + buffer_max, sizeof(uint8_t),
		  BUFFER_SIZE - buffer_max, fp);
	if (got < 0) {
		perror("fread");
		exit(2);
	}
	offset += got;
	buffer_max += got;

	//fprintf(stderr,"bump %d more\n",got);
}

void buffer_seek(loff_t location)
{
	// is this location within our current buffer?
	if (location < offset && location >= buffer_tell()) {
		buffer_min += location - buffer_tell();
	} else {
		// outside: clear the buffer 
		buffer_min = buffer_max = 0;	// clear buffer
		offset = location;
		if (fseeko(fp, offset, SEEK_SET) < 0) {
			fprintf(stderr, "fseeko failed: err = %s\n",
				strerror(errno));
			exit(1);
		}
	}
	//don't do this... way too slow
	//buffer_refill();
}

int buffer_start(char *filename)
{
	if (fp)
		fclose(fp);
	fp = NULL;

	if (!(fp = fopen(filename, "r"))) {
		perror(filename);
		return -1;
	}

	offset = 0;
	buffer_min = 0;
	buffer_max = 0;
	return 0;
}

int buffer_get_byte(uint8_t * byte)
{
	if (!byte)
		return -2;
	if (buffer_min == buffer_max)
		buffer_refill();
	if (buffer_min == buffer_max)
		return -1;

	*byte = buffer[buffer_min++];
	return 0;
}

int buffer_look_ahead(uint8_t * bytes, int count)
{
	int i;

	if (!bytes)
		return -2;
	if (count <= 0)
		return 0;
	if (count >= BUFFER_SIZE)
		return -3;

	if (buffer_min + count > buffer_max)
		buffer_refill();
	if (buffer_min + count > buffer_max)
		return -1;

	for (i = 0; i < count; i++) {
		*(bytes++) = buffer[buffer_min + i];
		//fprintf(stderr,"\tsaw: 0x%02X\n",buffer[buffer_min+i]);
	}

	return 0;
}

uint8_t packet_buffer[65536];	// max size of a double byte
uint8_t *packet_start;
uint16_t packet_size;

loff_t file_offset_of_last_packet = 0;
uint32_t framecount = 0;

typedef union {
	struct {
		uint32_t padding:5, broken:1, closed:1, frame:6, second:6,
		    padding2:1, minute:6, hour:5, drop:1;
	};
	struct {
		uint32_t data;
	};
} gop_header_t;

struct mpeg_index_entry {
	unsigned int frame;
	gop_header_t timestamp;
	unsigned long long offset;
};

FILE *indexfd = NULL;
struct mpeg_index_entry last_index_written;
int last_was_gop_packet = 0;

static inline uint32_t bswap(uint32_t x)
{
      asm("bswap %0": "=r"(x):"0"(x));
	return x;
}

char *timestamp_to_string(char *str, gop_header_t timestamp)
{
	static char buf[256];
	char *p = (str) ? str : buf;
	snprintf(p, sizeof(buf), "%d:%02d:%02d:%d", timestamp.hour,
		 timestamp.minute, timestamp.second, timestamp.frame);
	return p;
}

static inline gop_header_t make_timestamp(int hour, int minute, int second,
					  int frame)
{
	gop_header_t gop;
	gop.hour = hour;
	gop.minute = minute;
	gop.second = second;
	gop.frame = frame;
	return gop;
}

loff_t process_packet(uint8_t code)
{
	uint8_t bytes[32];
	int i;

	unsigned int length, header_len;
	loff_t position = 0;

	//unsigned long long system_clock_ref_base;   // "H"igh, "M"edium, "L"ow bits
	//unsigned long system_clock_ref_ext; // "E"
	//unsigned int program_mux_rate;      // "R"
	//unsigned int pack_stuffing_length;  // "S"

	loff_t file_offset = buffer_tell() - 4;

	// initialize "unknown" packet length
	header_len = length = 0;

	last_was_gop_packet = 0;
	// find the packet type
	for (i = 0; packet_tags[i].packet != PACK_NONE; i++) {
		if (code >= packet_tags[i].code_match_lo
		    && code <= packet_tags[i].code_match_hi) {
			int sid = 0;

			switch (packet_tags[i].packet) {
			case PACK_SPECIAL:
				// actually, nothing special
				break;
			case PACK_PES_SIMPLE:
			case PACK_PES_COMPLEX:
				if (packet_tags[i].packet == PACK_PES_COMPLEX) {
					if (buffer_look_ahead(bytes, 5) != 0)
						return 0;
					header_len = 5 + bytes[4];

					if (dvd_names
					    && packet_tags[i].subid_method ==
					    SUBID_DATA
					    && buffer_look_ahead(bytes,
								 header_len +
								 1) == 0) {
						sid = bytes[header_len];
					}
				} else {
					if (buffer_look_ahead(bytes, 2) != 0)
						return 0;
				}
				length = bytes[1] | (bytes[0] << 8);

				// get position updated for next packet location
				if ((code & PES_TYPE_MASK_video) ==
				    PES_TYPE_video)
					break;	// skip position update
				position = buffer_tell() + length + 2;
				break;
			default:
			case PACK_NONE:
				// FIXME: not possible!
				break;
			}

			break;	// found a match
		}
	}

	if (code == PES_TYPE_pack_start) {
		//++MTY
		file_offset_of_last_packet = file_offset;

		if (buffer_look_ahead(bytes, 10) == 0) {

			//|   0   |   1   |   2   |   3   |   4   |   5   |   6   |
			// 76543210765432107654321076543210765432107654321076543210
			// xxHHHxMMMMMMMMMMMMMMMxLLLLLLLLLLLLLLLxEEEEEEEEExRRRRRRRR
			//   333 222222222211111 111110000000000 000000000 22111111// bit
			//   210 987654321098765 432109876543210 876543210 10987654//     number
			//     -  -       -    -  -       -    -  -      -        -
			//     2  2       2    1  1       0    -  0      -        1// left
			//     7  8       0    2  3       5    3  7      1        4//      shift
			//
			//|   7   |   8   |   9   |
			// 765432107654321076543210
			// RRRRRRRRRRRRRRxxrrrrrSSS
			// 11110000000000  00000000   // bit
			// 32109876543210  43210210   //     number
			//        -     -      -  -
			//        0     -      -  0   // left
			//        6     2      3  0   //      shift

#if 0
			int system_clock_ref_base = (((bytes[0] & 0x38) << 27) |
						     ((bytes[0] & 0x03) << 28) |
						     ((bytes[1] & 0xFF) << 20) |
						     ((bytes[2] & 0xF8) << 12) |
						     ((bytes[2] & 0x03) << 13) |
						     ((bytes[3] & 0xFF) << 5) |
						     ((bytes[4] & 0xF8) >> 3));
			int system_clock_ref_ext = (((bytes[4] & 0x03) << 7) |
						    ((bytes[5] & 0xFE) >> 1));
			int program_mux_rate = (((bytes[6] & 0xFF) << 14) |
						((bytes[7] & 0xFF) << 6) |
						((bytes[8] & 0xFC) >> 2));
			int pack_stuffing_length = (bytes[9] & 0x07);
#endif

			//sprintf(answer, "Pack Start (Mux Rate: %u Stuffing: %u)",
			// FIXME: too many damn bits
			//                                system_clock_ref_base,
			//                                system_clock_ref_ext,
			//program_mux_rate, pack_stuffing_length);
			//return answer;
		} else {
			//return "Pack Start (incomplete flags)";
		}
	} else if (code == PES_TYPE_group_start) {
		//if (buffer_look_ahead(bytes, 4) == 0) {
		gop_header_t gop;
		if (buffer_look_ahead((uint8_t *)&gop, sizeof(gop)) == 0) {
			gop.data = bswap(gop.data);
			/*
			   int drop = ((bytes[0] & 0x80) > 0);
			   int hour = ((bytes[0] & 0x7C) >> 2);
			   int min = ((bytes[0] & 0x3) << 4) | ((bytes[1] & 0xF0) >> 4);
			   int sec = ((bytes[1] & 0x7) << 3) | ((bytes[2] & 0xE0) >> 6);
			   int pictures = ((bytes[2] & 0x3F) << 1) | ((bytes[3] & 0x80) >> 7);
			   int closed = ((bytes[3] & 0x40) > 0);
			   int broken = ((bytes[3] & 0x20) > 0);
			 */

			/*
			   sprintf(answer, "  GOP Start (%02d:%02d:%02d.%02d%s%s%s)", hour, min, sec, pictures, drop ? " drop" : "", closed ? " closed" : " open", broken ? " broken" : "");
			 */

			gop.padding2 = gop.padding = gop.closed = gop.broken =
			    gop.drop = 0;
			last_index_written.frame = framecount;
			last_index_written.timestamp = gop;	//((unsigned long long)((gop.hour * (60*60)) + (gop.minute*60) + gop.second) << 32LL) + gop.frame;
			last_index_written.offset = file_offset_of_last_packet;

			if (indexfd)
				fwrite(&last_index_written,
				       sizeof(last_index_written), 1, indexfd);
			last_was_gop_packet = 1;
			//printf("==> % 15lld: GOP 0x%08x (%02d:%02d:%02d.%02d) on frame %d; %s %s %s\n", file_offset_of_last_packet, (int)(last_index_written.timestamp.data), gop.hour, gop.minute, gop.second, gop.frame,
			//framecount, (gop.closed) ? "closed" : "", (gop.broken) ? "broken" : "", (gop.drop) ? "drop" : "");
		} else {
			//return "  GOP Start (incomplete flags)";
		}
	} else if (code == PES_TYPE_picture_start) {
		framecount++;
	}

	return position;
}

int main(int argc, char *argv[])
{
	uint32_t marker;
	uint8_t byte;

	int running = 1;

	if (argc < 3) {
		fprintf(stderr,
			"mpegindex: Syntax is mpegindex mpegfile indexfile\n\n");
		return -1;
	}

	indexfd = fopen(argv[2], "w");
	if (!indexfd) {
		fprintf(stderr,
			"mpegindex: Error: cannot create index file %s.\n",
			argv[2]);
		return -2;
	}

	if (buffer_start(argv[1]) < 0) {
		fprintf(stderr, "mpegindex: Error: cannot open mpeg file %s.\n",
			argv[1]);
		return -3;
	}

	buffer_seek(begin_at);

	marker = 0xFFFFFFFF;
	while (running) {
		//fprintf(stderr,"%08X\n",marker);
		if ((marker & 0xFFFFFF00) == 0x100) {

			loff_t newpos = process_packet(byte);
			// we skipped to a new location?
			if (newpos != 0) {
				//fprintf(stdout,"skipping to %" OFF_T_FORMAT "\n",newpos);
				marker = 0xFFFFFFFF;
				buffer_seek(newpos);
			}
		}
		marker <<= 8;
		if (buffer_get_byte(&byte) < 0) {
			running = 0;
		} else
			marker |= byte;

		if (num_bytes && buffer_tell() - begin_at > num_bytes) {
			running = 0;
		}

		if (last_was_gop_packet
		    && ((last_index_written.timestamp.second % 30) == 0)) {
			fprintf(stdout,
				"\r%s: Processed frame %d at %s (file offset %lld)",
				argv[1], last_index_written.frame,
				timestamp_to_string(NULL,
						    last_index_written.
						    timestamp),
				last_index_written.offset);
			fflush(stdout);
		}
	}

	fprintf(stdout,
		"\r%s: Processed %d frames covering time %s (file size %lld)\n\n",
		argv[1], last_index_written.frame, timestamp_to_string(NULL,
								       last_index_written.
								       timestamp),
		last_index_written.offset);

	fclose(indexfd);
	return 0;
}
