Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 33 additions & 33 deletions electron/native/bin/win32-x64/helpers-manifest.json
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
{
"version": 1,
"platform": "win32",
"arch": "x64",
"helpers": {
"wgc-capture": {
"binaryName": "wgc-capture.exe",
"binarySha256": "4f8873abbff58add37672114184c154dee838939ee8f2b7df45d658d078af28c",
"sourceDir": "electron/native/wgc-capture",
"sourceFingerprint": "6f725fad6eb515d81b2d553ec45ddbf7c681d2725bba48f0c0722ce00166d967",
"updatedAt": "2026-05-09T00:49:28.306Z"
},
"cursor-monitor": {
"binaryName": "cursor-monitor.exe",
"binarySha256": "6ae6d91103b6e891a851e8ea5791e1c1f9aaab700134c18bc4c46cfffd7fdd12",
"sourceDir": "electron/native/cursor-monitor",
"sourceFingerprint": "6ad1b8b50bb336f2a48937b06f5ec56d90b6ab4a3e56a4bca278cf67a5d3e52e",
"updatedAt": "2026-05-07T15:22:18.173Z"
},
"recordly-gpu-export": {
"binaryName": "recordly-gpu-export.exe",
"binarySha256": "4cb3a293fd36f718af55906820d9b3fd78babc855888c2e248f0b918ec1aff3c",
"sourceDir": "electron/native/gpu-export-probe",
"sourceFingerprint": "743b386a5f1bbcc99cec5465c3de228d2b045061dead31dfcbf25cf6a1e61de5",
"updatedAt": "2026-05-07T20:13:48.585Z"
},
"recordly-nvidia-cuda-compositor": {
"binaryName": "recordly-nvidia-cuda-compositor.exe",
"binarySha256": "a787531c07142de7c292d1726e0339c97dbce5073d9a0853d539a725265fd945",
"sourceDir": "electron/native/nvidia-cuda-compositor",
"sourceFingerprint": "528b599e9d576d81ec087d0d4dc93a79af1bbf30fdb969f44f773bef90146135",
"updatedAt": "2026-05-07T20:14:13.794Z"
}
}
"version": 1,
"platform": "win32",
"arch": "x64",
"helpers": {
"wgc-capture": {
"binaryName": "wgc-capture.exe",
"binarySha256": "b840fb2847ac8fcfc5137febfc8dd49b55a4d15bea4f9185a0916fb0c1544bed",
"sourceDir": "electron/native/wgc-capture",
"sourceFingerprint": "fdbc8002a894a1300c7fed58a0f7aae18119dbb2bd71bcaacf3bfc398db904a9",
"updatedAt": "2026-05-22T10:12:36.734Z"
},
"cursor-monitor": {
"binaryName": "cursor-monitor.exe",
"binarySha256": "6ae6d91103b6e891a851e8ea5791e1c1f9aaab700134c18bc4c46cfffd7fdd12",
"sourceDir": "electron/native/cursor-monitor",
"sourceFingerprint": "6ad1b8b50bb336f2a48937b06f5ec56d90b6ab4a3e56a4bca278cf67a5d3e52e",
"updatedAt": "2026-05-07T15:22:18.173Z"
},
"recordly-gpu-export": {
"binaryName": "recordly-gpu-export.exe",
"binarySha256": "4cb3a293fd36f718af55906820d9b3fd78babc855888c2e248f0b918ec1aff3c",
"sourceDir": "electron/native/gpu-export-probe",
"sourceFingerprint": "743b386a5f1bbcc99cec5465c3de228d2b045061dead31dfcbf25cf6a1e61de5",
"updatedAt": "2026-05-07T20:13:48.585Z"
},
"recordly-nvidia-cuda-compositor": {
"binaryName": "recordly-nvidia-cuda-compositor.exe",
"binarySha256": "a787531c07142de7c292d1726e0339c97dbce5073d9a0853d539a725265fd945",
"sourceDir": "electron/native/nvidia-cuda-compositor",
"sourceFingerprint": "528b599e9d576d81ec087d0d4dc93a79af1bbf30fdb969f44f773bef90146135",
"updatedAt": "2026-05-07T20:14:13.794Z"
}
}
}
Binary file modified electron/native/bin/win32-x64/wgc-capture.exe
Binary file not shown.
36 changes: 31 additions & 5 deletions electron/native/wgc-capture/src/mf_encoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ bool MFEncoder::initialize(const std::wstring& outputPath, int width, int height

if (initialized_) return false;

if (fps <= 0) {
std::cerr << "ERROR: Encoder fps must be positive, got " << fps << std::endl;
return false;
}

if (width % 2 != 0 || height % 2 != 0) {
std::cerr << "ERROR: Encoder dimensions must be even, got " << width << "x" << height << std::endl;
return false;
Expand Down Expand Up @@ -227,6 +232,12 @@ bool MFEncoder::writeFrame(ID3D11Texture2D* texture, int64_t timestampHns) {

context_->Unmap(stagingTexture_.Get(), 0);

// WGC may stop delivering frames while the scene is static; keep the MP4
// timeline continuous by repeating the previous frame before writing a new one.
if (!lastFrameBuffer_.empty() && !extendLastFrameToLocked(timestampHns)) {
return false;
}

bool wroteSample = writeNv12SampleLocked(nv12Buffer_, timestampHns);
if (wroteSample) {
lastFrameBuffer_ = nv12Buffer_;
Expand All @@ -238,24 +249,39 @@ bool MFEncoder::writeFrame(ID3D11Texture2D* texture, int64_t timestampHns) {
bool MFEncoder::extendLastFrameTo(int64_t timestampHns) {
std::lock_guard<std::mutex> lock(mutex_);

return extendLastFrameToLocked(timestampHns);
}

bool MFEncoder::extendLastFrameToLocked(int64_t timestampHns) {
if (!initialized_ || !sinkWriter_) return false;
if (lastFrameBuffer_.empty()) return false;
if (lastSampleTimeHns_ < 0) return false;

if (fps_ <= 0) return false;
const int64_t frameDurationHns = 10000000LL / fps_;
if (lastSampleTimeHns_ >= 0 && timestampHns <= lastSampleTimeHns_ + frameDurationHns) {
if (frameDurationHns <= 0) return false;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (timestampHns <= lastSampleTimeHns_ + frameDurationHns) {
return true;
}

if (!writeNv12SampleLocked(lastFrameBuffer_, timestampHns)) {
return false;
int64_t nextSampleTimeHns = lastSampleTimeHns_ + frameDurationHns;
while (nextSampleTimeHns + frameDurationHns <= timestampHns) {
if (!writeNv12SampleLocked(lastFrameBuffer_, nextSampleTimeHns)) {
return false;
}
lastSampleTimeHns_ = nextSampleTimeHns;
nextSampleTimeHns += frameDurationHns;
}

lastSampleTimeHns_ = timestampHns;
return true;
}

bool MFEncoder::writeNv12SampleLocked(const std::vector<uint8_t>& frameBuffer, int64_t timestampHns) {
if (frameBuffer.empty()) return false;
if (fps_ <= 0) return false;

const int64_t frameDurationHns = 10000000LL / fps_;
if (frameDurationHns <= 0) return false;

// Create MF sample
DWORD bufferSize = static_cast<DWORD>(frameBuffer.size());
Expand All @@ -277,7 +303,7 @@ bool MFEncoder::writeNv12SampleLocked(const std::vector<uint8_t>& frameBuffer, i

sample->AddBuffer(buffer.Get());
sample->SetSampleTime(timestampHns);
sample->SetSampleDuration(10000000LL / fps_);
sample->SetSampleDuration(frameDurationHns);

hr = sinkWriter_->WriteSample(streamIndex_, sample.Get());
if (FAILED(hr)) {
Expand Down
1 change: 1 addition & 0 deletions electron/native/wgc-capture/src/mf_encoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class MFEncoder {
bool finalize();

private:
bool extendLastFrameToLocked(int64_t timestampHns);
bool writeNv12SampleLocked(const std::vector<uint8_t>& frameBuffer, int64_t timestampHns);

ComPtr<IMFSinkWriter> sinkWriter_;
Expand Down