メディアサンプルのハンドリング(続き)
メディアサンプルの受信
ProcessOutput
メディアサンプルのハンドリングについて再掲しておく。
アクティビティ
![]() |
| 送信者 imageryblog |
MFVP_MESSAGE_PROCESSINPUTNOTIFY メッセージを受け取ったとき、プレゼンタはミキサーからメディアサンプルを受け取る。Media Foundation では DirectShow でいうところのプルモデルを採用しているので、プレゼンタ側からミキサーに対しサンプルを要求する。ミキサー上の IMFTransform::ProcessOutput を呼び出しメディアサンプルを受け取る。
CEvrPres::ProcessMessage で当該メッセージを受け取ったとき CEvrPres::ProcessInputNotify を呼び出している。実際には、さらにその中で呼び出している CEvrPres::ProcessOutput でメディアサンプルを受信している。おおまかな処理の流れは以下。
- メディアサンプルプールに空きができるのを待つ。
CEvrPres::SetMediaTypeで作成しておいた Win32 イベントハンドル群を待つようにWaitForMultipleObjectsを呼び出す。(ProcessOutputから抜ける前に空きを待っているので、必ず1つ以上のあるはず。) - メディアサンプルにミキサーの出力を書き込む。ここでミキサー上の
IMFTransform::ProcessOutputを呼び出す。 - メディアサンプルキューに書き込んだメディアサンプルを追加する。
EC_PROCESSING_LATENCYイベントを EVR に通知する。これは、どの程度処理に時間がかかったかをEVRに報告するメッセージである。- メディアサンプルプールがいっぱいなら、空きができる(キューが消費される)まで待つ。
ProcessOutput
HRESULT CEvrPres::ProcessOutput()
{
HRESULT hr = S_OK;
MFTIME hns_time;
ATLENSURE_RETURN_HR(m_Mixer, MF_E_INVALIDREQUEST);
ATLENSURE_RETURN_HR(m_SamplesPool.GetCount() > 0, MF_E_TRANSFORM_TYPE_NOT_SET);
CComPtr<IMFSample> mf_sample;
size_t sample_num;
// 空きサンプル待ち
DWORD wait_result;
wait_result = WaitForMultipleObjects(m_Consumed.size(),
&m_Consumed.at(0), FALSE, MFSAMPLE_TIMEOUT);
ATLENSURE_RETURN_HR(wait_result != WAIT_TIMEOUT, S_FALSE);
ATLASSERT(wait_result >= WAIT_OBJECT_0 &&
wait_result < (WAIT_OBJECT_0 + m_Consumed.size()));
sample_num = wait_result - WAIT_OBJECT_0;
// ミキサーからサンプルを受信する
CAutoCritSecLock pool_lock(m_SamplesPoolLock);
MFT_OUTPUT_DATA_BUFFER data_buffer = {0};
mf_sample = m_SamplesPool.GetAt(sample_num);
if (mf_sample == NULL) {
return E_FAIL;
}
data_buffer.pSample = mf_sample;
LONGLONG mixer_start_time, mixer_end_time;
if (m_Clock) {
m_Clock->GetCorrelatedTime(0, &mixer_start_time, &hns_time);
}
DWORD status;
hr = m_Mixer->ProcessOutput(0, 1, &data_buffer, &status);
if (FAILED(hr)) {
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
return hr;
}
else if (hr == MF_E_TRANSFORM_TYPE_NOT_SET) {
hr = RenegotiateMediaType();
return hr;
}
else if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
// メディアタイプが変更された
SetMediaType(NULL);
}
return hr;
}
// EC_PROCESSING_LATENCY を送信する
if (m_Clock) {
MFCLOCK_STATE mfc_state;
m_Clock->GetState(0, &mfc_state);
m_Clock->GetCorrelatedTime(0, &mixer_end_time, &hns_time);
const LONGLONG latency = mixer_end_time - mixer_start_time;
if (mfc_state == MFCLOCK_STATE_RUNNING) {
NotifyEvent(EC_PROCESSING_LATENCY, (LONG_PTR)&latency, 0);
}
}
ResetEvent(m_Consumed.at(sample_num));
m_QueuedSamples.AddTail(mf_sample);
pool_lock.Unlock();
// サンプルプールのサイズとキュー済サンプルのサイズが同じ
// → サンプルプールがいっぱいのときは、キューが消費されるまで待つ
size_t count;
do {
Sleep(0);
{
CAutoCritSecLock pool_lock(m_SamplesPoolLock);
GetCount(&count);
}
} while (count == m_SamplesPool.GetCount());
ATLASSERT(count < m_SamplesPool.GetCount());
return hr;
}
レンダリングスレッド
レンダリングスレッドでは、メディアサンプルキューからメディアサンプルを1つ取り出し、バックバッファにレンダリングする。ここでは PPLの Concurrency::task_group を使ってスレッドを作成し、 CMyWindow::Render に実装している。おおまかな処理の流れは以下。
- Media Session の現在のプレゼンテーション時刻を取得する。あらかじめMedia Session から
IMFClockを取得しておきIMFClock::GetCorrelatedTimeを呼ぶ。 - メディアサンプルキューからメディアサンプルを1つ取り出す。但し削除しない(Peek)。
- メディアサンプルのプレゼンテーション時刻と長さ(Duration)を取得する。
- プレゼンテーション時刻と比較して、メディアサンプルを表示すべきかどうか確認する。
- メディアサンプルから
IDirect3DSurface9を取得し、バックバッファへ転送。
レンダリングスレッドからメディアサンプルキューにアクセスするために CEvrPres は ISurfaceSharing インターフェイスを公開している。
ISurfaceSharing の定義
__interface __declspec(uuid("{AD4A803A-06DC-45f5-983E-DAE5D1EB10E8}")) ISurfaceSharing : IUnknown
{
HRESULT STDMETHODCALLTYPE GetLock(LPCRITICAL_SECTION * lock);
HRESULT STDMETHODCALLTYPE PeekHead(IMFSample ** mf_sample);
HRESULT STDMETHODCALLTYPE GetCount(size_t * count);
HRESULT STDMETHODCALLTYPE RemoveHead();
};
レンダリングスレッドのコードは以下。全部書くと冗長なのでかいつまんで。
CMyWindow::Render
// Topologyを構築したときに ISurfaceSharing インターフェイスを取得しておく。
CComPtr<ISurfaceSharing> m_SurfSharing;
MFGetService(m_MFSession, MR_VIDEO_RENDER_SERVICE, PPV_ARGS(&m_VideoDisplay));
m_VideoDisplay->QueryInterface(IID_PPV_ARGS(&m_SurfSharing));
// 1. 現在のプレゼンテーション時刻を取得
LONGLONG clock_time;
MFTIME hns_time;
m_Clock->GetCorrelatedTime(0, &clock_time, &hns_time);
CComPtr<IMFSample> mf_sample;
LPCRITICAL_SECTION crit_sec;
m_SurfSharing->GetLock(&crit_sec);
EnterCriticalSection(crit_sec);
CComPtr<IMFSample> head_sample;
LONGLONG sample_time, sample_duration;
do {
// 2. メディアサンプルを取り出す
head_sample.Release();
m_SurfSharing->PeekHead(&head_sample); // 取り出し
if (head_sample == NULL) {
// サンプルが一つもない
LeaveCriticalSection(crit_sec);
return 0;
}
// 3. 時刻と長さを取得
head_sample->GetSampleTime(&sample_time);
head_sample->GetSampleDuration(&sample_duration);
UINT32 consumed = TRUE;
head_sample->GetUINT32(ATTR_CONSUMED, &consumed);
// 4. 表示すべきかどうか確認する
if ((sample_time + sample_duration) < clock_time) {
// 表示すべき
break;
}
.
.
.
} while (true);
// 5. IDirect3DSurface9 を取得し、バックバッファに転送
CComPtr<IMFMediaBuffer> mf_mbuf;
CComPtr<IDirect3DSurface9> surface;
head_sample->GetBufferByIndex(0, &mf_mbuf);
MFGetService(mf_mbuf, MR_BUFFER_SERVICE, __uuidof(IDirect3DSurface9), (void**)&surface);
以上でプレゼンタの実装は完了。

0 コメント:
コメントを投稿