/* Notice: the SpeedControl-code was taken from this post on vdr-portal:
           http://www.vdr-portal.de/board/thread.php?postid=249389#post249389
           and is based on this post: http://lkml.org/lkml/2004/11/24/339
*/
#include <stdio.h>
#include <stdlib.h>
extern "C" {
#ifdef boolean
#define HAVE_BOOLEAN
#endif
#include <jpeglib.h>
#undef boolean
}

#include "common.h"
#include "device.h"
#include "osd.h"
#include "setup.h"
#include "thread.h"
#include "tools.h"

#include <vdr/config.h>
#include <fmp.h>
#include <cregset.h>

#include <sys/ioctl.h>
#include <linux/cdrom.h>


cDeviceEm84xx* cDeviceEm84xx::mInstance = NULL;
int cDeviceEm84xx::mControlDvdSpeed = 0;

DWORD dvd_audiolanguage_codes[] =
{
       RM_LANGUAGECODE_ORIGINAL,
       RM_LANGUAGECODE_ENGLISH,
       RM_LANGUAGECODE_FRENCH,
       RM_LANGUAGECODE_SPANISH,
       RM_LANGUAGECODE_ITALIAN,
       RM_LANGUAGECODE_GERMAN,
       RM_LANGUAGECODE_DUTCH,
       RM_LANGUAGECODE_PORTUGUESE,
       RM_LANGUAGECODE_CHINESE,
       RM_LANGUAGECODE_JAPANESE,
       RM_LANGUAGECODE_OTHERS,
};

DWORD dvd_subpicturelanguage_codes[] =
{
       RM_LANGUAGECODE_AUDIOFOLLOW,
       RM_LANGUAGECODE_ENGLISH,
       RM_LANGUAGECODE_FRENCH,
       RM_LANGUAGECODE_SPANISH,
       RM_LANGUAGECODE_ITALIAN,
       RM_LANGUAGECODE_GERMAN,
       RM_LANGUAGECODE_DUTCH,
       RM_LANGUAGECODE_PORTUGUESE,
       RM_LANGUAGECODE_CHINESE,
       RM_LANGUAGECODE_JAPANESE,
       RM_LANGUAGECODE_OTHERS
};

DWORD dvd_menulanguage_codes[] =
{
       RM_LANGUAGECODE_ENGLISH,
       RM_LANGUAGECODE_FRENCH,
       RM_LANGUAGECODE_SPANISH,
       RM_LANGUAGECODE_ITALIAN,
       RM_LANGUAGECODE_GERMAN,
       RM_LANGUAGECODE_DUTCH,
       RM_LANGUAGECODE_PORTUGUESE,
       RM_LANGUAGECODE_CHINESE,
       RM_LANGUAGECODE_JAPANESE,
       RM_LANGUAGECODE_OTHERS
};

bool cDeviceEm84xx::mOsdHas4mb = false;

cDeviceEm84xx::cDeviceEm84xx(void):
      mOsdProvider(NULL),
      mThread(new cThreadEm84xx(this)),
      mIsOpen(false)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);

   mInputAspectRatio169  = false;
   mAspectRatioDataChanged = false;
   mUsePlayDisc = false;
   mSpdifForwardEnabled = SetupEm84xx.SpdifForward;
   mPlayMode = pmNone;
   mAc3 = false;
   mLastVolume = -1;
   mLastSampleRate = 48000;

cIFrameDecoder::InitFFMpeg();
if (!mIFrameDecoder.Init())
   esyslog("em84xx: failed to initialize iframe decoder");

   Open();
   Configure();
}

cDeviceEm84xx::~cDeviceEm84xx() 
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   mThread->Stop();
   delete mThread;
   Close();

mIFrameDecoder.Release();
}

void cDeviceEm84xx::StopThread(void) 
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   mThread->Stop();
   delete mThread;
}

void cDeviceEm84xx::Open(void)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   if (!mIsOpen) {

#ifdef EM_MPLAYER_SUPPORT
      DWORD res = MPEGDriverEntry((sDvdConnected)?PRIMARY_MASTER:NO_DRIVE);
      
      if (res != 0)
      {
         esyslog("ERROR: failed to open em84 MPEG driver");
         return;
      }
#endif
   
      FMPOpen(FMPF_PROGRAM, KILOBYTE(32), 10, NULL, 0);
      InitEm84();

      mIsOpen = true;
   }
}

void cDeviceEm84xx::InitEm84(void)
{
   Wnd_type dest_wnd;
   dest_wnd.x = 0;
   dest_wnd.y = 0;
   dest_wnd.w = 720;
   dest_wnd.h = 576;

   DWORD sizeOut = 0;
   DWORD doHwReset = 0;
   DWORD disableSpdifOndoHwReset = 0;
      
   eAudioDigitalOutput_type spdifMode = eAudioDigitalOutput_Pcm;

   DWORD tvOut = SET_TV | SET_PAL;// | 0x10;// | SET_ONETOONE;// | SET_TV_AS_USER;

   FMPProperty (KSPROPERTY_TYPE_SET, REGISTRY_COMMON_SET, eTvOut, 0, &tvOut, sizeof(tvOut ), &sizeOut);
   FMPProperty (KSPROPERTY_TYPE_SET, REGISTRY_COMMON_SET, eDoHwReset, 0, &doHwReset, sizeof(doHwReset), &sizeOut);
   FMPProperty (KSPROPERTY_TYPE_SET, REGISTRY_COMMON_SET, eDisableSpdifOutputInReset , 0, &disableSpdifOndoHwReset , sizeof(disableSpdifOndoHwReset ), &sizeOut); // not needed, but you never now :)
   FMPProperty (KSPROPERTY_TYPE_SET, AUDIO_SET, eAudioDigitalOutput, 0, &spdifMode, sizeof(eAudioDigitalOutput_type), &sizeOut);
   FMPProperty (KSPROPERTY_TYPE_SET, VIDEO_SET, evDestinationWindow, 0, &dest_wnd, sizeof(Wnd_type), &sizeOut);
        FMPProperty (KSPROPERTY_TYPE_SET, VIDEO_SET, evSourceWindow,      0, &dest_wnd, sizeof(Wnd_type), &sizeOut);

   // this line is necc, so that 16x9 switching works even after setting the video dest. window from above
   FMPSet(FMPI_OVERLAY_FLAGS, OSD_VIDEO_INDEPENDENT_DEST);

   FMPSet(FMPI_VIDEOOUT, FMPV_VIDEOOUT_COMPOSITE | FMPV_VIDEOOUT_TV | 
                       FMPV_VIDEOOUT_PAL);

   //SetVideoFormat(Setup.VideoFormat); NECCESSARY????

   FMPSetVideoPortDimensions(720, 576);
   FMPSet(FMPI_AUDIO_OUTPUT,SetupEm84xx.SpdifForward);

   evVideoHwPlay_type type;
   type.PlayOptions =  VideoHwPlaySyncFaster;
   FMPProperty (KSPROPERTY_TYPE_SET, VIDEO_SET, evVideoHwPlay, 0, &type, sizeof(type), &sizeOut);
}

void cDeviceEm84xx::Close(void)
{
// Dprintf("%s\n", __PRETTY_FUNCTION__);
// esyslog("%s\n", __PRETTY_FUNCTION__);

   if ((mOsdProvider) && (mOsdProvider->IsEnabled()))
      mOsdProvider->Enable(false);

   if (mIsOpen) {
      FMPClose();
#ifdef EM_MPLAYER_SUPPORT
      MPEGDriverUnload();
#endif
      mIsOpen = false;
   }
}
   
bool cDeviceEm84xx::Initialize(bool iOsdHas4mb) 
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);

   mOsdHas4mb = iOsdHas4mb;

#ifndef EM_MPLAYER_SUPPORT
   /*DWORD res = */MPEGDriverEntry((sDvdConnected)?PRIMARY_MASTER:NO_DRIVE);
#endif

   //if (res == 0) {
   mInstance = new cDeviceEm84xx;
   return true;
   //}
   //return false;
}

void cDeviceEm84xx::Destruct(void)
{
        mInstance->StopThread();
#ifndef EM_MPLAYER_SUPPORT
   MPEGDriverUnload();
#endif
   // vdr kills all devices, so no need to delete mInstance    
   mInstance = NULL;
}

cDeviceEm84xx* cDeviceEm84xx::GetInstance(void)
{
   return mInstance;
}

void cDeviceEm84xx::Configure()
{
   // out brightness, saturation and contrast
   FMPSet(FMPI_CONTRAST,SetupEm84xx.Contrast);
   FMPSet(FMPI_BRIGHTNESS,SetupEm84xx.Brightness);
   FMPSet(FMPI_SATURATION,SetupEm84xx.Saturation);

   if (cDeviceEm84xx::GetInstance() != NULL)
   {
      cDeviceEm84xx::GetInstance()->SetOutputAspectRatio(SetupEm84xx.TvIs169);
      cDeviceEm84xx::GetInstance()->SetOutputHandle169on43(SetupEm84xx.Tv43HandleOther);
      cDeviceEm84xx::GetInstance()->SetOutputHandle43on169(SetupEm84xx.Tv169HandleOther);
      cDeviceEm84xx::GetInstance()->SetAudioOutput(SetupEm84xx.SpdifForward);
      if (sDvdConnected)
         cDeviceEm84xx::GetInstance()->SetControlDvdSpeed(SetupEm84xx.ControlDvdSpeed);
   }

   // set audio delay   
// DWORD sizeOut;
// int doAudioLater = SetupEm84xx.DoAudioLater;
// FMPProperty (KSPROPERTY_TYPE_SET, REGISTRY_COMMON_SET, eDoAudioLater, 0, &doAudioLater, sizeof(doAudioLater), &sizeOut);

   // settings for dvd(disc-)playback
   if (SetupEm84xx.TvIs169)
   {
      if (SetupEm84xx.Tv169HandleOther == 2)
         FMPWriteProfileWord(RMREG_USER_ASPECT_RATIO, ASPECT_RATIO_4_3); // do nothing for 4:3 on 16:9 tv sets
      else
         FMPWriteProfileWord(RMREG_USER_ASPECT_RATIO, ASPECT_RATIO_16_9);

      if (SetupEm84xx.Tv169HandleOther == 0)
         FMPWriteProfileWord( RMREG_USER_VIDEO_OUTPUT_MODE, DISPLAY_MODE_ZOOM_ON);//FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_4x3_16x9_ZOOM_ON);
      else if (SetupEm84xx.Tv169HandleOther == 1)
         FMPWriteProfileWord( RMREG_USER_VIDEO_OUTPUT_MODE, DISPLAY_MODE_NORMAL_OR_WIDE);//FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_4x3_16x9_ZOOM_OFF);
      else
         FMPWriteProfileWord( RMREG_USER_VIDEO_OUTPUT_MODE, DISPLAY_MODE_NORMAL_OR_WIDE);//FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_4x3_4x3_NORMAL);

   }    
   else if (!SetupEm84xx.TvIs169)
   {
      FMPWriteProfileWord(RMREG_USER_ASPECT_RATIO, ASPECT_RATIO_4_3);

      if (SetupEm84xx.Tv43HandleOther == 0)
         FMPWriteProfileWord( RMREG_USER_VIDEO_OUTPUT_MODE, DISPLAY_MODE_LETTERBOX);//FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_16x9_4x3_LETTERBOX);
      else
         FMPWriteProfileWord( RMREG_USER_VIDEO_OUTPUT_MODE, DISPLAY_MODE_PAN_SCAN);//FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_16x9_4x3_PANSCAN);
   }

   // spdif
   FMPWriteProfileWord( RMREG_AUDIO_TYPE, (SetupEm84xx.SpdifForward)?RM_AUDIO_TYPE_SPDIF:RM_AUDIO_TYPE_ANALOG);

   // dvd default languages (audio, subpicture und menu)
   FMPWriteProfileWord( RMREG_AUDIO_LANGUAGECODE, dvd_audiolanguage_codes[SetupEm84xx.DvdAudioLanguage]);
   FMPWriteProfileWord( RMREG_SUBPICTURE_LANGUAGECODE, dvd_subpicturelanguage_codes[SetupEm84xx.DvdSubpictureLanguage]);
   FMPWriteProfileWord( RMREG_MENU_LANGUAGECODE, dvd_menulanguage_codes[SetupEm84xx.DvdMenuLanguage]);

   // always turn macrovision off
   // FMPWriteProfileWord( RMREG_MACROVISION, RM_MACROVISION_DISABLED);
   // dvd subpicture on/off
   FMPSet(FMPI_SUBPICTURE_ON,(SetupEm84xx.DvdSubpicture)?0x01:0x00);
}

void cDeviceEm84xx::EnableOsd(bool Enable)
{
   if (mOsdProvider)
      mOsdProvider->Enable(Enable);
}

void cDeviceEm84xx::MakePrimaryDevice(bool On) 
{
   Dprintf("%s(%s)\n", __PRETTY_FUNCTION__, On ? "true" : "false");
   mOsdProvider = new cOsdProviderEm84xx(mOsdHas4mb);
}

uchar * cDeviceEm84xx::GrabOsd(int &Size, bool Jpeg/* = true*/, int Quality /*= -1*/, int SizeX /*= -1*/, int SizeY /*= -1*/)
{
   char wPnmHeader[32];
   snprintf(wPnmHeader, sizeof(wPnmHeader), "P6\n%d\n%d\n255\n",_EM84OSD_MAXWIDTH, _EM84OSD_MAXHEIGHT);
   int wPnmHeaderLength = strlen(wPnmHeader);


   unsigned char *wRgbBuffer = (unsigned char*)malloc((Jpeg)?(_EM84OSD_MAXWIDTH*_EM84OSD_MAXHEIGHT*3):(_EM84OSD_MAXWIDTH*_EM84OSD_MAXHEIGHT*3)+wPnmHeaderLength);
   unsigned char *wResultBuffer = NULL;
   Size = _EM84OSD_MAXWIDTH*_EM84OSD_MAXHEIGHT*3;

   memset(wRgbBuffer,0,(Jpeg)?(_EM84OSD_MAXWIDTH*_EM84OSD_MAXHEIGHT*3):(_EM84OSD_MAXWIDTH*_EM84OSD_MAXHEIGHT*3)+wPnmHeaderLength);
   if (!cOsdEm84xx::GetOsdBufferRGB((Jpeg)?wRgbBuffer:&wRgbBuffer[wPnmHeaderLength]))
      isyslog("em84xx: osd not visible - grabbed image will be black");

   int wTargetWidth = _EM84OSD_MAXWIDTH;
   int wTargetHeight = _EM84OSD_MAXHEIGHT;
   wResultBuffer = wRgbBuffer;

   if (((SizeX > 0) && (SizeY > 0))
      && ((SizeX != _EM84OSD_MAXWIDTH) || (SizeY <= _EM84OSD_MAXHEIGHT)))
   {
      // resize
      int wOldPnmHeaderLength = wPnmHeaderLength;
      snprintf(wPnmHeader, sizeof(wPnmHeader), "P6\n%d\n%d\n255\n",SizeX, SizeY);
      wPnmHeaderLength = strlen(wPnmHeader);

      wResultBuffer = (unsigned char*)malloc((Jpeg)?(SizeX*SizeY*3):((SizeX*SizeY*3)+wPnmHeaderLength ));
      Size = SizeX*SizeY*3;

      if (ImageResizeRGB((Jpeg)?wRgbBuffer:&wRgbBuffer[wOldPnmHeaderLength], _EM84OSD_MAXWIDTH, _EM84OSD_MAXHEIGHT, (Jpeg)?wResultBuffer:&wResultBuffer[wPnmHeaderLength], SizeX, SizeY) != 0)
      {
         esyslog("em84xx: GrabImage - image resizing failed");
         free(wRgbBuffer);
         free(wResultBuffer);
         return NULL;
      }

      wTargetWidth  = SizeX;
      wTargetHeight = SizeY;
      free(wRgbBuffer);
       }

   isyslog("em84xx: grabbing osd (%s %d %d %d)", Jpeg ? "JPEG" : "PNM", Quality, wTargetWidth, wTargetHeight);
   
   if (Jpeg)
   {
      uchar* wJpegResult = Em84RgbToJpeg(wResultBuffer, wTargetWidth, wTargetHeight, Size, Quality);
      free(wResultBuffer);
              if (!wJpegResult)
                       esyslog("em84xx: failed to convert image to JPEG");

      return wJpegResult;
   }
   else
   {
      // PNM (write header)
         //snprintf((char*)wResultBuffer, 32, "P6\n%d\n%d\n255\n", wTargetWidth, wTargetHeight);
      memcpy(wResultBuffer,wPnmHeader,wPnmHeaderLength);
      Size += wPnmHeaderLength;
   }
      
   return wResultBuffer;
}

#if VDRVERSNUM >= 10338
uchar * cDeviceEm84xx::GrabImage(int &Size, bool Jpeg/* = true*/, int Quality /*= -1*/, int SizeX /*= -1*/, int SizeY /*= -1*/)
{
   return GrabOsd(Size, Jpeg, Quality, SizeX, SizeY);
}
#else
bool cDeviceEm84xx::GrabImage(const char *FileName, bool Jpeg /*= true*/, int Quality /*= -1*/, int SizeX /*= -1*/, int SizeY /*= -1*/)
{
   int wImageSize;
   bool wResult = true;
   uchar* wImageData = GrabOsd(wImageSize, Jpeg, Quality, SizeX, SizeY);
   if (!wImageData)
   {
      esyslog("em84xx: GrabOsd failed");
      return false;
   }


   int fd = open(FileName, O_WRONLY | O_CREAT | O_NOFOLLOW | O_TRUNC, DEFFILEMODE);
   if (fd >= 0)
   {

      if (safe_write(fd, wImageData, wImageSize) == wImageSize)
         isyslog("em84xx: grabbed image to %s", FileName);
          else
      {
         LOG_ERROR_STR(FileName);
         wResult = false;
      }

   }
   else
   {
      LOG_ERROR_STR(FileName);
      wResult = false;
   }

   free(wImageData);
   return wResult;

   /*unsigned char *wRgbBuffer = new unsigned char[_EM84OSD_MAXWIDTH*_EM84OSD_MAXHEIGHT*3];
   unsigned char *wResultBuffer = NULL;

   memset(wRgbBuffer,0,(_EM84OSD_MAXWIDTH*_EM84OSD_MAXHEIGHT*3));
   if (!cOsdEm84xx::GetOsdBufferRGB(wRgbBuffer))
      isyslog("em84xx: osd not visible - grabbed image will be black");


   int wTargetWidth = _EM84OSD_MAXWIDTH;
   int wTargetHeight = _EM84OSD_MAXHEIGHT;
   wResultBuffer = wRgbBuffer;

   if (((SizeX > 0) && (SizeY > 0))
      && ((SizeX != _EM84OSD_MAXWIDTH) || (SizeY <= _EM84OSD_MAXHEIGHT)))
   {
      // resize
      wResultBuffer = new unsigned char[SizeX*SizeY*3];

      if (ImageResizeRGB(wRgbBuffer, _EM84OSD_MAXWIDTH, _EM84OSD_MAXHEIGHT, wResultBuffer, SizeX, SizeY) != 0)
      {
         esyslog("em84xx: GrabImage - image resizing failed");
         delete[] wRgbBuffer;
         delete[] wResultBuffer;
         return false;
      }

              wTargetWidth  = SizeX;
              wTargetHeight = SizeY;
      delete[] wRgbBuffer;
       }

   isyslog("em84xx: grabbing to %s (%s %d %d %d)", FileName, Jpeg ? "JPEG" : "PNM", Quality, wTargetWidth, wTargetHeight);
   
   FILE *f = fopen(FileName, "wb");
   if (f)
   {
      if (Jpeg)
      {
         // write JPEG file:
         struct jpeg_compress_struct cinfo;
         struct jpeg_error_mgr jerr;
         cinfo.err = jpeg_std_error(&jerr);
         jpeg_create_compress(&cinfo);
         jpeg_stdio_dest(&cinfo, f);
         cinfo.image_width = wTargetWidth;
         cinfo.image_height = wTargetHeight;
         cinfo.input_components = 3;
         cinfo.in_color_space = JCS_RGB;

         jpeg_set_defaults(&cinfo);
         jpeg_set_quality(&cinfo, Quality, true);
         jpeg_start_compress(&cinfo, true);

         int rs = wTargetWidth * 3;
         JSAMPROW rp[wTargetHeight];
         for (int k = 0; k < wTargetHeight; k++)
            rp[k] = &wResultBuffer[rs * k];
         jpeg_write_scanlines(&cinfo, rp, wTargetHeight);
         jpeg_finish_compress(&cinfo);
         jpeg_destroy_compress(&cinfo);
      }
      else
      {
         // write PNM file:
                 if (fprintf(f, "P6\n%d\n%d\n255\n", wTargetWidth, wTargetHeight) < 0 ||
                     fwrite(wResultBuffer, wTargetWidth  * wTargetHeight * 3, 1, f) != 1)
         {
            LOG_ERROR_STR(FileName);
            delete[] wResultBuffer;
            return false;
         }
      }
   
              fclose(f);
   }
   else
   {
      LOG_ERROR_STR(FileName);
      delete[] wResultBuffer;
      return false;
   }

   delete[] wResultBuffer;
   return true;*/
}
#endif

bool cDeviceEm84xx::SetPlayMode(ePlayMode PlayMode) 
{
   Dprintf("%s(%d)\n", __PRETTY_FUNCTION__, (int)PlayMode);

   switch (PlayMode) {
      case pmNone:
         mThread->Stop();
         if (mLastPlayMode == pmExtern_THIS_SHOULD_BE_AVOIDED) 
         {
         Close();
         mLastPlayMode = pmNone; 
         }
      break;

      case pmAudioVideo:
      case pmAudioOnlyBlack:
      case pmAudioOnly:
      case pmVideoOnly:
         Open();
         mThread->Start();
      break;

      case pmExtern_THIS_SHOULD_BE_AVOIDED:
                        FMPSetSource(0,0,720,576);
                        FMPSet(FMPI_OVERLAY_FLAGS,OSD_VIDEO_INDEPENDENT_DEST);
                        FMPSet(FMPI_VIDEOASPECT, FMPV_VIDEOASPECT_16x9_16x9_NORMAL);
         mThread->Stop();
                        mLastPlayMode = PlayMode;
         if (GetUsePlayDisc())
         {
         cDeviceEm84xx::GetInstance()->SetUsePlayDisc(false);
         mLastPlayMode = pmNone; 
         Close();
         }
      break;

      default:
      break;
   }

   PlaymodeChanged(PlayMode);
   
   return true;
}

void cDeviceEm84xx::PlaymodeChanged(ePlayMode NewPlayMode)
{
   if (mPlayMode == NewPlayMode)
         return;
         
   // we always reset this vals - doesn't harm and should work best in this horrible code :)

// esyslog("%s NewPlayMode: %d --- mPlayMode: %d \n", __PRETTY_FUNCTION__, NewPlayMode, mPlayMode );
   
   if ((NewPlayMode != pmExtern_THIS_SHOULD_BE_AVOIDED) && (NewPlayMode != pmNone))
   {
      // this forces us to switch aspect ratio when coming back from playmode pmExtern
      mAspectRatioDataChanged = true;

      // and reset volume correctly
      if (mLastVolume > -1)
         SetVolumeDevice(mLastVolume);

      // get current samplerate
      DWORD sizeOut;
      FMPProperty (KSPROPERTY_TYPE_GET, AUDIO_SET, eAudioSampleRate, 0, &mLastSampleRate, sizeof(mLastSampleRate), &sizeOut);
      // set default
      if (mLastSampleRate != 48000)
         SetAudioSampleRate(48000);
      
      if ((mOsdProvider) && (!mOsdProvider->IsEnabled()))
         mOsdProvider->Enable(true);
   }
         
   mPlayMode = NewPlayMode;
}

int cDeviceEm84xx::PlayVideo(const uchar *Data, int Length) 
{
   //Dprintf("%s(%p,%d)\n", __PRETTY_FUNCTION__, Data, Length);
   //Dprintf("PlayVideo length=%d, first four = %02x %02x %02x %02x\n", Length,
           //(int)Data[0], (int)Data[1], (int)Data[2], (int)Data[3]);

   if (((mPlayMode == pmAudioVideo) || (mPlayMode == pmVideoOnly)) && (Length > 9))
   {
      /*uint32_t*/ int pos = 0;
      int esLength = 0;

      for (; pos + 9 < Length; pos++)
      {
         if (!Data[pos] && !Data[pos+1] && Data[pos+2] == 0x01 && (Data[pos+3] >= 0xE0 && Data[pos+3] <= 0xEF))
         {
            if ((Data[pos + 6] & 0xC0) == 0x80)
            {
                       if (pos + 9 + Data[pos + 8] < Length)
               {
                  if ((((int)Data[pos + 4]) * (int)256 + (int)Data[pos + 5]) > 0)
                  {
                     esLength = ((int)Data[pos + 4]) * (int)256 + (int)Data[pos + 5] + (int)6 - (9 + (int)Data[pos + 8]);

                     if (pos + 9 + Data[pos + 8] + esLength <= Length)
                     {
                        //if ((pos+9+Data[pos + 8] + esLength) < Length)
                           GetAspectRatioVideoData(&Data[pos+9+Data[pos + 8]],esLength);
                     }
                  }
                       }
            }
            else
            {
               /*uint32_t*/ int fpos = pos + 6;
               esLength = ((int)Data[pos + 4]) * (int)256 + (int)Data[pos + 5];

               // this looked suspicious
               if (Length >= pos + 6 + esLength)
               {
                  while (Data[fpos] == 0xff) ++fpos; // skip stuffing bytes
                  if ((Data[fpos] & 0xC0) == 0x40) fpos += 2; // skip std buffer scale and size
                    
                  if ((Data[fpos] & 0xF0) == 0x20) { // pts only
                     fpos += 5;
                  } else if ((Data[fpos] & 0xF0) == 0x30) { // pts and dts
                     fpos += 10;
                  } else {
                     ++fpos;
                  }
                    
                  if (esLength) esLength = esLength - (fpos - pos - 6);
                  //if ((fpos+esLength) < Length)
                     GetAspectRatioVideoData(&Data[fpos],esLength);
               }
               }
         }
      }
   }

   return mThread->PlayPacket(Data, Length, false);
}

void cDeviceEm84xx::GetAspectRatioVideoData(const uchar* Data, /*uint32_t*/ int Length)
{
    for (/*uint32_t*/ int i = 0; Length > /*(uint32_t)*/ 8 && i < Length - 8; i++)
    {
   if (Data[i] == 0 && Data[i+1] == 0 && Data[i+2] == 1)
   {
      if ((Data[i + 3] & 0xFF) == 0xB3)
      {
                switch ((Data[i + 7]) & 0xF0) {

                    case 0x20:
            SwitchAspectRatio(false);
                        break;

                    case 0x30:
            SwitchAspectRatio(true);
                        break;
                    default:
            SwitchAspectRatio(false);
                        break;
                }
         }
    }
    }
}

#if VDRVERSNUM >= 10342
int cDeviceEm84xx::PlayAudio(const uchar *Data, int Length, uchar Id)
#else
int cDeviceEm84xx::PlayAudio(const uchar *Data, int Length) 
#endif
{
   return mThread->PlayPacket(Data, Length, true);
}

int64_t cDeviceEm84xx::GetSTC(void) 
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   return FMPGet(FMPI_STC);
}

void cDeviceEm84xx::TrickSpeed(int Speed) 
{
   Dprintf("%s(%d)\n", __PRETTY_FUNCTION__, Speed);
   //Dprintf("TrickMode %lu\n", FMPSet(FMPI_TRICKMODE, FMPV_TRICKMODE_START));
   mThread->TrickSpeed(Speed);
}

void cDeviceEm84xx::Clear(void) 
{
   FMPFlush(TRUE);

   cDevice::Clear();
}

void cDeviceEm84xx::Play(void) 
{
   mThread->Play();

   cDevice::Play();
}

void cDeviceEm84xx::Freeze(void) 
{
   mThread->Freeze();

   cDevice::Freeze();
}

void cDeviceEm84xx::Mute(void) 
{
   FMPSet(FMPI_MUTE, FMPGet(FMPI_MUTE) ? 1 : 0);

   cDevice::Mute();
}

void cDeviceEm84xx::StillPicture(const uchar *Data, int Length) 
{
//FILE* f = fopen("still_pes.mpg", "wb");
//fwrite(Data,(size_t)Length,1,f);
//fclose(f);


   uchar esbuffer[Length];
   size_t esbufferlength;
   cIFrameDecoder::GetVideoES((unsigned char*)Data, Length, esbuffer, &esbufferlength);
   if (esbufferlength == 0)
   {
      esyslog("em84xx: no es-data from stilldata (size:%d bytes)", Length);
      return;
   }

   if (!mIFrameDecoder.DecodeFrame(esbuffer,(int)esbufferlength))
   {
      esyslog("em84xx: failed to decode frame from stillpicture es-data");
      return;
   }

   mThread->StillPicture(Data, Length);
}

bool cDeviceEm84xx::Poll(cPoller &Poller, int TimeoutMs) 
{
   mThread->AddPoller(Poller);
   return Poller.Poll(TimeoutMs);
}

bool cDeviceEm84xx::Flush(int TimeoutMs) 
{
   FMPFlush(TRUE);
   return true;
}

void cDeviceEm84xx::SetVideoFormat(bool VideoFormat16_9)
{
   FMPSet(FMPI_VIDEOASPECT, VideoFormat16_9 ? FMPV_VIDEOASPECT_16x9_16x9_NORMAL 
                                            : FMPV_VIDEOASPECT_4x3_4x3_NORMAL);
}

eVideoSystem cDeviceEm84xx::GetVideoSystem(void)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);
   DWORD mode = FMPGet(FMPI_VIDEOOUT);
   return (mode & FMPV_VIDEOOUT_STANDARDTV_MASK) == FMPV_VIDEOOUT_NTSC
          ? vsNTSC 
          : vsPAL;
}

void cDeviceEm84xx::SetAudioTrackDevice(eTrackType Type)
{
   Dprintf("%s(%d)\n", __PRETTY_FUNCTION__, Type);
}

int cDeviceEm84xx::GetAudioChannelDevice(void)
{
   Dprintf("%s\n", __PRETTY_FUNCTION__);

// FMPI_AUDIO_MODE related values.
// They have to match values in mpegcmn.h used by hardware library:
//#define FMPV_AUDIO_MODE_STEREO       0x0000
//#define FMPV_AUDIO_MODE_RIGHT_ONLY      0x0001
//#define FMPV_AUDIO_MODE_LEFT_ONLY    0x0002
//#define FMPV_AUDIO_MODE_MONOMIX         0x0003

   int audioMode = FMPGet(FMPI_AUDIO_MODE);
   if (audioMode == 1)
      return 2;
   if (audioMode == 2)
      return 1;
   return audioMode;
}

void cDeviceEm84xx::SetAudioChannelDevice(int AudioChannel)
{
   Dprintf("%s(%d)\n", __PRETTY_FUNCTION__, AudioChannel);

   if (AudioChannel == 1)
      FMPSet(FMPI_AUDIO_MODE, FMPV_AUDIO_MODE_LEFT_ONLY);
   else if (AudioChannel == 2)
      FMPSet(FMPI_AUDIO_MODE, FMPV_AUDIO_MODE_RIGHT_ONLY);
   else
      FMPSet(FMPI_AUDIO_MODE, AudioChannel);
}

void cDeviceEm84xx::SetVolumeDevice(int Volume)
{
       Dprintf("%s(%d)\n", __PRETTY_FUNCTION__, Volume);
    if (mLastPlayMode == pmExtern_THIS_SHOULD_BE_AVOIDED) 
   return;

   int fmpvol = Volume * 100 / 255;
   FMPSet(FMPI_LEFT_VOLUME, fmpvol);
   FMPSet(FMPI_RIGHT_VOLUME, fmpvol);

   mLastVolume = Volume;
}

void cDeviceEm84xx::SetDigitalAudioDevice(bool On)
{
   Dprintf("%s(%s)\n", __PRETTY_FUNCTION__, On ? "true" : "false");

   if (On != mAc3)
   {
      mThread->Stop();
      mThread->Start();

      DWORD sizeOut = 0;
      eAudioDigitalOutput_type spdifMode = eAudioDigitalOutput_Pcm;
      if (On)
         spdifMode = eAudioDigitalOutput_Compressed;
      FMPProperty (KSPROPERTY_TYPE_SET, AUDIO_SET, eAudioDigitalOutput, 0, &spdifMode, sizeof(eAudioDigitalOutput_type), &sizeOut);
   }

   const tTrackId* track = GetTrack(GetCurrentAudioTrack());
   if ((track != NULL) && !On)
   {
      FMPSet(FMPI_AUDIO_SELECT,track->id-0xC0);       
   }

   mAc3 = On;
}

void cDeviceEm84xx::SwitchAspectRatio(bool Is169)
{
  if ((Is169 == mInputAspectRatio169) && (!mAspectRatioDataChanged))
    return;

  mInputAspectRatio169 = Is169;

  Tv169Handle = SetupEm84xx.Tv169HandleOther;
  Tv43Handle  = SetupEm84xx.Tv43HandleOther;

//esyslog("mInputAspectRatio169: %d  SetupEm84xx.TvIs169: %d  Tv169Handle: %d  Tv43Handle: %d  SetupEm84xx.Tv169HandleOther: %d  SetupEm84xx.Tv43HandleOther: %d", mInputAspectRatio169, SetupEm84xx.TvIs169, Tv169Handle, Tv43Handle, SetupEm84xx.Tv169HandleOther, SetupEm84xx.Tv43HandleOther);

  if (mInputAspectRatio169)
  {
    if (SetupEm84xx.TvIs169)
    {
    esyslog("reset to VIDEOASPECT_16x9_16x9_NORMAL");
    FMPSetVisibleSource(0,0,720,576);
    FMPSet(FMPI_OVERLAY_FLAGS,OSD_VIDEO_INDEPENDENT_DEST);
    FMPSet(FMPI_VIDEOASPECT, FMPV_VIDEOASPECT_16x9_16x9_NORMAL); 

        switch (Tv169Handle) {
       
        case _16x9_ZOOM_ON:
       break;
       
   case _16x9_ZOOM_OFF:
       break;
       
        case _16x9_NOTHING:
       break;
       
        case _16x9_ZOOM_ON_169:
            {
       esyslog("switch to VIDEOASPECT_16x9_16x9_NORMAL");
       FMPSetVisibleSource(0,0,720,576);
       FMPSet(FMPI_OVERLAY_FLAGS,OSD_VIDEO_INDEPENDENT_DEST);
       FMPSet(FMPI_VIDEOASPECT, FMPV_VIDEOASPECT_16x9_16x9_NORMAL); 
       }
       break;

        case _16x9_ZOOM_ON_1610:
       {
       esyslog("switch to VIDEOASPECT_16x10_16x10_NORMAL");
       FMPSet(FMPI_OVERLAY_FLAGS,VIDEO_ZOOM_ENABLE);
       FMPSetVisibleSource(10,0,700,576);
       }
       break;

   default:
       break;
   }
    } 
    else
    {
    switch (Tv43Handle) {
    
    case _4x3_LETTERBOX:
   FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_16x9_4x3_LETTERBOX);
   break;
   
    case _4x3_PANSCAN:
        FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_16x9_4x3_PANSCAN);
   break;

    default:
   break;
    }
    
   }

  }
  else
  {
    if (!SetupEm84xx.TvIs169)
      FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_4x3_4x3_NORMAL);
    else
    {

    switch (Tv169Handle) {

    case _16x9_ZOOM_ON:
        {
        esyslog("switch to VIDEOASPECT_4x3_16x9_ZOOM_ON");
   FMPSetVisibleSource(0,0,720,576);
   FMPSet(FMPI_OVERLAY_FLAGS,OSD_VIDEO_INDEPENDENT_DEST);
        FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_4x3_16x9_ZOOM_ON); 
   }
    break;
    
    case _16x9_ZOOM_OFF:
        {
        esyslog("switch to VIDEOASPECT_4x3_16x9_ZOOM_OFF");
   FMPSetVisibleSource(0,0,720,576);
   FMPSet(FMPI_OVERLAY_FLAGS,OSD_VIDEO_INDEPENDENT_DEST);
        FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_4x3_16x9_ZOOM_OFF);
   }
    break;
    
    case _16x9_NOTHING:
   {
        esyslog("switch to VIDEOASPECT_4x3_16x9_NOTHING");
   FMPSetVisibleSource(0,0,720,576);
   FMPSet(FMPI_OVERLAY_FLAGS,OSD_VIDEO_INDEPENDENT_DEST);
        FMPSet(FMPI_VIDEOASPECT,  FMPV_VIDEOASPECT_4x3_4x3_NORMAL);
   }
    break;
    
    case _16x9_ZOOM_ON_169:
        {
        esyslog("switch to VIDEOASPECT_4x3_16x9_ZOOM_ON 16:9");
        FMPSet(FMPI_OVERLAY_FLAGS,VIDEO_ZOOM_ENABLE);
        FMPSetVisibleSource(0,70,720,434);//Aspect 4:3 on 16:9 TV --> zoom in
        }
    break;
    
    case _16x9_ZOOM_ON_1610:
        {
        esyslog("switch to VIDEOASPECT_4x3_16x9_ZOOM_ON 16:10");
        FMPSet(FMPI_OVERLAY_FLAGS,VIDEO_ZOOM_ENABLE);
        FMPSetVisibleSource(4,50,712,474);//Aspect 4:3 on 16:9 TV --> zoom in
        }
    break;
    
    default:
    break;
    }
    
   }

  }
  mAspectRatioDataChanged = false;
}

void cDeviceEm84xx::SetOutputAspectRatio(bool Is169)
{
  //mOutputAspectRatio169 = Is169;
  mAspectRatioDataChanged = true;
}

void cDeviceEm84xx::SetOutputHandle43on169(int Handle)
{
  //mOutputHandle43on169 = Handle;
  mAspectRatioDataChanged = true;
}

void cDeviceEm84xx::SetOutputHandle169on43(int Handle)
{
  //mOutputHandle169on43 = Handle;
  mAspectRatioDataChanged = true;
}

void cDeviceEm84xx::SetAudioOutput(bool EnableSpdifForward)
{
  if (EnableSpdifForward != mSpdifForwardEnabled)
  {
    mSpdifForwardEnabled = EnableSpdifForward;
    mThread->Stop();
    FMPSet(FMPI_AUDIO_OUTPUT,mSpdifForwardEnabled);
    mThread->Start();
  }
}

void cDeviceEm84xx::SetAudioSampleRate(int SampleRate)
{
   if (SampleRate == mLastSampleRate)
      return;

   mLastSampleRate = SampleRate;
   
   DWORD sizeOut;
   FMPProperty (KSPROPERTY_TYPE_SET, AUDIO_SET, eAudioSampleRate, 0, &mLastSampleRate, sizeof(mLastSampleRate), &sizeOut);

   int newSampleFactor = (int)(((float)mLastSampleRate/48000.0)*1000.0);
   FMPSet(FMPI_AUDIO_SPEED, newSampleFactor);
}

void cDeviceEm84xx::SetControlDvdSpeed(int ControlDvdSpeed)
{
  if (ControlDvdSpeed != mControlDvdSpeed)
  {
    cDeviceEm84xx::SpeedControl(ControlDvdSpeed);
    mControlDvdSpeed = ControlDvdSpeed;
  }
}

// speed = 0; turn off speedcontrol - >0 turn on
bool cDeviceEm84xx::SpeedControl(int Speed)
{
  int fd;
  unsigned long rw_size;
  unsigned char buffer[28];
  struct cdrom_generic_command cgc;
  struct request_sense sense;

  const char* device = getenv("USE_DRIVE");
  if (!device)
  {
    esyslog("SpeedControl() - unable to determine USE_DRIVE env var");
    return false;
  }

  fd = open(device, O_RDONLY | O_NONBLOCK);
  if (fd < 0) {
    esyslog("SpeedControl() Can't open device %s", device);
    return false;
  }

  memset(&cgc, 0, sizeof(cgc));
  memset(&sense, 0, sizeof(sense));
  memset(&buffer, 0, sizeof(buffer));

  /* SET STREAMING command */
  cgc.cmd[0] = 0xb6;
  /* 28 byte parameter list length */
  cgc.cmd[10] = 28;

  cgc.sense = &sense;
  cgc.buffer = buffer;
  cgc.buflen = sizeof(buffer);
  cgc.data_direction = CGC_DATA_WRITE;
  cgc.quiet = 1;

  if (Speed == 0)
    buffer[0] = 4; // set Restore Drive Defaults

  buffer[8] = 0xff;
  buffer[9] = 0xff;
  buffer[10] = 0xff;
  buffer[11] = 0xff;
  rw_size = 177 * Speed;

  /* read size */
  buffer[12] = (rw_size >> 24) & 0xff;
  buffer[13] = (rw_size >> 16) & 0xff;
  buffer[14] = (rw_size >>  8) & 0xff;
  buffer[15] = rw_size & 0xff;

  /* read time 1 sec. */
  buffer[18] = 0x03;
  buffer[19] = 0xE8;

  /* write size */
  buffer[20] = (rw_size >> 24) & 0xff;
  buffer[21] = (rw_size >> 16) & 0xff;
  buffer[22] = (rw_size >>  8) & 0xff;
  buffer[23] = rw_size & 0xff;

  /* write time 1 sec. */
  buffer[26] = 0x03;
  buffer[27] = 0xE8;

  if (ioctl(fd, CDROM_SEND_PACKET, &cgc) != 0)
  {
    if (ioctl(fd, CDROM_SELECT_SPEED, Speed) != 0) {
      esyslog("SpeedControl() Command failed");
      return false;
    }
  }

  close(fd);
  return true;
}

