/*==========================[ (c) ALPHA SOFT ]==================================

FICHIER     : [mpeg2dec.c]

DATE        : 2007/08/0014 21:44:00

CREATEUR    : [Linux!jef]

COMMENTAIRE :
		Released under GPL license, see gnu.org
================================================================================

==============================================================================*/
#include "../config.h"
#include <gnome.h>

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <inttypes.h>

#ifdef HAVE_LIBMPEG2

#include <pthread.h>
#include <semaphore.h>
#include <mpeg2dec/mpeg2.h>
#include <mpeg2dec/mpeg2convert.h>

#include "ac.h"
#include "globals.h"

typedef struct BUFFER_T
{
	size_t size;
	unsigned char * data;
	struct BUFFER_T * next;
} Buffer_t;

typedef struct {
	int		enabled;
	pthread_t	thread;
	sem_t		wSem;			/* Fifo sem */
	pthread_mutex_t aMutex;		/* Mutex for list access */
	mpeg2dec_t *	decoder;
	unsigned int	mpegFrameNo;
	Buffer_t *	head;
	Buffer_t *	tail;
	GtkWidget *	image;
	long long	bufferWrited;
	long long	bufferReaded;
	struct timeval	lastUpdate;
} Mpeg2Decoder_t;

#define IMAGE_REFRESH_SEC		0.5


#define MAX_BUFFERED_SIZE		((long long)( 8 * 1024 * 1024 ))

#define BufferedSize()			( Decoder.bufferWrited - Decoder.bufferReaded )

static Mpeg2Decoder_t Decoder;

#define DEMUX_PAYLOAD_START	1

#define DEMUX_HEADER		0
#define DEMUX_DATA		1
#define DEMUX_SKIP		2

#define NEEDBYTES(x)						\
    do {							\
	int missing;						\
								\
	missing = (x) - bytes;					\
	if (missing > 0) {					\
	    if (header == head_buf) {				\
		if (missing <= end - buffer) {			\
		    tc_memcpy (header + bytes, buffer, missing);	\
		    buffer += missing;				\
		    bytes = (x);				\
		} else {					\
		    tc_memcpy (header + bytes, buffer, end - buffer);	\
		    state_bytes = bytes + end - buffer;		\
		    return( 0 );					\
		}						\
	    } else {						\
		tc_memcpy (head_buf, header, bytes);		\
		state = DEMUX_HEADER;				\
		state_bytes = bytes;				\
		return( 0 );					\
	    }							\
	}							\
    } while (0)

#define DONEBYTES(x)		\
    do {			\
	if (header != head_buf)	\
	    buffer = header + (x);	\
    } while (0)

static int mpeg1_skip_table[16] = {
	0, 0, 4, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

#define DUMP_MPG		0

#if DUMP_MPG
static FILE * Fp = NULL;
static int Mp2Size = 0;
#	define MPG_FILE		"sample.mpg"
#	define MPG_SIZE		(10 * 1024 * 1024 )
#endif

#pragma pack(1)
typedef struct {
	uint8_t r;
	uint8_t g;
	uint8_t b;
} Pixel_t;
#pragma pack()

/*@$#[mpeg2dec.c] static proto. AutoProtoSigV1.1. date: 2009/02/04 23:21:14 */
#include "proto.h"
#ifdef __cplusplus
extern "C" {
#endif
/* mpeg2dec.c */
static void UpdateImage PROTO((int width, int height, Pixel_t *buf));
static void Mpeg2Process PROTO((unsigned char *buffer, unsigned char *end));
static int _Mpeg2Demux PROTO((unsigned char *buffer, size_t size));
static void *Mpeg2Thread PROTO((void *arg));
static void PostBuffer PROTO((Buffer_t *b));
#ifdef __cplusplus
}
#endif
/*@$% end of AutoProtoSigV1.1 (Dont remove this line) [-I ../include]*/

/*------------------------------------------------------------------------------
	UPDATEIMAGE-
Linux!jef 2007/08/20 22:15:55
------------------------------------------------------------------------------*/

static void UpdateImage( int width, int height, Pixel_t * buf )
{
	int sx = width >> 2;
	int sy = height >> 2;
	int x,y;
	Pixel_t * pixels;
	FILE * ppmfile;

	if( ImageReadyF )	return;

// fprintf(stderr,"sx: %d sy: %d\n", sx, sy );
	pixels =(Pixel_t *)malloc( sx * sy * sizeof( *pixels) );
	for( y = 0; y < sy; y++ ) {
		int oy = y << 2;
		int ox;

		for( x = ox = 0; x < sx; x++, ox += 4 ) {
			pixels[ (y * sx) + x ] = buf[ (oy * width) + ox ];
		}
	}
	ppmfile = fopen( PPM_FILE, "wb");
	if( ppmfile ) {
		int dummy;

		fprintf( ppmfile, "P6\n%d %d\n255\n", sx, sy);
		dummy = fwrite( pixels, sizeof(*pixels), sx*sy, ppmfile);
		fclose (ppmfile);
		ImageReadyF = 1;
	}
	free( pixels );
}
/*------------------------------------------------------------------------------
	MPEG2PROCESS-
Linux!jef 2007/08/14 21:46:49
------------------------------------------------------------------------------*/

static void Mpeg2Process( unsigned char * buffer, unsigned char * end )
{
	const mpeg2_info_t * info;
	mpeg2_state_t state;

	mpeg2_buffer( Decoder.decoder, buffer, end );

	info = mpeg2_info( Decoder.decoder );
	if( !info )	return;

	while( 1 ) {
		state = mpeg2_parse( Decoder.decoder );
if( state > 8 )	fprintf(stderr,"##mpeg2_parse: %d\n", state );
		switch( state ) {
			case STATE_BUFFER :
				return;
			case STATE_SEQUENCE :
				mpeg2_convert( Decoder.decoder, mpeg2convert_rgb24, NULL );
				break;
			case STATE_SLICE :
// fprintf(stderr,"mpeg2_parse: %d\n", state );
			case STATE_END :
			case STATE_INVALID_END :

				if( info->display_fbuf ) {
					struct timeval tv;
					double delta;

					gettimeofday( &tv, NULL );
					delta = tv.tv_sec - Decoder.lastUpdate.tv_sec;
					delta += ((double)( tv.tv_usec - Decoder.lastUpdate.tv_usec )) / 1000000.0;
					++Decoder.mpegFrameNo;
//					if( !( Decoder.mpegFrameNo % 100 ) ) {
					if( delta >= IMAGE_REFRESH_SEC  ) {
						if( Decoder.enabled ) {
							UpdateImage(	info->sequence->width,
									info->sequence->height,
									(Pixel_t *)info->display_fbuf->buf[0] );
						}
					}
				}

				break;
			default :
				break;
		}
	}
}

/*------------------------------------------------------------------------------
	MPEG2DEMUX-
Linux!jef 2007/08/15 22:32:30
------------------------------------------------------------------------------*/

static int _Mpeg2Demux( unsigned char * buffer, size_t size )
{
	static int state = DEMUX_SKIP;
	static int state_bytes = 0;
	static uint8_t head_buf[264];

	uint8_t * end = buffer + size;
	uint8_t * header;
	int bytes;
	int len;

#if DUMP_MPG
	if( Fp ) {
		fwrite( buffer, 1, size, Fp );
		Mp2Size += size;
		if( Mp2Size >= MPG_SIZE ) {
			fclose( Fp );
			Fp = NULL;
		}
	}
#endif
	if( !Decoder.decoder )	return(0);

	switch (state) {
		case DEMUX_HEADER:
			if( state_bytes > 0 ) {
				header = head_buf;
				bytes = state_bytes;
				goto continue_header;
			}
			break;
		 case DEMUX_DATA:
			if( state_bytes > end - buffer ) {
				Mpeg2Process( buffer, end);
				state_bytes -= end - buffer;
				return( 0 );
			}
			Mpeg2Process( buffer, buffer + state_bytes );
			buffer += state_bytes;
			break;
		case DEMUX_SKIP:
			if( state_bytes > end - buffer ) {
				state_bytes -= end - buffer;
				return( 0 );
			}
			buffer += state_bytes;
			break;
	}

	while (1) {
payload_start:
		header = buffer;
		bytes = end - buffer;
continue_header:
		NEEDBYTES (4);
		if( header[0] || header[1] || (header[2] != 1)) {
			if (header != head_buf) {
				buffer++;
				goto payload_start;
			} else {
				header[0] = header[1];
				header[1] = header[2];
				header[2] = header[3];
				bytes = 3;
				goto continue_header;
			}
		}
		switch (header[3]) {
			case 0xb9:	/* program end code */
				return( 1 );
			case 0xba:	/* pack header */
				NEEDBYTES (5);
				if ((header[4] & 0xc0) == 0x40) {	/* mpeg2 */
					NEEDBYTES (14);
					len = 14 + (header[13] & 7);
					NEEDBYTES (len);
					DONEBYTES (len);
					/* header points to the mpeg2 pack header */
				} else if ((header[4] & 0xf0) == 0x20) {	/* mpeg1 */
					NEEDBYTES (12);
					DONEBYTES (12);
					/* header points to the mpeg1 pack header */
				} else {
					fprintf (stderr, "##weird pack header\n");
					DONEBYTES (5);
				}
				break;
			default:
				if (header[3] == 0xe0 ) {
					NEEDBYTES (7);
					if ((header[6] & 0xc0) == 0x80) {	/* mpeg2 */
						NEEDBYTES (9);
						len = 9 + header[8];
						NEEDBYTES (len);
						/* header points to the mpeg2 pes header */
						if (header[7] & 0x80) {
							uint32_t pts, dts;

							pts = (((header[9] >> 1) << 30) |
								(header[10] << 22) | ((header[11] >> 1) << 15) |
								(header[12] << 7) | (header[13] >> 1));
							dts = (!(header[7] & 0x40) ? pts :
								(((header[14] >> 1) << 30) |
								(header[15] << 22) |
								((header[16] >> 1) << 15) |
								(header[17] << 7) | (header[18] >> 1)));
							mpeg2_tag_picture( Decoder.decoder, pts, dts);
						}
					} else {	/* mpeg1 */
						int len_skip;
						uint8_t * ptsbuf;

						len = 7;
						while (header[len - 1] == 0xff) {
							len++;
							NEEDBYTES (len);
							if (len > 23) {
								fprintf (stderr, "##too much stuffing\n");
								break;
							}
						}
						if ((header[len - 1] & 0xc0) == 0x40) {
							len += 2;
							NEEDBYTES (len);
						}
						len_skip = len;
						len += mpeg1_skip_table[header[len - 1] >> 4];
						NEEDBYTES (len);
						/* header points to the mpeg1 pes header */
						ptsbuf = header + len_skip;
						if ((ptsbuf[-1] & 0xe0) == 0x20) {
							uint32_t pts, dts;

							pts = (((ptsbuf[-1] >> 1) << 30) |
								(ptsbuf[0] << 22) | ((ptsbuf[1] >> 1) << 15) |
								(ptsbuf[2] << 7) | (ptsbuf[3] >> 1));
							dts = (((ptsbuf[-1] & 0xf0) != 0x30) ? pts :
								(((ptsbuf[4] >> 1) << 30) |
								(ptsbuf[5] << 22) | ((ptsbuf[6] >> 1) << 15) |
								(ptsbuf[7] << 7) | (ptsbuf[18] >> 1)));
							mpeg2_tag_picture( Decoder.decoder, pts, dts);
						}
					}
					DONEBYTES (len);
					bytes = 6 + (header[4] << 8) + header[5] - len;
					if( bytes > end - buffer ) {
						Mpeg2Process( buffer, end);
						state = DEMUX_DATA;
						state_bytes = bytes - (end - buffer);
						return( 0 );
					} else if (bytes > 0) {
						Mpeg2Process(buffer, buffer + bytes);
						buffer += bytes;
					}
				} else if (header[3] < 0xb9) {
					fprintf (stderr,"##looks like a video stream, not system stream\n");
					DONEBYTES (4);
				} else {
					NEEDBYTES (6);
					DONEBYTES (6);
					bytes = (header[4] << 8) + header[5];
					if (bytes > end - buffer) {
						state = DEMUX_SKIP;
						state_bytes = bytes - (end - buffer);
						return( 0 );
					}
					buffer += bytes;
				}
		}
	}
	return( 0 );
}
/*------------------------------------------------------------------------------
	MPEG2THREAD-
Linux!jef 2007/08/20 21:18:24
------------------------------------------------------------------------------*/

static void * Mpeg2Thread( void * arg )
{
	Decoder.decoder = mpeg2_init();

// fprintf(stderr,"accel=%d\n", mpeg2_accel(0));

	Decoder.mpegFrameNo = 0;
#if DUMP_MPG
	Fp = fopen( MPG_FILE, "w" );
	Mp2Size = 0;
#endif
	while( 1 ) {
		Buffer_t * b;

		sem_wait( &Decoder.wSem );

		pthread_testcancel();

		if( Decoder.head ) {
			pthread_mutex_lock( &Decoder.aMutex );
			b = Decoder.head;
			Decoder.head = b->next;
			if( !Decoder.head )	Decoder.tail = NULL;
			pthread_mutex_unlock( &Decoder.aMutex );
			if( b->size == -1 ) {
				free( b );
				break;
			}
			_Mpeg2Demux( b->data, b->size );
			Decoder.bufferReaded += b->size;

			free( b->data );
			free( b );
		}
	}
	if( Decoder.decoder ) {
		mpeg2_close( Decoder.decoder );
		Decoder.decoder = NULL;
	}
#if DUMP_MPG
	if( Fp ) {
		fclose( Fp );
		Fp = NULL;
	}
#endif
// fprintf(stderr,"Buffered size: %lld\n", BufferedSize() );
	unlink( PPM_FILE );
	return( NULL );
}
/*------------------------------------------------------------------------------
	POSTBUFFER-
Linux!jef 2007/08/20 21:32:51
------------------------------------------------------------------------------*/

static void PostBuffer( Buffer_t * b )
{
	pthread_mutex_lock( &Decoder.aMutex );
	Decoder.bufferWrited += b->size;
	if( !Decoder.head ) {
		Decoder.head = Decoder.tail = b;
	}
	else {
		Decoder.tail->next = b;
		Decoder.tail = b;
	}
	pthread_mutex_unlock( &Decoder.aMutex );
	sem_post( &Decoder.wSem );
}


/*------------------------------------------------------------------------------
	MPEG2DEMUX-
Linux!jef 2007/08/15 22:32:30
------------------------------------------------------------------------------*/

int Mpeg2Demux( unsigned char * buffer, size_t size )
{
	if( Decoder.enabled && BufferedSize() < MAX_BUFFERED_SIZE ) {
		Buffer_t * b = (Buffer_t *)calloc( 1, sizeof( *b ));

		b->data = (unsigned char * )malloc( size );
		tc_memcpy( b->data, buffer, size );
		b->size = size;

		PostBuffer( b );
	}
	return( 0 );
}

/*------------------------------------------------------------------------------
	MPEG2INIT-
Linux!jef 2007/08/14 21:45:37
------------------------------------------------------------------------------*/

void Mpeg2Init()
{
	pthread_mutexattr_t attr;

	ImageReadyF = 0;
	Decoder.enabled = 0;
	Decoder.bufferReaded = Decoder.bufferWrited = 0;
	gettimeofday( &Decoder.lastUpdate, NULL );
	sem_init( &Decoder.wSem, 0, 0 );
	pthread_mutexattr_init( &attr );
	pthread_mutex_init( &Decoder.aMutex, &attr );
	pthread_create( &Decoder.thread, NULL, Mpeg2Thread, (void *)&Decoder );
}

/*------------------------------------------------------------------------------
	MPEG2SETSTATE-
Linux!jef 2007/08/20 21:49:26
------------------------------------------------------------------------------*/

void Mpeg2SetState( int state )
{
	Decoder.enabled = state;
}

/*------------------------------------------------------------------------------
	MPEG2END-
Linux!jef 2007/08/14 21:45:51
------------------------------------------------------------------------------*/

void Mpeg2End()
{
	Buffer_t * b = (Buffer_t *)calloc( 1, sizeof( *b ));
	b->size = -1;

	PostBuffer( b );
}

#else
/*------------------------------------------------------------------------------
	MPEG2INIT-
Linux!jef 2007/08/14 21:45:37
------------------------------------------------------------------------------*/

void Mpeg2Init()
{
}

/*------------------------------------------------------------------------------
	MPEG2END-
Linux!jef 2007/08/14 21:45:51
------------------------------------------------------------------------------*/

void Mpeg2End()
{
}
/*------------------------------------------------------------------------------
	MPEG2PROCESS-
Linux!jef 2007/08/14 21:46:49
------------------------------------------------------------------------------*/

void Mpeg2Demux( unsigned char * buffer, size_t size )
{
}
/*------------------------------------------------------------------------------
	MPEG2SETSTATE-
Linux!jef 2007/08/20 21:49:26
------------------------------------------------------------------------------*/

void Mpeg2SetState( int state )
{
}
#endif
