/**
 *  GraphTFT plugin for the Video Disk Recorder 
 * 
 *  mpeg2decoder.c - A plugin for the Video Disk Recorder
 *
 *  (c) 2004 Lars Tegeler, Sascha Volkenandt  
 *
 * This code is distributed under the terms and conditions of the
 * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details.
 *
 * $Id: mpeg2decoder.c,v 1.3 2007/11/10 09:14:27 root Exp $
 *
 **/

//The most part of this Code is from the Softdevice Plugin:

#include <sys/time.h>

#include "mpeg2decoder.h"
#include "common.h"
#include <vdr/plugin.h>


extern "C" 
{
#include <avcodec.h>

# ifdef HAVE_SWSCALE
#  include <swscale.h>
# endif
}

#define UNSYNCED 0
#define PAYLOAD 100
#define PAYLOADDATA 200
#define HEADER 300
#define OPTHEADER 400
#define STREAM 500


#define GET_MPEG2_PTS(x)   ( ((uint64_t)x[4]&0xFE) >>1     | \
     		    	              ((uint64_t)x[3])      <<7     | \
                             ((uint64_t)x[2]&0xFE) <<14    | \
                             ((uint64_t)x[1])      <<22    | \
                             ((uint64_t)x[0]&0x0E) <<29 )

uint64_t getTimeMilis(void) 
{
   struct timeval tv;
   gettimeofday(&tv,NULL);

   return (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

// --- cStreamDecoder -----------------------------------------------------------

cStreamDecoder::cStreamDecoder(unsigned int StreamID)
{
    streamID = StreamID;
    frame=0;
}

cStreamDecoder::~cStreamDecoder() 
{
}

void cStreamDecoder::SyncPTS(uint64_t spts) 
{
    int diff= (int)(pts-spts);

    if (abs(diff) > 100) 
       pts=spts;
}

int cStreamDecoder::ParseStream(uchar *Data, int Length) 
{
	int len, streamlen;
	uint8_t *inbuf_ptr;                                 
   inbuf_ptr = (uint8_t *)Data;
	int size=Length;

   while (size > 0) 
   {
      len=1;

      switch (state) 
      {
         // here we found a sync header

         case UNSYNCED:
            syncword = (syncword << 8) | *inbuf_ptr;

            if ( syncword == streamID ) 
               state=HEADER;

            break;    
         case HEADER:		//Payload length(hi)
            payload = *inbuf_ptr << 8;
            state++;
            break;
         case HEADER+1:		//Payload length(lo)
            payload += *inbuf_ptr;
            state++;
            break;
		
         case HEADER+2:		//weiß ich nicht (immer 0x80 oder 0x85)
            state++;  
            payload--;  
            break;

         case HEADER+3:		// ??
            state++;  
            payload--;  
            break;
		
         case HEADER+4:		// PTS-Header length
            headerLength=*inbuf_ptr;
            state=STREAM;
            hdr_ptr=0;
            validPTS=false;

            if (headerLength) 
            { 
               // we found optional headers (=PTS)

               validPTS=true;
               state=OPTHEADER;  
               hdr_ptr=header;
            }

            payload--;  
            break;
		
         case OPTHEADER:		// save PTS
            payload--;
            *hdr_ptr=*inbuf_ptr;
            hdr_ptr++;
            if (!--headerLength) state=STREAM;  
            break;
		
         case STREAM:
            streamlen=min(payload,size); // Max data that is avaiable
            len=DecodeData(inbuf_ptr,streamlen);
            payload-=len;

            if (payload<=0) 
               state=UNSYNCED;	

            break;    

         default:
            state=UNSYNCED;
      }
      size -= len;
      inbuf_ptr += len;
   }

	return Length;
}

// --- AUDIO --------------------------------------------------------------------------
cAudioStreamDecoder::cAudioStreamDecoder(unsigned int StreamID, /*cAudioOut *AudioOut,*/
                                         uint64_t *commonPTS)
   : cStreamDecoder(StreamID)
{
   //audioOut=AudioOut;

   cPTS=commonPTS;
   codec = avcodec_find_decoder(CODEC_ID_MP2);

   if (!codec) 
   {                                                                                         
      tell(4, "mpeg2decoder.c: Fatal error! Audio codec not found\n");
      exit(1);                                                                                          
   }

   context=avcodec_alloc_context();
   audiosamples=(uint8_t *)malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE);

   if (avcodec_open(context, codec) < 0) 
   {
      tell(4, "mpeg2decoder.c: Fatal error! Could not open audio codec\n");
      exit(1);                                                                                          
   }
}    

void cAudioStreamDecoder::Write(uchar *Data, int Length) 
{
    int size = Length;

    while (size > 0) 
    {
       int len = ParseStream(Data,size);
       Data += len;
       size -= len;
    }
} 

int cAudioStreamDecoder::DecodeData(uchar *Data, int Length) 
{
   int len;
   int audio_size;

   len = avcodec_decode_audio(context, (short*)audiosamples, &audio_size, Data, Length);

   if (audio_size > 0) 
   {
      //fwrite(audiosamples,1,audio_size,fo);
      //audioOut->SetParams(2,48000); // FIXME Mono Ton
      //audioOut->Write(audiosamples,audio_size);

      int delay = 0; /*audioOut->GetDelay();*/

      if (delay < 20) 
      { 
         // if we have less than 20 ms in buffer we double frames

         //audioOut->Write(audiosamples,audio_size);
      }

      pts += (audio_size/(48*4)); // PTS weiterzählen, egal ob Samples gespielt oder nicht

      //	d(4, "mpeg2decoder.c: Audiodelay: %d \n",delay);	

      *cPTS = pts - delay; // Das ist die Master-PTS die wird an den video Teil übergeben, 
      // damit Video syncronisieren kann

      if (validPTS) 
         SyncPTS(GET_MPEG2_PTS(header)/90); // milisekunden
   }

   return len;
}


cAudioStreamDecoder::~cAudioStreamDecoder()
{
    avcodec_close(context);
    free(context);
    free(audiosamples);
}

// --- VIDEO --------------------------------------------------------------------

cVideoStreamDecoder::cVideoStreamDecoder(unsigned int StreamID, unsigned char *frame_buffer, 
                                         int fb_type, int res_x, int res_y, /*cVideoOut *VideoOut,*/
                                         uint64_t *commonPTS)
   : cStreamDecoder(StreamID)
#if VDRVERSNUM >= 10300
   , cThread("GraphTFT fbrenderer mpeg2decoder")
#endif
{
   codec = avcodec_find_decoder(CODEC_ID_MPEG2VIDEO);

   if (!codec) 
   {
      tell(4, "mpeg2decoder.c: Fatal error! Video codec not found\n");
      exit(1);                                                                                          
   }

   //videoOut = VideoOut;
   cPTS=commonPTS;
   //syncdevice = SyncDevice;
   context=avcodec_alloc_context();
   picture=avcodec_alloc_frame();
    
   /* prepare necessary buffers */

   const int width = res_x, height = res_y;
	_res_x = res_x;
	_res_y = res_y;
	_fb_type = fb_type;
	_frame_buffer = frame_buffer;
   rgbpic_dat = (uint8_t *) malloc( avpicture_get_size(fb_type, width, height) );
   memset(rgbpic_dat, 0, avpicture_get_size(fb_type, width, height));
   avpicture_fill(&rgbpic, rgbpic_dat, fb_type, width, height);
    
   if(codec->capabilities&CODEC_CAP_TRUNCATED)
      context->flags|= CODEC_FLAG_TRUNCATED; /* we dont send complete frames */

   context->pix_fmt=PIX_FMT_YUV420P;  // patch

   if (avcodec_open(context, codec) < 0) {
      tell(4, "mpeg2decoder.c: Fatal error! Could not open video codec\n");
      exit(1);                                                                                          
   }     

   ringBuffer=new cRingBufferLinear(1024*1024,1024,true);
   Start(); // starte thread	
}    


void cVideoStreamDecoder::Write(uchar *Data, int Length) 
{
  int p;
  p = ringBuffer->Put(Data, Length);

#define BLOCK_BUFFER

#ifdef BLOCK_BUFFER

  while (p != Length) 
  {
    tell(4, "mpeg2decoder.c: Videobuffer is full. This should not happen! Audio is playing too slow\n");
    Length -= p;
    Data += p;
    p = ringBuffer->Put(Data, Length);
    usleep(1);
  }

#endif
}


void cVideoStreamDecoder::Action()
{
#if VDRVERSNUM < 10300
	dsyslog("graphTFT: mpeg2decoder thread started (pid=%d)", getpid());
#endif
   running=true;
   active=true;

   while(active) 
   {
      int size=10240;
      uchar *u =ringBuffer->Get(size);

      if (u) 
      {
         size=ParseStream(u,size);
         ringBuffer->Del(size);
      } 
      else 
      {
         usleep(1000);
      }
   }

   running=false;
#if VDRVERSNUM < 10300
	dsyslog("graphTFT: mpeg2decoder thread ended (pid=%d)", getpid());
#endif
}


void cVideoStreamDecoder::resetCodec(void) 
{
    tell(4, "mpeg2decoder.c: resetting codec\n");
    avcodec_close(context);

    if(codec->capabilities&CODEC_CAP_TRUNCATED)
       context->flags|= CODEC_FLAG_TRUNCATED; /* we dont send complete frames */

    context->pix_fmt=PIX_FMT_YUV420P;

    if (avcodec_open(context, codec) < 0) 
    {
        tell(4, "mpeg2decoder.c: Fatal error! Could not open video codec\n");
        exit(1);                                                                                          
    }     	
}

int cVideoStreamDecoder::DecodeData(uchar *Data, int Length) 
{
   int len;
   int got_picture;

   //    return Length;

   len = avcodec_decode_video(context, picture, &got_picture,Data, Length);

   if (len < 0) 
   {
      tell(4, "mpeg2decoder.c: Error while decoding frame %d\n", frame);
      resetCodec();
      return Length;
   }

   if (got_picture) 
   {
      if (validPTS && context->coded_frame->pict_type == FF_I_TYPE)
         pts=(GET_MPEG2_PTS(header)/90);

      // this few lines does the whole syncing

      int offset = *cPTS - pts;
      if (offset > 1000) offset = 1000;
      if (offset < -1000) offset = -1000;

      avgOffset = (int)( ( (24*avgOffset) + offset ) / 25);
      if (avgOffset > 1000) avgOffset = offset;
      if (avgOffset < -1000) avgOffset = offset;

      int frametime = 40; //ms

      if (avgOffset > 20 && offset > 0)
         frametime -= (avgOffset / 10);

      if (avgOffset < -20 && offset < 0) 
         frametime -= (avgOffset / 10);

      if (!(frame % 10) || context->hurry_up) 
         tell(4, "mpeg2decoder.c: Frame# %-5d A-V(ms) %-5d(Avg:%-4d) Frame Time:%-3d ms Hurry: %d\n",
              frame, (int)(*cPTS-pts), avgOffset, frametime, context->hurry_up);

      //frametime=40;

      pts += 40;
    	vpts += frametime; // 40 ms for PAL   // FIXME FOR NTSC

      int delay;
      delay = vpts - getTimeMilis();

      if (delay < -1000) 
      {
         vpts = getTimeMilis();
         context->hurry_up++;
      } 
      else 
      {
         if (delay > 1000) 
         {
            vpts = getTimeMilis();
         } 
         else 
         {
            while (delay > 0) 
            {
               usleep(1000);
               delay = vpts - getTimeMilis();
            }
         }

         if (frametime == 40 && context->hurry_up) 
            context->hurry_up--;

         if (context->hurry_up < 3) 
         {
            AVPicture yuvpic;

            /* copy the data from avframe to avpicture */

            for (int i=0; i<4; ++i)
            {
               yuvpic.data[i]= picture->data[i];
               yuvpic.linesize[i]= picture->linesize[i];
            }
#ifndef HAVE_SWSCALE

            // int img_convert(AVPicture *dst, int dst_pix_fmt,
            //                const AVPicture *src, int pix_fmt,
            //    int width, int height);

            // convert YUV to RGB

            img_convert(&rgbpic, _fb_type, 
                        &yuvpic, PIX_FMT_YUV420P, 
                        context->width, context->height);

#else

            SwsContext* convert_ctx = sws_getContext(context->width, context->height, PIX_FMT_RGB24, 
                                                     context->width, context->height, 
                                                     PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

            sws_scale(convert_ctx, yuvpic.data, yuvpic.linesize, 0, context->height, 
                      rgbpic.data, rgbpic.linesize);

            sws_freeContext(convert_ctx);

#endif

            //now in rgbpic.data[0] should be the decoded picture  
            fast_memcpy(_frame_buffer, rgbpic.data[0], rgbpic.linesize[0] * _res_y );
         }
      }		
      frame++;
   }
   return len;
}

cVideoStreamDecoder::~cVideoStreamDecoder()
{
    active = false;
    Cancel(3);
    delete(ringBuffer);
    avcodec_close(context);
    free(context);
    free(picture);
}

//----------------------------   create a new MPEG Decoder
cMpeg2Decoder::cMpeg2Decoder(unsigned char *frame_buffer, int fb_type, 
                             int res_x, int res_y 
                             /*cAudioOut *AudioOut, cVideoOut *VideoOut*/) 
{
    avcodec_init();
    avcodec_register_all();
    state=UNSYNCED;
    //audioOut=AudioOut;
    //videoOut=VideoOut;
    aout=vout=0;
    running=false;
    decoding=false;
    _res_x = res_x;
    _res_y = res_y;
	_frame_buffer = frame_buffer;
	_fb_type = fb_type;
}

cMpeg2Decoder::~cMpeg2Decoder()
{
    if (aout) delete(aout);
    aout=0;
    if (vout) delete(vout);  
    vout=0;
}

void cMpeg2Decoder::Start(void)
{
    // ich weiß nicht, ob man's so kompliziert machen soll, aber jetzt hab ich's 
    // schon so gemacht, das 2 Threads gestartet werden.
    // Audio is the master, Video syncs on Audio

    aout = new cAudioStreamDecoder( 0x000001C0, /*audioOut,*/ &commonPTS);
    vout = new cVideoStreamDecoder( 0x000001E0, _frame_buffer, 
                                    _fb_type, _res_x, _res_y, 
                                    /*videoOut,*/ &commonPTS);
    running=true;
}

void cMpeg2Decoder::Stop(void)
{
    if (running) 
    {
       running = false;

       while (decoding) 
          usleep(1);

       if (vout) 
          delete(vout);  
       
       if (aout) 
          delete(aout);
 
      aout=vout=0;
    }
}


int cMpeg2Decoder::Decode(const uchar *Data, int Length) 
{

   int len, streamlen;
   uint8_t *inbuf_ptr;                                 
   inbuf_ptr = (uint8_t *)Data;
   int size=Length;
   decoding=true;

   if (!running) 
   {
      decoding=false;
      return Length;
   }

   while (size > 0) 
   {
      len=1;

      switch (state) 
      {
         case UNSYNCED:
            syncword = (syncword << 8) | *inbuf_ptr;
            if ( (syncword >= 0x000001E0) && (syncword <= 0x000001EF) ) 
            {
               state=PAYLOAD;
               streamtype=0xE0;
            } 
            else 
               if ((syncword >= 0x000001C0) && (syncword <= 0x000001CF)) 
               {
                  state=PAYLOAD;
                  streamtype=0xC0;
               } 
            break;
         case PAYLOAD:		//Payload length(hi)
            payload = *inbuf_ptr << 8;
            state++;
            break;
         case PAYLOAD+1:		//Payload length(lo)
            payload += *inbuf_ptr;
            header[0]=0;
            header[1]=0;
            header[2]=1;
            header[3]=streamtype;
            header[4]=payload >> 8;
            header[5]=payload & 0xFF;

            if (streamtype == 0xE0) 
               vout->Write(header,6);

            if (streamtype == 0xC0) 
               aout->Write(header,6);

            state=PAYLOADDATA;

            break;
         case PAYLOADDATA:
            streamlen =min(payload,size);

            if (streamtype == 0xE0)
               vout->Write(inbuf_ptr,streamlen);

            if (streamtype == 0xC0) 
               aout->Write(inbuf_ptr,streamlen);

            payload-=streamlen;
            len = streamlen;

            if (payload <= 0) 
               state=UNSYNCED;
            break;
      }

    	size -= len;
      inbuf_ptr += len;
   }

   decoding=false;

   return Length;
}
