Improve Windows screen capture frame rate

This commit is contained in:
evgeny committed 2026-10-17 00:38:29 +02:00
1 parent f5660a4642
commit bf729502a1
1 file changed
+44 -16
+44 -16
View File
@@ -76,7 +76,7 @@ struct StreamPublishEngine::Impl {
QVideoFrame imageInput; QVideoFrame imageInput;
uint64_t imageGeneration = 0; uint64_t imageGeneration = 0;
int64_t imagePts = -1; int64_t imagePts = -1;
bool imagePending = false, imageStop = false; bool imagePending = false, imageStop = false, imageDrawCursor = false;
uint64_t id = 0, group = 0, generation = 0, sourceGeneration = 0, confirmedGeneration = 0, frameGeneration = 0, audioSequence = 0; uint64_t id = 0, group = 0, generation = 0, sourceGeneration = 0, confirmedGeneration = 0, frameGeneration = 0, audioSequence = 0;
int64_t origin = 0, started = 0, framePts = -1, backendOrigin = -1, backendAnchor = -1; int64_t origin = 0, started = 0, framePts = -1, backendOrigin = -1, backendAnchor = -1;
int64_t audioAnchor = -1; int64_t audioAnchor = -1;
@@ -85,6 +85,7 @@ struct StreamPublishEngine::Impl {
uint64_t capturedFrames = 0; uint64_t capturedFrames = 0;
uint64_t convertedCaptureFrames = 0; uint64_t convertedCaptureFrames = 0;
int64_t captureReport = 0; int64_t captureReport = 0;
uint64_t captureReportRaw = 0, captureReportConverted = 0, captureReportEncoded = 0, captureReportDelivered = 0;
std::atomic<const char*> videoStage{"stopped"}; std::atomic<const char*> videoStage{"stopped"};
std::atomic<int64_t> videoStageAt{0}; std::atomic<int64_t> videoStageAt{0};
std::atomic<const char*> imageStage{"starting"}; std::atomic<const char*> imageStage{"starting"};
@@ -114,7 +115,7 @@ struct StreamPublishEngine::Impl {
#endif #endif
std::unique_ptr<QTimer> screenGrabTimer; std::unique_ptr<QTimer> screenGrabTimer;
#if defined(UTUN_STREAM_X11_CURSOR) || defined(UTUN_STREAM_WIN_CURSOR) #if defined(UTUN_STREAM_X11_CURSOR) || defined(UTUN_STREAM_WIN_CURSOR)
std::unique_ptr<StreamScreenCursor> cursor; std::shared_ptr<StreamScreenCursor> cursor;
#endif #endif
std::unique_ptr<QWindowCapture> window; std::unique_ptr<QWindowCapture> window;
QImage frame; QImage frame;
@@ -326,7 +327,8 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) {
{ {
std::lock_guard<std::mutex> guard(m->lock); std::lock_guard<std::mutex> guard(m->lock);
if (!m->running || generation != m->sourceGeneration) return; if (!m->running || generation != m->sourceGeneration) return;
m->imageInput = frame; m->imageGeneration = generation; m->imagePts = framePts; m->imagePending = true; m->imageInput = frame; m->imageGeneration = generation; m->imagePts = framePts;
m->imageDrawCursor = m->wanted.showCursor && cursorSupported(m->wanted.source); m->imagePending = true;
} }
m->imageWake.notify_one(); m->imageWake.notify_one();
}, Qt::DirectConnection); }, Qt::DirectConnection);
@@ -375,9 +377,9 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) {
#endif #endif
// QScreen даёт выбранный монитор без курсора; накладываем его один раз до preview и кодирования. // QScreen даёт выбранный монитор без курсора; накладываем его один раз до preview и кодирования.
// Для X11 это также исключает путаницу Qt Multimedia с одноимёнными root. // Для X11 это также исключает путаницу Qt Multimedia с одноимёнными root.
if (selected && (QGuiApplication::platformName() == "xcb" || cursorSupported(config.source))) { if (selected && QGuiApplication::platformName() == "xcb") {
#if defined(UTUN_STREAM_X11_CURSOR) || defined(UTUN_STREAM_WIN_CURSOR) #if defined(UTUN_STREAM_X11_CURSOR)
m->cursor = std::make_unique<StreamScreenCursor>(selected); { std::lock_guard<std::mutex> guard(m->lock); m->cursor = std::make_shared<StreamScreenCursor>(selected); }
#endif #endif
m->screenGrabTimer = std::make_unique<QTimer>(); m->screenGrabTimer->setTimerType(Qt::PreciseTimer); m->screenGrabTimer = std::make_unique<QTimer>(); m->screenGrabTimer->setTimerType(Qt::PreciseTimer);
connect(m->screenGrabTimer.get(), &QTimer::timeout, this, [this, generation, screen = QPointer<QScreen>(selected), failed] { connect(m->screenGrabTimer.get(), &QTimer::timeout, this, [this, generation, screen = QPointer<QScreen>(selected), failed] {
@@ -417,6 +419,9 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) {
qPrintable(QGuiApplication::platformName()), qPrintable(config.source.name), m->screenGrabTimer->interval(), qPrintable(QGuiApplication::platformName()), qPrintable(config.source.name), m->screenGrabTimer->interval(),
config.showCursor, cursorSupported(config.source)); config.showCursor, cursorSupported(config.source));
} else { } else {
#ifdef UTUN_STREAM_WIN_CURSOR
if (selected) { std::lock_guard<std::mutex> guard(m->lock); m->cursor = std::make_shared<StreamScreenCursor>(selected); }
#endif
m->screen = std::make_unique<QScreenCapture>(); m->screen = std::make_unique<QScreenCapture>();
m->screen->setScreen(selected); m->screen->setScreen(selected);
m->session->setScreenCapture(m->screen.get()); m->session->setScreenCapture(m->screen.get());
@@ -426,6 +431,10 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) {
}, Qt::QueuedConnection); }, Qt::QueuedConnection);
connect(m->screen.get(), &QScreenCapture::errorOccurred, this, [failed](QScreenCapture::Error, const QString& error) { failed(error); }, Qt::QueuedConnection); connect(m->screen.get(), &QScreenCapture::errorOccurred, this, [failed](QScreenCapture::Error, const QString& error) { failed(error); }, Qt::QueuedConnection);
m->screen->start(); m->screen->start();
#ifdef UTUN_STREAM_WIN_CURSOR
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop Windows screen capture backend=QtMultimedia cursor_overlay=frame_worker source=%s",
qPrintable(config.source.name));
#endif
} }
} else { } else {
auto window = config.source.window.value<QCapturableWindow>(); auto window = config.source.window.value<QCapturableWindow>();
@@ -451,13 +460,13 @@ void StreamPublishEngine::closeSource() {
std::lock_guard<std::mutex> guard(m->lock); std::lock_guard<std::mutex> guard(m->lock);
++m->sourceGeneration; m->imagePending = false; m->imageInput = {}; ++m->sourceGeneration; m->imagePending = false; m->imageInput = {};
m->frame = {}; m->latestImage = {}; m->previewFrame = {}; m->presentedPreview = {}; m->framePts = -1; m->frame = {}; m->latestImage = {}; m->previewFrame = {}; m->presentedPreview = {}; m->framePts = -1;
#if defined(UTUN_STREAM_X11_CURSOR) || defined(UTUN_STREAM_WIN_CURSOR)
m->cursor.reset();
#endif
} }
m->screenGrabTimer.reset(); m->screenGrabTimer.reset();
#ifdef UTUN_STREAM_PORTAL #ifdef UTUN_STREAM_PORTAL
m->portal.reset(); m->portal.reset();
#endif
#if defined(UTUN_STREAM_X11_CURSOR) || defined(UTUN_STREAM_WIN_CURSOR)
m->cursor.reset();
#endif #endif
emit previewVideoChanged(QVariant()); emit previewVideoChanged(QVariant());
if (m->sink) disconnect(m->sink.get(), nullptr, this, nullptr); if (m->sink) disconnect(m->sink.get(), nullptr, this, nullptr);
@@ -472,13 +481,14 @@ void StreamPublishEngine::closeSource() {
void StreamPublishEngine::imageLoop() { void StreamPublishEngine::imageLoop() {
#ifdef HAVE_STREAM_CAPTURE #ifdef HAVE_STREAM_CAPTURE
for (;;) { for (;;) {
QVideoFrame frame; uint64_t generation; int64_t pts; QVideoFrame frame; uint64_t generation; int64_t pts; bool drawCursor;
m->imageProgress("wait-frame"); m->imageProgress("wait-frame");
{ {
std::unique_lock<std::mutex> lock(m->lock); std::unique_lock<std::mutex> lock(m->lock);
m->imageWake.wait(lock, [this] { return m->imageStop || m->imagePending; }); m->imageWake.wait(lock, [this] { return m->imageStop || m->imagePending; });
if (m->imageStop) return; if (m->imageStop) return;
frame = std::move(m->imageInput); generation = m->imageGeneration; pts = m->imagePts; m->imagePending = false; frame = std::move(m->imageInput); generation = m->imageGeneration; pts = m->imagePts;
drawCursor = m->imageDrawCursor; m->imagePending = false;
} }
int64_t convertAt = monotonicUs(); int64_t convertAt = monotonicUs();
m->imageProgress("convert-frame"); m->imageProgress("convert-frame");
@@ -493,6 +503,14 @@ void StreamPublishEngine::imageLoop() {
QMetaObject::invokeMethod(this, [this, generation] { sourceFailed(generation, text("Video frame conversion failed")); }); QMetaObject::invokeMethod(this, [this, generation] { sourceFailed(generation, text("Video frame conversion failed")); });
continue; continue;
} }
#ifdef UTUN_STREAM_WIN_CURSOR
std::shared_ptr<StreamScreenCursor> cursor;
{ std::lock_guard<std::mutex> guard(m->lock); cursor = m->cursor; }
if (drawCursor && cursor && !cursor->draw(image)) {
QMetaObject::invokeMethod(this, [this, generation] { sourceFailed(generation, text("Could not capture the mouse cursor")); });
continue;
}
#endif
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
if (frame.rotation() != QtVideo::Rotation::None) image = image.transformed(QTransform().rotate(static_cast<int>(frame.rotation()))); if (frame.rotation() != QtVideo::Rotation::None) image = image.transformed(QTransform().rotate(static_cast<int>(frame.rotation())));
if (frame.mirrored()) image = image.transformed(QTransform().scale(-1, 1)); if (frame.mirrored()) image = image.transformed(QTransform().scale(-1, 1));
@@ -534,6 +552,7 @@ bool StreamPublishEngine::start(uint64_t group, const StreamPublishConfig& confi
m->origin = m->started = monotonicUs(); m->stats = {}; } m->origin = m->started = monotonicUs(); m->stats = {}; }
m->convertedFrames = m->encodedFrames = m->deliveredFrames = m->repeatedFrames = 0; m->convertedFrames = m->encodedFrames = m->deliveredFrames = m->repeatedFrames = 0;
m->videoProgress("starting"); m->captureReport = 0; m->videoProgress("starting"); m->captureReport = 0;
m->captureReportRaw = m->captureReportConverted = m->captureReportEncoded = m->captureReportDelivered = 0;
m->id = QRandomGenerator::global()->generate64(); if (!m->id) m->id = 1; m->id = QRandomGenerator::global()->generate64(); if (!m->id) m->id = 1;
m->group = group; m->audio.reset(); m->audioSequence = 0; m->muted = false; m->group = group; m->audio.reset(); m->audioSequence = 0; m->muted = false;
stream_media_format format{(uint16_t)config.video.width, (uint16_t)config.video.height, (uint16_t)config.video.rotation, stream_media_format format{(uint16_t)config.video.width, (uint16_t)config.video.height, (uint16_t)config.video.rotation,
@@ -983,13 +1002,22 @@ void StreamPublishEngine::tick() {
sourceFailed(m->generation, text("Timed out waiting for the first video frame")); sourceFailed(m->generation, text("Timed out waiting for the first video frame"));
{ std::lock_guard<std::mutex> guard(m->lock); { std::lock_guard<std::mutex> guard(m->lock);
if (monotonicUs() - m->captureReport >= 5000000) { if (monotonicUs() - m->captureReport >= 5000000) {
m->captureReport = monotonicUs(); int64_t reportAt = monotonicUs();
double elapsed = m->captureReport ? (reportAt - m->captureReport) / 1000000.0 : 0.0;
double rawFps = elapsed > 0 ? (m->capturedFrames - m->captureReportRaw) / elapsed : 0.0;
double convertedFps = elapsed > 0 ? (m->convertedFrames.load() - m->captureReportConverted) / elapsed : 0.0;
double encodedFps = elapsed > 0 ? (m->encodedFrames.load() - m->captureReportEncoded) / elapsed : 0.0;
double deliveredFps = elapsed > 0 ? (m->deliveredFrames.load() - m->captureReportDelivered) / elapsed : 0.0;
m->captureReport = reportAt; m->captureReportRaw = m->capturedFrames;
m->captureReportConverted = m->convertedFrames.load(); m->captureReportEncoded = m->encodedFrames.load();
m->captureReportDelivered = m->deliveredFrames.load();
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop capture progress source_generation=%llu video_generation=%llu confirmed=%llu raw_frames=%llu raw_PTS=%lld " DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop capture progress source_generation=%llu video_generation=%llu confirmed=%llu raw_frames=%llu raw_PTS=%lld "
"converted=%llu encoded=%llu delivered=%llu repeated=%llu stage=%s stage_age_ms=%lld", "raw_fps=%.1f converted=%llu converted_fps=%.1f encoded=%llu encoded_fps=%.1f delivered=%llu delivered_fps=%.1f "
"repeated=%llu stage=%s stage_age_ms=%lld",
(unsigned long long)m->sourceGeneration, (unsigned long long)m->generation, (unsigned long long)m->confirmedGeneration, (unsigned long long)m->sourceGeneration, (unsigned long long)m->generation, (unsigned long long)m->confirmedGeneration,
(unsigned long long)m->capturedFrames, (long long)m->framePts, (unsigned long long)m->capturedFrames, (long long)m->framePts, rawFps,
(unsigned long long)m->convertedFrames.load(), (unsigned long long)m->encodedFrames.load(), (unsigned long long)m->convertedFrames.load(), convertedFps, (unsigned long long)m->encodedFrames.load(), encodedFps,
(unsigned long long)m->deliveredFrames.load(), (unsigned long long)m->repeatedFrames.load(), (unsigned long long)m->deliveredFrames.load(), deliveredFps, (unsigned long long)m->repeatedFrames.load(),
m->videoStage.load(), (long long)((monotonicUs() - m->videoStageAt.load()) / 1000)); m->videoStage.load(), (long long)((monotonicUs() - m->videoStageAt.load()) / 1000));
} } } }
} }