2010年10月8日金曜日

EVRカスタムプレゼンタを実装する(3)

メディアサンプルのハンドリング(続き)

メディアサンプルの受信

ProcessOutput

メディアサンプルのハンドリングについて再掲しておく。

アクティビティ
送信者 imageryblog

MFVP_MESSAGE_PROCESSINPUTNOTIFY メッセージを受け取ったとき、プレゼンタはミキサーからメディアサンプルを受け取る。Media Foundation では DirectShow でいうところのプルモデルを採用しているので、プレゼンタ側からミキサーに対しサンプルを要求する。ミキサー上の IMFTransform::ProcessOutput を呼び出しメディアサンプルを受け取る。

CEvrPres::ProcessMessage で当該メッセージを受け取ったとき CEvrPres::ProcessInputNotify を呼び出している。実際には、さらにその中で呼び出している CEvrPres::ProcessOutput でメディアサンプルを受信している。おおまかな処理の流れは以下。

  1. メディアサンプルプールに空きができるのを待つ。CEvrPres::SetMediaType で作成しておいた Win32 イベントハンドル群を待つように WaitForMultipleObjects を呼び出す。(ProcessOutput から抜ける前に空きを待っているので、必ず1つ以上のあるはず。)
  2. メディアサンプルにミキサーの出力を書き込む。ここでミキサー上の IMFTransform::ProcessOutput を呼び出す。
  3. メディアサンプルキューに書き込んだメディアサンプルを追加する。
  4. EC_PROCESSING_LATENCY イベントを EVR に通知する。これは、どの程度処理に時間がかかったかをEVRに報告するメッセージである。
  5. メディアサンプルプールがいっぱいなら、空きができる(キューが消費される)まで待つ。
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 に実装している。おおまかな処理の流れは以下。

  1. Media Session の現在のプレゼンテーション時刻を取得する。あらかじめMedia Session からIMFClock を取得しておき IMFClock::GetCorrelatedTime を呼ぶ。
  2. メディアサンプルキューからメディアサンプルを1つ取り出す。但し削除しない(Peek)。
  3. メディアサンプルのプレゼンテーション時刻と長さ(Duration)を取得する。
  4. プレゼンテーション時刻と比較して、メディアサンプルを表示すべきかどうか確認する。
  5. メディアサンプルから IDirect3DSurface9 を取得し、バックバッファへ転送。

レンダリングスレッドからメディアサンプルキューにアクセスするために CEvrPresISurfaceSharing インターフェイスを公開している。

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 コメント:

コメントを投稿