Tristan Matthews | 0a329cc | 2013-07-17 13:20:14 -0400 | [diff] [blame] | 1 | //------------------------------------------------------------------------------
|
| 2 | // File: RenBase.cpp
|
| 3 | //
|
| 4 | // Desc: DirectShow base classes.
|
| 5 | //
|
| 6 | // Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved.
|
| 7 | //------------------------------------------------------------------------------
|
| 8 |
|
| 9 | #include <pjmedia-videodev/config.h>
|
| 10 |
|
| 11 | #if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0
|
| 12 |
|
| 13 | #include <streams.h> // DirectShow base class definitions
|
| 14 | #include <mmsystem.h> // Needed for definition of timeGetTime
|
| 15 | #include <limits.h> // Standard data type limit definitions
|
| 16 | #include <measure.h> // Used for time critical log functions
|
| 17 |
|
| 18 | #pragma warning(disable:4355)
|
| 19 |
|
| 20 | // Helper function for clamping time differences
|
| 21 | int inline TimeDiff(REFERENCE_TIME rt)
|
| 22 | {
|
| 23 | if (rt < - (50 * UNITS)) {
|
| 24 | return -(50 * UNITS);
|
| 25 | } else
|
| 26 | if (rt > 50 * UNITS) {
|
| 27 | return 50 * UNITS;
|
| 28 | } else return (int)rt;
|
| 29 | }
|
| 30 |
|
| 31 | // Implements the CBaseRenderer class
|
| 32 |
|
| 33 | CBaseRenderer::CBaseRenderer(REFCLSID RenderClass, // CLSID for this renderer
|
| 34 | __in_opt LPCTSTR pName, // Debug ONLY description
|
| 35 | __inout_opt LPUNKNOWN pUnk, // Aggregated owner object
|
| 36 | __inout HRESULT *phr) : // General OLE return code
|
| 37 |
|
| 38 | CBaseFilter(pName,pUnk,&m_InterfaceLock,RenderClass),
|
| 39 | m_evComplete(TRUE, phr),
|
| 40 | m_RenderEvent(FALSE, phr),
|
| 41 | m_bAbort(FALSE),
|
| 42 | m_pPosition(NULL),
|
| 43 | m_ThreadSignal(TRUE, phr),
|
| 44 | m_bStreaming(FALSE),
|
| 45 | m_bEOS(FALSE),
|
| 46 | m_bEOSDelivered(FALSE),
|
| 47 | m_pMediaSample(NULL),
|
| 48 | m_dwAdvise(0),
|
| 49 | m_pQSink(NULL),
|
| 50 | m_pInputPin(NULL),
|
| 51 | m_bRepaintStatus(TRUE),
|
| 52 | m_SignalTime(0),
|
| 53 | m_bInReceive(FALSE),
|
| 54 | m_EndOfStreamTimer(0)
|
| 55 | {
|
| 56 | if (SUCCEEDED(*phr)) {
|
| 57 | Ready();
|
| 58 | #ifdef PERF
|
| 59 | m_idBaseStamp = MSR_REGISTER(TEXT("BaseRenderer: sample time stamp"));
|
| 60 | m_idBaseRenderTime = MSR_REGISTER(TEXT("BaseRenderer: draw time (msec)"));
|
| 61 | m_idBaseAccuracy = MSR_REGISTER(TEXT("BaseRenderer: Accuracy (msec)"));
|
| 62 | #endif
|
| 63 | }
|
| 64 | }
|
| 65 |
|
| 66 |
|
| 67 | // Delete the dynamically allocated IMediaPosition and IMediaSeeking helper
|
| 68 | // object. The object is created when somebody queries us. These are standard
|
| 69 | // control interfaces for seeking and setting start/stop positions and rates.
|
| 70 | // We will probably also have made an input pin based on CRendererInputPin
|
| 71 | // that has to be deleted, it's created when an enumerator calls our GetPin
|
| 72 |
|
| 73 | CBaseRenderer::~CBaseRenderer()
|
| 74 | {
|
| 75 | ASSERT(m_bStreaming == FALSE);
|
| 76 | ASSERT(m_EndOfStreamTimer == 0);
|
| 77 | StopStreaming();
|
| 78 | ClearPendingSample();
|
| 79 |
|
| 80 | // Delete any IMediaPosition implementation
|
| 81 |
|
| 82 | if (m_pPosition) {
|
| 83 | delete m_pPosition;
|
| 84 | m_pPosition = NULL;
|
| 85 | }
|
| 86 |
|
| 87 | // Delete any input pin created
|
| 88 |
|
| 89 | if (m_pInputPin) {
|
| 90 | delete m_pInputPin;
|
| 91 | m_pInputPin = NULL;
|
| 92 | }
|
| 93 |
|
| 94 | // Release any Quality sink
|
| 95 |
|
| 96 | ASSERT(m_pQSink == NULL);
|
| 97 | }
|
| 98 |
|
| 99 |
|
| 100 | // This returns the IMediaPosition and IMediaSeeking interfaces
|
| 101 |
|
| 102 | HRESULT CBaseRenderer::GetMediaPositionInterface(REFIID riid, __deref_out void **ppv)
|
| 103 | {
|
| 104 | CAutoLock cObjectCreationLock(&m_ObjectCreationLock);
|
| 105 | if (m_pPosition) {
|
| 106 | return m_pPosition->NonDelegatingQueryInterface(riid,ppv);
|
| 107 | }
|
| 108 |
|
| 109 | CBasePin *pPin = GetPin(0);
|
| 110 | if (NULL == pPin) {
|
| 111 | return E_OUTOFMEMORY;
|
| 112 | }
|
| 113 |
|
| 114 | HRESULT hr = NOERROR;
|
| 115 |
|
| 116 | // Create implementation of this dynamically since sometimes we may
|
| 117 | // never try and do a seek. The helper object implements a position
|
| 118 | // control interface (IMediaPosition) which in fact simply takes the
|
| 119 | // calls normally from the filter graph and passes them upstream
|
| 120 |
|
| 121 | m_pPosition = new CRendererPosPassThru(NAME("Renderer CPosPassThru"),
|
| 122 | CBaseFilter::GetOwner(),
|
| 123 | (HRESULT *) &hr,
|
| 124 | pPin);
|
| 125 | if (m_pPosition == NULL) {
|
| 126 | return E_OUTOFMEMORY;
|
| 127 | }
|
| 128 |
|
| 129 | if (FAILED(hr)) {
|
| 130 | delete m_pPosition;
|
| 131 | m_pPosition = NULL;
|
| 132 | return E_NOINTERFACE;
|
| 133 | }
|
| 134 | return GetMediaPositionInterface(riid,ppv);
|
| 135 | }
|
| 136 |
|
| 137 |
|
| 138 | // Overriden to say what interfaces we support and where
|
| 139 |
|
| 140 | STDMETHODIMP CBaseRenderer::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv)
|
| 141 | {
|
| 142 | // Do we have this interface
|
| 143 |
|
| 144 | if (riid == IID_IMediaPosition || riid == IID_IMediaSeeking) {
|
| 145 | return GetMediaPositionInterface(riid,ppv);
|
| 146 | } else {
|
| 147 | return CBaseFilter::NonDelegatingQueryInterface(riid,ppv);
|
| 148 | }
|
| 149 | }
|
| 150 |
|
| 151 |
|
| 152 | // This is called whenever we change states, we have a manual reset event that
|
| 153 | // is signalled whenever we don't won't the source filter thread to wait in us
|
| 154 | // (such as in a stopped state) and likewise is not signalled whenever it can
|
| 155 | // wait (during paused and running) this function sets or resets the thread
|
| 156 | // event. The event is used to stop source filter threads waiting in Receive
|
| 157 |
|
| 158 | HRESULT CBaseRenderer::SourceThreadCanWait(BOOL bCanWait)
|
| 159 | {
|
| 160 | if (bCanWait == TRUE) {
|
| 161 | m_ThreadSignal.Reset();
|
| 162 | } else {
|
| 163 | m_ThreadSignal.Set();
|
| 164 | }
|
| 165 | return NOERROR;
|
| 166 | }
|
| 167 |
|
| 168 |
|
| 169 | #ifdef DEBUG
|
| 170 | // Dump the current renderer state to the debug terminal. The hardest part of
|
| 171 | // the renderer is the window where we unlock everything to wait for a clock
|
| 172 | // to signal it is time to draw or for the application to cancel everything
|
| 173 | // by stopping the filter. If we get things wrong we can leave the thread in
|
| 174 | // WaitForRenderTime with no way for it to ever get out and we will deadlock
|
| 175 |
|
| 176 | void CBaseRenderer::DisplayRendererState()
|
| 177 | {
|
| 178 | DbgLog((LOG_TIMING, 1, TEXT("\nTimed out in WaitForRenderTime")));
|
| 179 |
|
| 180 | // No way should this be signalled at this point
|
| 181 |
|
| 182 | BOOL bSignalled = m_ThreadSignal.Check();
|
| 183 | DbgLog((LOG_TIMING, 1, TEXT("Signal sanity check %d"),bSignalled));
|
| 184 |
|
| 185 | // Now output the current renderer state variables
|
| 186 |
|
| 187 | DbgLog((LOG_TIMING, 1, TEXT("Filter state %d"),m_State));
|
| 188 |
|
| 189 | DbgLog((LOG_TIMING, 1, TEXT("Abort flag %d"),m_bAbort));
|
| 190 |
|
| 191 | DbgLog((LOG_TIMING, 1, TEXT("Streaming flag %d"),m_bStreaming));
|
| 192 |
|
| 193 | DbgLog((LOG_TIMING, 1, TEXT("Clock advise link %d"),m_dwAdvise));
|
| 194 |
|
| 195 | DbgLog((LOG_TIMING, 1, TEXT("Current media sample %x"),m_pMediaSample));
|
| 196 |
|
| 197 | DbgLog((LOG_TIMING, 1, TEXT("EOS signalled %d"),m_bEOS));
|
| 198 |
|
| 199 | DbgLog((LOG_TIMING, 1, TEXT("EOS delivered %d"),m_bEOSDelivered));
|
| 200 |
|
| 201 | DbgLog((LOG_TIMING, 1, TEXT("Repaint status %d"),m_bRepaintStatus));
|
| 202 |
|
| 203 |
|
| 204 | // Output the delayed end of stream timer information
|
| 205 |
|
| 206 | DbgLog((LOG_TIMING, 1, TEXT("End of stream timer %x"),m_EndOfStreamTimer));
|
| 207 |
|
| 208 | DbgLog((LOG_TIMING, 1, TEXT("Deliver time %s"),CDisp((LONGLONG)m_SignalTime)));
|
| 209 |
|
| 210 |
|
| 211 | // Should never timeout during a flushing state
|
| 212 |
|
| 213 | BOOL bFlushing = m_pInputPin->IsFlushing();
|
| 214 | DbgLog((LOG_TIMING, 1, TEXT("Flushing sanity check %d"),bFlushing));
|
| 215 |
|
| 216 | // Display the time we were told to start at
|
| 217 | DbgLog((LOG_TIMING, 1, TEXT("Last run time %s"),CDisp((LONGLONG)m_tStart.m_time)));
|
| 218 |
|
| 219 | // Have we got a reference clock
|
| 220 | if (m_pClock == NULL) return;
|
| 221 |
|
| 222 | // Get the current time from the wall clock
|
| 223 |
|
| 224 | CRefTime CurrentTime,StartTime,EndTime;
|
| 225 | m_pClock->GetTime((REFERENCE_TIME*) &CurrentTime);
|
| 226 | CRefTime Offset = CurrentTime - m_tStart;
|
| 227 |
|
| 228 | // Display the current time from the clock
|
| 229 |
|
| 230 | DbgLog((LOG_TIMING, 1, TEXT("Clock time %s"),CDisp((LONGLONG)CurrentTime.m_time)));
|
| 231 |
|
| 232 | DbgLog((LOG_TIMING, 1, TEXT("Time difference %dms"),Offset.Millisecs()));
|
| 233 |
|
| 234 |
|
| 235 | // Do we have a sample ready to render
|
| 236 | if (m_pMediaSample == NULL) return;
|
| 237 |
|
| 238 | m_pMediaSample->GetTime((REFERENCE_TIME*)&StartTime, (REFERENCE_TIME*)&EndTime);
|
| 239 | DbgLog((LOG_TIMING, 1, TEXT("Next sample stream times (Start %d End %d ms)"),
|
| 240 | StartTime.Millisecs(),EndTime.Millisecs()));
|
| 241 |
|
| 242 | // Calculate how long it is until it is due for rendering
|
| 243 | CRefTime Wait = (m_tStart + StartTime) - CurrentTime;
|
| 244 | DbgLog((LOG_TIMING, 1, TEXT("Wait required %d ms"),Wait.Millisecs()));
|
| 245 | }
|
| 246 | #endif
|
| 247 |
|
| 248 |
|
| 249 | // Wait until the clock sets the timer event or we're otherwise signalled. We
|
| 250 | // set an arbitrary timeout for this wait and if it fires then we display the
|
| 251 | // current renderer state on the debugger. It will often fire if the filter's
|
| 252 | // left paused in an application however it may also fire during stress tests
|
| 253 | // if the synchronisation with application seeks and state changes is faulty
|
| 254 |
|
| 255 | #define RENDER_TIMEOUT 10000
|
| 256 |
|
| 257 | HRESULT CBaseRenderer::WaitForRenderTime()
|
| 258 | {
|
| 259 | HANDLE WaitObjects[] = { m_ThreadSignal, m_RenderEvent };
|
| 260 | DWORD Result = WAIT_TIMEOUT;
|
| 261 |
|
| 262 | // Wait for either the time to arrive or for us to be stopped
|
| 263 |
|
| 264 | OnWaitStart();
|
| 265 | while (Result == WAIT_TIMEOUT) {
|
| 266 | Result = WaitForMultipleObjects(2,WaitObjects,FALSE,RENDER_TIMEOUT);
|
| 267 |
|
| 268 | #ifdef DEBUG
|
| 269 | if (Result == WAIT_TIMEOUT) DisplayRendererState();
|
| 270 | #endif
|
| 271 |
|
| 272 | }
|
| 273 | OnWaitEnd();
|
| 274 |
|
| 275 | // We may have been awoken without the timer firing
|
| 276 |
|
| 277 | if (Result == WAIT_OBJECT_0) {
|
| 278 | return VFW_E_STATE_CHANGED;
|
| 279 | }
|
| 280 |
|
| 281 | SignalTimerFired();
|
| 282 | return NOERROR;
|
| 283 | }
|
| 284 |
|
| 285 |
|
| 286 | // Poll waiting for Receive to complete. This really matters when
|
| 287 | // Receive may set the palette and cause window messages
|
| 288 | // The problem is that if we don't really wait for a renderer to
|
| 289 | // stop processing we can deadlock waiting for a transform which
|
| 290 | // is calling the renderer's Receive() method because the transform's
|
| 291 | // Stop method doesn't know to process window messages to unblock
|
| 292 | // the renderer's Receive processing
|
| 293 | void CBaseRenderer::WaitForReceiveToComplete()
|
| 294 | {
|
| 295 | for (;;) {
|
| 296 | if (!m_bInReceive) {
|
| 297 | break;
|
| 298 | }
|
| 299 |
|
| 300 | MSG msg;
|
| 301 | // Receive all interthread snedmessages
|
| 302 | PeekMessage(&msg, NULL, WM_NULL, WM_NULL, PM_NOREMOVE);
|
| 303 |
|
| 304 | Sleep(1);
|
| 305 | }
|
| 306 |
|
| 307 | // If the wakebit for QS_POSTMESSAGE is set, the PeekMessage call
|
| 308 | // above just cleared the changebit which will cause some messaging
|
| 309 | // calls to block (waitMessage, MsgWaitFor...) now.
|
| 310 | // Post a dummy message to set the QS_POSTMESSAGE bit again
|
| 311 | if (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) & QS_POSTMESSAGE) {
|
| 312 | // Send dummy message
|
| 313 | PostThreadMessage(GetCurrentThreadId(), WM_NULL, 0, 0);
|
| 314 | }
|
| 315 | }
|
| 316 |
|
| 317 | // A filter can have four discrete states, namely Stopped, Running, Paused,
|
| 318 | // Intermediate. We are in an intermediate state if we are currently trying
|
| 319 | // to pause but haven't yet got the first sample (or if we have been flushed
|
| 320 | // in paused state and therefore still have to wait for a sample to arrive)
|
| 321 |
|
| 322 | // This class contains an event called m_evComplete which is signalled when
|
| 323 | // the current state is completed and is not signalled when we are waiting to
|
| 324 | // complete the last state transition. As mentioned above the only time we
|
| 325 | // use this at the moment is when we wait for a media sample in paused state
|
| 326 | // If while we are waiting we receive an end of stream notification from the
|
| 327 | // source filter then we know no data is imminent so we can reset the event
|
| 328 | // This means that when we transition to paused the source filter must call
|
| 329 | // end of stream on us or send us an image otherwise we'll hang indefinately
|
| 330 |
|
| 331 |
|
| 332 | // Simple internal way of getting the real state
|
| 333 |
|
| 334 | FILTER_STATE CBaseRenderer::GetRealState() {
|
| 335 | return m_State;
|
| 336 | }
|
| 337 |
|
| 338 |
|
| 339 | // The renderer doesn't complete the full transition to paused states until
|
| 340 | // it has got one media sample to render. If you ask it for its state while
|
| 341 | // it's waiting it will return the state along with VFW_S_STATE_INTERMEDIATE
|
| 342 |
|
| 343 | STDMETHODIMP CBaseRenderer::GetState(DWORD dwMSecs,FILTER_STATE *State)
|
| 344 | {
|
| 345 | CheckPointer(State,E_POINTER);
|
| 346 |
|
| 347 | if (WaitDispatchingMessages(m_evComplete, dwMSecs) == WAIT_TIMEOUT) {
|
| 348 | *State = m_State;
|
| 349 | return VFW_S_STATE_INTERMEDIATE;
|
| 350 | }
|
| 351 | *State = m_State;
|
| 352 | return NOERROR;
|
| 353 | }
|
| 354 |
|
| 355 |
|
| 356 | // If we're pausing and we have no samples we don't complete the transition
|
| 357 | // to State_Paused and we return S_FALSE. However if the m_bAbort flag has
|
| 358 | // been set then all samples are rejected so there is no point waiting for
|
| 359 | // one. If we do have a sample then return NOERROR. We will only ever return
|
| 360 | // VFW_S_STATE_INTERMEDIATE from GetState after being paused with no sample
|
| 361 | // (calling GetState after either being stopped or Run will NOT return this)
|
| 362 |
|
| 363 | HRESULT CBaseRenderer::CompleteStateChange(FILTER_STATE OldState)
|
| 364 | {
|
| 365 | // Allow us to be paused when disconnected
|
| 366 |
|
| 367 | if (m_pInputPin->IsConnected() == FALSE) {
|
| 368 | Ready();
|
| 369 | return S_OK;
|
| 370 | }
|
| 371 |
|
| 372 | // Have we run off the end of stream
|
| 373 |
|
| 374 | if (IsEndOfStream() == TRUE) {
|
| 375 | Ready();
|
| 376 | return S_OK;
|
| 377 | }
|
| 378 |
|
| 379 | // Make sure we get fresh data after being stopped
|
| 380 |
|
| 381 | if (HaveCurrentSample() == TRUE) {
|
| 382 | if (OldState != State_Stopped) {
|
| 383 | Ready();
|
| 384 | return S_OK;
|
| 385 | }
|
| 386 | }
|
| 387 | NotReady();
|
| 388 | return S_FALSE;
|
| 389 | }
|
| 390 |
|
| 391 |
|
| 392 | // When we stop the filter the things we do are:-
|
| 393 |
|
| 394 | // Decommit the allocator being used in the connection
|
| 395 | // Release the source filter if it's waiting in Receive
|
| 396 | // Cancel any advise link we set up with the clock
|
| 397 | // Any end of stream signalled is now obsolete so reset
|
| 398 | // Allow us to be stopped when we are not connected
|
| 399 |
|
| 400 | STDMETHODIMP CBaseRenderer::Stop()
|
| 401 | {
|
| 402 | CAutoLock cRendererLock(&m_InterfaceLock);
|
| 403 |
|
| 404 | // Make sure there really is a state change
|
| 405 |
|
| 406 | if (m_State == State_Stopped) {
|
| 407 | return NOERROR;
|
| 408 | }
|
| 409 |
|
| 410 | // Is our input pin connected
|
| 411 |
|
| 412 | if (m_pInputPin->IsConnected() == FALSE) {
|
| 413 | NOTE("Input pin is not connected");
|
| 414 | m_State = State_Stopped;
|
| 415 | return NOERROR;
|
| 416 | }
|
| 417 |
|
| 418 | CBaseFilter::Stop();
|
| 419 |
|
| 420 | // If we are going into a stopped state then we must decommit whatever
|
| 421 | // allocator we are using it so that any source filter waiting in the
|
| 422 | // GetBuffer can be released and unlock themselves for a state change
|
| 423 |
|
| 424 | if (m_pInputPin->Allocator()) {
|
| 425 | m_pInputPin->Allocator()->Decommit();
|
| 426 | }
|
| 427 |
|
| 428 | // Cancel any scheduled rendering
|
| 429 |
|
| 430 | SetRepaintStatus(TRUE);
|
| 431 | StopStreaming();
|
| 432 | SourceThreadCanWait(FALSE);
|
| 433 | ResetEndOfStream();
|
| 434 | CancelNotification();
|
| 435 |
|
| 436 | // There should be no outstanding clock advise
|
| 437 | ASSERT(CancelNotification() == S_FALSE);
|
| 438 | ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
|
| 439 | ASSERT(m_EndOfStreamTimer == 0);
|
| 440 |
|
| 441 | Ready();
|
| 442 | WaitForReceiveToComplete();
|
| 443 | m_bAbort = FALSE;
|
| 444 |
|
| 445 | return NOERROR;
|
| 446 | }
|
| 447 |
|
| 448 |
|
| 449 | // When we pause the filter the things we do are:-
|
| 450 |
|
| 451 | // Commit the allocator being used in the connection
|
| 452 | // Allow a source filter thread to wait in Receive
|
| 453 | // Cancel any clock advise link (we may be running)
|
| 454 | // Possibly complete the state change if we have data
|
| 455 | // Allow us to be paused when we are not connected
|
| 456 |
|
| 457 | STDMETHODIMP CBaseRenderer::Pause()
|
| 458 | {
|
| 459 | CAutoLock cRendererLock(&m_InterfaceLock);
|
| 460 | FILTER_STATE OldState = m_State;
|
| 461 | ASSERT(m_pInputPin->IsFlushing() == FALSE);
|
| 462 |
|
| 463 | // Make sure there really is a state change
|
| 464 |
|
| 465 | if (m_State == State_Paused) {
|
| 466 | return CompleteStateChange(State_Paused);
|
| 467 | }
|
| 468 |
|
| 469 | // Has our input pin been connected
|
| 470 |
|
| 471 | if (m_pInputPin->IsConnected() == FALSE) {
|
| 472 | NOTE("Input pin is not connected");
|
| 473 | m_State = State_Paused;
|
| 474 | return CompleteStateChange(State_Paused);
|
| 475 | }
|
| 476 |
|
| 477 | // Pause the base filter class
|
| 478 |
|
| 479 | HRESULT hr = CBaseFilter::Pause();
|
| 480 | if (FAILED(hr)) {
|
| 481 | NOTE("Pause failed");
|
| 482 | return hr;
|
| 483 | }
|
| 484 |
|
| 485 | // Enable EC_REPAINT events again
|
| 486 |
|
| 487 | SetRepaintStatus(TRUE);
|
| 488 | StopStreaming();
|
| 489 | SourceThreadCanWait(TRUE);
|
| 490 | CancelNotification();
|
| 491 | ResetEndOfStreamTimer();
|
| 492 |
|
| 493 | // If we are going into a paused state then we must commit whatever
|
| 494 | // allocator we are using it so that any source filter can call the
|
| 495 | // GetBuffer and expect to get a buffer without returning an error
|
| 496 |
|
| 497 | if (m_pInputPin->Allocator()) {
|
| 498 | m_pInputPin->Allocator()->Commit();
|
| 499 | }
|
| 500 |
|
| 501 | // There should be no outstanding advise
|
| 502 | ASSERT(CancelNotification() == S_FALSE);
|
| 503 | ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
|
| 504 | ASSERT(m_EndOfStreamTimer == 0);
|
| 505 | ASSERT(m_pInputPin->IsFlushing() == FALSE);
|
| 506 |
|
| 507 | // When we come out of a stopped state we must clear any image we were
|
| 508 | // holding onto for frame refreshing. Since renderers see state changes
|
| 509 | // first we can reset ourselves ready to accept the source thread data
|
| 510 | // Paused or running after being stopped causes the current position to
|
| 511 | // be reset so we're not interested in passing end of stream signals
|
| 512 |
|
| 513 | if (OldState == State_Stopped) {
|
| 514 | m_bAbort = FALSE;
|
| 515 | ClearPendingSample();
|
| 516 | }
|
| 517 | return CompleteStateChange(OldState);
|
| 518 | }
|
| 519 |
|
| 520 |
|
| 521 | // When we run the filter the things we do are:-
|
| 522 |
|
| 523 | // Commit the allocator being used in the connection
|
| 524 | // Allow a source filter thread to wait in Receive
|
| 525 | // Signal the render event just to get us going
|
| 526 | // Start the base class by calling StartStreaming
|
| 527 | // Allow us to be run when we are not connected
|
| 528 | // Signal EC_COMPLETE if we are not connected
|
| 529 |
|
| 530 | STDMETHODIMP CBaseRenderer::Run(REFERENCE_TIME StartTime)
|
| 531 | {
|
| 532 | CAutoLock cRendererLock(&m_InterfaceLock);
|
| 533 | FILTER_STATE OldState = m_State;
|
| 534 |
|
| 535 | // Make sure there really is a state change
|
| 536 |
|
| 537 | if (m_State == State_Running) {
|
| 538 | return NOERROR;
|
| 539 | }
|
| 540 |
|
| 541 | // Send EC_COMPLETE if we're not connected
|
| 542 |
|
| 543 | if (m_pInputPin->IsConnected() == FALSE) {
|
| 544 | NotifyEvent(EC_COMPLETE,S_OK,(LONG_PTR)(IBaseFilter *)this);
|
| 545 | m_State = State_Running;
|
| 546 | return NOERROR;
|
| 547 | }
|
| 548 |
|
| 549 | Ready();
|
| 550 |
|
| 551 | // Pause the base filter class
|
| 552 |
|
| 553 | HRESULT hr = CBaseFilter::Run(StartTime);
|
| 554 | if (FAILED(hr)) {
|
| 555 | NOTE("Run failed");
|
| 556 | return hr;
|
| 557 | }
|
| 558 |
|
| 559 | // Allow the source thread to wait
|
| 560 | ASSERT(m_pInputPin->IsFlushing() == FALSE);
|
| 561 | SourceThreadCanWait(TRUE);
|
| 562 | SetRepaintStatus(FALSE);
|
| 563 |
|
| 564 | // There should be no outstanding advise
|
| 565 | ASSERT(CancelNotification() == S_FALSE);
|
| 566 | ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
|
| 567 | ASSERT(m_EndOfStreamTimer == 0);
|
| 568 | ASSERT(m_pInputPin->IsFlushing() == FALSE);
|
| 569 |
|
| 570 | // If we are going into a running state then we must commit whatever
|
| 571 | // allocator we are using it so that any source filter can call the
|
| 572 | // GetBuffer and expect to get a buffer without returning an error
|
| 573 |
|
| 574 | if (m_pInputPin->Allocator()) {
|
| 575 | m_pInputPin->Allocator()->Commit();
|
| 576 | }
|
| 577 |
|
| 578 | // When we come out of a stopped state we must clear any image we were
|
| 579 | // holding onto for frame refreshing. Since renderers see state changes
|
| 580 | // first we can reset ourselves ready to accept the source thread data
|
| 581 | // Paused or running after being stopped causes the current position to
|
| 582 | // be reset so we're not interested in passing end of stream signals
|
| 583 |
|
| 584 | if (OldState == State_Stopped) {
|
| 585 | m_bAbort = FALSE;
|
| 586 | ClearPendingSample();
|
| 587 | }
|
| 588 | return StartStreaming();
|
| 589 | }
|
| 590 |
|
| 591 |
|
| 592 | // Return the number of input pins we support
|
| 593 |
|
| 594 | int CBaseRenderer::GetPinCount()
|
| 595 | {
|
| 596 | if (m_pInputPin == NULL) {
|
| 597 | // Try to create it
|
| 598 | (void)GetPin(0);
|
| 599 | }
|
| 600 | return m_pInputPin != NULL ? 1 : 0;
|
| 601 | }
|
| 602 |
|
| 603 |
|
| 604 | // We only support one input pin and it is numbered zero
|
| 605 |
|
| 606 | CBasePin *CBaseRenderer::GetPin(int n)
|
| 607 | {
|
| 608 | CAutoLock cObjectCreationLock(&m_ObjectCreationLock);
|
| 609 |
|
| 610 | // Should only ever be called with zero
|
| 611 | ASSERT(n == 0);
|
| 612 |
|
| 613 | if (n != 0) {
|
| 614 | return NULL;
|
| 615 | }
|
| 616 |
|
| 617 | // Create the input pin if not already done so
|
| 618 |
|
| 619 | if (m_pInputPin == NULL) {
|
| 620 |
|
| 621 | // hr must be initialized to NOERROR because
|
| 622 | // CRendererInputPin's constructor only changes
|
| 623 | // hr's value if an error occurs.
|
| 624 | HRESULT hr = NOERROR;
|
| 625 |
|
| 626 | m_pInputPin = new CRendererInputPin(this,&hr,L"In");
|
| 627 | if (NULL == m_pInputPin) {
|
| 628 | return NULL;
|
| 629 | }
|
| 630 |
|
| 631 | if (FAILED(hr)) {
|
| 632 | delete m_pInputPin;
|
| 633 | m_pInputPin = NULL;
|
| 634 | return NULL;
|
| 635 | }
|
| 636 | }
|
| 637 | return m_pInputPin;
|
| 638 | }
|
| 639 |
|
| 640 |
|
| 641 | // If "In" then return the IPin for our input pin, otherwise NULL and error
|
| 642 |
|
| 643 | STDMETHODIMP CBaseRenderer::FindPin(LPCWSTR Id, __deref_out IPin **ppPin)
|
| 644 | {
|
| 645 | CheckPointer(ppPin,E_POINTER);
|
| 646 |
|
| 647 | if (0==lstrcmpW(Id,L"In")) {
|
| 648 | *ppPin = GetPin(0);
|
| 649 | if (*ppPin) {
|
| 650 | (*ppPin)->AddRef();
|
| 651 | } else {
|
| 652 | return E_OUTOFMEMORY;
|
| 653 | }
|
| 654 | } else {
|
| 655 | *ppPin = NULL;
|
| 656 | return VFW_E_NOT_FOUND;
|
| 657 | }
|
| 658 | return NOERROR;
|
| 659 | }
|
| 660 |
|
| 661 |
|
| 662 | // Called when the input pin receives an EndOfStream notification. If we have
|
| 663 | // not got a sample, then notify EC_COMPLETE now. If we have samples, then set
|
| 664 | // m_bEOS and check for this on completing samples. If we're waiting to pause
|
| 665 | // then complete the transition to paused state by setting the state event
|
| 666 |
|
| 667 | HRESULT CBaseRenderer::EndOfStream()
|
| 668 | {
|
| 669 | // Ignore these calls if we are stopped
|
| 670 |
|
| 671 | if (m_State == State_Stopped) {
|
| 672 | return NOERROR;
|
| 673 | }
|
| 674 |
|
| 675 | // If we have a sample then wait for it to be rendered
|
| 676 |
|
| 677 | m_bEOS = TRUE;
|
| 678 | if (m_pMediaSample) {
|
| 679 | return NOERROR;
|
| 680 | }
|
| 681 |
|
| 682 | // If we are waiting for pause then we are now ready since we cannot now
|
| 683 | // carry on waiting for a sample to arrive since we are being told there
|
| 684 | // won't be any. This sets an event that the GetState function picks up
|
| 685 |
|
| 686 | Ready();
|
| 687 |
|
| 688 | // Only signal completion now if we are running otherwise queue it until
|
| 689 | // we do run in StartStreaming. This is used when we seek because a seek
|
| 690 | // causes a pause where early notification of completion is misleading
|
| 691 |
|
| 692 | if (m_bStreaming) {
|
| 693 | SendEndOfStream();
|
| 694 | }
|
| 695 | return NOERROR;
|
| 696 | }
|
| 697 |
|
| 698 |
|
| 699 | // When we are told to flush we should release the source thread
|
| 700 |
|
| 701 | HRESULT CBaseRenderer::BeginFlush()
|
| 702 | {
|
| 703 | // If paused then report state intermediate until we get some data
|
| 704 |
|
| 705 | if (m_State == State_Paused) {
|
| 706 | NotReady();
|
| 707 | }
|
| 708 |
|
| 709 | SourceThreadCanWait(FALSE);
|
| 710 | CancelNotification();
|
| 711 | ClearPendingSample();
|
| 712 | // Wait for Receive to complete
|
| 713 | WaitForReceiveToComplete();
|
| 714 |
|
| 715 | return NOERROR;
|
| 716 | }
|
| 717 |
|
| 718 |
|
| 719 | // After flushing the source thread can wait in Receive again
|
| 720 |
|
| 721 | HRESULT CBaseRenderer::EndFlush()
|
| 722 | {
|
| 723 | // Reset the current sample media time
|
| 724 | if (m_pPosition) m_pPosition->ResetMediaTime();
|
| 725 |
|
| 726 | // There should be no outstanding advise
|
| 727 |
|
| 728 | ASSERT(CancelNotification() == S_FALSE);
|
| 729 | SourceThreadCanWait(TRUE);
|
| 730 | return NOERROR;
|
| 731 | }
|
| 732 |
|
| 733 |
|
| 734 | // We can now send EC_REPAINTs if so required
|
| 735 |
|
| 736 | HRESULT CBaseRenderer::CompleteConnect(IPin *pReceivePin)
|
| 737 | {
|
| 738 | // The caller should always hold the interface lock because
|
| 739 | // the function uses CBaseFilter::m_State.
|
| 740 | ASSERT(CritCheckIn(&m_InterfaceLock));
|
| 741 |
|
| 742 | m_bAbort = FALSE;
|
| 743 |
|
| 744 | if (State_Running == GetRealState()) {
|
| 745 | HRESULT hr = StartStreaming();
|
| 746 | if (FAILED(hr)) {
|
| 747 | return hr;
|
| 748 | }
|
| 749 |
|
| 750 | SetRepaintStatus(FALSE);
|
| 751 | } else {
|
| 752 | SetRepaintStatus(TRUE);
|
| 753 | }
|
| 754 |
|
| 755 | return NOERROR;
|
| 756 | }
|
| 757 |
|
| 758 |
|
| 759 | // Called when we go paused or running
|
| 760 |
|
| 761 | HRESULT CBaseRenderer::Active()
|
| 762 | {
|
| 763 | return NOERROR;
|
| 764 | }
|
| 765 |
|
| 766 |
|
| 767 | // Called when we go into a stopped state
|
| 768 |
|
| 769 | HRESULT CBaseRenderer::Inactive()
|
| 770 | {
|
| 771 | if (m_pPosition) {
|
| 772 | m_pPosition->ResetMediaTime();
|
| 773 | }
|
| 774 | // People who derive from this may want to override this behaviour
|
| 775 | // to keep hold of the sample in some circumstances
|
| 776 | ClearPendingSample();
|
| 777 |
|
| 778 | return NOERROR;
|
| 779 | }
|
| 780 |
|
| 781 |
|
| 782 | // Tell derived classes about the media type agreed
|
| 783 |
|
| 784 | HRESULT CBaseRenderer::SetMediaType(const CMediaType *pmt)
|
| 785 | {
|
| 786 | return NOERROR;
|
| 787 | }
|
| 788 |
|
| 789 |
|
| 790 | // When we break the input pin connection we should reset the EOS flags. When
|
| 791 | // we are asked for either IMediaPosition or IMediaSeeking we will create a
|
| 792 | // CPosPassThru object to handles media time pass through. When we're handed
|
| 793 | // samples we store (by calling CPosPassThru::RegisterMediaTime) their media
|
| 794 | // times so we can then return a real current position of data being rendered
|
| 795 |
|
| 796 | HRESULT CBaseRenderer::BreakConnect()
|
| 797 | {
|
| 798 | // Do we have a quality management sink
|
| 799 |
|
| 800 | if (m_pQSink) {
|
| 801 | m_pQSink->Release();
|
| 802 | m_pQSink = NULL;
|
| 803 | }
|
| 804 |
|
| 805 | // Check we have a valid connection
|
| 806 |
|
| 807 | if (m_pInputPin->IsConnected() == FALSE) {
|
| 808 | return S_FALSE;
|
| 809 | }
|
| 810 |
|
| 811 | // Check we are stopped before disconnecting
|
| 812 | if (m_State != State_Stopped && !m_pInputPin->CanReconnectWhenActive()) {
|
| 813 | return VFW_E_NOT_STOPPED;
|
| 814 | }
|
| 815 |
|
| 816 | SetRepaintStatus(FALSE);
|
| 817 | ResetEndOfStream();
|
| 818 | ClearPendingSample();
|
| 819 | m_bAbort = FALSE;
|
| 820 |
|
| 821 | if (State_Running == m_State) {
|
| 822 | StopStreaming();
|
| 823 | }
|
| 824 |
|
| 825 | return NOERROR;
|
| 826 | }
|
| 827 |
|
| 828 |
|
| 829 | // Retrieves the sample times for this samples (note the sample times are
|
| 830 | // passed in by reference not value). We return S_FALSE to say schedule this
|
| 831 | // sample according to the times on the sample. We also return S_OK in
|
| 832 | // which case the object should simply render the sample data immediately
|
| 833 |
|
| 834 | HRESULT CBaseRenderer::GetSampleTimes(IMediaSample *pMediaSample,
|
| 835 | __out REFERENCE_TIME *pStartTime,
|
| 836 | __out REFERENCE_TIME *pEndTime)
|
| 837 | {
|
| 838 | ASSERT(m_dwAdvise == 0);
|
| 839 | ASSERT(pMediaSample);
|
| 840 |
|
| 841 | // If the stop time for this sample is before or the same as start time,
|
| 842 | // then just ignore it (release it) and schedule the next one in line
|
| 843 | // Source filters should always fill in the start and end times properly!
|
| 844 |
|
| 845 | if (SUCCEEDED(pMediaSample->GetTime(pStartTime, pEndTime))) {
|
| 846 | if (*pEndTime < *pStartTime) {
|
| 847 | return VFW_E_START_TIME_AFTER_END;
|
| 848 | }
|
| 849 | } else {
|
| 850 | // no time set in the sample... draw it now?
|
| 851 | return S_OK;
|
| 852 | }
|
| 853 |
|
| 854 | // Can't synchronise without a clock so we return S_OK which tells the
|
| 855 | // caller that the sample should be rendered immediately without going
|
| 856 | // through the overhead of setting a timer advise link with the clock
|
| 857 |
|
| 858 | if (m_pClock == NULL) {
|
| 859 | return S_OK;
|
| 860 | }
|
| 861 | return ShouldDrawSampleNow(pMediaSample,pStartTime,pEndTime);
|
| 862 | }
|
| 863 |
|
| 864 |
|
| 865 | // By default all samples are drawn according to their time stamps so we
|
| 866 | // return S_FALSE. Returning S_OK means draw immediately, this is used
|
| 867 | // by the derived video renderer class in its quality management.
|
| 868 |
|
| 869 | HRESULT CBaseRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample,
|
| 870 | __out REFERENCE_TIME *ptrStart,
|
| 871 | __out REFERENCE_TIME *ptrEnd)
|
| 872 | {
|
| 873 | return S_FALSE;
|
| 874 | }
|
| 875 |
|
| 876 |
|
| 877 | // We must always reset the current advise time to zero after a timer fires
|
| 878 | // because there are several possible ways which lead us not to do any more
|
| 879 | // scheduling such as the pending image being cleared after state changes
|
| 880 |
|
| 881 | void CBaseRenderer::SignalTimerFired()
|
| 882 | {
|
| 883 | m_dwAdvise = 0;
|
| 884 | }
|
| 885 |
|
| 886 |
|
| 887 | // Cancel any notification currently scheduled. This is called by the owning
|
| 888 | // window object when it is told to stop streaming. If there is no timer link
|
| 889 | // outstanding then calling this is benign otherwise we go ahead and cancel
|
| 890 | // We must always reset the render event as the quality management code can
|
| 891 | // signal immediate rendering by setting the event without setting an advise
|
| 892 | // link. If we're subsequently stopped and run the first attempt to setup an
|
| 893 | // advise link with the reference clock will find the event still signalled
|
| 894 |
|
| 895 | HRESULT CBaseRenderer::CancelNotification()
|
| 896 | {
|
| 897 | ASSERT(m_dwAdvise == 0 || m_pClock);
|
| 898 | DWORD_PTR dwAdvise = m_dwAdvise;
|
| 899 |
|
| 900 | // Have we a live advise link
|
| 901 |
|
| 902 | if (m_dwAdvise) {
|
| 903 | m_pClock->Unadvise(m_dwAdvise);
|
| 904 | SignalTimerFired();
|
| 905 | ASSERT(m_dwAdvise == 0);
|
| 906 | }
|
| 907 |
|
| 908 | // Clear the event and return our status
|
| 909 |
|
| 910 | m_RenderEvent.Reset();
|
| 911 | return (dwAdvise ? S_OK : S_FALSE);
|
| 912 | }
|
| 913 |
|
| 914 |
|
| 915 | // Responsible for setting up one shot advise links with the clock
|
| 916 | // Return FALSE if the sample is to be dropped (not drawn at all)
|
| 917 | // Return TRUE if the sample is to be drawn and in this case also
|
| 918 | // arrange for m_RenderEvent to be set at the appropriate time
|
| 919 |
|
| 920 | BOOL CBaseRenderer::ScheduleSample(IMediaSample *pMediaSample)
|
| 921 | {
|
| 922 | REFERENCE_TIME StartSample, EndSample;
|
| 923 |
|
| 924 | // Is someone pulling our leg
|
| 925 |
|
| 926 | if (pMediaSample == NULL) {
|
| 927 | return FALSE;
|
| 928 | }
|
| 929 |
|
| 930 | // Get the next sample due up for rendering. If there aren't any ready
|
| 931 | // then GetNextSampleTimes returns an error. If there is one to be done
|
| 932 | // then it succeeds and yields the sample times. If it is due now then
|
| 933 | // it returns S_OK other if it's to be done when due it returns S_FALSE
|
| 934 |
|
| 935 | HRESULT hr = GetSampleTimes(pMediaSample, &StartSample, &EndSample);
|
| 936 | if (FAILED(hr)) {
|
| 937 | return FALSE;
|
| 938 | }
|
| 939 |
|
| 940 | // If we don't have a reference clock then we cannot set up the advise
|
| 941 | // time so we simply set the event indicating an image to render. This
|
| 942 | // will cause us to run flat out without any timing or synchronisation
|
| 943 |
|
| 944 | if (hr == S_OK) {
|
| 945 | EXECUTE_ASSERT(SetEvent((HANDLE) m_RenderEvent));
|
| 946 | return TRUE;
|
| 947 | }
|
| 948 |
|
| 949 | ASSERT(m_dwAdvise == 0);
|
| 950 | ASSERT(m_pClock);
|
| 951 | ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
|
| 952 |
|
| 953 | // We do have a valid reference clock interface so we can ask it to
|
| 954 | // set an event when the image comes due for rendering. We pass in
|
| 955 | // the reference time we were told to start at and also the current
|
| 956 | // stream time which is the offset from the start reference time
|
| 957 |
|
| 958 | hr = m_pClock->AdviseTime(
|
| 959 | (REFERENCE_TIME) m_tStart, // Start run time
|
| 960 | StartSample, // Stream time
|
| 961 | (HEVENT)(HANDLE) m_RenderEvent, // Render notification
|
| 962 | &m_dwAdvise); // Advise cookie
|
| 963 |
|
| 964 | if (SUCCEEDED(hr)) {
|
| 965 | return TRUE;
|
| 966 | }
|
| 967 |
|
| 968 | // We could not schedule the next sample for rendering despite the fact
|
| 969 | // we have a valid sample here. This is a fair indication that either
|
| 970 | // the system clock is wrong or the time stamp for the sample is duff
|
| 971 |
|
| 972 | ASSERT(m_dwAdvise == 0);
|
| 973 | return FALSE;
|
| 974 | }
|
| 975 |
|
| 976 |
|
| 977 | // This is called when a sample comes due for rendering. We pass the sample
|
| 978 | // on to the derived class. After rendering we will initialise the timer for
|
| 979 | // the next sample, NOTE signal that the last one fired first, if we don't
|
| 980 | // do this it thinks there is still one outstanding that hasn't completed
|
| 981 |
|
| 982 | HRESULT CBaseRenderer::Render(IMediaSample *pMediaSample)
|
| 983 | {
|
| 984 | // If the media sample is NULL then we will have been notified by the
|
| 985 | // clock that another sample is ready but in the mean time someone has
|
| 986 | // stopped us streaming which causes the next sample to be released
|
| 987 |
|
| 988 | if (pMediaSample == NULL) {
|
| 989 | return S_FALSE;
|
| 990 | }
|
| 991 |
|
| 992 | // If we have stopped streaming then don't render any more samples, the
|
| 993 | // thread that got in and locked us and then reset this flag does not
|
| 994 | // clear the pending sample as we can use it to refresh any output device
|
| 995 |
|
| 996 | if (m_bStreaming == FALSE) {
|
| 997 | return S_FALSE;
|
| 998 | }
|
| 999 |
|
| 1000 | // Time how long the rendering takes
|
| 1001 |
|
| 1002 | OnRenderStart(pMediaSample);
|
| 1003 | DoRenderSample(pMediaSample);
|
| 1004 | OnRenderEnd(pMediaSample);
|
| 1005 |
|
| 1006 | return NOERROR;
|
| 1007 | }
|
| 1008 |
|
| 1009 |
|
| 1010 | // Checks if there is a sample waiting at the renderer
|
| 1011 |
|
| 1012 | BOOL CBaseRenderer::HaveCurrentSample()
|
| 1013 | {
|
| 1014 | CAutoLock cRendererLock(&m_RendererLock);
|
| 1015 | return (m_pMediaSample == NULL ? FALSE : TRUE);
|
| 1016 | }
|
| 1017 |
|
| 1018 |
|
| 1019 | // Returns the current sample waiting at the video renderer. We AddRef the
|
| 1020 | // sample before returning so that should it come due for rendering the
|
| 1021 | // person who called this method will hold the remaining reference count
|
| 1022 | // that will stop the sample being added back onto the allocator free list
|
| 1023 |
|
| 1024 | IMediaSample *CBaseRenderer::GetCurrentSample()
|
| 1025 | {
|
| 1026 | CAutoLock cRendererLock(&m_RendererLock);
|
| 1027 | if (m_pMediaSample) {
|
| 1028 | m_pMediaSample->AddRef();
|
| 1029 | }
|
| 1030 | return m_pMediaSample;
|
| 1031 | }
|
| 1032 |
|
| 1033 |
|
| 1034 | // Called when the source delivers us a sample. We go through a few checks to
|
| 1035 | // make sure the sample can be rendered. If we are running (streaming) then we
|
| 1036 | // have the sample scheduled with the reference clock, if we are not streaming
|
| 1037 | // then we have received an sample in paused mode so we can complete any state
|
| 1038 | // transition. On leaving this function everything will be unlocked so an app
|
| 1039 | // thread may get in and change our state to stopped (for example) in which
|
| 1040 | // case it will also signal the thread event so that our wait call is stopped
|
| 1041 |
|
| 1042 | HRESULT CBaseRenderer::PrepareReceive(IMediaSample *pMediaSample)
|
| 1043 | {
|
| 1044 | CAutoLock cInterfaceLock(&m_InterfaceLock);
|
| 1045 | m_bInReceive = TRUE;
|
| 1046 |
|
| 1047 | // Check our flushing and filter state
|
| 1048 |
|
| 1049 | // This function must hold the interface lock because it calls
|
| 1050 | // CBaseInputPin::Receive() and CBaseInputPin::Receive() uses
|
| 1051 | // CBasePin::m_bRunTimeError.
|
| 1052 | HRESULT hr = m_pInputPin->CBaseInputPin::Receive(pMediaSample);
|
| 1053 |
|
| 1054 | if (hr != NOERROR) {
|
| 1055 | m_bInReceive = FALSE;
|
| 1056 | return E_FAIL;
|
| 1057 | }
|
| 1058 |
|
| 1059 | // Has the type changed on a media sample. We do all rendering
|
| 1060 | // synchronously on the source thread, which has a side effect
|
| 1061 | // that only one buffer is ever outstanding. Therefore when we
|
| 1062 | // have Receive called we can go ahead and change the format
|
| 1063 | // Since the format change can cause a SendMessage we just don't
|
| 1064 | // lock
|
| 1065 | if (m_pInputPin->SampleProps()->pMediaType) {
|
| 1066 | hr = m_pInputPin->SetMediaType(
|
| 1067 | (CMediaType *)m_pInputPin->SampleProps()->pMediaType);
|
| 1068 | if (FAILED(hr)) {
|
| 1069 | m_bInReceive = FALSE;
|
| 1070 | return hr;
|
| 1071 | }
|
| 1072 | }
|
| 1073 |
|
| 1074 |
|
| 1075 | CAutoLock cSampleLock(&m_RendererLock);
|
| 1076 |
|
| 1077 | ASSERT(IsActive() == TRUE);
|
| 1078 | ASSERT(m_pInputPin->IsFlushing() == FALSE);
|
| 1079 | ASSERT(m_pInputPin->IsConnected() == TRUE);
|
| 1080 | ASSERT(m_pMediaSample == NULL);
|
| 1081 |
|
| 1082 | // Return an error if we already have a sample waiting for rendering
|
| 1083 | // source pins must serialise the Receive calls - we also check that
|
| 1084 | // no data is being sent after the source signalled an end of stream
|
| 1085 |
|
| 1086 | if (m_pMediaSample || m_bEOS || m_bAbort) {
|
| 1087 | Ready();
|
| 1088 | m_bInReceive = FALSE;
|
| 1089 | return E_UNEXPECTED;
|
| 1090 | }
|
| 1091 |
|
| 1092 | // Store the media times from this sample
|
| 1093 | if (m_pPosition) m_pPosition->RegisterMediaTime(pMediaSample);
|
| 1094 |
|
| 1095 | // Schedule the next sample if we are streaming
|
| 1096 |
|
| 1097 | if ((m_bStreaming == TRUE) && (ScheduleSample(pMediaSample) == FALSE)) {
|
| 1098 | ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
|
| 1099 | ASSERT(CancelNotification() == S_FALSE);
|
| 1100 | m_bInReceive = FALSE;
|
| 1101 | return VFW_E_SAMPLE_REJECTED;
|
| 1102 | }
|
| 1103 |
|
| 1104 | // Store the sample end time for EC_COMPLETE handling
|
| 1105 | m_SignalTime = m_pInputPin->SampleProps()->tStop;
|
| 1106 |
|
| 1107 | // BEWARE we sometimes keep the sample even after returning the thread to
|
| 1108 | // the source filter such as when we go into a stopped state (we keep it
|
| 1109 | // to refresh the device with) so we must AddRef it to keep it safely. If
|
| 1110 | // we start flushing the source thread is released and any sample waiting
|
| 1111 | // will be released otherwise GetBuffer may never return (see BeginFlush)
|
| 1112 |
|
| 1113 | m_pMediaSample = pMediaSample;
|
| 1114 | m_pMediaSample->AddRef();
|
| 1115 |
|
| 1116 | if (m_bStreaming == FALSE) {
|
| 1117 | SetRepaintStatus(TRUE);
|
| 1118 | }
|
| 1119 | return NOERROR;
|
| 1120 | }
|
| 1121 |
|
| 1122 |
|
| 1123 | // Called by the source filter when we have a sample to render. Under normal
|
| 1124 | // circumstances we set an advise link with the clock, wait for the time to
|
| 1125 | // arrive and then render the data using the PURE virtual DoRenderSample that
|
| 1126 | // the derived class will have overriden. After rendering the sample we may
|
| 1127 | // also signal EOS if it was the last one sent before EndOfStream was called
|
| 1128 |
|
| 1129 | HRESULT CBaseRenderer::Receive(IMediaSample *pSample)
|
| 1130 | {
|
| 1131 | ASSERT(pSample);
|
| 1132 |
|
| 1133 | // It may return VFW_E_SAMPLE_REJECTED code to say don't bother
|
| 1134 |
|
| 1135 | HRESULT hr = PrepareReceive(pSample);
|
| 1136 | ASSERT(m_bInReceive == SUCCEEDED(hr));
|
| 1137 | if (FAILED(hr)) {
|
| 1138 | if (hr == VFW_E_SAMPLE_REJECTED) {
|
| 1139 | return NOERROR;
|
| 1140 | }
|
| 1141 | return hr;
|
| 1142 | }
|
| 1143 |
|
| 1144 | // We realize the palette in "PrepareRender()" so we have to give away the
|
| 1145 | // filter lock here.
|
| 1146 | if (m_State == State_Paused) {
|
| 1147 | PrepareRender();
|
| 1148 | // no need to use InterlockedExchange
|
| 1149 | m_bInReceive = FALSE;
|
| 1150 | {
|
| 1151 | // We must hold both these locks
|
| 1152 | CAutoLock cRendererLock(&m_InterfaceLock);
|
| 1153 | if (m_State == State_Stopped)
|
| 1154 | return NOERROR;
|
| 1155 |
|
| 1156 | m_bInReceive = TRUE;
|
| 1157 | CAutoLock cSampleLock(&m_RendererLock);
|
| 1158 | OnReceiveFirstSample(pSample);
|
| 1159 | }
|
| 1160 | Ready();
|
| 1161 | }
|
| 1162 | // Having set an advise link with the clock we sit and wait. We may be
|
| 1163 | // awoken by the clock firing or by a state change. The rendering call
|
| 1164 | // will lock the critical section and check we can still render the data
|
| 1165 |
|
| 1166 | hr = WaitForRenderTime();
|
| 1167 | if (FAILED(hr)) {
|
| 1168 | m_bInReceive = FALSE;
|
| 1169 | return NOERROR;
|
| 1170 | }
|
| 1171 |
|
| 1172 | PrepareRender();
|
| 1173 |
|
| 1174 | // Set this here and poll it until we work out the locking correctly
|
| 1175 | // It can't be right that the streaming stuff grabs the interface
|
| 1176 | // lock - after all we want to be able to wait for this stuff
|
| 1177 | // to complete
|
| 1178 | m_bInReceive = FALSE;
|
| 1179 |
|
| 1180 | // We must hold both these locks
|
| 1181 | CAutoLock cRendererLock(&m_InterfaceLock);
|
| 1182 |
|
| 1183 | // since we gave away the filter wide lock, the sate of the filter could
|
| 1184 | // have chnaged to Stopped
|
| 1185 | if (m_State == State_Stopped)
|
| 1186 | return NOERROR;
|
| 1187 |
|
| 1188 | CAutoLock cSampleLock(&m_RendererLock);
|
| 1189 |
|
| 1190 | // Deal with this sample
|
| 1191 |
|
| 1192 | Render(m_pMediaSample);
|
| 1193 | ClearPendingSample();
|
| 1194 | SendEndOfStream();
|
| 1195 | CancelNotification();
|
| 1196 | return NOERROR;
|
| 1197 | }
|
| 1198 |
|
| 1199 |
|
| 1200 | // This is called when we stop or are inactivated to clear the pending sample
|
| 1201 | // We release the media sample interface so that they can be allocated to the
|
| 1202 | // source filter again, unless of course we are changing state to inactive in
|
| 1203 | // which case GetBuffer will return an error. We must also reset the current
|
| 1204 | // media sample to NULL so that we know we do not currently have an image
|
| 1205 |
|
| 1206 | HRESULT CBaseRenderer::ClearPendingSample()
|
| 1207 | {
|
| 1208 | CAutoLock cRendererLock(&m_RendererLock);
|
| 1209 | if (m_pMediaSample) {
|
| 1210 | m_pMediaSample->Release();
|
| 1211 | m_pMediaSample = NULL;
|
| 1212 | }
|
| 1213 | return NOERROR;
|
| 1214 | }
|
| 1215 |
|
| 1216 |
|
| 1217 | // Used to signal end of stream according to the sample end time
|
| 1218 |
|
| 1219 | void CALLBACK EndOfStreamTimer(UINT uID, // Timer identifier
|
| 1220 | UINT uMsg, // Not currently used
|
| 1221 | DWORD_PTR dwUser,// User information
|
| 1222 | DWORD_PTR dw1, // Windows reserved
|
| 1223 | DWORD_PTR dw2) // is also reserved
|
| 1224 | {
|
| 1225 | CBaseRenderer *pRenderer = (CBaseRenderer *) dwUser;
|
| 1226 | NOTE1("EndOfStreamTimer called (%d)",uID);
|
| 1227 | pRenderer->TimerCallback();
|
| 1228 | }
|
| 1229 |
|
| 1230 | // Do the timer callback work
|
| 1231 | void CBaseRenderer::TimerCallback()
|
| 1232 | {
|
| 1233 | // Lock for synchronization (but don't hold this lock when calling
|
| 1234 | // timeKillEvent)
|
| 1235 | CAutoLock cRendererLock(&m_RendererLock);
|
| 1236 |
|
| 1237 | // See if we should signal end of stream now
|
| 1238 |
|
| 1239 | if (m_EndOfStreamTimer) {
|
| 1240 | m_EndOfStreamTimer = 0;
|
| 1241 | SendEndOfStream();
|
| 1242 | }
|
| 1243 | }
|
| 1244 |
|
| 1245 |
|
| 1246 | // If we are at the end of the stream signal the filter graph but do not set
|
| 1247 | // the state flag back to FALSE. Once we drop off the end of the stream we
|
| 1248 | // leave the flag set (until a subsequent ResetEndOfStream). Each sample we
|
| 1249 | // get delivered will update m_SignalTime to be the last sample's end time.
|
| 1250 | // We must wait this long before signalling end of stream to the filtergraph
|
| 1251 |
|
| 1252 | #define TIMEOUT_DELIVERYWAIT 50
|
| 1253 | #define TIMEOUT_RESOLUTION 10
|
| 1254 |
|
| 1255 | HRESULT CBaseRenderer::SendEndOfStream()
|
| 1256 | {
|
| 1257 | ASSERT(CritCheckIn(&m_RendererLock));
|
| 1258 | if (m_bEOS == FALSE || m_bEOSDelivered || m_EndOfStreamTimer) {
|
| 1259 | return NOERROR;
|
| 1260 | }
|
| 1261 |
|
| 1262 | // If there is no clock then signal immediately
|
| 1263 | if (m_pClock == NULL) {
|
| 1264 | return NotifyEndOfStream();
|
| 1265 | }
|
| 1266 |
|
| 1267 | // How long into the future is the delivery time
|
| 1268 |
|
| 1269 | REFERENCE_TIME Signal = m_tStart + m_SignalTime;
|
| 1270 | REFERENCE_TIME CurrentTime;
|
| 1271 | m_pClock->GetTime(&CurrentTime);
|
| 1272 | LONG Delay = LONG((Signal - CurrentTime) / 10000);
|
| 1273 |
|
| 1274 | // Dump the timing information to the debugger
|
| 1275 |
|
| 1276 | NOTE1("Delay until end of stream delivery %d",Delay);
|
| 1277 | NOTE1("Current %s",(LPCTSTR)CDisp((LONGLONG)CurrentTime));
|
| 1278 | NOTE1("Signal %s",(LPCTSTR)CDisp((LONGLONG)Signal));
|
| 1279 |
|
| 1280 | // Wait for the delivery time to arrive
|
| 1281 |
|
| 1282 | if (Delay < TIMEOUT_DELIVERYWAIT) {
|
| 1283 | return NotifyEndOfStream();
|
| 1284 | }
|
| 1285 |
|
| 1286 | // Signal a timer callback on another worker thread
|
| 1287 |
|
| 1288 | m_EndOfStreamTimer = CompatibleTimeSetEvent((UINT) Delay, // Period of timer
|
| 1289 | TIMEOUT_RESOLUTION, // Timer resolution
|
| 1290 | EndOfStreamTimer, // Callback function
|
| 1291 | DWORD_PTR(this), // Used information
|
| 1292 | TIME_ONESHOT); // Type of callback
|
| 1293 | if (m_EndOfStreamTimer == 0) {
|
| 1294 | return NotifyEndOfStream();
|
| 1295 | }
|
| 1296 | return NOERROR;
|
| 1297 | }
|
| 1298 |
|
| 1299 |
|
| 1300 | // Signals EC_COMPLETE to the filtergraph manager
|
| 1301 |
|
| 1302 | HRESULT CBaseRenderer::NotifyEndOfStream()
|
| 1303 | {
|
| 1304 | CAutoLock cRendererLock(&m_RendererLock);
|
| 1305 | ASSERT(m_bEOSDelivered == FALSE);
|
| 1306 | ASSERT(m_EndOfStreamTimer == 0);
|
| 1307 |
|
| 1308 | // Has the filter changed state
|
| 1309 |
|
| 1310 | if (m_bStreaming == FALSE) {
|
| 1311 | ASSERT(m_EndOfStreamTimer == 0);
|
| 1312 | return NOERROR;
|
| 1313 | }
|
| 1314 |
|
| 1315 | // Reset the end of stream timer
|
| 1316 | m_EndOfStreamTimer = 0;
|
| 1317 |
|
| 1318 | // If we've been using the IMediaPosition interface, set it's start
|
| 1319 | // and end media "times" to the stop position by hand. This ensures
|
| 1320 | // that we actually get to the end, even if the MPEG guestimate has
|
| 1321 | // been bad or if the quality management dropped the last few frames
|
| 1322 |
|
| 1323 | if (m_pPosition) m_pPosition->EOS();
|
| 1324 | m_bEOSDelivered = TRUE;
|
| 1325 | NOTE("Sending EC_COMPLETE...");
|
| 1326 | return NotifyEvent(EC_COMPLETE,S_OK,(LONG_PTR)(IBaseFilter *)this);
|
| 1327 | }
|
| 1328 |
|
| 1329 |
|
| 1330 | // Reset the end of stream flag, this is typically called when we transfer to
|
| 1331 | // stopped states since that resets the current position back to the start so
|
| 1332 | // we will receive more samples or another EndOfStream if there aren't any. We
|
| 1333 | // keep two separate flags one to say we have run off the end of the stream
|
| 1334 | // (this is the m_bEOS flag) and another to say we have delivered EC_COMPLETE
|
| 1335 | // to the filter graph. We need the latter otherwise we can end up sending an
|
| 1336 | // EC_COMPLETE every time the source changes state and calls our EndOfStream
|
| 1337 |
|
| 1338 | HRESULT CBaseRenderer::ResetEndOfStream()
|
| 1339 | {
|
| 1340 | ResetEndOfStreamTimer();
|
| 1341 | CAutoLock cRendererLock(&m_RendererLock);
|
| 1342 |
|
| 1343 | m_bEOS = FALSE;
|
| 1344 | m_bEOSDelivered = FALSE;
|
| 1345 | m_SignalTime = 0;
|
| 1346 |
|
| 1347 | return NOERROR;
|
| 1348 | }
|
| 1349 |
|
| 1350 |
|
| 1351 | // Kills any outstanding end of stream timer
|
| 1352 |
|
| 1353 | void CBaseRenderer::ResetEndOfStreamTimer()
|
| 1354 | {
|
| 1355 | ASSERT(CritCheckOut(&m_RendererLock));
|
| 1356 | if (m_EndOfStreamTimer) {
|
| 1357 | timeKillEvent(m_EndOfStreamTimer);
|
| 1358 | m_EndOfStreamTimer = 0;
|
| 1359 | }
|
| 1360 | }
|
| 1361 |
|
| 1362 |
|
| 1363 | // This is called when we start running so that we can schedule any pending
|
| 1364 | // image we have with the clock and display any timing information. If we
|
| 1365 | // don't have any sample but we have queued an EOS flag then we send it. If
|
| 1366 | // we do have a sample then we wait until that has been rendered before we
|
| 1367 | // signal the filter graph otherwise we may change state before it's done
|
| 1368 |
|
| 1369 | HRESULT CBaseRenderer::StartStreaming()
|
| 1370 | {
|
| 1371 | CAutoLock cRendererLock(&m_RendererLock);
|
| 1372 | if (m_bStreaming == TRUE) {
|
| 1373 | return NOERROR;
|
| 1374 | }
|
| 1375 |
|
| 1376 | // Reset the streaming times ready for running
|
| 1377 |
|
| 1378 | m_bStreaming = TRUE;
|
| 1379 |
|
| 1380 | timeBeginPeriod(1);
|
| 1381 | OnStartStreaming();
|
| 1382 |
|
| 1383 | // There should be no outstanding advise
|
| 1384 | ASSERT(WAIT_TIMEOUT == WaitForSingleObject((HANDLE)m_RenderEvent,0));
|
| 1385 | ASSERT(CancelNotification() == S_FALSE);
|
| 1386 |
|
| 1387 | // If we have an EOS and no data then deliver it now
|
| 1388 |
|
| 1389 | if (m_pMediaSample == NULL) {
|
| 1390 | return SendEndOfStream();
|
| 1391 | }
|
| 1392 |
|
| 1393 | // Have the data rendered
|
| 1394 |
|
| 1395 | ASSERT(m_pMediaSample);
|
| 1396 | if (!ScheduleSample(m_pMediaSample))
|
| 1397 | m_RenderEvent.Set();
|
| 1398 |
|
| 1399 | return NOERROR;
|
| 1400 | }
|
| 1401 |
|
| 1402 |
|
| 1403 | // This is called when we stop streaming so that we can set our internal flag
|
| 1404 | // indicating we are not now to schedule any more samples arriving. The state
|
| 1405 | // change methods in the filter implementation take care of cancelling any
|
| 1406 | // clock advise link we have set up and clearing any pending sample we have
|
| 1407 |
|
| 1408 | HRESULT CBaseRenderer::StopStreaming()
|
| 1409 | {
|
| 1410 | CAutoLock cRendererLock(&m_RendererLock);
|
| 1411 | m_bEOSDelivered = FALSE;
|
| 1412 |
|
| 1413 | if (m_bStreaming == TRUE) {
|
| 1414 | m_bStreaming = FALSE;
|
| 1415 | OnStopStreaming();
|
| 1416 | timeEndPeriod(1);
|
| 1417 | }
|
| 1418 | return NOERROR;
|
| 1419 | }
|
| 1420 |
|
| 1421 |
|
| 1422 | // We have a boolean flag that is reset when we have signalled EC_REPAINT to
|
| 1423 | // the filter graph. We set this when we receive an image so that should any
|
| 1424 | // conditions arise again we can send another one. By having a flag we ensure
|
| 1425 | // we don't flood the filter graph with redundant calls. We do not set the
|
| 1426 | // event when we receive an EndOfStream call since there is no point in us
|
| 1427 | // sending further EC_REPAINTs. In particular the AutoShowWindow method and
|
| 1428 | // the DirectDraw object use this method to control the window repainting
|
| 1429 |
|
| 1430 | void CBaseRenderer::SetRepaintStatus(BOOL bRepaint)
|
| 1431 | {
|
| 1432 | CAutoLock cSampleLock(&m_RendererLock);
|
| 1433 | m_bRepaintStatus = bRepaint;
|
| 1434 | }
|
| 1435 |
|
| 1436 |
|
| 1437 | // Pass the window handle to the upstream filter
|
| 1438 |
|
| 1439 | void CBaseRenderer::SendNotifyWindow(IPin *pPin,HWND hwnd)
|
| 1440 | {
|
| 1441 | IMediaEventSink *pSink;
|
| 1442 |
|
| 1443 | // Does the pin support IMediaEventSink
|
| 1444 | HRESULT hr = pPin->QueryInterface(IID_IMediaEventSink,(void **)&pSink);
|
| 1445 | if (SUCCEEDED(hr)) {
|
| 1446 | pSink->Notify(EC_NOTIFY_WINDOW,LONG_PTR(hwnd),0);
|
| 1447 | pSink->Release();
|
| 1448 | }
|
| 1449 | NotifyEvent(EC_NOTIFY_WINDOW,LONG_PTR(hwnd),0);
|
| 1450 | }
|
| 1451 |
|
| 1452 |
|
| 1453 | // Signal an EC_REPAINT to the filter graph. This can be used to have data
|
| 1454 | // sent to us. For example when a video window is first displayed it may
|
| 1455 | // not have an image to display, at which point it signals EC_REPAINT. The
|
| 1456 | // filtergraph will either pause the graph if stopped or if already paused
|
| 1457 | // it will call put_CurrentPosition of the current position. Setting the
|
| 1458 | // current position to itself has the stream flushed and the image resent
|
| 1459 |
|
| 1460 | #define RLOG(_x_) DbgLog((LOG_TRACE,1,TEXT(_x_)));
|
| 1461 |
|
| 1462 | void CBaseRenderer::SendRepaint()
|
| 1463 | {
|
| 1464 | CAutoLock cSampleLock(&m_RendererLock);
|
| 1465 | ASSERT(m_pInputPin);
|
| 1466 |
|
| 1467 | // We should not send repaint notifications when...
|
| 1468 | // - An end of stream has been notified
|
| 1469 | // - Our input pin is being flushed
|
| 1470 | // - The input pin is not connected
|
| 1471 | // - We have aborted a video playback
|
| 1472 | // - There is a repaint already sent
|
| 1473 |
|
| 1474 | if (m_bAbort == FALSE) {
|
| 1475 | if (m_pInputPin->IsConnected() == TRUE) {
|
| 1476 | if (m_pInputPin->IsFlushing() == FALSE) {
|
| 1477 | if (IsEndOfStream() == FALSE) {
|
| 1478 | if (m_bRepaintStatus == TRUE) {
|
| 1479 | IPin *pPin = (IPin *) m_pInputPin;
|
| 1480 | NotifyEvent(EC_REPAINT,(LONG_PTR) pPin,0);
|
| 1481 | SetRepaintStatus(FALSE);
|
| 1482 | RLOG("Sending repaint");
|
| 1483 | }
|
| 1484 | }
|
| 1485 | }
|
| 1486 | }
|
| 1487 | }
|
| 1488 | }
|
| 1489 |
|
| 1490 |
|
| 1491 | // When a video window detects a display change (WM_DISPLAYCHANGE message) it
|
| 1492 | // can send an EC_DISPLAY_CHANGED event code along with the renderer pin. The
|
| 1493 | // filtergraph will stop everyone and reconnect our input pin. As we're then
|
| 1494 | // reconnected we can accept the media type that matches the new display mode
|
| 1495 | // since we may no longer be able to draw the current image type efficiently
|
| 1496 |
|
| 1497 | BOOL CBaseRenderer::OnDisplayChange()
|
| 1498 | {
|
| 1499 | // Ignore if we are not connected yet
|
| 1500 |
|
| 1501 | CAutoLock cSampleLock(&m_RendererLock);
|
| 1502 | if (m_pInputPin->IsConnected() == FALSE) {
|
| 1503 | return FALSE;
|
| 1504 | }
|
| 1505 |
|
| 1506 | RLOG("Notification of EC_DISPLAY_CHANGE");
|
| 1507 |
|
| 1508 | // Pass our input pin as parameter on the event
|
| 1509 |
|
| 1510 | IPin *pPin = (IPin *) m_pInputPin;
|
| 1511 | m_pInputPin->AddRef();
|
| 1512 | NotifyEvent(EC_DISPLAY_CHANGED,(LONG_PTR) pPin,0);
|
| 1513 | SetAbortSignal(TRUE);
|
| 1514 | ClearPendingSample();
|
| 1515 | m_pInputPin->Release();
|
| 1516 |
|
| 1517 | return TRUE;
|
| 1518 | }
|
| 1519 |
|
| 1520 |
|
| 1521 | // Called just before we start drawing.
|
| 1522 | // Store the current time in m_trRenderStart to allow the rendering time to be
|
| 1523 | // logged. Log the time stamp of the sample and how late it is (neg is early)
|
| 1524 |
|
| 1525 | void CBaseRenderer::OnRenderStart(IMediaSample *pMediaSample)
|
| 1526 | {
|
| 1527 | #ifdef PERF
|
| 1528 | REFERENCE_TIME trStart, trEnd;
|
| 1529 | pMediaSample->GetTime(&trStart, &trEnd);
|
| 1530 |
|
| 1531 | MSR_INTEGER(m_idBaseStamp, (int)trStart); // dump low order 32 bits
|
| 1532 |
|
| 1533 | m_pClock->GetTime(&m_trRenderStart);
|
| 1534 | MSR_INTEGER(0, (int)m_trRenderStart);
|
| 1535 | REFERENCE_TIME trStream;
|
| 1536 | trStream = m_trRenderStart-m_tStart; // convert reftime to stream time
|
| 1537 | MSR_INTEGER(0,(int)trStream);
|
| 1538 |
|
| 1539 | const int trLate = (int)(trStream - trStart);
|
| 1540 | MSR_INTEGER(m_idBaseAccuracy, trLate/10000); // dump in mSec
|
| 1541 | #endif
|
| 1542 |
|
| 1543 | } // OnRenderStart
|
| 1544 |
|
| 1545 |
|
| 1546 | // Called directly after drawing an image.
|
| 1547 | // calculate the time spent drawing and log it.
|
| 1548 |
|
| 1549 | void CBaseRenderer::OnRenderEnd(IMediaSample *pMediaSample)
|
| 1550 | {
|
| 1551 | #ifdef PERF
|
| 1552 | REFERENCE_TIME trNow;
|
| 1553 | m_pClock->GetTime(&trNow);
|
| 1554 | MSR_INTEGER(0,(int)trNow);
|
| 1555 | int t = (int)((trNow - m_trRenderStart)/10000); // convert UNITS->msec
|
| 1556 | MSR_INTEGER(m_idBaseRenderTime, t);
|
| 1557 | #endif
|
| 1558 | } // OnRenderEnd
|
| 1559 |
|
| 1560 |
|
| 1561 |
|
| 1562 |
|
| 1563 | // Constructor must be passed the base renderer object
|
| 1564 |
|
| 1565 | CRendererInputPin::CRendererInputPin(__inout CBaseRenderer *pRenderer,
|
| 1566 | __inout HRESULT *phr,
|
| 1567 | __in_opt LPCWSTR pPinName) :
|
| 1568 | CBaseInputPin(NAME("Renderer pin"),
|
| 1569 | pRenderer,
|
| 1570 | &pRenderer->m_InterfaceLock,
|
| 1571 | (HRESULT *) phr,
|
| 1572 | pPinName)
|
| 1573 | {
|
| 1574 | m_pRenderer = pRenderer;
|
| 1575 | ASSERT(m_pRenderer);
|
| 1576 | }
|
| 1577 |
|
| 1578 |
|
| 1579 | // Signals end of data stream on the input pin
|
| 1580 |
|
| 1581 | STDMETHODIMP CRendererInputPin::EndOfStream()
|
| 1582 | {
|
| 1583 | CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
|
| 1584 | CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
|
| 1585 |
|
| 1586 | // Make sure we're streaming ok
|
| 1587 |
|
| 1588 | HRESULT hr = CheckStreaming();
|
| 1589 | if (hr != NOERROR) {
|
| 1590 | return hr;
|
| 1591 | }
|
| 1592 |
|
| 1593 | // Pass it onto the renderer
|
| 1594 |
|
| 1595 | hr = m_pRenderer->EndOfStream();
|
| 1596 | if (SUCCEEDED(hr)) {
|
| 1597 | hr = CBaseInputPin::EndOfStream();
|
| 1598 | }
|
| 1599 | return hr;
|
| 1600 | }
|
| 1601 |
|
| 1602 |
|
| 1603 | // Signals start of flushing on the input pin - we do the final reset end of
|
| 1604 | // stream with the renderer lock unlocked but with the interface lock locked
|
| 1605 | // We must do this because we call timeKillEvent, our timer callback method
|
| 1606 | // has to take the renderer lock to serialise our state. Therefore holding a
|
| 1607 | // renderer lock when calling timeKillEvent could cause a deadlock condition
|
| 1608 |
|
| 1609 | STDMETHODIMP CRendererInputPin::BeginFlush()
|
| 1610 | {
|
| 1611 | CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
|
| 1612 | {
|
| 1613 | CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
|
| 1614 | CBaseInputPin::BeginFlush();
|
| 1615 | m_pRenderer->BeginFlush();
|
| 1616 | }
|
| 1617 | return m_pRenderer->ResetEndOfStream();
|
| 1618 | }
|
| 1619 |
|
| 1620 |
|
| 1621 | // Signals end of flushing on the input pin
|
| 1622 |
|
| 1623 | STDMETHODIMP CRendererInputPin::EndFlush()
|
| 1624 | {
|
| 1625 | CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
|
| 1626 | CAutoLock cSampleLock(&m_pRenderer->m_RendererLock);
|
| 1627 |
|
| 1628 | HRESULT hr = m_pRenderer->EndFlush();
|
| 1629 | if (SUCCEEDED(hr)) {
|
| 1630 | hr = CBaseInputPin::EndFlush();
|
| 1631 | }
|
| 1632 | return hr;
|
| 1633 | }
|
| 1634 |
|
| 1635 |
|
| 1636 | // Pass the sample straight through to the renderer object
|
| 1637 |
|
| 1638 | STDMETHODIMP CRendererInputPin::Receive(IMediaSample *pSample)
|
| 1639 | {
|
| 1640 | HRESULT hr = m_pRenderer->Receive(pSample);
|
| 1641 | if (FAILED(hr)) {
|
| 1642 |
|
| 1643 | // A deadlock could occur if the caller holds the renderer lock and
|
| 1644 | // attempts to acquire the interface lock.
|
| 1645 | ASSERT(CritCheckOut(&m_pRenderer->m_RendererLock));
|
| 1646 |
|
| 1647 | {
|
| 1648 | // The interface lock must be held when the filter is calling
|
| 1649 | // IsStopped() or IsFlushing(). The interface lock must also
|
| 1650 | // be held because the function uses m_bRunTimeError.
|
| 1651 | CAutoLock cRendererLock(&m_pRenderer->m_InterfaceLock);
|
| 1652 |
|
| 1653 | // We do not report errors which occur while the filter is stopping,
|
| 1654 | // flushing or if the m_bAbort flag is set . Errors are expected to
|
| 1655 | // occur during these operations and the streaming thread correctly
|
| 1656 | // handles the errors.
|
| 1657 | if (!IsStopped() && !IsFlushing() && !m_pRenderer->m_bAbort && !m_bRunTimeError) {
|
| 1658 |
|
| 1659 | // EC_ERRORABORT's first parameter is the error which caused
|
| 1660 | // the event and its' last parameter is 0. See the Direct
|
| 1661 | // Show SDK documentation for more information.
|
| 1662 | m_pRenderer->NotifyEvent(EC_ERRORABORT,hr,0);
|
| 1663 |
|
| 1664 | {
|
| 1665 | CAutoLock alRendererLock(&m_pRenderer->m_RendererLock);
|
| 1666 | if (m_pRenderer->IsStreaming() && !m_pRenderer->IsEndOfStreamDelivered()) {
|
| 1667 | m_pRenderer->NotifyEndOfStream();
|
| 1668 | }
|
| 1669 | }
|
| 1670 |
|
| 1671 | m_bRunTimeError = TRUE;
|
| 1672 | }
|
| 1673 | }
|
| 1674 | }
|
| 1675 |
|
| 1676 | return hr;
|
| 1677 | }
|
| 1678 |
|
| 1679 |
|
| 1680 | // Called when the input pin is disconnected
|
| 1681 |
|
| 1682 | HRESULT CRendererInputPin::BreakConnect()
|
| 1683 | {
|
| 1684 | HRESULT hr = m_pRenderer->BreakConnect();
|
| 1685 | if (FAILED(hr)) {
|
| 1686 | return hr;
|
| 1687 | }
|
| 1688 | return CBaseInputPin::BreakConnect();
|
| 1689 | }
|
| 1690 |
|
| 1691 |
|
| 1692 | // Called when the input pin is connected
|
| 1693 |
|
| 1694 | HRESULT CRendererInputPin::CompleteConnect(IPin *pReceivePin)
|
| 1695 | {
|
| 1696 | HRESULT hr = m_pRenderer->CompleteConnect(pReceivePin);
|
| 1697 | if (FAILED(hr)) {
|
| 1698 | return hr;
|
| 1699 | }
|
| 1700 | return CBaseInputPin::CompleteConnect(pReceivePin);
|
| 1701 | }
|
| 1702 |
|
| 1703 |
|
| 1704 | // Give the pin id of our one and only pin
|
| 1705 |
|
| 1706 | STDMETHODIMP CRendererInputPin::QueryId(__deref_out LPWSTR *Id)
|
| 1707 | {
|
| 1708 | CheckPointer(Id,E_POINTER);
|
| 1709 |
|
| 1710 | const WCHAR szIn[] = L"In";
|
| 1711 |
|
| 1712 | *Id = (LPWSTR)CoTaskMemAlloc(sizeof(szIn));
|
| 1713 | if (*Id == NULL) {
|
| 1714 | return E_OUTOFMEMORY;
|
| 1715 | }
|
| 1716 | CopyMemory(*Id, szIn, sizeof(szIn));
|
| 1717 | return NOERROR;
|
| 1718 | }
|
| 1719 |
|
| 1720 |
|
| 1721 | // Will the filter accept this media type
|
| 1722 |
|
| 1723 | HRESULT CRendererInputPin::CheckMediaType(const CMediaType *pmt)
|
| 1724 | {
|
| 1725 | return m_pRenderer->CheckMediaType(pmt);
|
| 1726 | }
|
| 1727 |
|
| 1728 |
|
| 1729 | // Called when we go paused or running
|
| 1730 |
|
| 1731 | HRESULT CRendererInputPin::Active()
|
| 1732 | {
|
| 1733 | return m_pRenderer->Active();
|
| 1734 | }
|
| 1735 |
|
| 1736 |
|
| 1737 | // Called when we go into a stopped state
|
| 1738 |
|
| 1739 | HRESULT CRendererInputPin::Inactive()
|
| 1740 | {
|
| 1741 | // The caller must hold the interface lock because
|
| 1742 | // this function uses m_bRunTimeError.
|
| 1743 | ASSERT(CritCheckIn(&m_pRenderer->m_InterfaceLock));
|
| 1744 |
|
| 1745 | m_bRunTimeError = FALSE;
|
| 1746 |
|
| 1747 | return m_pRenderer->Inactive();
|
| 1748 | }
|
| 1749 |
|
| 1750 |
|
| 1751 | // Tell derived classes about the media type agreed
|
| 1752 |
|
| 1753 | HRESULT CRendererInputPin::SetMediaType(const CMediaType *pmt)
|
| 1754 | {
|
| 1755 | HRESULT hr = CBaseInputPin::SetMediaType(pmt);
|
| 1756 | if (FAILED(hr)) {
|
| 1757 | return hr;
|
| 1758 | }
|
| 1759 | return m_pRenderer->SetMediaType(pmt);
|
| 1760 | }
|
| 1761 |
|
| 1762 |
|
| 1763 | // We do not keep an event object to use when setting up a timer link with
|
| 1764 | // the clock but are given a pointer to one by the owning object through the
|
| 1765 | // SetNotificationObject method - this must be initialised before starting
|
| 1766 | // We can override the default quality management process to have it always
|
| 1767 | // draw late frames, this is currently done by having the following registry
|
| 1768 | // key (actually an INI key) called DrawLateFrames set to 1 (default is 0)
|
| 1769 |
|
| 1770 | const TCHAR AMQUALITY[] = TEXT("ActiveMovie");
|
| 1771 | const TCHAR DRAWLATEFRAMES[] = TEXT("DrawLateFrames");
|
| 1772 |
|
| 1773 | CBaseVideoRenderer::CBaseVideoRenderer(
|
| 1774 | REFCLSID RenderClass, // CLSID for this renderer
|
| 1775 | __in_opt LPCTSTR pName, // Debug ONLY description
|
| 1776 | __inout_opt LPUNKNOWN pUnk, // Aggregated owner object
|
| 1777 | __inout HRESULT *phr) : // General OLE return code
|
| 1778 |
|
| 1779 | CBaseRenderer(RenderClass,pName,pUnk,phr),
|
| 1780 | m_cFramesDropped(0),
|
| 1781 | m_cFramesDrawn(0),
|
| 1782 | m_bSupplierHandlingQuality(FALSE)
|
| 1783 | {
|
| 1784 | ResetStreamingTimes();
|
| 1785 |
|
| 1786 | #ifdef PERF
|
| 1787 | m_idTimeStamp = MSR_REGISTER(TEXT("Frame time stamp"));
|
| 1788 | m_idEarliness = MSR_REGISTER(TEXT("Earliness fudge"));
|
| 1789 | m_idTarget = MSR_REGISTER(TEXT("Target (mSec)"));
|
| 1790 | m_idSchLateTime = MSR_REGISTER(TEXT("mSec late when scheduled"));
|
| 1791 | m_idDecision = MSR_REGISTER(TEXT("Scheduler decision code"));
|
| 1792 | m_idQualityRate = MSR_REGISTER(TEXT("Quality rate sent"));
|
| 1793 | m_idQualityTime = MSR_REGISTER(TEXT("Quality time sent"));
|
| 1794 | m_idWaitReal = MSR_REGISTER(TEXT("Render wait"));
|
| 1795 | // m_idWait = MSR_REGISTER(TEXT("wait time recorded (msec)"));
|
| 1796 | m_idFrameAccuracy = MSR_REGISTER(TEXT("Frame accuracy (msecs)"));
|
| 1797 | m_bDrawLateFrames = GetProfileInt(AMQUALITY, DRAWLATEFRAMES, FALSE);
|
| 1798 | //m_idSendQuality = MSR_REGISTER(TEXT("Processing Quality message"));
|
| 1799 |
|
| 1800 | m_idRenderAvg = MSR_REGISTER(TEXT("Render draw time Avg"));
|
| 1801 | m_idFrameAvg = MSR_REGISTER(TEXT("FrameAvg"));
|
| 1802 | m_idWaitAvg = MSR_REGISTER(TEXT("WaitAvg"));
|
| 1803 | m_idDuration = MSR_REGISTER(TEXT("Duration"));
|
| 1804 | m_idThrottle = MSR_REGISTER(TEXT("Audio-video throttle wait"));
|
| 1805 | // m_idDebug = MSR_REGISTER(TEXT("Debug stuff"));
|
| 1806 | #endif // PERF
|
| 1807 | } // Constructor
|
| 1808 |
|
| 1809 |
|
| 1810 | // Destructor is just a placeholder
|
| 1811 |
|
| 1812 | CBaseVideoRenderer::~CBaseVideoRenderer()
|
| 1813 | {
|
| 1814 | ASSERT(m_dwAdvise == 0);
|
| 1815 | }
|
| 1816 |
|
| 1817 |
|
| 1818 | // The timing functions in this class are called by the window object and by
|
| 1819 | // the renderer's allocator.
|
| 1820 | // The windows object calls timing functions as it receives media sample
|
| 1821 | // images for drawing using GDI.
|
| 1822 | // The allocator calls timing functions when it starts passing DCI/DirectDraw
|
| 1823 | // surfaces which are not rendered in the same way; The decompressor writes
|
| 1824 | // directly to the surface with no separate rendering, so those code paths
|
| 1825 | // call direct into us. Since we only ever hand out DCI/DirectDraw surfaces
|
| 1826 | // when we have allocated one and only one image we know there cannot be any
|
| 1827 | // conflict between the two.
|
| 1828 | //
|
| 1829 | // We use timeGetTime to return the timing counts we use (since it's relative
|
| 1830 | // performance we are interested in rather than absolute compared to a clock)
|
| 1831 | // The window object sets the accuracy of the system clock (normally 1ms) by
|
| 1832 | // calling timeBeginPeriod/timeEndPeriod when it changes streaming states
|
| 1833 |
|
| 1834 |
|
| 1835 | // Reset all times controlling streaming.
|
| 1836 | // Set them so that
|
| 1837 | // 1. Frames will not initially be dropped
|
| 1838 | // 2. The first frame will definitely be drawn (achieved by saying that there
|
| 1839 | // has not ben a frame drawn for a long time).
|
| 1840 |
|
| 1841 | HRESULT CBaseVideoRenderer::ResetStreamingTimes()
|
| 1842 | {
|
| 1843 | m_trLastDraw = -1000; // set up as first frame since ages (1 sec) ago
|
| 1844 | m_tStreamingStart = timeGetTime();
|
| 1845 | m_trRenderAvg = 0;
|
| 1846 | m_trFrameAvg = -1; // -1000 fps == "unset"
|
| 1847 | m_trDuration = 0; // 0 - strange value
|
| 1848 | m_trRenderLast = 0;
|
| 1849 | m_trWaitAvg = 0;
|
| 1850 | m_tRenderStart = 0;
|
| 1851 | m_cFramesDrawn = 0;
|
| 1852 | m_cFramesDropped = 0;
|
| 1853 | m_iTotAcc = 0;
|
| 1854 | m_iSumSqAcc = 0;
|
| 1855 | m_iSumSqFrameTime = 0;
|
| 1856 | m_trFrame = 0; // hygeine - not really needed
|
| 1857 | m_trLate = 0; // hygeine - not really needed
|
| 1858 | m_iSumFrameTime = 0;
|
| 1859 | m_nNormal = 0;
|
| 1860 | m_trEarliness = 0;
|
| 1861 | m_trTarget = -300000; // 30mSec early
|
| 1862 | m_trThrottle = 0;
|
| 1863 | m_trRememberStampForPerf = 0;
|
| 1864 |
|
| 1865 | #ifdef PERF
|
| 1866 | m_trRememberFrameForPerf = 0;
|
| 1867 | #endif
|
| 1868 |
|
| 1869 | return NOERROR;
|
| 1870 | } // ResetStreamingTimes
|
| 1871 |
|
| 1872 |
|
| 1873 | // Reset all times controlling streaming. Note that we're now streaming. We
|
| 1874 | // don't need to set the rendering event to have the source filter released
|
| 1875 | // as it is done during the Run processing. When we are run we immediately
|
| 1876 | // release the source filter thread and draw any image waiting (that image
|
| 1877 | // may already have been drawn once as a poster frame while we were paused)
|
| 1878 |
|
| 1879 | HRESULT CBaseVideoRenderer::OnStartStreaming()
|
| 1880 | {
|
| 1881 | ResetStreamingTimes();
|
| 1882 | return NOERROR;
|
| 1883 | } // OnStartStreaming
|
| 1884 |
|
| 1885 |
|
| 1886 | // Called at end of streaming. Fixes times for property page report
|
| 1887 |
|
| 1888 | HRESULT CBaseVideoRenderer::OnStopStreaming()
|
| 1889 | {
|
| 1890 | m_tStreamingStart = timeGetTime()-m_tStreamingStart;
|
| 1891 | return NOERROR;
|
| 1892 | } // OnStopStreaming
|
| 1893 |
|
| 1894 |
|
| 1895 | // Called when we start waiting for a rendering event.
|
| 1896 | // Used to update times spent waiting and not waiting.
|
| 1897 |
|
| 1898 | void CBaseVideoRenderer::OnWaitStart()
|
| 1899 | {
|
| 1900 | MSR_START(m_idWaitReal);
|
| 1901 | } // OnWaitStart
|
| 1902 |
|
| 1903 |
|
| 1904 | // Called when we are awoken from the wait in the window OR by our allocator
|
| 1905 | // when it is hanging around until the next sample is due for rendering on a
|
| 1906 | // DCI/DirectDraw surface. We add the wait time into our rolling average.
|
| 1907 | // We grab the interface lock so that we're serialised with the application
|
| 1908 | // thread going through the run code - which in due course ends up calling
|
| 1909 | // ResetStreaming times - possibly as we run through this section of code
|
| 1910 |
|
| 1911 | void CBaseVideoRenderer::OnWaitEnd()
|
| 1912 | {
|
| 1913 | #ifdef PERF
|
| 1914 | MSR_STOP(m_idWaitReal);
|
| 1915 | // for a perf build we want to know just exactly how late we REALLY are.
|
| 1916 | // even if this means that we have to look at the clock again.
|
| 1917 |
|
| 1918 | REFERENCE_TIME trRealStream; // the real time now expressed as stream time.
|
| 1919 | #if 0
|
| 1920 | m_pClock->GetTime(&trRealStream); // Calling clock here causes W95 deadlock!
|
| 1921 | #else
|
| 1922 | // We will be discarding overflows like mad here!
|
| 1923 | // This is wrong really because timeGetTime() can wrap but it's
|
| 1924 | // only for PERF
|
| 1925 | REFERENCE_TIME tr = timeGetTime()*10000;
|
| 1926 | trRealStream = tr + m_llTimeOffset;
|
| 1927 | #endif
|
| 1928 | trRealStream -= m_tStart; // convert to stream time (this is a reftime)
|
| 1929 |
|
| 1930 | if (m_trRememberStampForPerf==0) {
|
| 1931 | // This is probably the poster frame at the start, and it is not scheduled
|
| 1932 | // in the usual way at all. Just count it. The rememberstamp gets set
|
| 1933 | // in ShouldDrawSampleNow, so this does invalid frame recording until we
|
| 1934 | // actually start playing.
|
| 1935 | PreparePerformanceData(0, 0);
|
| 1936 | } else {
|
| 1937 | int trLate = (int)(trRealStream - m_trRememberStampForPerf);
|
| 1938 | int trFrame = (int)(tr - m_trRememberFrameForPerf);
|
| 1939 | PreparePerformanceData(trLate, trFrame);
|
| 1940 | }
|
| 1941 | m_trRememberFrameForPerf = tr;
|
| 1942 | #endif //PERF
|
| 1943 | } // OnWaitEnd
|
| 1944 |
|
| 1945 |
|
| 1946 | // Put data on one side that describes the lateness of the current frame.
|
| 1947 | // We don't yet know whether it will actually be drawn. In direct draw mode,
|
| 1948 | // this decision is up to the filter upstream, and it could change its mind.
|
| 1949 | // The rules say that if it did draw it must call Receive(). One way or
|
| 1950 | // another we eventually get into either OnRenderStart or OnDirectRender and
|
| 1951 | // these both call RecordFrameLateness to update the statistics.
|
| 1952 |
|
| 1953 | void CBaseVideoRenderer::PreparePerformanceData(int trLate, int trFrame)
|
| 1954 | {
|
| 1955 | m_trLate = trLate;
|
| 1956 | m_trFrame = trFrame;
|
| 1957 | } // PreparePerformanceData
|
| 1958 |
|
| 1959 |
|
| 1960 | // update the statistics:
|
| 1961 | // m_iTotAcc, m_iSumSqAcc, m_iSumSqFrameTime, m_iSumFrameTime, m_cFramesDrawn
|
| 1962 | // Note that because the properties page reports using these variables,
|
| 1963 | // 1. We need to be inside a critical section
|
| 1964 | // 2. They must all be updated together. Updating the sums here and the count
|
| 1965 | // elsewhere can result in imaginary jitter (i.e. attempts to find square roots
|
| 1966 | // of negative numbers) in the property page code.
|
| 1967 |
|
| 1968 | void CBaseVideoRenderer::RecordFrameLateness(int trLate, int trFrame)
|
| 1969 | {
|
| 1970 | // Record how timely we are.
|
| 1971 | int tLate = trLate/10000;
|
| 1972 |
|
| 1973 | // Best estimate of moment of appearing on the screen is average of
|
| 1974 | // start and end draw times. Here we have only the end time. This may
|
| 1975 | // tend to show us as spuriously late by up to 1/2 frame rate achieved.
|
| 1976 | // Decoder probably monitors draw time. We don't bother.
|
| 1977 | MSR_INTEGER( m_idFrameAccuracy, tLate );
|
| 1978 |
|
| 1979 | // This is a kludge - we can get frames that are very late
|
| 1980 | // especially (at start-up) and they invalidate the statistics.
|
| 1981 | // So ignore things that are more than 1 sec off.
|
| 1982 | if (tLate>1000 || tLate<-1000) {
|
| 1983 | if (m_cFramesDrawn<=1) {
|
| 1984 | tLate = 0;
|
| 1985 | } else if (tLate>0) {
|
| 1986 | tLate = 1000;
|
| 1987 | } else {
|
| 1988 | tLate = -1000;
|
| 1989 | }
|
| 1990 | }
|
| 1991 | // The very first frame often has a invalid time, so don't
|
| 1992 | // count it into the statistics. (???)
|
| 1993 | if (m_cFramesDrawn>1) {
|
| 1994 | m_iTotAcc += tLate;
|
| 1995 | m_iSumSqAcc += (tLate*tLate);
|
| 1996 | }
|
| 1997 |
|
| 1998 | // calculate inter-frame time. Doesn't make sense for first frame
|
| 1999 | // second frame suffers from invalid first frame stamp.
|
| 2000 | if (m_cFramesDrawn>2) {
|
| 2001 | int tFrame = trFrame/10000; // convert to mSec else it overflows
|
| 2002 |
|
| 2003 | // This is a kludge. It can overflow anyway (a pause can cause
|
| 2004 | // a very long inter-frame time) and it overflows at 2**31/10**7
|
| 2005 | // or about 215 seconds i.e. 3min 35sec
|
| 2006 | if (tFrame>1000||tFrame<0) tFrame = 1000;
|
| 2007 | m_iSumSqFrameTime += tFrame*tFrame;
|
| 2008 | ASSERT(m_iSumSqFrameTime>=0);
|
| 2009 | m_iSumFrameTime += tFrame;
|
| 2010 | }
|
| 2011 | ++m_cFramesDrawn;
|
| 2012 |
|
| 2013 | } // RecordFrameLateness
|
| 2014 |
|
| 2015 |
|
| 2016 | void CBaseVideoRenderer::ThrottleWait()
|
| 2017 | {
|
| 2018 | if (m_trThrottle>0) {
|
| 2019 | int iThrottle = m_trThrottle/10000; // convert to mSec
|
| 2020 | MSR_INTEGER( m_idThrottle, iThrottle);
|
| 2021 | DbgLog((LOG_TRACE, 0, TEXT("Throttle %d ms"), iThrottle));
|
| 2022 | Sleep(iThrottle);
|
| 2023 | } else {
|
| 2024 | Sleep(0);
|
| 2025 | }
|
| 2026 | } // ThrottleWait
|
| 2027 |
|
| 2028 |
|
| 2029 | // Whenever a frame is rendered it goes though either OnRenderStart
|
| 2030 | // or OnDirectRender. Data that are generated during ShouldDrawSample
|
| 2031 | // are added to the statistics by calling RecordFrameLateness from both
|
| 2032 | // these two places.
|
| 2033 |
|
| 2034 | // Called in place of OnRenderStart..OnRenderEnd
|
| 2035 | // When a DirectDraw image is drawn
|
| 2036 | void CBaseVideoRenderer::OnDirectRender(IMediaSample *pMediaSample)
|
| 2037 | {
|
| 2038 | m_trRenderAvg = 0;
|
| 2039 | m_trRenderLast = 5000000; // If we mode switch, we do NOT want this
|
| 2040 | // to inhibit the new average getting going!
|
| 2041 | // so we set it to half a second
|
| 2042 | // MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000);
|
| 2043 | RecordFrameLateness(m_trLate, m_trFrame);
|
| 2044 | ThrottleWait();
|
| 2045 | } // OnDirectRender
|
| 2046 |
|
| 2047 |
|
| 2048 | // Called just before we start drawing. All we do is to get the current clock
|
| 2049 | // time (from the system) and return. We have to store the start render time
|
| 2050 | // in a member variable because it isn't used until we complete the drawing
|
| 2051 | // The rest is just performance logging.
|
| 2052 |
|
| 2053 | void CBaseVideoRenderer::OnRenderStart(IMediaSample *pMediaSample)
|
| 2054 | {
|
| 2055 | RecordFrameLateness(m_trLate, m_trFrame);
|
| 2056 | m_tRenderStart = timeGetTime();
|
| 2057 | } // OnRenderStart
|
| 2058 |
|
| 2059 |
|
| 2060 | // Called directly after drawing an image. We calculate the time spent in the
|
| 2061 | // drawing code and if this doesn't appear to have any odd looking spikes in
|
| 2062 | // it then we add it to the current average draw time. Measurement spikes may
|
| 2063 | // occur if the drawing thread is interrupted and switched to somewhere else.
|
| 2064 |
|
| 2065 | void CBaseVideoRenderer::OnRenderEnd(IMediaSample *pMediaSample)
|
| 2066 | {
|
| 2067 | // The renderer time can vary erratically if we are interrupted so we do
|
| 2068 | // some smoothing to help get more sensible figures out but even that is
|
| 2069 | // not enough as figures can go 9,10,9,9,83,9 and we must disregard 83
|
| 2070 |
|
| 2071 | int tr = (timeGetTime() - m_tRenderStart)*10000; // convert mSec->UNITS
|
| 2072 | if (tr < m_trRenderAvg*2 || tr < 2 * m_trRenderLast) {
|
| 2073 | // DO_MOVING_AVG(m_trRenderAvg, tr);
|
| 2074 | m_trRenderAvg = (tr + (AVGPERIOD-1)*m_trRenderAvg)/AVGPERIOD;
|
| 2075 | }
|
| 2076 | m_trRenderLast = tr;
|
| 2077 | ThrottleWait();
|
| 2078 | } // OnRenderEnd
|
| 2079 |
|
| 2080 |
|
| 2081 | STDMETHODIMP CBaseVideoRenderer::SetSink( IQualityControl * piqc)
|
| 2082 | {
|
| 2083 |
|
| 2084 | m_pQSink = piqc;
|
| 2085 |
|
| 2086 | return NOERROR;
|
| 2087 | } // SetSink
|
| 2088 |
|
| 2089 |
|
| 2090 | STDMETHODIMP CBaseVideoRenderer::Notify( IBaseFilter * pSelf, Quality q)
|
| 2091 | {
|
| 2092 | // NOTE: We are NOT getting any locks here. We could be called
|
| 2093 | // asynchronously and possibly even on a time critical thread of
|
| 2094 | // someone else's - so we do the minumum. We only set one state
|
| 2095 | // variable (an integer) and if that happens to be in the middle
|
| 2096 | // of another thread reading it they will just get either the new
|
| 2097 | // or the old value. Locking would achieve no more than this.
|
| 2098 |
|
| 2099 | // It might be nice to check that we are being called from m_pGraph, but
|
| 2100 | // it turns out to be a millisecond or so per throw!
|
| 2101 |
|
| 2102 | // This is heuristics, these numbers are aimed at being "what works"
|
| 2103 | // rather than anything based on some theory.
|
| 2104 | // We use a hyperbola because it's easy to calculate and it includes
|
| 2105 | // a panic button asymptote (which we push off just to the left)
|
| 2106 | // The throttling fits the following table (roughly)
|
| 2107 | // Proportion Throttle (msec)
|
| 2108 | // >=1000 0
|
| 2109 | // 900 3
|
| 2110 | // 800 7
|
| 2111 | // 700 11
|
| 2112 | // 600 17
|
| 2113 | // 500 25
|
| 2114 | // 400 35
|
| 2115 | // 300 50
|
| 2116 | // 200 72
|
| 2117 | // 125 100
|
| 2118 | // 100 112
|
| 2119 | // 50 146
|
| 2120 | // 0 200
|
| 2121 |
|
| 2122 | // (some evidence that we could go for a sharper kink - e.g. no throttling
|
| 2123 | // until below the 750 mark - might give fractionally more frames on a
|
| 2124 | // P60-ish machine). The easy way to get these coefficients is to use
|
| 2125 | // Renbase.xls follow the instructions therein using excel solver.
|
| 2126 |
|
| 2127 | if (q.Proportion>=1000) { m_trThrottle = 0; }
|
| 2128 | else {
|
| 2129 | // The DWORD is to make quite sure I get unsigned arithmetic
|
| 2130 | // as the constant is between 2**31 and 2**32
|
| 2131 | m_trThrottle = -330000 + (388880000/(q.Proportion+167));
|
| 2132 | }
|
| 2133 | return NOERROR;
|
| 2134 | } // Notify
|
| 2135 |
|
| 2136 |
|
| 2137 | // Send a message to indicate what our supplier should do about quality.
|
| 2138 | // Theory:
|
| 2139 | // What a supplier wants to know is "is the frame I'm working on NOW
|
| 2140 | // going to be late?".
|
| 2141 | // F1 is the frame at the supplier (as above)
|
| 2142 | // Tf1 is the due time for F1
|
| 2143 | // T1 is the time at that point (NOW!)
|
| 2144 | // Tr1 is the time that f1 WILL actually be rendered
|
| 2145 | // L1 is the latency of the graph for frame F1 = Tr1-T1
|
| 2146 | // D1 (for delay) is how late F1 will be beyond its due time i.e.
|
| 2147 | // D1 = (Tr1-Tf1) which is what the supplier really wants to know.
|
| 2148 | // Unfortunately Tr1 is in the future and is unknown, so is L1
|
| 2149 | //
|
| 2150 | // We could estimate L1 by its value for a previous frame,
|
| 2151 | // L0 = Tr0-T0 and work off
|
| 2152 | // D1' = ((T1+L0)-Tf1) = (T1 + (Tr0-T0) -Tf1)
|
| 2153 | // Rearranging terms:
|
| 2154 | // D1' = (T1-T0) + (Tr0-Tf1)
|
| 2155 | // adding (Tf0-Tf0) and rearranging again:
|
| 2156 | // = (T1-T0) + (Tr0-Tf0) + (Tf0-Tf1)
|
| 2157 | // = (T1-T0) - (Tf1-Tf0) + (Tr0-Tf0)
|
| 2158 | // But (Tr0-Tf0) is just D0 - how late frame zero was, and this is the
|
| 2159 | // Late field in the quality message that we send.
|
| 2160 | // The other two terms just state what correction should be applied before
|
| 2161 | // using the lateness of F0 to predict the lateness of F1.
|
| 2162 | // (T1-T0) says how much time has actually passed (we have lost this much)
|
| 2163 | // (Tf1-Tf0) says how much time should have passed if we were keeping pace
|
| 2164 | // (we have gained this much).
|
| 2165 | //
|
| 2166 | // Suppliers should therefore work off:
|
| 2167 | // Quality.Late + (T1-T0) - (Tf1-Tf0)
|
| 2168 | // and see if this is "acceptably late" or even early (i.e. negative).
|
| 2169 | // They get T1 and T0 by polling the clock, they get Tf1 and Tf0 from
|
| 2170 | // the time stamps in the frames. They get Quality.Late from us.
|
| 2171 | //
|
| 2172 |
|
| 2173 | HRESULT CBaseVideoRenderer::SendQuality(REFERENCE_TIME trLate,
|
| 2174 | REFERENCE_TIME trRealStream)
|
| 2175 | {
|
| 2176 | Quality q;
|
| 2177 | HRESULT hr;
|
| 2178 |
|
| 2179 | // If we are the main user of time, then report this as Flood/Dry.
|
| 2180 | // If our suppliers are, then report it as Famine/Glut.
|
| 2181 | //
|
| 2182 | // We need to take action, but avoid hunting. Hunting is caused by
|
| 2183 | // 1. Taking too much action too soon and overshooting
|
| 2184 | // 2. Taking too long to react (so averaging can CAUSE hunting).
|
| 2185 | //
|
| 2186 | // The reason why we use trLate as well as Wait is to reduce hunting;
|
| 2187 | // if the wait time is coming down and about to go into the red, we do
|
| 2188 | // NOT want to rely on some average which is only telling is that it used
|
| 2189 | // to be OK once.
|
| 2190 |
|
| 2191 | q.TimeStamp = (REFERENCE_TIME)trRealStream;
|
| 2192 |
|
| 2193 | if (m_trFrameAvg<0) {
|
| 2194 | q.Type = Famine; // guess
|
| 2195 | }
|
| 2196 | // Is the greater part of the time taken bltting or something else
|
| 2197 | else if (m_trFrameAvg > 2*m_trRenderAvg) {
|
| 2198 | q.Type = Famine; // mainly other
|
| 2199 | } else {
|
| 2200 | q.Type = Flood; // mainly bltting
|
| 2201 | }
|
| 2202 |
|
| 2203 | q.Proportion = 1000; // default
|
| 2204 |
|
| 2205 | if (m_trFrameAvg<0) {
|
| 2206 | // leave it alone - we don't know enough
|
| 2207 | }
|
| 2208 | else if ( trLate> 0 ) {
|
| 2209 | // try to catch up over the next second
|
| 2210 | // We could be Really, REALLY late, but rendering all the frames
|
| 2211 | // anyway, just because it's so cheap.
|
| 2212 |
|
| 2213 | q.Proportion = 1000 - (int)((trLate)/(UNITS/1000));
|
| 2214 | if (q.Proportion<500) {
|
| 2215 | q.Proportion = 500; // don't go daft. (could've been negative!)
|
| 2216 | } else {
|
| 2217 | }
|
| 2218 |
|
| 2219 | } else if ( m_trWaitAvg>20000
|
| 2220 | && trLate<-20000
|
| 2221 | ){
|
| 2222 | // Go cautiously faster - aim at 2mSec wait.
|
| 2223 | if (m_trWaitAvg>=m_trFrameAvg) {
|
| 2224 | // This can happen because of some fudges.
|
| 2225 | // The waitAvg is how long we originally planned to wait
|
| 2226 | // The frameAvg is more honest.
|
| 2227 | // It means that we are spending a LOT of time waiting
|
| 2228 | q.Proportion = 2000; // double.
|
| 2229 | } else {
|
| 2230 | if (m_trFrameAvg+20000 > m_trWaitAvg) {
|
| 2231 | q.Proportion
|
| 2232 | = 1000 * (m_trFrameAvg / (m_trFrameAvg + 20000 - m_trWaitAvg));
|
| 2233 | } else {
|
| 2234 | // We're apparently spending more than the whole frame time waiting.
|
| 2235 | // Assume that the averages are slightly out of kilter, but that we
|
| 2236 | // are indeed doing a lot of waiting. (This leg probably never
|
| 2237 | // happens, but the code avoids any potential divide by zero).
|
| 2238 | q.Proportion = 2000;
|
| 2239 | }
|
| 2240 | }
|
| 2241 |
|
| 2242 | if (q.Proportion>2000) {
|
| 2243 | q.Proportion = 2000; // don't go crazy.
|
| 2244 | }
|
| 2245 | }
|
| 2246 |
|
| 2247 | // Tell the supplier how late frames are when they get rendered
|
| 2248 | // That's how late we are now.
|
| 2249 | // If we are in directdraw mode then the guy upstream can see the drawing
|
| 2250 | // times and we'll just report on the start time. He can figure out any
|
| 2251 | // offset to apply. If we are in DIB Section mode then we will apply an
|
| 2252 | // extra offset which is half of our drawing time. This is usually small
|
| 2253 | // but can sometimes be the dominant effect. For this we will use the
|
| 2254 | // average drawing time rather than the last frame. If the last frame took
|
| 2255 | // a long time to draw and made us late, that's already in the lateness
|
| 2256 | // figure. We should not add it in again unless we expect the next frame
|
| 2257 | // to be the same. We don't, we expect the average to be a better shot.
|
| 2258 | // In direct draw mode the RenderAvg will be zero.
|
| 2259 |
|
| 2260 | q.Late = trLate + m_trRenderAvg/2;
|
| 2261 |
|
| 2262 | // log what we're doing
|
| 2263 | MSR_INTEGER(m_idQualityRate, q.Proportion);
|
| 2264 | MSR_INTEGER( m_idQualityTime, (int)q.Late / 10000 );
|
| 2265 |
|
| 2266 | // A specific sink interface may be set through IPin
|
| 2267 |
|
| 2268 | if (m_pQSink==NULL) {
|
| 2269 | // Get our input pin's peer. We send quality management messages
|
| 2270 | // to any nominated receiver of these things (set in the IPin
|
| 2271 | // interface), or else to our source filter.
|
| 2272 |
|
| 2273 | IQualityControl *pQC = NULL;
|
| 2274 | IPin *pOutputPin = m_pInputPin->GetConnected();
|
| 2275 | ASSERT(pOutputPin != NULL);
|
| 2276 |
|
| 2277 | // And get an AddRef'd quality control interface
|
| 2278 |
|
| 2279 | hr = pOutputPin->QueryInterface(IID_IQualityControl,(void**) &pQC);
|
| 2280 | if (SUCCEEDED(hr)) {
|
| 2281 | m_pQSink = pQC;
|
| 2282 | }
|
| 2283 | }
|
| 2284 | if (m_pQSink) {
|
| 2285 | return m_pQSink->Notify(this,q);
|
| 2286 | }
|
| 2287 |
|
| 2288 | return S_FALSE;
|
| 2289 |
|
| 2290 | } // SendQuality
|
| 2291 |
|
| 2292 |
|
| 2293 | // We are called with a valid IMediaSample image to decide whether this is to
|
| 2294 | // be drawn or not. There must be a reference clock in operation.
|
| 2295 | // Return S_OK if it is to be drawn Now (as soon as possible)
|
| 2296 | // Return S_FALSE if it is to be drawn when it's due
|
| 2297 | // Return an error if we want to drop it
|
| 2298 | // m_nNormal=-1 indicates that we dropped the previous frame and so this
|
| 2299 | // one should be drawn early. Respect it and update it.
|
| 2300 | // Use current stream time plus a number of heuristics (detailed below)
|
| 2301 | // to make the decision
|
| 2302 |
|
| 2303 | HRESULT CBaseVideoRenderer::ShouldDrawSampleNow(IMediaSample *pMediaSample,
|
| 2304 | __inout REFERENCE_TIME *ptrStart,
|
| 2305 | __inout REFERENCE_TIME *ptrEnd)
|
| 2306 | {
|
| 2307 |
|
| 2308 | // Don't call us unless there's a clock interface to synchronise with
|
| 2309 | ASSERT(m_pClock);
|
| 2310 |
|
| 2311 | MSR_INTEGER(m_idTimeStamp, (int)((*ptrStart)>>32)); // high order 32 bits
|
| 2312 | MSR_INTEGER(m_idTimeStamp, (int)(*ptrStart)); // low order 32 bits
|
| 2313 |
|
| 2314 | // We lose a bit of time depending on the monitor type waiting for the next
|
| 2315 | // screen refresh. On average this might be about 8mSec - so it will be
|
| 2316 | // later than we think when the picture appears. To compensate a bit
|
| 2317 | // we bias the media samples by -8mSec i.e. 80000 UNITs.
|
| 2318 | // We don't ever make a stream time negative (call it paranoia)
|
| 2319 | if (*ptrStart>=80000) {
|
| 2320 | *ptrStart -= 80000;
|
| 2321 | *ptrEnd -= 80000; // bias stop to to retain valid frame duration
|
| 2322 | }
|
| 2323 |
|
| 2324 | // Cache the time stamp now. We will want to compare what we did with what
|
| 2325 | // we started with (after making the monitor allowance).
|
| 2326 | m_trRememberStampForPerf = *ptrStart;
|
| 2327 |
|
| 2328 | // Get reference times (current and late)
|
| 2329 | REFERENCE_TIME trRealStream; // the real time now expressed as stream time.
|
| 2330 | m_pClock->GetTime(&trRealStream);
|
| 2331 | #ifdef PERF
|
| 2332 | // While the reference clock is expensive:
|
| 2333 | // Remember the offset from timeGetTime and use that.
|
| 2334 | // This overflows all over the place, but when we subtract to get
|
| 2335 | // differences the overflows all cancel out.
|
| 2336 | m_llTimeOffset = trRealStream-timeGetTime()*10000;
|
| 2337 | #endif
|
| 2338 | trRealStream -= m_tStart; // convert to stream time (this is a reftime)
|
| 2339 |
|
| 2340 | // We have to wory about two versions of "lateness". The truth, which we
|
| 2341 | // try to work out here and the one measured against m_trTarget which
|
| 2342 | // includes long term feedback. We report statistics against the truth
|
| 2343 | // but for operational decisions we work to the target.
|
| 2344 | // We use TimeDiff to make sure we get an integer because we
|
| 2345 | // may actually be late (or more likely early if there is a big time
|
| 2346 | // gap) by a very long time.
|
| 2347 | const int trTrueLate = TimeDiff(trRealStream - *ptrStart);
|
| 2348 | const int trLate = trTrueLate;
|
| 2349 |
|
| 2350 | MSR_INTEGER(m_idSchLateTime, trTrueLate/10000);
|
| 2351 |
|
| 2352 | // Send quality control messages upstream, measured against target
|
| 2353 | HRESULT hr = SendQuality(trLate, trRealStream);
|
| 2354 | // Note: the filter upstream is allowed to this FAIL meaning "you do it".
|
| 2355 | m_bSupplierHandlingQuality = (hr==S_OK);
|
| 2356 |
|
| 2357 | // Decision time! Do we drop, draw when ready or draw immediately?
|
| 2358 |
|
| 2359 | const int trDuration = (int)(*ptrEnd - *ptrStart);
|
| 2360 | {
|
| 2361 | // We need to see if the frame rate of the file has just changed.
|
| 2362 | // This would make comparing our previous frame rate with the current
|
| 2363 | // frame rate inefficent. Hang on a moment though. I've seen files
|
| 2364 | // where the frames vary between 33 and 34 mSec so as to average
|
| 2365 | // 30fps. A minor variation like that won't hurt us.
|
| 2366 | int t = m_trDuration/32;
|
| 2367 | if ( trDuration > m_trDuration+t
|
| 2368 | || trDuration < m_trDuration-t
|
| 2369 | ) {
|
| 2370 | // There's a major variation. Reset the average frame rate to
|
| 2371 | // exactly the current rate to disable decision 9002 for this frame,
|
| 2372 | // and remember the new rate.
|
| 2373 | m_trFrameAvg = trDuration;
|
| 2374 | m_trDuration = trDuration;
|
| 2375 | }
|
| 2376 | }
|
| 2377 |
|
| 2378 | MSR_INTEGER(m_idEarliness, m_trEarliness/10000);
|
| 2379 | MSR_INTEGER(m_idRenderAvg, m_trRenderAvg/10000);
|
| 2380 | MSR_INTEGER(m_idFrameAvg, m_trFrameAvg/10000);
|
| 2381 | MSR_INTEGER(m_idWaitAvg, m_trWaitAvg/10000);
|
| 2382 | MSR_INTEGER(m_idDuration, trDuration/10000);
|
| 2383 |
|
| 2384 | #ifdef PERF
|
| 2385 | if (S_OK==pMediaSample->IsDiscontinuity()) {
|
| 2386 | MSR_INTEGER(m_idDecision, 9000);
|
| 2387 | }
|
| 2388 | #endif
|
| 2389 |
|
| 2390 | // Control the graceful slide back from slow to fast machine mode.
|
| 2391 | // After a frame drop accept an early frame and set the earliness to here
|
| 2392 | // If this frame is already later than the earliness then slide it to here
|
| 2393 | // otherwise do the standard slide (reduce by about 12% per frame).
|
| 2394 | // Note: earliness is normally NEGATIVE
|
| 2395 | BOOL bJustDroppedFrame
|
| 2396 | = ( m_bSupplierHandlingQuality
|
| 2397 | // Can't use the pin sample properties because we might
|
| 2398 | // not be in Receive when we call this
|
| 2399 | && (S_OK == pMediaSample->IsDiscontinuity()) // he just dropped one
|
| 2400 | )
|
| 2401 | || (m_nNormal==-1); // we just dropped one
|
| 2402 |
|
| 2403 |
|
| 2404 | // Set m_trEarliness (slide back from slow to fast machine mode)
|
| 2405 | if (trLate>0) {
|
| 2406 | m_trEarliness = 0; // we are no longer in fast machine mode at all!
|
| 2407 | } else if ( (trLate>=m_trEarliness) || bJustDroppedFrame) {
|
| 2408 | m_trEarliness = trLate; // Things have slipped of their own accord
|
| 2409 | } else {
|
| 2410 | m_trEarliness = m_trEarliness - m_trEarliness/8; // graceful slide
|
| 2411 | }
|
| 2412 |
|
| 2413 | // prepare the new wait average - but don't pollute the old one until
|
| 2414 | // we have finished with it.
|
| 2415 | int trWaitAvg;
|
| 2416 | {
|
| 2417 | // We never mix in a negative wait. This causes us to believe in fast machines
|
| 2418 | // slightly more.
|
| 2419 | int trL = trLate<0 ? -trLate : 0;
|
| 2420 | trWaitAvg = (trL + m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD;
|
| 2421 | }
|
| 2422 |
|
| 2423 |
|
| 2424 | int trFrame;
|
| 2425 | {
|
| 2426 | REFERENCE_TIME tr = trRealStream - m_trLastDraw; // Cd be large - 4 min pause!
|
| 2427 | if (tr>10000000) {
|
| 2428 | tr = 10000000; // 1 second - arbitrarily.
|
| 2429 | }
|
| 2430 | trFrame = int(tr);
|
| 2431 | }
|
| 2432 |
|
| 2433 | // We will DRAW this frame IF...
|
| 2434 | if (
|
| 2435 | // ...the time we are spending drawing is a small fraction of the total
|
| 2436 | // observed inter-frame time so that dropping it won't help much.
|
| 2437 | (3*m_trRenderAvg <= m_trFrameAvg)
|
| 2438 |
|
| 2439 | // ...or our supplier is NOT handling things and the next frame would
|
| 2440 | // be less timely than this one or our supplier CLAIMS to be handling
|
| 2441 | // things, and is now less than a full FOUR frames late.
|
| 2442 | || ( m_bSupplierHandlingQuality
|
| 2443 | ? (trLate <= trDuration*4)
|
| 2444 | : (trLate+trLate < trDuration)
|
| 2445 | )
|
| 2446 |
|
| 2447 | // ...or we are on average waiting for over eight milliseconds then
|
| 2448 | // this may be just a glitch. Draw it and we'll hope to catch up.
|
| 2449 | || (m_trWaitAvg > 80000)
|
| 2450 |
|
| 2451 | // ...or we haven't drawn an image for over a second. We will update
|
| 2452 | // the display, which stops the video looking hung.
|
| 2453 | // Do this regardless of how late this media sample is.
|
| 2454 | || ((trRealStream - m_trLastDraw) > UNITS)
|
| 2455 |
|
| 2456 | ) {
|
| 2457 | HRESULT Result;
|
| 2458 |
|
| 2459 | // We are going to play this frame. We may want to play it early.
|
| 2460 | // We will play it early if we think we are in slow machine mode.
|
| 2461 | // If we think we are NOT in slow machine mode, we will still play
|
| 2462 | // it early by m_trEarliness as this controls the graceful slide back.
|
| 2463 | // and in addition we aim at being m_trTarget late rather than "on time".
|
| 2464 |
|
| 2465 | BOOL bPlayASAP = FALSE;
|
| 2466 |
|
| 2467 | // we will play it AT ONCE (slow machine mode) if...
|
| 2468 |
|
| 2469 | // ...we are playing catch-up
|
| 2470 | if ( bJustDroppedFrame) {
|
| 2471 | bPlayASAP = TRUE;
|
| 2472 | MSR_INTEGER(m_idDecision, 9001);
|
| 2473 | }
|
| 2474 |
|
| 2475 | // ...or if we are running below the true frame rate
|
| 2476 | // exact comparisons are glitchy, for these measurements,
|
| 2477 | // so add an extra 5% or so
|
| 2478 | else if ( (m_trFrameAvg > trDuration + trDuration/16)
|
| 2479 |
|
| 2480 | // It's possible to get into a state where we are losing ground, but
|
| 2481 | // are a very long way ahead. To avoid this or recover from it
|
| 2482 | // we refuse to play early by more than 10 frames.
|
| 2483 | && (trLate > - trDuration*10)
|
| 2484 | ){
|
| 2485 | bPlayASAP = TRUE;
|
| 2486 | MSR_INTEGER(m_idDecision, 9002);
|
| 2487 | }
|
| 2488 | #if 0
|
| 2489 | // ...or if we have been late and are less than one frame early
|
| 2490 | else if ( (trLate + trDuration > 0)
|
| 2491 | && (m_trWaitAvg<=20000)
|
| 2492 | ) {
|
| 2493 | bPlayASAP = TRUE;
|
| 2494 | MSR_INTEGER(m_idDecision, 9003);
|
| 2495 | }
|
| 2496 | #endif
|
| 2497 | // We will NOT play it at once if we are grossly early. On very slow frame
|
| 2498 | // rate movies - e.g. clock.avi - it is not a good idea to leap ahead just
|
| 2499 | // because we got starved (for instance by the net) and dropped one frame
|
| 2500 | // some time or other. If we are more than 900mSec early, then wait.
|
| 2501 | if (trLate<-9000000) {
|
| 2502 | bPlayASAP = FALSE;
|
| 2503 | }
|
| 2504 |
|
| 2505 | if (bPlayASAP) {
|
| 2506 |
|
| 2507 | m_nNormal = 0;
|
| 2508 | MSR_INTEGER(m_idDecision, 0);
|
| 2509 | // When we are here, we are in slow-machine mode. trLate may well
|
| 2510 | // oscillate between negative and positive when the supplier is
|
| 2511 | // dropping frames to keep sync. We should not let that mislead
|
| 2512 | // us into thinking that we have as much as zero spare time!
|
| 2513 | // We just update with a zero wait.
|
| 2514 | m_trWaitAvg = (m_trWaitAvg*(AVGPERIOD-1))/AVGPERIOD;
|
| 2515 |
|
| 2516 | // Assume that we draw it immediately. Update inter-frame stats
|
| 2517 | m_trFrameAvg = (trFrame + m_trFrameAvg*(AVGPERIOD-1))/AVGPERIOD;
|
| 2518 | #ifndef PERF
|
| 2519 | // If this is NOT a perf build, then report what we know so far
|
| 2520 | // without looking at the clock any more. This assumes that we
|
| 2521 | // actually wait for exactly the time we hope to. It also reports
|
| 2522 | // how close we get to the manipulated time stamps that we now have
|
| 2523 | // rather than the ones we originally started with. It will
|
| 2524 | // therefore be a little optimistic. However it's fast.
|
| 2525 | PreparePerformanceData(trTrueLate, trFrame);
|
| 2526 | #endif
|
| 2527 | m_trLastDraw = trRealStream;
|
| 2528 | if (m_trEarliness > trLate) {
|
| 2529 | m_trEarliness = trLate; // if we are actually early, this is neg
|
| 2530 | }
|
| 2531 | Result = S_OK; // Draw it now
|
| 2532 |
|
| 2533 | } else {
|
| 2534 | ++m_nNormal;
|
| 2535 | // Set the average frame rate to EXACTLY the ideal rate.
|
| 2536 | // If we are exiting slow-machine mode then we will have caught up
|
| 2537 | // and be running ahead, so as we slide back to exact timing we will
|
| 2538 | // have a longer than usual gap at this point. If we record this
|
| 2539 | // real gap then we'll think that we're running slow and go back
|
| 2540 | // into slow-machine mode and vever get it straight.
|
| 2541 | m_trFrameAvg = trDuration;
|
| 2542 | MSR_INTEGER(m_idDecision, 1);
|
| 2543 |
|
| 2544 | // Play it early by m_trEarliness and by m_trTarget
|
| 2545 |
|
| 2546 | {
|
| 2547 | int trE = m_trEarliness;
|
| 2548 | if (trE < -m_trFrameAvg) {
|
| 2549 | trE = -m_trFrameAvg;
|
| 2550 | }
|
| 2551 | *ptrStart += trE; // N.B. earliness is negative
|
| 2552 | }
|
| 2553 |
|
| 2554 | int Delay = -trTrueLate;
|
| 2555 | Result = Delay<=0 ? S_OK : S_FALSE; // OK = draw now, FALSE = wait
|
| 2556 |
|
| 2557 | m_trWaitAvg = trWaitAvg;
|
| 2558 |
|
| 2559 | // Predict when it will actually be drawn and update frame stats
|
| 2560 |
|
| 2561 | if (Result==S_FALSE) { // We are going to wait
|
| 2562 | trFrame = TimeDiff(*ptrStart-m_trLastDraw);
|
| 2563 | m_trLastDraw = *ptrStart;
|
| 2564 | } else {
|
| 2565 | // trFrame is already = trRealStream-m_trLastDraw;
|
| 2566 | m_trLastDraw = trRealStream;
|
| 2567 | }
|
| 2568 | #ifndef PERF
|
| 2569 | int iAccuracy;
|
| 2570 | if (Delay>0) {
|
| 2571 | // Report lateness based on when we intend to play it
|
| 2572 | iAccuracy = TimeDiff(*ptrStart-m_trRememberStampForPerf);
|
| 2573 | } else {
|
| 2574 | // Report lateness based on playing it *now*.
|
| 2575 | iAccuracy = trTrueLate; // trRealStream-RememberStampForPerf;
|
| 2576 | }
|
| 2577 | PreparePerformanceData(iAccuracy, trFrame);
|
| 2578 | #endif
|
| 2579 | }
|
| 2580 | return Result;
|
| 2581 | }
|
| 2582 |
|
| 2583 | // We are going to drop this frame!
|
| 2584 | // Of course in DirectDraw mode the guy upstream may draw it anyway.
|
| 2585 |
|
| 2586 | // This will probably give a large negative wack to the wait avg.
|
| 2587 | m_trWaitAvg = trWaitAvg;
|
| 2588 |
|
| 2589 | #ifdef PERF
|
| 2590 | // Respect registry setting - debug only!
|
| 2591 | if (m_bDrawLateFrames) {
|
| 2592 | return S_OK; // draw it when it's ready
|
| 2593 | } // even though it's late.
|
| 2594 | #endif
|
| 2595 |
|
| 2596 | // We are going to drop this frame so draw the next one early
|
| 2597 | // n.b. if the supplier is doing direct draw then he may draw it anyway
|
| 2598 | // but he's doing something funny to arrive here in that case.
|
| 2599 |
|
| 2600 | MSR_INTEGER(m_idDecision, 2);
|
| 2601 | m_nNormal = -1;
|
| 2602 | return E_FAIL; // drop it
|
| 2603 |
|
| 2604 | } // ShouldDrawSampleNow
|
| 2605 |
|
| 2606 |
|
| 2607 | // NOTE we're called by both the window thread and the source filter thread
|
| 2608 | // so we have to be protected by a critical section (locked before called)
|
| 2609 | // Also, when the window thread gets signalled to render an image, it always
|
| 2610 | // does so regardless of how late it is. All the degradation is done when we
|
| 2611 | // are scheduling the next sample to be drawn. Hence when we start an advise
|
| 2612 | // link to draw a sample, that sample's time will always become the last one
|
| 2613 | // drawn - unless of course we stop streaming in which case we cancel links
|
| 2614 |
|
| 2615 | BOOL CBaseVideoRenderer::ScheduleSample(IMediaSample *pMediaSample)
|
| 2616 | {
|
| 2617 | // We override ShouldDrawSampleNow to add quality management
|
| 2618 |
|
| 2619 | BOOL bDrawImage = CBaseRenderer::ScheduleSample(pMediaSample);
|
| 2620 | if (bDrawImage == FALSE) {
|
| 2621 | ++m_cFramesDropped;
|
| 2622 | return FALSE;
|
| 2623 | }
|
| 2624 |
|
| 2625 | // m_cFramesDrawn must NOT be updated here. It has to be updated
|
| 2626 | // in RecordFrameLateness at the same time as the other statistics.
|
| 2627 | return TRUE;
|
| 2628 | }
|
| 2629 |
|
| 2630 |
|
| 2631 | // Implementation of IQualProp interface needed to support the property page
|
| 2632 | // This is how the property page gets the data out of the scheduler. We are
|
| 2633 | // passed into the constructor the owning object in the COM sense, this will
|
| 2634 | // either be the video renderer or an external IUnknown if we're aggregated.
|
| 2635 | // We initialise our CUnknown base class with this interface pointer. Then
|
| 2636 | // all we have to do is to override NonDelegatingQueryInterface to expose
|
| 2637 | // our IQualProp interface. The AddRef and Release are handled automatically
|
| 2638 | // by the base class and will be passed on to the appropriate outer object
|
| 2639 |
|
| 2640 | STDMETHODIMP CBaseVideoRenderer::get_FramesDroppedInRenderer(__out int *pcFramesDropped)
|
| 2641 | {
|
| 2642 | CheckPointer(pcFramesDropped,E_POINTER);
|
| 2643 | CAutoLock cVideoLock(&m_InterfaceLock);
|
| 2644 | *pcFramesDropped = m_cFramesDropped;
|
| 2645 | return NOERROR;
|
| 2646 | } // get_FramesDroppedInRenderer
|
| 2647 |
|
| 2648 |
|
| 2649 | // Set *pcFramesDrawn to the number of frames drawn since
|
| 2650 | // streaming started.
|
| 2651 |
|
| 2652 | STDMETHODIMP CBaseVideoRenderer::get_FramesDrawn( int *pcFramesDrawn)
|
| 2653 | {
|
| 2654 | CheckPointer(pcFramesDrawn,E_POINTER);
|
| 2655 | CAutoLock cVideoLock(&m_InterfaceLock);
|
| 2656 | *pcFramesDrawn = m_cFramesDrawn;
|
| 2657 | return NOERROR;
|
| 2658 | } // get_FramesDrawn
|
| 2659 |
|
| 2660 |
|
| 2661 | // Set iAvgFrameRate to the frames per hundred secs since
|
| 2662 | // streaming started. 0 otherwise.
|
| 2663 |
|
| 2664 | STDMETHODIMP CBaseVideoRenderer::get_AvgFrameRate( int *piAvgFrameRate)
|
| 2665 | {
|
| 2666 | CheckPointer(piAvgFrameRate,E_POINTER);
|
| 2667 | CAutoLock cVideoLock(&m_InterfaceLock);
|
| 2668 |
|
| 2669 | int t;
|
| 2670 | if (m_bStreaming) {
|
| 2671 | t = timeGetTime()-m_tStreamingStart;
|
| 2672 | } else {
|
| 2673 | t = m_tStreamingStart;
|
| 2674 | }
|
| 2675 |
|
| 2676 | if (t<=0) {
|
| 2677 | *piAvgFrameRate = 0;
|
| 2678 | ASSERT(m_cFramesDrawn == 0);
|
| 2679 | } else {
|
| 2680 | // i is frames per hundred seconds
|
| 2681 | *piAvgFrameRate = MulDiv(100000, m_cFramesDrawn, t);
|
| 2682 | }
|
| 2683 | return NOERROR;
|
| 2684 | } // get_AvgFrameRate
|
| 2685 |
|
| 2686 |
|
| 2687 | // Set *piAvg to the average sync offset since streaming started
|
| 2688 | // in mSec. The sync offset is the time in mSec between when the frame
|
| 2689 | // should have been drawn and when the frame was actually drawn.
|
| 2690 |
|
| 2691 | STDMETHODIMP CBaseVideoRenderer::get_AvgSyncOffset(__out int *piAvg)
|
| 2692 | {
|
| 2693 | CheckPointer(piAvg,E_POINTER);
|
| 2694 | CAutoLock cVideoLock(&m_InterfaceLock);
|
| 2695 |
|
| 2696 | if (NULL==m_pClock) {
|
| 2697 | *piAvg = 0;
|
| 2698 | return NOERROR;
|
| 2699 | }
|
| 2700 |
|
| 2701 | // Note that we didn't gather the stats on the first frame
|
| 2702 | // so we use m_cFramesDrawn-1 here
|
| 2703 | if (m_cFramesDrawn<=1) {
|
| 2704 | *piAvg = 0;
|
| 2705 | } else {
|
| 2706 | *piAvg = (int)(m_iTotAcc / (m_cFramesDrawn-1));
|
| 2707 | }
|
| 2708 | return NOERROR;
|
| 2709 | } // get_AvgSyncOffset
|
| 2710 |
|
| 2711 |
|
| 2712 | // To avoid dragging in the maths library - a cheap
|
| 2713 | // approximate integer square root.
|
| 2714 | // We do this by getting a starting guess which is between 1
|
| 2715 | // and 2 times too large, followed by THREE iterations of
|
| 2716 | // Newton Raphson. (That will give accuracy to the nearest mSec
|
| 2717 | // for the range in question - roughly 0..1000)
|
| 2718 | //
|
| 2719 | // It would be faster to use a linear interpolation and ONE NR, but
|
| 2720 | // who cares. If anyone does - the best linear interpolation is
|
| 2721 | // to approximates sqrt(x) by
|
| 2722 | // y = x * (sqrt(2)-1) + 1 - 1/sqrt(2) + 1/(8*(sqrt(2)-1))
|
| 2723 | // 0r y = x*0.41421 + 0.59467
|
| 2724 | // This minimises the maximal error in the range in question.
|
| 2725 | // (error is about +0.008883 and then one NR will give error .0000something
|
| 2726 | // (Of course these are integers, so you can't just multiply by 0.41421
|
| 2727 | // you'd have to do some sort of MulDiv).
|
| 2728 | // Anyone wanna check my maths? (This is only for a property display!)
|
| 2729 |
|
| 2730 | int isqrt(int x)
|
| 2731 | {
|
| 2732 | int s = 1;
|
| 2733 | // Make s an initial guess for sqrt(x)
|
| 2734 | if (x > 0x40000000) {
|
| 2735 | s = 0x8000; // prevent any conceivable closed loop
|
| 2736 | } else {
|
| 2737 | while (s*s<x) { // loop cannot possible go more than 31 times
|
| 2738 | s = 2*s; // normally it goes about 6 times
|
| 2739 | }
|
| 2740 | // Three NR iterations.
|
| 2741 | if (x==0) {
|
| 2742 | s= 0; // Wouldn't it be tragic to divide by zero whenever our
|
| 2743 | // accuracy was perfect!
|
| 2744 | } else {
|
| 2745 | s = (s*s+x)/(2*s);
|
| 2746 | if (s>=0) s = (s*s+x)/(2*s);
|
| 2747 | if (s>=0) s = (s*s+x)/(2*s);
|
| 2748 | }
|
| 2749 | }
|
| 2750 | return s;
|
| 2751 | }
|
| 2752 |
|
| 2753 | //
|
| 2754 | // Do estimates for standard deviations for per-frame
|
| 2755 | // statistics
|
| 2756 | //
|
| 2757 | HRESULT CBaseVideoRenderer::GetStdDev(
|
| 2758 | int nSamples,
|
| 2759 | __out int *piResult,
|
| 2760 | LONGLONG llSumSq,
|
| 2761 | LONGLONG iTot
|
| 2762 | )
|
| 2763 | {
|
| 2764 | CheckPointer(piResult,E_POINTER);
|
| 2765 | CAutoLock cVideoLock(&m_InterfaceLock);
|
| 2766 |
|
| 2767 | if (NULL==m_pClock) {
|
| 2768 | *piResult = 0;
|
| 2769 | return NOERROR;
|
| 2770 | }
|
| 2771 |
|
| 2772 | // If S is the Sum of the Squares of observations and
|
| 2773 | // T the Total (i.e. sum) of the observations and there were
|
| 2774 | // N observations, then an estimate of the standard deviation is
|
| 2775 | // sqrt( (S - T**2/N) / (N-1) )
|
| 2776 |
|
| 2777 | if (nSamples<=1) {
|
| 2778 | *piResult = 0;
|
| 2779 | } else {
|
| 2780 | LONGLONG x;
|
| 2781 | // First frames have invalid stamps, so we get no stats for them
|
| 2782 | // So we need 2 frames to get 1 datum, so N is cFramesDrawn-1
|
| 2783 |
|
| 2784 | // so we use m_cFramesDrawn-1 here
|
| 2785 | x = llSumSq - llMulDiv(iTot, iTot, nSamples, 0);
|
| 2786 | x = x / (nSamples-1);
|
| 2787 | ASSERT(x>=0);
|
| 2788 | *piResult = isqrt((LONG)x);
|
| 2789 | }
|
| 2790 | return NOERROR;
|
| 2791 | }
|
| 2792 |
|
| 2793 | // Set *piDev to the standard deviation in mSec of the sync offset
|
| 2794 | // of each frame since streaming started.
|
| 2795 |
|
| 2796 | STDMETHODIMP CBaseVideoRenderer::get_DevSyncOffset(__out int *piDev)
|
| 2797 | {
|
| 2798 | // First frames have invalid stamps, so we get no stats for them
|
| 2799 | // So we need 2 frames to get 1 datum, so N is cFramesDrawn-1
|
| 2800 | return GetStdDev(m_cFramesDrawn - 1,
|
| 2801 | piDev,
|
| 2802 | m_iSumSqAcc,
|
| 2803 | m_iTotAcc);
|
| 2804 | } // get_DevSyncOffset
|
| 2805 |
|
| 2806 |
|
| 2807 | // Set *piJitter to the standard deviation in mSec of the inter-frame time
|
| 2808 | // of frames since streaming started.
|
| 2809 |
|
| 2810 | STDMETHODIMP CBaseVideoRenderer::get_Jitter(__out int *piJitter)
|
| 2811 | {
|
| 2812 | // First frames have invalid stamps, so we get no stats for them
|
| 2813 | // So second frame gives invalid inter-frame time
|
| 2814 | // So we need 3 frames to get 1 datum, so N is cFramesDrawn-2
|
| 2815 | return GetStdDev(m_cFramesDrawn - 2,
|
| 2816 | piJitter,
|
| 2817 | m_iSumSqFrameTime,
|
| 2818 | m_iSumFrameTime);
|
| 2819 | } // get_Jitter
|
| 2820 |
|
| 2821 |
|
| 2822 | // Overidden to return our IQualProp interface
|
| 2823 |
|
| 2824 | STDMETHODIMP
|
| 2825 | CBaseVideoRenderer::NonDelegatingQueryInterface(REFIID riid,__deref_out VOID **ppv)
|
| 2826 | {
|
| 2827 | // We return IQualProp and delegate everything else
|
| 2828 |
|
| 2829 | if (riid == IID_IQualProp) {
|
| 2830 | return GetInterface( (IQualProp *)this, ppv);
|
| 2831 | } else if (riid == IID_IQualityControl) {
|
| 2832 | return GetInterface( (IQualityControl *)this, ppv);
|
| 2833 | }
|
| 2834 | return CBaseRenderer::NonDelegatingQueryInterface(riid,ppv);
|
| 2835 | }
|
| 2836 |
|
| 2837 |
|
| 2838 | // Override JoinFilterGraph so that, just before leaving
|
| 2839 | // the graph we can send an EC_WINDOW_DESTROYED event
|
| 2840 |
|
| 2841 | STDMETHODIMP
|
| 2842 | CBaseVideoRenderer::JoinFilterGraph(__inout_opt IFilterGraph *pGraph, __in_opt LPCWSTR pName)
|
| 2843 | {
|
| 2844 | // Since we send EC_ACTIVATE, we also need to ensure
|
| 2845 | // we send EC_WINDOW_DESTROYED or the resource manager may be
|
| 2846 | // holding us as a focus object
|
| 2847 | if (!pGraph && m_pGraph) {
|
| 2848 |
|
| 2849 | // We were in a graph and now we're not
|
| 2850 | // Do this properly in case we are aggregated
|
| 2851 | IBaseFilter* pFilter = this;
|
| 2852 | NotifyEvent(EC_WINDOW_DESTROYED, (LPARAM) pFilter, 0);
|
| 2853 | }
|
| 2854 | return CBaseFilter::JoinFilterGraph(pGraph, pName);
|
| 2855 | }
|
| 2856 |
|
| 2857 |
|
| 2858 | // This removes a large number of level 4 warnings from the
|
| 2859 | // Microsoft compiler which in this case are not very useful
|
| 2860 | #pragma warning(disable: 4514)
|
| 2861 |
|
| 2862 | #endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */
|