diff --git a/tools/chatgui/src/call_video_cadence.h b/tools/chatgui/src/call_video_cadence.h new file mode 100644 index 00000000..00e8aaa1 --- /dev/null +++ b/tools/chatgui/src/call_video_cadence.h @@ -0,0 +1,15 @@ +#pragma once +#include +#include + +// Сетка 30 fps с допуском половины периода: дрожание PTS не превращает 30 fps в 15. +class CallVideoCadence { + int64_t next = -1; +public: + bool accept(int64_t pts) { + constexpr int64_t period = 1000000 / 30; + if (next >= 0 && pts < next - period / 2) return false; + next = next < 0 ? pts + period : std::max(next + period, pts + period / 2); + return true; + } +}; diff --git a/tools/chatgui/src/call_video_engine.cpp b/tools/chatgui/src/call_video_engine.cpp index d9e1c4ae..f69675e9 100644 --- a/tools/chatgui/src/call_video_engine.cpp +++ b/tools/chatgui/src/call_video_engine.cpp @@ -1,6 +1,7 @@ #include "call_video_engine.h" #include "call_video_encoder.h" #include "call_video_decoder.h" +#include "call_video_cadence.h" #include "../../../src/call/call_video_port.h" #include "../../../src/call/call_audio.h" #include "debug_config.h" @@ -174,7 +175,7 @@ void CallVideoEngine::transmit() { int64_t reportAt = nowUs(), encodeMax = 0; uint64_t captureBase = 0; unsigned encodedCount = 0, keyCount = 0, sendBusy = 0, paced = 0; - int64_t pacedPts = -1; + CallVideoCadence cadence; while (m->running) { struct call_video_port_status state{}; if (call_video_port_status(m->id, &state) < 0) break; @@ -210,10 +211,6 @@ void CallVideoEngine::transmit() { } if (format.generation && state.have_peer && !call_video_format_supported(&caps, &state.peer, &format)) appliedRevision = 0; if (!enabled || !state.have_peer || pts <= lastPts) { idle(); continue; } - if (lastPts >= 0 && pts - lastPts < 33333) { - if (pacedPts != pts) { pacedPts = pts; ++paced; } - idle(); continue; - } if (revision != appliedRevision) { if (lastKey >= 0 && pts - lastKey < 1000000) { idle(); continue; } call_video_format next{format.generation + 1, (uint16_t)width, (uint16_t)height, 30, 0, (uint8_t)codec, 0, 4000000, 0}; @@ -243,6 +240,7 @@ void CallVideoEngine::transmit() { call_video_port_applied(m->id, format.generation, request, 0); } #ifdef HAVE_STREAM_CAPTURE + if (!cadence.accept(pts)) { lastPts = pts; ++paced; idle(); continue; } QImage image = raw.toImage(); if (image.isNull()) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "camera frame conversion failed"); lastPts = pts; continue; } LiveEncodedFrame output; CallVideoRateCompletion completion; diff --git a/tools/chatgui/tests/test_call_video_encoder.cpp b/tools/chatgui/tests/test_call_video_encoder.cpp index c18da029..81bfd67b 100644 --- a/tools/chatgui/tests/test_call_video_encoder.cpp +++ b/tools/chatgui/tests/test_call_video_encoder.cpp @@ -5,6 +5,7 @@ #include #include "../src/call_video_encoder.h" #include "../src/call_video_decoder.h" +#include "../src/call_video_cadence.h" #include "../../../src/call/call_video_bitstream.h" extern "C" { #include "debug_config.h" @@ -41,6 +42,15 @@ class CallVideoEncoderTest : public QObject { return {17, 640, 360, 30, 0, (uint8_t)codec, (uint8_t)(codec == 1 ? 31 : 93), 4000000, (uint8_t)(codec == 1 ? 2 : 1)}; } private slots: + void captureCadence() { + CallVideoCadence jittered, fast; + for (int i = 0; i < 300; i++) QVERIFY(jittered.accept(1000000 + i * 33333 + (i % 2 ? -1500 : 1500))); + int accepted = 0; + for (int i = 0; i < 600; i++) if (fast.accept(1000000 + i * 1000000LL / 60)) accepted++; + QCOMPARE(accepted, 300); + QVERIFY(jittered.accept(30000000)); // Долгая пауза не создаёт догоняющий burst. + QVERIFY(!jittered.accept(30001000)); + } void initTestCase() { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_CALL, DEBUG_LEVEL_DEBUG); av_log_set_level(AV_LOG_ERROR);