DirectShowを利用して再生・その6
前回はビデオの映像をビットマップとして取得できるようにしました。
今回はコマ送りと再生速度の変更を行います。
スロー再生やコマ送り、コマ戻しなどができればビデオのキャプチャもよりやりやすくなります。
コマ送り
コマ送り(フレーム移動)はフレーム間の平均時間を求めて現在位置の時間に足し引きして移動することで実現できます。
現在位置の時間に1フレーム分の時間を足して移動させれば次のフレームが表示されるし1フレーム分の時間を引いて移動させれば一つ前のフレームが表示されるということです。
単純です。
フレーム間の平均時間はフレームレートの取得で詳しく述べていますが、ビデオレンダラー(VMR7)の入力ピンのメディアタイプを取得しそのフォーマットタイプを調べてそのうちのAvgTimePerFrameの値がフレーム間の平均時間です。
//--- フレームレート ---
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;
function TForm1.F_GetFrameRate: String;
var
li_Time : Int64;
l_EnumPins : IEnumPins;
l_Pin : IPin;
l_PinInfo : TPinInfo;
l_MediaType : TAMMediaType;
begin
Result := '-';
F_fAvgTimePerFrame := 0;
if (F_VideoRenderer = nil) then begin
Exit;
end;
F_VideoRenderer.EnumPins(l_EnumPins);
try
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
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
F_fAvgTimePerFrame
:= li_Time / UNITS;
Result := Format('%g',
[_Round(100 / F_fAvgTimePerFrame) / 100]);
end;
Break;
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;
finally
if (l_EnumPins <> nil) then begin
l_EnumPins := nil;
end;
end;
end;
この関数は'29.97'や'59.94'などのフレームレートの表示用の文字列を取得する関数なのですが、この中でF_fAvgTimePerFrameにセットしている値がフレーム間の平均時間で、単位は秒です。
59.97fpsの動画なら0.0166833、30fpsの動画なら0.0333333、29.97fpsの動画なら0.033375などの値(に近い値)になります。
//--- フレーム移動 ---
procedure TForm1.Button_FrameBackClick(Sender: TObject);
//前のフレーム。
var
lf_Pos : TRefTime;
begin
Button_PauseClick(nil);
if (F_fAvgTimePerFrame = 0) then begin
//F_fAvgTimePerFrameが0の場合暫定で29.97fpsのビデオであるとする。
F_fAvgTimePerFrame := 1 / 29.97;
end;
F_MediaPosition.get_CurrentPosition(lf_Pos);
F_MediaPosition.put_CurrentPosition(lf_Pos - F_fAvgTimePerFrame);
end;
procedure TForm1.Button_FrameNextClick(Sender: TObject);
//次のフレーム。
var
lf_Pos : TRefTime;
begin
Button_PauseClick(nil);
if (F_fAvgTimePerFrame = 0) then begin
//F_fAvgTimePerFrameが0の場合暫定で29.97fpsのビデオであるとする。
F_fAvgTimePerFrame := 1 / 29.97;
end;
F_MediaPosition.get_CurrentPosition(lf_Pos);
F_MediaPosition.put_CurrentPosition(lf_Pos + F_fAvgTimePerFrame);
end;
//--------
フレーム間の平均時間であるF_fAvgTimePerFrameを現在位置の時間から引いて移動すれば前のフレーム、足せば次のフレームになります。
F_fAvgTimePerFrameを5倍すればそれぞれ5フレーム前、5フレーム後のフレームが表示されます。
目的のフレーム位置まで移動するシーク操作があるので速度は遅いです。
高解像度の動画で非力なPCだと暫く無反応になることもあります。
IVideoFrameStepインターフェースを使うと動画フォーマットが対応していればもっとスムーズな表示ができます。
まずフレーム移動ができるか調べます。
IVideoFrameStepインターフェースのCanStepメソッドの第一引数にステップ数を指定して調べます。
1なら次のフレーム、-1なら一つ前のフレームになります。
フレーム移動可能であればIVideoFrameStepインターフェースのStepメソッドに移動するフレーム数を指定して呼ぶことでフレーム移動できます。
とはいえ現状大抵の動画ファイルは1以上ならOKでも-1以下だとNGになると思います。
つまりコマ送りはできるけれどもコマ戻しはIVideoFrameStepインターフェースでは現状できないということになります。
その結果コマ送りはスムーズにできるけれどもコマ戻しは上記のフレーム間の平均時間(F_fAvgTimePerFrame)を使ってシーク移動する方法をとらざるを得ないので遅くなってしまいます。
function TForm1.F_CanStep(iStep: Integer): Boolean;
var
l_VideoFrameStep : IVideoFrameStep;
begin
Result := False;
if (F_GraphBuilder <> nil) then
begin
F_GraphBuilder.QueryInterface(IVideoFrameStep, l_VideoFrameStep);
if (l_VideoFrameStep <> nil) then
begin
Result := (l_VideoFrameStep.CanStep(iStep, nil) = S_OK);
l_VideoFrameStep := nil;
end;
end;
end;
procedure TForm1.Button_FrameNextClick(Sender: TObject);
//次のフレーム。
var
l_VideoFrameStep : IVideoFrameStep;
lb_Step : Boolean;
lf_Pos : TRefTime;
begin
lb_Step := False;
if (F_CanStep(1)) then
begin
F_GraphBuilder.QueryInterface(IVideoFrameStep, l_VideoFrameStep);
if (l_VideoFrameStep <> nil) then
begin
lb_Step := (l_VideoFrameStep.Step(1, nil) = S_OK);
l_VideoFrameStep := nil;
end;
end;
if (lb_Step) then
begin
//フレーム移動成功なので以下の処理を飛ばす
Exit;
end;
//↑でうまくいかなかった場合
Button_PauseClick(nil);
if (F_fAvgTimePerFrame = 0) then begin
//F_fAvgTimePerFrameが0の場合暫定で29.97fpsのビデオであるとする。
F_fAvgTimePerFrame := 1 / 29.97;
end;
F_MediaPosition.get_CurrentPosition(lf_Pos);
F_MediaPosition.put_CurrentPosition(lf_Pos + F_fAvgTimePerFrame);
end;
再生速度変更
再生速度の変更はIMediaPositionインターフェースのput_Rateメソッドで行います。
等倍なら1を、2倍速なら2を、半分の速度なら0.5を指定します。
TWindowsMediaPlayerの場合と違いwmaとwmvファイルの速度の変更はできないようです。
逆にTWindowsMediaPlayerではできなかったmp4やflvファイルの速度の変更はできます。
また0.5倍未満の速度(例えば0.1倍)であっても音が出ます(TWindowsMediaPlayerでは0.5倍未満は音はでません)。
//--- 再生速度変更 ---
procedure TForm1.MenuItem_Rate_10Click(Sender: TObject);
//wmv,wma,amazonのmp3は再生速度の変更はできない
var
li_Ret : HResult;
ls_Err : String;
ls_Msg : String;
lf_Rate : Double;
i : Integer;
begin
if (Sender is TMenuItem) then begin
TMenuItem(Sender).Checked := True;
end;
if (F_MediaPosition = nil) then begin
Exit;
end;
lf_Rate := 1.0;
for i := 0 to MenuItem_Rate.Count -1 do begin
if (MenuItem_Rate.Items[i].Checked) then begin
lf_Rate := StrToFloatDef(MenuItem_Rate.Items[i].Caption,
1.0);
Break;
end;
end;
li_Ret := F_MediaPosition.put_Rate(lf_Rate);
case li_Ret of
S_OK
:begin
ls_Err := 'S_OK';
ls_Msg := '再生速度変更'
end;
E_NOTIMPL
:begin
ls_Err := 'E_NOTIMPL';
ls_Msg := '指定された機能が実装されていません。';
end;
E_INVALIDARG
:begin
ls_Err := 'E_INVALIDARG';
ls_Msg := '指定したレートは、0 または負の値だった。';
end;
E_POINTER
:begin
ls_Err := 'E_POINTER';
ls_Msg := 'NULL ポインタ引数。';
end;
VFW_E_UNSUPPORTED_AUDIO
:begin
ls_Err := 'VFW_E_UNSUPPORTED_AUDIO';
ls_Msg := 'オーディオ デバイスあるいはフィルタがこのレートをサポートしていない。';
end;
end;
debug_msg.ShowDbg(Format('%.1f 0x%.8x %s %s', [lf_Rate, li_Ret,
ls_Err, ls_Msg]));
end;
//--------
再生速度の指定はメニューのCaptionに'1.0'や'2.0'や'0.5'などとしているのを利用しています。
ダウンロード
dplay_6.zip ソースコードと実行ファイルの詰め合わせ。