ホーム >プログラム >Delphi 6 ローテクTips >動画ファイルからビットマップを取得・IBasicVideo版

動画ファイルからビットマップを取得・IBasicVideo版

動画ファイルのビデオ映像をビットマップとして取得したいと思いました。
参考サイトを頼りに割りとすんなりいけてしまいましたが、色々試していくうちにデコーダとスプリッタの組み合わせによってうまくいったりいかなかったりすることが分かりました。


参考サイト


IBasicVideoインターフェース

IBasicVideoインターフェースでビットマップを取得するのはIMediaDetインターフェースでビットマップを取得するのと同じくビットマップのファイルヘッダーを作る部分が面倒なだけでそれほど難しくありません。
またフレームレートを取得する場合やアスペクト比を保持する場合などと違い、四種類のビデオレンダラー(旧ビデオレンダラー、オーバーレイミキサー、VMR7、VMR9)で同じやり方で取得できます。

注意点

IBasicVideoインターフェースを使ってビットマップを取得するにはDirectShow9.pasのソースコードを書き換えないとなりません。
コメントにもあるようにIBasicVideo.GetCurrentImageの宣言を書き換えます。
第二引数の'var pDIBImage'となっているものを'pDIBImage: Pointer'とします。
GetCurrentImageは必要なビットマップのバッファのサイズを得るのに第二引数にnilをセットするのですが、それをするためにはDirectShow9.pasの宣言ではできません。

{
DirectShow9.pasのIBasicVideo.GetCurrentImageの宣言を↓のように書き換える必要あり。
// function GetCurrentImage(var BufferSize: Longint; var pDIBImage): HResult; stdcall;
function GetCurrentImage(var BufferSize: Longint; pDIBImage: Pointer): HResult; stdcall;
}
    if not(Succeeded(l_BasicVideo.GetCurrentImage(li_BuffSize, nil))) then begin
      Exit;
    end;
    GetMem(lp_Buff, li_BuffSize);
    try
      //BitmapInfoHeader+ビットマップ本体がバッファにコピーされる。
      li_Ret := l_BasicVideo.GetCurrentImage(li_BuffSize, lp_Buff);
      if not(Succeeded(li_Ret)) then begin
        Exit;
      end;

またXE2のスターター版などではソースコードが付属してこないのでDirectShow9.pasを書き換えることはできないので直接書き換えるのではなく、GetCurrentImageを使用するunitに以下のコードを書き加えてこちらを優先使用してもらうようにします。

// DirectShow9.pas から ---
type
  {$HPPEMIT 'typedef System::DelphiInterface<IBasicVideo> _di_IBasicVideo;'}
  {$EXTERNALSYM IBasicVideo}
  IBasicVideo = interface(IDispatch)
    ['{56A868B5-0AD4-11CE-B03A-0020AF0BA770}']
    (*** IBasicVideo methods ***)
    function get_AvgTimePerFrame(out pAvgTimePerFrame: TRefTime): HResult; stdcall;
    function get_BitRate(out pBitRate: Longint): HResult; stdcall;
    function get_BitErrorRate(out pBitErrorRate: Longint): HResult; stdcall;
    function get_VideoWidth(out pVideoWidth: Longint): HResult; stdcall;
    function get_VideoHeight(out pVideoHeight: Longint): HResult; stdcall;
    function put_SourceLeft(SourceLeft: Longint): HResult; stdcall;
    function get_SourceLeft(out pSourceLeft: Longint): HResult; stdcall;
    function put_SourceWidth(SourceWidth: Longint): HResult; stdcall;
    function get_SourceWidth(out pSourceWidth: Longint): HResult; stdcall;
    function put_SourceTop(SourceTop: Longint): HResult; stdcall;
    function get_SourceTop(out pSourceTop: Longint): HResult; stdcall;
    function put_SourceHeight(SourceHeight: Longint): HResult; stdcall;
    function get_SourceHeight(out pSourceHeight: Longint): HResult; stdcall;
    function put_DestinationLeft(DestinationLeft: Longint): HResult; stdcall;
    function get_DestinationLeft(out pDestinationLeft: Longint): HResult; stdcall;
    function put_DestinationWidth(DestinationWidth: Longint): HResult; stdcall;
    function get_DestinationWidth(out pDestinationWidth: Longint): HResult; stdcall;
    function put_DestinationTop(DestinationTop: Longint): HResult; stdcall;
    function get_DestinationTop(out pDestinationTop: Longint): HResult; stdcall;
    function put_DestinationHeight(DestinationHeight: Longint): HResult; stdcall;
    function get_DestinationHeight(out pDestinationHeight: Longint): HResult; stdcall;
    function SetSourcePosition(Left, Top, Width, Height: Longint): HResult; stdcall;
    function GetSourcePosition(out pLeft, pTop, pWidth, pHeight: Longint): HResult; stdcall;
    function SetDefaultSourcePosition: HResult; stdcall;
    function SetDestinationPosition(Left, Top, Width, Height: Longint): HResult; stdcall;
    function GetDestinationPosition(out pLeft, pTop, pWidth, pHeight: Longint): HResult; stdcall;
    function SetDefaultDestinationPosition: HResult; stdcall;
    function GetVideoSize(out pWidth, Height: Longint): HResult; stdcall;
    function GetVideoPaletteEntries(StartIndex, Entries: Longint;
        out pRetrieved: Longint; out pPalette): HResult; stdcall;
//イメージのサイズを取得するため第二引数にnilをセットできるように書き換え
//    function GetCurrentImage(var BufferSize: Longint; var pDIBImage): HResult; stdcall;
    function GetCurrentImage(var BufferSize: Longint; pDIBImage: Pointer): HResult; stdcall;
    function IsUsingDefaultSource: HResult; stdcall;
    function IsUsingDefaultDestination: HResult; stdcall;
  end;

関数化

下の例では引数でビデオレンダラの種類を指定できるようにしています。
そのためにTMyVMRModeという列挙型を宣言しています。
デコーダとスプリッタの組み合わせによってビデオレンダラの種類によってうまくいかないこともあるのでこのようにビデオレンダラを指定できるようにしました。
ABitmapはこの関数を呼ぶ側で作成・破棄しなければなりません。

// DirectShow9.pas から ---
//DirectShow9.pasを直接書き換えられない場合GetCurrentImageを使用するunitに以下のコードを書き加えてこちらを優先使用するようにする

type
  {$HPPEMIT 'typedef System::DelphiInterface<IBasicVideo> _di_IBasicVideo;'}
  {$EXTERNALSYM IBasicVideo}
  IBasicVideo = interface(IDispatch)
    ['{56A868B5-0AD4-11CE-B03A-0020AF0BA770}']
    (*** IBasicVideo methods ***)
    function get_AvgTimePerFrame(out pAvgTimePerFrame: TRefTime): HResult; stdcall;
    function get_BitRate(out pBitRate: Longint): HResult; stdcall;
    function get_BitErrorRate(out pBitErrorRate: Longint): HResult; stdcall;
    function get_VideoWidth(out pVideoWidth: Longint): HResult; stdcall;
    function get_VideoHeight(out pVideoHeight: Longint): HResult; stdcall;
    function put_SourceLeft(SourceLeft: Longint): HResult; stdcall;
    function get_SourceLeft(out pSourceLeft: Longint): HResult; stdcall;
    function put_SourceWidth(SourceWidth: Longint): HResult; stdcall;
    function get_SourceWidth(out pSourceWidth: Longint): HResult; stdcall;
    function put_SourceTop(SourceTop: Longint): HResult; stdcall;
    function get_SourceTop(out pSourceTop: Longint): HResult; stdcall;
    function put_SourceHeight(SourceHeight: Longint): HResult; stdcall;
    function get_SourceHeight(out pSourceHeight: Longint): HResult; stdcall;
    function put_DestinationLeft(DestinationLeft: Longint): HResult; stdcall;
    function get_DestinationLeft(out pDestinationLeft: Longint): HResult; stdcall;
    function put_DestinationWidth(DestinationWidth: Longint): HResult; stdcall;
    function get_DestinationWidth(out pDestinationWidth: Longint): HResult; stdcall;
    function put_DestinationTop(DestinationTop: Longint): HResult; stdcall;
    function get_DestinationTop(out pDestinationTop: Longint): HResult; stdcall;
    function put_DestinationHeight(DestinationHeight: Longint): HResult; stdcall;
    function get_DestinationHeight(out pDestinationHeight: Longint): HResult; stdcall;
    function SetSourcePosition(Left, Top, Width, Height: Longint): HResult; stdcall;
    function GetSourcePosition(out pLeft, pTop, pWidth, pHeight: Longint): HResult; stdcall;
    function SetDefaultSourcePosition: HResult; stdcall;
    function SetDestinationPosition(Left, Top, Width, Height: Longint): HResult; stdcall;
    function GetDestinationPosition(out pLeft, pTop, pWidth, pHeight: Longint): HResult; stdcall;
    function SetDefaultDestinationPosition: HResult; stdcall;
    function GetVideoSize(out pWidth, Height: Longint): HResult; stdcall;
    function GetVideoPaletteEntries(StartIndex, Entries: Longint;
        out pRetrieved: Longint; out pPalette): HResult; stdcall;
//イメージのサイズを取得するため第二引数にnilをセットできるように書き換え
//    function GetCurrentImage(var BufferSize: Longint; var pDIBImage): HResult; stdcall;
    function GetCurrentImage(var BufferSize: Longint; pDIBImage: Pointer): HResult; stdcall;
    function IsUsingDefaultSource: HResult; stdcall;
    function IsUsingDefaultDestination: HResult; stdcall;
  end;
//-------------------------


type
  TMyVMRMode = (vmOld, vmVMR7, vmVMR9);

function gfnbBmpFromMediaBasicVideo(ABitmap : TBitmap; sFileName : WideString; fTime : Double = 0; vmMode : TMyVMRMode = vmVMR9) : Boolean;
{
http://www.geekpage.jp/programming/directshow/getcurrentimage.php
http://logsoku.com/thread/pc2.2ch.net/tech/1026666092/
http://forum.4programmers.net/Delphi_Pascal/123438-Delphi_IBasicVideo.GetCurrentImage_-_do_TBitmap
}

var
  li_Ret          : HResult;
  l_GraphBuilder  : IGraphBuilder;
  l_VideoRenderer : IBaseFilter;
  l_MediaControl  : IMediaControl;
  l_MediaPosition : IMediaPosition;
  l_BasicVideo    : IBasicVideo;
  l_VideoWindow   : IVideoWindow;

  lp_Buff            : PByte;
  li_BuffSize        : Longint;
  l_Stream           : TMemoryStream;
  l_BitmapFileHeader : TBitmapFileHeader;
  lp_BimapInfoHeader : PBitmapInfoHeader;
  li_Palette         : DWORD;
begin
  Result := False;

  //グラフ作成
  li_Ret := CoCreateInstance(
    CLSID_FilterGraph,
    nil,
    CLSCTX_INPROC_SERVER,
    IID_IGraphBuilder,
    l_GraphBuilder
  );
  if not(Succeeded(li_Ret)) then begin
    Exit;
  end;

  try
    //ビデオレンダラ作成
    case vmMode of
      vmOld
      :begin
        //旧ビデオレンダラ
        CoCreateInstance(
          CLSID_VideoRenderer,
          nil,
          CLSCTX_INPROC,
          IID_IBaseFilter,
          l_VideoRenderer
        );
        l_GraphBuilder.AddFilter(l_VideoRenderer, 'Video Renderer');
      end;
      vmVMR7
      :begin
        //VMR7
        CoCreateInstance(
          CLSID_VideoMixingRenderer,
          nil,
          CLSCTX_INPROC,
          IID_IBaseFilter,
          l_VideoRenderer
        );
        l_GraphBuilder.AddFilter(l_VideoRenderer, 'Video Renderer');
      end;
      vmVMR9
      :begin
        //VMR9
        CoCreateInstance(
          CLSID_VideoMixingRenderer9,
          nil,
          CLSCTX_INPROC,
          IID_IBaseFilter,
          l_VideoRenderer
        );
        l_GraphBuilder.AddFilter(l_VideoRenderer, 'VMR9');
      end;
    end;

    //読み込み。
    if not(Succeeded(l_GraphBuilder.RenderFile(POLESTR(sFileName), nil))) then begin
      Exit;
    end;

    //非表示用。
    l_GraphBuilder.QueryInterface(IVideoWindow, l_VideoWindow);
    l_VideoWindow.put_AutoShow(False);
    l_VideoWindow := nil;
    //頭だし。
    l_GraphBuilder.QueryInterface(IMediaPosition, l_MediaPosition);
    l_MediaPosition.put_CurrentPosition(fTime);
    l_MediaPosition := nil;
    //ポーズ用。
    l_GraphBuilder.QueryInterface(IMediaControl, l_MediaControl);
    l_MediaControl.StopWhenReady;
    l_MediaControl := nil;
    //キャプチャ用。
    l_GraphBuilder.QueryInterface(IBasicVideo, l_BasicVideo);

    if not(Succeeded(l_BasicVideo.GetCurrentImage(li_BuffSize, nil))) then begin
      Exit;
    end;
    GetMem(lp_Buff, li_BuffSize);
    try
      //BitmapInfoHeader+ビットマップ本体がバッファにコピーされる。
      li_Ret := l_BasicVideo.GetCurrentImage(li_BuffSize, lp_Buff);
      if not(Succeeded(li_Ret)) then begin
        Exit;
      end;

      lp_BimapInfoHeader := PBitmapInfoHeader(lp_Buff);
      //TBitmapに読み込ませるにはBitmapFileHeaderが足りないので作成。
      FillChar(l_BitmapFileHeader, SizeOf(l_BitmapFileHeader), 0);
      l_BitmapFileHeader.bfType := $4d42; //'MB'。リトルエンディアンなので'BM'ではない。
      l_BitmapFileHeader.bfSize := SizeOf(l_BitmapFileHeader) + li_BuffSize;
      //IMediaDetと違いRGB24に決めうちではないかも知れないのでパレット分も計算する。
      if  (lp_BimapInfoHeader.biClrUsed = 0)
      and (lp_BimapInfoHeader.biBitCount <= 8)
      then begin
        li_Palette := (1 shl lp_BimapInfoHeader.biBitCount)
      end else begin
        li_Palette := lp_BimapInfoHeader.biClrUsed;
      end;
      l_BitmapFileHeader.bfOffBits := SizeOf(l_BitmapFileHeader) + lp_BimapInfoHeader.biSize + (li_Palette * SizeOf(TRGBQuad));

      l_Stream := TMemoryStream.Create;
      try
        //ストリームにBitmapFileHeaderを書き込み。
        l_Stream.Write(l_BitmapFileHeader, Sizeof(l_BitmapFileHeader));
        //BitmapInfoHeaderとビットマップ本体を書き込み。
        l_Stream.Write(lp_Buff^, li_BuffSize);
        l_Stream.Position := 0;
        ABitmap.LoadFromStream(l_Stream);
        Result := True;
      finally
        l_Stream.Free;
      end;
    finally
      FreeMem(lp_Buff);
    end;
  finally
    l_GraphBuilder  := nil;
    l_VideoRenderer := nil;
    l_BasicVideo    := nil;
  end;
end;

取得の流れ

グラフビルダーとビデオレンダラを作成してファイルを読み込みIMediaPositionインターフェースで再生位置をセットしてIBasicVideoインターフェースのGetCurrentImageで画像を取り出すという流れです。

そのままやるとビデオを表示する別ウィンドウが表示されてしまうのでIVideoWindowインターフェースのputAutoShowメソッドで別ウィンドウを表示しないようにします。
またIMediaPositionで再生位置をセットしただけではGetCurrentImageで取得されるビデオのイメージは更新されないのでIMediaControlインターフェースのStopWhenReadyメソッドを呼んで更新します。

あとはバッファに入っているビットマップをTBitmapに読み込ませるためにビットマップファイルヘッダーを作ってバッファと一緒にストリームに書き込み、それをTBitmapに読み込ませます。

最後にストリームやバッファ、使ったインターフェース、ビデオレンダラ、グラフビルダーの後始末をして終了です。


2013-03-11:DirectShow9.pasを直接書き換えられない場合への対処を追加。