Fix corrupted MP4 recordings on Windows#100
Conversation
The WGC capture session uses Direct3D11CaptureFramePool::CreateFreeThreaded, which dispatches FrameArrived callbacks on thread pool threads concurrently. MFEncoder::writeFrame() accesses non-thread-safe resources (ID3D11DeviceContext, IMFSinkWriter, shared staging texture and NV12 buffer) without synchronization, causing MP4 container corruption when callbacks overlap at high frame rates. Add a mutex to serialize writeFrame() and finalize() calls, preventing concurrent access to the D3D context and Media Foundation sink writer. Fixes webadderall#96
|
Warning
|
| Cohort / File(s) | Summary |
|---|---|
Thread Synchronization Header electron/native/wgc-capture/src/mf_encoder.h |
Added #include <mutex> and introduced private std::mutex writeMutex_ member for synchronization. |
Thread Synchronization Implementation electron/native/wgc-capture/src/mf_encoder.cpp |
Wrapped MFEncoder::writeFrame() and MFEncoder::finalize() methods with std::lock_guard to acquire writeMutex_, serializing access to frame-copy/mapping/conversion operations and sink writer finalization. |
Estimated code review effort
🎯 2 (Simple) | ⏱️ ~12 minutes
🐰 A mutex stands guard with care,
Threads no longer interfere,
Locks protect each precious frame,
Safe and sound now is the game!
✨ Synchronization's gentle dance,
Gives our data one more chance! 🎬
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title clearly summarizes the main fix: addressing corrupted MP4 recordings on Windows through thread-safety improvements to the capture encoder. |
| Description check | ✅ Passed | The description provides comprehensive context including root cause analysis, solution overview, and a detailed test plan covering the linked issue requirements. |
| Linked Issues check | ✅ Passed | The PR correctly addresses issue #96 by fixing the thread-safety bug causing corrupted MP4 files, which resolves the 'Failed to load video' error by ensuring recorded files are valid and playable. |
| Out of Scope Changes check | ✅ Passed | All changes are scoped to the thread-safety fix: adding mutex synchronization to MFEncoder's writeFrame() and finalize() methods. No unrelated modifications are present. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
- Create PR with unit tests
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
electron/native/wgc-capture/src/mf_encoder.cpp (1)
127-128: Lockinitialize()withwriteMutex_to close a race window in lifecycle state initialization.
initialize()modifiesinitialized_,sinkWriter_,stagingTexture_, andnv12Buffer_without acquiringwriteMutex_, whilewriteFrame()andfinalize()protect their checks with the same lock. Although the current call sequence (single init → many frames → single finalize) avoids collision in practice, the asymmetric locking leaves a window wherewriteFrame()could observe partially-initialized state if the threading model changes. Adding a lock guard at the start ofinitialize()ensures all state transitions are synchronized.Proposed patch
bool MFEncoder::initialize(const std::wstring& outputPath, int width, int height, int fps, ID3D11Device* device, ID3D11DeviceContext* context) { + std::lock_guard<std::mutex> lock(writeMutex_); if (initialized_) return false;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@electron/native/wgc-capture/src/mf_encoder.cpp` around lines 127 - 128, The initialize() method currently mutates initialized_, sinkWriter_, stagingTexture_, and nv12Buffer_ without holding writeMutex_, leaving a race against writeFrame() and finalize(); fix this by acquiring the same lock at the start of initialize() (e.g., add a std::lock_guard<std::mutex> lock(writeMutex_); as the first statement in initialize()) so all state changes to initialized_, sinkWriter_, stagingTexture_, and nv12Buffer_ are synchronized with writeFrame() and finalize().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@electron/native/wgc-capture/src/mf_encoder.cpp`:
- Around line 127-128: The initialize() method currently mutates initialized_,
sinkWriter_, stagingTexture_, and nv12Buffer_ without holding writeMutex_,
leaving a race against writeFrame() and finalize(); fix this by acquiring the
same lock at the start of initialize() (e.g., add a std::lock_guard<std::mutex>
lock(writeMutex_); as the first statement in initialize()) so all state changes
to initialized_, sinkWriter_, stagingTexture_, and nv12Buffer_ are synchronized
with writeFrame() and finalize().
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 832a336a-3b26-4070-b95e-6b910f8f0dcb
📒 Files selected for processing (2)
electron/native/wgc-capture/src/mf_encoder.cppelectron/native/wgc-capture/src/mf_encoder.h
Summary
wgc-capture) that causes corrupted/unplayable MP4 filesDirect3D11CaptureFramePool::CreateFreeThreadedframe pool dispatchesFrameArrivedcallbacks on thread pool threads concurrently, butMFEncoder::writeFrame()accesses non-thread-safe resources (ID3D11DeviceContext,IMFSinkWriter, shared staging texture and NV12 buffer) without synchronizationstd::mutexto serializewriteFrame()andfinalize()callsTest plan
Fixes #96
Summary by CodeRabbit