diff --git a/electron/native/bin/win32-x64/helpers-manifest.json b/electron/native/bin/win32-x64/helpers-manifest.json index 047625ee..53baaf7a 100644 --- a/electron/native/bin/win32-x64/helpers-manifest.json +++ b/electron/native/bin/win32-x64/helpers-manifest.json @@ -5,10 +5,10 @@ "helpers": { "wgc-capture": { "binaryName": "wgc-capture.exe", - "binarySha256": "a0e2f0b98a903f89ab4706a56623f141604c3a311102e8914c2780745e94c635", + "binarySha256": "88226bd499c820081a27264b40f37875d0949d9f921920738e05b7eaeb1a9e7d", "sourceDir": "electron/native/wgc-capture", - "sourceFingerprint": "820d13741c659a0357d4d7944f3dbf4ce778b9716725b47ec088f573db0e7311", - "updatedAt": "2026-05-06T13:15:19.804Z" + "sourceFingerprint": "7190e148ede1fb1c573cc1866405d6345b0c35378a7e32499580bffacf1cc6d2", + "updatedAt": "2026-05-06T16:09:49.253Z" }, "cursor-monitor": { "binaryName": "cursor-monitor.exe", diff --git a/electron/native/bin/win32-x64/wgc-capture.exe b/electron/native/bin/win32-x64/wgc-capture.exe index c99ebf22..e55df506 100644 Binary files a/electron/native/bin/win32-x64/wgc-capture.exe and b/electron/native/bin/win32-x64/wgc-capture.exe differ diff --git a/electron/native/wgc-capture/src/main.cpp b/electron/native/wgc-capture/src/main.cpp index 65c2ec33..2c433b3b 100644 --- a/electron/native/wgc-capture/src/main.cpp +++ b/electron/native/wgc-capture/src/main.cpp @@ -231,6 +231,8 @@ static void writeCompanionAudioTimingMetadata( metadataFile << ",\"insertedSilenceFrames\":" << capture.insertedSilenceFrames(); metadataFile << ",\"compactedDiscontinuityCount\":" << capture.compactedDiscontinuityCount(); metadataFile << ",\"compactedDiscontinuityFrames\":" << capture.compactedDiscontinuityFrames(); + metadataFile << ",\"compactedSilentDiscontinuityCount\":" << capture.compactedSilentDiscontinuityCount(); + metadataFile << ",\"compactedSilentDiscontinuityFrames\":" << capture.compactedSilentDiscontinuityFrames(); const uint32_t discontinuityCount = capture.dataDiscontinuityCount(); if (discontinuityCount > 0) { metadataFile << ",\"dataDiscontinuityCount\":" << discontinuityCount; @@ -448,7 +450,8 @@ int main(int argc, char* argv[]) { loopback.dataDiscontinuityCount() > 0 || loopback.timestampErrorCount() > 0 || loopback.gapFillCount() > 0 || - loopback.compactedDiscontinuityCount() > 0 + loopback.compactedDiscontinuityCount() > 0 || + loopback.compactedSilentDiscontinuityCount() > 0 ) { std::cerr << "WARNING: System audio timing metadata includes discontinuities=" << loopback.dataDiscontinuityCount() @@ -457,6 +460,8 @@ int main(int argc, char* argv[]) { << " insertedSilenceFrames=" << loopback.insertedSilenceFrames() << " compactedDiscontinuities=" << loopback.compactedDiscontinuityCount() << " compactedDiscontinuityFrames=" << loopback.compactedDiscontinuityFrames() + << " compactedSilentDiscontinuities=" << loopback.compactedSilentDiscontinuityCount() + << " compactedSilentDiscontinuityFrames=" << loopback.compactedSilentDiscontinuityFrames() << std::endl; } std::cout << "Audio path: " << config.audioOutputPath << std::endl; @@ -466,7 +471,8 @@ int main(int argc, char* argv[]) { micCapture.dataDiscontinuityCount() > 0 || micCapture.timestampErrorCount() > 0 || micCapture.gapFillCount() > 0 || - micCapture.compactedDiscontinuityCount() > 0 + micCapture.compactedDiscontinuityCount() > 0 || + micCapture.compactedSilentDiscontinuityCount() > 0 ) { std::cerr << "WARNING: Microphone timing metadata includes discontinuities=" << micCapture.dataDiscontinuityCount() @@ -475,6 +481,8 @@ int main(int argc, char* argv[]) { << " insertedSilenceFrames=" << micCapture.insertedSilenceFrames() << " compactedDiscontinuities=" << micCapture.compactedDiscontinuityCount() << " compactedDiscontinuityFrames=" << micCapture.compactedDiscontinuityFrames() + << " compactedSilentDiscontinuities=" << micCapture.compactedSilentDiscontinuityCount() + << " compactedSilentDiscontinuityFrames=" << micCapture.compactedSilentDiscontinuityFrames() << std::endl; } std::cout << "Mic path: " << config.micOutputPath << std::endl; diff --git a/electron/native/wgc-capture/src/wasapi_loopback.cpp b/electron/native/wgc-capture/src/wasapi_loopback.cpp index 80fa4dcc..2a8a5d17 100644 --- a/electron/native/wgc-capture/src/wasapi_loopback.cpp +++ b/electron/native/wgc-capture/src/wasapi_loopback.cpp @@ -14,6 +14,7 @@ constexpr uint32_t kMinimumGapFillThresholdMs = 50; // Wireless headsets can flag short WASAPI discontinuities that are less audible // when compacted and later duration-corrected than when rendered as hard silence. constexpr uint32_t kDiscontinuityGapFillThresholdMs = 140; +constexpr uint32_t kSilentDiscontinuityCompactThresholdMs = 40; constexpr uint32_t kBoundaryFadeInMs = 5; int64_t queryPerformanceCounterHns() { @@ -197,6 +198,8 @@ bool WasapiCapture::start() { insertedSilenceFrames_ = 0; compactedDiscontinuityCount_ = 0; compactedDiscontinuityFrames_ = 0; + compactedSilentDiscontinuityCount_ = 0; + compactedSilentDiscontinuityFrames_ = 0; fadeInFramesRemaining_ = 0; writeWavHeader(outputFile_, 0); @@ -478,8 +481,28 @@ void WasapiCapture::captureThread() { } UINT32 totalSamples = numFrames * channels; + const bool isSilentPacket = (flags & AUDCLNT_BUFFERFLAGS_SILENT) != 0; + const uint32_t silentCompactThresholdFrames = + (mixFormat_->nSamplesPerSec * kSilentDiscontinuityCompactThresholdMs) / 1000; + if ( + hasDataDiscontinuity && + isSilentPacket && + framesWritten_.load() > 0 && + numFrames > 0 && + numFrames <= (silentCompactThresholdFrames > 0 ? silentCompactThresholdFrames : 1) + ) { + compactedSilentDiscontinuityCount_.fetch_add(1); + compactedSilentDiscontinuityFrames_.fetch_add(numFrames); + captureClient_->ReleaseBuffer(numFrames); + hr = captureClient_->GetNextPacketSize(&packetLength); + if (FAILED(hr)) { + std::cerr << "WASAPI: GetNextPacketSize failed hr=0x" << std::hex << hr << std::dec << std::endl; + break; + } + continue; + } - if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { + if (isSilentPacket) { pcmBuffer.assign(totalSamples, 0); } else if (isFloat && bitsPerSample == 32 && sourceBytesPerSample >= 4) { pcmBuffer.resize(totalSamples); diff --git a/electron/native/wgc-capture/src/wasapi_loopback.h b/electron/native/wgc-capture/src/wasapi_loopback.h index 01b6a01f..bf88cb16 100644 --- a/electron/native/wgc-capture/src/wasapi_loopback.h +++ b/electron/native/wgc-capture/src/wasapi_loopback.h @@ -30,6 +30,8 @@ public: uint64_t insertedSilenceFrames() const { return insertedSilenceFrames_.load(); } uint32_t compactedDiscontinuityCount() const { return compactedDiscontinuityCount_.load(); } uint64_t compactedDiscontinuityFrames() const { return compactedDiscontinuityFrames_.load(); } + uint32_t compactedSilentDiscontinuityCount() const { return compactedSilentDiscontinuityCount_.load(); } + uint64_t compactedSilentDiscontinuityFrames() const { return compactedSilentDiscontinuityFrames_.load(); } private: bool initializeCommon(); @@ -65,5 +67,7 @@ private: std::atomic insertedSilenceFrames_{0}; std::atomic compactedDiscontinuityCount_{0}; std::atomic compactedDiscontinuityFrames_{0}; + std::atomic compactedSilentDiscontinuityCount_{0}; + std::atomic compactedSilentDiscontinuityFrames_{0}; std::atomic fadeInFramesRemaining_{0}; };