Populate call resolutions from camera modes and test Full HD IPC

This commit is contained in:
evgeny committed 2026-10-19 22:35:05 +02:00
1 parent eb11d2ebc5
commit 7a0bc9516c
3 files changed
+60 -12

No files matched your search

+11 -2
View File
@@ -147,7 +147,16 @@ struct CallCameraCapture::Session : QObject {
quint32 count = 0; in >> count;
if (in.status() != QDataStream::Ok || count > 128) return false;
CallCameraDevices devices;
for (quint32 i = 0; i < count; ++i) { QString name; QByteArray id; in >> name >> id; devices.append({name, id}); }
for (quint32 i = 0; i < count; ++i) {
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});
}
devices.append(device);
}
if (owner) emit owner->devicesChanged(devices);
} else if (type == Stage) {
QString next; in >> next;
@@ -158,7 +167,7 @@ struct CallCameraCapture::Session : QObject {
if (revision == generation) fail(error);
} else if (type == Frame) {
qint64 pts = -1, correction = 0; qint32 w = 0, h = 0; in >> pts >> correction >> w >> h;
if (in.status() != QDataStream::Ok || pts < 0 || correction < 0 || w < 1 || h < 1 || w > 1280 || h > 720 || !pixels) return false;
if (in.status() != QDataStream::Ok || pts < 0 || correction < 0 || !validSize(w, h) || !pixels) return false;
const qint64 now = call_video_playout_now_us();
const bool current = owner && enabled && revision == generation && !failureReported;
if (current && pts <= now && now - pts <= CallCameraMaxFrameAgeUs) {
+34 -5
View File
@@ -340,15 +340,37 @@ CallWindow::CallWindow(QWidget* parent)
});
auto* modes = new QHBoxLayout();
m_camera = new QComboBox(this);
connect(m_video, &CallVideoEngine::camerasChanged, this, [this](const CallCameraDevices& cameras) {
m_resolution = new QComboBox(this);
auto refreshModes = [this] {
QSize wanted = m_resolution->currentData().toSize();
if (wanted.isEmpty()) wanted = QSize(640, 480);
m_resolution->clear();
for (const auto& value : m_camera->currentData(Qt::UserRole + 1).toList()) {
const QSize size = value.toSize();
m_resolution->addItem(QStringLiteral("%1×%2").arg(size.width()).arg(size.height()), size);
}
int index = m_resolution->findData(wanted);
if (index < 0) index = m_resolution->findData(QSize(640, 480));
if (index < 0) index = m_resolution->count() - 1;
m_resolution->setCurrentIndex(index); m_resolution->setEnabled(index >= 0);
DEBUG_INFO(DEBUG_CATEGORY_CALL, "call camera resolutions device=%s count=%d requested=%dx%d selected=%dx%d fps=30",
m_camera->currentData().toByteArray().constData(), m_resolution->count(), wanted.width(), wanted.height(),
m_resolution->currentData().toSize().width(), m_resolution->currentData().toSize().height());
};
connect(m_video, &CallVideoEngine::camerasChanged, this, [this, refreshModes](const CallCameraDevices& cameras) {
const auto selected = m_camera->currentData();
m_camera->clear();
for (const auto& camera : cameras) m_camera->addItem(camera.first, camera.second);
for (const auto& camera : cameras) {
QVariantList sizes;
for (const auto& mode : camera.modes) sizes.append(mode.size);
m_camera->addItem(camera.name, camera.id);
m_camera->setItemData(m_camera->count() - 1, sizes, Qt::UserRole + 1);
}
const int index = m_camera->findData(selected);
if (index >= 0) m_camera->setCurrentIndex(index);
refreshModes();
if (m_videoEnabled->isChecked()) applyVideo();
});
m_resolution = new QComboBox(this); m_resolution->addItem("640×480", QSize(640, 480)); m_resolution->addItem("1280×720", QSize(1280, 720));
m_codec = new QComboBox(this); m_codec->addItem(callText("Auto (H.265 → H.264)"), 0); m_codec->addItem("H.265", 2); m_codec->addItem("H.264", 1);
modes->addWidget(m_camera, 1); modes->addWidget(m_resolution); modes->addWidget(m_codec); videoLayout->addLayout(modes);
auto* rates = new QHBoxLayout(); rates->addWidget(new QLabel(callText("Bitrate range, kbit/s:"), this));
@@ -356,7 +378,8 @@ CallWindow::CallWindow(QWidget* parent)
for (auto* spin : {m_videoMin, m_videoMax}) { spin->setRange(300, 4000); spin->setSingleStep(100); rates->addWidget(spin); }
m_videoMin->setValue(300); m_videoMax->setValue(4000); videoLayout->addLayout(rates); lay->addWidget(m_videoControls);
connect(m_videoEnabled, &QCheckBox::toggled, this, [this] { applyVideo(); });
for (auto* combo : {m_camera, m_resolution, m_codec})
connect(m_camera, QOverload<int>::of(&QComboBox::activated), this, [this, refreshModes] { refreshModes(); applyVideo(); });
for (auto* combo : {m_resolution, m_codec})
connect(combo, QOverload<int>::of(&QComboBox::activated), this, [this] { applyVideo(); });
auto updateRange = [this] {
if (m_videoMin->value() > m_videoMax->value()) m_videoMax->setValue(m_videoMin->value());
@@ -536,7 +559,13 @@ void CallWindow::updateVideoPresentation() {
void CallWindow::applyVideo() {
if (!m_videoCall) return;
QSize size = m_resolution->currentData().toSize();
m_video->setVideo(m_videoEnabled->isChecked(), m_camera->currentData().toByteArray(), size.width(), size.height(), m_codec->currentData().toInt());
const bool enabled = m_videoEnabled->isChecked() && !size.isEmpty();
if (m_videoEnabled->isChecked() && !enabled) {
DEBUG_WARN(DEBUG_CATEGORY_CALL, "call camera has no selected mode device=%s", m_camera->currentData().toByteArray().constData());
m_videoStatus->setText(callText("No supported video modes")); m_videoStatus->show();
}
if (size.isEmpty()) size = QSize(640, 480);
m_video->setVideo(enabled, m_camera->currentData().toByteArray(), size.width(), size.height(), m_codec->currentData().toInt());
}
void CallWindow::postCallFn(void (*fn)(void*), uint64_t callId) {
@@ -71,7 +71,9 @@ public:
raw.setStartTime(call_video_playout_now_us()); emit frame(raw);
});
}
CallCameraDevices devices() override { return {{QStringLiteral("Test camera"), "red"}}; }
CallCameraDevices devices() override {
return {{QStringLiteral("Test camera"), "red", {{QSize(1920, 1080), 30}, {QSize(1280, 720), 30}, {QSize(640, 480), 30}}}};
}
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);
@@ -125,27 +127,35 @@ private slots:
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_data() {
QTest::addColumn<QSize>("size");
QTest::newRow("720p") << QSize(1280, 720);
QTest::newRow("1080p") << QSize(1920, 1080);
}
void latestFrameAndGeneration() {
QFETCH(QSize, size);
auto capture = std::make_unique<CallCameraCapture>();
unsigned frames = 0; qint64 maxAge = 0; QColor color; bool expectBlue = false, wrongGeneration = false;
connect(capture.get(), &CallCameraCapture::frameReady, this, [&](const QImage& image, qint64 pts, qint64) {
QCOMPARE(image.size(), size);
QCOMPARE(image.pixelColor(image.width() - 1, image.height() - 1), image.pixelColor(0, 0));
++frames; maxAge = std::max(maxAge, call_video_playout_now_us() - pts); color = image.pixelColor(0, 0);
if (expectBlue && color != QColor(Qt::blue)) wrongGeneration = true;
});
QSignalSpy errors(capture.get(), &CallCameraCapture::failed);
capture->configure(true, "red", 1280, 720);
capture->configure(true, "red", size.width(), size.height());
{ Heartbeat heartbeat;
QTRY_VERIFY_WITH_TIMEOUT(frames >= 15, 4000);
qInfo("camera 720p latency max=%lldus GUI gap=%lldms", (long long)maxAge, (long long)heartbeat.maximum);
qInfo("camera %dx%d latency max=%lldus GUI gap=%lldms", size.width(), size.height(), (long long)maxAge, (long long)heartbeat.maximum);
QVERIFY(maxAge < 100000); QVERIFY(heartbeat.maximum < 150); }
QCOMPARE(color, QColor(Qt::red));
// Пока GUI занят, разрешён лишь один передаваемый кадр; после паузы берётся свежий.
QThread::msleep(350); const unsigned before = frames;
expectBlue = true;
capture->configure(true, "blue", 1280, 720);
capture->configure(true, "blue", size.width(), size.height());
QTRY_VERIFY_WITH_TIMEOUT(frames > before && color == QColor(Qt::blue), 2000);
QVERIFY(!wrongGeneration);
const unsigned stopped = frames; capture->configure(false, "blue", 1280, 720);
const unsigned stopped = frames; capture->configure(false, "blue", size.width(), size.height());
QTest::qWait(150); QCOMPARE(frames, stopped); QCOMPARE(errors.size(), 0);
const qint64 child = pid(); QVERIFY(child > 0); capture.reset(); QTRY_VERIFY_WITH_TIMEOUT(!alive(child), 3000);
}