/*
 * mpeg2dec.c
 * Copyright (C) 1999-2001 Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
 *
 * This file is part of mpeg2dec, a free MPEG-2 video stream decoder.
 *
 * mpeg2dec 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.
 *
 * mpeg2dec 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
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <OSLib:os.h>

#include "inttypes.h"


#include "video_out.h"
#include "mpeg2.h"
#include "mm_accel.h"
#include "mpglib.h"
#include "soundcode.h"

#define BUFFER_SIZE 262144
static uint8_t buffer[BUFFER_SIZE];

#define SNDBUFFER_SIZE 8192
static char sbuf[SNDBUFFER_SIZE];
struct mpstr mp;
//FILE *sout;

static long freqs[9] = { 44100, 48000, 32000,
                  22050, 24000, 16000 ,
                  11025 , 12000 , 8000 };

int fSound=0;
int fSoundSucc=0;
int sRate=4;

#ifdef REALTIME
#include "timer1.h"
extern int video_step;
int soundfreq;
#endif

int normalExit=0;

static FILE * in_file;
static uint32_t frame_counter = 0;

static os_t tv_beg, tv_end, tv_start;
static uint32_t elapsed;
static uint32_t total_elapsed;
static uint32_t last_count = 0;
static uint32_t demux_ps = 0;
static mpeg2dec_t mpeg2dec;
static vo_open_t * output_open = NULL;
static vo_instance_t * output;


static void print_fps (int final)
{
    int fps, tfps, frames;

    tv_end = os_read_monotonic_time();

    if (frame_counter++ == 0) {
	tv_start = tv_beg = tv_end;
    }

    elapsed = tv_end - tv_beg;
    total_elapsed = tv_end - tv_start;

    if (final) {
	if (total_elapsed)
	    tfps = frame_counter * 10000 / total_elapsed;
	else
	    tfps = 0;

	fprintf (stderr,"\n%d frames decoded in %d.%02d "
		 "seconds (%d.%02d fps)\n", frame_counter,
		 total_elapsed / 100, total_elapsed % 100,
		 tfps / 100, tfps % 100);

	return;
    }

    if (elapsed < 50)	/* only display every 0.50 seconds */
	return;

    tv_beg = tv_end;
    frames = frame_counter - last_count;

    fps = frames * 10000 / elapsed;			/* 100x */

//    if ((fps/100)<15) mpeg2_drop(&mpeg2dec,1);
//    else mpeg2_drop(&mpeg2dec,0);

    tfps = frame_counter * 10000 / total_elapsed;	/* 100x */
/*
    fprintf (stderr, "%d frames in %d.%02d sec (%d.%02d fps), "
	     "%d last %d.%02d sec (%d.%02d fps)\033[K\r", frame_counter,
	     total_elapsed / 100, total_elapsed % 100,
	     tfps / 100, tfps % 100, frames, elapsed / 100, elapsed % 100,
	     fps / 100, fps % 100);
 */
    last_count = frame_counter;
}

static void print_usage (char * argv[])
{
    int i;
    vo_driver_t * drivers;

    fprintf (stderr, "usage: %s [-o mode] [-s] file\n"
	     "\t-s\tuse program stream demultiplexer\n"
	     "\t-o\tvideo output mode\n", argv[0]);

    drivers = vo_drivers ();
    for (i = 0; drivers[i].name; i++)
	fprintf (stderr, "\t\t\t%s\n", drivers[i].name);

    exit (1);
}

static void handle_args (int argc, char * argv[])
{
//    int c;
    vo_driver_t * drivers;
    int i,j,f;
    f = 0;
    j=1;
    drivers = vo_drivers ();
    while(j<argc){
        if (strcmp(argv[j],"-o")==0) {
	  for (i=0; drivers[i].name != NULL; i++)
	    if (strcmp (drivers[i].name, argv[j+1]) == 0)
	      output_open = drivers[i].open;
	  if (output_open == NULL) {
	      fprintf (stderr, "Invalid video driver: %s\n", argv[j+1]);
	      print_usage (argv);
	  }
	  j+=2;
        } else if (strcmp(argv[j],"-s")==0) {
          demux_ps = 1;
          j++;
        } else if (strcmp(argv[j],"-S")==0) {
          f = 1;
          j++;
        } else if (strcmp(argv[j],"-h")==0) {
          print_usage(argv);
        } else {
          break;
        }
    }

    /* -o not specified, use a default driver */
    if (output_open == NULL)
	output_open = drivers[0].open;

    if (j < argc) {
	in_file = fopen (argv[j], "rb");
	if (!in_file) {
	    fprintf (stderr, "%s - couldnt open file %s\n", strerror (errno),
		     argv[j]);
	    exit (1);
	}
    } else
	in_file = stdin;
    if (f) {
      unsigned int i;
      fread(&i, 1, 4, in_file);
      fseek(in_file, 0, SEEK_SET);
      if (i==0xB3010000) demux_ps = 0;
      else demux_ps = 1;
    }
}

static void ps_loop (void)
{
    static int mpeg1_skip_table[16] = {
	     1, 0xffff,      5,     10, 0xffff, 0xffff, 0xffff, 0xffff,
	0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff
    };

    register uint8_t * buf;
    register uint8_t * end;
    register uint8_t * tmp1;
    register uint8_t * tmp2;
    register int complain_loudly;

    complain_loudly = 1;
    buf = buffer;

    do {
	end = buf + fread (buf, 1, buffer + BUFFER_SIZE - buf, in_file);
	buf = buffer;

	while (buf + 4 <= end) {
	    /* check start code */
	    if (buf[0] || buf[1] || (buf[2] != 0x01)) {
		if (complain_loudly) {
		    fprintf (stderr, "missing start code at %#lx\n",
			     ftell (in_file) - (end - buf));
		    if ((buf[0] == 0) && (buf[1] == 0) && (buf[2] == 0))
			fprintf (stderr, "this stream appears to use "
				 "zero-byte padding before start codes,\n"
				 "which is not correct according to the "
				 "mpeg system standard.\n"
				 "mp1e was one encoder known to do this "
				 "before version 1.8.0.\n");
		    complain_loudly = 0;
		}
		buf++;
		continue;
	    }

	    switch (buf[3]) {
	    case 0xb9:	/* program end code */
//sash
	        if (buf[7]==0) return;
               	buf += 4;
               	goto copy;
	    case 0xba:	/* pack header */
		/* skip */
		if ((buf[4] & 0xc0) == 0x40)	/* mpeg2 */
		    tmp1 = buf + 14 + (buf[13] & 7);
		else if ((buf[4] & 0xf0) == 0x20)	/* mpeg1 */
		    tmp1 = buf + 12;
		else if (buf + 5 > end)
		    goto copy;
		else {
		    fprintf (stderr, "weird pack header\n");
		    exit (1);
		}
		if (tmp1 > end)
		    goto copy;
		buf = tmp1;
		break;
	    case 0xe0:	/* video */
		tmp2 = buf + 6 + (buf[4] << 8) + buf[5];
		if (tmp2 > end)
		    goto copy;
		if ((buf[6] & 0xc0) == 0x80)	/* mpeg2 */
		    tmp1 = buf + 9 + buf[8];
		else {	/* mpeg1 */
		    for (tmp1 = buf + 6; *tmp1 == 0xff; tmp1++)
			if (tmp1 == buf + 6 + 16) {
			    fprintf (stderr, "too much stuffing\n");
			    buf = tmp2;
			    break;
			}
		    if ((*tmp1 & 0xc0) == 0x40)
			tmp1 += 2;
		    tmp1 += mpeg1_skip_table [*tmp1 >> 4];
		}
		if (tmp1 < tmp2) {
		    int num_frames;

		    num_frames = mpeg2_decode_data (&mpeg2dec, tmp1, tmp2);
#ifndef REALTIME
		    while (num_frames--)
			print_fps (0);
#endif
		}
		buf = tmp2;
		break;
	    case 0xc0: /* audio */
	    {
                register int ret;
	        register int val, pts;
		register int len;
		int ssize, freespace, used;
#ifdef REALTIME
		register int btime;
		btime = read_timer1();
#endif
		len = (buf[4] << 8) + buf[5];
		if ((buf[6] & 0xc0) == 0x80) { //MPEG 2
		  tmp2 = buf + 9 + buf[8];
		  tmp1 = buf + 6 + len;
		  if (tmp1 > end)
		      goto copy;
		} else {                       // MPEG 1
		  tmp2 = buf + 6;
		  tmp1 = tmp2 + len;
		  if (tmp1 > end)
		      goto copy;
		  val = *tmp2++; len--;
		  while (val & 0x80) { val = *tmp2++; len--; }
		  if ((val&0xc0)==0x40) {

			val = *tmp2++; len--;
			val = *tmp2++; len--;

//		        tmp2 += 2; len -= 2;
		  }
		  if ((val & 0xF0) == 0x20) {

			pts = (val & 0xe) << 29 ;
			val = (*tmp2++)<<8; val += *tmp2++; len -= 2;
			pts = (val & 0xFFFE) << 14;
			val = (*tmp2++)<<8; val += *tmp2++; len -= 2;
			pts |= (val & 0xFFFE) >> 1;
//			printf ("pts=%d",pts);

//		        tmp2 += 4; len -= 4;
		  } else if ((val & 0xF0) == 0x30) {

			pts = (val & 0x0e) << 29 ;
			val = (*tmp2++)<<8; val += *tmp2++; len -= 2;
			pts |= (val & 0xFFFE) << 14;
			val = (*tmp2++)<<8; val += *tmp2++; len -= 2;
			pts |= (val & 0xFFFE) >> 1;
//			printf ("pts2=%d\n",pts);
			// Decoding Time Stamp
			val = (*tmp2++)<<16; val += (*tmp2++)<<8; val += *tmp2++; len -= 3;
			val = (*tmp2++)<<8; val += *tmp2++; len -= 2;

//		        tmp2 += 9; len -= 9;
		  } else {
//			printf (", val = %02x",val);
//			if (val != 0x0f) printf (" ERROR val (%02x) != 0x0F ",val);
		  }
		}
//		fwrite(tmp2, 1, len, sout);
//		printf("Audio frag len=%d\n", len);

                if (fSoundSucc){
                  ret = decodeMP3(&mp, (char *)tmp2, len, sbuf, SNDBUFFER_SIZE, &ssize);
                  if (fSound) {
                     soundfreq = freqs[mp.fr.sampling_frequency]/sRate;
                     if (soundcode_play(freqs[mp.fr.sampling_frequency]/sRate, mp.fr.stereo) < 0) {
//                       printf("Can't init sound playback device!\n");
                       fSoundSucc=0;
                     } else fSound=0;
                  }
                  if (fSound==0) {
                    do{
//!                      soundcode_readposition(NULL, &freespace);
//!                      if (freespace >= ssize){
                        soundcode_fill((short *)sbuf, ssize/(mp.fr.stereo<<1), &used, NULL);
//!                      }
                    }while ((ret=decodeMP3(&mp, NULL, 0, sbuf, SNDBUFFER_SIZE, &ssize)) == MP3_OK);
                  }
//                  printf("ret=%d (%d, %d)\n", ret, mp.bsize, mp.framesize);
                }
//                mpeg2_drop(&mpeg2dec,1);
#ifdef REALTIME
                mpeg2dec.drop_flag+=((video_step>>1)+read_timer1()-btime)/video_step;
#endif
//exit(-1);
//		if (tmp1 > end)
//		    goto copy;
		buf = tmp1;
		break;
	    }
	    default:
		if (buf[3] < 0xb9) {
		    fprintf (stderr,
			     "looks like a video stream, not system stream\n");
		    exit (1);
		}
		/* skip */
		tmp1 = buf + 6 + (buf[4] << 8) + buf[5];
		if (tmp1 > end)
		    goto copy;
		buf = tmp1;
		break;
	    }
	}

	if (buf < end) {
	copy:
	    /* we only pass here for mpeg1 ps streams */
	    memmove (buffer, buf, end - buf);
	}
	buf = buffer + (end - buf);

    } while (end == buffer + BUFFER_SIZE);
}

static void es_loop (void)
{
    uint8_t * end;
    int num_frames;

    do {
	end = buffer + fread (buffer, 1, BUFFER_SIZE, in_file);

	num_frames = mpeg2_decode_data (&mpeg2dec, buffer, end);

//	while (num_frames--)
//	    print_fps (0);

    } while (end == buffer + BUFFER_SIZE);
}

static void finishall()
{
    if (normalExit==0) {
        mpeg2_close (&mpeg2dec);
        vo_close (output);
#ifdef REALTIME
        uninstall_timer1();
#endif
        soundcode_stop();
    }
}

int main (int argc,char *argv[])
{
    uint32_t accel;

//    fprintf (stderr, PACKAGE"-"VERSION
//	     " Kino version 0.2 (http://www.eqrd.net)\n");

    handle_args (argc, argv);

    accel = MM_ACCEL_MLIB;

    vo_accel (accel);
    output = vo_open (output_open);
    if (output == NULL) {
	fprintf (stderr, "Can not open output\n");
	return 1;
    }

//    if (soundcode_play(16000, 1) < 0) {
//      printf("Can't init sound playback device!\n");
//      return -1;
//    }
    sRate = 4;    // audio freq will original/sRate
    fSound = 1;   // enable initialize audio device
    fSoundSucc=1; // audio enable

    atexit(finishall);
    InitMP3(&mp, sRate);
#ifdef REALTIME
    install_timer1();
#endif
    normalExit=0;
    mpeg2_init (&mpeg2dec, accel, output);
//    sout = fopen("out/mp2", "wb");

    tv_beg = os_read_monotonic_time();

    if (demux_ps)
	ps_loop ();
    else
	es_loop ();
    normalExit=1;
    mpeg2_close (&mpeg2dec);
    vo_close (output);
#ifdef REALTIME
    uninstall_timer1();
#endif
    soundcode_stop();
//    printf("video_step=%d", video_step);
//    printf("sound freq=%d\n",soundfreq);

//    print_fps (1);
    return 0;
}
