前回のまとめ。
- EVRの構成要素
- プレゼンタの状態、実装すべきインターフェイス
- プレゼンタの初期化、Direct3Dの受け渡し
- IMFGetService, IMFTopologyServiceLookupClient, IMFVideoDeviceID の実装
引き続き、残りのインターフェイスの実装について見ていくことにする。
プレゼンタに必要なインターフェイスを実装する(2)
IMFVideoPresenter
IMFVideoPresenter は以下の2つのメソッドを持つインターフェイスである。
| メソッド | 説明 |
|---|---|
| GetCurrentMediaType | プレゼンタの現在の入力メディアタイプを返す。 |
| ProcessMessage | EVR から渡されたメッセージを処理する。ストリーミングが開始したとき、フレームを受け取ったときなどのタイミングで呼ばれる。 |
GetCurrentMediaType
プレゼンタの現在の入力メディアタイプを返す。
プレゼンタの入力メディアタイプを CEvrPres のメンバ変数 m_MediaType として記憶しておき、それを返す。メディアタイプはミキサーとフォーマットをネゴシエーションして決定する(後述)。他のスレッドでメディアタイプが書き換わるのを防ぐため、メソッド全体をクリティカルセクションでロックする。
// メンバ変数
CCritSec m_ObjLock;
CComPtr<IMFMediaType> m_MediaType;
HRESULT CEvrPres::GetCurrentMediaType(IMFVideoMediaType ** ppMediaType)
{
HRESULT hr;
ATLENSURE_RETURN_HR(ppMediaType, E_POINTER);
*ppMediaType = NULL;
CAutoCritSecLock lock(m_ObjLock); // ロック
ATLENSURE_RETURN_HR(SUCCEEDED(IsShutdowned), IsShutdowned); // 状態チェック
if (m_MediaType == NULL) { // メディアタイプが未設定??
return MF_E_NOT_INITIALIZED;
}
// IMFVideoMediaType インターフェイスを取得して返す
hr = m_MediaType.QueryInterface<IMFVideoMediaType>(ppMediaType);
return hr;
}
IsShutdowned はプレゼンタの状態が RENDER_STATE_SHUTDOWN かどうかチェックするプロパティである。
__declspec(property(get=get_IsShutdowned)) HRESULT IsShutdowned;
HRESULT CEvrPres::get_IsShutdowned() const
{
return m_RenderState == RENDER_STATE_SHUTDOWN ? MF_E_SHUTDOWN : S_OK;
}
ProcessMessage
メソッドの引数としてメッセージ(eMessage)とメッセージの引数(ulParam)が渡されてくるので、メッセージを処理するメソッドにディスパッチする。 メッセージは MFVP_MESSAGE* で始まるシンボルが定義されている。
コードは以下。コマ送り機能は実装しないため、それに関わるメッセージは未実装としている。
// メンバ変数
// MFVP_MESSAGE_PROCESSINPUTNOTIFY を受け取った時に true
bool m_InputNotify;
// MFVP_MESSAGE_ENDOFSTREAM を受け取ったときに true
bool m_EOS;
HRESULT CEvrPres::ProcessMessage(MFVP_MESSAGE_TYPE eMessage, ULONG_PTR ulParam)
{
HRESULT hr;
CAutoCritSecLock lock(m_ObjLock); // メッセージ処理中はスレッドセーフにする
ATLENSURE_RETURN_HR(SUCCEEDED(IsShutdowned), IsShutdowned);
struct func_table {
MFVP_MESSAGE_TYPE msg;
std::function<HRESULT (void)> func;
};
const std::array<func_table, 9> funcs = { {
{MFVP_MESSAGE_FLUSH, [this]() -> HRESULT { return Flush(); }},
{MFVP_MESSAGE_INVALIDATEMEDIATYPE, [this]() -> HRESULT { return RenegotiateMediaType(); }},
{MFVP_MESSAGE_PROCESSINPUTNOTIFY, [this]() -> HRESULT { return ProcessInputNotify(); }},
{MFVP_MESSAGE_BEGINSTREAMING, [this]() -> HRESULT { return BeginStreaming(); }},
{MFVP_MESSAGE_ENDSTREAMING, [this]() -> HRESULT { return EndStreaming(); }},
{MFVP_MESSAGE_ENDOFSTREAM, [this]() -> HRESULT { m_EOS = true; return CheckEndOfStream(); }},
// 今回は以下の2つのメッセージは未実装。コマ送りを実現するなら実装が必要。
{MFVP_MESSAGE_STEP, [this, ulParam]() -> HRESULT { return E_NOTIMPL; }},
{MFVP_MESSAGE_CANCELSTEP, [this]() -> HRESULT { return E_NOTIMPL; }},
} };
m_InputNotify = false;
auto it = std::find_if(funcs.begin(), funcs.end(),
[eMessage](const func_table & a) -> bool { return eMessage == a.msg; });
ATLENSURE_THROW(it != funcs.end(), E_INVALIDARG);
hr = it->func();
return hr;
}
IMFClockStateSink
IMFClockStateSink はプレゼンテーションクロックの状態が変化した時に呼び出されるメソッドをまとめたインターフェイスである。IMFVideoPresenter は、このインターフェイスから派生しているのでプレゼンタは必ず実装しなければならない。プレゼンタの状態を更新する契機となる。
| メソッド | プレゼンタの状態 | 説明 |
|---|---|---|
| OnClockPause | RENDER_STATE_PAUSED | ポーズ(一時停止)。 |
| OnClockRestart | RENDER_STATE_STARTED | ポーズから再スタート。 |
| OnClockSetRate | (変更なし。今回はサポートしない。) | レートを変更したとき。今回はサポートしない。 |
| OnClockStart | RENDER_STATE_STARTED | 開始。 |
| OnClockStop | RENDER_STATE_STOPPED | 停止。 |
詳細
プレゼンタに必要なインターフェイスの実装は以上である。ここからは、実装の詳細をみていく。
メディアサンプルのハンドリング
プレゼンタがミキサーからメディアサンプルを受け取るスレッドと、画面にレンダリングするスレッドは異なる。そこでプレゼンタは受け取ったサンプルをキューに蓄積することにする。ただしオーバーヘッドを避けるために深いコピーではなくインターフェイスへのポインタコピーするだけとする。レンダリングスレッドはキューからサンプルを取り出し、バックバッファにレンダリングする。これを実現するために以下のメンバ変数を設ける。
| メンバ変数名 | 型 | 解説 |
|---|---|---|
| m_SamplesPool | CInterfaceArray<IMFSample> | メディアサンプルプール。プレゼンタはここからメディアサンプルを一つ取り出す。ミキサーからの出力を書き込む。 |
| m_QueuedSamples | CInterfaceList<IMFSample> | メディアサンプルキュー。プレゼンタはメディアサンプルをここにキューする。レンダリングスレッドは、ここから1つ取り出してバックバッファに書き込む。 |
| m_Consumed | std::vector<HANDLE> | Win32 イベントの配列。メディアサンプルプールの各サンプルと1対1で紐づいている。プールにあるメディアサンプルがキューされると対応するハンドルがリセットされる。レンダリングスレッドがメディアサンプルを取り出すか、キューがフラッシュされとシグナル状態になる。プール内のどれかのメディアサンプルが使用可能になるのを WaitForMultipleObject で待つことができる。 |
| m_SamplesPoolLock | CCritSec | メディアサンプルプール、メディアサンプルキューへのアクセスを排他制御するためのクリティカルセクション。 |
![]() |
| 送信者 imageryblog |
IMFStateClockSink のメソッドで以下の場合はメディアサンプルキューをフラッシュ(Flush)する。
IMFStateClockSink::OnClockStartで開始位置がPRESENTATION_CURRENT_POSITIONでないとき。IMFStateClockSink::OnClockStopが呼ばれたとき。
フラッシュはキューに残ってるサンプルをすべてレンダリングするのが普通だが、ここでは単にキューの内容を破棄する。
フォーマットのネゴシエーション
RenegotiateMediaType
MFVP_MESSAGE_INVALIDATEMEDIATYPE メッセージを受け取ったとき、ミキサーが出力するフォーマットと、プレゼンタが受け入れるフォーマットを調整する。。大まかな処理の手順は以下。
- ミキサーから利用可能な出力メディアタイプを1つ取得する。
IMFTransform::GetOutputAvailableTypeを呼び出す。 - メディアタイプがこのプレゼンタでサポートできる形式であることを確認する。(
CEvrPres::IsMediaTypeSupported) - ミキサーから受け取るメディアサンプルを直接プレゼンタで取り扱えるように、メディアタイプをより適する内容に書き換える。(
CEvrPres::CreateOptimalVideoType) - 書き換えたメディアタイプが設定可能か確認する。
IMFTransform::SetOutputTypeにMFT_SET_TYPE_TEST_ONLYオプションを与えて呼び出す。 - プレゼンタの現在の入力メディアタイプとして、メンバ変数
m_MediaTypeに記憶する。(CEvrPres::SetMediaType) - ミキサーにメディアタイプを設定する。
サンプルでは CEvrPres::RenegotiateMediaType にその処理を実装している。コードは以下。
HRESULT CEvrPres::RenegotiateMediaType()
{
HRESULT hr = S_OK;
CComPtr<IMFMediaType> mixer_type;
CComPtr<IMFMediaType> optimal_type;
CComPtr<IMFVideoMediaType> video_type;
ATLENSURE_RETURN_HR(m_Mixer != NULL, MF_E_INVALIDREQUEST);
DWORD type_index = 0;
while (hr != MF_E_NO_MORE_TYPES) {
mixer_type.Release();
hr = m_Mixer->GetOutputAvailableType(0, type_index++, &mixer_type);
if (FAILED(hr)) {
break;
}
CHResult nego_hr;
try {
nego_hr = IsMediaTypeSupported(mixer_type);
optimal_type.Release();
nego_hr = CreateOptimalVideoType(mixer_type, &optimal_type);
nego_hr = m_Mixer->SetOutputType(0, optimal_type, MFT_SET_TYPE_TEST_ONLY);
nego_hr = SetMediaType(optimal_type);
nego_hr = m_Mixer->SetOutputType(0, optimal_type, 0);
hr = S_OK;
break;
}
catch (CAtlException) {
SetMediaType(NULL);
}
}
return hr;
}
IsMediaTypeSupported
メディアタイプに対し以下の項目を確認する。
- 非圧縮であること。
- フォーマットはD3DFMT_X8R8G8B8であること。
- プログレッシブ (つまり非インターレス) であること。
HRESULT CEvrPres::IsMediaTypeSupported(IMFMediaType * mt)
{
ATLENSURE_RETURN_HR(mt != NULL, E_POINTER);
CHResult hr;
try {
// 非圧縮?
BOOL compressed;
hr = mt->IsCompressedFormat(&compressed);
if (compressed) {
return MF_E_INVALIDMEDIATYPE;
}
// フォーマットは D3DFMT_X8R8G8B8?
GUID sub_type;
hr = mt->GetGUID(MF_MT_SUBTYPE, &sub_type);
D3DFORMAT format = (D3DFORMAT)sub_type.Data1;
if (format != D3DFMT_X8R8G8B8) {
return MF_E_INVALIDMEDIATYPE;
}
// プログレッシブ?
UINT32 interlace_mode;
hr = mt->GetUINT32(MF_MT_INTERLACE_MODE, (UINT32*)&interlace_mode);
if (interlace_mode != MFVideoInterlace_Progressive) {
return MF_E_INVALIDMEDIATYPE;
}
}
catch (CAtlException &e) {
return e.m_hr;
}
return hr;
}
CreateOptimalVideoType
ミキサーが提示してきたメディアタイプをベースに、より適するメディアタイプに書き換える。(コード中のシンボルにリンクを張っておいたので、どのような設定をしているのかはリンク先のMSDNを参照のこと。)
HRESULT CEvrPres::CreateOptimalVideoType(IMFMediaType * propsed_mt, IMFMediaType ** optimal_mt)
{
CHResult hr;
ATLENSURE_RETURN_HR(propsed_mt != NULL, E_POINTER);
ATLENSURE_RETURN_HR(optimal_mt != NULL, E_POINTER);
try {
CRect outupt_rect;
MFVideoArea displayArea = {0};
hr = MFCreateMediaType(optimal_mt);
hr = propsed_mt->CopyAllItems(*optimal_mt);
hr = MFSetAttributeRatio(*optimal_mt, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
hr = (*optimal_mt)->SetUINT32(MF_MT_YUV_MATRIX, MFVideoTransferMatrix_BT709);
hr = (*optimal_mt)->SetUINT32(MF_MT_TRANSFER_FUNCTION, (UINT32)MFVideoTransFunc_709);
hr = (*optimal_mt)->SetUINT32(MF_MT_VIDEO_PRIMARIES, (UINT32)MFVideoPrimaries_BT709);
hr = (*optimal_mt)->SetUINT32(MF_MT_VIDEO_NOMINAL_RANGE, (UINT32)MFNominalRange_16_235);
hr = (*optimal_mt)->SetUINT32(MF_MT_VIDEO_LIGHTING, (UINT32)MFVideoLighting_dim);
hr = (*optimal_mt)->SetUINT32(MF_MT_PAN_SCAN_ENABLED, (UINT32)FALSE);
}
catch (CAtlException &e) {
return e.m_hr;
}
return hr;
}
SetMediaType と CreateMFSample
メディアタイプが決定したら、プレゼンタのメンバ変数 m_MediaType に記憶する。そして画像フレームを受け取るためのメディアサンプルを作成する。メディアサンプルは 1 つではなく複数個用意し配列で管理する。メディアサンプルが使用済みかどうかを判断する Win32 イベントハンドルを ATTR_CONSUMED 属性に記憶する。 ATTR_CONSUMED は guidgen.exe で GUID を生成しておく。
HRESULT CEvrPres::SetMediaType(IMFMediaType * mt)
{
if (mt == NULL) {
m_MediaType.Release();
ReleaseResources();
return S_OK;
}
ATLENSURE_RETURN_HR(SUCCEEDED(IsShutdowned), IsShutdowned);
DWORD flags;
if (m_MediaType) {
HRESULT is_equal = m_MediaType->IsEqual(mt, &flags);
if (is_equal == S_OK) {
return S_OK;
}
m_MediaType.Release();
}
ReleaseResources();
// メディアサンプルを作成する
CHResult hr;
try {
m_SamplesPool.SetCount(BUFFER_SIZE);
for (size_t n = 0;n < m_SamplesPool.GetCount(); n++) {
CComPtr<IMFSample> obj;
hr = m_DGrph->CreateMFSample(mt, &obj); // (後述)
HANDLE e = CreateEvent(NULL, TRUE, TRUE, NULL);
obj->SetUINT64(ATTR_CONSUMED, (UINT64)e);
m_SamplesPool.SetAt(n, obj);
m_Consumed.push_back(e);
}
ATLASSERT(m_SamplesPool.GetCount() == m_Consumed.size());
}
catch (CAtlException &e) {
m_SamplesPool.RemoveAll();
return e.m_hr;
}
m_MediaType.Attach(mt);
mt->AddRef();
return hr;
}
メディアサンプルを作成するには Direct3D9 サーフェイスを作成し、それを引数として MFCreateVideoSampleFromSurface を呼び出す。今回は Direct3D10.1 との同期共有サーフェイスを作成するので ID3D10Texture2D など関連するインターフェイスをメディアサンプルの属性として登録する。
HRESULT CDGrph::CreateMFSample(IMFMediaType * mt, IMFSample ** mf_sample)
{
CHResult hr;
ATLENSURE_RETURN_HR(m_Window, MF_E_INVALIDREQUEST);
ATLENSURE_RETURN_HR(mt, MF_E_UNEXPECTED);
ATLENSURE_RETURN_HR(mf_sample, MF_E_UNEXPECTED);
CAutoCritSecLock lock(m_ObjLock);
// サーフェイスフォーマットを決める
UINT32 width, height;
hr = MFGetAttributeSize(mt, MF_MT_FRAME_SIZE, &width, &height);
// note : SUBTYPEのData1はD3DFORMATと同じ値
// http://msdn.microsoft.com/en-us/library/aa370819(VS.85).aspx
GUID sub_type;
hr = mt->GetGUID(MF_MT_SUBTYPE, &sub_type);
D3DFORMAT d3dformat = (D3DFORMAT)sub_type.Data1;
ATLASSERT(d3dformat == D3DFMT_X8R8G8B8);
// Direct3D9Ex - 10.1 の同期共有サーフェイスを作成し、関連インターフェイスを属性として登録
SharedSurface surf;
hr = CreateSharedSurface(D3DFMT_A8R8G8B8, CSize(width, height), surf);
hr = MFCreateVideoSampleFromSurface(surf.Surface9, mf_sample);
hr = (*mf_sample)->SetUnknown(__uuidof(ID3D10Texture2D), surf.Texture10);
hr = (*mf_sample)->SetUnknown(__uuidof(ID3D10ShaderResourceView1), surf.SRView);
hr = (*mf_sample)->SetUnknown(__uuidof(IDXGISurface), surf.DxgiSurf);
hr = (*mf_sample)->SetUnknown(__uuidof(ID2D1RenderTarget) , surf.D2DTarget);
hr = (*mf_sample)->SetUnknown(__uuidof(ID2D1SolidColorBrush), surf.Brush);
return S_OK;
}
次はミキサーからメディアサンプルを受信し、バックバッファにレンダリングする処理を実装していく。
(3)に続く。
