From ee1e28bff96d83cfa521a7f4c124db085f5d0496 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 20 Oct 2026 00:02:25 +0200 Subject: [PATCH] Carry selected camera frame rate through isolated capture IPC --- tools/chatgui/src/call_camera_capture.cpp | 27 +++++++++++++---------- tools/chatgui/src/call_camera_capture.h | 4 ++-- tools/chatgui/src/call_camera_worker.cpp | 21 +++++++++--------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/tools/chatgui/src/call_camera_capture.cpp b/tools/chatgui/src/call_camera_capture.cpp index a216f1a7..39b78db1 100644 --- a/tools/chatgui/src/call_camera_capture.cpp +++ b/tools/chatgui/src/call_camera_capture.cpp @@ -26,7 +26,7 @@ struct CallCameraCapture::Session : QObject { quint64 generation = 0; bool enabled = false, retiring = false, failureReported = false, restartRequested = false, exiting = false; QByteArray device; - int width = 640, height = 480; + int width = 640, height = 480, fps = 30; QString phase = QStringLiteral("launch"); qint64 progressAt = 0, frameAt = 0, stopAt = 0; @@ -115,17 +115,20 @@ struct CallCameraCapture::Session : QObject { socket->flush(); } void sendConfiguration() { - send(packet(Configure, generation, [this](QDataStream& out) { out << enabled << device << qint32(width) << qint32(height); })); + send(packet(Configure, generation, [this](QDataStream& out) { + out << enabled << device << qint32(width) << qint32(height) << qint32(fps); + })); } - void configure(bool active, const QByteArray& id, int w, int h) { + void configure(bool active, const QByteArray& id, int w, int h, int rate) { if (exiting) return; - if (enabled == active && device == id && width == w && height == h && !failureReported) return; - enabled = active; device = id; width = w; height = h; ++generation; + if (!validSize(w, h) || rate < 1 || rate > 60) { fail(QStringLiteral("Invalid camera size/frame rate")); return; } + if (enabled == active && device == id && width == w && height == h && fps == rate && !failureReported) return; + enabled = active; device = id; width = w; height = h; fps = rate; ++generation; progressAt = call_video_playout_now_us(); frameAt = progressAt; stopAt = active ? 0 : progressAt + 1500000; - DEBUG_INFO(DEBUG_CATEGORY_CALL, "camera request generation=%llu enabled=%d size=%dx%d device=%s", - (unsigned long long)generation, active, w, h, id.constData()); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "camera request generation=%llu enabled=%d size=%dx%d output_fps=%d device=%s", + (unsigned long long)generation, active, w, h, rate, id.constData()); if (failureReported && active) restartRequested = true; if (process.state() == QProcess::NotRunning) { restartRequested = false; launch(); } if (socket) sendConfiguration(); @@ -151,9 +154,9 @@ struct CallCameraCapture::Session : QObject { CallCameraDevice device; quint32 modes = 0; in >> device.name >> device.id >> modes; if (in.status() != QDataStream::Ok || modes > 256) return false; for (quint32 j = 0; j < modes; ++j) { - qint32 w = 0, h = 0; in >> w >> h; - if (!validSize(w, h)) return false; - device.modes.append({QSize(w, h), 30}); + qint32 w = 0, h = 0, rate = 0; in >> w >> h >> rate; + if (!validSize(w, h) || rate < 1 || rate > 60) return false; + device.modes.append({QSize(w, h), rate}); } devices.append(device); } @@ -190,6 +193,6 @@ struct CallCameraCapture::Session : QObject { CallCameraCapture::CallCameraCapture(QObject* parent) : QObject(parent), m(new Session(this)) {} CallCameraCapture::~CallCameraCapture() { m->shutdown(); } -void CallCameraCapture::configure(bool enabled, const QByteArray& device, int width, int height) { - m->configure(enabled, device, width, height); +void CallCameraCapture::configure(bool enabled, const QByteArray& device, int width, int height, int fps) { + m->configure(enabled, device, width, height, fps); } diff --git a/tools/chatgui/src/call_camera_capture.h b/tools/chatgui/src/call_camera_capture.h index 2cac7adf..66aa319c 100644 --- a/tools/chatgui/src/call_camera_capture.h +++ b/tools/chatgui/src/call_camera_capture.h @@ -8,7 +8,7 @@ struct CallCameraDevice { QString name; // Отображаемое имя устройства. QByteArray id; // Непрозрачный идентификатор Qt backend. - QList modes; // Аппаратные размеры до Full HD с частотой 30/29.97 fps. + QList modes; // Размеры до Full HD и частоты передачи до 60 fps, capture может быть быстрее. }; using CallCameraDevices = QList; constexpr qint64 CallCameraMaxFrameAgeUs = 250000; @@ -19,7 +19,7 @@ class CallCameraCapture : public QObject { public: explicit CallCameraCapture(QObject* parent = nullptr); ~CallCameraCapture() override; - void configure(bool enabled, const QByteArray& device = {}, int width = 640, int height = 480); + void configure(bool enabled, const QByteArray& device = {}, int width = 640, int height = 480, int fps = 30); signals: void devicesChanged(const CallCameraDevices& devices); void frameReady(const QImage& image, qint64 pts, qint64 correction); diff --git a/tools/chatgui/src/call_camera_worker.cpp b/tools/chatgui/src/call_camera_worker.cpp index 38e18bb8..5cd60b76 100644 --- a/tools/chatgui/src/call_camera_worker.cpp +++ b/tools/chatgui/src/call_camera_worker.cpp @@ -43,8 +43,7 @@ public: if (validSize(size.width(), size.height())) stream_video_add_camera_modes(entry.modes, size, format.minFrameRate(), format.maxFrameRate()); } - entry.modes.removeIf([](const StreamVideoMode& mode) { return mode.fps != 30; }); - DEBUG_INFO(DEBUG_CATEGORY_CALL, "camera modes device=%s count=%lld limit=1920x1080 fps=30", + DEBUG_INFO(DEBUG_CATEGORY_CALL, "camera modes device=%s count=%lld limit=1920x1080 output_fps<=60", device.id().constData(), (long long)entry.modes.size()); result.append(entry); } @@ -60,7 +59,7 @@ public: QCameraFormat best; for (const auto& format : selected.videoFormats()) { if (!stream_video_camera_supports(format.resolution(), format.minFrameRate(), format.maxFrameRate(), - {QSize(width, height), 30})) continue; + {QSize(width, height), 1})) continue; if (best.isNull() || format.maxFrameRate() < best.maxFrameRate()) best = format; } if (best.isNull()) { @@ -104,7 +103,7 @@ class CameraWorker : public QObject { CallVideoCadence cadence; quint64 generation = 0, inFlight = 0; bool sending = false; - int width = 640, height = 480; + int width = 640, height = 480, fps = 30; qint64 lastPts = -1, reportAt = 0, convertMax = 0; unsigned frames = 0, paced = 0; @@ -124,7 +123,7 @@ class CameraWorker : public QObject { out << quint32(devices.size()); for (const auto& device : devices) { out << device.name << device.id << quint32(device.modes.size()); - for (const auto& mode : device.modes) out << qint32(mode.size.width()) << qint32(mode.size.height()); + for (const auto& mode : device.modes) out << qint32(mode.size.width()) << qint32(mode.size.height()) << qint32(mode.fps); } })); stage(pending ? QStringLiteral("running") : QStringLiteral("idle")); @@ -149,7 +148,7 @@ 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. + // Ограничиваем поток до дорогой конвертации и IPC; preview и TX получают выбранную частоту. if (!cadence.accept(pts)) { ++paced; return; } stage(QStringLiteral("convert")); QImage image = raw.toImage(); @@ -174,8 +173,8 @@ 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 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); + DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "camera flow output_fps=%d frames=%u paced=%u convert_max_us=%lld age_us=%lld clock_correction_us=%lld", + fps, frames, paced, (long long)convertMax, (long long)(converted - pts), (long long)clock.correction); reportAt = started; convertMax = 0; frames = paced = 0; } } @@ -191,9 +190,9 @@ class CameraWorker : public QObject { else if (type == Ack) { if (sending && revision == inFlight) { sending = false; if (pending) convert(pending, generation); } } else if (type == Configure) { - bool enabled = false; QByteArray device; qint32 w = 0, h = 0; in >> enabled >> device >> w >> h; - if (in.status() != QDataStream::Ok || !validSize(w, h) || !pixels) return false; - stop(); generation = revision; width = w; height = h; clock = {}; cadence = {}; lastPts = -1; + bool enabled = false; QByteArray device; qint32 w = 0, h = 0, rate = 0; in >> enabled >> device >> w >> h >> rate; + if (in.status() != QDataStream::Ok || !validSize(w, h) || rate < 1 || rate > 60 || !pixels) return false; + stop(); generation = revision; width = w; height = h; fps = rate; clock = {}; cadence = CallVideoCadence(rate); lastPts = -1; if (!enabled) { stage(QStringLiteral("idle")); return true; } stage(QStringLiteral("start")); pending = std::make_shared();