/********************************************************************
 *                                                                  *
 * THIS FILE IS PART OF THE aoTuvWriter SOURCE CODE.                *
 * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS     *
 * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
 * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING.       *
 *                                                                  *
 * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2007             *
 * by the Xiph.Org Foundation http://www.xiph.org/                  *
 *                                                                  *
 * THE aoTuv SOURCE CODE IS (C) COPYRIGHT 2003-2008 Aoyumi          *
 * http://www.geocities.jp/aoyoume/aotuv/                           *
 *                                                                  *
 * THE aoTuvWriter SOURCE CODE IS (C) COPYRIGHT 2008 Cocha          *
 * http://sourceforge.jp/projects/directshow/                       *
 *                                                                  *
 ********************************************************************/

#include "aoTuvWriter.h"

// ------------------------------------------------------------------------------------------------------------------------
void CaoTuvEncode::SJIStoUTF8(char *pcText, int nArraySize)
{  // シフトJISからUTF8に変換する

   if(nArraySize <= 0)
      return;

   int nLen = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pcText, -1, NULL, NULL);

   WCHAR *pwTempBuf = new WCHAR[nLen];

   // シフトJIS → Unicode
   MultiByteToWideChar(CP_ACP, 0, (LPCSTR)pcText, -1, (LPWSTR)pwTempBuf, nLen);

   // Unicode → UTF8
   WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)pwTempBuf, -1, (LPSTR)pcText, nArraySize, NULL, NULL);

}
// ------------------------------------------------------------------------------------------------------------------------
HRESULT CaoTuvEncode::EncodeInit(WCHAR *pwFileName, VORBISFORMAT *pVorbisFormat, VORBISTAG *pVorbisTag)
{  // OggVorbisエンコード作業の準備をする
   int eos = 0, ret;

   // vorbisを初期化する
   vorbis_info_init(&m_vi);

   if(-0.11f <= pVorbisFormat->fQuality && pVorbisFormat->fQuality <= 1.01f)
   {  // VBRの初期化
      ret = vorbis_encode_init_vbr(&m_vi, (long)pVorbisFormat->nChannels, pVorbisFormat->nSamplesPerSec, pVorbisFormat->fQuality);
   }
   else
   {  // ABR,CBRの初期化
      ret = vorbis_encode_init(&m_vi, (long)pVorbisFormat->nChannels, pVorbisFormat->nSamplesPerSec, pVorbisFormat->nMaxBitsPerSec, pVorbisFormat->nAvgBitsPerSec, pVorbisFormat->nMinBitsPerSec);
   }

   if(ret != 0)
   {  // 初期化に失敗した場合

      vorbis_info_clear(&m_vi);
      return E_FAIL;
   }

   // vorbisのタグ情報を付加する
   vorbis_comment_init(&m_vc);

   if(pVorbisTag != NULL)
   {
      if(pVorbisTag->acTitle[0] != '\0')
      {
         SJIStoUTF8(pVorbisTag->acTitle, sizeof(pVorbisTag->acTitle));
         vorbis_comment_add_tag(&m_vc, "TITLE", pVorbisTag->acTitle);
      }

      if(pVorbisTag->acArtist[0] != '\0')
      {
         SJIStoUTF8(pVorbisTag->acArtist, sizeof(pVorbisTag->acArtist));
         vorbis_comment_add_tag(&m_vc, "ARTIST", pVorbisTag->acArtist);
      }

      if(pVorbisTag->acAlbum[0] != '\0')
      {
         SJIStoUTF8(pVorbisTag->acAlbum, sizeof(pVorbisTag->acAlbum));
         vorbis_comment_add_tag(&m_vc, "ALBUM", pVorbisTag->acAlbum);
      }

      if(pVorbisTag->acTrackNumber[0] != '\0')
      {
         SJIStoUTF8(pVorbisTag->acTrackNumber, sizeof(pVorbisTag->acTrackNumber));
         vorbis_comment_add_tag(&m_vc, "TRACKNUMBER", pVorbisTag->acTrackNumber);
      }

      if(pVorbisTag->acDate[0] != '\0')
      {
         SJIStoUTF8(pVorbisTag->acDate, sizeof(pVorbisTag->acDate));
         vorbis_comment_add_tag(&m_vc, "DATE", pVorbisTag->acDate);
      }

      if(pVorbisTag->acGenre[0] != '\0')
      {
         SJIStoUTF8(pVorbisTag->acGenre, sizeof(pVorbisTag->acGenre));
         vorbis_comment_add_tag(&m_vc, "GENRE", pVorbisTag->acGenre);
      }

      if(pVorbisTag->acComment[0] != '\0')
      {
         SJIStoUTF8(pVorbisTag->acComment, sizeof(pVorbisTag->acComment));
         vorbis_comment_add_tag(&m_vc, "COMMENT", pVorbisTag->acComment);
      }
   }

   // m_vdの初期化
   vorbis_analysis_init(&m_vd, &m_vi);
   vorbis_block_init(&m_vd, &m_vb);

   // 出力ファイルがすでに存在するなら削除する
   if(::PathFileExists(pwFileName) != FALSE)
   {
      if(::DeleteFile(pwFileName) == FALSE)
      {  // ファイルがロック中なら、ファイル出力できない

         vorbis_info_clear(&m_vi);
         return (HRESULT)GetLastError();
      }
   }

   // 出力ファイルを新規に作成する
   m_hFile = ::CreateFile(pwFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, (DWORD)0, NULL);
   if(m_hFile == INVALID_HANDLE_VALUE)
      return (HRESULT)INVALID_HANDLE_VALUE;

   // oggのヘッダ情報をファイルに出力する  
   srand((unsigned int)time(NULL));
   ogg_stream_init(&m_os, rand());

   ogg_packet header;
   ogg_packet header_comm;
   ogg_packet header_code;

   vorbis_analysis_headerout(&m_vd, &m_vc, &header, &header_comm, &header_code);
   ogg_stream_packetin(&m_os, &header);
   ogg_stream_packetin(&m_os, &header_comm);
   ogg_stream_packetin(&m_os, &header_code);

   while(!eos)
   {
      int result = ogg_stream_flush(&m_os, &m_og);

      if(result==0)
         break;

      BOOL flag;
      DWORD dwWriteSize;

      flag = ::WriteFile(m_hFile , (LPVOID)m_og.header, m_og.header_len, &dwWriteSize , NULL);
      if(flag == FALSE)
      {
         vorbis_info_clear(&m_vi);
         return (HRESULT)GetLastError();
      }

      flag = ::WriteFile(m_hFile , (LPVOID)m_og.body, m_og.body_len, &dwWriteSize , NULL);
      if(flag == FALSE)
      {
         vorbis_info_clear(&m_vi);
         return (HRESULT)GetLastError();
      }
   }

   // チャンネル数を格納する
   m_nChannels = (int)pVorbisFormat->nChannels;

   return S_OK;
}
// -----------------------------------------------------------------------------------------------------------------------
HRESULT CaoTuvEncode::Encode(int nBufferSize)
{  // エンコードする

   int eos = 0;

   vorbis_analysis_wrote(&m_vd, nBufferSize);

   while(vorbis_analysis_blockout(&m_vd, &m_vb)==1)
   {
      vorbis_analysis(&m_vb, NULL);
      vorbis_bitrate_addblock(&m_vb);

      while(vorbis_bitrate_flushpacket(&m_vd, &m_op))
      {
         ogg_stream_packetin(&m_os, &m_op);
    
         while(!eos)
         {
            int result = ogg_stream_pageout(&m_os, &m_og);
            if(result == 0)
               break;

            BOOL flag;
            DWORD dwWriteSize;

            flag = ::WriteFile(m_hFile , (LPVOID)m_og.header, m_og.header_len, &dwWriteSize , NULL);
            if(flag == FALSE) return E_FAIL;

            flag = ::WriteFile(m_hFile , (LPVOID)m_og.body, m_og.body_len, &dwWriteSize , NULL);
            if(flag == FALSE) return E_FAIL;

            if(ogg_page_eos(&m_og))
               eos = 1;
         }
      }
   }

   return S_OK;
}
// ------------------------------------------------------------------------------------------------------------------------
HRESULT CaoTuvEncode::Receive(short *psSrc, int nSrcLength)
{
   HRESULT hr;
   float **ppfBuffer;

   if(m_nChannels == 1)
   {  // モノラルの場合

      int nBufSize;

      while(nSrcLength > 0)
      {
         if(nSrcLength >= ENCODE_BUFFER_SIZE)
            nBufSize = ENCODE_BUFFER_SIZE;
         else
            nBufSize = nSrcLength;

         ppfBuffer = vorbis_analysis_buffer(&m_vd, nBufSize);

         // ppfBufferにデータを移動する
         for(int i=0;i<nBufSize;i++)
         {
            ppfBuffer[0][i] = *psSrc / 32768.f;
            *psSrc++;
         }

         // エンコードを行う
         hr = Encode(nBufSize);
         if(FAILED(hr))
            return hr;

         nSrcLength -= nBufSize;
      }

   }
   else
   {  // ステレオの場合

      int nBufSize, nBufHalfSize;

      while(nSrcLength > 0)
      {
         if(nSrcLength >= ENCODE_BUFFER_SIZE)
            nBufSize = ENCODE_BUFFER_SIZE;
         else
            nBufSize = nSrcLength;

         nBufHalfSize = nBufSize / 2;

         // ppfBufferにデータを移動する
         ppfBuffer = vorbis_analysis_buffer(&m_vd, nBufSize);

         for(int i=0;i<nBufHalfSize;i++)
         {
            ppfBuffer[0][i] = *psSrc / 32768.f;
            *psSrc++;
            ppfBuffer[1][i] = *psSrc / 32768.f;
            *psSrc++;
         }

         // エンコードを行う
         hr = Encode(nBufHalfSize);
         if(FAILED(hr))
            return hr;

         nSrcLength -= nBufSize;
      }
   }

   return S_OK;
}
// ------------------------------------------------------------------------------------------------------------------------
HRESULT CaoTuvEncode::EncodeEnd()
{
   // バッファにまだ残っているデータのエンコードを行う
   Encode(0);

   // ogg vorbisの後処理
   ogg_stream_clear(&m_os);
   vorbis_block_clear(&m_vb);
   vorbis_dsp_clear(&m_vd);
   vorbis_comment_clear(&m_vc);
   vorbis_info_clear(&m_vi);

   // ファイルを閉じる
   if(m_hFile != NULL)
   {
      ::CloseHandle(m_hFile);
      m_hFile = NULL;
   }

   return S_OK;
}
// ------------------------------------------------------------------------------------------------------------------------
CaoTuvEncode::CaoTuvEncode()
{  // CaoTuvEncodeのコンストラクタ

   m_hFile = NULL;
}
// ------------------------------------------------------------------------------------------------------------------------
CaoTuvEncode::~CaoTuvEncode()
{  // CaoTuvEncodeのデストラクタ

   // ファイルを閉じる
   if(m_hFile != NULL)
   {
      ::CloseHandle(m_hFile);
      m_hFile = NULL;
   }
}
// ------------------------------------------------------------------------------------------------------------------------
