Skip to content

[Bug]: Windows 10 native recording can hang on stop and fail to save MP4 in 1.9.4-rc.3 #359

Description

@slipperpeng

Search existing issues

  • I have searched the existing issues.

Related: #252, #336, #346 (and the earlier #34 / #115 stop-path reports).

Describe the bug

On the packaged OpenScreen 1.9.4-rc.3 app on Windows 10, ending a native Windows screen recording can fail to save the recording. The user sees a save/recording error after clicking Stop, the editor does not open with a usable recording, and the MP4 may never be finalized.

I reproduced and debugged this from the openscreen-release-v1.9.4 source archive on the affected machine. There were three distinct problems in the native helper path:

  1. Windows 10 startup crash: session_.IsBorderRequired(false) was called through the projected GraphicsCaptureSession object without first querying the optional IGraphicsCaptureSession3 interface. On Windows 10 19045 this could terminate wgc-capture.exe with 0xC0000005; the surrounding C++ exception handler cannot catch a call through a missing ABI interface.
  2. Stop/save hang: the DXGI input path created a second D3D11 encoding device and copied every frame through a shared texture guarded by IDXGIKeyedMutex::AcquireSync. Under the full recording load on this machine, the display driver could fail to return from that cross-device synchronization. At the same time, application frame-state locking covered GPU copy/readback work. WGC callbacks then could not quiesce, the video thread could not join, and IMFSinkWriter::Finalize was never reached, leaving the MP4 unsaved.
  3. Packaged-app DPI compatibility: the installed 1.9.4-rc.3 main process supplied the selected display as Electron DIP bounds (2560x1440 at 150%), while the rebuilt per-monitor-v2-aware helper enumerated the physical monitor as 3840x2160. Strict matching rejected the only monitor and the app fell back from native capture. This is closely related to wgc-capture.exe is DPI-unaware, and its monitor lookup silently depends on it #346.

The virtual display adapter and the discrete GPU may make the D3D synchronization failure more likely, but the blocking cross-device bridge and lock ownership make a driver stall fatal regardless of the trigger.

Expected behavior

Clicking Stop should quiesce WGC callbacks, join the writer threads, finalize the fragmented MP4, emit recording-stopped, and open the editor promptly. Optional Windows capture interfaces must not crash older Windows versions, and a single-monitor scaled setup must resolve the selected display without weakening multi-monitor correctness.

To Reproduce

  1. Run packaged OpenScreen 1.9.4-rc.3 on Windows 10 22H2.
  2. Select the 3840x2160 display at 150% Windows scaling.
  3. Enable system audio, microphone, webcam, and editable cursor capture.
  4. Record for several seconds.
  5. Click Stop.
  6. Observe that native capture can fail to stop/finalize and the application reports that the recording could not be saved.

The native-helper failure is below the React UI. Chromium MediaRecorder/WebM fallback saved correctly, but that only avoids the affected WGC helper path.

Root cause details

The relevant code is under electron/native/wgc-capture/src/.

  • wgc_session.cpp: direct use of IsBorderRequired assumes IGraphicsCaptureSession3 is present.
  • mf_encoder.cpp / .h: the DXGI pipeline owns a second D3D11 device plus a shared keyed-mutex bridge between capture and encoding devices.
  • main.cpp: the WGC callback and video writer hold the frame-state mutex across D3D CopyResource, conversion, or staging readback work. A driver call that does not return prevents shutdown from draining the producer and joining the writer.
  • monitor_utils.cpp: physical bounds are correct for the current helper, but an already packaged 1.9.4-rc.3 main process can still provide legacy DIP bounds.

The finite keyed-mutex timeout was not a sufficient safeguard on this machine: the driver-level synchronization could still fail to return. Because the writer was stranded before finalization, the user-visible result was a save failure rather than a recoverable dropped frame.

Fix implemented and tested locally

I rebuilt wgc-capture.exe with the following changes:

  1. Query IGraphicsCaptureSession3 with try_as before setting IsBorderRequired; skip the cosmetic border setting when unavailable.
  2. Remove the second D3D11 encoding device, the shared bridge textures, and both IDXGIKeyedMutex handshakes.
  3. Reuse the WGC capture device and its multithread-protected immediate context for Media Foundation, BGRA-to-NV12 video processing, sample allocation, and hardware H.264 input.
  4. Create one stable BGRA frame texture before callbacks start. On the DXGI path it has D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET.
  5. Keep the frame-state mutex limited to timestamps and small state updates. Do CopyResource, GPU conversion, CPU readback, and synchronous encoder submission outside that application mutex. D3D's multithread protection serializes the shared immediate context.
  6. Prefer the repaired dxgi-nv12 path by default; retain OPENSCREEN_WGC_ENABLE_DXGI_INPUT=0 as a diagnostic CPU fallback.
  7. Preserve strict physical-bounds matching on multi-monitor systems. If and only if Windows enumerates exactly one monitor, accept legacy mismatched DIP bounds and select that sole monitor. This allows the rebuilt helper to work with the already packaged 1.9.4-rc.3 main process without reintroducing wgc-capture.exe is DPI-unaware, and its monitor lookup silently depends on it #346's wrong-monitor guessing.

Test results

All tests below were performed on the affected machine with the rebuilt helper.

Native helper, default configuration, display + system audio + microphone

  • videoInput: dxgi-nv12
  • 351 video frames
  • wgc-quiesce drained=true
  • recording-stopped emitted
  • Media Foundation finalize: approximately 169 ms
  • Total stop: approximately 215 ms
  • Exit code: 0

Repository full Windows helper test

Command: node scripts/test-windows-wgc-helper.mjs --webcam --system-audio --microphone

  • Screen: H.264, 8.033 s, 8,014,121 bytes
  • Audio: AAC, 7.872 s
  • Webcam: H.264, 1920x1080, 8.033 s, 8,329,975 bytes
  • Decoded first-frame mean brightness: 92.52 (not black)
  • Stop through all outputs saved: approximately 971 ms
  • Fragmented MP4 prefix readability check passed
  • Exit code: 0

End-to-end development app test, forced native helper

  • Log contains [native-wgc] starting Windows capture and [native-wgc] capture started
  • Encoder selection: default, videoInput: dxgi-nv12, container: fragmented-mp4
  • Stop to editor: approximately 1.32 s
  • Screen MP4: 3,818,300 bytes
  • Webcam MP4: 8,020,193 bytes
  • No Chromium/WebM fallback

End-to-end test of the actually installed packaged 1.9.4-rc.3 app

  • Used the helper from the installed app resources, with display + system audio + microphone + webcam + editable cursor overlay
  • Legacy 2560x1440 DIP bounds safely resolved to the sole physical 3840x2160 monitor
  • Encoder selection: default, videoInput: dxgi-nv12, container: fragmented-mp4
  • Stop to editor: 1,369 ms
  • Screen file: H.264 3840x2160 + AAC 48 kHz stereo, 8.0668 s, 6,947,583 bytes
  • Separate webcam file: H.264 1280x720, 8.0667 s, 7,860,646 bytes
  • Both files were non-empty and readable by ffprobe
  • No native-capture fallback and no save error

npm run build-vite also passed. The native helper and cursor sampler rebuilt successfully with Visual Studio 2022 Build Tools.

Screenshots

The original user-facing error was displayed immediately after ending the recording and stated that the recording could not be saved. I can provide the screenshot separately if useful; it contains no additional native stack trace.

OS

Windows

OS Version

Windows 10 Pro 22H2, version 10.0.19045, build 19045, 64-bit

Browser

Other

Browser Version

Packaged Electron app (Electron 41.2.1)

Device Type

Desktop

Additional context

  • OpenScreen: 1.9.4-rc.3 (1.9.4.0 product version)
  • CPU: AMD Ryzen 7 5700X, 8 cores / 16 logical processors
  • Memory: 31.9 GiB
  • Physical GPU: NVIDIA GeForce RTX 4060 Ti
  • NVIDIA driver: 31.0.15.5152
  • Additional display adapter: OrayIddDriver Device, driver 17.50.19.949
  • Display: 3840x2160
  • Windows scaling: 150% (AppliedDPI=144)
  • Rebuilt helper SHA-256: 1234FA69D894E6AEFB805BFD0CC4E4D9AE042806FF95DE3784DB94AE226893C2
  • Toolchain: Visual Studio 2022 Build Tools 17.10.5, Node 22.22.1, npm 10.9.4

No username, machine name, local paths, browser media device IDs, or recording content are included in this report.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions