動画ファイルのフレームレートを取得
動画ファイルのフレームレートを取得したいと思いました。
調べてみるとビデオレンダラーの違いで二種類の取得方法ともう一つ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となったりすることがあります。