Skip to content

Commit c9ff08a

Browse files
committed
fix(win): pace the capture loop to a deadline instead of sleeping a full period
The video writer ended each iteration with `sleep_for(1/fps)`, after the frame had already been captured, converted and submitted to the sink writer. The real period was therefore `work + 1/fps`, never `1/fps`: at 1080p the work is ~11 ms, so a 30 fps recording ran at 22. Keep a `nextFrameDue` deadline and `sleep_until` it. When a frame runs long the deadline is resynced to now rather than carried forward, so a stall costs the frames it costs instead of being repaid as a burst of catch-up frames -- the same rule the webcam cadence a few lines above already follows. Measured on a Ryzen 5 7520U / Radeon iGPU, 15 s display capture at 1080p with 30 fps requested: before 317 frames / 14.672 s = 21.6 fps after 440 frames / 14.675 s = 30.0 fps The frame count comes from the new `[pacing]` line on stderr because the recording cannot answer this question: the sink writer re-times its output to nominal CFR, so `nb_frames / duration` reads exactly 30.000 whatever the loop actually did -- it stayed at 30 even with a 300 ms stall injected through OPENSCREEN_WGC_TEST_STALL_READBACK_MS. The line sits next to the existing [stop-timing] instrumentation and is picked up by the diagnostic tool's stderr capture. macOS and Linux do not share the pattern: ScreenCaptureKit paces the callbacks itself via `minimumFrameInterval`, and the pipewire helper derives its output frame index from the wall clock.
1 parent 09bc7d2 commit c9ff08a

1 file changed

Lines changed: 19 additions & 1 deletion

File tree

electron/native/wgc-capture/src/main.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,7 @@ int main(int argc, char* argv[]) {
734734
int64_t nextWebcamWriteDueHns = 0;
735735
const int64_t nominalWebcamIntervalHns =
736736
static_cast<int64_t>(10'000'000ULL / std::max(1, webcamCapture.fps()));
737+
auto nextFrameDue = std::chrono::steady_clock::now();
737738

738739
while (!control.stopRequested && !encodeFailed) {
739740
Microsoft::WRL::ComPtr<IMFSample> videoSample;
@@ -890,8 +891,25 @@ int main(int argc, char* argv[]) {
890891
}
891892

892893
frameIndex += 1;
893-
std::this_thread::sleep_for(frameDuration);
894+
// Pace to a deadline, not `sleep_for(frameDuration)` after the work:
895+
// capturing, converting and encoding a 1080p frame costs ~11 ms, so
896+
// sleeping a whole period on top of it made the real period
897+
// `work + 1/fps` -- 30 fps requested delivered 22.5 measured.
898+
nextFrameDue += frameDuration;
899+
const auto now = std::chrono::steady_clock::now();
900+
if (nextFrameDue < now) {
901+
// Fell behind (slow frame, or waiting on the first one). Resync
902+
// to now rather than firing a burst of catch-up frames, same as
903+
// the webcam cadence above.
904+
nextFrameDue = now;
905+
}
906+
std::this_thread::sleep_until(nextFrameDue);
894907
}
908+
std::cerr << "[pacing] frames=" << frameIndex << " elapsed_ms="
909+
<< std::chrono::duration_cast<std::chrono::milliseconds>(
910+
std::chrono::steady_clock::now() - control.recordingStartedAt)
911+
.count()
912+
<< std::endl;
895913
};
896914

897915
std::thread videoWriterThread;

0 commit comments

Comments
 (0)