ホーム >プログラム >Delphi 6 ローテクTips >ビットマップからAVIファイルを作成

ビットマップからAVIファイルを作成

何枚かのビットマップからAVIファイルを作成したいと思いました。
結構てこずるかと思ったものの意外とすんなりいけてしまいました。

参考サイト

VFWユニットを用意する

Delphi 6でAVIファイルを作るにはVFW SDKをDelphi用にコンバートしたユニットが必要になります。
で、参考サイトからダウンロードしたVFW.pasを使うのですが二点ほど注意点を。

  1. 「プロジェクトオプション」での「コンパイラ」タブの「実行時エラー」グループの「範囲チェック」にチェックが入っているといくつか「定数式が範囲を越えました」というエラーが出ます。
    これは呼び出すAPIの引数の型がUINTやLPCSTRなどの符号なしの整数で宣言されているのに-1を渡しているためです。
    それぞれのAPIの定義を見てUINTまたはLPCSTRでキャストすることで回避できます。
    function ICQueryAbout(hic: HIC): BOOL;
    begin
      //定数式が範囲を越えたというエラーが出るので-1をUINTでキャスト
      Result := ICSendMessage(hic, ICM_ABOUT, UINT(-1), ICMF_ABOUT_QUERY) = ICERR_OK;
    end;

    function ICQueryConfigure(hic: HIC): BOOL;
    begin
     //定数式が範囲を越えたというエラーが出るので-1をUINTでキャスト
      Result := ICSendMessage(hic, ICM_CONFIGURE, UINT(-1), ICMF_CONFIGURE_QUERY) = ICERR_OK;
    end;

    function MCIWndSaveDialog(hwnd: HWND): DWORD;
    begin
      //定数式が範囲を越えたというエラーが出るので-1をLPCSTRでキャスト
      Result := MCIWndSave(hwnd, LPCSTR(-1));
    end;

    function MCIWndOpenDialog(hwnd: HWND): DWORD;
    begin
     //定数式が範囲を越えたというエラーが出るので-1をLPCSTRでキャスト
      Result := MCIWndOpen(hwnd, LPCSTR(-1), False);
    end;
    ICQueryAboutとICQueryConfigure、MCIWndSaveDialog、MCIWndOpenDialogの四つの関数を修正します。
  2. VFW.pasはOle2ユニットを使用するのですがDelphiのデフォルトの設定ではこのユニットはライブラリパスの通ったフォルダに含まれないので「ファイル'Ole2.dcu'が見つかりません」というエラーが出ます。
    これはOle2.pas(デフォルトではC:\Program Files\Borland\Delphi6\Source\Rtl\Winにあります)をライブラリパスの通ったフォルダにコピーすることで回避できます。

これでVFWユニットの準備はOKです。

AVI作成

参考サイトのソースコードをDelphiにほぼそのまま移植しただけのものですが以下のソースコードで。

function gfniRoundUp(fNum: Extended): Int64;
//fNumを切り上げて返す
begin
  if (fNum >= 0) then begin
    Result := Trunc(X);
    if (Frac(X) > 0) then begin
      Inc(Result);
    end;
  end else begin
   Result := Trunc(X);
    if (Frac(X) < 0) then begin
      Dec(Result);
    end;
  end;
end;
procedure SaveAvi(AImageList : TImageList; sFile : WideString; iRate : DWORD = 15; iScale : DWORD = 1);
//ビットマップから無圧縮AVIファイルを作成して保存する
//http://www.ecoop.net/coop/vfw/avi.html
//http://www.ecoop.net/coop/vfw/aviapi.html#S_AVISTREAMINFO

const
  lci_BITCOUNT = 24; //RGB24形式
//  lci_BITCOUNT = 32; //RGB32形式
var
  l_StreamInfo : TAviStreamInfoW;
  l_BmpHeader  : TBitmapInfoHeader;
  li_Size      : Int64;
  li_Mod       : Integer;
  lp_Buff      : PAnsiChar;
  i            : Integer;
  li_Width     : Integer;
  li_Height    : Integer;
  l_AviFile    : PAVIFILE;
  l_AviStream  : PAVISTREAM;
  l_Bitmap     : TBitmap;
  l_Stream     : TMemoryStream;
begin
  li_Width  := AImageList.Width;
  li_Height := AImageList.Height;

  li_Size := li_Width * (lci_BITCOUNT div 8); //RGB24 or RGB32形式
  li_Mod  := li_Size mod SizeOf(DWORD);
  if (li_Mod <> 0) then begin
    //DWORDにアライメントされているための調整
    // Inc(li_Size, SizeOf(DWORD) - li_Mod);
    li_Size := gfniRoundUp(li_Size / SizeOf(DWORD)) * SizeOf(DWORD);
  end;
  li_Size := li_Height * li_Size;

  FillChar(l_StreamInfo, SizeOf(l_StreamInfo), 0);
  with l_StreamInfo do begin
    fccType               := streamtypeVIDEO;
    fccHandler            := comptypeDIB;
    dwFlags               := 0;                // Contains AVITF_* flags
    dwCaps                := 0;
    wPriority             := 0;
    wLanguage             := 0;
    dwScale               := iScale;
    dwRate                := iRate;            // dwRate / dwScale = フレームレート
    dwStart               := 0;
    dwLength              := AImageList.Count; // In units above...
    dwInitialFrames       := 0;
    dwSuggestedBufferSize := 0;
    dwQuality             := DWORD(-1);
    dwSampleSize          := 0;
    rcFrame               := Rect(0, 0, li_Width, li_Height);
    dwEditCount           := 0;
    dwFormatChangeCount   := 0;
    szName[0] := '#';
    szName[1] := 'V';
    szName[2] := 'i';
    szName[3] := 'd';
    szName[4] := 'e';
    szName[5] := 'o';
    szName[6] := '1';
  end;

  FillChar(l_BmpHeader, SizeOf(l_BmpHeader), 0);
  with l_BmpHeader do begin
    biSize          := SizeOf(l_BmpHeader);
    biWidth         := li_Width;
    biHeight        := li_Height;
    biPlanes        := 1;
    biBitCount      := lci_BITCOUNT;
    biCompression   := BI_RGB;
    biSizeImage     := 0;
    biXPelsPerMeter := 0;
    biYPelsPerMeter := 0;
    biClrUsed       := 0;
    biClrImportant  := 0;
  end;

  //初期化
  AVIFileInit;
  try
    //ファイルを開く
    if (AVIFileOpenW(l_AviFile, PWideChar(sFile), OF_CREATE or OF_WRITE or OF_SHARE_DENY_NONE, nil) <> 0) then begin
      Exit;
    end;
    //ストリームを開く
    if (AVIFileCreateStreamW(l_AviFile, l_AviStream, @l_StreamInfo) <> 0) then begin
      Exit;
    end;
    //フォーマットをセット
    if (AVIStreamSetFormat(l_AviStream, 0, @l_BmpHeader, SizeOf(l_BmpHeader)) <> 0) then begin
      Exit;
    end;
    l_Bitmap := TBitmap.Create;
    l_Stream := TMemoryStream.Create;
    GetMem(lp_Buff, li_Size);
    try
      l_Bitmap.HandleType := bmDDB;
      if (lci_BITCOUNT = 24) then begin
        l_Bitmap.PixelFormat := pf24bit;
      end else begin
        l_Bitmap.PixelFormat := pf32bit;
      end;
      l_Bitmap.Width  := AImageList.Width;
      l_Bitmap.Height := AImageList.Height;
      for i := 0 to AImageList.Count-1 do begin
        AImageList.GetBitmap(i, l_Bitmap); //この中でビットマップの幅と高さがセットされる
        l_Stream.Position := 0;
        l_Bitmap.SaveToStream(l_Stream);
        l_Stream.Position := SizeOf(TBitmapFileHeader) + SizeOf(l_BmpHeader);
        l_Stream.Read(lp_Buff^, li_Size);
        //ストリームにビットマップを書き込む
        if (AVIStreamWrite(l_AviStream, i, 1, lp_Buff, li_Size, AVIIF_KEYFRAME, nil, nil) <> 0) then begin
          Exit;
        end;
      end;
    finally
      FreeMem(lp_Buff);
      l_Stream.Free;
      l_Bitmap.Free;
    end;
  finally
    AVIStreamRelease(l_AviStream); //ストリームを閉じる
    AVIFileRelease(l_AviFile);     //ファイルを閉じる
    AVIFileExit;                   //終了処理
  end;
end;

この例では書き出すビットマップの指定はイメージリストを利用していますが大きなサイズで枚数が多い場合はイメージリストでエラーが起きてしまいます。
なのでエラーが起きる場合はTListなどを使う必要があります。

フレームレート

TAviStreamInfoWのdwScaleとdwRateの値で出力AVIのフレームレートが決まるようです。
dwRate÷dwScale=フレームレートという関係のようで、dwRateがフレームレートの分子でdwScaleがフレームレート分母ということのようです。
私は2fpsとか10fpsあるいは15fpsといった切りのいいものしか必要としていないので単純にdwScaleを1にしてdwRateを2とか10とか15とかにしていますが、29.97fpsにしたい場合はdwRateを30000に、dwScaleを1001にするようです。
23.976fpsならdwRateを24000にdwScaleを1001、59.94fpsならdwRateを60000にdwScaleを1001にするようです。

注意点

動画のデコーダにffdshowを使っている場合でコーデックの「Raw video」で「全サポート形式」か「全RGB型」、「RGB24」のどれかを選択している場合色化けして表示されてしまうようです。
そのような場合はffdshowの「Raw video」の設定を「無効」にすれば正常に表示されます。

それができない場合無圧縮AVIをRGB24形式ではなくRGB32形式で保存するという方法もあります。
上記SaveAvi手続きのlci_BITCOUNTを24から32に変更することで可能です。

ちなみにffdshowの「Raw video」の設定を「全サポート形式」にした場合はRGB24形式の無圧縮AVIだけでなく通常のBMPファイルも色化けするのでffdshow側の問題のような気がします。


2011-08-25:注意点を追記。