はじめに
前回
古い方法で連続したビットマップをAVIファイルにする方法が古くて今は使えないのを記事にした
今回はMediaFoundationを使用して動画ファイルmp4から静止画を取り出す方法をやってみたいと思う
MediaFoundationとは
20年ぐらいにあったDirectShowの後継だそうです
※記事を書きながら調べてプログラムを書いてる
APIの数も多くて複雑なのでDelphiから使えるようになっているMfPackからソースファイルをダウンロードして使うのが便利そうです
サンプル
フォームにボタン、エディット、スクロールバー、イメージを配置して、ボタンを押すと動画ファイルを読み込んでスクロールバーでシークしたフレームの映像をイメージにコピーします
unit MainForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs,FileCapture, Vcl.StdCtrls,System.TimeSpan,
Vcl.ExtCtrls;
type
TFormMain = class(TForm)
Button1: TButton;
Edit1: TEdit;
ScrollBar1: TScrollBar;
Image1: TImage;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure ScrollBar1Change(Sender: TObject);
private
{ Private 宣言 }
FCapture : TFileCapture;
FFrequency : int64;
FCaptureStart : int64;
procedure VideoOpen(const Filename : string);
procedure VideoFrameRequest(const Position : Integer);
procedure OnFrameFound(ABitmap: TBitmap;ATimeStamp: TTimeSpan);
public
{ Public 宣言 }
end;
var
FormMain: TFormMain;
implementation
uses System.Math,WinApi.ComBaseApi,WinApi.ActiveX.ObjBase,FileCapture.Asynchronous,
FileCapture.Synchronous, WinApi.MediaFoundationApi.MfApi,
WinApi.MediaFoundationApi.MfUtils;
{$R *.dfm}
procedure TFormMain.FormCreate(Sender: TObject);
begin
CoInitializeEx(Nil,
COINIT_APARTMENTTHREADED);
FCapture := TFileCaptureASync.Create;
FCapture.OnFrameFound := OnFrameFound;
MFStartup(MF_VERSION, 0);
end;
procedure TFormMain.FormDestroy(Sender: TObject);
begin
FCapture.Free;
end;
procedure TFormMain.OnFrameFound(ABitmap: TBitmap; ATimeStamp: TTimeSpan);
var
iCaptureEnd: int64;
begin
QueryPerformanceCounter(iCaptureEnd);
try
Image1.Picture.Bitmap.Assign(ABitmap);
finally
FreeAndNil(ABitmap);
end;
end;
procedure TFormMain.ScrollBar1Change(Sender: TObject);
begin
VideoFrameRequest(ScrollBar1.Position);
end;
procedure TFormMain.VideoFrameRequest(const Position: Integer);
var
span: TTimeSpan;
begin
if FCapture.SourceOpen then
begin
span := TTimeSpan.Create(0, 0,Position);
QueryPerformanceFrequency(FFrequency);
QueryPerformanceCounter(FCaptureStart);
FCapture.MaxFramesToSkip := 200;
FCapture.Accuracy := 120;
FCapture.RequestFrame(span);
end;
end;
procedure TFormMain.VideoOpen(const Filename : string);
var
oPreviousRounding: TRoundingMode;
f : Boolean;
begin
if not FileExists(Filename) then exit;
f := FCapture.OpenSource(Filename);
if f then begin
Edit1.Text := FCapture.URL;
ScrollBar1.Position := 0;
oPreviousRounding := GetRoundMode;
try
SetRoundMode(rmDown);
ScrollBar1.Max := Round(FCapture.Duration.TotalSeconds);
finally
SetRoundMode(oPreviousRounding);
end;
end;
end;
procedure TFormMain.Button1Click(Sender: TObject);
begin
// 動画ファイルをフルパスで指定
VideoOpen('');
end;
end.
注意点
あまり巨大なファイルを使うとメモリ消費がすごくて落ちるかもしれません、それとシークを繰り返す度にメモリが消費されます。
タスクバーなどでメモリ使用量を確認しながら動作確認してください
フレームの画像を要求する処理でメモリが解放されていないようです
MfPackのサンプルでも同じ現象になります
結論
メモリの問題を抜きにしても1フレーム取り出すのに0.5秒かかっているので使うのは厳しい、MediaFoundation自体の再生機能はDirectShowの後継だけあってスムーズに再生できるので、再生とフレームの取り出しはまた違うのだろう
さらに別の方法が必要そうだ