動画ファイルのフレームレートを取得
動画ファイルのフレームレートを取得したいと思いました。
調べてみるとビデオレンダラーの違いで二種類の取得方法ともう一つIMediaDetインターフェースを使う方法の三種類あることが分かりました。
XPでDirectShowを利用してビデオを再生する場合、特別なことをしなくても四種類のビデオレンダラーを利用できます。
旧ビデオレンダラーとオーバーレイミキサーとVMR7とVMR9です。
VMR7とVMR9は同じような方法でフレームレートを取得できます。
オーバーレイミキサーもVMR7などと同じやり方になりますが、こちらはビデオレンダラーから情報を取得するのではなくビデオレンダラーとデコーダの間に入るオーバーレイミキサー(フィルター)から情報を得る点が違ってきます。
このページではわざわざオーバーレイミキサーを使う必要というのもそうそうなかろうということでオーバーレイミキサーの場合の説明は省いています。
旧ビデオレンダラーはVMR7などとはだいぶ違うやり方になります。
VMR7やVMR9でやるよりも旧ビデオレンダラーで取得する方が簡単です。
またIMediaDetインターフェースを使う方法はVMR7などと似た感じなのですがグラフを作成せずIMediaDetインターフェースを作成するだけで済むので手軽です。
参考サイト
IMediaDetインターフェース
まずは割と手軽なIMediaDetインターフェースを使ったやり方から説明しますが、その前に一点。
フレームレートを取得するのに必要となる小数点以下を四捨五入する関数としてRoundがありますがDelphiのRound関数は数学で使われるような四捨五入とは少し違っています。
詳しくはヘルプをみてもらうとして普通の四捨五入をしたければ自作して使うことになります。
function _Round(fNum: Double): Integer;
//fNumを四捨五入して返す。
//Delphiのヘルプからコピー。
begin
if (fNum >= 0)
then begin
Result := Trunc(fNum + 0.5);
end else begin
Result := Trunc(fNum - 0.5);
end;
end;
IMediaDetインターフェースを利用してフレームレートを取得する方法はIMediaDetインターフェースで静止画を取得してファイルに保存する方法に似ています。
動画ファイル内の映像ストリームのメディアタイプの情報から取得、あるいは計算して取得します。
function GetFrameRateMediaDet(sFileName:
WideString) : Double;
//メディアのフレームレートを返す。
var
l_IMediaDet : IMediaDet;
l_MediaType : TAMMediaType;
i : Longint;
li_StreamCount : Longint;
li_Time : Int64;
begin
Result := 0;
if not(FileExists(sFileName))
then begin
Exit;
end;
try
CoCreateInstance(
CLSID_MediaDet,
nil,
CLSCTX_INPROC_SERVER,
IMediaDet,
l_IMediaDet
);
if not(Succeeded(l_IMediaDet.put_Filename(PWideChar(sFileName))))
then begin
Exit;
end;
l_IMediaDet.get_OutputStreams(li_StreamCount);
for i := 0
to li_StreamCount-1
do begin
//ストリーム指定。
l_IMediaDet.put_CurrentStream(i);
l_IMediaDet.get_StreamMediaType(l_MediaType);
try
if (IsEqualGUID(l_MediaType.majortype, MEDIATYPE_Video))
then begin
//ストリームがビデオの場合フレームレートを取得。
l_IMediaDet.get_FrameRate(Result);
if (Result = 0)
then begin
//フレームレートが0だったのでAvgTimePerFrameからフレームレートを算出。
if (IsEqualGUID(l_MediaType.formattype, FORMAT_VideoInfo))
then begin
//VIDEOINFOHEADER
li_Time := PVideoInfoHeader(l_MediaType.pbFormat)^.AvgTimePerFrame;
end else if (IsEqualGUID(l_MediaType.formattype, FORMAT_VideoInfo2))
then begin
//VIDEOINFOHEADER2
li_Time := PVideoInfoHeader2(l_MediaType.pbFormat)^.AvgTimePerFrame;
end else if (IsEqualGUID(l_MediaType.formattype, FORMAT_MPEGVideo))
then begin
//MPEG1VIDEOINFO
li_Time := PMpeg1VideoInfo(l_MediaType.pbFormat)^.hdr.AvgTimePerFrame;
end else if (IsEqualGUID(l_MediaType.formattype, FORMAT_MPEG2Video))
then begin
//MPEG2VIDEOINFO
li_Time := PMpeg2VideoInfo(l_MediaType.pbFormat)^.hdr.AvgTimePerFrame;
end else begin
li_Time := 0;
end;
if (li_Time <> 0)
then begin
//小数点二桁。
Result :=
_Round(100 / (li_Time / UNITS)) / 100;
end;
end;
//データが取れたのでループを抜ける。
Break;
end;
finally
FreeMediaType(@l_MediaType);
end;
end;
finally
if (l_IMediaDet <>
nil)
then begin
l_IMediaDet :=
nil;
end;
end;
end;
uses節にActiveXとDirectShow9を付け足す必要があります。
DirectShow9ユニットはDSPackの中に入っています。
- IMediaDetはCoCreateInstanceで作成します。
- put_Filenameで動画ファイルを指定します。
- get_OutputStreamsで動画ファイルにある映像と音声のストリームの数を取得します。
通常の動画ファイルなら、映像1音声1の計2になります。
動画ファイルのストリームの順番は決まっていないようなので映像ストリームかどうかはループを使って総当りで調べます。
- put_CurrentStreamに0から始まるストリームの番号をセットしてget_StreamMediaTypeでストリームのメディアタイプを取得します。
取得したメディアタイプのメジャータイプがMEDIATYPE_Videoならそのストリームが映像ストリームであると判断できます。
- その名もずばりなget_FrameRateメソッドでフレームレートを取得します。
- get_FrameRateで取得したフレームレートが0だったらメディアタイプのpbFormat中のAvgTimePerFrameから計算します。
AvgTimePerFrameというのはビデオのフレームの1枚当たりの表示時間で、単位は100ナノ秒です。
例えば25fpsの動画なら1秒間に25枚の映像を表示するので1枚あたりの表示時間は0.04秒になりますが、単位が100ナノ秒なので400000という値になります。
旧ビデオレンダラー
旧ビデオレンダラーでフレームレートを取得するにはIBasicVideoインターフェースを使います。
IBasicVideoで取得できるAvgTimePerFrameは単位がIMediaDetやVMR7およびVMR9で取得できるAvgTimePerFrameと違います。
IMediaDetやVMR7、VMR9は100ナノ秒ですが、IBasicVideoは秒です。
例えば25fpsの動画なら0.04になります。
IMediaDetやVMR7、VMR9では上記のように400000になります。
function GetFrameRateBasicVideo(sFileName :
WideString): TRefTime;
var
li_Ret : HResult;
l_GraphBuilder : IGraphBuilder;
l_VideoRenderer : IBaseFilter;
l_BasicVideo : IBasicVideo;
lf_Time : TRefTime;
begin
Result := 0;
if not(FileExists(sFileName))
then begin
Exit;
end;
try
{グラフ作成}
li_Ret := CoCreateInstance(
CLSID_FilterGraph,
nil,
CLSCTX_INPROC_SERVER,
IID_IGraphBuilder,
l_GraphBuilder
);
if (li_Ret <> S_OK)
then begin
Exit;
end;
//旧ビデオレンダラ
CoCreateInstance(
CLSID_VideoRenderer,
nil,
CLSCTX_INPROC,
IID_IBaseFilter,
l_VideoRenderer
);
l_GraphBuilder.AddFilter(l_VideoRenderer, 'Video Renderer');
if not(Succeeded(l_GraphBuilder.RenderFile(PWideChar(sFileName),
nil)))
then begin
Exit;
end;
l_GraphBuilder.QueryInterface(IBasicVideo, l_BasicVideo);
if (l_BasicVideo <>
nil)
then begin
l_BasicVideo.get_AvgTimePerFrame(lf_Time);
if (lf_Time <> 0)
then begin
//IMediaDetやVMR7、VMR9とは違い単位は秒なのでUNITSで割らない。
Result :=
_Round(100 / lf_Time) / 100;
end;
l_BasicVideo :=
nil;
end;
finally
if (l_VideoRenderer <>
nil)
then begin
l_VideoRenderer :=
nil;
end;
if (l_GraphBuilder <>
nil)
then begin
l_GraphBuilder :=
nil;
end;
end;
end;
VMR7とVMR9でフレームレートを取得するには旧ビデオレンダラーでやるよりは難しくなります。
ビデオレンダラーの入力ピンのメディアタイプを取得しその中のフォーマットタイプを調べてAvgTimePerFrameの値を取得してフレームレートを計算します。
function GetFrameRateVMR(sFileName :
WideString): TRefTime;
var
li_Ret : HResult;
l_GraphBuilder : IGraphBuilder;
l_VideoRenderer : IBaseFilter;
lf_Time : TRefTime;
l_EnumPins : IEnumPins;
l_Pin : IPin;
l_PinInfo : TPinInfo;
l_MediaType : TAMMediaType;
begin
Result := 0;
if not(FileExists(sFileName))
then begin
Exit;
end;
try
{グラフ作成}
li_Ret := CoCreateInstance(
CLSID_FilterGraph,
nil,
CLSCTX_INPROC_SERVER,
IID_IGraphBuilder,
l_GraphBuilder
);
if (li_Ret <> S_OK)
then begin
Exit;
end;
//VMR7作成。
//VMR9でもフレームレートの取得のやり方は同じ。
CoCreateInstance(
CLSID_VideoMixingRenderer,
nil,
CLSCTX_INPROC,
IID_IBaseFilter,
l_VideoRenderer
);
if (l_VideoRenderer =
nil)
then begin
Exit;
end;
l_GraphBuilder.AddFilter(l_VideoRenderer, 'Video Renderer');
if not(Succeeded(l_GraphBuilder.RenderFile(PWideChar(sFileName),
nil)))
then begin
Exit;
end;
l_VideoRenderer.EnumPins(l_EnumPins);
while(l_EnumPins.Next(1, l_Pin,
nil) = S_OK)
do begin
l_Pin.QueryPinInfo(l_PinInfo);
try
if (l_PinInfo.dir = PINDIR_INPUT)
then begin
//入力。
l_Pin.ConnectionMediaType(l_MediaType);
try
if (IsEqualGUID(l_MediaType.majortype, MEDIATYPE_Video))
then begin
//サブタイプに応じたAvgTimePerFrameの取得。
if (IsEqualGUID(l_MediaType.formattype, FORMAT_VideoInfo))
then begin
//VIDEOINFOHEADER
lf_Time := PVideoInfoHeader(l_MediaType.pbFormat)^.AvgTimePerFrame;
end else if (IsEqualGUID(l_MediaType.formattype, FORMAT_VideoInfo2))
then begin
//VIDEOINFOHEADER2
li_Time := PVideoInfoHeader2(l_MediaType.pbFormat)^.AvgTimePerFrame;
end else if (IsEqualGUID(l_MediaType.formattype, FORMAT_MPEGVideo))
then begin
//MPEG1VIDEOINFO
li_Time := PMpeg1VideoInfo(l_MediaType.pbFormat)^.hdr.AvgTimePerFrame;
end else if (IsEqualGUID(l_MediaType.formattype, FORMAT_MPEG2Video))
then begin
//MPEG2VIDEOINFO
li_Time := PMpeg2VideoInfo(l_MediaType.pbFormat)^.hdr.AvgTimePerFrame;
end else begin
li_Time := 0;
end;
if (lf_Time <> 0)
then begin
Result :=
_Round(100 / (lf_Time / UNITS)) / 100;
Break;
end;
end;
finally
FreeMediaType(@l_MediaType);
end;
end;
finally
if (l_PinInfo.pFilter <>
nil)
then begin
l_PinInfo.pFilter :=
nil;
end;
l_Pin :=
nil;
end;
end;
l_EnumPins :=
nil;
l_VideoRenderer :=
nil;
finally
l_GraphBuilder :=
nil;
end;
end;
- フィルタグラフを作成します。
- VMR7を作成しフィルタグラフへ追加します。
- ファイルを読み込みます。
- VMR7のピンを列挙し入力ピンを探します。
- 入力ピンが見つかったらConnectionMediaTypeで入力ピンに接続されているメディアタイプを取得します。
- 取得したメディアタイプのメジャータイプがMEDIATYPE_Videoならビデオの入力であるのでサブタイプの種類に応じてAvgTimePerFrameを取得してフレームレートを計算します。
問題点
- PCの構成やデコーダやスプリッタの組み合わせにもよるのかも知れませんが、IMediaDetインターフェースではYouTubeの低画質のflvファイルのフレームレートは取得できないようです。
- 取得できるフレームレートはおおよその目安になるだけで正確性に欠けることがあります。
例えば先にあげたYouTubeの低画質のflvファイルで本来30fpsであるはずの動画が25fpsとして計算されたり、細かいところでは29.97fpsのものが29.96fpsとなったりすることがあります。