AVIファイルフォーマットが大変参考になりました。
1280x720、60FPSで2分30秒ほどの動画を作成。 38GBほどのファイルが出力された。
そのままでは重くて見られなかったのでffmpegおよび、
VideoConverter 1.10でwmvに変換、再生したところ正しく再生できる様子だった。
曲は適当。
■仕様
・RIFF-AVIは1GB以下
・4GBセグメントを越えないようRIFFサイズを512MBにした。
・RIFFサイズを512MBぴったりにJUNKを調整する
・最低1TB記録するにはAVISUPERINDEX_ENTRYが2048個必要
・moviリスト後が2048バイト境界ぴったりにする
・JUNKに無駄がでないようにAVISUPERINDEX_ENTRYにまわす
といった条件を満たすよう計算しファイルに記録していきます。
512MBではなく1GBにしても特に問題はないがなんとなく。
■使い方 AVIRecorder avi;
avi.create( filename, width, height, fps );
avi.update( image );
avi.update( image );
...
avi.close();
create()でファイル先頭に適切なサイズのスペースをあけ
update()で画像を記録、
512MBいっぱいになったら次のRIFFを作成
close()でRIFFのサイズ、moviサイズと
ファイル先頭のヘッダ情報(総フレーム数とか)を書き換えて記録終了な流れになります。
■AVIファイルフォーマット
RIFF - AVI
├─LIST - hdrl
│ ├─avih
│ │ ├─dwMicroSecPerFrame
│ │ ├─dwMaxBytesPerSec
│ │ ├─dwPaddingGranularity
│ │ ├─dwFlags
│ │ ├─dwTotalFrames
│ │ ├─dwInitialFrames
│ │ ├─dwSuggestedBufferSize
│ │ ├─dwWidth
│ │ ├─dwHeight
│ │ └─dwReserved[4]
│ │
│ ├─LIST - strl
│ │ ├─strh
│ │ │ ├─fccType
│ │ │ ├─fccHeader
│ │ │ ├─dwFlags
│ │ │ ├─dwPriority
│ │ │ ├─dwLanguage
│ │ │ ├─dwInitialFrames
│ │ │ ├─dwScale
│ │ │ ├─dwRate
│ │ │ ├─dwStart
│ │ │ ├─dwLength
│ │ │ ├─dwSuggestedBufferSize
│ │ │ ├─dwQuality
│ │ │ ├─dwSampleSize
│ │ │ └─rcFrame
│ │ │
│ │ ├─strf
│ │ │ ├─biSize
│ │ │ ├─biWidth
│ │ │ ├─biHeight
│ │ │ ├─biPlanes
│ │ │ ├─biBitCount
│ │ │ ├─biSizeImage
│ │ │ ├─biXPelsPerMeter
│ │ │ ├─biYPelsPerMeter
│ │ │ ├─biClrUsed
│ │ │ └─biClrImportant
│ │ │
│ │ └─index (32 + 16*x byte)
│ │ ├─fcc
│ │ ├─cd
│ │ ├─wLongsPerEntry
│ │ ├─bIndexSubType
│ │ ├─bIndexType
│ │ ├─nEntriesInUse
│ │ ├─dwChunkID
│ │ ├─dwReserved[3]
│ │ │(* riff)
│ │ ├─qwOffset
│ │ ├─dwSize
│ │ └─dwDuration
│ │
│ └JUNK
│
├ LIST - movi
│ ├─00db
│ ├─00db
│ ├─00db...
│ └─ix00
│ ├─fcc
│ ├─cd
│ ├─wLongsPerEntry
│ ├─bIndexSubType
│ ├─bIndexType
│ ├─nEntriesInUse
│ ├─dwChunkID
│ ├─qwBaseOffset
│ ├─dwReserved
│ │(* frame)
│ ├─dwOffset
│ └─dwSize
│
└─idx1
│(* frame)
├─ckid
├─dwFlags
├─dwChunkOffset
└─dwChunkLength
RIFF - AVIX
├─JUNK
├─LIST - movi
│ ├─00db
│ ├─00db
│ ├─00db...
│ └─ix00
│ ├─fcc
│ ├─cd
│ ├─wLongsPerEntry
│ ├─bIndexSubType
│ ├─bIndexType
│ ├─nEntriesInUse
│ ├─dwChunkID
│ ├─qwBaseOffset
│ ├─dwReserved
│ │(* frame)
│ ├─dwOffset
│ └─dwSize
│
└─JUNK
RIFF - AVIX
RIFF - AVIX...
■サンプルプログラム
実行すると実行時間に応じたoutput.aviが出力される。
#include <cstdio> #include <vector> #include <windows.h> #include <vfw.h> #include <aviriff.h> const int WINDOW_WIDTH = 640; const int WINDOW_HEIGHT = 480; const int TIMER_ID = 1000; const int FPS = 60; class DIB32 { public: /** * Point */ struct Point { LONG x, y; LONG u, v; void set( LONG x, LONG y, LONG u, LONG v ) { this->x = x; this->y = y; this->u = u; this->v = v; } }; 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; //96dpiだと3780らしい。0の場合もあるとのこと 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 ); } template < class T > inline void renderLine( LPDWORD destLine, LONG destMaxWidth, LONG startX, LONG endX, float startU, float startV, float endU, float endV ) { if ( startX > endX ) { std::swap( startX, endX ); std::swap( startU, endU ); std::swap( startV, endV ); } if ( startX > destMaxWidth || endX < 0 || (endX-startX)==0 ) return; const float addU = (endU-startU) / static_cast<float>(endX - startX); const float addV = (endV-startV) / static_cast<float>(endX - startX); // cliping-left if ( startX < 0 ) { startU += (addU * static_cast<float>(-startX)); startV += (addV * static_cast<float>(-startX)); startX = 0; } // cliping-right if ( endX > destMaxWidth ) { endX = destMaxWidth; } float u = startU; float v = startV; for (LONG x=startX; x<endX; x++) { T::update( *(destLine + x), getPixel(static_cast<LONG>(u), static_cast<LONG>(v)) ); u += addU; v += addV; } } template < class T > bool triangle( DIB32& dest, const Point& point1, const Point& point2, const Point& point3 ) { Point p1( point1 ), p2( point2 ), p3( point3 ); // 手動ソート if ( p1.y > p2.y ) std::swap( p1, p2 ); if ( p1.y > p3.y ) std::swap( p1, p3 ); if ( p2.y > p3.y ) std::swap( p2, p3 ); const float height1_2 = static_cast<float>( p2.y-p1.y ? p2.y-p1.y : 1 ); const float height1_3 = static_cast<float>( p3.y-p1.y ? p3.y-p1.y : 1 ); const float height2_3 = static_cast<float>( p3.y-p2.y ? p3.y-p2.y : 1 ); const float destAddX1_2 = static_cast<float>(p2.x-p1.x) / height1_2; const float destAddX1_3 = static_cast<float>(p3.x-p1.x) / height1_3; const float destAddX2_3 = static_cast<float>(p3.x-p2.x) / height2_3; const float addU1_2 = static_cast<float>(p2.u-p1.u) / height1_2; const float addU1_3 = static_cast<float>(p3.u-p1.u) / height1_3; const float addU2_3 = static_cast<float>(p3.u-p2.u) / height2_3; const float addV1_2 = static_cast<float>(p2.v-p1.v) / height1_2; const float addV1_3 = static_cast<float>(p3.v-p1.v) / height1_3; const float addV2_3 = static_cast<float>(p3.v-p2.v) / height2_3; // 1~2 float startX = static_cast<float>( p1.x ); float endX = static_cast<float>( p1.x ); float startU = static_cast<float>( p1.u ); float startV = static_cast<float>( p1.v ); float endU = static_cast<float>( p1.u ); float endV = static_cast<float>( p1.v ); LPDWORD destLine = dest.getPixelAddr( 0, p1.y ); for (LONG y=p1.y; y<p2.y&&y<dest.getHeight(); y++) { // render line if ( y >= 0 ) { renderLine<T>( destLine, dest.getWidth(), static_cast<LONG>(startX), static_cast<LONG>(endX), startU, startV, endU, endV ); } startX += destAddX1_2; endX += destAddX1_3; startU += addU1_2; endU += addU1_3; startV += addV1_2; endV += addV1_3; destLine -= dest.getWidth(); } // 2~3 startX = static_cast<float>( p2.x ); startU = static_cast<float>( p2.u ); startV = static_cast<float>( p2.v ); for (LONG y=p2.y; y<p3.y&&y<dest.getHeight(); ++y) { if ( y >= 0 ) { renderLine<T>( destLine, dest.getWidth(), static_cast<LONG>(startX), static_cast<LONG>(endX), startU, startV, endU, endV ); } startX += destAddX2_3; endX += destAddX1_3; startU += addU2_3; endU += addU1_3; startV += addV2_3; endV += addV1_3; destLine -= dest.getWidth(); } return true; } LONG getWidth() const { return m_bmi.bmiHeader.biWidth; } LONG getHeight() const { return m_bmi.bmiHeader.biHeight; } const LPDWORD getPixelAddr() const { return m_pixel; } const LPDWORD getPixelAddr( LONG x, LONG y ) const { return m_pixel + ((getHeight()-1)-y)*getWidth() + x; } LPDWORD getPixelAddr() { return m_pixel; } LPDWORD getPixelAddr( LONG x, LONG y ) { return const_cast<LPDWORD>(static_cast<const DIB32&>(*this).getPixelAddr(x, y)); } DWORD getPixel( LONG x, LONG y ) const { if ( x >= 0 && x < getWidth() && y >= 0 && y < getHeight() ) { return *getPixelAddr( x, y ); } return 0; } protected: LPDWORD m_pixel; BITMAPINFO m_bmi; }; class AVIRecorder { public: #pragma pack(push, 2) struct LIST { DWORD dwList; DWORD dwSize; DWORD dwFourCC; }; struct CHUNK { DWORD dwFourCC; DWORD dwSize; }; 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; }; 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; }; enum { //MAX_RIFF_SIZE = 1 << 30, //< 1024MB MAX_RIFF_SIZE = 1 << 29, //< 512MB //MAX_RIFF_SIZE = 1 << 27, //< 128MB //MAX_RIFF_SIZE = 1 << 25, //< 32MB MIN_NUM_OF_RIFF = 2048, PADDING_GRANULARITY = 2048, NEED_SUPER_INDEX_ENTRYS_SIZE = MIN_NUM_OF_RIFF * sizeof(AVISUPERINDEX_ENTRY) }; struct AVIHeader { LIST aviList; LIST hdrlList; CHUNK avih; MainAVIHeader mainAVIHeader; LIST strlList; CHUNK strh; AVIStreamHeader streamHeader; CHUNK strf; BITMAPINFOHEADER streamFormat; AVISUPERINDEX superIndex; void setData( DWORD width, DWORD height, DWORD frame, DWORD fps, DWORD numOfRiff, DWORD headerJUNKSize ) { const DWORD imageSize = width * height * 4; // RIFF - AVI aviList.dwList = mmioFOURCC('R','I','F','F'); aviList.dwSize = sizeof(aviList) + sizeof(hdrlList) + sizeof(avih) + sizeof(mainAVIHeader) + sizeof(strlList) + sizeof(strh) + sizeof(streamHeader) + sizeof(strf) + sizeof(streamFormat) + sizeof(superIndex) + sizeof(AVISUPERINDEX_ENTRY)*numOfRiff + sizeof(LIST) + sizeof(AVIEXTHEADER) + headerJUNKSize + sizeof(LIST) + (sizeof(CHUNK) + imageSize)*frame + sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY)*frame + sizeof(CHUNK) + sizeof(AVIINDEXENTRY)*frame //+ tailJUNKSize //< AVI互換だからJUNKは入れなくて良し - 8; aviList.dwFourCC = mmioFOURCC('A','V','I',' '); // LIST - hdrl hdrlList.dwList = mmioFOURCC('L','I','S','T'); hdrlList.dwSize = sizeof(hdrlList) + sizeof(avih) + sizeof(mainAVIHeader) + sizeof(strlList) + sizeof(strh) + sizeof(streamHeader) + sizeof(strf) + sizeof(streamFormat) + sizeof(superIndex) + sizeof(AVISUPERINDEX_ENTRY)*numOfRiff + sizeof(LIST) + sizeof(AVIEXTHEADER) - 8; hdrlList.dwFourCC = mmioFOURCC('h','d','r','l'); // avih chunk avih.dwFourCC = mmioFOURCC('a','v','i','h'); avih.dwSize = sizeof(mainAVIHeader); // MainAVIHeader mainAVIHeader.dwMicroSecPerFrame = 1000000 / fps; mainAVIHeader.dwMaxBytesPerSec = imageSize * fps; mainAVIHeader.dwPaddingGranularity = PADDING_GRANULARITY; mainAVIHeader.dwFlags = 2064; mainAVIHeader.dwTotalFrames = frame; mainAVIHeader.dwInitialFrames = 0; mainAVIHeader.dwStreams = 1; mainAVIHeader.dwSuggestedBufferSize = imageSize + sizeof(CHUNK); mainAVIHeader.dwWidth = width; mainAVIHeader.dwHeight = height; // LIST - strl strlList.dwList = mmioFOURCC('L','I','S','T'); strlList.dwSize = sizeof(strlList) + sizeof(strh) + sizeof(streamHeader) + sizeof(strf) + sizeof(streamFormat) + sizeof(superIndex) + sizeof(AVISUPERINDEX_ENTRY)*numOfRiff - 8; strlList.dwFourCC = mmioFOURCC('s','t','r','l'); // strh chunk strh.dwFourCC = mmioFOURCC('s','t','r','h'); strh.dwSize = sizeof(streamHeader); // AVIStreamHeader 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 = 1; streamHeader.dwRate = fps; streamHeader.dwStart = 0; streamHeader.dwLength = frame; streamHeader.dwSuggestedBufferSize = mainAVIHeader.dwSuggestedBufferSize; streamHeader.dwQuality = 0; streamHeader.dwSampleSize = imageSize; streamHeader.rcFrame.left = 0; streamHeader.rcFrame.top = 0; streamHeader.rcFrame.right = width; streamHeader.rcFrame.bottom = height; // strf chunk strf.dwFourCC = mmioFOURCC('s','t','r','f'); strf.dwSize = sizeof(streamFormat); // BITMAPINFOHEADER streamFormat.biSize = sizeof(streamFormat); streamFormat.biWidth = width; streamFormat.biHeight = height; streamFormat.biPlanes = 1; streamFormat.biBitCount = 32; streamFormat.biCompression = 0; streamFormat.biSizeImage = imageSize; streamFormat.biXPelsPerMeter = 3780; streamFormat.biYPelsPerMeter = 3780; streamFormat.biClrUsed = 0; streamFormat.biClrImportant = 0; // AVISUPERINDEX superIndex.fcc = mmioFOURCC('i','n','d','x'); superIndex.cd = sizeof(superIndex) + sizeof(AVISUPERINDEX_ENTRY)*numOfRiff - 8; superIndex.wLongsPerEntry = 4; superIndex.bIndexSubType = 0; superIndex.bIndexType = 0; superIndex.nEntriesInUse = numOfRiff; superIndex.dwChunkID = mmioFOURCC('0','0','d','b'); } }; #pragma pack(pop) /** constructor */ AVIRecorder() : m_file( INVALID_HANDLE_VALUE ) , m_nowRiffFrame( 0 ) , m_totalFrame( 0 ) , m_numOfRiff( 0 ) , m_maxNumOfFrame( 0 ) , m_maxNumOfRiff( 0 ) , m_tailJUNKSize( 0 ) , m_isAVIX( false ) , m_aviFrame( 0 ) , m_width( 0 ) , m_height( 0 ) , m_fps( 0 ) , m_aviHeaderJUNKSize( 0 ) { ZeroMemory( &m_nowRiffFilePos, sizeof(m_nowRiffFilePos) ); ZeroMemory( &m_dataStartPos, sizeof(m_dataStartPos) ); } /** desturctor */ ~AVIRecorder() { clear(); } /** clear */ void clear() { close(); m_nowRiffFrame = 0; m_totalFrame = 0; m_numOfRiff = 0; m_maxNumOfFrame = 0; m_maxNumOfRiff = 0; m_tailJUNKSize = 0; ZeroMemory( &m_nowRiffFilePos, sizeof(m_nowRiffFilePos) ); ZeroMemory( &m_dataStartPos, sizeof(m_dataStartPos) ); m_isAVIX = false; m_aviFrame = 0; m_fps = 0; m_width = 0; m_height = 0; m_aviHeaderJUNKSize = 0; } /** isOpen */ bool isOpen() const { return m_file!=INVALID_HANDLE_VALUE; } /** create */ bool create( LPCTSTR fileName, DWORD width, DWORD height, DWORD fps ) { clear(); 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; calcNewAVIParams(); // とりあえず2048Byte書き込みしてヘッダ部分をスキップ std::vector< BYTE > tmp( 532 + m_maxNumOfRiff*sizeof(AVISUPERINDEX_ENTRY) + m_headerJUNKSize ); DWORD writeSize; WriteFile( m_file, &tmp[0], tmp.size(), &writeSize, NULL ); // データ開始位置記憶 // 'ix00'の記録に使用する saveNowFilePos( m_dataStartPos ); return true; } return false; } /** update */ void update( const DIB32& image ) { if ( isOpen() ) { 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 ); m_nowRiffFrame++; m_totalFrame++; if ( m_nowRiffFrame == m_maxNumOfFrame ) { if ( m_numOfRiff+1 > m_maxNumOfRiff ) { close(); } else { nextRIFF(); } } } } /** close */ void close() { if ( isOpen() ) { exitRIFF(); // close CloseHandle( m_file ); m_file = INVALID_HANDLE_VALUE; } } /** getTotalFrame */ DWORD getTotalFrame() const { return m_totalFrame; } private: void nextRIFF() { saveSuperIndexPos(); // RIFF終端辺を書き込み write_ix00(); if ( m_isAVIX == false ) { write_idx1(); m_aviFrame = m_nowRiffFrame; } writeJUNK( m_tailJUNKSize ); // 新しいRIFFの準備 m_isAVIX = true; m_nowRiffFrame = 0; // save RIFF-AVIX pos saveNowFilePos( m_nowRiffFilePos ); // パラメタ計算 // 内部でm_nowRiffFilePos使用するので先にデータをセットすること calcNewAVIXParams(); // write RIFF-AVIX // とりあえず最大サイズで記述しておけば、次のRIFF作成時に戻ってこずにすむ write_AVIX( MAX_RIFF_SIZE - 8 ); // write JUNK chunk writeJUNK( m_headerJUNKSize ); // save movi LIST pos //saveNowFilePos( m_moviPos ); // write LIST-movi { DWORD maxMoviSize = sizeof(LIST) + sizeof(AVISTDINDEX) + (sizeof(CHUNK) + m_imageSize) * m_maxNumOfFrame + sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY)*m_maxNumOfFrame - 8; write_movi( maxMoviSize ); } // save imageData startPos saveNowFilePos( m_dataStartPos ); m_numOfRiff++; } /** * exitRIFF */ void exitRIFF() { saveSuperIndexPos(); // RIFF終端辺を書き込み write_ix00(); if ( m_isAVIX == false ) { write_idx1(); m_aviFrame = m_nowRiffFrame; } // AVIXヘッダ書き直し else { SetFilePointer( m_file, m_nowRiffFilePos.LowPart, &m_nowRiffFilePos.HighPart, FILE_BEGIN ); // write RIFF-AVIX DWORD avixSize = sizeof(LIST) + m_headerJUNKSize + sizeof(LIST) + (sizeof(CHUNK) + m_imageSize)*m_nowRiffFrame + sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY) * m_nowRiffFrame //+ m_tailJUNKSize - 8; write_AVIX( avixSize ); // write junk writeJUNK( m_headerJUNKSize ); // write movi DWORD moviSize = sizeof(LIST) + (sizeof(CHUNK) + m_imageSize)*m_nowRiffFrame + sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY) * m_nowRiffFrame - 8; write_movi( moviSize ); } // ファイルヘッダの記述 SetFilePointer( m_file, 0, NULL, FILE_BEGIN ); writeHeader(); } /** * 'AVI 'の最大格納数などを計算 */ void calcNewAVIParams() { // MAX_RIFF_SIZEに格納可能な最大フレーム数を計算する // 572および32は固定の必須データサイズ (MainAVIHeaderとか) m_maxNumOfFrame = (MAX_RIFF_SIZE - (572 + NEED_SUPER_INDEX_ENTRYS_SIZE + PADDING_GRANULARITY)) / (32 + m_imageSize); // 最大フレーム数格納時の、空きスペースを計算 DWORD freeSize = MAX_RIFF_SIZE - (572 + NEED_SUPER_INDEX_ENTRYS_SIZE + PADDING_GRANULARITY + (32 + m_imageSize)*m_maxNumOfFrame) + PADDING_GRANULARITY; DWORD tmpHeaderJUNK = 0; m_tailJUNKSize = 0; if ( PADDING_GRANULARITY ) { // 空きスペースから'movi'前のJUNKを抜き取り(2048境界を考慮して) tmpHeaderJUNK = PADDING_GRANULARITY - ((532 + freeSize - sizeof(CHUNK)) % PADDING_GRANULARITY); // 空きスペースから終端のJUNKサイズ分をぬきとり。 m_tailJUNKSize = ((532 + freeSize - sizeof(CHUNK)) % PADDING_GRANULARITY) + sizeof(CHUNK); } // 空きスペースからAVISUPERINDEX_ENTRY分をぬきとり DWORD superIndexEntrySize = freeSize - tmpHeaderJUNK - m_tailJUNKSize; // 最小のJUNKを計算 DWORD addHeaderJUNK = superIndexEntrySize % sizeof(AVISUPERINDEX_ENTRY); m_headerJUNKSize = tmpHeaderJUNK + addHeaderJUNK; // 格納可能なRIFF数を計算 m_maxNumOfRiff = (superIndexEntrySize - addHeaderJUNK) / sizeof(AVISUPERINDEX_ENTRY) + MIN_NUM_OF_RIFF; m_aviHeaderJUNKSize = m_headerJUNKSize; } /** * 'AVIX'の最大格納数などを計算 * 必要:m_nowRiffPos, m_imageSize */ void calcNewAVIXParams() { if ( PADDING_GRANULARITY ) { m_headerJUNKSize = PADDING_GRANULARITY - ((m_nowRiffFilePos.LowPart + sizeof(LIST) + sizeof(LIST)) % PADDING_GRANULARITY); } else { m_headerJUNKSize = 0; } m_maxNumOfFrame = (MAX_RIFF_SIZE - (sizeof(LIST) + m_headerJUNKSize + sizeof(LIST))) / (sizeof(CHUNK) + sizeof(AVISTDINDEX_ENTRY) + m_imageSize); m_tailJUNKSize = MAX_RIFF_SIZE - (sizeof(LIST) + m_headerJUNKSize + sizeof(LIST) + (sizeof(CHUNK) + sizeof(AVISTDINDEX_ENTRY) + m_imageSize)*m_maxNumOfFrame + sizeof(AVISTDINDEX)); } /** * saveNowFilePos */ void saveNowFilePos( LARGE_INTEGER& pos ) { pos.QuadPart = 0; pos.LowPart = SetFilePointer( m_file, 0, &pos.HighPart, FILE_CURRENT ); } /** * saveSuperIndexPos */ bool saveSuperIndexPos() { if ( isOpen() ) { LARGE_INTEGER superIndexPos = {0}; saveNowFilePos( superIndexPos ); AVISUPERINDEX_ENTRY entry = {0}; entry.dwSize = sizeof(AVISTDINDEX) + sizeof(AVISTDINDEX_ENTRY)*m_nowRiffFrame; entry.dwDuration = m_nowRiffFrame; entry.qwOffset = superIndexPos.QuadPart; m_superIndexEntryList.push_back( entry ); return true; } return false; } /** * write_AVIX */ bool write_AVIX( DWORD size ) { if ( isOpen() ) { LIST avix; avix.dwList = mmioFOURCC('R','I','F','F'); avix.dwSize = size; avix.dwFourCC = mmioFOURCC('A','V','I','X'); DWORD writeSize; WriteFile( m_file, &avix, sizeof(avix), &writeSize, NULL ); return true; } return false; } /** * write_odml */ bool write_odml() { if ( isOpen() ) { DWORD writeSize; LIST odml; odml.dwList = mmioFOURCC('L','I','S','T'); odml.dwSize = sizeof(LIST) + sizeof(AVIEXTHEADER) - 8; odml.dwFourCC = mmioFOURCC('o','d','m','l'); WriteFile( m_file, &odml, sizeof(odml), &writeSize, NULL ); AVIEXTHEADER dmlh = {0}; dmlh.fcc = mmioFOURCC('d','m','l','h'); dmlh.cb = sizeof(dmlh) - 8; dmlh.dwGrandFrames = m_totalFrame; WriteFile( m_file, &dmlh, sizeof(dmlh), &writeSize, NULL ); return true; } return false; } /** * write_movi */ bool write_movi( DWORD size ) { if ( isOpen() ) { LIST movi; movi.dwList = mmioFOURCC('L','I','S','T'); movi.dwSize = size; movi.dwFourCC = mmioFOURCC('m','o','v','i'); DWORD writeSize; WriteFile( m_file, &movi, sizeof(movi), &writeSize, NULL ); return true; } return false; } /** * write_ix00 * ファイルポインタがimageData直後である必要がある */ bool write_ix00() { if ( isOpen() ) { // write std index AVISTDINDEX standardIndex = {0}; standardIndex.fcc = mmioFOURCC('i','x','0','0'); standardIndex.cd = sizeof(standardIndex) + sizeof(AVISTDINDEX_ENTRY)*m_nowRiffFrame - 8; standardIndex.wLongsPerEntry = 2; standardIndex.bIndexSubType = 0; standardIndex.bIndexType = 1; standardIndex.nEntriesInUse = m_nowRiffFrame; standardIndex.dwChunkID = mmioFOURCC('0','0','d','b'); standardIndex.qwBaseOffset = m_dataStartPos.QuadPart; standardIndex.dwReserved = 0; DWORD writeSize; WriteFile( m_file, &standardIndex, sizeof(standardIndex), &writeSize, NULL ); AVISTDINDEX_ENTRY entry = {0}; entry.dwSize = m_imageSize; for (DWORD i=0; i<m_nowRiffFrame; ++i) { entry.dwOffset = (sizeof(CHUNK) + entry.dwSize) * i; WriteFile( m_file, &entry, sizeof(entry), &writeSize, NULL ); } return true; } return false; } /** * write_idx1 */ bool write_idx1() { // 'idx1' if ( isOpen() ) { // write index header CHUNK index; index.dwFourCC = mmioFOURCC('i','d','x','1'); index.dwSize = sizeof(AVIINDEXENTRY) * m_nowRiffFrame; DWORD writeSize; WriteFile( m_file, &index, sizeof(index), &writeSize, NULL ); // write index chunks AVIINDEXENTRY entry = {0}; entry.ckid = mmioFOURCC('0','0','d','b'); entry.dwFlags = AVIIF_KEYFRAME; entry.dwChunkLength = m_imageSize; for (DWORD i=0; i<m_nowRiffFrame; ++i) { entry.dwChunkOffset = (sizeof(LIST) - 8) + (sizeof(CHUNK) + entry.dwChunkLength)*i; WriteFile( m_file, &entry, sizeof(entry), &writeSize, NULL ); } return true; } return false; } /** * writeJUNK * @param size CHUNK分含む */ bool writeJUNK( DWORD size ) { if ( isOpen() && size >= sizeof(CHUNK) ) { // write junk chunk CHUNK junk; junk.dwFourCC = mmioFOURCC('J','U','N','K'); junk.dwSize = size - sizeof(junk); // write junk data std::vector< BYTE > junkData( size ); ZeroMemory( &junkData[0], junkData.size() ); CopyMemory( &junkData[0], &junk, sizeof(junk) ); DWORD writeSize; WriteFile( m_file, &junkData[0], junkData.size(), &writeSize, NULL ); return true; } return false; } /** * writeHeader * dmlh記述のためラストに一括して書き込むことになる */ bool writeHeader() { DWORD writeSize; const DWORD headerJUNKSize = m_aviHeaderJUNKSize + (m_maxNumOfRiff - m_superIndexEntryList.size()) * sizeof(AVISUPERINDEX_ENTRY); // write file header { AVIHeader header = {0}; header.setData( m_width, m_height, m_aviFrame, m_fps, m_superIndexEntryList.size(), headerJUNKSize ); WriteFile( m_file, &header, sizeof(header), &writeSize, NULL ); } // write super index entry { DWORD size = m_superIndexEntryList.size() * sizeof(AVISUPERINDEX_ENTRY); WriteFile( m_file, &m_superIndexEntryList[0], size, &writeSize, NULL ); } // write 'odml' write_odml(); // write header junk writeJUNK( headerJUNKSize ); // write movi write_movi( sizeof(LIST) + sizeof(AVISTDINDEX) + (sizeof(CHUNK) + m_imageSize + sizeof(AVISTDINDEX_ENTRY)) * m_aviFrame - 8 ); return true; } HANDLE m_file; DWORD m_nowRiffFrame; DWORD m_totalFrame; DWORD m_numOfRiff; DWORD m_maxNumOfFrame; DWORD m_maxNumOfRiff; std::vector< AVISUPERINDEX_ENTRY > m_superIndexEntryList; LARGE_INTEGER m_nowRiffFilePos; LARGE_INTEGER m_dataStartPos; DWORD m_imageSize; DWORD m_headerJUNKSize; DWORD m_tailJUNKSize; bool m_isAVIX; DWORD m_width, m_height; DWORD m_fps; DWORD m_aviFrame; //< RIFF-AVI 内のフレーム数 DWORD m_aviHeaderJUNKSize; //< RIFF-AVI 内のimageData前のJUNKサイズ }; struct ObjPoint { float x, y, vx, vy; }; LRESULT CALLBACK wndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { static DIB32 back, image; static ObjPoint point[ 4 ]; static AVIRecorder avi; switch (msg) { case WM_DESTROY: avi.close(); ShowWindow( hWnd, SW_HIDE ); PostQuitMessage(0); break; case WM_CREATE: back.create( WINDOW_WIDTH, WINDOW_HEIGHT ); image.create( TEXT("image.bmp") ); point[0].x = 100; point[0].y = 100; point[1].x = 100; point[1].y = 200; point[2].x = 200; point[2].y = 100; point[3].x = 200; point[3].y = 200; srand( GetTickCount() ); for (int i=0; i<4; ++i) { point[i].vx = (static_cast<float>( std::rand() & 1023 ) / 1023.f) * 32 - 16; point[i].vy = (static_cast<float>( std::rand() & 1023 ) / 1023.f) * 32 - 16; } avi.create( TEXT("output.avi"), back.getWidth(), back.getHeight(), FPS ); 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 ); for (int i=0; i<4; ++i) { point[i].x += point[i].vx; point[i].y += point[i].vy; if ( point[i].x < 0 || point[i].x > WINDOW_WIDTH ) point[i].vx = -point[i].vx; if ( point[i].y < 0 || point[i].y > WINDOW_HEIGHT ) point[i].vy = -point[i].vy; } { DIB32::Point p1, p2, p3, p4; p1.x = point[0].x; p1.y = point[0].y; p1.u = 0; p1.v = 0; p2.x = point[1].x; p2.y = point[1].y; p2.u = 0; p2.v = 32; p3.x = point[2].x; p3.y = point[2].y; p3.u = 32; p3.v = 0; p4.x = point[3].x; p4.y = point[3].y; p4.u = 32; p4.v = 32; image.triangle< DIB32::PutColorAdd >( back, p1, p2, p3 ); image.triangle< DIB32::PutColorAdd >( back, p2, p3, p4 ); InvalidateRect( hWnd, NULL, FALSE ); avi.update( back ); } { TCHAR title[ 128 ]; wsprintf(title, TEXT("frame: %d(%dsec)"), avi.getTotalFrame(),avi.getTotalFrame()/FPS ); 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 ); SetTimer( hWnd, TIMER_ID, 1000/FPS, NULL ); MSG msg; for (;;) { if ( !GetMessage(&msg, NULL, 0, 0) ) break; TranslateMessage( &msg ); DispatchMessage( &msg ); } UnregisterClass( WINDOW_NAME, hInstance ); return msg.wParam; }これでようやくPVが好きに作れる。
ムービーファイルメーカーがavi2.0に対応していないのでなんにせよ一度変換しなければならないが。
■関連記事:
生産がす: 32bitDIB(1) 作成と破棄
生産がす: 32bitDIB(2) 画像読み込み
生産がす: 32bitDIB(3) 塗りつぶし
生産がす: 32bitDIB(4) DIBに描画
生産がす: 32bitDIB(5) ファイルに出力
生産がす: 32bitDIB(6) 拡大縮小描画
生産がす: 32bitDIB(7) DIBSection
生産がす: DIB(8) - 直線描画
生産がす: DIB(9) - 回転描画
生産がす: DIB(10) - 三角形描画
生産がす: 日記ちゃん半透明合成
生産がす: 32bitDIBから無圧縮AVI2.0
4 件のコメント:
c#でavi2.0作成システムを作っている者です。
今回の記事、とても参考になりました、ありがとうございます。
なんとかavi2.0の作成、再生までは出来たのですが
RIFF-AVIXの部分でWindowsMediaPlayerが再生中に問題が発生といったエラーが出てしまいます。
SUPERINDEX周辺に問題があるのか調べていますが今のところ原因が分かっていません。
どうかお力添えを頂ければと思います、宜しくお願いします。
コメントありがとうございます。
自分の環境でもWindowsMediaPlayer(ver12.0.10011.16384)で再生し、しばらくするとエラーがでました。
MPC-HC.1.7.10.x64では特にエラーがでず最後まで再生できました。
WindowsMediaPlayerがAVI2.0に対応していないためか(未確認)と思っていたのと、
適当なエンコーダでwmv等に変換して再生するとかねがね問題なく再生できたので、とりあえずよしとしていました。
さらに別の問題があり、
ファイル出力後、ふたたび新規に録画処理を行うと以前のデータが混じることがある問題も発生しています。
体感では初期化処理にも問題がありそうです。
出力したファイルのチェックに
RIFFPad(http://www.menasoft.com/blog/?p=34)
をよく使用してました。
迅速なお返事と情報共有ありがとうございます。
適当なavi2.0のファイルが再生できたのでwmpは一応対応はされていると思います。
原因がまだわからず調査中です、また何か分かり次第ご連絡します。
RIFFPad試してみましたが凄く見やすくて良いですね、ありがとうございます。
コメントを投稿