2016年10月13日木曜日

32bitDIBから無圧縮AVI2.0 (2)





実際に出力した動画ファイル
[161013]avi20_output.avi
filesize: 19.8MB




 ざっくりとしたファイルフォーマット図。
青字が記述に使用する構造体、緑字がその構造体のサイズになります。
自分がプログラムを組む際どこに何を書き込むのかよくわからなかったのでまとめました。
矢印は各インデックスがさすデータ位置で、
チャンク先頭だったりデータ先頭だったりするようです。
 WindowsMediaPlayerで再生するには、
AVIStreamHeaderのdwLengthをファイル全体のトータルフレーム数にしないとRIFF-AVI部分を再生後にエラーがでるっぽいです。
仕様ではRIFF-AVI内のフレーム数を指定するはずです。



ソースファイル
file: [20161013]AVI20.cpp
 前回のソースコードはどうもチャンクサイズが間違ってるっぽいので新しく書き直しました。
ファイル作成後ヘッダー部分を飛ばし、画像データを書き込みつつRIFFリストを追記更新。
記録終了時にヘッダーを書きにファイル先端に戻ってヘッダー記述、という流れになります。


#include <windows.h>
#include <vector>



const int WINDOW_WIDTH = 640 / 2;
const int WINDOW_HEIGHT = 480/ 2;
const int TIMER_ID = 1000;
const int FPS = 30;


class DIB32
{
public:
 class PutColorAdd
 {
 public:
  static inline void update( DWORD& dest, DWORD src )
  {
   DWORD color = (dest & 0x00fefefe) + (src & 0x00fefefe);
   DWORD mask = color & 0x01010100;
   dest = (dest&0xFF000000) | color | (mask - (mask >> 8));
  }
 };




 DIB32()
  : m_pixel( NULL )
 {
  ZeroMemory( &m_bmi, sizeof(m_bmi) );
 }
 
 virtual ~DIB32()
 {
  release();
 }
 
 virtual bool create( LONG width, LONG height )
 {
  if ( width <= 0 || height <= 0 ) return false;
  release();
 
  m_pixel = new DWORD[ width * height ];
  if ( !m_pixel ) return false;
 
  m_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  m_bmi.bmiHeader.biWidth = width;
  m_bmi.bmiHeader.biHeight = height;
  m_bmi.bmiHeader.biPlanes = 1;
  m_bmi.bmiHeader.biBitCount = 32;
  m_bmi.bmiHeader.biSizeImage = width * height * 4;
  m_bmi.bmiHeader.biCompression = BI_RGB;
  m_bmi.bmiHeader.biXPelsPerMeter = 3780;
  m_bmi.bmiHeader.biYPelsPerMeter = 3780;
 
  return true;
 }
 
 /** create from HBITMAP */
 bool create( LPCTSTR fileName )
 {
  HBITMAP hBmp = static_cast<HBITMAP>( LoadImage( NULL, fileName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE ) );
  if ( !hBmp ) return false;

  BITMAP bm = {0};
  GetObject( hBmp, sizeof(BITMAP), &bm );

  // create DC
  HDC hdc = CreateCompatibleDC( NULL );
  if ( !hdc ) return false;

  // create buf
  if ( !create( bm.bmWidth, bm.bmHeight ) )
  {
   DeleteDC( hdc );
   return false;
  }

  // copy
  GetDIBits( hdc, hBmp, 0, bm.bmHeight, m_pixel, &m_bmi, DIB_RGB_COLORS );

  DeleteDC( hdc );
  DeleteObject( hBmp );

  return true;
 }
 
 virtual void release()
 {
  if ( m_pixel )
  {
   delete [] m_pixel;
   m_pixel = NULL;
  }
  ZeroMemory( &m_bmi, sizeof(m_bmi) );
 }
 
 bool render( HDC hdc, HWND hWnd ) const
 {
  RECT rect;
  GetClientRect( hWnd, &rect );
  return GDI_ERROR != StretchDIBits(
   hdc, 0, 0, rect.right, rect.bottom,
   0, 0, getWidth(), getHeight(),
   m_pixel, &m_bmi, DIB_RGB_COLORS, SRCCOPY );
 }
 
 
 /** render to dib */
 template < class T >
 bool render( DIB32& dest,
  LONG destX, LONG destY, LONG destWidth, LONG destHeight,
  LONG srcX, LONG srcY, LONG srcWidth, LONG srcHeight ) const
 {
  LONG width = abs( destWidth );
  LONG height = abs( destHeight );
  const LONG absDestWidth = width;
  const LONG absDestHeight = height;
  const LONG absSrcWidth = abs( srcWidth );
  const LONG absSrcHeight = abs( srcHeight );
  const float magniX = static_cast<float>(width) / static_cast<float>(srcWidth);
  const float magniY = static_cast<float>(height) / static_cast<float>(srcHeight);
  LONG sw = static_cast<LONG>( static_cast<float>(getWidth()) * magniX );
  LONG sh = static_cast<LONG>( static_cast<float>(getHeight()) * magniY );
  LONG sx = static_cast<LONG>( static_cast<float>(srcX) * magniX );
  LONG sy = static_cast<LONG>( static_cast<float>(srcY) * magniY );
  
  if ( !cliping( destX, destY, width, height, sx, sy, dest.getWidth(), dest.getHeight(), sw, sh ) )
  {
   return false;
  }
  
  
  const int shift = 14;
  const LONG addX = (absSrcWidth << shift) / absDestWidth;
  const LONG addY = (absSrcHeight << shift) / absDestHeight;

  // src*magniだったsxを元に戻しつつ、固定小数点数に
  LONG fsx = static_cast<LONG>( static_cast<float>(sx << shift) / magniX );
  LONG fsy = static_cast<LONG>( static_cast<float>(sy << shift) / magniY );
   
  LPDWORD destLine = dest.m_pixel + ((dest.getHeight()-1)-destY) * dest.getWidth() + destX;
  for (LONG y=0; y<height; ++y)
  {
   LPDWORD destPixel = destLine;
   LPDWORD srcLine = m_pixel + ((getHeight()-1)-(fsy>>shift)) * getWidth();
   LONG tmpx = fsx;
   for (LONG x=0; x<width; ++x)
   {
    T::update( *destPixel++, srcLine[ tmpx >> shift ] );
    tmpx += addX;
   }

   fsy += addY;
   destLine -= dest.getWidth();
  }
  
  
  return true;
 }

 /** cliping */
 static inline bool cliping(
  LONG& destX, LONG& destY, LONG& width, LONG& height, LONG& srcX, LONG& srcY,
  const LONG destWidth, const LONG destHeight, const LONG srcWidth, const LONG srcHeight )
 {
  // 左端クリッピング
  if ( destX < 0 ) { width += destX; srcX -= destX; destX = 0; }
  if ( srcX < 0 ) { width += srcX; destX -= srcX; srcX = 0; }

  // 右端
  if ( destX + width > destWidth ) { width -= ((destX+width)-destWidth); }
  if ( srcX + width > srcWidth ) { width -= ((srcX+width)-srcWidth); }

  // 上
  if ( destY < 0 ) { height += destY; srcY -= destY; destY = 0; }
  if ( srcY < 0 ) { height += srcY; destY -= srcY; srcY = 0; }

  // 下
  if ( destY + height > destHeight ) { height -= ((destY+height)-destHeight); }
  if ( srcY + height > srcHeight ) { height -= ((srcY+height)-srcHeight); }

  return ((width > 0) & (height > 0));
 }

 /** abs */
 template<class T> static T abs(const T& a) { return ((a) < 0 ? -(a) : (a)); }
 
 LONG getWidth() const { return m_bmi.bmiHeader.biWidth; }
 LONG getHeight() const { return m_bmi.bmiHeader.biHeight; }
 const LPDWORD getPixelAddr() const { return m_pixel; }
 LPDWORD getPixelAddr() { return m_pixel;  }

 
 const BITMAPINFO& getBMPInfo() { return m_bmi; }

protected:

 LPDWORD m_pixel;
 BITMAPINFO m_bmi;
};
 
 


/*
AVISTREAMHEADER 構造体 <https://msdn.microsoft.com/ja-jp/library/cc352263.aspx>
AVIMAINHEADER 構造体 <https://msdn.microsoft.com/ja-jp/library/cc352261.aspx>
BITMAPINFOHEADER 構造体 <https://msdn.microsoft.com/ja-jp/library/cc352308.aspx>
AVISUPERINDEX structure (Windows) <https://msdn.microsoft.com/ja-jp/library/windows/desktop/ff625871(v=vs.85).aspx>
AVISTDINDEX structure (Windows) <https://msdn.microsoft.com/ja-jp/library/windows/desktop/ff625869(v=vs.85).aspx>
AVIOLDINDEX structure (Windows) <https://msdn.microsoft.com/ja-jp/library/windows/desktop/dd318181(v=vs.85).aspx>
*/

 
class AVIRecorder
{
public:
#pragma pack(1)
 struct LIST
 {
  DWORD dwList;
  DWORD dwSize;
  DWORD dwFourCC;
 };

 struct CHUNK
 {
  DWORD dwFourCC;
  DWORD dwSize;
 };

 struct MainAVIHeader
 {

  DWORD  dwMicroSecPerFrame; // frame display rate (or 0L)
  DWORD  dwMaxBytesPerSec; // max. transfer rate
  DWORD  dwPaddingGranularity; // pad to multiples of this
  // size; normally 2K.
  DWORD  dwFlags;  // the ever-present flags
#define AVIF_HASINDEX        0x00000010 // Index at end of file?
#define AVIF_MUSTUSEINDEX    0x00000020
#define AVIF_ISINTERLEAVED   0x00000100
#define AVIF_TRUSTCKTYPE     0x00000800 // Use CKType to find key frames
#define AVIF_WASCAPTUREFILE  0x00010000
#define AVIF_COPYRIGHTED     0x00020000
  DWORD  dwTotalFrames;  // # frames in file
  DWORD  dwInitialFrames;
  DWORD  dwStreams;
  DWORD  dwSuggestedBufferSize;

  DWORD  dwWidth;
  DWORD  dwHeight;

  DWORD  dwReserved[4];
 };

 struct AVIStreamHeader
 {
  FOURCC  fccType;
  FOURCC  fccHandler;
  DWORD  dwFlags; /* Contains AVITF_* flags */
  WORD  wPriority;
  WORD  wLanguage;
  DWORD  dwInitialFrames;
  DWORD  dwScale; 
  DWORD  dwRate; /* dwRate / dwScale == samples/second */
  DWORD  dwStart;
  DWORD  dwLength; /* In units above... */
  DWORD  dwSuggestedBufferSize;
  DWORD  dwQuality;
  DWORD  dwSampleSize;
  //RECT  rcFrame;
  struct
  {
   short int left;
   short int top;
   short int right;
   short int bottom;
  } rcFrame;
 };

/* Flags for index */
#define AVIIF_LIST          0x00000001L // chunk is a 'LIST'
#define AVIIF_KEYFRAME      0x00000010L // this frame is a key frame.
#define AVIIF_FIRSTPART     0x00000020L // this frame is the start of a partial frame.
#define AVIIF_LASTPART      0x00000040L // this frame is the end of a partial frame.
#define AVIIF_MIDPART       (AVIIF_LASTPART|AVIIF_FIRSTPART)

#define AVIIF_NOTIME     0x00000100L // this frame doesn't take any time
#define AVIIF_COMPUSE       0x0FFF0000L // these bits are for compressor use

 // 1022 -> 16384 byte
 // single -> 32 + 16*n byte
 struct AVISUPERINDEX
 {
  FOURCC fcc;
  DWORD cd;
  WORD wLongsPerEntry;
  BYTE bIndexSubType;
  BYTE bIndexType;
  DWORD nEntriesInUse;
  DWORD dwChunkID;
  DWORD dwReserved[3];
 };

 struct AVISUPERINDEX_ENTRY
 {
  DWORDLONG qwOffset;
  DWORD dwSize;
  DWORD dwDuration;
 };

#define AVI_INDEX_OF_INDEXES       0x00
#define AVI_INDEX_OF_CHUNKS        0x01
#define AVI_INDEX_OF_TIMED_CHUNKS  0x02
#define AVI_INDEX_OF_SUB_2FIELD    0x03
#define AVI_INDEX_IS_DATA          0x80

 struct AVISTDINDEX
 {
  FOURCC fcc;
  DWORD cd;
  WORD wLongsPerEntry;
  BYTE bIndexSubType;
  BYTE bIndexType;
  DWORD nEntriesInUse;
  DWORD dwChunkID;
  DWORDLONG qwBaseOffset;
  DWORD dwReserved;  
 };

 struct AVISTDINDEX_ENTRY
 {
  DWORD dwOffset;
  DWORD dwSize;
 };

 struct AVIINDEXENTRY
 {
  DWORD  ckid;
  DWORD  dwFlags;
  DWORD  dwChunkOffset;  // Position of chunk
  DWORD  dwChunkLength;  // Length of chunk
 };

 struct AVIEXTHEADER
 {
  FOURCC  fcc;                    // 'dmlh'
  DWORD   cb;                     // size of this structure -8
  DWORD   dwGrandFrames;          // total number of frames in the file
  DWORD   dwFuture[61];           // to be defined later
 };
#pragma pack()

 enum
 {
  MAX_CHUNK_SIZE = 8 * 1024 * 1024,  //< 1チャンクの最大サイズ
  SUPER_INDEX_ENTRY_SIZE = 32 * 1024,  //< SuperIndexEntryを確保するサイズ。
  PADDING_GRANULARITY = 2048,
 };



 AVIRecorder()
  : m_file( INVALID_HANDLE_VALUE )
  , m_nowRiffFrame( 0 )
  , m_totalFrame( 0 )
  , m_numOfRiff( 0 )
  , m_nowRiffSize( 0 )
  , m_mostLargeImageSize( 0 )
  , m_maxNumOfRiff( 0 )
  , m_moviChunkOffset( 0 )
  , m_isAVIX( false )
  , m_width( 0 )
  , m_height( 0 )
  , m_imageSize( 0 )
  , m_fps( 0 )
  , m_riffAVIChunkFrame( 0 )
  , m_riffAVIChunkSize( 0 )
 {
  ZeroMemory( &m_nowRiffFileTopPos, sizeof(m_nowRiffFileTopPos) );
  ZeroMemory( &m_moviListPos, sizeof(m_moviListPos) );
 }

 ~AVIRecorder()
 {
  close();
 }



 /** create */
 bool create( LPCTSTR fileName, DWORD width, DWORD height, DWORD fps )
 {
  close();

  m_file = CreateFile( fileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
  if ( isOpen() )
  {
   m_width = width;
   m_height = height;
   m_fps = fps;
   m_imageSize = width * height * 4;

   // 何も書き込んでないので0のままだけど、一応現在RiffListの先端位置を記憶
   ZeroMemory( &m_nowRiffFileTopPos, sizeof(m_nowRiffFileTopPos) );
   m_nowRiffFileTopPos.LowPart = SetFilePointer( m_file, 0, &m_nowRiffFileTopPos.HighPart, FILE_CURRENT );

   // ファイル作成時にすっとばすヘッダ分の領域
   const DWORD preFileHeaderSkipSize =
    sizeof(LIST)              //< RIFF-AVI LIST
     + sizeof(LIST)             //< hdrl List
      + sizeof(CHUNK) + sizeof(MainAVIHeader)      //< avih Chunk + MainAVIHeader
      + sizeof(LIST)            //< list-strl
       + sizeof(CHUNK) + sizeof(AVIStreamHeader)    //< strh Chunk + AVIStreamHeader
       + sizeof(CHUNK) + sizeof(BITMAPINFOHEADER)    //< strf Chunk + BITMAPINFOHEADER
       + sizeof(AVISUPERINDEX) + SUPER_INDEX_ENTRY_SIZE  //< AVISUPERINDEX + SUPER_INDEX_ENTRY_SIZE
     + sizeof(LIST)            //< odml List
      + sizeof(AVIEXTHEADER)         //< AVIEXTHEADER
     + sizeof(LIST);            //< movi LIST


   DWORD fileHeaderSkipSize = preFileHeaderSkipSize;

   if ( PADDING_GRANULARITY )
   {
    const DWORD PaddingGranularity = PADDING_GRANULARITY;   //< 0だとコンパイル時に0除算で怒られるので適当に変数にいったん入れる
    fileHeaderSkipSize = preFileHeaderSkipSize + (PADDING_GRANULARITY - (preFileHeaderSkipSize % PaddingGranularity));
   }

   // 'movi'は直後に追記するのでその分は省く
   fileHeaderSkipSize -= sizeof(LIST);

   
   // ヘッダ部分をスキップ
   SetFilePointer( m_file, fileHeaderSkipSize, NULL, FILE_BEGIN );

   // 格納可能RIFF数
   m_maxNumOfRiff = SUPER_INDEX_ENTRY_SIZE / sizeof(AVISUPERINDEX_ENTRY);
   m_numOfRiff = 1;
   m_nowRiffSize = fileHeaderSkipSize;
   


   // 'movi'開始位置記憶。movi-LISTのサイズを書き換えに戻ってくる
   ZeroMemory( &m_moviListPos, sizeof(m_moviListPos) );
   m_moviListPos.LowPart = SetFilePointer( m_file, 0, &m_moviListPos.HighPart, FILE_CURRENT );
  

   // 'movi'LIST 書き込み
   {
    LIST movi;
    movi.dwList = mmioFOURCC('L','I','S','T');
    movi.dwSize = (MAX_CHUNK_SIZE - fileHeaderSkipSize) - 8;
    movi.dwFourCC = mmioFOURCC('m','o','v','i');

    DWORD writeSize;
    WriteFile( m_file, &movi, sizeof(movi), &writeSize, NULL );

    m_nowRiffSize += sizeof(LIST);
   }
 
   m_moviChunkOffset = 0;

   return true;
  }

  return false;
 }

 void update( const DIB32& image )
 {
  if ( isOpen() )
  {
   
   // 2048境界に合わせてJUNKつめ
   {
    const DWORD junkSize = writePaddingJUNK();
    m_nowRiffSize += junkSize;
    m_moviChunkOffset += junkSize;
   }
   

   // 画像データ書き込み
   CHUNK chunk;
   chunk.dwFourCC = mmioFOURCC('0','0','d','b');
   chunk.dwSize = m_imageSize;

   DWORD writeSize;
   WriteFile( m_file, &chunk, sizeof(chunk), &writeSize, NULL );
   WriteFile( m_file, const_cast<LPDWORD>(image.getPixelAddr()), chunk.dwSize, &writeSize, NULL );


   // ix00用にデータを詰める
   {
    AVISTDINDEX_ENTRY entry = {0};
    entry.dwSize = chunk.dwSize;
    entry.dwOffset = (sizeof(LIST)-8) + sizeof(CHUNK) + m_moviChunkOffset; //< データ位置。

    m_aviStdIndexEntryList.push_back( entry );
   }

   // idx1用にデータを詰める
   if ( !m_isAVIX )
   {
    AVIINDEXENTRY aviIndexEntry = {0};
    aviIndexEntry.ckid = mmioFOURCC('0','0','d','b');
    aviIndexEntry.dwFlags = AVIIF_KEYFRAME;
    aviIndexEntry.dwChunkLength = chunk.dwSize;      //< sizeof(CHUNK)+chunk.dwSizeじゃないらしい。なんで
    aviIndexEntry.dwChunkOffset = (sizeof(LIST)-8) + m_moviChunkOffset; //< 'movi'チャンクヘッダからの距離。-8する意味が分からないが数値はこうっぽい

    m_aviIndexEntryList.push_back( aviIndexEntry );
   }

   

   m_mostLargeImageSize = (std::max)( m_mostLargeImageSize, chunk.dwSize );
   m_nowRiffSize += sizeof(CHUNK) + chunk.dwSize;
   m_nowRiffFrame++;
   m_totalFrame++;
   m_moviChunkOffset += sizeof(CHUNK) + chunk.dwSize;


   // チャンクが指定サイズに到達しそうなら次のチャンクに移る
   const DWORD freeChunkSize = MAX_CHUNK_SIZE - (m_nowRiffSize + sizeof(AVISTDINDEX) + m_nowRiffFrame*sizeof(AVIINDEXENTRY));
   if ( freeChunkSize < m_mostLargeImageSize )
   {
    if ( m_numOfRiff >= m_maxNumOfRiff )
    {
     close();
    }
    else
    {
     nextRIFF();
    }
   }
  }
 }

 /** close */
 void close()
 {
  if ( isOpen() )
  {
   exitRIFF();

   // close
   CloseHandle( m_file );
   m_file = INVALID_HANDLE_VALUE;
  }



  m_nowRiffFrame = 0;
  m_totalFrame = 0;
  m_numOfRiff = 0;
  m_nowRiffSize = 0;
  m_mostLargeImageSize = 0;
  m_maxNumOfRiff = 0;
  m_moviChunkOffset = 0;


  ZeroMemory( &m_nowRiffFileTopPos, sizeof(m_nowRiffFileTopPos) );
  ZeroMemory( &m_moviListPos, sizeof(m_moviListPos) );

  m_isAVIX = false;
  m_riffAVIChunkFrame = 0;
  m_riffAVIChunkSize = 0;
  m_fps = 0;
  m_width = 0;
  m_height = 0;
  m_imageSize = 0;

  m_superIndexEntryList.clear();
  m_aviIndexEntryList.clear();
  m_aviStdIndexEntryList.clear();
 }

 /** isOpen */
 bool isOpen() const { return m_file!=INVALID_HANDLE_VALUE; }


 DWORD getTotalFrame() const { return m_totalFrame; }
 DWORD getNowRiffFrame() const { return m_nowRiffFrame; }


private:
 void nextRIFF()
 {
  // AVISUPERINDEX_ENTRY蓄積。各チャンクのix00の位置を示す
  {
   LARGE_INTEGER nowPos = {0};
   nowPos.LowPart = SetFilePointer( m_file, 0, &nowPos.HighPart, FILE_CURRENT );

   AVISUPERINDEX_ENTRY entry = {0};
   entry.dwSize = sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY)*m_aviStdIndexEntryList.size();
   entry.dwDuration = m_aviStdIndexEntryList.size();
   entry.qwOffset = nowPos.QuadPart;
   m_superIndexEntryList.push_back( entry );
  }


  // ix00
  {
   AVISTDINDEX standardIndex = {0};
   standardIndex.fcc = mmioFOURCC('i','x','0','0');
   standardIndex.cd = (sizeof(AVISTDINDEX) - 8) + sizeof(AVISTDINDEX_ENTRY)*m_aviStdIndexEntryList.size();
   standardIndex.wLongsPerEntry = 2;  //< must be 2
   standardIndex.bIndexSubType = 0;  //< must be 0
   standardIndex.bIndexType = AVI_INDEX_OF_CHUNKS;  //< AVI_INDEX_OF_CHUNKS == 1
   standardIndex.nEntriesInUse = m_aviStdIndexEntryList.size();
   standardIndex.dwChunkID = mmioFOURCC('0','0','d','b');
   standardIndex.qwBaseOffset = m_moviListPos.QuadPart;

   DWORD writeSize;
   WriteFile( m_file, &standardIndex, sizeof(standardIndex), &writeSize, NULL );

   // entry
   if ( !m_aviStdIndexEntryList.empty() )
   {
    WriteFile( m_file, &m_aviStdIndexEntryList[0], sizeof(m_aviStdIndexEntryList[0])*m_aviStdIndexEntryList.size(), &writeSize, NULL );
   }

   m_nowRiffSize += sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY) * m_aviStdIndexEntryList.size();
  }


  // idx1
  if ( !m_isAVIX )
  {
   // write index entry
   CHUNK index;
   index.dwFourCC = mmioFOURCC('i','d','x','1');
   index.dwSize = sizeof(AVIINDEXENTRY) * m_aviIndexEntryList.size(); 

   DWORD writeSize;
   WriteFile( m_file, &index, sizeof(index), &writeSize, NULL );

   // write entry
   if ( !m_aviIndexEntryList.empty() )
   {
    WriteFile( m_file, &m_aviIndexEntryList[0], sizeof(m_aviIndexEntryList[0])*m_aviIndexEntryList.size(), &writeSize, NULL );
   }

   m_nowRiffSize += sizeof(CHUNK) +  sizeof(AVIINDEXENTRY) * m_aviIndexEntryList.size();
  }


  if ( !m_isAVIX )
  {
   m_riffAVIChunkSize = m_nowRiffSize;
   m_riffAVIChunkFrame = m_nowRiffFrame;
  }


  {
   // 現在位置記憶
   LARGE_INTEGER nowFilePos = {0};
   nowFilePos.LowPart = SetFilePointer( m_file, 0, &nowFilePos.HighPart, FILE_CURRENT );

   // ヘッダ位置に戻ってサイズ情報を更新する
   if ( m_isAVIX )
   {
    // 現在チャンクのヘッダ位置に移動
    SetFilePointer( m_file, m_nowRiffFileTopPos.LowPart, &m_nowRiffFileTopPos.HighPart, FILE_BEGIN );
   
    // ヘッダ書き換え
    LIST avix;
    avix.dwList = mmioFOURCC('R','I','F','F');
    avix.dwSize = m_nowRiffSize - 8;
    avix.dwFourCC = mmioFOURCC('A','V','I','X');

    DWORD writeSize;
    WriteFile( m_file, &avix, sizeof(avix), &writeSize, NULL );
   }

   // moviチャンクのサイズを正しい値に書き換える
   {
    // movi位置に移動
    SetFilePointer( m_file, m_moviListPos.LowPart, &m_moviListPos.HighPart, FILE_BEGIN );

    LIST movi;
    movi.dwList = mmioFOURCC('L','I','S','T');
    movi.dwSize = (sizeof(LIST) - 8) + m_moviChunkOffset + sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY)*m_aviStdIndexEntryList.size();
    movi.dwFourCC = mmioFOURCC('m','o','v','i');

    DWORD writeSize;
    WriteFile( m_file, &movi, sizeof(movi), &writeSize, NULL );
   }

   // もとの位置に移動
   SetFilePointer( m_file, nowFilePos.LowPart, &nowFilePos.HighPart, FILE_BEGIN );
  }


  m_numOfRiff++;
  m_nowRiffSize = 0;
  m_nowRiffFrame = 0;
  m_moviChunkOffset = 0;

  m_aviIndexEntryList.clear();
  m_aviStdIndexEntryList.clear();

  m_isAVIX = true;

  // ヘッダの位置記憶
  ZeroMemory( &m_nowRiffFileTopPos, sizeof(m_nowRiffFileTopPos) );
  m_nowRiffFileTopPos.LowPart = SetFilePointer( m_file, 0, &m_nowRiffFileTopPos.HighPart, FILE_CURRENT );

  // RIFF-AVIX
  {
   LIST avix;
   avix.dwList = mmioFOURCC('R','I','F','F');
   avix.dwSize = MAX_CHUNK_SIZE;    //< 後で書き換えに来るけどとりあえず最大値
   avix.dwFourCC = mmioFOURCC('A','V','I','X');

   DWORD writeSize;
   WriteFile( m_file, &avix, sizeof(avix), &writeSize, NULL );

   m_nowRiffSize += sizeof(LIST);
  }

  // JUNK
  {
   const DWORD junkSize = writePaddingJUNK();
   m_nowRiffSize += junkSize;
  }


  // ヘッダの位置記憶
  ZeroMemory( &m_moviListPos, sizeof(m_moviListPos) );
  m_moviListPos.LowPart = SetFilePointer( m_file, 0, &m_moviListPos.HighPart, FILE_CURRENT );

  // 'movi'LIST 書き込み
  {
   LIST movi;
   movi.dwList = mmioFOURCC('L','I','S','T');
   movi.dwSize = (MAX_CHUNK_SIZE - m_nowRiffSize) - 8;
   movi.dwFourCC = mmioFOURCC('m','o','v','i');

   DWORD writeSize;
   WriteFile( m_file, &movi, sizeof(movi), &writeSize, NULL );

   m_nowRiffSize += sizeof(LIST);
  }
 }


 void exitRIFF()
 {
  // AVISUPERINDEX_ENTRY蓄積。各チャンクのix00の位置を示す
  {
   LARGE_INTEGER nowPos = {0};
   nowPos.LowPart = SetFilePointer( m_file, 0, &nowPos.HighPart, FILE_CURRENT );

   AVISUPERINDEX_ENTRY entry = {0};
   entry.dwSize = sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY)*m_aviStdIndexEntryList.size();
   entry.dwDuration = m_aviStdIndexEntryList.size();
   entry.qwOffset = nowPos.QuadPart;
   m_superIndexEntryList.push_back( entry );
  }
  
  // ix00
  {
   AVISTDINDEX standardIndex = {0};
   standardIndex.fcc = mmioFOURCC('i','x','0','0');
   standardIndex.cd = (sizeof(AVISTDINDEX) - 8) + sizeof(AVISTDINDEX_ENTRY)*m_aviStdIndexEntryList.size();
   standardIndex.wLongsPerEntry = 2;  //< must be 2
   standardIndex.bIndexSubType = 0;  //< must be 0
   standardIndex.bIndexType = AVI_INDEX_OF_CHUNKS;  //< AVI_INDEX_OF_CHUNKS == 1
   standardIndex.nEntriesInUse = m_aviStdIndexEntryList.size();
   standardIndex.dwChunkID = mmioFOURCC('0','0','d','b');
   standardIndex.qwBaseOffset = m_moviListPos.QuadPart;

   DWORD writeSize;
   WriteFile( m_file, &standardIndex, sizeof(standardIndex), &writeSize, NULL );

   // entry
   if ( !m_aviStdIndexEntryList.empty() )
   {
    WriteFile( m_file, &m_aviStdIndexEntryList[0], sizeof(m_aviStdIndexEntryList[0])*m_aviStdIndexEntryList.size(), &writeSize, NULL );
   }

   m_nowRiffSize += sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY) * m_aviStdIndexEntryList.size();
  }

  // idx1
  if ( !m_isAVIX )
  {
   // write index header
   CHUNK index;
   index.dwFourCC = mmioFOURCC('i','d','x','1');
   index.dwSize = sizeof(AVIINDEXENTRY) * m_aviIndexEntryList.size(); 

   DWORD writeSize;
   WriteFile( m_file, &index, sizeof(index), &writeSize, NULL );

   // write entry
   if ( !m_aviIndexEntryList.empty() )
   {
    WriteFile( m_file, &m_aviIndexEntryList[0], sizeof(m_aviIndexEntryList[0])*m_aviIndexEntryList.size(), &writeSize, NULL );
   }

   m_nowRiffSize += sizeof(CHUNK) +  sizeof(AVIINDEXENTRY) * m_aviIndexEntryList.size();
  }


  // まだAVI-RIFFだったらAVI-RIFFでのサイズとフレーム数記憶
  if ( !m_isAVIX )
  {
   m_riffAVIChunkSize = m_nowRiffSize;
   m_riffAVIChunkFrame = m_nowRiffFrame;
  }

  // ヘッダ位置に戻ってサイズ情報を更新する
  if ( m_isAVIX )
  {
   // 現在チャンクのヘッダ位置に移動
   SetFilePointer( m_file, m_nowRiffFileTopPos.LowPart, &m_nowRiffFileTopPos.HighPart, FILE_BEGIN );
   
   // ヘッダ書き換え
   LIST avix;
   avix.dwList = mmioFOURCC('R','I','F','F');
   avix.dwSize = m_nowRiffSize - 8;
   avix.dwFourCC = mmioFOURCC('A','V','I','X');

   DWORD writeSize;
   WriteFile( m_file, &avix, sizeof(avix), &writeSize, NULL );
  }

  // moviチャンクのサイズを正しい値に書き換える
  {
   // movi位置に移動
   SetFilePointer( m_file, m_moviListPos.LowPart, &m_moviListPos.HighPart, FILE_BEGIN );

   LIST movi;
   movi.dwList = mmioFOURCC('L','I','S','T');
   movi.dwSize = (sizeof(LIST) - 8) + m_moviChunkOffset + sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY)*m_aviStdIndexEntryList.size();
   movi.dwFourCC = mmioFOURCC('m','o','v','i');

   DWORD writeSize;
   WriteFile( m_file, &movi, sizeof(movi), &writeSize, NULL );
  }



  // AVI header
  {
   SetFilePointer( m_file, 0, NULL, FILE_BEGIN );

   // write avi header
   // RIFF-AVI LIST
   {
    LIST aviList;
    aviList.dwList = mmioFOURCC('R','I','F','F');
    aviList.dwSize = m_riffAVIChunkSize - 8;
    aviList.dwFourCC = mmioFOURCC('A','V','I',' ');

    DWORD writeSize;
    WriteFile( m_file, &aviList, sizeof(aviList), &writeSize, NULL );
   }

   // LIST-hdrl
   {
    LIST hdrlList;
    hdrlList.dwList = mmioFOURCC('L','I','S','T');
    hdrlList.dwSize = (sizeof(LIST) - 8)
     + sizeof(CHUNK) + sizeof(MainAVIHeader)
     + sizeof(LIST)
      + sizeof(CHUNK) + sizeof(AVIStreamHeader)
      + sizeof(CHUNK) + sizeof(BITMAPINFOHEADER)
      + sizeof(AVISUPERINDEX) + sizeof(AVISUPERINDEX_ENTRY) * m_superIndexEntryList.size()
     + sizeof(LIST)
      + sizeof(AVIEXTHEADER);
    hdrlList.dwFourCC = mmioFOURCC('h','d','r','l');

    DWORD writeSize;
    WriteFile( m_file, &hdrlList, sizeof(hdrlList), &writeSize, NULL );
   }

   // avih chunk
   {
    CHUNK avihChunk;
    avihChunk.dwFourCC = mmioFOURCC('a','v','i','h');
    avihChunk.dwSize = sizeof(MainAVIHeader);

    DWORD writeSize;
    WriteFile( m_file, &avihChunk, sizeof(avihChunk), &writeSize, NULL );
   }

   // MainAVIHeader
   {
    MainAVIHeader mainAVIHeader = {0};

    
    mainAVIHeader.dwMaxBytesPerSec = m_mostLargeImageSize * m_fps;
    mainAVIHeader.dwMicroSecPerFrame = (1000*1000) / m_fps;
    mainAVIHeader.dwPaddingGranularity = PADDING_GRANULARITY;
    mainAVIHeader.dwFlags = AVIF_TRUSTCKTYPE | AVIF_HASINDEX; //<< 2064
    mainAVIHeader.dwTotalFrames = m_riffAVIChunkFrame;
    mainAVIHeader.dwInitialFrames = 0;
    mainAVIHeader.dwStreams = 1;
    mainAVIHeader.dwSuggestedBufferSize = (m_mostLargeImageSize + sizeof(CHUNK));
    mainAVIHeader.dwWidth = m_width;
    mainAVIHeader.dwHeight = m_height;
    
    DWORD writeSize;
    WriteFile( m_file, &mainAVIHeader, sizeof(mainAVIHeader), &writeSize, NULL );
   }

   // LIST-strl
   {
    LIST strlList;
    strlList.dwList = mmioFOURCC('L','I','S','T');
    strlList.dwSize = (sizeof(LIST) - 8)
      + sizeof(CHUNK) + sizeof(AVIStreamHeader)
      + sizeof(CHUNK) + sizeof(BITMAPINFOHEADER)
      + sizeof(AVISUPERINDEX) + sizeof(AVISUPERINDEX_ENTRY) * m_superIndexEntryList.size();
    strlList.dwFourCC = mmioFOURCC('s','t','r','l');

    DWORD writeSize;
    WriteFile( m_file, &strlList, sizeof(strlList), &writeSize, NULL );
   }

   // strh chunk
   {
    CHUNK strhChunk;
    strhChunk.dwFourCC = mmioFOURCC('s','t','r','h');
    strhChunk.dwSize = sizeof(AVIStreamHeader);

    DWORD writeSize;
    WriteFile( m_file, &strhChunk, sizeof(strhChunk), &writeSize, NULL );
   }

   // AVIStreamHeader
   {
    AVIStreamHeader streamHeader = {0};
    streamHeader.fccType = mmioFOURCC('v','i','d','s');
    streamHeader.fccHandler = mmioFOURCC('D','I','B',' ');
    streamHeader.dwFlags = 0;
    streamHeader.wPriority = 0;
    streamHeader.wLanguage = 0;
    streamHeader.dwInitialFrames = 0;
    streamHeader.dwScale = 1000;
    streamHeader.dwRate = m_fps*1000;
    streamHeader.dwStart = 0;
    streamHeader.dwLength = /*m_riffAVIChunkFrame*/m_totalFrame;  //< 納得いかないがWMPだと全体のフレーム数でないと途中で落ちる
    streamHeader.dwSuggestedBufferSize = m_imageSize;
    streamHeader.dwQuality = -1;
    streamHeader.dwSampleSize = m_imageSize;
    streamHeader.rcFrame.left = 0;
    streamHeader.rcFrame.top = 0;
    streamHeader.rcFrame.right = m_width;
    streamHeader.rcFrame.bottom = m_height;
    DWORD writeSize;
    WriteFile( m_file, &streamHeader, sizeof(streamHeader), &writeSize, NULL );
   }

   // strf chunk
   {
    CHUNK strfChunk;
    strfChunk.dwFourCC = mmioFOURCC('s','t','r','f');
    strfChunk.dwSize = sizeof(BITMAPINFOHEADER);

    DWORD writeSize;
    WriteFile( m_file, &strfChunk, sizeof(strfChunk), &writeSize, NULL );
   }

   // BITMAPINFOHEADER
   {
    BITMAPINFOHEADER bmpInfoHeader = {0};
    bmpInfoHeader.biSize = sizeof(bmpInfoHeader);
    bmpInfoHeader.biWidth = m_width;
    bmpInfoHeader.biHeight = m_height;
    bmpInfoHeader.biPlanes = 1;
    bmpInfoHeader.biBitCount = 32;
    bmpInfoHeader.biCompression = BI_RGB; //< =0
    bmpInfoHeader.biSizeImage = m_imageSize;
    bmpInfoHeader.biXPelsPerMeter = 3780;
    bmpInfoHeader.biYPelsPerMeter = 3780;
    bmpInfoHeader.biClrUsed = 0;
    bmpInfoHeader.biClrImportant = 0;

    DWORD writeSize;
    WriteFile( m_file, &bmpInfoHeader, sizeof(bmpInfoHeader), &writeSize, NULL );
   }

   // AVISUPERINDEX + entry
   {
    
    AVISUPERINDEX aviSuperIndex = {0};
    aviSuperIndex.fcc = mmioFOURCC('i','n','d','x');
    aviSuperIndex.cd = (sizeof(AVISUPERINDEX) - 8) + sizeof(AVISUPERINDEX_ENTRY)*m_superIndexEntryList.size();
    aviSuperIndex.wLongsPerEntry = sizeof(AVISUPERINDEX_ENTRY) / 4;  //< must be 4
    aviSuperIndex.bIndexType = AVI_INDEX_OF_INDEXES;
    aviSuperIndex.bIndexSubType = 0;
    aviSuperIndex.nEntriesInUse = m_superIndexEntryList.size();
    aviSuperIndex.dwChunkID = mmioFOURCC('0','0','d','b');
    
    DWORD writeSize;
    WriteFile( m_file, &aviSuperIndex, sizeof(aviSuperIndex), &writeSize, NULL );
    
    // entry
    if ( !m_superIndexEntryList.empty() )
    {
     WriteFile( m_file, &m_superIndexEntryList[0], sizeof(AVISUPERINDEX_ENTRY)*m_superIndexEntryList.size(), &writeSize, NULL );
    }
   }

   // LIST-odml
   {
    LIST odmlList;
    odmlList.dwList = mmioFOURCC('L','I','S','T');
    odmlList.dwSize = (sizeof(LIST) - 8)
      + sizeof(AVIEXTHEADER);
    odmlList.dwFourCC = mmioFOURCC('o','d','m','l');

    DWORD writeSize;
    WriteFile( m_file, &odmlList, sizeof(odmlList), &writeSize, NULL );
   }

   // dmlh
   {
    AVIEXTHEADER dmlh = {0};
    dmlh.fcc = mmioFOURCC('d','m','l','h');
    dmlh.cb = sizeof(AVIEXTHEADER) - 8;
    dmlh.dwGrandFrames = m_totalFrame;

    DWORD writeSize;
    WriteFile( m_file, &dmlh, sizeof(dmlh), &writeSize, NULL );
   }


   // JUNK
   {
    // ファイル作成時にすっとばしたヘッダ分の領域
    const DWORD preFileHeaderSkipSize =
     sizeof(LIST)              //< RIFF-AVI LIST
      + sizeof(LIST)             //< hdrl List
       + sizeof(CHUNK) + sizeof(MainAVIHeader)      //< avih Chunk + MainAVIHeader
       + sizeof(LIST)            //< list-strl
        + sizeof(CHUNK) + sizeof(AVIStreamHeader)    //< strh Chunk + AVIStreamHeader
        + sizeof(CHUNK) + sizeof(BITMAPINFOHEADER)    //< strf Chunk + BITMAPINFOHEADER
        + sizeof(AVISUPERINDEX) + SUPER_INDEX_ENTRY_SIZE  //< AVISUPERINDEX + SUPER_INDEX_ENTRY_SIZE
       + sizeof(LIST)            //< odml List
        + sizeof(AVIEXTHEADER)         //< AVIEXTHEADER
      + sizeof(LIST);            //< movi LIST
    DWORD fileHeaderSkipSize = preFileHeaderSkipSize;


    if ( PADDING_GRANULARITY )
    {
     const DWORD PaddingGranularity = PADDING_GRANULARITY;   //< 0だとコンパイル時に0除算で怒られるので適当に変数にいったん入れる
     fileHeaderSkipSize = preFileHeaderSkipSize + (PADDING_GRANULARITY - (preFileHeaderSkipSize % PaddingGranularity));
    }


    // 'movi'はすでに記述されているのでその分の領域は計算に入れない
    fileHeaderSkipSize -= sizeof(LIST);

    // 実際に記述したヘッダ分
    const DWORD writtenHeaderSize =
     sizeof(LIST)                       // riff-avi
      + sizeof(LIST)                      // list-hdrl
       + sizeof(CHUNK) + sizeof(MainAVIHeader)               // avih + MainAVIHeader
       + sizeof(LIST)                     // list-strl
        + sizeof(CHUNK) + sizeof(AVIStreamHeader)             // strh + AVIStreamHeader
        + sizeof(CHUNK) + sizeof(BITMAPINFOHEADER)             // strf + BITMAPINFOHEADER
        + sizeof(AVISUPERINDEX) + sizeof(AVISUPERINDEX_ENTRY) * m_superIndexEntryList.size()  // AVISUPERINDEX + AVISUPERINDEX_ENTRY
       + sizeof(LIST)                     // LIST-odml
        + sizeof(AVIEXTHEADER);                  // AVIEXTHEADER
    const DWORD junkSize = fileHeaderSkipSize - writtenHeaderSize;
    writeJUNK( junkSize );
   }
  }

 }


 // 指定サイズのJUNKチャンクを書き込む。CHUNK込み
 void writeJUNK( DWORD junkSize )
 {
  CHUNK chunk;
  chunk.dwFourCC = mmioFOURCC('J','U','N','K');
  chunk.dwSize = junkSize - sizeof(chunk);

  DWORD writeSize;
  WriteFile( m_file, &chunk, sizeof(chunk), &writeSize, NULL );
  //SetFilePointer( m_file, chunk.dwSize, NULL, FILE_CURRENT );
  std::vector< BYTE > blankData( chunk.dwSize );
  WriteFile( m_file, &blankData[0], chunk.dwSize, &writeSize, NULL );
 }

 // 2048境界になるようJUNKを書き込む。
 DWORD writePaddingJUNK()
 {
  // 2048バイト境界になるようJUNKをつめる
  if ( PADDING_GRANULARITY )
  {
   DWORD padding = SetFilePointer( m_file, 0, NULL, FILE_CURRENT );
   
   const DWORD PaddingGranularity = PADDING_GRANULARITY;   //< 0だとコンパイル時に0除算で怒られるので適当に変数にいったん入れる
   const DWORD paddingOverSize = padding % PaddingGranularity;
   if ( paddingOverSize )
   {
    // つめるjunkサイズを計算する。
    // chunk構造体より大きいサイズでないとならないので、それより小さければかさ増しする
    // PADDING_GRANULARITYがsizeof(CHUNK)より小さいと問題が出るかも
    const DWORD junkSize = PADDING_GRANULARITY - paddingOverSize;
    if ( junkSize > sizeof(CHUNK) )
    {
     writeJUNK( junkSize );
     return junkSize;
    }
    else
    {
     writeJUNK( junkSize + PADDING_GRANULARITY );
     return junkSize + PADDING_GRANULARITY;
    }
   }
  }

  return 0;
 }




 HANDLE m_file;
 DWORD m_nowRiffFrame;
 DWORD m_totalFrame;
 
 DWORD m_numOfRiff;
 DWORD m_nowRiffSize;
 DWORD m_mostLargeImageSize;

 DWORD m_maxNumOfRiff;
 std::vector< AVIINDEXENTRY > m_aviIndexEntryList;
 std::vector< AVISUPERINDEX_ENTRY > m_superIndexEntryList;
 
 DWORD m_moviChunkOffset;   //< 'movi'チャンク位置からの距離
 std::vector< AVISTDINDEX_ENTRY > m_aviStdIndexEntryList;

 LARGE_INTEGER m_nowRiffFileTopPos; //< 各チャンクサイズを書き換えに戻る際に使用する、チャンク開始位置
 LARGE_INTEGER m_moviListPos;  //< 'movi'開始位置
 
 bool m_isAVIX;

 DWORD m_width, m_height;
 DWORD m_imageSize;
 DWORD m_fps;
 DWORD m_riffAVIChunkFrame;   //< AVI-RIFF 内のフレーム数
 DWORD m_riffAVIChunkSize;   //< AVI-RIFFのサイズ
};






struct ObjPos
{
 float x, y;
 float vx, vy;
};

 
 
LRESULT CALLBACK wndProc(
 HWND hWnd,
 UINT msg,
 WPARAM wParam,
 LPARAM lParam )
{
 static DIB32 back, image;
 static ObjPos pos[ 64 ];
 static AVIRecorder avi;

 switch (msg)
 {
 case WM_DESTROY:
  //SendMessage( hWnd, WM_TIMER, 0, 0 );
  avi.close();
  ShowWindow( hWnd, SW_HIDE );
  PostQuitMessage(0);
  break;
 case WM_CREATE:
  SetTimer( hWnd, TIMER_ID, 1000/FPS, NULL );

  back.create( WINDOW_WIDTH, WINDOW_HEIGHT );
  image.create( TEXT("image.bmp") );

  // init obj
  {
   srand( GetTickCount() );

   const int n  = sizeof(pos) / sizeof(*pos);
   for (int i=0; i<n; ++i)
   {
    pos[i].x = static_cast<float>( std::rand() % WINDOW_WIDTH );
    pos[i].y = static_cast<float>( std::rand() % WINDOW_HEIGHT );
    pos[i].vx = (static_cast<float>( std::rand() & 1023 ) / 1023.f) * 8 - 4;
    pos[i].vy = (static_cast<float>( std::rand() & 1023 ) / 1023.f) * 8 - 4;
   }
  }
  
  if ( !avi.create( TEXT("output.avi"), back.getWidth(), back.getHeight(), FPS ) )
  {
   MessageBox( hWnd, TEXT("errir create avi"), NULL, MB_OK );
  }

  break;
 case WM_PAINT:
  {
   PAINTSTRUCT ps = {0};
   HDC hdc = BeginPaint( hWnd, &ps );
   back.render( hdc, hWnd );
   EndPaint( hWnd, &ps );
  }
  break;
 case WM_TIMER:
  ZeroMemory( back.getPixelAddr(), back.getWidth() * back.getHeight() * 4 );
  
  // move obj + render
  {
   const int n  = sizeof(pos) / sizeof(*pos);
   for (int i=0; i<n; ++i)
   {
    if ( pos[i].x + pos[i].vx < 0 || pos[i].x + pos[i].vx > WINDOW_WIDTH ) pos[i].vx = -pos[i].vx;
    if ( pos[i].y + pos[i].vy < 0 || pos[i].y + pos[i].vy > WINDOW_HEIGHT ) pos[i].vy = -pos[i].vy;
    pos[i].x += pos[i].vx;
    pos[i].y += pos[i].vy;

    image.render< DIB32::PutColorAdd >( back, pos[i].x, pos[i].y, image.getWidth(),image.getHeight(), 0,0,image.getWidth(),image.getHeight() );
   }
  }

  InvalidateRect( hWnd, NULL, FALSE );
  avi.update( back );


  // title
  {
   TCHAR title[ 256 ] = {0};
   wsprintf( title, TEXT("%d(%d)"), avi.getNowRiffFrame(), avi.getTotalFrame() );
   SetWindowText( hWnd, title );
  }
  break;

 default:
  return DefWindowProc( hWnd, msg, wParam, lParam );
 }
 
 return 0;
}
 



 
 
 
int WINAPI WinMain(
 HINSTANCE hInstance,
 HINSTANCE, PSTR, int )
{
 LPCTSTR WINDOW_NAME = TEXT("sample");
 
 WNDCLASSEX wc;
 wc.style  = CS_HREDRAW | CS_VREDRAW;
 wc.lpfnWndProc = reinterpret_cast<WNDPROC>( wndProc );
 wc.cbClsExtra = 0;
 wc.cbWndExtra = 0;
 wc.cbSize  = sizeof( WNDCLASSEX );
 wc.hInstance = hInstance;
 wc.hIcon  = NULL;
 wc.hIconSm  = NULL;
 wc.hCursor  = LoadCursor( NULL, IDC_ARROW );
 wc.hbrBackground= reinterpret_cast<HBRUSH>( GetStockObject(WHITE_BRUSH) );
 wc.lpszMenuName = NULL;
 wc.lpszClassName= WINDOW_NAME;
 if ( !RegisterClassEx(&wc) ) return 0;
 
 LONG winWidth = WINDOW_WIDTH
  + GetSystemMetrics(SM_CXEDGE)
  + GetSystemMetrics(SM_CXBORDER)
  + GetSystemMetrics(SM_CXDLGFRAME);
 LONG winHeight = WINDOW_HEIGHT
  + GetSystemMetrics(SM_CYEDGE)
  + GetSystemMetrics(SM_CYBORDER)
  + GetSystemMetrics(SM_CYDLGFRAME)
  + GetSystemMetrics(SM_CYCAPTION);
 HWND hWnd = CreateWindowEx(
  0, WINDOW_NAME, NULL, WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME,
  CW_USEDEFAULT, CW_USEDEFAULT, winWidth, winHeight,
  NULL, NULL, hInstance, NULL);
 if ( !hWnd ) return -1;
 
 ShowWindow( hWnd, SW_SHOWNORMAL );
 UpdateWindow( hWnd );

 
 MSG msg;
 for (;;)
 {
  if ( !GetMessage(&msg, NULL, 0, 0) ) break;
  TranslateMessage( &msg );
  DispatchMessage( &msg );
 }
 
 
 UnregisterClass( WINDOW_NAME, hInstance );
 return msg.wParam;
}