#include "MediaSink.h" ///////////////////////////////////////////////////////////////////////////////////////////// // // CMediaSink class. - Implements the media sink. // // Notes: // - Most public methods calls CheckShutdown. This method fails if the sink was shut down. // ///////////////////////////////////////////////////////////////////////////////////////////// DX11VideoRenderer::CCritSec DX11VideoRenderer::CMediaSink::s_csStreamSinkAndScheduler; //------------------------------------------------------------------- // Name: CreateInstance // Description: Creates an instance of the DX11 Video Renderer sink object. //------------------------------------------------------------------- /* static */ HRESULT DX11VideoRenderer::CMediaSink::CreateInstance(_In_ REFIID iid, _COM_Outptr_ void** ppSink) { if (ppSink == NULL) { return E_POINTER; } *ppSink = NULL; HRESULT hr = S_OK; CMediaSink* pSink = new CMediaSink(); // Created with ref count = 1. if (pSink == NULL) { hr = E_OUTOFMEMORY; } if (SUCCEEDED(hr)) { hr = pSink->Initialize(); } if (SUCCEEDED(hr)) { hr = pSink->QueryInterface(iid, ppSink); } SafeRelease(pSink); return hr; } // IUnknown methods ULONG DX11VideoRenderer::CMediaSink::AddRef(void) { return InterlockedIncrement(&m_nRefCount); } HRESULT DX11VideoRenderer::CMediaSink::QueryInterface(REFIID iid, __RPC__deref_out _Result_nullonfailure_ void** ppv) { if (!ppv) { return E_POINTER; } if (iid == IID_IUnknown) { *ppv = static_cast(static_cast(this)); } else if (iid == __uuidof(IMFMediaSink)) { *ppv = static_cast(this); } else if (iid == __uuidof(IMFClockStateSink)) { *ppv = static_cast(this); } else if (iid == __uuidof(IMFGetService)) { *ppv = static_cast(this); } else if (iid == IID_IMFRateSupport) { *ppv = static_cast(this); } else if (iid == IID_IMFMediaSinkPreroll) { *ppv = static_cast(this); } else { *ppv = NULL; return E_NOINTERFACE; } AddRef(); return S_OK; } ULONG DX11VideoRenderer::CMediaSink::Release(void) { ULONG uCount = InterlockedDecrement(&m_nRefCount); if (uCount == 0) { delete this; } // For thread safety, return a temporary variable. return uCount; } /// IMFMediaSink methods. //------------------------------------------------------------------- // Name: AddStreamSink // Description: Adds a new stream to the sink. // // Note: This sink has a fixed number of streams, so this method // always returns MF_E_STREAMSINKS_FIXED. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::AddStreamSink(DWORD dwStreamSinkIdentifier, __RPC__in_opt IMFMediaType* pMediaType, __RPC__deref_out_opt IMFStreamSink** ppStreamSink) { return MF_E_STREAMSINKS_FIXED; } //------------------------------------------------------------------- // Name: GetCharacteristics // Description: Returns the characteristics flags. // // Note: This sink has a fixed number of streams. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::GetCharacteristics(__RPC__out DWORD* pdwCharacteristics) { CAutoLock lock(&m_csMediaSink); if (pdwCharacteristics == NULL) { return E_POINTER; } HRESULT hr = CheckShutdown(); if (SUCCEEDED(hr)) { *pdwCharacteristics = MEDIASINK_FIXED_STREAMS | MEDIASINK_CAN_PREROLL; } return hr; } //------------------------------------------------------------------- // Name: GetPresentationClock // Description: Returns a pointer to the presentation clock. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::GetPresentationClock(__RPC__deref_out_opt IMFPresentationClock** ppPresentationClock) { CAutoLock lock(&m_csMediaSink); if (ppPresentationClock == NULL) { return E_POINTER; } HRESULT hr = CheckShutdown(); if (SUCCEEDED(hr)) { if (m_pClock == NULL) { hr = MF_E_NO_CLOCK; // There is no presentation clock. } else { // Return the pointer to the caller. *ppPresentationClock = m_pClock; (*ppPresentationClock)->AddRef(); } } return hr; } //------------------------------------------------------------------- // Name: GetStreamSinkById // Description: Retrieves a stream by ID. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::GetStreamSinkById(DWORD dwStreamSinkIdentifier, __RPC__deref_out_opt IMFStreamSink** ppStreamSink) { CAutoLock lock(&m_csMediaSink); if (ppStreamSink == NULL) { return E_POINTER; } // Fixed stream ID. if (dwStreamSinkIdentifier != STREAM_ID) { return MF_E_INVALIDSTREAMNUMBER; } HRESULT hr = CheckShutdown(); if (SUCCEEDED(hr)) { *ppStreamSink = m_pStream; (*ppStreamSink)->AddRef(); } return hr; } //------------------------------------------------------------------- // Name: GetStreamSinkByIndex // Description: Retrieves a stream by index. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::GetStreamSinkByIndex(DWORD dwIndex, __RPC__deref_out_opt IMFStreamSink** ppStreamSink) { CAutoLock lock(&m_csMediaSink); if (ppStreamSink == NULL) { return E_POINTER; } // Fixed stream: Index 0. if (dwIndex > 0) { return MF_E_INVALIDINDEX; } HRESULT hr = CheckShutdown(); if (SUCCEEDED(hr)) { *ppStreamSink = m_pStream; (*ppStreamSink)->AddRef(); } return hr; } //------------------------------------------------------------------- // Name: GetStreamSinkCount // Description: Returns the number of streams. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::GetStreamSinkCount(__RPC__out DWORD* pcStreamSinkCount) { CAutoLock lock(&m_csMediaSink); if (pcStreamSinkCount == NULL) { return E_POINTER; } HRESULT hr = CheckShutdown(); if (SUCCEEDED(hr)) { *pcStreamSinkCount = 1; // Fixed number of streams. } return hr; } //------------------------------------------------------------------- // Name: RemoveStreamSink // Description: Removes a stream from the sink. // // Note: This sink has a fixed number of streams, so this method // always returns MF_E_STREAMSINKS_FIXED. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::RemoveStreamSink(DWORD dwStreamSinkIdentifier) { return MF_E_STREAMSINKS_FIXED; } //------------------------------------------------------------------- // Name: SetPresentationClock // Description: Sets the presentation clock. // // pPresentationClock: Pointer to the clock. Can be NULL. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::SetPresentationClock(__RPC__in_opt IMFPresentationClock* pPresentationClock) { CAutoLock lock(&m_csMediaSink); HRESULT hr = CheckShutdown(); // If we already have a clock, remove ourselves from that clock's // state notifications. if (SUCCEEDED(hr)) { if (m_pClock) { hr = m_pClock->RemoveClockStateSink(this); } } // Register ourselves to get state notifications from the new clock. if (SUCCEEDED(hr)) { if (pPresentationClock) { hr = pPresentationClock->AddClockStateSink(this); } } if (SUCCEEDED(hr)) { // Release the pointer to the old clock. // Store the pointer to the new clock. SafeRelease(m_pClock); m_pClock = pPresentationClock; if (m_pClock) { m_pClock->AddRef(); } } return hr; } //------------------------------------------------------------------- // Name: Shutdown // Description: Releases resources held by the media sink. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::Shutdown(void) { CAutoLock lock(&m_csMediaSink); HRESULT hr = MF_E_SHUTDOWN; m_IsShutdown = TRUE; if (m_pStream != NULL) { m_pStream->Shutdown(); } if (m_pPresenter != NULL) { m_pPresenter->Shutdown(); } SafeRelease(m_pClock); SafeRelease(m_pStream); SafeRelease(m_pPresenter); if (m_pScheduler != NULL) { hr = m_pScheduler->StopScheduler(); } SafeRelease(m_pScheduler); return hr; } //------------------------------------------------------------------- // Name: OnClockPause // Description: Called when the presentation clock paused. // // Note: For an archive sink, the paused state is equivalent to the // running (started) state. We still accept data and archive it. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::OnClockPause( /* [in] */ MFTIME hnsSystemTime) { CAutoLock lock(&m_csMediaSink); HRESULT hr = CheckShutdown(); if (SUCCEEDED(hr)) { hr = m_pStream->Pause(); } return hr; } //------------------------------------------------------------------- // Name: OnClockRestart // Description: Called when the presentation clock restarts. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::OnClockRestart( /* [in] */ MFTIME hnsSystemTime) { CAutoLock lock(&m_csMediaSink); HRESULT hr = CheckShutdown(); if (SUCCEEDED(hr)) { hr = m_pStream->Restart(); } return hr; } //------------------------------------------------------------------- // Name: OnClockSetRate // Description: Called when the presentation clock's rate changes. // // Note: For a rateless sink, the clock rate is not important. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::OnClockSetRate( /* [in] */ MFTIME hnsSystemTime, /* [in] */ float flRate) { if (m_pScheduler != NULL) { // Tell the scheduler about the new rate. m_pScheduler->SetClockRate(flRate); } return S_OK; } //------------------------------------------------------------------- // Name: OnClockStart // Description: Called when the presentation clock starts. // // hnsSystemTime: System time when the clock started. // llClockStartOffset: Starting presentatation time. // // Note: For an archive sink, we don't care about the system time. // But we need to cache the value of llClockStartOffset. This // gives us the earliest time stamp that we archive. If any // input samples have an earlier time stamp, we discard them. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::OnClockStart( /* [in] */ MFTIME hnsSystemTime, /* [in] */ LONGLONG llClockStartOffset) { CAutoLock lock(&m_csMediaSink); HRESULT hr = CheckShutdown(); if (FAILED(hr)) { return hr; } // Check if the clock is already active (not stopped). // And if the clock position changes while the clock is active, it // is a seek request. We need to flush all pending samples. if (m_pStream->IsActive() && llClockStartOffset != PRESENTATION_CURRENT_POSITION) { // This call blocks until the scheduler threads discards all scheduled samples. hr = m_pStream->Flush(); } else { if (m_pScheduler != NULL) { // Start the scheduler thread. hr = m_pScheduler->StartScheduler(m_pClock); } } if (SUCCEEDED(hr)) { hr = m_pStream->Start(llClockStartOffset); } return hr; } //------------------------------------------------------------------- // Name: OnClockStop // Description: Called when the presentation clock stops. // // Note: After this method is called, we stop accepting new data. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::OnClockStop( /* [in] */ MFTIME hnsSystemTime) { CAutoLock lock(&m_csMediaSink); HRESULT hr = CheckShutdown(); if (SUCCEEDED(hr)) { hr = m_pStream->Stop(); } if (SUCCEEDED(hr)) { if (m_pScheduler != NULL) { // Stop the scheduler thread. hr = m_pScheduler->StopScheduler(); } } return hr; } //------------------------------------------------------------------------- // Name: GetService // Description: IMFGetService //------------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::GetService(__RPC__in REFGUID guidService, __RPC__in REFIID riid, __RPC__deref_out_opt LPVOID* ppvObject) { HRESULT hr = S_OK; if (guidService == MF_RATE_CONTROL_SERVICE) { hr = QueryInterface(riid, ppvObject); } else if (guidService == MR_VIDEO_RENDER_SERVICE) { hr = m_pPresenter->QueryInterface(riid, ppvObject); } else if (guidService == MR_VIDEO_ACCELERATION_SERVICE) { hr = m_pPresenter->GetService(guidService, riid, ppvObject); } else { hr = MF_E_UNSUPPORTED_SERVICE; } return hr; } STDMETHODIMP DX11VideoRenderer::CMediaSink::GetFastestRate( MFRATE_DIRECTION eDirection, BOOL fThin, _Out_ float *pflRate ) { HRESULT hr = S_OK; CAutoLock lock(&m_csMediaSink); do { hr = CheckShutdown(); if (FAILED(hr)) { break; } if (NULL == pflRate) { hr = E_POINTER; break; } float rate; hr = m_pStream->GetMaxRate(fThin, &rate); if (FAILED(hr)) { break; } if (MFRATE_FORWARD == eDirection) { *pflRate = rate; } else { *pflRate = -rate; } } while (FALSE); return hr; } //------------------------------------------------------------------------- // Name: GetSlowestRate // Description: IMFRateSupport //------------------------------------------------------------------------- STDMETHODIMP DX11VideoRenderer::CMediaSink::GetSlowestRate( MFRATE_DIRECTION eDirection, BOOL fThin, _Out_ float* pflRate ) { HRESULT hr = S_OK; CAutoLock lock(&m_csMediaSink); do { hr = CheckShutdown(); if (FAILED(hr)) { break; } if (NULL == pflRate) { hr = E_POINTER; break; } if (SUCCEEDED(hr)) { // // We go as slow as you want! // *pflRate = 0; } } while (FALSE); return hr; } STDMETHODIMP DX11VideoRenderer::CMediaSink::IsRateSupported(BOOL fThin, float flRate, __RPC__inout_opt float* pflNearestSupportedRate) { HRESULT hr = S_OK; float flNearestSupportedRate = flRate; CAutoLock lock(&m_csMediaSink); do { hr = CheckShutdown(); if (FAILED(hr)) { break; } // // Only support rates up to the refresh rate of the monitor. // This check makes sense only if we're going to be receiving // all frames // if ( !fThin ) { float rate; hr = m_pStream->GetMaxRate(fThin, &rate); if (FAILED(hr)) { break; } if ( (flRate > 0 && flRate > (float)rate) || (flRate < 0 && flRate < -(float)rate) ) { hr = MF_E_UNSUPPORTED_RATE; flNearestSupportedRate = ( flRate >= 0.0f ) ? rate : -rate; break; } } } while (FALSE); if ( NULL != pflNearestSupportedRate ) { *pflNearestSupportedRate = flNearestSupportedRate; } return hr; } STDMETHODIMP DX11VideoRenderer::CMediaSink::NotifyPreroll(MFTIME hnsUpcomingStartTime) { HRESULT hr = S_OK; CAutoLock lock(&m_csMediaSink); hr = CheckShutdown(); if (SUCCEEDED(hr)) { hr = m_pStream->Preroll(); } return hr; } /// Private methods //------------------------------------------------------------------- // CMediaSink constructor. //------------------------------------------------------------------- DX11VideoRenderer::CMediaSink::CMediaSink(void) : STREAM_ID(1), m_nRefCount(1), m_csMediaSink(), // default ctor m_IsShutdown(FALSE), m_pStream(NULL), m_pClock(NULL), m_pScheduler(NULL), m_pPresenter(NULL) { } //------------------------------------------------------------------- // CMediaSink destructor. //------------------------------------------------------------------- DX11VideoRenderer::CMediaSink::~CMediaSink(void) { } HRESULT DX11VideoRenderer::CMediaSink::CheckShutdown(void) const { if (m_IsShutdown) { return MF_E_SHUTDOWN; } else { return S_OK; } } //------------------------------------------------------------------- // Name: Initialize // Description: Initializes the media sink. // // Note: This method is called once when the media sink is first // initialized. //------------------------------------------------------------------- HRESULT DX11VideoRenderer::CMediaSink::Initialize(void) { HRESULT hr = S_OK; IMFMediaSink* pSink = NULL; do { m_pScheduler = new CScheduler(s_csStreamSinkAndScheduler); if (m_pScheduler == NULL) { hr = E_OUTOFMEMORY; break; } m_pStream = new CStreamSink(STREAM_ID, s_csStreamSinkAndScheduler, m_pScheduler); if (m_pStream == NULL) { hr = E_OUTOFMEMORY; break; } m_pPresenter = new CPresenter(); // Created with ref count = 1. if (m_pPresenter == NULL) { hr = E_OUTOFMEMORY; break; } hr = QueryInterface(IID_PPV_ARGS(&pSink)); if (FAILED(hr)) { break; } hr = m_pStream->Initialize(pSink, m_pPresenter); if (FAILED(hr)) { break; } m_pScheduler->SetCallback(static_cast(m_pStream)); } while (FALSE); if (FAILED(hr)) { Shutdown(); } SafeRelease(pSink); return hr; }