実際に出力した動画ファイル
[161013]avi20_output.avi
filesize: 19.8MB
ざっくりとしたファイルフォーマット図。
青字が記述に使用する構造体、緑字がその構造体のサイズになります。
自分がプログラムを組む際どこに何を書き込むのかよくわからなかったのでまとめました。
矢印は各インデックスがさすデータ位置で、
チャンク先頭だったりデータ先頭だったりするようです。
WindowsMediaPlayerで再生するには、
AVIStreamHeaderのdwLengthをファイル全体のトータルフレーム数にしないとRIFF-AVI部分を再生後にエラーがでるっぽいです。
仕様ではRIFF-AVI内のフレーム数を指定するはずです。
ソースファイル
file: [20161013]AVI20.cpp
前回のソースコードはどうもチャンクサイズが間違ってるっぽいので新しく書き直しました。
ファイル作成後ヘッダー部分を飛ばし、画像データを書き込みつつRIFFリストを追記更新。
記録終了時にヘッダーを書きにファイル先端に戻ってヘッダー記述、という流れになります。
■2018/07/07追記
書込みが4GBを超えているときにSetFilePointer()が-1(INVALID_SET_FILE_POINTER)を返すパターンに遭遇。
このときのGetLastError()は87で「パラメーターが間違っています。」でした。
SetFilePointerEx()を使うべきなのでしょう。
#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; }
5 件のコメント:
Thank you for sharing this code. I think there is a bug though.
The first few pixels of the bitmap frames are corrupt. You can see this if you open the avi file then zoom into the bottom left corner. Do you know what is causing this corruption?
コメントありがとうございます。
AVIRecorder::update()内の
// ix00用にデータを詰める
{
AVISTDINDEX_ENTRY entry = {0};
entry.dwSize = chunk.dwSize;
entry.dwOffset = (sizeof(LIST)-8) + sizeof(CHUNK) + m_moviChunkOffset; //< データ位置。
m_aviStdIndexEntryList.push_back( entry );
}
↓
// ix00用にデータを詰める
{
AVISTDINDEX_ENTRY entry = {0};
entry.dwSize = chunk.dwSize;
entry.dwOffset = sizeof(LIST) + sizeof(CHUNK) + m_moviChunkOffset; //< データ位置。
m_aviStdIndexEntryList.push_back( entry );
}
sorry.
でもなんでこうなのかはよくわからないけどとりあえず
Thank you. That change fixes the corrupt pixels.
Would it be possible for you to update your sample code to also output audio streams? That would be very useful.
Hi. Is audio support something you could add?
Thanks in advance
もともとはリアルタイムので録画が難しい環境で60fpsの動画を出力するのが目的だったので、録音は考えていません。
sorry.
コメントを投稿