/**************************************************************************
 * Copyright (C) 2008 Cocha                                               *
 * http://sourceforge.jp/projects/ecodecotool/                            *
 *                                                                        *
 *  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, 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 GNU Make; see the file COPYING.  If not, write to          *
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. *
 *                                                                        *
 **************************************************************************/

#include "SourceTta.h"


CSourceTta::CSourceTta(IUnknown *pUnk, HRESULT *phr)
   : CSimplePushSource(L"Source", pUnk, CLSID_SourceTta, phr)
{  

   
   ::ZeroMemory(&m_outFormat, sizeof(m_outFormat));
   m_hFile = NULL;
   m_pTTACodec = NULL;
   m_nTotalLength = 0;
   m_nID3v2Length = 0;

   m_pnSeekTable = NULL;
   m_pbInBuffer = NULL;
   m_nSeekTableSize = 0;
   m_nCurrentFrameNumber = 0;

   m_rtCurrent = 0;
   m_rtStop = INFINITE_SECOND;
}

CSourceTta::~CSourceTta()  
{  

   
   if(m_pnSeekTable != NULL)
   {
      delete []m_pnSeekTable;
      m_pnSeekTable = NULL;
   }

   if(m_pbInBuffer != NULL)
   {
      delete []m_pbInBuffer;
      m_pbInBuffer = NULL;
   }

   if(m_pTTACodec != NULL)
   {
      tta_codec_free(m_pTTACodec);
      m_pTTACodec = NULL;
   }

   if(m_hFile != NULL)
   {
      ::CloseHandle(m_hFile);
      m_hFile = NULL;
   }
}  

HRESULT CSourceTta::FillBuffer(IMediaSample *pOutSample)
{  

   if(m_rtCurrent >= m_rtStop)
   {  
      return S_FALSE;
   }

   if(m_nCurrentFrameNumber >= m_nSeekTableSize)
   {
      return S_FALSE;
   }

   REFERENCE_TIME rtEnd;
   BYTE *pbOutBuffer = NULL;
   pOutSample->GetPointer(&pbOutBuffer);

   
   DWORD dwRead = 0;
   unsigned long len;

   ::ReadFile(m_hFile, m_pbInBuffer, m_pnSeekTable[m_nCurrentFrameNumber], &dwRead, NULL);
   len = tta_codec_decoder_decompress(m_pTTACodec, m_pbInBuffer, dwRead, pbOutBuffer, pOutSample->GetSize());

   
   rtEnd = m_rtCurrent + (LONGLONG)len * ONE_SECOND / m_outFormat.nAvgBytesPerSec;

   if(rtEnd >= m_rtStop)
   {  

      len = len - (int)((rtEnd - m_rtStop) * m_outFormat.nAvgBytesPerSec / ONE_SECOND);
      rtEnd = m_rtStop;

      if(len < 0)
         len = 0;
   }

   
   pOutSample->SetTime(&m_rtCurrent, &rtEnd);
   pOutSample->SetActualDataLength(len);
   pOutSample->SetSyncPoint(FALSE);

   
   m_rtCurrent = rtEnd;

   
   m_nCurrentFrameNumber++;

   return S_OK;
}

HRESULT CSourceTta::OnSeek(LONGLONG llNewPosition, LONGLONG llStopPosition)
{  

   if(m_pTTACodec == NULL)
      return E_NOTIMPL;

   if(llNewPosition >= 0)
   {  

      m_nCurrentFrameNumber = (int)(m_nSeekTableSize * llNewPosition / m_llTotalDuration);
      m_rtCurrent = (REFERENCE_TIME)(m_llTotalDuration * m_nCurrentFrameNumber / m_nSeekTableSize);

      if(m_nCurrentFrameNumber < m_nSeekTableSize)
      {  

         int nPointerPos = sizeof(TTA_HEADER) + m_nSeekTableSize * 4 + 4;

         for(int i=0;i<m_nCurrentFrameNumber;i++)
            nPointerPos += m_pnSeekTable[i];

         ::SetFilePointer(m_hFile, m_nID3v2Length + nPointerPos, NULL, FILE_BEGIN);
      }
   }

   if(llStopPosition >= 0)
   {  
      m_rtStop = (REFERENCE_TIME)llStopPosition;
   }

   return S_OK;
}

HRESULT CSourceTta::OnConnectOutPin(CMediaType *pmtOut, int *pnOutBufferSize, REFERENCE_TIME *pTotalMediaTime)
{  

   if(m_pTTACodec == NULL)
      return E_NOTIMPL;

   
   pmtOut->SetType(&MEDIATYPE_Audio);
   pmtOut->SetSubtype(&MEDIASUBTYPE_PCM);
   pmtOut->SetTemporalCompression(FALSE);
   pmtOut->SetSampleSize(0);
   pmtOut->SetFormatType(&FORMAT_WaveFormatEx);
   pmtOut->SetFormat((BYTE*)&m_outFormat, sizeof(m_outFormat));

   
   *pnOutBufferSize = tta_codec_get_frame_len(m_outFormat.nSamplesPerSec) * (m_outFormat.wBitsPerSample/8) * m_outFormat.nChannels;

   
   m_llTotalDuration = (REFERENCE_TIME)((LONGLONG)m_nTotalLength * (LONGLONG)m_outFormat.nBlockAlign * ONE_SECOND / (LONGLONG)m_outFormat.nAvgBytesPerSec);
   *pTotalMediaTime = (REFERENCE_TIME)m_llTotalDuration;

   return S_OK;
}

STDMETHODIMP CSourceTta::OnQueryInterface(REFIID riid, void ** ppv)
{
   if(riid == IID_IFileSourceFilter)
      return GetInterface((IFileSourceFilter *)this, ppv);

   return CSource::NonDelegatingQueryInterface(riid, ppv);
}

CUnknown * WINAPI CSourceTta::CreateInstance(LPUNKNOWN punk, HRESULT *phr)
{  

   ASSERT(phr);
    
   CSourceTta *pNewObject = new CSourceTta(punk, phr);
   if (pNewObject == NULL)
   {
      if(phr != NULL)
         *phr = E_OUTOFMEMORY;
   }

   return dynamic_cast<CUnknown *>(pNewObject);
}

STDMETHODIMP CSourceTta::Load(LPCOLESTR pszFileName, const AM_MEDIA_TYPE *pmt)
{  

   if(::PathFileExists(pszFileName) == FALSE)
      return E_FAIL;

   
   if(m_pnSeekTable != NULL)
   {
      delete []m_pnSeekTable;
      m_pnSeekTable = NULL;
   }

   if(m_pbInBuffer != NULL)
   {
      delete []m_pbInBuffer;
      m_pbInBuffer = NULL;
   }

   if(m_pTTACodec != NULL)
   {
      tta_codec_free(m_pTTACodec);
      m_pTTACodec = NULL;
   }

   if(m_hFile != NULL)
   {
      ::CloseHandle(m_hFile);
      m_hFile = NULL;
   }

   
   m_hFile = ::CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, (DWORD)0, NULL);
   if(m_hFile == NULL)
      goto error;

   
   DWORD dwRead = 0;
   id3v2 id3Header;
   ::ReadFile(m_hFile, &id3Header, sizeof(id3Header), &dwRead, NULL);
   if(dwRead != sizeof(id3Header))
       goto error;

   if(id3Header.id[0] == 'I' && id3Header.id[1] == 'D' && id3Header.id[2] == '3')
   {
      
      
      int nSize;
      nSize = (id3Header.size[0] & 0x7f);
      nSize = (nSize << 7) | (id3Header.size[1] & 0x7f);
      nSize = (nSize << 7) | (id3Header.size[2] & 0x7f);
      nSize = (nSize << 7) | (id3Header.size[3] & 0x7f);

      
      m_nID3v2Length = nSize + sizeof(id3Header);

      
      ::SetFilePointer(m_hFile, nSize, NULL, FILE_CURRENT);
   }
   else
   {
      
      ::SetFilePointer(m_hFile, 0, NULL, FILE_BEGIN);
   }

   
   TTA_HEADER header;
   ::ZeroMemory(&header, sizeof(header));
   ::ReadFile(m_hFile, &header, sizeof(header), &dwRead, NULL);

   if(dwRead != sizeof(header))
       goto error;

   if(header.TTAid[0] != 'T' || header.TTAid[1] != 'T' || header.TTAid[2] != 'A' || header.TTAid[3] != '1')
       goto error;

   int nSamplesPerSec = header.SampleRate * header.NumChannels * (header.BitsPerSample / 8);
   if(nSamplesPerSec <= 0)
      goto error;

   
   int nFrameLength = (int)(FRAME_TIME * (double)header.SampleRate);
   m_nSeekTableSize = header.DataLength / nFrameLength;

   if((header.DataLength % nFrameLength) > 0)
      m_nSeekTableSize++;

   m_pnSeekTable = new int[m_nSeekTableSize];

   int nMax = 0;
   for(int i=0;i<m_nSeekTableSize;i++)
   {
      ::ReadFile(m_hFile, &m_pnSeekTable[i], 4, &dwRead, NULL);
      if(dwRead != 4)
          goto error;

      if(m_pnSeekTable[i] > nMax)
         nMax = m_pnSeekTable[i];
   }

   
   int nSeekCRC;
   ::ReadFile(m_hFile, &nSeekCRC, 4, &dwRead, NULL);
   if(dwRead != 4)
       goto error;

   
   m_pTTACodec = tta_codec_decoder_new(header.SampleRate, header.NumChannels, header.BitsPerSample);
   if(m_pTTACodec == NULL)
      goto error;

   
   m_outFormat.nSamplesPerSec  = header.SampleRate;
   m_outFormat.nChannels       = (WORD)header.NumChannels;
   m_outFormat.wBitsPerSample  = (WORD)header.BitsPerSample;
   m_outFormat.nBlockAlign     = m_outFormat.nChannels * (m_outFormat.wBitsPerSample / 8);
   m_outFormat.nAvgBytesPerSec = m_outFormat.nSamplesPerSec * m_outFormat.nBlockAlign;
   m_outFormat.wFormatTag      = (WORD)header.AudioFormat;
   m_outFormat.cbSize = 0;

   
   m_nInBufferSize = nMax;
   m_pbInBuffer = new BYTE[m_nInBufferSize];

   
   m_nTotalLength = header.DataLength;

   
   ::lstrcpy(m_awInputFileName, pszFileName);
   return S_OK;

error:

   
   if(m_pnSeekTable != NULL)
   {
      delete []m_pnSeekTable;
      m_pnSeekTable = NULL;
   }

   if(m_pbInBuffer != NULL)
   {
      delete []m_pbInBuffer;
      m_pbInBuffer = NULL;
   }

   if(m_pTTACodec != NULL)
   {
      tta_codec_free(m_pTTACodec);
      m_pTTACodec = NULL;
   }

   if(m_hFile != NULL)
   {
      ::CloseHandle(m_hFile);
      m_hFile = NULL;
   }

   return E_FAIL;
}

STDMETHODIMP CSourceTta::GetCurFile(LPOLESTR *ppszFileName, AM_MEDIA_TYPE *pmt)
{  

   CheckPointer(ppszFileName, E_POINTER);

   ::lstrcpy(*ppszFileName, m_awInputFileName);

   if(pmt != NULL)
   {
      ::ZeroMemory(pmt, sizeof(*pmt));
      pmt->majortype = MEDIATYPE_NULL;
      pmt->subtype = MEDIASUBTYPE_NULL;
   }

   return S_OK;
}

