From 3037c5fa877d6325a01f9fcb683bca6474a1858d Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 20 Oct 2026 00:16:23 +0200 Subject: [PATCH] Verify 50 fps backends and decode all frames during display catch-up --- tools/chatgui/src/call_video_decoder.cpp | 12 +++--- tools/chatgui/src/call_video_engine.cpp | 2 +- tools/chatgui/tests/test_call_video_clock.cpp | 39 +++++++++++++++---- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/tools/chatgui/src/call_video_decoder.cpp b/tools/chatgui/src/call_video_decoder.cpp index 11b058c5..9dccd1ef 100644 --- a/tools/chatgui/src/call_video_decoder.cpp +++ b/tools/chatgui/src/call_video_decoder.cpp @@ -16,8 +16,8 @@ extern "C" { } namespace { // Отдельная проверка для 720p и Full HD: CPU-резерв 1080p не отключает GPU на 720p. -std::atomic gpuProfiles[2][2][2]{}; -std::atomic gpuPipeline[2][2][2]{}; +std::atomic gpuProfiles[2][2][3]{}; +std::atomic gpuPipeline[2][2][3]{}; QString ffError(int value) { char text[AV_ERROR_MAX_STRING_SIZE]; av_strerror(value, text, sizeof(text)); return text; } #ifdef _WIN32 constexpr AVHWDeviceType DeviceType = AV_HWDEVICE_TYPE_D3D11VA; @@ -59,7 +59,7 @@ struct CallVideoDecoder::Impl { if (!codec) { failed("allocation", AVERROR(ENOMEM)); return false; } unsigned verifiedPipeline = 0; bool verifiedGpu = false; for (unsigned tier = 0; tier < 2; ++tier) { - const unsigned rate = f.fps > 30 ? 1 : 0; + const unsigned rate = f.fps > 50 ? 2 : (f.fps > 30 ? 1 : 0); const unsigned width = tier ? 1920 : 1280, height = tier ? 1080 : 720; if (std::max(f.width, f.height) <= width && uint64_t(f.width) * f.height <= width * height && (gpuProfiles[f.codec - 1][tier][rate].load() & f.profile)) { @@ -247,8 +247,8 @@ call_video_caps CallVideoDecoder::capabilities(const std::atomic* running) call_video_limit limit{}; for (unsigned profile = 1; profile <= 2; ++profile) { for (unsigned tier = 0; tier < 2; ++tier) { - for (unsigned rate = 0; rate < 2; ++rate) { - const unsigned fps = rate ? 60 : 30; + for (unsigned rate = 0; rate < 3; ++rate) { + const unsigned fps = rate == 2 ? 60 : (rate ? 50 : 30); const unsigned width = tier ? 1920 : 1280, height = tier ? 1080 : 720; const uint8_t level = codec == 1 ? (tier ? (rate ? 42 : 40) : (rate ? 32 : 31)) : (tier ? (rate ? 123 : 120) : (rate || profile == 2 ? 120 : 93)); @@ -259,7 +259,7 @@ call_video_caps CallVideoDecoder::capabilities(const std::atomic* running) for (int portrait = 0; portrait <= 1 && bothOrientations; ++portrait) { if (cancelled()) return {}; const unsigned w = portrait ? height : width, h = portrait ? width : height; - uint8_t bytes[8192]; int size = call_video_probe_key(codec, profile, w, h, fps, bytes, sizeof(bytes)); + uint8_t bytes[8192]; int size = call_video_probe_key(codec, profile, w, h, rate ? 60 : 30, bytes, sizeof(bytes)); if (size < 0) { bothOrientations = false; break; } CallVideoDecoder decoder(backend); call_video_frame input{}; input.call_id = 1; input.flags = CALL_VIDEO_KEY; diff --git a/tools/chatgui/src/call_video_engine.cpp b/tools/chatgui/src/call_video_engine.cpp index a741d4ca..d8b06d1c 100644 --- a/tools/chatgui/src/call_video_engine.cpp +++ b/tools/chatgui/src/call_video_engine.cpp @@ -257,7 +257,7 @@ void CallVideoEngine::transmit() { // Не связываем приём с наличием собственного encoder. for (unsigned codec = 1; codec <= 2 && m->running; ++codec) { for (const QSize size : {QSize(1920, 1080), QSize(1280, 720)}) { - for (unsigned fps : {30u, 60u}) { + for (unsigned fps : {30u, 50u, 60u}) { const bool fullhd = size.width() == 1920; call_video_format probe{1, (uint16_t)size.width(), (uint16_t)size.height(), (uint16_t)fps, 0, (uint8_t)codec, (uint8_t)(codec == 1 ? (fullhd ? (fps > 30 ? 42 : 40) : (fps > 30 ? 32 : 31)) diff --git a/tools/chatgui/tests/test_call_video_clock.cpp b/tools/chatgui/tests/test_call_video_clock.cpp index 50d55443..5a7a70f8 100644 --- a/tools/chatgui/tests/test_call_video_clock.cpp +++ b/tools/chatgui/tests/test_call_video_clock.cpp @@ -23,7 +23,8 @@ static std::atomic requests{0}, rejects{0}; static std::atomic cursor{0}; static int64_t start; static bool disturbed = false; -static bool slowDecoder = false, holdAck = false; +static bool slowDecoder = false, holdAck = false, limitedDisplay = false; +static std::atomic decodedFrames{0}; static std::atomic displayThread{0}; static std::atomic unsafeTimer{false}; static size_t threadId() { return std::hash{}(std::this_thread::get_id()); } @@ -32,6 +33,7 @@ extern "C" int __real_avcodec_receive_frame(AVCodecContext*, AVFrame*); extern "C" int __wrap_avcodec_receive_frame(AVCodecContext* codec, AVFrame* frame) { if (threadId() == displayThread.load()) unsafeTimer = true; int result = __real_avcodec_receive_frame(codec, frame); + if (!result && frame->width == 640 && frame->height == 360) ++decodedFrames; if (!result && slowDecoder && frame->pts >= 1000000 && (frame->pts - 1000000) / 33333 % 3 == 0) QThread::msleep(12); return result; } @@ -44,7 +46,7 @@ extern "C" int __wrap_call_audio_try_clock(uint64_t, int64_t now, call_clock_sna extern "C" uint64_t __wrap_call_video_port_received(uint64_t) { unsigned count = 0; for (; count < frames.size(); ++count) { - if (start + count * 33333ll + (disturbed && count == 30 ? 20000 : 0) > nowUs()) break; + if (start + count * 1000000ll / format.fps + (disturbed && count == 30 ? 20000 : 0) > nowUs()) break; } return count; } @@ -55,7 +57,7 @@ extern "C" int __wrap_call_video_port_status(uint64_t, struct call_video_port_st *out = {}; unsigned available = 0; for (unsigned i = cursor.load(); i < frames.size(); ++i) { - const int64_t received = start + i * 33333ll + (disturbed && i == 30 ? 20000 : 0); + const int64_t received = start + i * 1000000ll / format.fps + (disturbed && i == 30 ? 20000 : 0); if (received > nowUs()) break; ++available; } @@ -65,7 +67,7 @@ extern "C" void __wrap_call_video_port_request_key(uint64_t) { ++requests; } extern "C" int __wrap_call_video_port_decoded(uint64_t, uint64_t, int accepted) { if (!accepted) ++rejects; return 0; } extern "C" int __wrap_call_video_port_pull(uint64_t id, call_video_frame* out) { if (threadId() == displayThread.load()) unsafeTimer = true; - const int64_t received = start + cursor * 33333ll + (disturbed && cursor == 30 ? 20000 : 0); + const int64_t received = start + cursor * 1000000ll / format.fps + (disturbed && cursor == 30 ? 20000 : 0); if (cursor >= (int)frames.size() || nowUs() < received) return 0; // Кадр уже пришёл: остановка RX worker не должна подменить время доступности данных. if (disturbed && cursor == 60) QThread::msleep(80); @@ -122,16 +124,32 @@ int main(int argc, char** argv) { if (!frame.isNull()) { published.push_back(nowUs()); lastSerial = serial; } if (holdAck) return; // Подтверждение потребления отдельно от доставки signal, как paintEvent окна. - QTimer::singleShot(5, &engine, [&, serial, video = !frame.isNull()] { + const int64_t now = nowUs(); + const int64_t nextRefresh = start + ((now - start) * 60 / 1000000 + 1) * 1000000 / 60; + const int delay = limitedDisplay ? int((nextRefresh - now + 999) / 1000) : 5; + QTimer::singleShot(delay, &engine, [&, serial, video = !frame.isNull()] { if (video) presented.push_back(nowUs()); engine.imagePresented(serial); }); }); - for (int scenario = 0; scenario < 3; ++scenario) { + for (int scenario = 0; scenario < 4; ++scenario) { + limitedDisplay = scenario == 3; + if (limitedDisplay) { + frames.clear(); encoder.close(); format.fps = 60; + if (!encoder.open(format, 1000000, "libx264")) return 2; + for (int i = 0; i < 180; ++i) { + QList output; CallVideoRateCompletion completion; + if (!encoder.encode(image, 1000000 + i * 1000000ll / 60, i == 0 || i == 45, output, completion) + || output.size() != 1) return 3; + frames.push_back(output.front()); + } + } + decodedFrames = 0; disturbed = scenario == 1; slowDecoder = scenario == 2; cursor = 0; published.clear(); presented.clear(); displayThread = 0; start = nowUs(); engine.start(7); QElapsedTimer timer; timer.start(); bool uiPaused = false; while (timer.elapsed() < 4000) { if (disturbed && !uiPaused && timer.elapsed() >= 1500) { QThread::msleep(60); uiPaused = true; } + if (limitedDisplay && !uiPaused && timer.elapsed() >= 1000) { QThread::msleep(500); uiPaused = true; } app.processEvents(); QThread::msleep(2); } const int64_t stopAt = nowUs(); engine.stop(); const int64_t stopUs = nowUs() - stopAt; @@ -144,8 +162,13 @@ int main(int argc, char** argv) { "gap_p50_us=%lld gap_p95_us=%lld gap_max_us=%lld audio_epochs=32 stop_us=%lld", disturbed, slowDecoder, presented.size(), requests.load(), rejects.load(), (long long)(gaps.empty() ? 0 : gaps[gaps.size() / 2]), (long long)(gaps.empty() ? 0 : gaps[gaps.size() * 95 / 100]), (long long)(gaps.empty() ? 0 : gaps.back()), (long long)stopUs); - if (published.size() != frames.size() || presented.size() != frames.size() || requests || rejects || unsafeTimer || stopUs > 250000) return 1; - if (!disturbed && (gaps.empty() || gaps[gaps.size() * 95 / 100] > 45000)) { + if (decodedFrames != frames.size() || requests || rejects || unsafeTimer || stopUs > 250000) return 1; + if (limitedDisplay) { + DEBUG_INFO(DEBUG_CATEGORY_CALL, "60fps limited display decoded=%u presented=%zu skipped=%zu KEY_requests=%d", + decodedFrames.load(), presented.size(), frames.size() - presented.size(), requests.load()); + if (presented.size() <= 100 || presented.size() >= frames.size() || published.size() != presented.size()) return 1; + } else if (published.size() != frames.size() || presented.size() != frames.size()) return 1; + if (!disturbed && !limitedDisplay && (gaps.empty() || gaps[gaps.size() * 95 / 100] > 45000)) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "steady video presentation jitter exceeds 45ms at 30fps"); return 1; } }