とりあえず1TBまで対応。
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