画像ファイル(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」が生成されます。