AVI example code for creating AVI files
(c) 2002 Lucian Wischik. This code is free, and anyone can do with it whatever they like, including incorporating it in commercial products.
This code is concerned with creating AVI files, including video and audio. The video can be compressed using whichever filters are installed on your computer - you can make it pop up a system dialog box asking the user which filter to use, or you can remember this information to avoid the dialog box in future, or you can assume no compression and avoid the dialog box totally. As for compressing the audio, I believe that this is impossible using the AVI functions. Instead, you'll have to create an AVI with uncompressed audio, and then compress it using some external utility like Windows Movie Maker.
The code consists of seven functions, written in (classless) C++ and using Video for Windows (which is built into all versions of windows). I use this code as a unit (avi_utils.cpp, avi_utils.h) added to my own project. This web page contains examples of how to use the functions to create AVI files, the header file and the source code. You should copy the code and paste it into your own project. The code is plain Win32, and compiles cleanly under Borland C++Builder5 and Visual Studio .NET, and should work on other compilers without problems.
Borland compilers normally allow FPU exceptions. This can cause a problem, because some filters (e.g. DivX) raise FPU exceptions. Therefore, Borland users should use this code at some point before creating the AVI -- I do it at the start of WinMain. (This is not necessary for Visual C++, since that disables the exceptions by default).
#include <float.h> _control87(MCW_EM, MCW_EM); // reprogram the FPU so it doesn't raise exceptions
Examples of how to create AVIs
To create an AVI from a bunch of files. The following code creates an AVI out of five bitmaps (1.bmp, 2.bmp, ... 5.bmp) and a WAV file (wav.wav).
const char *fns[] = {"1.bmp","2.bmp","3.bmp","4.bmp","5.bmp"};
HAVI avi = CreateAvi("test.avi",1000,NULL); // 1000ms is the period between frames
for (int i=0; i<sizeof(fns)/sizeof(fns[0]); i++)
{ HBITMAP hbm=(HBITMAP)LoadImage(NULL,fns[i],IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
AddAviFrame(avi,hbm);
DeleteObject(hbm);
}
AddAviWav(avi,"wav.wav",SND_FILENAME);
CloseAvi(avi);
To create an AVI with DivX compression. This code creates the same AVI, with DivX compression for the video, and without the audio. The code actually just suggests DivX and pops up a dialog to let the user control more details (hwnd is some window to be used as the parent for that dialog). You might keep a copy of the opts structure so as to give the same defaults to the user next time. Or you can pass false to the SetAviVideoCompression function to suppress the user-interface dialog, if the opts structure is already filled out correctly.
const char *fns[] = {"1.bmp","2.bmp","3.bmp","4.bmp","5.bmp"};
HAVI avi = CreateAvi("test.avi",1000,NULL);
for (int i=0; i<sizeof(fns)/sizeof(fns[0]); i++)
{ HBITMAP hbm=(HBITMAP)LoadImage(NULL,fns[i],IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
if (i==0) // Set up compression just before the first frame
{ AVICOMPRESSOPTIONS opts; ZeroMemory(&opts,sizeof(opts));
opts.fccHandler=mmioFOURCC('d','i','v','x');
SetAviVideoCompression(avi,hbm,&opts,true,hwnd);
}
AddAviFrame(avi,hbm);
DeleteObject(hbm);
}
CloseAvi(avi);
To create an AVI from dynamically-drawn images. This code shows how to draw your own animations for the AVI file, rather than just relying on existing bitmap files. Note: you must use DIBSections instead of normal device-dependent bitmaps.
HDC hdcscreen=GetDC(0), hdc=CreateCompatibleDC(hdcscreen); ReleaseDC(0,hdcscreen); BITMAPINFO bi; ZeroMemory(&bi,sizeof(bi)); BITMAPINFOHEADER &bih = bi.bmiHeader; bih.biSize=sizeof(bih); bih.biWidth=200; bih.biHeight=200; bih.biPlanes=1; bih.biBitCount=24; bih.biCompression=BI_RGB; bih.biSizeImage = ((bih.biWidth*bih.biBitCount/8+3)&0xFFFFFFFC)*bih.biHeight; bih.biXPelsPerMeter=10000; bih.biYPelsPerMeter=10000; bih.biClrUsed=0; bih.biClrImportant=0; void *bits; HBITMAP hbm=CreateDIBSection(hdc,(BITMAPINFO*)&bih,DIB_RGB_COLORS,&bits,NULL,NULL); // HGDIOBJ holdb=SelectObject(hdc,hbm); HPEN hp = CreatePen(PS_SOLID,16,RGB(255,255,128)); HGDIOBJ holdp=SelectObject(hdc,hp); // HAVI avi = CreateAvi("test.avi",100,NULL); for (int frame=0; frame<50; frame++) { // static background DWORD seed=GetTickCount(); DWORD *dbits=(DWORD*)bits; for (unsigned int i=0; i<bih.biSizeImage/sizeof(DWORD); i++) {dbits[i]=seed; seed+=79;} // a line moving MoveToEx(hdc,0,0,NULL); LineTo(hdc,frame*3,100); AddAviFrame(avi,hbm); } CloseAvi(avi); // SelectObject(hdc,holdb); SelectObject(hdc,holdp); DeleteDC(hdc); DeleteObject(hbm); DeleteObject(hp);
Header-file 'avi_utils.h'
#ifndef _avi_utils_H #define _avi_utils_H // AVI utilities -- for creating avi files // (c) 2002 Lucian Wischik. No restrictions on use. DECLARE_HANDLE(HAVI); // An HAVI identifies an avi file that is being created HAVI CreateAvi(const char *fn, int frameperiod, const WAVEFORMATEX *wfx); // CreateAvi - call this to start the creation of the avi file. // The period is the number of ms between each bitmap frame. // The waveformat can be null if you're not going to add any audio, // or if you're going to add audio from a file. HRESULT AddAviFrame(HAVI avi, HBITMAP hbm); HRESULT AddAviAudio(HAVI avi, void *dat, unsigned long numbytes); // AddAviFrame - adds this bitmap to the avi file. hbm must point be a DIBSection. // It is the callers responsibility to free the hbm. // AddAviAudio - adds this junk of audio. The format of audio was as specified in the // wfx parameter to CreateAVI. This fails if NULL was given. // Both return S_OK if okay, otherwise one of the AVI errors. HRESULT AddAviWav(HAVI avi, const char *wav, DWORD flags); // AddAviWav - a convenient way to add an entire wave file to the avi. // The wav file may be in in memory (in which case flags=SND_MEMORY) // or a file on disk (in which case flags=SND_FILENAME). // This function requires that either a null WAVEFORMATEX was passed to CreateAvi, // or that the wave file now being added has the same format as was // added earlier. HRESULT SetAviVideoCompression(HAVI avi, HBITMAP hbm, AVICOMPRESSOPTIONS *opts, bool ShowDialog, HWND hparent); // SetAviVideoCompression - allows compression of the video. If compression is desired, // then this function must have been called before any bitmap frames had been added. // The bitmap hbm must be a DIBSection (so that avi knows what format/size you're giving it), // but won't actually be added to the movie. // This function can display a dialog box to let the user choose compression. In this case, // set ShowDialog to true and specify the parent window. If opts is non-NULL and its // dwFlags property includes AVICOMPRESSF_VALID, then opts will be used to give initial // values to the dialog. If opts is non-NULL then the chosen options will be placed in it. // This function can also be used to choose a compression without a dialog box. In this // case, set ShowDialog to false, and hparent is ignored, and the compression specified // in 'opts' is used, and there's no need to call GotAviVideoCompression afterwards. HRESULT CloseAvi(HAVI avi); // CloseAvi - the avi must be closed with this message. unsigned int FormatAviMessage(HRESULT code, char *buf,unsigned int len); // FormatAviMessage - given an error code, formats it as a string. // It returns the length of the error message. If buf/len points // to a real buffer, then it also writes as much as possible into there. #endif
Source-code 'avi_utils.cpp'
#define STRICT #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <vfw.h> #include "avi_utils.h" // First, we'll define the WAV file format. #include <pshpack1.h> typedef struct { char id[4]; //="fmt " unsigned long size; //=16 short wFormatTag; //=WAVE_FORMAT_PCM=1 unsigned short wChannels; //=1 or 2 for mono or stereo unsigned long dwSamplesPerSec; //=11025 or 22050 or 44100 unsigned long dwAvgBytesPerSec; //=wBlockAlign * dwSamplesPerSec unsigned short wBlockAlign; //=wChannels * (wBitsPerSample==8?1:2) unsigned short wBitsPerSample; //=8 or 16, for bits per sample } FmtChunk; typedef struct { char id[4]; //="data" unsigned long size; //=datsize, size of the following array unsigned char data[1]; //=the raw data goes here } DataChunk; typedef struct { char id[4]; //="RIFF" unsigned long size; //=datsize+8+16+4 char type[4]; //="WAVE" FmtChunk fmt; DataChunk dat; } WavChunk; #include <poppack.h> // This is the internal structure represented by the HAVI handle: typedef struct { IAVIFile *pfile; // created by CreateAvi WAVEFORMATEX wfx; // as given to CreateAvi (.nChanels=0 if none was given). Used when audio stream is first created. int period; // specified in CreateAvi, used when the video stream is first created IAVIStream *as; // audio stream, initialised when audio stream is first created IAVIStream *ps, *psCompressed; // video stream, when first created unsigned long nframe, nsamp; // which frame will be added next, which sample will be added next bool iserr; // if true, then no function will do anything } TAviUtil; HAVI CreateAvi(const char *fn, int frameperiod, const WAVEFORMATEX *wfx) { IAVIFile *pfile; AVIFileInit(); HRESULT hr = AVIFileOpen(&pfile, fn, OF_WRITE|OF_CREATE, NULL); if (hr!=AVIERR_OK) {AVIFileExit(); return NULL;} TAviUtil *au = new TAviUtil; au->pfile = pfile; if (wfx==NULL) ZeroMemory(&au->wfx,sizeof(WAVEFORMATEX)); else CopyMemory(&au->wfx,wfx,sizeof(WAVEFORMATEX)); au->period = frameperiod; au->as=0; au->ps=0; au->psCompressed=0; au->nframe=0; au->nsamp=0; au->iserr=false; return (HAVI)au; } HRESULT CloseAvi(HAVI avi) { if (avi==NULL) return AVIERR_BADHANDLE; TAviUtil *au = (TAviUtil*)avi; if (au->as!=0) AVIStreamRelease(au->as); au->as=0; if (au->psCompressed!=0) AVIStreamRelease(au->psCompressed); au->psCompressed=0; if (au->ps!=0) AVIStreamRelease(au->ps); au->ps=0; if (au->pfile!=0) AVIFileRelease(au->pfile); au->pfile=0; AVIFileExit(); delete au; return S_OK; } HRESULT SetAviVideoCompression(HAVI avi, HBITMAP hbm, AVICOMPRESSOPTIONS *opts, bool ShowDialog, HWND hparent) { if (avi==NULL) return AVIERR_BADHANDLE; if (hbm==NULL) return AVIERR_BADPARAM; DIBSECTION dibs; int sbm = GetObject(hbm,sizeof(dibs),&dibs); if (sbm!=sizeof(DIBSECTION)) return AVIERR_BADPARAM; TAviUtil *au = (TAviUtil*)avi; if (au->iserr) return AVIERR_ERROR; if (au->psCompressed!=0) return AVIERR_COMPRESSOR; // if (au->ps==0) // create the stream, if it wasn't there before { AVISTREAMINFO strhdr; ZeroMemory(&strhdr,sizeof(strhdr)); strhdr.fccType = streamtypeVIDEO;// stream type strhdr.fccHandler = 0; strhdr.dwScale = au->period; strhdr.dwRate = 1000; strhdr.dwSuggestedBufferSize = dibs.dsBmih.biSizeImage; SetRect(&strhdr.rcFrame, 0, 0, dibs.dsBmih.biWidth, dibs.dsBmih.biHeight); HRESULT hr=AVIFileCreateStream(au->pfile, &au->ps, &strhdr); if (hr!=AVIERR_OK) {au->iserr=true; return hr;} } // if (au->psCompressed==0) // set the compression, prompting dialog if necessary { AVICOMPRESSOPTIONS myopts; ZeroMemory(&myopts,sizeof(myopts)); AVICOMPRESSOPTIONS *aopts[1]; if (opts!=NULL) aopts[0]=opts; else aopts[0]=&myopts; if (ShowDialog) { BOOL res = (BOOL)AVISaveOptions(hparent,0,1,&au->ps,aopts); if (!res) {AVISaveOptionsFree(1,aopts); au->iserr=true; return AVIERR_USERABORT;} } HRESULT hr = AVIMakeCompressedStream(&au->psCompressed, au->ps, aopts[0], NULL); AVISaveOptionsFree(1,aopts); if (hr != AVIERR_OK) {au->iserr=true; return hr;} DIBSECTION dibs; GetObject(hbm,sizeof(dibs),&dibs); hr = AVIStreamSetFormat(au->psCompressed, 0, &dibs.dsBmih, dibs.dsBmih.biSize+dibs.dsBmih.biClrUsed*sizeof(RGBQUAD)); if (hr!=AVIERR_OK) {au->iserr=true; return hr;} } // return AVIERR_OK; } HRESULT AddAviFrame(HAVI avi, HBITMAP hbm) { if (avi==NULL) return AVIERR_BADHANDLE; if (hbm==NULL) return AVIERR_BADPARAM; DIBSECTION dibs; int sbm = GetObject(hbm,sizeof(dibs),&dibs); if (sbm!=sizeof(DIBSECTION)) return AVIERR_BADPARAM; TAviUtil *au = (TAviUtil*)avi; if (au->iserr) return AVIERR_ERROR; // if (au->ps==0) // create the stream, if it wasn't there before { AVISTREAMINFO strhdr; ZeroMemory(&strhdr,sizeof(strhdr)); strhdr.fccType = streamtypeVIDEO;// stream type strhdr.fccHandler = 0; strhdr.dwScale = au->period; strhdr.dwRate = 1000; strhdr.dwSuggestedBufferSize = dibs.dsBmih.biSizeImage; SetRect(&strhdr.rcFrame, 0, 0, dibs.dsBmih.biWidth, dibs.dsBmih.biHeight); HRESULT hr=AVIFileCreateStream(au->pfile, &au->ps, &strhdr); if (hr!=AVIERR_OK) {au->iserr=true; return hr;} } // // create an empty compression, if the user hasn't set any if (au->psCompressed==0) { AVICOMPRESSOPTIONS opts; ZeroMemory(&opts,sizeof(opts)); opts.fccHandler=mmioFOURCC('D','I','B',' '); HRESULT hr = AVIMakeCompressedStream(&au->psCompressed, au->ps, &opts, NULL); if (hr != AVIERR_OK) {au->iserr=true; return hr;} hr = AVIStreamSetFormat(au->psCompressed, 0, &dibs.dsBmih, dibs.dsBmih.biSize+dibs.dsBmih.biClrUsed*sizeof(RGBQUAD)); if (hr!=AVIERR_OK) {au->iserr=true; return hr;} } // //Now we can add the frame HRESULT hr = AVIStreamWrite(au->psCompressed, au->nframe, 1, dibs.dsBm.bmBits, dibs.dsBmih.biSizeImage, AVIIF_KEYFRAME, NULL, NULL); if (hr!=AVIERR_OK) {au->iserr=true; return hr;} au->nframe++; return S_OK; } HRESULT AddAviAudio(HAVI avi, void *dat, unsigned long numbytes) { if (avi==NULL) return AVIERR_BADHANDLE; if (dat==NULL || numbytes==0) return AVIERR_BADPARAM; TAviUtil *au = (TAviUtil*)avi; if (au->iserr) return AVIERR_ERROR; if (au->wfx.nChannels==0) return AVIERR_BADFORMAT; unsigned long numsamps = numbytes*8 / au->wfx.wBitsPerSample; if ((numsamps*au->wfx.wBitsPerSample/8)!=numbytes) return AVIERR_BADPARAM; // if (au->as==0) // create the stream if necessary { AVISTREAMINFO ahdr; ZeroMemory(&ahdr,sizeof(ahdr)); ahdr.fccType=streamtypeAUDIO; ahdr.dwScale=au->wfx.nBlockAlign; ahdr.dwRate=au->wfx.nSamplesPerSec*au->wfx.nBlockAlign; ahdr.dwSampleSize=au->wfx.nBlockAlign; ahdr.dwQuality=(DWORD)-1; HRESULT hr = AVIFileCreateStream(au->pfile, &au->as, &ahdr); if (hr!=AVIERR_OK) {au->iserr=true; return hr;} hr = AVIStreamSetFormat(au->as,0,&au->wfx,sizeof(WAVEFORMATEX)); if (hr!=AVIERR_OK) {au->iserr=true; return hr;} } // // now we can write the data HRESULT hr = AVIStreamWrite(au->as,au->nsamp,numsamps,dat,numbytes,0,NULL,NULL); if (hr!=AVIERR_OK) {au->iserr=true; return hr;} au->nsamp+=numsamps; return S_OK; } HRESULT AddAviWav(HAVI avi, const char *src, DWORD flags) { if (avi==NULL) return AVIERR_BADHANDLE; if (flags!=SND_MEMORY && flags!=SND_FILENAME) return AVIERR_BADFLAGS; if (src==0) return AVIERR_BADPARAM; TAviUtil *au = (TAviUtil*)avi; if (au->iserr) return AVIERR_ERROR; // char *buf=0; WavChunk *wav = (WavChunk*)src; if (flags==SND_FILENAME) { HANDLE hf=CreateFile(src,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,0,NULL); if (hf==INVALID_HANDLE_VALUE) {au->iserr=true; return AVIERR_FILEOPEN;} DWORD size = GetFileSize(hf,NULL); buf = new char[size]; DWORD red; ReadFile(hf,buf,size,&red,NULL); CloseHandle(hf); wav = (WavChunk*)buf; } // // check that format doesn't clash bool badformat=false; if (au->wfx.nChannels==0) { au->wfx.wFormatTag=wav->fmt.wFormatTag; au->wfx.cbSize=0; au->wfx.nAvgBytesPerSec=wav->fmt.dwAvgBytesPerSec; au->wfx.nBlockAlign=wav->fmt.wBlockAlign; au->wfx.nChannels=wav->fmt.wChannels; au->wfx.nSamplesPerSec=wav->fmt.dwSamplesPerSec; au->wfx.wBitsPerSample=wav->fmt.wBitsPerSample; } else { if (au->wfx.wFormatTag!=wav->fmt.wFormatTag) badformat=true; if (au->wfx.nAvgBytesPerSec!=wav->fmt.dwAvgBytesPerSec) badformat=true; if (au->wfx.nBlockAlign!=wav->fmt.wBlockAlign) badformat=true; if (au->wfx.nChannels!=wav->fmt.wChannels) badformat=true; if (au->wfx.nSamplesPerSec!=wav->fmt.dwSamplesPerSec) badformat=true; if (au->wfx.wBitsPerSample!=wav->fmt.wBitsPerSample) badformat=true; } if (badformat) {if (buf!=0) delete[] buf; return AVIERR_BADFORMAT;} // if (au->as==0) // create the stream if necessary { AVISTREAMINFO ahdr; ZeroMemory(&ahdr,sizeof(ahdr)); ahdr.fccType=streamtypeAUDIO; ahdr.dwScale=au->wfx.nBlockAlign; ahdr.dwRate=au->wfx.nSamplesPerSec*au->wfx.nBlockAlign; ahdr.dwSampleSize=au->wfx.nBlockAlign; ahdr.dwQuality=(DWORD)-1; HRESULT hr = AVIFileCreateStream(au->pfile, &au->as, &ahdr); if (hr!=AVIERR_OK) {if (buf!=0) delete[] buf; au->iserr=true; return hr;} hr = AVIStreamSetFormat(au->as,0,&au->wfx,sizeof(WAVEFORMATEX)); if (hr!=AVIERR_OK) {if (buf!=0) delete[] buf; au->iserr=true; return hr;} } // // now we can write the data unsigned long numbytes = wav->dat.size; unsigned long numsamps = numbytes*8 / au->wfx.wBitsPerSample; HRESULT hr = AVIStreamWrite(au->as,au->nsamp,numsamps,wav->dat.data,numbytes,0,NULL,NULL); if (buf!=0) delete[] buf; if (hr!=AVIERR_OK) {au->iserr=true; return hr;} au->nsamp+=numsamps; return S_OK; } unsigned int FormatAviMessage(HRESULT code, char *buf,unsigned int len) { const char *msg="unknown avi result code"; switch (code) { case S_OK: msg="Success"; break; case AVIERR_BADFORMAT: msg="AVIERR_BADFORMAT: corrupt file or unrecognized format"; break; case AVIERR_MEMORY: msg="AVIERR_MEMORY: insufficient memory"; break; case AVIERR_FILEREAD: msg="AVIERR_FILEREAD: disk error while reading file"; break; case AVIERR_FILEOPEN: msg="AVIERR_FILEOPEN: disk error while opening file"; break; case REGDB_E_CLASSNOTREG: msg="REGDB_E_CLASSNOTREG: file type not recognised"; break; case AVIERR_READONLY: msg="AVIERR_READONLY: file is read-only"; break; case AVIERR_NOCOMPRESSOR: msg="AVIERR_NOCOMPRESSOR: a suitable compressor could not be found"; break; case AVIERR_UNSUPPORTED: msg="AVIERR_UNSUPPORTED: compression is not supported for this type of data"; break; case AVIERR_INTERNAL: msg="AVIERR_INTERNAL: internal error"; break; case AVIERR_BADFLAGS: msg="AVIERR_BADFLAGS"; break; case AVIERR_BADPARAM: msg="AVIERR_BADPARAM"; break; case AVIERR_BADSIZE: msg="AVIERR_BADSIZE"; break; case AVIERR_BADHANDLE: msg="AVIERR_BADHANDLE"; break; case AVIERR_FILEWRITE: msg="AVIERR_FILEWRITE: disk error while writing file"; break; case AVIERR_COMPRESSOR: msg="AVIERR_COMPRESSOR"; break; case AVIERR_NODATA: msg="AVIERR_READONLY"; break; case AVIERR_BUFFERTOOSMALL: msg="AVIERR_BUFFERTOOSMALL"; break; case AVIERR_CANTCOMPRESS: msg="AVIERR_CANTCOMPRESS"; break; case AVIERR_USERABORT: msg="AVIERR_USERABORT"; break; case AVIERR_ERROR: msg="AVIERR_ERROR"; break; } unsigned int mlen=(unsigned int)strlen(msg); if (buf==0 || len==0) return mlen; unsigned int n=mlen; if (n+1>len) n=len-1; strncpy(buf,msg,n); buf[n]=0; return mlen; }
Keywords
The code on this page demonstrates the following functions and data structures:
WAV file format, RIFF file, Chunk, WAVE_FORMAT_PCM, IAVIFile, PAVIFile, WAVEFORMAT, WAVEFORMATEX, IAVIStream, PAVIStream, AVIFileInit, AVIFileOpen, AVIFileExit, AVIStreamRelease, AVIStreamClose, AVIFileRelease, DIBSECTION, GetObject, device independent bitmap, AVISTREAMINFO, streamtypeVIDEO, streamtypeAUDIO, AVIFileCreateStream, AVICOMPRESSOPTIONS, AVIMakeCompressedStream, AVIStreamSetFormat, mmioFOURCC, AVIStreamWrite.