mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-24 23:05:49 +00:00
fix(recording): stabilize WGC resize frames
This commit is contained in:
@@ -5,10 +5,10 @@
|
||||
"helpers": {
|
||||
"wgc-capture": {
|
||||
"binaryName": "wgc-capture.exe",
|
||||
"binarySha256": "88226bd499c820081a27264b40f37875d0949d9f921920738e05b7eaeb1a9e7d",
|
||||
"binarySha256": "ee24c7ce2fcd86eb513663416ad0aca673ac2a9b90b73532bc457e021b7b78a6",
|
||||
"sourceDir": "electron/native/wgc-capture",
|
||||
"sourceFingerprint": "7190e148ede1fb1c573cc1866405d6345b0c35378a7e32499580bffacf1cc6d2",
|
||||
"updatedAt": "2026-05-07T15:21:44.354Z"
|
||||
"sourceFingerprint": "c5a28a1999758f5b98dc79c247507eacb672a188d81d19aedc2dd656ee92a737",
|
||||
"updatedAt": "2026-05-08T16:48:17.850Z"
|
||||
},
|
||||
"cursor-monitor": {
|
||||
"binaryName": "cursor-monitor.exe",
|
||||
|
||||
Binary file not shown.
@@ -397,7 +397,7 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
// Wait for stop signal while pausing/resuming audio tracks in lockstep.
|
||||
while (!g_stopRequested) {
|
||||
while (!g_stopRequested && !session.hasFatalError()) {
|
||||
if (g_pauseRequested) {
|
||||
if (audioActive) loopback.pause();
|
||||
if (micActive) micCapture.pause();
|
||||
@@ -416,6 +416,13 @@ int main(int argc, char* argv[]) {
|
||||
if (audioActive) loopback.stop();
|
||||
if (micActive) micCapture.stop();
|
||||
|
||||
if (session.hasFatalError()) {
|
||||
std::cerr << "ERROR: WGC capture session failed during recording" << std::endl;
|
||||
encoder.finalize();
|
||||
DeleteFileW(outputPathW.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (audioActive) {
|
||||
writeCompanionAudioTimingMetadata(
|
||||
config.audioOutputPath,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <mfapi.h>
|
||||
#include <mferror.h>
|
||||
#include <codecapi.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
@@ -116,6 +117,31 @@ bool MFEncoder::initialize(const std::wstring& outputPath, int width, int height
|
||||
return false;
|
||||
}
|
||||
|
||||
// WGC window captures can change frame size while recording. Keep the muxer
|
||||
// output dimensions stable by compositing resized frames into this fixed
|
||||
// BGRA surface before CPU readback.
|
||||
D3D11_TEXTURE2D_DESC compositeDesc = {};
|
||||
compositeDesc.Width = width_;
|
||||
compositeDesc.Height = height_;
|
||||
compositeDesc.MipLevels = 1;
|
||||
compositeDesc.ArraySize = 1;
|
||||
compositeDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
compositeDesc.SampleDesc.Count = 1;
|
||||
compositeDesc.Usage = D3D11_USAGE_DEFAULT;
|
||||
compositeDesc.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
|
||||
hr = device_->CreateTexture2D(&compositeDesc, nullptr, &resizeCompositeTexture_);
|
||||
if (FAILED(hr)) {
|
||||
std::cerr << "ERROR: Failed to create resize composite texture: 0x" << std::hex << hr << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = device_->CreateRenderTargetView(resizeCompositeTexture_.Get(), nullptr, &resizeCompositeView_);
|
||||
if (FAILED(hr)) {
|
||||
std::cerr << "ERROR: Failed to create resize composite view: 0x" << std::hex << hr << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pre-allocate NV12 buffer
|
||||
const int ySize = width_ * height_;
|
||||
const int uvSize = (width_ / 2) * (height_ / 2) * 2;
|
||||
@@ -132,7 +158,39 @@ bool MFEncoder::writeFrame(ID3D11Texture2D* texture, int64_t timestampHns) {
|
||||
|
||||
if (!initialized_ || !sinkWriter_) return false;
|
||||
|
||||
context_->CopyResource(stagingTexture_.Get(), texture);
|
||||
D3D11_TEXTURE2D_DESC sourceDesc = {};
|
||||
texture->GetDesc(&sourceDesc);
|
||||
|
||||
if (sourceDesc.Width == static_cast<UINT>(width_) &&
|
||||
sourceDesc.Height == static_cast<UINT>(height_)) {
|
||||
context_->CopyResource(stagingTexture_.Get(), texture);
|
||||
} else {
|
||||
if (!resizeCompositeTexture_ || !resizeCompositeView_) return false;
|
||||
|
||||
const FLOAT clearColor[4] = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
context_->ClearRenderTargetView(resizeCompositeView_.Get(), clearColor);
|
||||
|
||||
D3D11_BOX sourceBox = {};
|
||||
sourceBox.left = 0;
|
||||
sourceBox.top = 0;
|
||||
sourceBox.front = 0;
|
||||
sourceBox.right = (std::min)(sourceDesc.Width, static_cast<UINT>(width_));
|
||||
sourceBox.bottom = (std::min)(sourceDesc.Height, static_cast<UINT>(height_));
|
||||
sourceBox.back = 1;
|
||||
|
||||
if (sourceBox.right == 0 || sourceBox.bottom == 0) return false;
|
||||
|
||||
context_->CopySubresourceRegion(
|
||||
resizeCompositeTexture_.Get(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
texture,
|
||||
0,
|
||||
&sourceBox);
|
||||
context_->CopyResource(stagingTexture_.Get(), resizeCompositeTexture_.Get());
|
||||
}
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE mapped;
|
||||
HRESULT hr = context_->Map(stagingTexture_.Get(), 0, D3D11_MAP_READ, 0, &mapped);
|
||||
@@ -242,6 +300,8 @@ bool MFEncoder::finalize() {
|
||||
initialized_ = false;
|
||||
sinkWriter_.Reset();
|
||||
stagingTexture_.Reset();
|
||||
resizeCompositeView_.Reset();
|
||||
resizeCompositeTexture_.Reset();
|
||||
nv12Buffer_.clear();
|
||||
lastFrameBuffer_.clear();
|
||||
nv12Buffer_.shrink_to_fit();
|
||||
|
||||
@@ -30,6 +30,8 @@ private:
|
||||
ID3D11Device* device_ = nullptr;
|
||||
ID3D11DeviceContext* context_ = nullptr;
|
||||
ComPtr<ID3D11Texture2D> stagingTexture_;
|
||||
ComPtr<ID3D11Texture2D> resizeCompositeTexture_;
|
||||
ComPtr<ID3D11RenderTargetView> resizeCompositeView_;
|
||||
std::vector<uint8_t> nv12Buffer_;
|
||||
std::vector<uint8_t> lastFrameBuffer_;
|
||||
DWORD streamIndex_ = 0;
|
||||
|
||||
@@ -24,6 +24,12 @@ extern "C" {
|
||||
IInspectable** graphicsDevice);
|
||||
}
|
||||
|
||||
static int normalizeFramePoolExtent(int value) {
|
||||
int normalized = value < 2 ? 2 : value;
|
||||
if ((normalized % 2) != 0) ++normalized;
|
||||
return normalized;
|
||||
}
|
||||
|
||||
WgcSession::WgcSession() {}
|
||||
|
||||
WgcSession::~WgcSession() {
|
||||
@@ -114,6 +120,8 @@ bool WgcSession::initializeWithItem(int fps) {
|
||||
auto size = captureItem_.Size();
|
||||
captureWidth_ = size.Width;
|
||||
captureHeight_ = size.Height;
|
||||
framePoolWidth_ = size.Width;
|
||||
framePoolHeight_ = size.Height;
|
||||
|
||||
framePool_ = winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::CreateFreeThreaded(
|
||||
winrtDevice_,
|
||||
@@ -134,6 +142,40 @@ bool WgcSession::initializeWithItem(int fps) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WgcSession::recreateFramePoolIfNeeded(
|
||||
winrt::Windows::Graphics::SizeInt32 const& contentSize) {
|
||||
if (!framePool_) return false;
|
||||
|
||||
const int normalizedWidth = normalizeFramePoolExtent(contentSize.Width);
|
||||
const int normalizedHeight = normalizeFramePoolExtent(contentSize.Height);
|
||||
if (normalizedWidth == framePoolWidth_ && normalizedHeight == framePoolHeight_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
winrt::Windows::Graphics::SizeInt32 normalizedSize{
|
||||
normalizedWidth,
|
||||
normalizedHeight,
|
||||
};
|
||||
|
||||
try {
|
||||
framePool_.Recreate(
|
||||
winrtDevice_,
|
||||
winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
|
||||
2,
|
||||
normalizedSize);
|
||||
framePoolWidth_ = normalizedWidth;
|
||||
framePoolHeight_ = normalizedHeight;
|
||||
std::cerr << "INFO: Recreated WGC frame pool for resized content "
|
||||
<< framePoolWidth_ << "x" << framePoolHeight_ << std::endl;
|
||||
} catch (winrt::hresult_error const& e) {
|
||||
fatalError_ = true;
|
||||
std::cerr << "ERROR: Failed to recreate WGC frame pool after resize: 0x"
|
||||
<< std::hex << e.code() << std::dec << std::endl;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WgcSession::initialize(HMONITOR monitor, int fps) {
|
||||
fps_ = fps;
|
||||
frameIntervalHns_ = 10000000LL / fps_;
|
||||
@@ -174,6 +216,7 @@ bool WgcSession::startCapture() {
|
||||
if (!session_ || !framePool_) return false;
|
||||
|
||||
capturing_ = true;
|
||||
fatalError_ = false;
|
||||
lastFrameTimeHns_ = 0;
|
||||
|
||||
frameArrivedRevoker_ = framePool_.FrameArrived(
|
||||
@@ -209,6 +252,11 @@ void WgcSession::onFrameArrived(
|
||||
|
||||
auto frame = sender.TryGetNextFrame();
|
||||
if (!frame) return;
|
||||
auto contentSize = frame.ContentSize();
|
||||
if (recreateFramePoolIfNeeded(contentSize)) {
|
||||
frame.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
auto timestamp = frame.SystemRelativeTime();
|
||||
int64_t frameTimeHns = std::chrono::duration_cast<std::chrono::duration<int64_t, std::ratio<1, 10000000>>>(timestamp).count();
|
||||
|
||||
@@ -28,6 +28,7 @@ public:
|
||||
void setFrameCallback(FrameCallback callback);
|
||||
bool startCapture();
|
||||
void stopCapture();
|
||||
bool hasFatalError() const { return fatalError_.load(); }
|
||||
|
||||
int captureWidth() const { return captureWidth_; }
|
||||
int captureHeight() const { return captureHeight_; }
|
||||
@@ -45,9 +46,12 @@ private:
|
||||
|
||||
FrameCallback frameCallback_;
|
||||
std::atomic<bool> capturing_{false};
|
||||
std::atomic<bool> fatalError_{false};
|
||||
int fps_ = 60;
|
||||
int captureWidth_ = 0;
|
||||
int captureHeight_ = 0;
|
||||
int framePoolWidth_ = 0;
|
||||
int framePoolHeight_ = 0;
|
||||
int64_t frameIntervalHns_ = 0;
|
||||
int64_t lastFrameTimeHns_ = 0;
|
||||
|
||||
@@ -56,6 +60,8 @@ private:
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem createCaptureItemForMonitor(HMONITOR monitor);
|
||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem createCaptureItemForWindow(HWND hwnd);
|
||||
bool initializeWithItem(int fps);
|
||||
bool recreateFramePoolIfNeeded(
|
||||
winrt::Windows::Graphics::SizeInt32 const& contentSize);
|
||||
void onFrameArrived(
|
||||
winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool const& sender,
|
||||
winrt::Windows::Foundation::IInspectable const& args);
|
||||
|
||||
Reference in New Issue
Block a user