MediaFoundationAPIをMfPackを通じてDelphiから使ってみる

# はじめに
前回

https://qiita.com/vram/items/7de76dc7681cbba02d8d

古い方法で連続したビットマップをAVIファイルにする方法が古くて今は使えないのを記事にした

今回は**MediaFoundation**を使用して動画ファイルmp4から静止画を取り出す方法をやってみたいと思う

# MediaFoundationとは
20年ぐらいにあったDirectShowの後継だそうです
※記事を書きながら調べてプログラムを書いてる
APIの数も多くて複雑なのでDelphiから使えるようになっている**MfPack**からソースファイルをダウンロードして使うのが便利そうです

https://github.com/FactoryXCode/MfPack

# サンプル
フォームにボタン、エディット、スクロールバー、イメージを配置して、ボタンを押すと動画ファイルを読み込んでスクロールバーでシークしたフレームの映像をイメージにコピーします

~~~sample.pas
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の後継だけあってスムーズに再生できるので、再生とフレームの取り出しはまた違うのだろう

さらに別の方法が必要そうだ