というわけで実装その2です。前回はこちらから。
各種インターフェイスを作成したり解放する部分ができたのでそれを使ってみるところです。
・・・が、単に内部のサポート関数やらを呼び出しているだけなので微妙だったりします。
まずは再生、停止、一時停止
//再生 BOOL CMFSession::PlayMovie(BOOL bIsLoop) { if(!InnerTestStatus(MFSSTATUS_LOAD)) return FALSE; if(!InnerTestStatus(MFSSTATUS_PAUSE)){ PROPVARIANT varStart; PropVariantInit(&varStart); m_lpMediaSession->Start(NULL,&varStart); if(bIsLoop) m_nStatusCode |= MFSSTATUS_LOOP; m_nStatusCode = (m_nStatusCode & ~MFSSTATUS_PAUSE) | MFSSTATUS_PLAY; } return TRUE; } //停止 BOOL CMFSession::StopMovie(void) { if(!InnerTestStatus(MFSSTATUS_LOAD)) return FALSE; if(InnerTestStatus(MFSSTATUS_PLAY)){ m_lpMediaSession->Stop(); m_nStatusCode &= ~(MFSSTATUS_PLAY | MFSSTATUS_PAUSE); } return TRUE; } //再生中かどうか BOOL CMFSession::IsPlayMovie(void) const { return InnerTestStatus(MFSSTATUS_PLAY); } //一時停止 BOOL CMFSession::PauseMovie(void) { if(!InnerTestStatus(MFSSTATUS_LOAD)) return FALSE; if(InnerTestStatus(MFSSTATUS_PAUSE)){ PROPVARIANT varStart; PropVariantInit(&varStart); m_lpMediaSession->Start(NULL,&varStart); m_nStatusCode &= ~MFSSTATUS_PAUSE; } else if(InnerTestStatus(MFSSTATUS_PLAY)){ m_lpMediaSession->Pause(); m_nStatusCode |= MFSSTATUS_PAUSE; } return TRUE; }
普通にIMFMediaSessionに再生、停止、一時停止があるのでそのまま呼び出すだけで終わります。
注意点としては再生命令であるStartに秘密があり、ここで再生開始時間を指定することができます。
時間を指定しない時(第一引数のGUIDを指定しないかつ第二引数のPROPVARIANTが初期化されているだけ)の時は前回停止した地点から再生するのでそれを利用します。
時間移動時はPROPVARIANTでの指定を利用しますが、それは後ほど。
早送りおよび巻き戻し
//早送り(ms) BOOL CMFSession::ForwardMovie(uint32_t nTime) { return SeekMovieRel((int64_t)MilliSecondToMediaTime(nTime)); } //巻き戻し(ms) BOOL CMFSession::BackwardMovie(uint32_t nTime) { return SeekMovieRel(-(int64_t)MilliSecondToMediaTime(nTime)); } //再生位置の移動(ms) BOOL CMFSession::SeekMovie(uint32_t nTime) { return SeekMovieAbs(MilliSecondToMediaTime(nTime)); } //再生位置の相対移動(100ns) BOOL CMFSession::SeekMovieRel(int64_t llTime) { MFTIME vMFTime; HRESULT hr; if(m_lpPresentationClock == NULL) return FALSE; hr = m_lpPresentationClock->GetTime(&vMFTime); if(FAILED(hr)) return FALSE; return SUCCEEDED(InnerSeekTime(vMFTime + llTime)); } //再生位置の絶対移動(100ns) BOOL CMFSession::SeekMovieAbs(uint64_t ullTime) { return SUCCEEDED(InnerSeekTime(ullTime)); }
結局最後はInnerSeekTimeに渡しているだけです。
注意点としては、再生位置の相対移動時に現在の(メディア上の)時刻を取得する必要があります。
で、それを持っているのはMediaFoundationの場合はIMFPresentationClockとなります。
ここから現在のメディア内時間を取得することになります。
ウィンドウ状態の設定
//再生ウィンドウの設定 BOOL CMFSession::SetPlayWindow(const POINT *lpPlayPos,const SIZE *lpPlaySize) { RECT rcRect; POINT ptPlay; SIZE sizePlay,sizeVideo; if(!IsEnableVideo()) return FALSE; if(m_lpVideoDisplay != NULL){ m_lpVideoDisplay->GetNativeVideoSize(&sizeVideo,NULL); if(lpPlayPos != NULL){ ptPlay = *lpPlayPos; } else{ ptPlay.x = ptPlay.y = 0; } if(lpPlaySize != NULL){ sizePlay = *lpPlaySize; } else{ sizePlay = sizeVideo; } SetRect(&rcRect,ptPlay.x,ptPlay.y,ptPlay.x + sizePlay.cx,ptPlay.y + sizePlay.cy); m_lpVideoDisplay->SetVideoPosition(NULL,&rcRect); } return TRUE; }
面倒なのはSetVideoPositionの指定です。第一引数がちょっと特殊で、MFVideoNormalizedRectという構造体を使います。
メンバはRECT構造体がfloatになったようなもので、(0.0,0.0)を左上、(1.0,1.0)を右下とします。
テクスチャ系の座標指定を模しているもので、これが描画元領域を設定します。
第二引数は描画先のウィンドウ内での位置を設定します。大きさが合わなければ勝手に縮尺をあわせてくれるので普通に使います。
再生速度の変更
//スピードの変更 BOOL CMFSession::SetSpeed(double dSpeed) { IMFGetService *lpGetService = NULL; IMFRateSupport *lpRateSupport = NULL; IMFRateControl *lpRateControl = NULL; float fSpeed,fNearSpeed; HRESULT hr; do{ fSpeed = (float)dSpeed; FAILED_BREAK(hr,m_lpMediaSession->QueryInterface(IID_PPV_ARGS(&lpGetService))); FAILED_BREAK(hr,lpGetService->GetService(MF_RATE_CONTROL_SERVICE,IID_PPV_ARGS(&lpRateSupport))); FAILED_BREAK(hr,lpRateSupport->IsRateSupported(FALSE,fSpeed,&fNearSpeed)); FAILED_BREAK(hr,lpGetService->GetService(MF_RATE_CONTROL_SERVICE,IID_PPV_ARGS(&lpRateControl))); FAILED_BREAK(hr,lpRateControl->SetRate(FALSE,fSpeed)); } while(0); if(lpRateControl != NULL){ lpRateControl->Release(); } if(lpRateSupport != NULL){ lpRateSupport->Release(); } if(lpGetService != NULL){ lpGetService->Release(); } return SUCCEEDED(hr); }
実はいくつか設定を無視していますがとりあえず再生速度の設定です。
再生速度を設定するためには以下のインターフェイスを使います。
インターフェイス | 説明 |
---|---|
IMFRateSupport | 再生速度のサポート範囲を問い合わせる為に使用する |
IMFRateControl | 実際の再生速度の取得、設定を行う |
この系のサポートインターフェイスはDirectShowではGraphBuilderに対してQueryInterfaceで取得していましたが、MediaFoundationではIMFGetServiceを通して取得します。
今回は取得が必要なインターフェイスが二つあるのでIMFGetServiceを直接使っていますが、一つだけでいいならヘルパ関数のMFGetServiceを使った方が安全です。(IMFGetServiceの解放漏れがないため)
また、IsRateSupportedの第一引数とSetRateの第一引数は同じ意味で、英語の説明だとわかりづらかったのですが、つまるところ
第一引数のfThinがTRUE => 速度が等速以外(特に速度が速い場合)ではフレームスキップを行い、キーフレーム以外を表示しない
第一引数のfThinがFALSE => できる限りフレームを表示する(キーフレーム以外でも表示する)
となります。
音量系の処理
//パンの変化を設定する BOOL CMFSession::SetPan(int nAbsolutePan) { float fBalance; HRESULT hr; if(!IsEnableAudio()) return FALSE; if(nAbsolutePan < MFS_PANLEFT || nAbsolutePan > MFS_PANRIGHT) return FALSE; fBalance = (float)nAbsolutePan / (float)MFS_VOLUMEMAX; if(fBalance == m_fAudioBalance) return TRUE; m_fAudioBalance = fBalance; hr = InnerSetVolume(); return SUCCEEDED(hr); } //音量の設定をする BOOL CMFSession::SetVolume(int nAbsoluteVolume) { float fVolume; HRESULT hr; if(!IsEnableAudio()) return FALSE; if(nAbsoluteVolume < MFS_VOLUMEMIN || nAbsoluteVolume > MFS_VOLUMEMAX) return FALSE; fVolume = (float)(nAbsoluteVolume - MFS_VOLUMEMIN) / (float)(MFS_VOLUMEMAX - MFS_VOLUMEMIN); if(fVolume == m_fAudioVolume) return TRUE; m_fAudioVolume = fVolume; hr = InnerSetVolume(); return SUCCEEDED(hr); }
再生位置移動と同じくサポート関数のInnerSetVolumeに任せているだけなのでそちらでやります。
メディアの情報の取得
//メディアの再生時間を取得 uint32_t CMFSession::GetMovieTime(void) const { return MediaTimeToMilliSecond(m_mtMediaLength); } //メディア時間で再生時間を取得 uint64_t CMFSession::GetMediaTime(void) const { return m_mtMediaLength; } //メディアの画像のサイズを取得 BOOL CMFSession::GetMovieSize(int32_t *lpnWidth,int32_t *lpnHeight) const { SIZE sizeVideo; HRESULT hr; if(m_lpVideoDisplay == NULL) return FALSE; if(lpnWidth == NULL || lpnHeight == NULL) return FALSE; hr = m_lpVideoDisplay->GetNativeVideoSize(&sizeVideo,NULL); if(FAILED(hr)) return FALSE; *lpnWidth = sizeVideo.cx; *lpnHeight = sizeVideo.cy; return TRUE; } //メディアが映像ラインを保持しているかどうか BOOL CMFSession::HasMediaVideo(void) const { return InnerTestStatus(MFSSTATUS_HASVIDEOLINE); } //メディアが音声ラインを保持しているかどうか BOOL CMFSession::HasMediaAudio(void) const { return InnerTestStatus(MFSSTATUS_HASAUDIOLINE); }
画像サイズの取得だけが問題ですか。
前回にも言いましたとおり、ロードした直後はIMFVideoDisplayControlを持っていないのでNULLチェックが必要です。
あとはGetNativeVideoSizeでビデオサイズが取得できればそれを返して完了ですね。
次回はそのサポート関数の実装を見る
サポート関数群はMediaFoundationで設定に必要なコードをいろいろと使っているのでこちらの方が必要でしょう。
DirectShowの内部的な動作と同じでいろいろと面倒ですよ・・・。