Windowsの普及でAVIファイルは標準的なフォーマットになりました。ビデオキャプチャーボードも普及してきていて、科学的実験や解析に動画を用いたいという要求は決して少なくはないと思います。そしてそれらの結果をAVIファイルで示したいという要求もあると思います。
ここでは、WindowsでVideo for Windows(以下VFW)のAPIなどを用いてAVIファイルを扱う方法を説明します。
この文書およびサンプルは無償で利用できます。この文書およびサンプルによって生じたいかなる問題も岩本一樹は保証しません。またこの文書およびサンプルの内容も保証しません。
営利/非営利を問わず配布できます。改変して配布することもできます。
サンプルを改変したり別のプログラムに流用するなど、営利/非営利を問わず利用できます。
この文書およびサンプルプログラムなどの著作権は岩本一樹に属します。
Eメール | iwm@maid.org |
Webサイト | http://www.maid.org/ |
電話 | 090-9224-6801 |
AVIファイルは1992年にMicrosoftがWindows3.1のデジタルビデオ用に発表したVideo for Windowsで使われているフォーマットです。今日ではVideo for WindowsはWindowsの標準的な機能の1つとなり、デジタルビデオに留まらずアニメーションコントロールにもGIFアニメーションのような用途で使われています。
AVIとはAudio Video Interleaveの略です。AVIファイルはその名の通りオーディオ(音声)とビデオ(映像)を併せ持つことができるファイル形式です。
その形式はAVIファイルはRIFF(Resource Interchange File Format)になっています。RIFFは様々なリソース(データ)を1つのファイルにするためのファイルの形式で、将来、新しい形式のリソースが考案されても互換が保たれる構造になっています。実はWAVEファイルもRIFFになっており、VFWのAPIを用いればWAVEファイルをビデオが無いオーディオのみのAVIファイルとして扱うことができます。
AVIファイルはストリームという単位に別れています。LZHなどのアーカイブされた1つのファイルの中にいくつかのファイルが存在するように、AVIファイルの中にはストリームが1つ以上存在します。現在、ストリームは下記の4種類が定義されています。
また理論上は同じ種類のストリームを複数含めることができます。複数のオーディオストリームを用いて複数の言語で再生したり、複数のビデオストリームを用いて違う視点(カメラ)から撮影した映像を同時に再生するなどが考えられます。しかし現在、ほとんどのファイルは下記の構成です。
オーディオストリームの実体はWAVEファイルそのもので、ビデオストリームは連続するビットマップの集まりです。ご存知かと思いますが、映像は連続する画像の集まりです。(教科書のすみに描いたパラパラ漫画を思い出して下さい。それと同じ原理です。)AVIファイルではその画像がWindowsプログラマが慣れ親しんだビットマップになっているのです。
このビットマップは実際には圧縮されている場合がほとんどです。「圧縮」と言ってもビットマップファイルでおなじみのRLE8やRLE4だけではありません。CinepakやIndeoなどのAVIファイル専用のフォーマットで圧縮されています。このAVIに含まれるビットマップの圧縮/展開を行うドライバは追加/削除が可能になっており、新しい圧縮が開発された場合にはドライバを追加するだけで対応できるようになります。
VFWのAPIを使う限りでは圧縮形式を気にする必要はありません。API側で適切な圧縮/展開ドライバを自動的に呼び出します。
AVIファイルを扱う方法はVFW以外にもあります。最も高レベルなものから順に列挙すると
があります。MCIはもうお馴染みのだと思いますが、一応簡単に説明します。MCIはMedia Control Interfaceの略で、AVIファイル、WAVEファイル、MIDIファイル、CDなどのメディアを「再生」、「停止」などというような抽象的な概念で扱うことができます。ゆえにMCIを利用すればAVIファイルの再生は容易です。MCIWNDはウインドウの作成、管理まで行うことができるます。これによりアプリケーションはMCIよりもさらに容易にAVIファイルを再生できます。
反面、MCIはAVIファイルからビットマップを取り出す、ビットマップファイルからAVIファイルを作る、AVIファイルを再圧縮する、AVIファイルを編集するなどの目的には不向きです。
ACM(Audio Compression Manager)とVCM(Video Compression Manager)はそれぞれオーディオ/ビデオの圧縮/展開を行うドライバを直接扱うことができます。これらをアプリケーションから呼び出す場合、オーディオやビデオのデータはRIFFのファイルである必要もなく、かなり柔軟にデータを扱えます。しかしこの方法ではファイルの読み込み/書き出し、メモリの確保などはアプリケーションの責任です。その手続きの多さはMCIの比ではありません。
mmioはRIFFのファイルへのアクセスを助けます。ACMやVCMを利用する場合にはmmioを使うことになると思います。
そして最も低レベルな手段は汎用的なファイル入出力を用いる方法です。ANSI-Cで定義されたfopenなどを使えばOSをWindowsに限定しないプログラムが組めます。しかしこの方法ではWindows用に供給される圧縮/展開のドライバが使えません。今後、新しい圧縮形式が発表されないなどということはありません。それらに独自で対応するのは事実上不可能です。この場合は結局、未圧縮のAVIファイルに限定するなどということになると思いますが、それでもかなり困難な手段と言えます。
VFWのAVI APIはACM、VCM、mmioを制御し、AVIファイルがRIFFであることを意識することなく扱えるようになり、圧縮/展開のドライバ選択などを容易にします。しかしVFWのAVI APIにはAVIファイルを再生する機能はありません。VFWのAVI APIはACMとVCM、mmioよりも上でMCI下に位置していると考えられます。
なお、ここでいう高レベル/低レベル、上/下などという言い方は優劣を言うのではありません。高レベルなものは低レベルなものを利用しているということです。高レベルな方法で十分ならば、高レベルな方法を選択する方が賢明です。しかし高レベルな方法で実現できない場合というのもあるので、その時には低レベルな方法を使うべきなのです。
AVIファイルからビットマップを取り出すプログラムはTEST01のようになります。
TEST01.C
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <vfw.h>
BOOL SaveBitmap(LPCSTR lpszFile,LPBITMAPINFOHEADER pbmih)
{
BITMAPFILEHEADER bmfh;
FILE *fp;
bmfh.bfType=0x4d42;
bmfh.bfReserved1=bmfh.bfReserved2=0;
if (pbmih->biClrUsed==0)
switch (pbmih->biBitCount) {
case 1:bmfh.bfOffBits=sizeof(RGBQUAD)*2;break;
case 4:bmfh.bfOffBits=sizeof(RGBQUAD)*16;break;
case 8:bmfh.bfOffBits=sizeof(RGBQUAD)*256;break;
case 24:bmfh.bfOffBits=0;break;
case 16:
case 32:
bmfh.bfOffBits=pbmih->biCompression==BI_RGB?0:sizeof(DWORD)*3;
}
else
bmfh.bfOffBits=pbmih->biClrUsed*sizeof(RGBQUAD);
bmfh.bfOffBits+=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
bmfh.bfSize=bmfh.bfOffBits+(pbmih->biSizeImage==0
?((pbmih->biWidth*pbmih->biBitCount+31)&~31)/8*abs(pbmih->biHeight)
:pbmih->biSizeImage);
if ((fp=fopen(lpszFile,"wb"))==NULL)
return FALSE;
if (fwrite(&bmfh,sizeof(BITMAPFILEHEADER),1,fp)!=1
|| fwrite(pbmih,bmfh.bfSize-sizeof(BITMAPFILEHEADER),1,fp)!=1)
return FALSE;
return fclose(fp)==0;
}
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
LPBITMAPINFOHEADER pbmih;
PAVIFILE pavi;
PAVISTREAM pstm;
PGETFRAME pfrm;
AVIFileInit();
if (AVIFileOpen(&pavi,_T("TEST.AVI"),OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (AVIFileGetStream(pavi,&pstm,0,0)!=0)
return 0;
if ((pfrm=AVIStreamGetFrameOpen(pstm,NULL))==NULL)
return 0;
if ((pbmih=AVIStreamGetFrame(pfrm,0))==NULL)
return 0;
if (SaveBitmap("TEST00.BMP",pbmih)!=0)
return 0;
if (AVIStreamGetFrameClose(pfrm)!=0)
return 0;
AVIStreamRelease(pstm);
AVIFileRelease(pavi);
AVIFileExit();
return 0;
}
AVIFileInitはVFWのライブラリを初期化します。VFWの機能を利用するアプリケーションは起動された時に必ずこのAPIを1回呼び出します。またこのAPIを呼び出したアプリケーションは必ず終了する時にAVIFileExitを呼ばなければなりません。
さらに今度はAVIFileOpenでファイル名を指定してAVIファイルを開きます。PAVIFILEはfopenのFILE構造体のようなものです。今後はAVIファイルへのアクセスはこれを用います。szFileはファイル名を示す'\0'で終わる文字列へのポインタです。pclsidhandlerにはNULLを指定します。NULLを指定するとレジストリに登録されている情報を元にファイルを開きます。なお、オープンしたファイルはクローズしなければなりません。ファイルを閉じるにはAVIFileReleaseを呼び出します。引数はAVIFileOpenで取得したファイルインターフェイスポインタだけです。
しかしファイルを開いただけではAVIファイルに含まれるビットマップを取り出すことはできません。ファイルの中にあるストリームを開かなくてはならないのです。ストリームを開くためにはAVIFileGetStreamを用います。ファイルと同様に開いたストリームは閉じなくてはなりません。ストリームを閉じるAPIはAVIStreamReleaseです。
AVIファイルに含まれるビットマップは圧縮されています。このビットマップを適切な展開ドライバで無圧縮のビットマップに展開するためにはAVIStreamGetFrameOpenでGetFrameオブジェクトを取得しなければなりません。このAPIの引数はストリームインターフェイスポインタとBITMAPINFOHEADER構造体へのポインタです。AVIファイルがどの様な形式で圧縮されているかをVFWを利用するプログラマが心配する必要はありません。VFW側で適切な展開ドライバが選択されます。BITMAPINFOHEADERへのポインタはNULLまたはAVIGETFRAMEF_BESTDISPLAYFMTを指定します。APIの仕様上はBITMAPINFOHEADER構造体を指定して目的のサイズ、解像度のビットマップが取得できるように思えます。しかし実際はBITMAPINFOHEADER構造体を指定してもNULLが返されこのAPIは失敗します。指定されたサイズ、解像度のビットマップを取得できる展開ドライバが存在するかも知れませんが、現時点ではそのような親切な機能は実装されていないドライバが大半です。ですからビットマップのサイズや解像度の変更は別に作成する必要があります。
これでようやくGetFrameオブジェクトを利用してビットマップを取得します。ビットマップを取得するAPIはAVIStreamGetFrameです。パックDIBへのポインタが返されます。AVIStreamGetFrameOpenでBITMAPINFOHEADER構造体を指定しGetFrameオブジェクトを取得しない限り、このAPIで取得できるビットマップのフォーマットはあらゆる場合が考えられます。極端な話、1×1のモノクロのビットマップが返される可能性もありますし、1024×768で32ビットという可能性もあります。しかし現実には解像度は24ビット(フルカラー)の場合がほとんどです。
もちろんGetFrameオブジェクトも閉じる必要があります。GetFrameオブジェクトを閉じるAPIはAVIStreamGetFrameCloseです。
AVIファイルからビットマップを取り出すためにはこの幾重にも重なるの開く/閉じるを理解しなければなりません。ファイルの中にストリームがあるということを考えればこの様な構造は必然ですが、最初は戸惑うかも知れませんが慣れるしかありません。
さて先に述べたようにAVIファイルには複数のストリームを含めることができます。現在は実際には存在しませんがビデオストリームが2つ以上含まれるAVIファイルもありえます。これに対応するためにはAVIFileGetStreamのfccTypeとlParamを利用します。
fccTypeにはストリームの種類を、lParamはストリームタイプのカウントです。この値は0から始まります。「ではlParamの最大値は?」という疑問がすぐに湧くと思います。0から始めてエラーになるまでAVIFileGetStreamを呼び出す方法もありますが、もっとスマートな方法があります。それはAVIFileInfoを呼び出してAVIFILEINFO構造体にファイルの情報を取得する方法です。AVIFILEINFO構造体のdwStreamsはファイルに含まれるストリーム数です。これを利用するとTEST02のようになります。
TEST02.C
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
AVIFILEINFO fi;
AVISTREAMINFO si;
CHAR szFile[MAX_PATH];
DWORD dwStream;
LONG i,lStart,lEnd;
LPBITMAPINFOHEADER pbmih;
PAVIFILE pavi;
PAVISTREAM pstm=NULL,ptmp;
PGETFRAME pfrm;
WORD wVideo=~0;
AVIFileInit();
if (AVIFileOpen(&pavi,_T("TEST.AVI"),OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (AVIFileInfo(pavi,&fi,sizeof(AVIFILEINFO))!=0)
return 0;
for (dwStream=0;dwStream<fi.dwStreams;dwStream++) {
if (AVIFileGetStream(pavi,&ptmp,0,dwStream)!=0)
return 0;
if (AVIStreamInfo(ptmp,&si,sizeof(AVISTREAMINFO))!=0)
return 0;
switch (si.fccType) {
case streamtypeVIDEO:
if (pstm==NULL || si.wPriority<wVideo) {
if (pstm!=NULL)
AVIStreamRelease(pstm);
pstm=ptmp;
wVideo=si.wPriority;
}
continue;
}
AVIStreamRelease(ptmp);
}
if (pstm==NULL)
return 0;
if ((pfrm=AVIStreamGetFrameOpen(pstm,NULL))==NULL)
return 0;
lStart=AVIStreamStart(pstm);
lEnd=AVIStreamLength(pstm)+lStart;
for (i=lStart;i<lEnd;i++) {
if ((pbmih=AVIStreamGetFrame(pfrm,i))==NULL)
return 0;
wsprintfA(szFile,"TEST%02d.BMP",i);
if (!SaveBitmap(szFile,pbmih))
return 0;
}
if (AVIStreamGetFrameClose(pfrm)!=0)
return 0;
AVIStreamRelease(pstm);
AVIFileRelease(pavi);
AVIFileExit();
return 0;
}
TEST02のプログラムではfor文を使いすべてのストリームを開いています。AVISTREAMINFO構造体のwPriorityの値が小さいストリームほど優先順位が高いストリームです。TEST02のプログラムでは一番優先順位が高いビデオストリームを開いています。またAVISTREAMINFO構造体のwLanguageを用いて現在のユーザーの言語に合うストリームを開く方法もあります。switch文の分岐を増やせばオーディオストリームを同時に開くこともできます。
しかし実際にはこんなに複雑なことをする必要はありません。なぜならば今のところはAVIファイルのストリームの構成は先に述べた3種類しかないためです。
また、AVIStreamOpenFromFileはAVIFileOpen、AVIFileGetStream、AVIFileReleaseを呼び出し、ファイル名からストリームを開くことができます。ファイルの中の特定の種類のストリームを1つだけ開くような場合はこのAPIが有効です。
TEST02の例ではAVIStreamGetFrameの引数にAVIStreamStartの戻り値を渡しています。AVIStreamStartはビデオストリームの先頭のサンプル番号を返します。通常は0です。この"サンプル"というのは、ストリームのデータ単位です。ビデオストリームの場合、1サンプルは1フレーム(1枚のビットマップ)です。
AVIStreamStartの値が0でない場合、再生ソフトはそのサンプル数分だけビデオストリームの再生を遅らせなければなりません。例えば秒間30フレームのAVIファイルでAVIStreamStartの値が60の場合、ビデオストリームは2秒遅れて再生を開始します。AVIStreamGetFrameに渡す値の最小値はAVIStreamStartの戻り値になります。ちなみにサンプル番号の最大値はAVIStreamStartとAVIStreamLengthの返す値の合計値-1です。
サンプルと時間の対応はAVIStreamSampleToTimeとAVIStreamTimeToSampleで求められます。しかしこれらはAVISTREAMINFOのメンバーから求めることもできます。「dwRate÷dwScale」で1秒あたりのサンプル数が求められます。dwScale=1、dwRate=15の場合、秒間15フレームということになります。注意しなければならないのはこれらの値はさまざまであるということです。うっかりすると桁あふれや丸め誤差で実際とは異なる値になってしまうことがあります。秒間30フレームであることをあらわすのにdwScale=33333、dwRate=1000000という場合もあればdwScale=1、dwRate=30という場合もあります。またAVIStreamStartの戻り値はdwStartであり、AVIStreamLengthの戻り値はdwLengthです。最後のサンプルは「dwLength-dwStart-1」で計算できます。
独自にサンプルと時間の対応を計算するメリットは、実際には存在しない時間からサンプル番号を求めることができることです。AVIStreanTimeToSampleでAVIファイルの実際の再生時間を超える値を指定すると最後のサンプル番号を返してしまいます。
オーディオストリームを扱う時には1サンプル当たりのバイト数に気を配る必要があります。オーディオストリームの圧縮形式がPCMで8ビットのモノラルの時、1サンプルは1バイトです。160×120のビットマップが約56kバイトです。同じ1サンプルでもそのバイト数は大きく異なります。また同じ時間(長さ)のストリームでも含まれるサンプル数はビデオストリームとオーディオストリームでは大きく異なります。220050kHzのPCMは1秒間のサンプル数が22050サンプルになりますが、秒間30フレームのビデオストリームは1秒間に30サンプルです。
フレームの中にはキーフレームと呼ばれる特別なフレームが存在します。このキーフレームは他のフレームに依存しないフレームのことです。
フレームが圧縮されている場合、フォーマットによっては前のフレームとの差分をとることでデータサイズの縮小を図っています。この場合、前のフレームとの違う部分だけが、そのフレームの圧縮されたデータとして存在します。ゆえにこのフレームを未圧縮のビットマップに展開するためには前のフレームの情報も必要になります。
キーフレームとは他のフレームの情報を必要としない独立したフレームのことです。フォーマットによってはすべてのフレームがキーフレームになります。未圧縮のAVIファイルはすべてのフレームがキーフレームです。
キーフレームは圧縮に関する情報であり、キーフレームが映像のシーンの切れ目(カット点)となるわけではありません。キーフレームあっても前のフレームと見た目がほとんど同じ場合もありますし、キーフレームでなくてもそのフレームから別の映像に変化する場合もあります。
ビットマップからAVIファイルを作るプログラムはTEST03のようになります。
TEST03.C
#include <math.h>
#include <tchar.h>
#include <windows.h>
#include <vfw.h>
#define LENG 10
#define PIXELS 100
#define WIDTH 160
#define HEIGHT 120
#define LINE ((((WIDTH)*24+31)&~31)/8)
#define SIZEIMAGE (LINE*(HEIGHT))
#define PI 3.1415926535
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
int i,j,s;
AVISTREAMINFO si={streamtypeVIDEO,comptypeDIB,0,0,0,0,
1,30,0,LENG,0,0,(DWORD)-1,0,{0,0,WIDTH,HEIGHT},0,0,_T("Video #1")};
BITMAPINFOHEADER bmih={sizeof(BITMAPINFOHEADER),WIDTH,HEIGHT,1,24,BI_RGB,
SIZEIMAGE,0,0,0,0};
BYTE bBit[SIZEIMAGE];
PAVIFILE pavi;
PAVISTREAM pstm;
memset(bBit,0,SIZEIMAGE);
AVIFileInit();
if (AVIFileOpen(&pavi,_T("TEST03.AVI"),
OF_CREATE | OF_WRITE | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (AVIFileCreateStream(pavi,&pstm,&si)!=0)
return 0;
if (AVIStreamSetFormat(pstm,0,&bmih,sizeof(BITMAPINFOHEADER))!=0)
return 0;
for (i=0;i<LENG;i++) {
for (j=0;j<PIXELS;j++) {
s=(int)(cos(PI*2*j/PIXELS)*min(WIDTH,HEIGHT)*(i*PIXELS+j)
/(LENG*PIXELS*2)+WIDTH/2)*3
+(int)(sin(PI*2*j/PIXELS)*min(WIDTH,HEIGHT)*(i*PIXELS+j)
/(LENG*PIXELS*2)+HEIGHT/2)*LINE;
bBit[s]=bBit[s+1]=bBit[s+2]=0xff;
}
if (AVIStreamWrite(pstm,i,1,bBit,SIZEIMAGE,
AVIIF_KEYFRAME,NULL,NULL)!=0)
return 0;
}
AVIStreamRelease(pstm);
AVIFileRelease(pavi);
AVIFileExit();
return 0;
}
TEST03のプログラムではAVIFileCreateStreamで新規にストリームを作り、そこに未圧縮のビットマップを書き込んでいます。AVIFileCreateStreamの引数のAVISTREAMINFO構造体のdwLengthにはビットマップの枚数を指定します。通常はrcFrameにはビットマップの幅と高さを指定します。rcFrameの値を設定することでビットマップの一部だけを表示して再生させることもできます。
AVIStreamSetFormatではBITMAPINFOHEADER構造体とパレットを書き込みます。パレットが存在しない場合にはBITMAPINFOHEADER構造体だけを書き込みます。パレットが存在する場合、BITMAPINFOHEADER構造体と一緒にパレットも書き込みます。
残りのビットマップの本体はAVIStreamWriteで書き込みます。無圧縮のビットマップはすべてのフレームがキーフレームになります。AVIStreamWriteの引数でAVIIF_KEYFRAMEを指定して下さい。
ビットマップからならばAVIファイルを作ることは可能で、白黒や16色のAVIファイルも作ることができます。
AVIファイルのストリームを別のフォーマットで圧縮したり、ビデオストリームとオーディオストリーム(WAVEファイル)を合わせて1つのAVIファイルを作るなどさまざまな要求があると思います。
TEST03の方法を使えば、AVIファイルのビットマップからAVIファイルを作ることもできます。しかし新しいAVIファイルは未圧縮になってしまいますし、オーディオストリームには対応できません。ストリームをそのまま読み込むにはAVIStreamReadFormatとAVIStreamReadを使います。
TEST04のプログラムはAVIファイルのビデオストリームとWAVEファイルを1つのAVIファイルにします。
TEST04.C
#include <stdlib.h>
#include <tchar.h>
#include <windows.h>
#include <vfw.h>
#define WAVBUFFER 4096
BOOL CopyStream(PAVIFILE pavi,PAVISTREAM pstm)
{
AVISTREAMINFO si;
LONG i,lStart,lEnd,lLength,lSample;
LPVOID p;
PAVISTREAM ptmp;
lStart=AVIStreamStart(pstm);
lEnd=lStart+AVIStreamLength(pstm)-1;
if (AVIStreamInfo(pstm,&si,sizeof(AVISTREAMINFO))!=0)
return FALSE;
if (AVIFileCreateStream(pavi,&ptmp,&si)!=0)
return FALSE;
if (AVIStreamReadFormat(pstm,lStart,NULL,&lLength)!=0)
return FALSE;
if ((p=malloc(lLength))==NULL)
return FALSE;
if (AVIStreamReadFormat(pstm,lStart,p,&lLength)!=0)
return FALSE;
if (AVIStreamSetFormat(ptmp,lStart,p,lLength)!=0)
return FALSE;
for (i=lStart;i<=lEnd;i+=lSample) {
if (AVIStreamRead(pstm,i,
AVISTREAMREAD_CONVENIENT,NULL,0,&lLength,&lSample)!=0)
return FALSE;
if ((lLength<=0 || lSample<=0)
&& AVIStreamRead(pstm,i,WAVBUFFER,NULL,0,&lLength,&lSample)!=0)
return FALSE;
if ((p=realloc(p,lLength))==NULL)
return FALSE;
if (AVIStreamRead(pstm,i,lSample,p,lLength,&lLength,&lSample)!=0)
return FALSE;
if (AVIStreamWrite(ptmp,i,lSample,p,lLength,
AVIIF_KEYFRAME,NULL,NULL)!=0)
return FALSE;
}
AVIStreamRelease(ptmp);
free(p);
return TRUE;
}
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
PAVIFILE pavi;
PAVISTREAM pstm;
AVIFileInit();
if (AVIFileOpen(&pavi,_T("TESTX.AVI"),
OF_CREATE | OF_WRITE | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (AVIStreamOpenFromFile(&pstm,_T("TEST.AVI"),streamtypeVIDEO,0,
OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (!CopyStream(pavi,pstm))
return 0;
AVIStreamRelease(pstm);
if (AVIStreamOpenFromFile(&pstm,_T("TEST.WAV"),streamtypeAUDIO,0,
OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (!CopyStream(pavi,pstm))
return 0;
AVIStreamRelease(pstm);
AVIFileRelease(pavi);
AVIFileExit();
return 0;
}
CopyStreamは引数で指定されたファイルに引数で指定されたストリームをコピーします。まずコピー元のストリームの情報を使ってファイルに新しいストリームを作ります。次にコピー元のストリームのフォーマットを読み込みます。
AVIStreamReadFormatには2つの機能があります。1つはフォーマットを読み込むことですが、もう1つはフォーマットのバイト数を取得する機能です。AVIStreamReadFormatでフォーマットのバイト数を求めないで固定長のバッファに読み込むのは危険です。フォーマットのバイト数は可変です。
その後で、ストリームのデータを書き込みます。ここで使われるのがAVIStreamReadです。このAPIもAVIStreamReadFormatと同様に2つの機能があります。引数のlpBufferにNULLを指定して呼び出すとplBytesとplSamplesに値を返します。この値に基づいてバッファを確保し、データを読み込みます。lSampleにAVISTREAMREAD_CONVENIENTを指定すると適当なサンプル数が取得できます。ただしWAVEファイルではAVISTREAMREAD_CONVENIENTで適当なサンプル数が求められずに0が返されます。そのため返された値をチェックして0の場合はlSampleに適当な値を入れて再度AVIStreamReadを呼び出します。
これを応用して逆の動作(AVIファイルからビデオストリームとオーディオストリームを取り出す)の例はTEST05のようになります。
TEST05.C
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
int i;
PAVIFILE pavi;
PAVISTREAM pstm[2];
AVIFileInit();
if (AVIFileOpen(&pavi,_T("TESTX.AVI"),
OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (AVIFileGetStream(pavi,&pstm[0],streamtypeVIDEO,0)!=0)
return 0;
if (AVIFileGetStream(pavi,&pstm[1],streamtypeAUDIO,0)!=0)
return 0;
AVIFileRelease(pavi);
if (AVIFileOpen(&pavi,_T("TEST05.AVI"),
OF_CREATE | OF_WRITE | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (!CopyStream(pavi,pstm[0]))
return 0;
AVIFileRelease(pavi);
if (AVIFileOpen(&pavi,_T("TEST05.WAV"),
OF_CREATE | OF_WRITE | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (!CopyStream(pavi,pstm[1]))
return 0;
AVIFileRelease(pavi);
for (i=0;i<2;i++)
AVIStreamRelease(pstm[i]);
AVIFileExit();
return 0;
}
今までの例ではストリームの圧縮形式を自由に変更できませんでした。しかし僅かな改良で圧縮形式を選択できるようになります。AVIMakeCompressedStream、AVISaveOptions、AVISaveOptionsFreeの3つのAPIを加えるだけです。
AVISaveOptionsはダイアログボックスを表示してユーザーが圧縮形式を選択できるようにします。選択された値はAVICOMPRESSOPTIONS構造体に返されます。
AVISaveOptionsを使用せずにAVICOMPRESSOPTIONS構造体のメンバを直接設定してAVIMakeCompressedStreamを呼び出すこともできます。しかしビデオストリームやオーディオストリームをの圧縮するドライバは、ユーザーが追加/削除することができます(コントロールパネルのマルチメディアの詳細設定を参照)。そのためAVICOMPRESSOPTIONS構造体をプログラムで固定的に設定してしまうと、ドライバがインストールされていない場合に対応できなくなってしまいます。
TEST06.C
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
int i,s=0;
AVICOMPRESSOPTIONS opt[2];
LPAVICOMPRESSOPTIONS popt[2];
PAVIFILE pavi;
PAVISTREAM pstm[2],ptmp;
AVIFileInit();
if (AVIFileOpen(&pavi,_T("TESTX.AVI"),
OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (AVIFileGetStream(pavi,&pstm[s],streamtypeVIDEO,0)==0)
s++;
if (AVIFileGetStream(pavi,&pstm[s],streamtypeAUDIO,0)==0)
s++;
AVIFileRelease(pavi);
if (s<=0)
return 0;
for (i=0;i<s;i++) {
popt[i]=&opt[i];
memset(popt[i],0,sizeof(AVICOMPRESSOPTIONS));
}
if (AVISaveOptions(NULL,
ICMF_CHOOSE_DATARATE | ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_PREVIEW,
s,pstm,popt)) {
if (AVIFileOpen(&pavi,_T("TEST06.AVI"),
OF_CREATE | OF_WRITE | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
for (i=0;i<s;i++) {
if (AVIMakeCompressedStream(&ptmp,pstm[i],popt[i],NULL)!=AVIERR_OK)
return 0;
if (!CopyStream(pavi,ptmp))
return 0;
AVIStreamRelease(ptmp);
}
AVIFileRelease(pavi);
}
AVISaveOptionsFree(s,popt);
for (i=0;i<s;i++)
AVIStreamRelease(pstm[i]);
AVIFileExit();
return 0;
}
AVIMakeCompressedStreamを応用し、TEST03をベースにビットマップから圧縮されたAVIファイルを作ってみたいと思います。ビットマップからAVIファイルを作る場合、元になるストリームがないのでAVISaveOptions、AVISaveOptionsFreeを呼び出すことができません。そこで低レベルなAPIのICCompressorChooseとICCompressorFreeを使います。
ICCompressorChooseではBITMAPINFO構造体からフォーマットを選択可能です。ただしこのAPIはAVICOMPRESSOPTIONS構造体ではなくCOMPVARS構造体を使うので、AVICOMPRESSOPTIONS構造体の対応するメンバにCOMPVARS構造体の値を代入してからAVIMakeCompressedStreamを呼び出す必要があります。
TEST07.C
#include <math.h>
#include <tchar.h>
#include <windows.h>
#include <vfw.h>
#define LENG 10
#define PIXELS 100
#define WIDTH 160
#define HEIGHT 120
#define LINE ((((WIDTH)*24+31)&~31)/8)
#define SIZEIMAGE (LINE*(HEIGHT))
#define PI 3.1415926535
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
int i,j,s;
AVICOMPRESSOPTIONS opt;
AVISTREAMINFO si={streamtypeVIDEO,comptypeDIB,0,0,0,0,
1,30,0,LENG,0,0,(DWORD)-1,0,{0,0,WIDTH,HEIGHT},0,0,_T("Video #1")};
BITMAPINFOHEADER bmih={sizeof(BITMAPINFOHEADER),WIDTH,HEIGHT,1,24,BI_RGB,
SIZEIMAGE,0,0,0,0};
BYTE bBit[SIZEIMAGE];
COMPVARS cv;
PAVIFILE pavi;
PAVISTREAM pstm,ptmp;
memset(bBit,0,SIZEIMAGE);
AVIFileInit();
memset(&cv,0,sizeof(COMPVARS));
cv.cbSize=sizeof(COMPVARS);
cv.dwFlags=ICMF_COMPVARS_VALID;
cv.fccHandler=comptypeDIB;
cv.lQ=ICQUALITY_DEFAULT;
if (!ICCompressorChoose(NULL,ICMF_CHOOSE_DATARATE | ICMF_CHOOSE_KEYFRAME,
&bmih,NULL,&cv,NULL))
return 0;
si.fccHandler=cv.fccHandler;
opt.fccType=streamtypeVIDEO;
opt.fccHandler=cv.fccHandler;
opt.dwKeyFrameEvery=cv.lKey;
opt.dwQuality=cv.lQ;
opt.dwBytesPerSecond=cv.lDataRate;
opt.dwFlags=(cv.lDataRate>0?AVICOMPRESSF_DATARATE:0)
|(cv.lKey>0?AVICOMPRESSF_KEYFRAMES:0);
opt.lpFormat=NULL;
opt.cbFormat=0;
opt.lpParms=cv.lpState;
opt.cbParms=cv.cbState;
opt.dwInterleaveEvery=0;
if (AVIFileOpen(&pavi,_T("TEST07.AVI"),
OF_CREATE | OF_WRITE | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (AVIFileCreateStream(pavi,&pstm,&si)!=0)
return 0;
if (AVIMakeCompressedStream(&ptmp,pstm,&opt,NULL)!=AVIERR_OK)
return 0;
if (AVIStreamSetFormat(ptmp,0,&bmih,sizeof(BITMAPINFOHEADER))!=0)
return 0;
for (i=0;i<LENG;i++) {
for (j=0;j<PIXELS;j++) {
s=(int)(cos(PI*2*j/PIXELS)*min(WIDTH,HEIGHT)*(i*PIXELS+j)
/(LENG*PIXELS*2)+WIDTH/2)*3
+(int)(sin(PI*2*j/PIXELS)*min(WIDTH,HEIGHT)*(i*PIXELS+j)
/(LENG*PIXELS*2)+HEIGHT/2)*LINE;
bBit[s]=bBit[s+1]=bBit[s+2]=0xff;
}
if (AVIStreamWrite(ptmp,i,1,bBit,SIZEIMAGE,
AVIIF_KEYFRAME,NULL,NULL)!=0)
return 0;
}
AVIStreamRelease(ptmp);
AVIStreamRelease(pstm);
AVIFileRelease(pavi);
ICCompressorFree(&cv);
AVIFileExit();
return 0;
}
AVISaveまたはAVISaveVを利用するとファイルへの保存を簡潔に行うことができます。AVISaveとAVISaveVはコールバック関数を利用できます。コールバック関数でAVIERR_OKを返すと処理を継続します。AVIERR_USERABORTを返すと処理を中断します。コールバック関数の戻り値はAVISaveまたはAVISaveVの戻り値になります。
TEST08はAVISaveVを利用した例です。下記の例ではダイアログボックスを表示してユーザーが処理を中断できるようにしています。
TEST08.C
#include <string.h>
#include <tchar.h>
#include <windows.h>
#include <vfw.h>
#include "test08.rh"
HWND hDlg;
LONG lUserBreak=AVIERR_OK;
LONG FAR PASCAL SaveCallback(int nPercent)
{
SetDlgItemInt(hDlg,IDC_STATIC,nPercent,TRUE);
return lUserBreak;
}
BOOL CALLBACK AbortDlgProc(HWND hDlg,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
switch (uMsg) {
case WM_INITDIALOG:
{
RECT rc;
GetWindowRect(hDlg,&rc);
SetWindowPos(hDlg,0,
(GetSystemMetrics(SM_CXSCREEN)-rc.right+rc.left)/2,
(GetSystemMetrics(SM_CYSCREEN)-rc.bottom+rc.top)/2,
0,0,SWP_NOSIZE | SWP_NOZORDER);
}
return TRUE;
case WM_COMMAND:
if (LOWORD(wParam)==IDCANCEL)
lUserBreak=AVIERR_USERABORT;
return TRUE;
}
return FALSE;
}
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
int i,s=0;
AVICOMPRESSOPTIONS opt[2];
LPAVICOMPRESSOPTIONS popt[2];
PAVIFILE pavi;
PAVISTREAM pstm[2];
AVIFileInit();
if (AVIFileOpen(&pavi,_T("TESTX.AVI"),
OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (AVIFileGetStream(pavi,&pstm[s],streamtypeVIDEO,0)==0)
s++;
if (AVIFileGetStream(pavi,&pstm[s],streamtypeAUDIO,0)==0)
s++;
AVIFileRelease(pavi);
if (s<=0)
return 0;
for (i=0;i<s;i++) {
popt[i]=&opt[i];
memset(popt[i],0,sizeof(AVICOMPRESSOPTIONS));
}
if (AVISaveOptions(NULL,
ICMF_CHOOSE_DATARATE | ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_PREVIEW,
s,pstm,popt)) {
if ((hDlg=CreateDialogParam(hInstance,MAKEINTRESOURCE(DIALOG_1),
NULL,AbortDlgProc,0))==NULL)
return 0;
if (AVISaveV(_T("TEST08.AVI"),NULL,
SaveCallback,s,pstm,popt)!=AVIERR_OK)
return 0;
DestroyWindow(hDlg);
}
AVISaveOptionsFree(s,popt);
for (i=0;i<s;i++)
AVIStreamRelease(pstm[i]);
AVIFileExit();
return 0;
}
AVIファイルからWAVEファイルを取り出すのはTEST05で行ったような方法があります。しかしこの場合、元になるAVIファイルのオーディオストリームをそのままWAVEファイルに保存するだけでフォーマットの変更はできませんでした。特にオーディオストリームを波形表示したり解析するようなプログラムでは未圧縮のPCMが必要になります。
TEST09では圧縮されたオーディオストリームから未圧縮のPCMを取得しています。
TEST09.C
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <windows.h>
#include <vfw.h>
#define WAVBUFFER 4096
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
AVICOMPRESSOPTIONS opt;
DWORD dwSize=0;
FILE *fp;
LONG i,lStart,lEnd,lLength,lSample;
LPVOID p;
PAVISTREAM pstm,ptmp;
WAVEFORMATEX wfx;
AVIFileInit();
if (AVIStreamOpenFromFile(&pstm,_T("TESTX.AVI"),streamtypeAUDIO,0,
OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
lStart=AVIStreamStart(pstm);
if (AVIStreamReadFormat(pstm,lStart,NULL,&lLength)!=0)
return 0;
if ((p=malloc(lLength))==NULL)
return 0;
if (AVIStreamReadFormat(pstm,lStart,p,&lLength)!=0)
return 0;
wfx=*(LPWAVEFORMATEX)p;
wfx.wFormatTag=WAVE_FORMAT_PCM;
wfx.wBitsPerSample=wfx.wBitsPerSample<12?8:16;
wfx.nBlockAlign=wfx.nChannels*wfx.wBitsPerSample/8;
wfx.nAvgBytesPerSec=wfx.nSamplesPerSec*wfx.nBlockAlign;
wfx.cbSize=0;
memset(&opt,0,sizeof(AVICOMPRESSOPTIONS));
opt.fccType=streamtypeAUDIO;
opt.lpFormat=&wfx;
opt.cbFormat=sizeof(WAVEFORMATEX)-sizeof(WORD);
if (AVIMakeCompressedStream(&ptmp,pstm,&opt,NULL)!=AVIERR_OK)
return 0;
lStart=AVIStreamStart(ptmp);
lEnd=lStart+AVIStreamLength(ptmp)-1;
if ((fp=fopen("TESTP.WAV","wb"))==NULL)
return 0;
if (fwrite("RIFF",sizeof(char),4,fp)!=4)
return 0;
for (i=0;i<4;i++)
if (fputc(0,fp)==EOF)
return 0;
if (fwrite("WAVE",sizeof(char),4,fp)!=4)
return 0;
if (fwrite("fmt ",sizeof(char),4,fp)!=4)
return 0;
if (fwrite(&opt.cbFormat,sizeof(DWORD),1,fp)!=1)
return 0;
if (fwrite(opt.lpFormat,opt.cbFormat,1,fp)!=1)
return 0;
if (fwrite("data",sizeof(char),4,fp)!=4)
return 0;
for (i=0;i<4;i++)
if (fputc(0,fp)==EOF)
return 0;
for (i=lStart;i<=lEnd;i+=lSample) {
if (AVIStreamRead(ptmp,i,
AVISTREAMREAD_CONVENIENT,NULL,0,&lLength,&lSample)!=0)
return 0;
if ((lLength<=0 || lSample<=0)
&& AVIStreamRead(ptmp,i,WAVBUFFER,NULL,0,&lLength,&lSample)!=0)
return 0;
if ((p=realloc(p,lLength))==NULL)
return 0;
if (AVIStreamRead(ptmp,i,lSample,p,lLength,&lLength,&lSample)!=0)
return 0;
if (fwrite(p,lLength,1,fp)!=1)
return 0;
dwSize+=lLength;
}
if (fseek(fp,-(dwSize+sizeof(DWORD)),SEEK_END)!=0)
return 0;
if (fwrite(&dwSize,sizeof(DWORD),1,fp)!=1)
return 0;
if (fseek(fp,sizeof(char)*4,SEEK_SET)!=0)
return 0;
dwSize+=sizeof(char)*12+sizeof(DWORD)*2+opt.cbFormat;
if (fwrite(&dwSize,sizeof(DWORD),1,fp)!=1)
return 0;
fclose(fp);
AVIStreamRelease(ptmp);
AVIStreamRelease(pstm);
return 0;
}
しかしこの方法ではAVIMakeCompressedStreamが失敗する場合があります。例えば「Microsoft ADPCM 22.050kHz 4ビット モノラル」のオーディオストリームは「PCM 22.050kHz 8ビット モノラル」または「PCM 22.050kHz 16ビット モノラル」以外のフォーマットに変更できません。44.100kHzのPCMやステレオに変換することはできません。
TEST09を改良して任意のPCMを取得できるようにしたのがTEST10です。これはAVIMakeStreamFromClipboardとAVIMakeCompressedStreamを多重に使うことで目的の形式にしています。AVIMakeStreamFromClipboardはクリップボードの内容から編集ストリームインターフェイスポインタを作るAPIですがメモリ上のWAVEファイルから編集ストリームインターフェイスポインタを作ることもできます。
TEST10.C
#include <stdio.h>
#include <stdlib.h>
#include <tchar.h>
#include <windows.h>
#include <vfw.h>
#define WAVBUFFER 32768
#define CHANNELS 2
#define SAMPLESPERSEC 44100
#define BITSPERSAMPLE 16
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
AVICOMPRESSOPTIONS opt;
DWORD dwSize;
FILE *fp;
HGLOBAL hGlobal;
LONG lFormat,lLength,lPos,lSample;
LPVOID p;
LPWAVEFORMATEX pwfx;
PAVISTREAM pstm,ptmp;
WAVEFORMATEX wfx;
memset(&opt,0,sizeof(AVICOMPRESSOPTIONS));
opt.fccType=streamtypeAUDIO;
opt.lpFormat=&wfx;
opt.cbFormat=sizeof(WAVEFORMATEX)-sizeof(WORD);
AVIFileInit();
if (AVIStreamOpenFromFile(&pstm,_T("TESTX.AVI"),streamtypeAUDIO,0,
OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
lPos=AVIStreamStart(pstm);
lSample=AVIStreamLength(pstm);
if (AVIStreamReadFormat(pstm,lPos,NULL,&lFormat)!=0)
return 0;
if ((pwfx=malloc(lFormat))==NULL)
return 0;
if (AVIStreamReadFormat(pstm,lPos,pwfx,&lFormat)!=0)
return 0;
wfx=*pwfx;
if (pwfx->wFormatTag!=WAVE_FORMAT_PCM || pwfx->nChannels!=CHANNELS
|| pwfx->nSamplesPerSec!=SAMPLESPERSEC
|| pwfx->wBitsPerSample!=BITSPERSAMPLE) {
if (AVIStreamReadFormat(pstm,lPos,NULL,&lFormat)!=0)
return 0;
if (AVIStreamRead(pstm,lPos,lSample,NULL,0,&lLength,&lSample)!=0)
lLength=lSample*pwfx->nBlockAlign;
if ((hGlobal=GlobalAlloc(GMEM_MOVEABLE,lFormat+lLength
+sizeof(FOURCC)*4+sizeof(DWORD)*3))==NULL)
return 0;
if ((p=GlobalLock(hGlobal))==NULL)
return 0;
if (AVIStreamReadFormat(pstm,lPos,
(LPBYTE)p+sizeof(FOURCC)*3+sizeof(DWORD)*2,&lFormat)!=0
|| AVIStreamRead(pstm,lPos,lSample,
(LPBYTE)p+lFormat+sizeof(FOURCC)*4+sizeof(DWORD)*3,
lLength,&lLength,NULL)!=0)
return 0;
AVIStreamRelease(pstm);
*(FOURCC *)p=FOURCC_RIFF;
((FOURCC *)p)++;
*(LPDWORD)p=lFormat+lLength+sizeof(FOURCC)*3+sizeof(DWORD)*2;
((LPDWORD)p)++;
*(FOURCC *)p=mmioFOURCC('W','A','V','E');
((FOURCC *)p)++;
*(FOURCC *)p=mmioFOURCC('f','m','t',' ');
((FOURCC *)p)++;
*(LPDWORD)p=lFormat;
((LPDWORD)p)++;
wfx=*(LPWAVEFORMATEX)p;
p=(LPBYTE)p+lFormat;
*(FOURCC *)p=mmioFOURCC('d','a','t','a');
((FOURCC *)p)++;
*(LPDWORD)p=lLength;
GlobalUnlock(hGlobal);
if (AVIMakeStreamFromClipboard(CF_WAVE,hGlobal,&ptmp)!=0)
return 0;
GlobalFree(hGlobal);
wfx.wFormatTag=WAVE_FORMAT_PCM;
wfx.wBitsPerSample=wfx.wBitsPerSample<=12?8:16;
wfx.nBlockAlign=wfx.nChannels*wfx.wBitsPerSample/8;
wfx.nAvgBytesPerSec=wfx.nSamplesPerSec*wfx.nBlockAlign;
wfx.cbSize=0;
if (AVIMakeCompressedStream(&pstm,ptmp,&opt,NULL)!=AVIERR_OK)
return 0;
AVIStreamRelease(ptmp);
lPos=AVIStreamStart(pstm);
lSample=AVIStreamLength(pstm);
if (wfx.nChannels!=CHANNELS || wfx.nSamplesPerSec!=SAMPLESPERSEC
|| wfx.wBitsPerSample!=BITSPERSAMPLE) {
if (AVIStreamRead(pstm,lPos,lSample,NULL,0,&lLength,&lSample)!=0)
lLength=lSample*wfx.nBlockAlign;
if ((hGlobal=GlobalAlloc(GMEM_MOVEABLE,lLength+opt.cbFormat
+sizeof(FOURCC)*4+sizeof(DWORD)*3))==NULL)
return 0;
if ((p=GlobalLock(hGlobal))==NULL)
return 0;
if (AVIStreamRead(pstm,lPos,lSample,
(LPBYTE)p+opt.cbFormat+sizeof(FOURCC)*4+sizeof(DWORD)*3,
lLength,&lLength,NULL)!=0)
return 0;
AVIStreamRelease(pstm);
*(FOURCC *)p=FOURCC_RIFF;
((FOURCC *)p)++;
*(LPDWORD)p=lLength+opt.cbFormat+sizeof(FOURCC)*3+sizeof(DWORD)*2;
((LPDWORD)p)++;
*(FOURCC *)p=mmioFOURCC('W','A','V','E');
((FOURCC *)p)++;
*(FOURCC *)p=mmioFOURCC('f','m','t',' ');
((FOURCC *)p)++;
*(LPDWORD)p=opt.cbFormat;
((LPDWORD)p)++;
memcpy(p,opt.lpFormat,opt.cbFormat);
p=((LPBYTE)p)+opt.cbFormat;
*(FOURCC *)p=mmioFOURCC('d','a','t','a');
((FOURCC *)p)++;
*(LPDWORD)p=lLength;
GlobalUnlock(hGlobal);
if (AVIMakeStreamFromClipboard(CF_WAVE,hGlobal,&ptmp)!=0)
return 0;
GlobalFree(hGlobal);
wfx.wFormatTag=WAVE_FORMAT_PCM;
wfx.nChannels=CHANNELS;
wfx.nSamplesPerSec=SAMPLESPERSEC;
wfx.wBitsPerSample=BITSPERSAMPLE;
wfx.nBlockAlign=wfx.nChannels*wfx.wBitsPerSample/8;
wfx.nAvgBytesPerSec=wfx.nSamplesPerSec*wfx.nBlockAlign;
wfx.cbSize=0;
if (AVIMakeCompressedStream(&pstm,ptmp,&opt,NULL)!=AVIERR_OK)
return 0;
AVIStreamRelease(ptmp);
lPos=AVIStreamStart(pstm);
lSample=AVIStreamLength(pstm);
}
}
free(pwfx);
if (AVIStreamRead(pstm,lPos,lSample,NULL,0,&lLength,&lSample)!=0)
lLength=lSample*wfx.nBlockAlign;
if ((p=malloc(lLength))==NULL)
return 0;
if (AVIStreamRead(pstm,lPos,lSample,p,lLength,&lLength,NULL)!=0)
return 0;
AVIStreamRelease(pstm);
AVIFileExit();
if ((fp=fopen("TESTP.WAV","wb"))==NULL)
return 0;
if (fwrite("RIFF",sizeof(FOURCC),1,fp)!=1)
return 0;
dwSize=lLength+sizeof(FOURCC)*4+sizeof(DWORD)*3+opt.cbFormat;
if (fwrite(&dwSize,sizeof(DWORD),1,fp)!=1)
return 0;
if (fwrite("WAVE",sizeof(FOURCC),1,fp)!=1)
return 0;
if (fwrite("fmt ",sizeof(FOURCC),1,fp)!=1)
return 0;
if (fwrite(&opt.cbFormat,sizeof(DWORD),1,fp)!=1)
return 0;
if (fwrite(opt.lpFormat,opt.cbFormat,1,fp)!=1)
return 0;
if (fwrite("data",sizeof(FOURCC),1,fp)!=1)
return 0;
if (fwrite(&lLength,sizeof(LONG),1,fp)!=1)
return 0;
if (fwrite(p,sizeof(BYTE),lLength,fp)!=lLength)
return 0;
free(p);
fclose(fp);
return 0;
}
AVIStreamReadとAVIStreamWriteを使うことでAVIファイルのフレームの順番を入れ替えたり、一部のフレームを取り出すことができるように思えます。しかし圧縮形式によっては、キーフレームとキーフレームではないフレームが存在します。キーフレームではないフレームは前のフレームに依存します。よってキーフレームではないフレームだけを取り出したり、順番を変えると正しく表示されない場合があります。
そこでAVIファイルを編集するために「編集ストリーム」を利用します。編集ストリームインターフェイスポインタは通常のストリームインターフェイスポインタを元にしてCreateEditableStreamで作ることができます。またCreateEditableStreamで空の編集ストリームを作ることもできます。TEST11の例ではフレームの順番を反転しています。
TEST11.C
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
AVICOMPRESSOPTIONS opt;
LONG i,lStart,lEnd,lLength,lPos,lCopy;
LPAVICOMPRESSOPTIONS popt;
PAVISTREAM pstm,psrc,pdst;
if (AVIStreamOpenFromFile(&pstm,_T("TEST.AVI"),streamtypeVIDEO,0,
OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (CreateEditableStream(&psrc,pstm)!=0)
return 0;
AVIStreamRelease(pstm);
if (CreateEditableStream(&pdst,NULL)!=0)
return 0;
lStart=AVIStreamStart(psrc);
lEnd=lStart+AVIStreamLength(psrc)-1;
for (i=lStart;i<=lEnd;i++) {
lLength=1;
lCopy=i;
if (EditStreamCopy(psrc,&lCopy,&lLength,&pstm)!=0)
return 0;
lLength=1;
lPos=0;
if (EditStreamPaste(pdst,&lPos,&lLength,pstm,0,-1)!=0)
return 0;
AVIStreamRelease(pstm);
}
AVIStreamRelease(psrc);
popt=&opt;
memset(popt,0,sizeof(AVICOMPRESSOPTIONS));
if (AVISaveOptions(NULL,
ICMF_CHOOSE_DATARATE | ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_PREVIEW,
1,&pdst,&popt)) {
if ((hDlg=CreateDialogParam(hInstance,MAKEINTRESOURCE(DIALOG_1),
NULL,AbortDlgProc,0))==NULL)
return 0;
if (AVISaveV(_T("TEST11.AVI"),NULL,
SaveCallback,1,&pdst,&popt)!=AVIERR_OK)
return 0;
DestroyWindow(hDlg);
}
AVISaveOptionsFree(1,&popt);
AVIStreamRelease(pdst);
AVIFileExit();
return 0;
}
EditStreamCopyでストリームの一部をコピーし、EditStreamPasteで貼り付けます。EditStreamPasteで貼り付け元の範囲を指定すれば1回で済むように思いますが、EditStreamPasteで貼り付け元の範囲を指定することはできないようです。よってEditStreamPasteの引数の最後の2つは0と-1を指定します。
編集ストリームインターフェイスポインタは通常のストリームインターフェイスポインタと同じように使うことができます。ストリームインターフェイスポインタを引数にもつ他のAPIも利用可能です。
AVIファイルを編集するためにはクリップボードを利用する必要があると思います。VFWではクリップボードを利用するためのAPIが用意されています。
TEST12の例ではAVIPutFileOnClipboardを使ってAVIファイルをクリップボードに入れ、クリップボードから取り出して保存します。クリップボードには同時にDIB、WAVEファイルが入ります。
TEST12.C
#include <tchar.h>
#include <windows.h>
#include <vfw.h>
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
AVIFILEINFO fi;
DWORD dwStream;
LPAVICOMPRESSOPTIONS *popt;
PAVIFILE pavi;
PAVISTREAM *pstm;
AVIFileInit();
if (AVIFileOpen(&pavi,_T("TESTX.AVI"),
OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
AVIPutFileOnClipboard(pavi);
AVIFileRelease(pavi);
if (AVIGetFromClipboard(&pavi)!=0)
return 0;
if (AVIFileInfo(pavi,&fi,sizeof(AVIFILEINFO))!=0)
return 0;
if ((pstm=malloc(fi.dwStreams*sizeof(PAVISTREAM)))==NULL)
return 0;
if ((popt=malloc(fi.dwStreams*sizeof(LPAVICOMPRESSOPTIONS)))==NULL)
return 0;
for (dwStream=0;dwStream<fi.dwStreams;dwStream++) {
if ((popt[dwStream]=calloc(1,sizeof(AVICOMPRESSOPTIONS)))==NULL)
return 0;
if (AVIFileGetStream(pavi,&pstm[dwStream],0,dwStream)!=0)
return 0;
}
if (AVISaveOptions(NULL,
ICMF_CHOOSE_DATARATE | ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_PREVIEW,
fi.dwStreams,pstm,popt)
&& AVISaveV(_T("TEST12.AVI"),NULL,NULL,
fi.dwStreams,pstm,popt)!=AVIERR_OK)
return 0;
AVISaveOptionsFree(fi.dwStreams,popt);
for (dwStream=0;dwStream<fi.dwStreams;dwStream++) {
AVIStreamRelease(pstm[dwStream]);
free(popt[dwStream]);
}
free(popt);
free(pstm);
AVIFileRelease(pavi);
AVIFileExit();
return 0;
}
VFWのAPIにはメモリに読み込んだビットマップファイルから編集ストリームインターフェイスポインタを作ることはできないように思えます。しかし、AVIMakeStreamFromClipboardを応用することでメモリに読み込んだビットマップファイルから編集ストリームインターフェイスポインタを作ることができます。この場合、クリップボード形式はCF_DIBを指定します。
TEST13.C
#include <string.h>
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <vfw.h>
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow) {
long fsize;
AVICOMPRESSOPTIONS opt;
FILE *fp;
HGLOBAL hGlobal;
LPAVICOMPRESSOPTIONS popt;
LPVOID p;
PAVISTREAM pstm;
AVIFileInit();
if ((fp=fopen("TEST00.BMP","rb"))==NULL)
return 0;
if (fseek(fp,0,SEEK_END)!=0)
return 0;
if ((fsize=ftell(fp))==-1)
return 0;
fsize-=sizeof(BITMAPFILEHEADER);
if (fseek(fp,sizeof(BITMAPFILEHEADER),SEEK_SET)!=0)
return 0;
if ((hGlobal=GlobalAlloc(GMEM_MOVEABLE,fsize))==NULL)
return 0;
if ((p=GlobalLock(hGlobal))==NULL)
return 0;
if (fread(p,1,fsize,fp)!=fsize)
return 0;
GlobalUnlock(hGlobal);
if (fclose(fp)!=0)
return 0;
if (AVIMakeStreamFromClipboard(CF_DIB,hGlobal,&pstm)==0) {
popt=&opt;
memset(popt,0,sizeof(AVICOMPRESSOPTIONS));
if (AVISaveOptions(NULL,ICMF_CHOOSE_DATARATE | ICMF_CHOOSE_KEYFRAME
| ICMF_CHOOSE_PREVIEW,1,&pstm,&popt)
&& AVISaveV(_T("TEST13.AVI"),NULL,NULL,1,&pstm,&popt)!=AVIERR_OK)
return 0;
AVISaveOptionsFree(1,&popt);
AVIStreamRelease(pstm);
}
GlobalFree(hGlobal);
AVIFileExit();
return 0;
}
エクスプローラでAVIファイルのプロパティを見るとビデオの圧縮形式の名前が表示されます。ビデオの圧縮形式の名前を示す文字列はVFWのAVI APIでは取得できません。VCM(Video Compression Manager)を利用する必要があります。
VCMを利用した例はTEST14のようになります。
TEST14.C
#include <tchar.h>
#include <windows.h>
#include <vfw.h>
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
AVISTREAMINFO si;
HIC hic;
ICINFO icinfo;
PAVISTREAM pstm;
TCHAR szText[128];
if (AVIStreamOpenFromFile(&pstm,_T("TEST.AVI"),streamtypeVIDEO,0,
OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (AVIStreamInfo(pstm,&si,sizeof(AVISTREAMINFO))!=0)
return 0;
AVIStreamRelease(pstm);
if ((hic=ICOpen(ICTYPE_VIDEO,si.fccHandler,ICMODE_QUERY))!=NULL) {
icinfo.dwSize=sizeof(ICINFO);
if (ICGetInfo(hic,&icinfo,sizeof(ICINFO))>0)
#ifdef UNICODE
lstrcpy(szText,icinfo.szDescription);
#else
WideCharToMultiByte(CP_ACP,0,icinfo.szDescription,-1,
szText,sizeof(TCHAR)*128,NULL,NULL);
#endif
else
lstrcpy(szText,_T("未知のフォーマット"));
if (ICClose(hic)!=ICERR_OK)
return 0;
} else {
lstrcpy(szText,_T("未知のフォーマット"));
}
MessageBox(NULL,szText,_T("TEST"),MB_OK);
return 0;
}
ビデオ形式と同様にオーディオの圧縮形式の名前を示す文字列はVFWのAVI APIでは取得できません。ACM(Audio Compression Manager)を利用する必要があります。
ACMを利用した例はTEST15のようになります。
TEST15.C
#include <stdlib.h>
#include <string.h>
#include <tchar.h>
#include <windows.h>
#include <vfw.h>
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
ACMFORMATTAGDETAILS aftd;
AVISTREAMINFO si;
LONG lLength;
LPWAVEFORMATEX pwfx;
PAVISTREAM pstm;
if (AVIStreamOpenFromFile(&pstm,_T("TESTX.AVI"),streamtypeAUDIO,0,
OF_READ | OF_SHARE_DENY_NONE,NULL)!=0)
return 0;
if (AVIStreamInfo(pstm,&si,sizeof(AVISTREAMINFO))!=0)
return 0;
if (AVIStreamReadFormat(pstm,si.dwStart,NULL,&lLength)!=0)
return 0;
if (lLength<sizeof(WAVEFORMATEX)-sizeof(WORD))
return 0;
if ((pwfx=malloc(lLength))==NULL)
return 0;
if (AVIStreamReadFormat(pstm,si.dwStart,pwfx,&lLength)!=0)
return 0;
memset(&aftd,0,sizeof(ACMFORMATTAGDETAILS));
aftd.cbStruct=sizeof(ACMFORMATTAGDETAILS);
aftd.dwFormatTag=pwfx->wFormatTag;
if (acmFormatTagDetails(NULL,&aftd,ACM_FORMATTAGDETAILSF_FORMATTAG)==0)
MessageBoxA(NULL,aftd.szFormatTag,"TEST",MB_OK);
else
MessageBox(NULL,_T("未知のフォーマット"),_T("TEST"),MB_OK);
free(pwfx);
return 0;
}
AVIFileOpen、AVIStreamOpenFromFile、AVISaveおよびAVISaveVの引数にはCLSIDへのポインタ、pclsidHandlerがあります。今までpclsidHandlerにはNULLを指定してきました。この引数がNULLときには拡張子に基づいてレジストリを検索してCLSIDを求めます。pclsidHandlerにNULLを指定したときにファイルの拡張子を「.AVI」や「.WAV」以外にするとCLSIDを求めることができず、APIは失敗します。
pclsidHandlerを直接指定すれば「.AVI」や「.WAV」以外に拡張子を変更してもファイルを開くことができます。CLSIDは下記のように定義されています。
typedef struct _GUID {
DWORD Data1
WORD Data2;
WORD Data3;
BYTE Data4[8];
} GUID;
typedef GUID CLSID;
AVIのCLSIDは{00020000-0000-0000-C000-000000000046}です。TEST16はCLSIDを使ってAVIファイルを開く例です。
詳しくはOLE、COMなどを調べて下さい。
TEST16.C
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,int nCmdShow)
{
CLSID clsid={0x00020000,0x0000,0x0000,
{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
LPBITMAPINFOHEADER pbmih;
PAVIFILE pavi;
PAVISTREAM pstm;
PGETFRAME pfrm;
AVIFileInit();
if (AVIFileOpen(&pavi,_T("TEST.AVI"),
OF_READ | OF_SHARE_DENY_NONE,&clsid)!=0)
return 0;
if (AVIFileGetStream(pavi,&pstm,0,0)!=0)
return 0;
if ((pfrm=AVIStreamGetFrameOpen(pstm,NULL))==NULL)
return 0;
if ((pbmih=AVIStreamGetFrame(pfrm,0))==NULL)
return 0;
if (!SaveBitmap("TEST00.BMP",pbmih))
return 0;
if (AVIStreamGetFrameClose(pfrm)!=0)
return 0;
AVIStreamRelease(pstm);
AVIFileRelease(pavi);
AVIFileExit();
return 0;
}
RIFFは複数のチャンクで構成されています。チャンクは
という構成になっています。データはWORD単位(2バイト)でアライメントされ、データが奇数サイズの時には0を付け加えます。
チャンクは階層構造(チャンクの中に複数のチャンクがある構造)にすることができます。RIFFチャンクとLISTチャンクのデータ部分の先頭4バイトはフォームタイプまたはリストタイプがあり、その後にサブチャンクがあります。RIFFファイル自体、1つのRIFFチャンクになっています。階層構造のRIFFファイルは図1のようになります。
RIFF | |||||||||||||
ファイルサイズ | |||||||||||||
フォームタイプ | |||||||||||||
|
AVIファイルの構造は複雑でいくつものチャンクがあります。オーディオストリームが1つとビデオストリームが1つの基本的なAVIファイルは図2のようになります。
図2.AVIファイルの例
RIFF 'AVI '
LIST 'hdrl'
avih (AVIMainHeader構造体)
LIST 'strl'
strh (AVIStreamHeader構造体)
strf (BITMAPINFO構造体)
strn (オプションヘッタ)
LIST 'strl'
strh (AVIStreamHeader構造体)
strf (WAVEFORMAT構造体)
strn (オプションヘッタ)
JUNK (2048バイト境界にアライメントするためのダミーデータ)
LIST 'movi'
00db (DIBデータ)
00db (DIBデータ)
00db (DIBデータ)
01wb (WAVEデータ)
01wb (WAVEデータ)
01wb (WAVEデータ)
idx1 (AVIINDEXENTRY構造体×movi内のチャンク数)
RIFFチャンクのフォームタイプは"AVI "です。この中のLISTチャンクhdrlにはヘッタ情報があります。avihはファイル全体の情報、LISTチャンクstrlはストリーム毎の情報が格納されます。strlはAVIStreamHeader構造体のチャンクとBITMAPINFO構造体またはWAVEFORMAT構造体のチャンクがあります。オプションヘッタは必須ではありません。オプションヘッタにはストリームの名前を示す文字列が入ります。
JUNKチャンクはLISTチャンクmoviの最初のデータの4文字コード(??db、??dc、??pc、??wb)を2048バイト境界にアライメントするためにあります。JUNKチャンクの内容は不定で場合によってはゴミデータが入っています。
LISTチャンクmoviは実際の映像や音声のデータが入っています。4文字コードの先頭2文字はそのデータのストリームの番号です。後の2文字は
です。これらのチャンクはLISTチャンクrecにまとめられることがあります。その場合にはmoviは図3のようになります。
図3.インターリーブされたLISTチャンクmovi
LIST 'movi'
LIST 'rec '
00db (DIBデータ)
01wb (WAVEデータ)
LIST 'rec '
00db (DIBデータ)
01wb (WAVEデータ)
LIST 'rec '
00db (DIBデータ)
01wb (WAVEデータ)
このようにまとめられていない場合、映像と音声を読み込むためにファイルをシークすることになります。しかしLISTチャンクrecにまとめることで、再生ソフトは一括してメモリに読み込みシーク時間を減らします。この方法をInterleaveと言います。
8ビット以下のDIBではパレットが使われます。パレットはBITMAPINFO構造体に含まれますが、途中で変更可能です。パレットを変更する場合にはLISTチャンクmoviに??pcというチャンクを入れます。このチャンクはAVIINDEXENTRY構造体です。
MainAVIHeader構造体のdwFlagsメンバにAVIF_HASINDEX(0x00000010)およびAVIF_MUSTUSEINDEX(0x00000020)がない場合にはチャンクの物理的な順番に従って再生します。しかしidx1チャンクがある場合にはAVIINDEXENTRY構造体の順番に従って再生します。通常のAVIファイルにはidx1チャンクがあります。
AVIファイルにはこの他のチャンクがある場合があります。再生プログラムは未知のチャンクを読み飛ばします。また編集プログラムやビデオキャプチャプログラムは独自のチャンクを挿入することができます。またRIFFチャンクの後ろに別のデータが付加されている場合もあります。
RIFFのファイル全体を占めるRIFFチャンクのサイズが32ビットという構造上、AVIファイルではどうしても4GBを超えるファイルを作ることができません。しかし実際にはMCIなどでは1GBを超えるファイルを扱えないようです。VFWのAVI APIでも2GBを超えることができません。ソフトウェアによっては4GBまで扱える場合もあると思いますが、結局は4GBが限界です。1GBは無圧縮で320×240のフルカラー、1秒あたり15フレームの映像で約5分です。
そこでAVI2という新しいフォーマットが考案されました。AVI2はRIFFチャンクの後ろにもう1つRIFFチャンクがあるような構造です。単純にAVIファイルを2つ合わせただけではないのですが、それに近い構造です。オーディオストリームが1つとビデオストリームが1つの基本的なAVI2ファイルは図4のようになります。
図4.AVI2ファイルの例
RIFF 'AVI '
LIST 'hdrl'
avih (AVIMainHeader構造体)
LIST 'strl'
strh (AVIStreamHeader構造体)
strf (BITMAPINFO構造体)
strn (オプションヘッタ)
indx (AVISUPERINDEX構造体)
LIST 'strl'
strh (AVIStreamHeader構造体)
strf (WAVEFORMAT構造体)
strn (オプションヘッタ)
indx (AVISUPERINDEX構造体)
LIST 'odml'
dmlh (AVIEXTHEADER構造体)
JUNK (2048バイト境界にアライメントするためのダミーデータ)
LIST 'movi'
00db (DIBデータ)
00db (DIBデータ)
00db (DIBデータ)
01wb (WAVEデータ)
01wb (WAVEデータ)
01wb (WAVEデータ)
ix00 (AVISTDINDEX構造体またはAVIFIELDINDEX構造体)
ix01 (AVISTDINDEX構造体またはAVIFIELDINDEX構造体)
idx1 (AVIINDEXENTRY構造体×movi内のチャンク数)
RIFF 'AVIX'
JUNK (2048バイト境界にアライメントするためのダミーデータ)
LIST 'movi'
00db (DIBデータ)
00db (DIBデータ)
00db (DIBデータ)
01wb (WAVEデータ)
01wb (WAVEデータ)
01wb (WAVEデータ)
ix00 (AVISTDINDEX構造体またはAVIFIELDINDEX構造体)
ix01 (AVISTDINDEX構造体またはAVIFIELDINDEX構造体)
新しく増えたチャンクはindxとix??、LISTチャンクodmlです。AVI2に対応していないプログラムではこれらは無視され先頭のRIFFチャンクだけが有効です。したがってAVI2は従来のAVIファイルと上位互換性があります。
indxチャンクはインデックスの位置を示すためのインデックスです。AVISUPERINDEX構造体のメンバのqwOffsetはix??チャンクのID(ix??)へのファイルオフセットを64ビットで示します。
ix??チャンクはチャンクのデータを示します。idx1とはことなりファイルの先頭からのオフセット値です。またチャンクのIDではなくデータへのオフセットです。通常はAVISTDINDEX構造体が使われますが、一部のビデオ圧縮ではAVIFIELDINDEX構造体が使われる場合もあります。
LISTチャンクodmlにはdmlhチャンクがあります。このチャンクはファイル全体のフレーム数を示します。
なお、indxとix??はセクタサイズの倍数になります。AVI2のサンプルとしてTEST2.AVIを作りましたので参考にして下さい。