/**************************************************************************
 * 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 "EcoDecoMp3.h"

CEncodeFraunhofer::CEncodeFraunhofer(IUnknown *pUnk, HRESULT *phr)
   : CSimpleWriter(L"Fraunhofer Writer", pUnk, CLSID_EncodeFraunhofer, phr)
{  

   ::ZeroMemory(&m_inFormat, sizeof(m_inFormat));
   ::ZeroMemory(&m_outFormat, sizeof(m_outFormat));

   m_nWait = 0;
   m_llInputSize = 0;

   m_hDLL = NULL;
   m_hDriver = NULL;
   m_hDriverID = 0;
   m_hStream = NULL;
   m_pInBuffer = NULL;
   m_pOutBuffer = NULL;

   m_nBitrate = 0;
}
CEncodeFraunhofer::~CEncodeFraunhofer()  
{  
   AcmRelease();
}
HRESULT CEncodeFraunhofer::OnConnectInPin(const CMediaType *pmtIn)
{  

   if(pmtIn->majortype != MEDIATYPE_Audio)
      return S_FALSE;

   if(pmtIn->subtype != MEDIASUBTYPE_PCM)
      return S_FALSE;

   if(pmtIn->formattype != FORMAT_WaveFormatEx)
      return S_FALSE;

   WAVEFORMATEX *pwf = (WAVEFORMATEX *)pmtIn->Format();
   if(pwf->wBitsPerSample == 16 && pwf->nChannels <= 2 && pwf->nSamplesPerSec <= 48000)
   {  
      ::CopyMemory(&m_inFormat, pwf, sizeof(m_inFormat));
      return S_OK;
   }

   return S_FALSE;
}
HRESULT CEncodeFraunhofer::SetWait(int nWait)
{
   CAutoLock lock(&m_Lock);

   m_nWait = nWait;

   return S_OK;
}
HRESULT CEncodeFraunhofer::GetTransformedBytes(LONGLONG *pllBytes)
{
   CAutoLock lock(&m_Lock);

   *pllBytes = m_llInputSize;

   return S_OK;
}
HRESULT CEncodeFraunhofer::GetInFormat(WAVEFORMATEX *pFormat)
{  
   CheckPointer(pFormat, E_POINTER);
   ::CopyMemory(pFormat, &m_inFormat, sizeof(m_inFormat));

   return S_OK;
}
HRESULT CEncodeFraunhofer::SetOutFormat(void *pFormat)
{
   int *pnBitrate = (int *)pFormat;

   m_nBitrate = *pnBitrate;

   return S_OK;
}
HRESULT CEncodeFraunhofer::CheckOutFormat(WAVEFORMATEX *pInFormat, int *pnBitrate)
{  

   if(pInFormat->wFormatTag != WAVE_FORMAT_PCM || pInFormat->wBitsPerSample != 16 ||
      pInFormat->nChannels > 2 || pInFormat->nSamplesPerSec > 48000)
   {  
      return E_FAIL;
   }


   MMRESULT mmr;
   HRESULT hr = E_FAIL;

   HMODULE hDLL = NULL;
   HACMDRIVER   hDriver = NULL;
   HACMDRIVERID hDriverID = 0;
   WCHAR awFileName[MAX_PATH];

   ::GetSystemDirectory(awFileName, MAX_PATH);
   ::lstrcat(awFileName, TEXT("\\l3codecp.acm"));

   if(::PathFileExists(awFileName) != FALSE)
      hDLL = ::LoadLibrary(awFileName);

   if(hDLL == NULL)
   {  

      ::GetSystemDirectory(awFileName, MAX_PATH);
      ::lstrcat(awFileName, TEXT("\\l3codeca.acm"));
      if(::PathFileExists(awFileName) == FALSE)
         goto end;

      hDLL = ::LoadLibrary(awFileName);
      if(hDLL == NULL)
         goto end;
   }

   mmr = ::acmDriverAdd(&hDriverID, hDLL, LPARAM(::GetProcAddress(hDLL, "DriverProc")), 0, ACM_DRIVERADDF_FUNCTION);
   if(mmr != MMSYSERR_NOERROR)
      goto end;

   mmr = ::acmDriverOpen(&hDriver, hDriverID, 0);
   if(mmr != MMSYSERR_NOERROR)
      goto end;

   MPEGLAYER3WAVEFORMAT mwf;
   hr = GetOutFortmat(hDriver, hDriverID, *pnBitrate, pInFormat, &mwf);

end:

   if(hDriver != NULL)
   {
      ::acmDriverClose(hDriver, 0);
      ::acmDriverRemove(hDriverID, 0);

      hDriver = NULL;
      hDriverID = 0;
   }

   if(hDLL != NULL)
   {
      ::FreeLibrary(hDLL);
      hDLL = NULL;
   }

   return hr;
}
HRESULT CEncodeFraunhofer::GetOutFortmat(HACMDRIVER hDriver, HACMDRIVERID hDriverID, int nBitrate, WAVEFORMATEX *pInFormat, MPEGLAYER3WAVEFORMAT *pOutFormat)
{
   MMRESULT mmr;
   ACMDRIVERDETAILS details;

   details.cbStruct = sizeof(ACMDRIVERDETAILS);
   mmr = acmDriverDetails(hDriverID, &details, 0);
   if(mmr != MMSYSERR_NOERROR)
      return E_FAIL;

   for(int i=0;i<(int)details.cFormatTags;i++)
   {
      ACMFORMATTAGDETAILS fmtDetails;
      ::ZeroMemory(&fmtDetails, sizeof(fmtDetails));

      fmtDetails.cbStruct = sizeof(ACMFORMATTAGDETAILS);
      fmtDetails.dwFormatTagIndex = i;
      mmr = acmFormatTagDetails(hDriver, &fmtDetails, ACM_FORMATTAGDETAILSF_INDEX);
      if(mmr != MMSYSERR_NOERROR)
         continue;

      if(fmtDetails.dwFormatTag == WAVE_FORMAT_MPEGLAYER3)
      {
         for(int n=0;n<(int)fmtDetails.cStandardFormats;n++)
         {  

            MPEGLAYER3WAVEFORMAT mwf;

            ACMFORMATDETAILS formatDetails;
            formatDetails.pwfx          = (WAVEFORMATEX *)&mwf;
            formatDetails.cbStruct      = sizeof(ACMFORMATDETAILS);
            formatDetails.fdwSupport    = 0;
            formatDetails.dwFormatTag   = WAVE_FORMAT_MPEGLAYER3;
            formatDetails.dwFormatIndex = n;
            acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &formatDetails.cbwfx); 

            mmr = acmFormatDetails(hDriver, &formatDetails, ACM_FORMATDETAILSF_INDEX);
            if(mmr != MMSYSERR_NOERROR)
               continue;

            if((mwf.wfx.nAvgBytesPerSec * 8) == nBitrate &&
                mwf.wfx.nSamplesPerSec == pInFormat->nSamplesPerSec &&
                mwf.wfx.nChannels == pInFormat->nChannels)
            {  

               mmr = ::acmStreamOpen(NULL, hDriver, pInFormat, (WAVEFORMATEX *)&mwf, NULL, 0, 0, ACM_STREAMOPENF_QUERY);
               if(mmr == MMSYSERR_NOERROR)
               {  
                  ::CopyMemory(pOutFormat, &mwf, sizeof(mwf));
                  return S_OK;
               }
            }
         }
      }
   }

   return E_FAIL;
}
void CEncodeFraunhofer::AcmRelease(void)
{
   if(m_hDriver != NULL)
   {
      ::acmDriverClose(m_hDriver, 0);
      ::acmDriverRemove(m_hDriverID, 0);

      m_hDriver = NULL;
      m_hDriverID = 0;
   }

   if(m_hStream != NULL)
   {
      ::acmStreamUnprepareHeader(m_hStream, &m_acmHeader, 0);
      ::acmStreamClose(m_hStream, 0);
      m_hStream = NULL;
   }

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

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

   if(m_hDLL != NULL)
   {
      ::FreeLibrary(m_hDLL);
      m_hDLL = NULL;
   }
}
HRESULT CEncodeFraunhofer::OnStart(HANDLE hFile)
{
   AcmRelease();

   HRESULT hr;
   MMRESULT mmr;
   WCHAR awFileName[MAX_PATH];

   ::GetSystemDirectory(awFileName, MAX_PATH);
   ::lstrcat(awFileName, TEXT("\\l3codecp.acm"));

   if(::PathFileExists(awFileName) != FALSE)
      m_hDLL = ::LoadLibrary(awFileName);

   if(m_hDLL == NULL)
   {  

      ::GetSystemDirectory(awFileName, MAX_PATH);
      ::lstrcat(awFileName, TEXT("\\l3codeca.acm"));
      if(::PathFileExists(awFileName) == FALSE)
         return E_FAIL;

      m_hDLL = ::LoadLibrary(awFileName);
      if(m_hDLL == NULL)
         return E_FAIL;
   }

   ALLOCATOR_PROPERTIES InProps;
   IMemAllocator * pInAlloc = NULL;

   hr = m_pInput->GetAllocator(&pInAlloc);
   if(FAILED(hr)) return hr;

   hr = pInAlloc->GetProperties(&InProps);
   SAFE_RELEASE(pInAlloc);

   int nInBufferSize = (int)InProps.cbBuffer;

   mmr = ::acmDriverAdd(&m_hDriverID, m_hDLL, LPARAM(::GetProcAddress(m_hDLL, "DriverProc")), 0, ACM_DRIVERADDF_FUNCTION);
   if(mmr != MMSYSERR_NOERROR)
      goto error;

   mmr = ::acmDriverOpen(&m_hDriver, m_hDriverID, 0);
   if(mmr != MMSYSERR_NOERROR)
      goto error;

   hr = GetOutFortmat(m_hDriver, m_hDriverID, m_nBitrate, &m_inFormat, &m_outFormat);
   if(FAILED(hr))
      goto error;

   nInBufferSize = nInBufferSize * 12 / 10;

   m_pInBuffer = new BYTE[nInBufferSize];

   mmr = ::acmStreamOpen(&m_hStream, m_hDriver, &m_inFormat, (WAVEFORMATEX *)&m_outFormat, NULL, 0, 0, ACM_STREAMOPENF_NONREALTIME);
   if(mmr != MMSYSERR_NOERROR)
      goto error;

   DWORD dwOutSize;
   mmr = ::acmStreamSize(m_hStream, nInBufferSize, &dwOutSize, ACM_STREAMSIZEF_SOURCE);
   if(mmr != MMSYSERR_NOERROR)
      goto error;

   m_pOutBuffer = new BYTE[dwOutSize];

   ::ZeroMemory(&m_acmHeader, sizeof(ACMSTREAMHEADER));
   m_acmHeader.cbStruct    = sizeof(ACMSTREAMHEADER);
   m_acmHeader.cbSrcLength = nInBufferSize;
   m_acmHeader.pbSrc       = m_pInBuffer;
   m_acmHeader.pbDst       = m_pOutBuffer;
   m_acmHeader.cbDstLength = dwOutSize;

   mmr = ::acmStreamPrepareHeader(m_hStream, &m_acmHeader, 0);
   if(mmr != MMSYSERR_NOERROR)
      goto error;

   return S_OK;

error:
   AcmRelease();
   return E_FAIL;
}
HRESULT CEncodeFraunhofer::OnReceive(HANDLE hFile, IMediaSample *pInSample)
{
   MMRESULT mmr;

   if(m_nWait > 0)
   {
      int nWait = int((m_llInputSize + pInSample->GetActualDataLength()) * 10 / m_inFormat.nAvgBytesPerSec) - int(m_llInputSize * 10 / m_inFormat.nAvgBytesPerSec);

      if(nWait > 0)
         ::Sleep(nWait * m_nWait * 2);
   }

   m_llInputSize += pInSample->GetActualDataLength();

   BYTE *pbInBuffer = NULL;
   pInSample->GetPointer(&pbInBuffer);

   ::CopyMemory(&m_pInBuffer[0], &pbInBuffer[0], pInSample->GetActualDataLength());

   m_acmHeader.cbSrcLength = (pInSample->GetActualDataLength() / m_inFormat.nBlockAlign) * m_inFormat.nBlockAlign;

   mmr = ::acmStreamConvert(m_hStream, &m_acmHeader, ACM_STREAMCONVERTF_BLOCKALIGN);
   if(mmr != MMSYSERR_NOERROR)
      return E_FAIL;

   if(m_acmHeader.cbDstLengthUsed > 0)
   {
      DWORD dwWritten;
      ::WriteFile(hFile, m_pOutBuffer, m_acmHeader.cbDstLengthUsed, &dwWritten, NULL);
   }

   return S_OK;
}
HRESULT CEncodeFraunhofer::OnStop(HANDLE hFile, bool bEos)
{
   MMRESULT mmr;

   m_acmHeader.cbSrcLength = 0;
   mmr = ::acmStreamConvert(m_hStream, &m_acmHeader, ACM_STREAMCONVERTF_END);

   if(mmr == MMSYSERR_NOERROR && m_acmHeader.cbDstLengthUsed > 0)
   {
      DWORD dwWritten;
      ::WriteFile(hFile, m_pOutBuffer, m_acmHeader.cbDstLengthUsed, &dwWritten, NULL);
   }

   AcmRelease();

   return S_OK;
}
STDMETHODIMP CEncodeFraunhofer::OnQueryInterface(REFIID riid, void ** ppv)
{
   if(riid == IID_IEcoDecoInterface)
      return GetInterface((IEcoDecoInterface *)this, ppv);
   else if(riid == IID_IEncodeFraunhoferInterface)
      return GetInterface((IEncodeFraunhoferInterface *)this, ppv);

   return CBaseFilter::NonDelegatingQueryInterface(riid, ppv);
}
CUnknown * WINAPI CEncodeFraunhofer::CreateInstance(LPUNKNOWN punk, HRESULT *phr)
{  

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

   return dynamic_cast<CUnknown *>(pNewObject);
}
