From 0dec628e49c651ee1479397bf3d87f5f397042b6 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 15 Oct 2026 01:13:36 +0200 Subject: [PATCH] Prepare call video bitrate encoders without pausing capture --- tools/chatgui/src/call_video_encoder.cpp | 37 +++++++++++++++++++----- tools/chatgui/src/call_video_encoder.h | 8 +++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/tools/chatgui/src/call_video_encoder.cpp b/tools/chatgui/src/call_video_encoder.cpp index 5b0a0e78..85c27c54 100644 --- a/tools/chatgui/src/call_video_encoder.cpp +++ b/tools/chatgui/src/call_video_encoder.cpp @@ -28,6 +28,7 @@ bool CallVideoEncoder::open(const call_video_format& format, uint32_t bitrate, c void CallVideoEncoder::close() { if (m_encoder) DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call video encoder close generation=%llu pending_request=%llu", (unsigned long long)m_format.generation, (unsigned long long)m_request); + m_prepared = {}; // Завершение фоновой подготовки ждём только при остановке/смене режима. m_encoder.reset(); m_config = {}; m_config.bitrate = 0; m_error.clear(); m_format = {}; m_request = m_lastRequest = m_lastPts = m_lastKey = 0; m_pendingBitrate = 0; m_haveFrame = false; @@ -41,6 +42,22 @@ bool CallVideoEncoder::requestBitrate(uint64_t generation, uint64_t request, uin (unsigned long long)generation, (unsigned long long)m_format.generation, (unsigned long long)request, (unsigned long long)m_lastRequest, (unsigned long long)m_request, bitrate); return false; } + if (bitrate != (uint32_t)m_config.bitrate) { + LiveVideoConfig next = m_config; next.bitrate = bitrate; + try { + m_prepared = std::async(std::launch::async, [next] { + Prepared result; QElapsedTimer timer; timer.start(); + try { + result.encoder = std::make_unique(); + if (!result.encoder->open(next)) { result.error = result.encoder->error(); result.encoder.reset(); } + } catch (const std::exception& e) { result.error = QString::fromUtf8(e.what()); result.encoder.reset(); } + result.elapsed = timer.elapsed(); return result; + }); + } catch (const std::exception& e) { + DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video prepare start failed request=%llu: %s", (unsigned long long)request, e.what()); + return false; + } + } m_request = m_lastRequest = request; m_pendingBitrate = bitrate; DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call video bitrate queued generation=%llu request=%llu applied=%d target=%u last_key=%llu", (unsigned long long)generation, (unsigned long long)request, m_config.bitrate, bitrate, (unsigned long long)m_lastKey); return true; @@ -58,22 +75,28 @@ bool CallVideoEncoder::encode(const QImage& image, uint64_t pts, bool forceKey, } bool key = forceKey || !m_haveFrame || pts - m_lastKey >= 1000000; QList frames; - if (key && m_request) { + bool prepared = !m_prepared.valid() || m_prepared.wait_for(std::chrono::seconds(0)) == std::future_status::ready; + if (key && m_request && !prepared) + DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call video preparation pending request=%llu; keep current encoder", (unsigned long long)m_request); + if (key && m_request && prepared) { completion = {m_format.generation, m_request, 0}; if (m_pendingBitrate == (uint32_t)m_config.bitrate) completion.bitrate = m_pendingBitrate; else { LiveVideoConfig next = m_config; next.bitrate = m_pendingBitrate; - auto candidate = std::make_unique(); QElapsedTimer timer; timer.start(); - bool ready = candidate->open(next) && candidate->encode(image, pts, true, frames) - && frames.size() == 1 && frames.front().key && frames.front().pts == pts; + auto replacement = m_prepared.get(); auto& candidate = replacement.encoder; + QElapsedTimer timer; timer.start(); + bool ready = candidate && candidate->encode(image, pts, true, frames) + && frames.size() == 1 && frames.front().key && frames.front().pts == pts + && !call_video_check_key(&m_format, reinterpret_cast(frames.front().data.constData()), frames.front().data.size()); if (ready) { m_encoder = std::move(candidate); m_config = next; completion.bitrate = m_pendingBitrate; DEBUG_INFO(DEBUG_CATEGORY_CALL, - "call video bitrate committed generation=%llu request=%llu bitrate=%u pts=%llu prepare_ms=%lld bytes=%lld", + "call video bitrate committed generation=%llu request=%llu bitrate=%u pts=%llu background_ms=%lld commit_ms=%lld bytes=%lld", (unsigned long long)m_format.generation, (unsigned long long)m_request, completion.bitrate, - (unsigned long long)pts, (long long)timer.elapsed(), (long long)frames.front().data.size()); + (unsigned long long)pts, (long long)replacement.elapsed, (long long)timer.elapsed(), (long long)frames.front().data.size()); } else { - m_error = candidate->error(); if (m_error.isEmpty()) m_error = "Replacement encoder did not produce an immediate IDR"; + m_error = candidate ? candidate->error() : replacement.error; + if (m_error.isEmpty()) m_error = "Replacement encoder did not produce a compatible immediate IDR"; DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video replacement failed generation=%llu request=%llu target=%u: %s", (unsigned long long)m_format.generation, (unsigned long long)m_request, m_pendingBitrate, qPrintable(m_error)); frames.clear(); diff --git a/tools/chatgui/src/call_video_encoder.h b/tools/chatgui/src/call_video_encoder.h index daddb585..c29375f3 100644 --- a/tools/chatgui/src/call_video_encoder.h +++ b/tools/chatgui/src/call_video_encoder.h @@ -7,6 +7,7 @@ #pragma once #include "live_video_encoder.h" #include "../../../src/call/call_video.h" +#include struct CallVideoRateCompletion { uint64_t generation = 0, request = 0; @@ -23,6 +24,11 @@ public: uint32_t bitrate() const { return m_encoder ? m_config.bitrate : 0; } QString error() const { return m_error; } private: + struct Prepared { + std::unique_ptr encoder; + QString error; + qint64 elapsed = 0; + }; std::unique_ptr m_encoder; LiveVideoConfig m_config; call_video_format m_format{}; @@ -30,4 +36,6 @@ private: uint64_t m_request = 0, m_lastRequest = 0, m_lastPts = 0, m_lastKey = 0; uint32_t m_pendingBitrate = 0; bool m_haveFrame = false; + // Отдельный контекст готовится в фоне; worker забирает его только после завершения open. + std::future m_prepared; };