Select camera capture rate and verify 30/60 fps pacing

This commit is contained in:
evgeny committed 2026-10-20 00:03:01 +02:00
1 parent ee1e28bff9
commit a852a9738b
3 files changed
+36 -15

No files matched your search

+1 -1
View File
@@ -10,7 +10,7 @@ class CallCameraSource : public QObject {
Q_OBJECT
public:
virtual CallCameraDevices devices() = 0;
virtual bool start(const QByteArray& device, int width, int height) = 0;
virtual bool start(const QByteArray& device, int width, int height, int fps) = 0;
virtual void stop() = 0;
signals:
void devicesChanged();
+6 -6
View File
@@ -49,7 +49,7 @@ public:
}
return result;
}
bool start(const QByteArray& device, int width, int height) override {
bool start(const QByteArray& device, int width, int height, int fps) override {
QCameraDevice selected;
for (const auto& item : QMediaDevices::videoInputs()) if (item.id() == device) selected = item;
if (selected.isNull()) { emit error(QStringLiteral("Camera is unavailable")); return false; }
@@ -59,18 +59,18 @@ public:
QCameraFormat best;
for (const auto& format : selected.videoFormats()) {
if (!stream_video_camera_supports(format.resolution(), format.minFrameRate(), format.maxFrameRate(),
{QSize(width, height), 1})) continue;
{QSize(width, height), fps})) continue;
if (best.isNull() || format.maxFrameRate() < best.maxFrameRate()) best = format;
}
if (best.isNull()) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "camera mode unavailable device=%s size=%dx%d fps=30", device.constData(), width, height);
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "camera mode unavailable device=%s size=%dx%d output_fps=%d", device.constData(), width, height, fps);
emit error(QStringLiteral("Selected camera mode is unavailable")); return false;
}
camera->setCameraFormat(best);
const auto format = camera->cameraFormat();
DEBUG_INFO(DEBUG_CATEGORY_CALL, "camera selected device=%s size=%dx%d pixel=%d fps=%.3f..%.3f",
DEBUG_INFO(DEBUG_CATEGORY_CALL, "camera selected device=%s size=%dx%d pixel=%d capture_fps=%.3f..%.3f output_fps=%d",
device.constData(), format.resolution().width(), format.resolution().height(), format.pixelFormat(),
format.minFrameRate(), format.maxFrameRate());
format.minFrameRate(), format.maxFrameRate(), fps);
connect(camera.get(), &QCamera::activeChanged, this, [this, device](bool active) {
if (active) rate.activate(device); else rate.restore();
});
@@ -211,7 +211,7 @@ class CameraWorker : public QObject {
post = !slot->posted; slot->posted = true; }
if (post) QMetaObject::invokeMethod(this, [this, slot, revision] { convert(slot, revision); }, Qt::QueuedConnection);
}, Qt::DirectConnection);
if (source->start(device, width, height)) stage(QStringLiteral("running"));
if (source->start(device, width, height, fps)) stage(QStringLiteral("running"));
} else return false;
return true;
}
@@ -1,5 +1,6 @@
#include "../src/call_camera_source.h"
#include "../src/call_camera_buffer.h"
#include "../src/call_video_cadence.h"
#include "../../../src/call/call_video_playout.h"
#include "debug_config.h"
#include <QtTest>
@@ -73,12 +74,13 @@ public:
});
}
CallCameraDevices devices() override {
return {{QStringLiteral("Test camera"), "red", {{QSize(1920, 1080), 30}, {QSize(1280, 720), 30}, {QSize(640, 480), 30}}}};
return {{QStringLiteral("Test camera"), "red", {{QSize(1920, 1080), 30}, {QSize(1280, 720), 30},
{QSize(640, 480), 30}, {QSize(640, 480), 60}}}};
}
bool start(const QByteArray& id, int w, int h) override {
bool start(const QByteArray& id, int w, int h, int fps) override {
device = id; width = w; height = h; record("start:" + id);
if (id == "start-stall") QThread::sleep(60);
timer.setInterval(id == "fast" ? 5 : 33);
timer.setInterval(id == "fast" ? 3 : 1000 / fps);
timer.start(); return true;
}
void stop() override {
@@ -115,21 +117,40 @@ private slots:
path = directory.filePath(QString::number(QDateTime::currentMSecsSinceEpoch()));
qputenv("UTUN_CAMERA_TEST_LOG", path.toUtf8());
}
void fastCameraKeepsThirtyFps() {
void captureCadence() {
for (int input : {50, 100, 330}) for (int output : {30, 50, 60}) {
if (output > input) continue;
CallVideoCadence cadence(output); unsigned accepted = 0;
for (int i = 0; i < input * 3; ++i) if (cadence.accept(1000000 + i * 1000000ll / input)) ++accepted;
qInfo("capture cadence input=%d output=%d accepted=%u/3s", input, output, accepted);
QVERIFY(std::abs(int(accepted) - output * 3) <= 1);
QVERIFY(!cadence.accept(1000000));
QVERIFY(cadence.accept(5000000)); QVERIFY(!cadence.accept(5000001));
}
CallVideoCadence jitter(30); unsigned accepted = 0;
for (int i = 0; i < 90; ++i) if (jitter.accept(1000000 + i * 1000000ll / 30 + (i % 2 ? 700 : -700))) ++accepted;
QCOMPARE(accepted, 90u);
}
void fastCameraKeepsSelectedFps_data() {
QTest::addColumn<int>("fps"); QTest::newRow("30") << 30; QTest::newRow("60") << 60;
}
void fastCameraKeepsSelectedFps() {
QFETCH(int, fps);
auto capture = std::make_unique<CallCameraCapture>();
CallCameraDevices devices;
connect(capture.get(), &CallCameraCapture::devicesChanged, this, [&](const CallCameraDevices& value) { devices = value; });
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);
capture->configure(true, "fast", 640, 480, fps);
QTRY_VERIFY_WITH_TIMEOUT(frames >= 5, 3000);
QCOMPARE(devices.size(), 1); QCOMPARE(devices.front().id, QByteArray("red"));
QVERIFY(devices.front().modes.contains({QSize(1920, 1080), 30}));
QVERIFY(devices.front().modes.contains({QSize(640, 480), 60}));
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 double actual = (frames - before) * 1000.0 / timer.elapsed();
qInfo("fast camera converted target=%d output_fps=%.2f frames=%u", fps, actual, frames - before);
QVERIFY(actual >= fps * 0.85); QVERIFY(actual <= fps + 1); QCOMPARE(errors.size(), 0);
const qint64 child = pid(); capture.reset(); QTRY_VERIFY_WITH_TIMEOUT(!alive(child), 3000);
}
void latestFrameAndGeneration_data() {