トップへ(mam-mam.net/)

画像ファイル(BMPなど)から動画ファイル(MP4)を作成(MfPack使用) ~Delphiソースコード集

検索:

画像ファイル(BMPなど)から動画ファイル(MP4)を作成(MfPack使用) ~Delphiソースコード集

DelphiでMFPack(Microsoft Media Foundation APIが扱えるライブラリ)を使って画像ファイル(bmp,jpg,png)から動画ファイル(mp4,wmv,avi)を作成します

MfPackのダウンロードとインストール

MfPackのダウンロードとインストールは https://mam-mam.net/delphi/mfpack_install.html を参照してください。

新規アプリケーションの準備

Delphiを起動し 「ファイル」→「新規作成」→「VCLフォームアプリケーション - Delphi」を選択します。
右下のツールパレットから「TButton」をドラッグ&ドロップします。
「ファイル」→「すべて保存」を押し、プロジェクト用フォルダを作成して、ユニット「Unit1.pas」とプロジェクトファイル「Project1.dproj」を保存します。

ユニットの作成

「ファイル」→「新規作成」→「ユニット -Delphi」をクリックしてユニットを作成します。
以下ソースコードを貼り付けます。
「UMamSinkWriter.pas」として名前を付けて保存します。

unit UMamSinkWriter;

interface

uses
  {Winapi}
  Winapi.Windows,
  WinApi.ComBaseApi,
  WinApi.WinApiTypes,
  {system}
  System.Classes,
  System.SysUtils,
  {VCL}
  Vcl.Graphics,
  Vcl.Imaging.jpeg,
  Vcl.Imaging.pngimage,
  {ActiveX}
  WinApi.ActiveX.ObjBase,
  {MediaFoundationApi}
  WinApi.MediaFoundationApi.MfUtils,
  WinApi.MediaFoundationApi.MfApi,
  WinApi.MediaFoundationApi.MfReadWrite,
  WinApi.MediaFoundationApi.Mfobjects  ;

type
  TMamSinkWriter = class(TObject)
  private
    fImgList:TStringList;
    fWidth,fHeight:Cardinal;
    fFPS:UINT32;
    fExt:String;//出力ファイル拡張子(.wmvなど)
    function InitializeSinkWriter(
      FileName:String;out ppWriter:IMFSinkWriter;out pStreamIndex:DWORD
    ):HResult;
    function SetVideoFrameBuffer(
      Idx:Integer;var VideoFrameBuffer:array of DWORD):Boolean;
  public
    constructor Create();
    destructor Destroy(); override;
    procedure AddImageFile(FileName:string);
    procedure ClearImageFile();
    procedure WriteVideoFile(FileName:String);
    property Width:Cardinal read fWidth write fWidth;
    property Height:Cardinal read fHeight write fHeight;
    property FPS:UINT32 read fFPS write fFPS;
  end;

implementation

{ TMamSinkWriter }

procedure TMamSinkWriter.AddImageFile(FileName: string);
begin
  fImgList.Add(FileName);
end;

procedure TMamSinkWriter.ClearImageFile;
begin
  fImgList.Clear;
end;

constructor TMamSinkWriter.Create;
begin
  inherited;
  fImgList:=TStringList.Create;
  fWidth:=640;
  fHeight:=480;
  fFPS:=30;
end;


destructor TMamSinkWriter.Destroy;
begin
  fImgList.Free;
  inherited;
end;

function TMamSinkWriter.InitializeSinkWriter(
  FileName:String; out ppWriter:IMFSinkWriter;out pStreamIndex:DWORD
): HResult;
var hr:HResult;
    pMediaTypeOut,pMediaTypeIn:IMFMediaType;
    //書き込む動画のフォーマットのGUID
    VIDEO_ENCODING_FORMAT:TGUID;
    VIDEO_BIT_RATE:UINT32;
begin
  //拡張子(ext)により出力エンコーディングを設定
  if fExt='.wmv' then
    VIDEO_ENCODING_FORMAT:=MFVideoFormat_WMV3
  else if fExt='.mp4' then
    VIDEO_ENCODING_FORMAT:=MFVideoFormat_H264
  else
    VIDEO_ENCODING_FORMAT:=MFVideoFormat_NV12;

  //■書き込むファイル名を設定
  hr:=MFCreateSinkWriterFromURL(
    PChar(FileName),nil,nil,ppWriter
  );

  //■出力のメディアタイプ
  if SUCCEEDED(hr) then
    hr:=MFCreateMediaType(pMediaTypeOut);
  if SUCCEEDED(hr) then
    hr:=pMediaTypeOut.SetGUID(MF_MT_MAJOR_TYPE,MFMediaType_Video);
  //出力エンコーディングの設定
  if SUCCEEDED(hr) then
    hr:=pMediaTypeOut.SetGUID(MF_MT_SUBTYPE,VIDEO_ENCODING_FORMAT);
  //★★出力ビットレートを適当に設定★★
  if SUCCEEDED(hr) then
  begin
    VIDEO_BIT_RATE:=fWidth*fHeight*4*fFPS div 16;
    hr:=pMediaTypeOut.SetUINT32(MF_MT_AVG_BITRATE,VIDEO_BIT_RATE);
  end;
  //インターレースモードをプログレッシブに設定
  if SUCCEEDED(hr) then
    hr:=pMediaTypeOut.SetUINT32(MF_MT_INTERLACE_MODE,MFVideoInterlace_Progressive);
  //出力時の幅と高さを設定
  if SUCCEEDED(hr) then
    hr:=MFSetAttributeSize(pMediaTypeOut,MF_MT_FRAME_SIZE,fWidth,fHeight);
  //出力時のフレームレート(FPS)を設定
  if SUCCEEDED(hr) then
    hr:=MFSetAttributeRatio(pMediaTypeOut,MF_MT_FRAME_RATE,fFPS,1);
  //ピクセル比率の設定
  if SUCCEEDED(hr) then
    hr:=MFSetAttributeRatio(pMediaTypeOut,MF_MT_PIXEL_ASPECT_RATIO,1,1);
  if SUCCEEDED(hr) then
    hr:=ppWriter.AddStream(pMediaTypeOut,pStreamIndex);

  //■入力のメディアタイプ
  if SUCCEEDED(hr) then
    hr:=MFCreateMediaType(pMediaTypeIn);
  if SUCCEEDED(hr) then
    hr:=pMediaTypeIn.SetGUID(MF_MT_MAJOR_TYPE,MFMediaType_Video);
  if SUCCEEDED(hr) then
    hr:=pMediaTypeIn.SetGUID(MF_MT_SUBTYPE,MFVideoFormat_RGB32);
  if SUCCEEDED(hr) then
    hr:=pMediaTypeIn.SetUINT32(MF_MT_INTERLACE_MODE,MFVideoInterlace_Progressive);
  if SUCCEEDED(hr) then
    hr:=MFSetAttributeSize(pMediaTypeIn,MF_MT_FRAME_SIZE,fWidth,fHeight);
  if SUCCEEDED(hr) then
    hr:=MFSetAttributeRatio(pMediaTypeIn,MF_MT_FRAME_RATE,fFPS,1);
  if SUCCEEDED(hr) then
    hr:=MFSetAttributeRatio(pMediaTypeIn,MF_MT_PIXEL_ASPECT_RATIO,1,1);
  if SUCCEEDED(hr) then
    hr:=ppWriter.SetInputMediaType(pStreamIndex,pMediaTypeIn,nil);

  //開始する
  if SUCCEEDED(hr) then
    hr:=ppWriter.BeginWriting();

  SafeRelease(pMediaTypeOut);
  SafeRelease(pMediaTypeIn);
  result:=hr;
end;


function TMamSinkWriter.SetVideoFrameBuffer(
  Idx: Integer;var VideoFrameBuffer: array of DWORD):boolean;
type
    TRGBAArr=array[0..30000] of DWORD;
    PRGBAArr=^TRGBAArr;
var sbmp,dbmp:TBitmap;
    jpg:TJpegImage;
    png:TPngImage;
    ext:String;
    h:UINT64;
    rgba:PRGBAArr;
begin
  result:=False;
  if not FileExists(fImgList[Idx]) then exit;
  ext:=LowerCase(ExtractFileExt(fImgList[Idx]));
  if (ext<>'.bmp') and (ext='.jpg') and
     (ext='.jpeg') and (ext='.png') then
  begin
    exit;
  end;

  sbmp:=TBitmap.Create;
  try
    if ext='.bmp' then
    begin
      sbmp.LoadFromFile(fImgList[Idx]);
    end
    else if (ext='.jpg') or (ext='.jpeg') then
    begin
      jpg:=TJpegImage.Create;
      try
        jpg.LoadFromFile(fImgList[Idx]);
        sbmp.Assign(jpg);
      finally
        jpg.Free;
      end;
    end
    else if ext='.png' then
    begin
      png:=TPngImage.Create;
      try
        png.LoadFromFile(fImgList[Idx]);
        sbmp.Assign(png);
      finally
        png.Free;
      end;
    end;

    dbmp:=TBitmap.Create;
    try
      dbmp.Width:=fWidth;
      dbmp.Height:=fHeight;
      dbmp.PixelFormat:=pf32bit;
      dbmp.Canvas.StretchDraw( Rect(0,0,fWidth,fHeight), sbmp );
      if fExt='.wmv' then
      begin
        for h := 0 to fHeight-1 do
        begin
          //wmvは通常に入れる必要がある
          rgba:=dbmp.ScanLine[h];
          Move(rgba[0],VideoFrameBuffer[h*fWidth],fWidth*4);
        end;
      end
      else
      begin
        for h := 0 to fHeight-1 do
        begin
          //mp4 aviは上下逆に入れる必要がある
          rgba:=dbmp.ScanLine[fHeight-h-1];
          Move(rgba[0],VideoFrameBuffer[h*fWidth],fWidth*4);
        end;
      end;
    finally
      dbmp.Free;
    end;
  finally
    sbmp.Free;
  end;
end;


procedure TMamSinkWriter.WriteVideoFile(FileName:String);
var VideoFrameBuffer: array of DWORD;
    pStreamIndex: DWORD;
    pSinkWriter: IMFSinkWriter;
    rtStart: HNSTIME;//INT64
    hr:HResult;
    i:Integer;
    pBuffer: IMFMediaBuffer;
    pData: PByte;
    pSample: IMFSample;
begin
  SetLength(VideoFrameBuffer,fWidth*fHeight);
  ZeroMemory(@VideoFrameBuffer[0],fWidth*fHeight*4);

  fExt:=LowerCase(System.SysUtils.ExtractFileExt(FileName));
  if (fExt<>'.mp4') and (fExt<>'.wmv') and (fExt<>'.avi') then fExt:='.wmv';

  rtStart:=0;

  hr := CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
  if SUCCEEDED(hr) then
  begin
    hr := MFStartup(MF_VERSION);
    if SUCCEEDED(hr) then
    begin
      hr:=InitializeSinkWriter(FileName,pSinkWriter,pStreamIndex);
      if SUCCEEDED(hr) then
      begin
        for i := 0 to fImgList.Count-1 do
        begin
          SetVideoFrameBuffer(i,VideoFrameBuffer);
          hr:=MFCreateMemoryBuffer(4*fWidth*fHeight,pBuffer);
          if SUCCEEDED(hr) then
          begin
            hr:=pBuffer.Lock(pData,nil,nil);
            if SUCCEEDED(hr) then
            begin
              hr:=MFCopyImage(
                pData,    //コピー先バッファ
                4*fWidth, //コピー先ストライド(ずらす)バイト数
                PByte(VideoFrameBuffer),//コピー元バッファ
                4*fWidth, //コピー元ストライド(ずらす)バイト数
                4*fWidth, //幅のバイト数
                fHeight   //高さ
              );
            end;
            if Assigned(pBuffer) then
              pBuffer.Unlock();
            if SUCCEEDED(hr) then
              hr:=pBuffer.SetCurrentLength(4*fWidth*fHeight);
            if SUCCEEDED(hr) then
              hr:=MFCreateSample(pSample);
            if SUCCEEDED(hr) then
              hr:=pSample.AddBuffer(pBuffer);
            //プレゼンテーション時間 (100 ナノ秒単位)
            if SUCCEEDED(hr) then
              hr:=pSample.SetSampleTime(rtStart);
            //100ナノ秒単位
            if SUCCEEDED(hr) then
              hr:=pSample.SetSampleDuration(10 * 1000 * 1000 div fFPS);
            if SUCCEEDED(hr) then
              hr:=pSinkWriter.WriteSample(pStreamIndex,pSample);
            inc(rtStart,10 * 1000 * 1000 div fFPS);
            SafeRelease(pBuffer);
            SafeRelease(pSample);
          end;
        end;
        pSinkWriter.Finalize;
        SafeRelease(pSinkWriter);
      end;
      MFShutdown();
    end;
    CoUninitialize();
  end;
end;


end.

Button1クリック時のソースコードを記述

「Unit1」のデザインを表示して「Button1」をダブルクリックして以下のようなソースコードを記述します。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses UMamSinkWriter;

procedure TForm1.Button1Click(Sender: TObject);
var sw:TMamSinkWriter;
begin
  sw:=TMamSinkWriter.Create;
  try
    //動画にしたい画像ファイル指定します
    sw.AddImageFile('..\..\01.jpg');
    sw.AddImageFile('..\..\02.jpg');
    sw.AddImageFile('..\..\03.jpg');
    sw.AddImageFile('..\..\04.jpg');
    sw.AddImageFile('..\..\05.jpg');
    sw.AddImageFile('..\..\06.jpg');
    sw.AddImageFile('..\..\07.jpg');
    sw.AddImageFile('..\..\08.jpg');
    sw.AddImageFile('..\..\09.jpg');
    sw.AddImageFile('..\..\10.jpg');
    sw.AddImageFile('..\..\11.jpg');
    sw.AddImageFile('..\..\12.jpg');
    sw.AddImageFile('..\..\13.jpg');
    sw.AddImageFile('..\..\14.jpg');
    //動画の縦横サイズを指定します
    sw.Width:=640;
    sw.Height:=360;
    //1秒あたりのフレーム数を指定します
    sw.FPS:=4;
    //動画ファイル名を指定して作成開始
    sw.WriteVideoFile('test.mp4');
  finally
    sw.Free;
  end;
end;

end.

実行

実行して「Button1」をクリックすると、動画ファイル「test.mp4」が生成されます。