diff --git a/tools/chatgui/src/call_camera_worker.cpp b/tools/chatgui/src/call_camera_worker.cpp index 8be52f4a..731b2242 100644 --- a/tools/chatgui/src/call_camera_worker.cpp +++ b/tools/chatgui/src/call_camera_worker.cpp @@ -2,6 +2,7 @@ #include "call_camera_protocol.h" #include "call_camera_buffer.h" #include "call_video_capture_clock.h" +#include "call_video_cadence.h" #include "call_camera_rate.h" #include "../../../src/call/call_video_playout.h" #include "debug_config.h" @@ -79,11 +80,12 @@ class CameraWorker : public QObject { std::shared_ptr pending; QMetaObject::Connection captureConnection, errorConnection; CallVideoCaptureClock clock; + CallVideoCadence cadence; quint64 generation = 0, inFlight = 0; bool sending = false; int width = 640, height = 480; qint64 lastPts = -1, reportAt = 0, convertMax = 0; - unsigned frames = 0; + unsigned frames = 0, paced = 0; void send(const QByteArray& data) { if (socket.write(data) != data.size()) { @@ -113,6 +115,8 @@ class CameraWorker : public QObject { DEBUG_WARN(DEBUG_CATEGORY_CALL, "camera skips old frame age_us=%lld pts=%lld last=%lld", (long long)(started - pts), (long long)pts, (long long)lastPts); return; } + // Ограничиваем поток до дорогой конвертации и IPC; preview и TX получают те же 30 fps. + if (!cadence.accept(pts)) { ++paced; return; } stage(QStringLiteral("convert")); QImage image = raw.toImage(); if (image.isNull()) { @@ -133,9 +137,9 @@ class CameraWorker : public QObject { stage(QStringLiteral("release")); image = {}; raw = {}; stage(QStringLiteral("running")); convertMax = std::max(convertMax, call_video_playout_now_us() - started); if (started - reportAt >= 1000000) { - DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "camera flow frames=%u convert_max_us=%lld age_us=%lld clock_correction_us=%lld", - frames, (long long)convertMax, (long long)(converted - pts), (long long)clock.correction); - reportAt = started; convertMax = 0; frames = 0; + DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "camera flow frames=%u paced=%u convert_max_us=%lld age_us=%lld clock_correction_us=%lld", + frames, paced, (long long)convertMax, (long long)(converted - pts), (long long)clock.correction); + reportAt = started; convertMax = 0; frames = paced = 0; } } bool receive(quint8 type, quint64 revision, QDataStream& in) { @@ -152,7 +156,7 @@ class CameraWorker : public QObject { } else if (type == Configure) { bool enabled = false; QByteArray device; qint32 w = 0, h = 0; in >> enabled >> device >> w >> h; if (in.status() != QDataStream::Ok || w < 1 || h < 1 || w > 1280 || h > 720 || !pixels) return false; - stop(); generation = revision; width = w; height = h; clock = {}; lastPts = -1; + stop(); generation = revision; width = w; height = h; clock = {}; cadence = {}; lastPts = -1; if (!enabled) { stage(QStringLiteral("idle")); return true; } stage(QStringLiteral("start")); pending = std::make_shared(); diff --git a/tools/chatgui/src/call_video_engine.cpp b/tools/chatgui/src/call_video_engine.cpp index 11ed139a..bf77889c 100644 --- a/tools/chatgui/src/call_video_engine.cpp +++ b/tools/chatgui/src/call_video_engine.cpp @@ -1,7 +1,6 @@ #include "call_video_engine.h" #include "call_video_encoder.h" #include "call_video_decoder.h" -#include "call_video_cadence.h" #include "call_camera_capture.h" #include "../../../src/call/call_video_port.h" #include "../../../src/call/call_video_playout.h" @@ -167,8 +166,7 @@ void CallVideoEngine::transmit() { bool forceKey = true; int64_t reportAt = nowUs(), encodeMax = 0, inputAgeMax = 0; uint64_t captureBase = 0; - unsigned encodedCount = 0, keyCount = 0, sendBusy = 0, paced = 0, expired = 0; - CallVideoCadence cadence; + unsigned encodedCount = 0, keyCount = 0, sendBusy = 0, expired = 0; uint64_t wakeSerial = 0; auto idle = [this, &wakeSerial] { std::unique_lock guard(m->lock); @@ -188,12 +186,12 @@ void CallVideoEngine::transmit() { int64_t now = nowUs(); if (now - reportAt >= 1000000) { DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, - "video capture flow call=%llu interval_us=%lld captured=%llu encoded=%u keys=%u send_busy=%u paced=%u " + "video capture flow call=%llu interval_us=%lld captured=%llu encoded=%u keys=%u send_busy=%u " "expired=%u encode_max_us=%lld input_age_max_us=%lld capture_age_us=%lld pts=%lld clock_correction_us=%lld", (unsigned long long)m->id, (long long)(now - reportAt), (unsigned long long)(captured - captureBase), - encodedCount, keyCount, sendBusy, paced, expired, (long long)encodeMax, (long long)inputAgeMax, + encodedCount, keyCount, sendBusy, expired, (long long)encodeMax, (long long)inputAgeMax, (long long)(pts >= 0 ? now - pts : 0), (long long)pts, (long long)clockCorrection); - reportAt = now; captureBase = captured; encodedCount = keyCount = sendBusy = paced = expired = 0; + reportAt = now; captureBase = captured; encodedCount = keyCount = sendBusy = expired = 0; encodeMax = inputAgeMax = 0; } if (revision != userRevision) { userRevision = revision; excluded = 0; } @@ -236,7 +234,6 @@ 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; } if (image.isNull() || nowUs() - pts > CallCameraMaxFrameAgeUs) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "video TX skips old capture age_us=%lld", (long long)(nowUs() - pts)); lastPts = pts; ++expired; continue; diff --git a/tools/chatgui/tests/test_call_camera_capture.cpp b/tools/chatgui/tests/test_call_camera_capture.cpp index 5c16cc2f..4d29a7a1 100644 --- a/tools/chatgui/tests/test_call_camera_capture.cpp +++ b/tools/chatgui/tests/test_call_camera_capture.cpp @@ -75,6 +75,7 @@ public: bool start(const QByteArray& id, int w, int h) override { device = id; width = w; height = h; record("start:" + id); if (id == "start-stall") QThread::sleep(60); + timer.setInterval(id == "fast" ? 5 : 33); timer.start(); return true; } void stop() override { @@ -111,6 +112,19 @@ private slots: path = directory.filePath(QString::number(QDateTime::currentMSecsSinceEpoch())); qputenv("UTUN_CAMERA_TEST_LOG", path.toUtf8()); } + void fastCameraKeepsThirtyFps() { + auto capture = std::make_unique(); + unsigned frames = 0; + connect(capture.get(), &CallCameraCapture::frameReady, this, [&](const QImage&, qint64, qint64) { ++frames; }); + QSignalSpy errors(capture.get(), &CallCameraCapture::failed); + capture->configure(true, "fast", 640, 480); + QTRY_VERIFY_WITH_TIMEOUT(frames >= 5, 3000); + const unsigned before = frames; QElapsedTimer timer; timer.start(); QTest::qWait(2000); + const double fps = (frames - before) * 1000.0 / timer.elapsed(); + qInfo("fast camera converted output fps=%.2f frames=%u", fps, frames - before); + QVERIFY(fps >= 25.0); QVERIFY(fps <= 31.0); QCOMPARE(errors.size(), 0); + const qint64 child = pid(); capture.reset(); QTRY_VERIFY_WITH_TIMEOUT(!alive(child), 3000); + } void latestFrameAndGeneration() { auto capture = std::make_unique(); unsigned frames = 0; qint64 maxAge = 0; QColor color; bool expectBlue = false, wrongGeneration = false;