📄 renbase.cpp
字号:
// Used to signal end of stream according to the sample end time
void CALLBACK EndOfStreamTimer(UINT uID, // Timer identifier
UINT uMsg, // Not currently used
DWORD_PTR dwUser,// User information
DWORD_PTR dw1, // Windows reserved
DWORD_PTR dw2) // is also reserved
{
CBaseRenderer *pRenderer = (CBaseRenderer *) dwUser;
NOTE1("EndOfStreamTimer called (%d)",uID);
pRenderer->TimerCallback();
}
// Do the timer callback work
void CBaseRenderer::TimerCallback()
{
// Lock for synchronization (but don't hold this lock when calling
// timeKillEvent)
CAutoLock cRendererLock(&m_RendererLock);
// See if we should signal end of stream now
if (m_EndOfStreamTimer) {
m_EndOfStreamTimer = 0;
SendEndOfStream();
}
}
// If we are at the end of the stream signal the filter graph but do not set
// the state flag back to FALSE. Once we drop off the end of the stream we
// leave the flag set (until a subsequent ResetEndOfStream). Each sample we
// get delivered will update m_SignalTime to be the last sample's end time.
// We must wait this long before signalling end of stream to the filtergraph
#define TIMEOUT_DELIVERYWAIT 50
#define TIMEOUT_RESOLUTION 10
HRESULT CBaseRenderer::SendEndOfStream()
{
ASSERT(CritCheckIn(&m_RendererLock));
if (m_bEOS == FALSE || m_bEOSDelivered || m_EndOfStreamTimer) {
return NOERROR;
}
// If there is no clock then signal immediately
if (m_pClock == NULL) {
return NotifyEndOfStream();
}
// How long into the future is the delivery time
REFERENCE_TIME Signal = m_tStart + m_SignalTime;
REFERENCE_TIME CurrentTime;
m_pClock->GetTime(&CurrentTime);
LONG Delay = LONG((Signal - CurrentTime) / 10000);
// Dump the timing information to the debugger
NOTE1("Delay until end of stream delivery %d",Delay);
NOTE1("Current %s",(LPCTSTR)CDisp((LONGLONG)CurrentTime));
NOTE1("Signal %s",(LPCTSTR)CDisp((LONGLONG)Signal));
// Wait for the delivery time to arrive
if (Delay < TIMEOUT_DELIVERYWAIT) {
return NotifyEndOfStream();
}
// Signal a timer callback on another worker thread
m_EndOfStreamTimer = CompatibleTimeSetEvent((UINT) Delay, // Period of timer
TIMEOUT_RESOLUTION, // Timer resolution
EndOfStreamTimer, // Callback function
DWORD_PTR(this), // Used information
TIME_ONESHOT); // Type of callback
if (m_EndOfStreamTimer == 0) {
return NotifyEndOfStream();
}
return NOERROR;
}
// Signals EC_COMPLETE to the filtergraph manager
HRESULT CBaseRenderer::NotifyEndOfStream()
{
CAutoLock cRendererLock(&m_RendererLock);
ASSERT(m_bEOSDelivered == FALSE);
ASSERT(m_EndOfStreamTimer == 0);
// Has the filter changed state
if (m_bStreaming == FALSE) {
ASSERT(m_EndOfStreamTimer == 0);
return NOERROR;
}
// Reset the end of stream timer
m_EndOfStreamTimer = 0;
// If we've been using the IMediaPosition interface, set it's start
// and end media "times" to the stop position by hand. This ensures
// that we actually get to the end, even if the MPEG guestimate has
// been bad or if the quality management dropped the last few frames
if (m_pPosition) m_pPosition->EOS();
m_bEOSDelivered = TRUE;
NOTE("Sending EC_COMPLETE...");
return NotifyEvent(EC_COMPLETE,S_OK,(LONG_PTR)(IBaseFilter *)this);
}
// Reset the end of stream flag, this is typically called when we transfer to
// stopped states since that resets the current position back to the start so
// we will receive more samples or another EndOfStream if there aren't any. We
// keep two separate flags one to say we have run off the end of the stream
// (this is the m_bEOS flag) and another to say we have delivered EC_COMPLETE
// to the filter graph. We need the latter otherwise we can end up sending an
// EC_COMPLETE every time the source changes state and calls our EndOfStream
HRESULT CBaseRenderer::ResetEndOfStream()
{
ResetEndOfStreamTimer();
CAutoLock cRendererLock(&m_RendererLock);
m_bEOS = FALSE;
m_bEOSDelivered = FALSE;
m_SignalTime = 0;
return NOERROR;
}
// Kills any outstanding end of stream timer
void CBaseRenderer::ResetEndOfStreamTimer()
{
ASSERT(CritCheckOut(&m_RendererLock));
if (m_EndOfStreamTimer) {
timeKillEvent(m_EndOfStreamTimer);
m_EndOfStreamTimer = 0;
}
}
// This is called when we start running so that we can schedule any pending
// image we have with the clock and display any timing information. If we
// don't have any sample but we have queued an EOS flag then we send it. If
// we do have a sample then we wait until that has been rendered before we
// signal the filter graph otherwise we may change state before it's done
HRESULT CBaseRenderer::StartStreaming()
{
CAutoLock cRendererLock(&m_RendererLock);
if (m_bStreaming == TRUE) {
return NOERROR;
}
// Reset the streaming times ready for running
m_bStreaming = TRUE;
timeBeginPeriod(1);
OnStartStreaming();
// There should be no outstanding advise
ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
ASSERT(CancelNotification() == S_FALSE);
// If we have an EOS and no data then deliver it now
if (m_pMediaSample == NULL) {
return SendEndOfStream();
}
// Have the data rendered
ASSERT(m_pMediaSample);
if (!ScheduleSample(m_pMediaSample))
m_RenderEvent.Set();
return NOERROR;
}
// This is called when we stop streaming so that we can set our internal flag
// indicating we are not now to schedule any more samples arriving. The state
// change methods in the filter implementation take care of cancelling any
// clock advise link we have set up and clearing any pending sample we have
HRESULT CBaseRenderer::StopStreaming()
{
CAutoLock cRendererLock(&m_RendererLock);
m_bEOSDelivered = FALSE;
if (m_bStreaming == TRUE) {
m_bStreaming = FALSE;
OnStopStreaming();
timeEndPeriod(1);
}
return NOERROR;
}
// We have a boolean flag that is reset when we have signalled EC_REPAINT to
// the filter graph. We set this when we receive an image so that should any
// conditions arise again we can send another one. By having a flag we ensure
// we don't flood the filter graph with redundant calls. We do not set the
// event when we receive an EndOfStream call since there is no point in us
// sending further EC_REPAINTs. In particular the AutoShowWindow method and
// the DirectDraw object use this method to control the window repainting
void CBaseRenderer::SetRepaintStatus(BOOL bRepaint)
{
CAutoLock cSampleLock(&m_RendererLock);
m_bRepaintStatus = bRepaint;
}
// Pass the window handle to the upstream filter
void CBaseRenderer::SendNotifyWindow(IPin *pPin,HWND hwnd)
{
IMediaEventSink *pSink;
// Does the pin support IMediaEventSink
HRESULT hr = pPin->QueryInterface(IID_IMediaEventSink,(void **)&pSink);
if (SUCCEEDED(hr)) {
pSink->Notify(EC_NOTIFY_WINDOW,LONG_PTR(hwnd),0);
pSink->Release();
}
NotifyEvent(EC_NOTIFY_WINDOW,LONG_PTR(hwnd),0);
}
// Signal an EC_REPAINT to the filter graph. This can be used to have data
// sent to us. For example when a video window is first displayed it may
// not have an image to display, at which point it signals EC_REPAINT. The
// filtergraph will either pause the graph if stopped or if already paused
// it will call put_CurrentPosition of the current position. Setting the
// current position to itself has the stream flushed and the image resent
#define RLOG(_x_) DbgLog((LOG_TRACE,1,TEXT(_x_)));
void CBaseRenderer::SendRepaint()
{
CAutoLock cSampleLock(&m_RendererLock);
ASSERT(m_pInputPin);
// We should not send repaint notifications when...
// - An end of stream has been notified
// - Our input pin is being flushed
// - The input pin is not connected
// - We have aborted a video playback
// - There is a repaint already sent
if (m_bAbort == FALSE) {
if (m_pInputPin->IsConnected() == TRUE) {
if (m_pInputPin->IsFlushing() == FALSE) {
if (IsEndOfStream() == FALSE) {
if (m_bRepaintStatus == TRUE) {
IPin *pPin = (IPin *) m_pInputPin;
NotifyEvent(EC_REPAINT,(LONG_PTR) pPin,0);
SetRepaintStatus(FALSE);
RLOG("Sending repaint");
}
}
}
}
}
}
// When a video window detects a display change (WM_DISPLAYCHANGE message) it
// can send an EC_DISPLAY_CHANGED event code along with the renderer pin. The
// filtergraph will stop everyone and reconnect our input pin. As we're then
// reconnected we can accept the media type that matches the new display mode
// since we may no longer be able to draw the current image type efficiently
BOOL CBaseRenderer::OnDisplayChange()
{
// Ignore if we are not connected yet
CAutoLock cSampleLock(&m_RendererLock);
if (m_pInputPin->IsConnected() == FALSE) {
return FALSE;
}
RLOG("Notification of EC_DISPLAY_CHANGE");
// Pass our input pin as parameter on the event
IPin *pPin = (IPin *) m_pInputPin;
m_pInputPin->AddRef();
NotifyEvent(EC_DISPLAY_CHANGED,(LONG_PTR) pPin,0);
SetAbortSignal(TRUE);
ClearPendingSample();
m_pInputPin->Release();
return TRUE;
}
// Called just before we start drawing.
// Store the current time in m_trRenderStart to allow the rendering time to be
// logged. Log the time stamp of the sample and how late it is (neg is early)
void CBaseRenderer::OnRenderStart(IMediaSample *pMediaSample)
{
#ifdef PERF
REFERENCE_TIME trStart, trEnd;
pMediaSample->GetTime(&trStart, &trEnd);
MSR_INTEGER(m_idBaseStamp, (int)trStart); // dump low order 32 bits
m_pClock->GetTime(&m_trRenderStart);
MSR_INTEGER(0, (int)m_trRenderStart);
REFERENCE_TIME trStream;
trStream = m_trRenderStart-m_tStart; // convert reftime to stream time
MSR_INTEGER(0,(int)trStream);
const int trLate = (int)(trStream - trStart);
MSR_INTEGER(m_idBaseAccuracy, trLate/10000); // dump in mSec
#endif
} // OnRenderStart
// Called directly after drawing an image.
// calculate the time spent drawing and log it.
void CBaseRenderer::OnRenderEnd(IMediaSample *pMediaSample)
{
#ifdef PERF
REFERENCE_TIME trNow;
m_pClock->GetTime(&trNow);
MSR_INTEGER(0,(int)trNow);
int t = (int)((trNow - m_trRenderStart)/10000); // convert UNITS->msec
MSR_INTEGER(m_idBaseRenderTime, t);
#endif
} // OnRenderEnd
// Constructor must be passed the base renderer object
CRendererInputPin::CRendererInputPin(CBaseRenderer *pRenderer,
HRESULT *phr,
LPCWSTR pPinName) :
CBaseInputPin(NAME("Renderer pin"),
pRenderer,
&pRenderer->m_InterfaceLock,
(HRESULT *) phr,
pPinName)
{
m_pRenderer = pRenderer;
ASSERT(m_pRenderer);
}
// Signals end of data stream on the input pin
STDMETHODIMP CRendererInputPin::EndOfStream()
{
CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
// Make sure we're streaming ok
HRESULT hr = CheckStreaming();
if (hr != NOERROR) {
return hr;
}
// Pass it onto the renderer
hr = m_pRenderer->EndOfStream();
if (SUCCEEDED(hr)) {
hr = CBaseInputPin::EndOfStream();
}
return hr;
}
// Signals start of flushing on the input pin - we do the final reset end of
// stream with the renderer lock unlocked but with the interface lock locked
// We must do this because we call timeKillEvent, our timer callback method
// has to take the renderer lock to serialise our state. Therefore holding a
// renderer lock when calling timeKillEvent could cause a deadlock condition
STDMETHODIMP CRendererInputPin::BeginFlush()
{
CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
{
CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
CBaseInputPin::BeginFlush();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -