動画ファイルからビットマップを取得・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を直接書き換えられない場合への対処を追加。