Avoid EC_DISPLAY_CHANGED for output-format-only renderer notifications in MPCVideoDec#1165
Conversation
ChangeOutputMediaFormat()'s renderer branch used NotifyEvent(EC_DISPLAY_CHANGED, ...) to tell a connected video renderer that the decoder's output media type changed. EC_DISPLAY_CHANGED is meant for a presenter to announce that *it* just reset its own D3D device (see SyncRenderer.cpp's OnResetDevice() and EVRAllocatorPresenter.cpp, both sending it after ResetDevice() per MS docs). quartz.dll's default filter graph manager reacts to EC_DISPLAY_CHANGED by automatically Stop()-ing (and restarting) the whole graph on its own worker thread. Since ChangeOutputMediaFormat(2) is reached the first time a D3D11 decode session discovers its real surface format - i.e. during completely normal first-time playback, not just on a deliberate resolution change - this fired on every channel tune, forcing the output pin through CompleteConnect()/PostConnect() again and recreating the D3D11 device a 2nd (sometimes 3rd) time within under a second. The resulting GPU resource churn could leave a freshly recreated allocator's buffer creation failing for several seconds, which had no recovery path and left decode permanently stuck producing no frames - observed as no video at TVTest startup or right after switching the video renderer setting while live. Send the new media type the same in-place way ReconnectOutput() already does for resolution changes instead: find a type the connected pin accepts and set it via SetMediaType()/m_bSendMediaType, which CD3D11Decoder:: DeliverFrame() already carries on the next delivered sample via IMediaSample::SetMediaType(). No connection-level reconnect, no EC_DISPLAY_CHANGED, no extra D3D11 device churn.
|
How to reproduce ? I checked this code when I wrote it, and everything works correctly. Test file with 2 different video streams - https://drive.google.com/file/d/1Gfpyq_FjU9gGloPj-sWjq_vIzbYkPqZa/view?usp=sharing |
|
Sorry for the confusion - you're right that this specific test doesn't reproduce it. I actually tested The trigger isn't a resolution change. It's I don't have a small synthetic file that reliably triggers this, but I have exact timestamped logging from the real scenario (D3D11 + 10-bit HEVC) showing the mechanism directly, not as a hypothesis: That second If you want to verify directly: add one The actual user-visible symptom: a D3D11 video renderer ends up recreating its D3D11 device because of that unwanted second |
|
AVC_MIX.ts - need select different video streams. |
Avoid implying upstream was at fault for not merging Aleksoid1978#1165 - just note the maintainer wasn't convinced, which is what actually happened.
|
Problem
With D3D11 hardware decoding selected, a connected video renderer (in my
case the MPC Video Renderer, but the underlying mechanism is
renderer-agnostic) can sometimes show no video at all - at TVTest's startup
auto-tune, or right after switching the renderer setting while a stream is
playing. Manually toggling playback off/on (forcing a fresh
avcodec_open2())"fixes" it.
Root cause
CMPCVideoDecFilter::ChangeOutputMediaFormat()'s renderer branch callsNotifyEvent(EC_DISPLAY_CHANGED, (LONG_PTR)pPin, 0)to tell a connectedvideo renderer that the decoder's output media type changed.
EC_DISPLAY_CHANGEDis meant for a presenter to announce that it justreset its own D3D device - see
SyncRenderer.cpp'sOnResetDevice()andEVRAllocatorPresenter.cpp, both of which send this event right afterResetDevice(), with a comment citing Microsoft's documentation("Presenter should send this message").
quartz.dll's default filter graph manager reacts to
EC_DISPLAY_CHANGEDbyautomatically
Stop()-ing (and restarting) the whole graph on its ownworker thread (
CFGControl::WorkerDisplayChanged->CFilterGraph::Stop()).Since
ChangeOutputMediaFormat(2)is reached the first time a D3D11 decodesession discovers its real surface format - i.e. during completely normal
first-time playback, not only on a deliberate resolution change - this
fires on every channel tune, forcing the output pin through
CompleteConnect()/CD3D11Decoder::PostConnect()again and recreating theD3D11 device a 2nd (sometimes 3rd) time within under a second of each
other. The resulting GPU resource churn can leave a freshly recreated
allocator's buffer creation failing for several seconds
(
CD3D11SurfaceAllocator::Commit()returningE_FAIL), which has norecovery path and leaves decode permanently stuck producing no frames.
Confirmed directly via logging:
NotifyEvent(EC_DISPLAY_CHANGED, ...)returning, and the second
CompleteConnect(PINDIR_OUTPUT)firing, wereconsistently ~100ms apart across multiple repro runs. Also confirmed this
isn't 4K/8K-specific - the same multi-
CompleteConnect()churn happens withregular 2K H.264 content too, it just doesn't reliably lose the GPU-resource
race there since the surfaces involved are much smaller.
Fix
Send the new media type the same in-place way
ReconnectOutput()alreadydoes for resolution changes, instead of
EC_DISPLAY_CHANGED: find a mediatype the connected pin's
QueryAccept()accepts, callm_pOutput->SetMediaType(&mt), and setm_bSendMediaType = truesoCD3D11Decoder::DeliverFrame()carries it on the next delivered sample viaIMediaSample::SetMediaType()- no connection-level reconnect, noEC_DISPLAY_CHANGED, no extra D3D11 device churn.Testing
Repeated TVTest startup auto-tune and live switching between MPC Video
Renderer and EVR - both reliably showed no video before this fix, both work
immediately after.