Select proportional screen and advertised camera stream modes

This commit is contained in:
evgeny committed 2026-10-16 13:53:33 +02:00
1 parent 6228873ce2
commit 9bacde3bc1
2 files changed
+279 -25

No files matched your search

+120
View File
@@ -3,6 +3,12 @@
#include <QtTest>
#include <QTimer>
#include <QLabel>
#include <QComboBox>
#include <QCheckBox>
#include <QButtonGroup>
#include <QPushButton>
#include <QJsonDocument>
#include <QJsonObject>
#include <QScreen>
#include <QLoggingCategory>
#include <atomic>
@@ -117,6 +123,120 @@ private slots:
sqlite3_finalize(statement); QVERIFY(group); QVERIFY(gui_stream_attach(inst)); uasync_mark_running(ua);
connect(&poll, &QTimer::timeout, this, [this] { uasync_poll(ua, 0); }); poll.start(1);
}
void captureSettingsSurviveReopening() {
QString saved;
QVariantList selectedMode;
{
StreamPublishEngine engine;
StreamPublishWindow controls(&engine, group);
connect(&controls, &StreamPublishWindow::settingsChanged, this, [&saved](const QString& value) { saved = value; });
auto boxes = controls.findChildren<QComboBox*>(); QCOMPARE(boxes.size(), 5);
if (StreamPublishEngine::supported()) QTRY_VERIFY_WITH_TIMEOUT(boxes[2]->count() > 0, 15000);
auto sources = StreamPublishEngine::sources();
int screen = -1;
for (int i = 0; i < sources.size(); i++) if (sources[i].kind == StreamVideoSource::Screen && !sources[i].size.isEmpty()) { screen = i; break; }
if (screen < 0) QSKIP("No enumerated screen for capture settings");
boxes[0]->setCurrentIndex(screen);
QSize maximum = stream_video_screen_size(sources[screen].size);
QCOMPARE(boxes[1]->currentData().toList(), (QVariantList{maximum.width(), maximum.height(), 30}));
QVERIFY(boxes[1]->count() >= 6);
boxes[1]->setCurrentIndex(boxes[1]->count() - 1); // Меньший размер сохраняется при повторном открытии.
selectedMode = boxes[1]->currentData().toList();
boxes[3]->setCurrentIndex(boxes[3]->findData(4000000));
controls.findChild<QButtonGroup*>()->button(270)->click();
controls.findChildren<QCheckBox*>().first()->setChecked(false);
if (boxes[2]->count() > 1) boxes[2]->setCurrentIndex(boxes[2]->count() - 1);
QVERIFY(!saved.isEmpty());
}
const auto stored = QJsonDocument::fromJson(saved.toUtf8()).object();
QCOMPARE(stored.value("width").toInt(), selectedMode[0].toInt()); QCOMPARE(stored.value("fps").toInt(), 60);
QVERIFY(!stored.value("native_size").toBool());
QCOMPARE(stored.value("bitrate").toInt(), 4000000); QCOMPARE(stored.value("rotation").toInt(), 270);
QVERIFY(!stored.value("audio").toBool());
StreamPublishEngine reopenedEngine;
StreamPublishWindow reopened(&reopenedEngine, group, nullptr, saved);
QSignalSpy changes(&reopened, &StreamPublishWindow::settingsChanged);
if (StreamPublishEngine::supported()) QTRY_VERIFY_WITH_TIMEOUT(!reopenedEngine.codecs().isEmpty(), 15000);
auto boxes = reopened.findChildren<QComboBox*>();
QCOMPARE(boxes[1]->currentData().toList(), selectedMode);
QCOMPARE(boxes[3]->currentData().toInt(), 4000000);
if (StreamPublishEngine::supported()) QTRY_COMPARE(boxes[2]->currentData().toInt(), stored.value("codec").toInt());
QCOMPARE(reopened.findChild<QButtonGroup*>()->checkedId(), 270);
QVERIFY(!reopened.findChildren<QCheckBox*>().first()->isChecked());
QVERIFY(changes.isEmpty()); // Асинхронный список кодеков не перезаписывает сохранённый выбор.
boxes[3]->setCurrentIndex(0);
QCOMPARE(changes.size(), 1);
auto automatic = changes.takeFirst().front().toString();
QCOMPARE(QJsonDocument::fromJson(automatic.toUtf8()).object().value("bitrate").toInt(-1), 0);
StreamPublishWindow restoredAuto(&reopenedEngine, group, nullptr, automatic);
QCOMPARE(restoredAuto.findChildren<QComboBox*>()[3]->currentIndex(), 0);
}
void proportionalScreenModes_data() {
QTest::addColumn<QSize>("source"); QTest::addColumn<QSize>("maximum"); QTest::addColumn<QSize>("reduced");
QTest::newRow("full-hd") << QSize(1920, 1080) << QSize(1920, 1080) << QSize(1280, 720);
QTest::newRow("16-10") << QSize(1920, 1200) << QSize(1920, 1200) << QSize(1152, 720);
QTest::newRow("ultrawide") << QSize(3440, 1440) << QSize(3440, 1440) << QSize(1720, 720);
QTest::newRow("limited") << QSize(5120, 1440) << QSize(4096, 1152) << QSize(2560, 720);
QTest::newRow("retina") << QSize(5120, 2880) << QSize(4096, 2304) << QSize(1280, 720);
QTest::newRow("portrait") << QSize(1440, 5120) << QSize(1152, 4096) << QSize(720, 2560);
QTest::newRow("odd") << QSize(1365, 767) << QSize(1364, 766) << QSize(1280, 720);
}
void proportionalScreenModes() {
QFETCH(QSize, source); QFETCH(QSize, maximum); QFETCH(QSize, reduced);
auto modes = stream_video_screen_modes(source); QVERIFY(!modes.isEmpty()); QCOMPARE(modes.front().size, maximum);
QVERIFY(modes.contains({reduced, 30}));
QList<StreamVideoMode> seen;
for (const auto& mode : modes) {
QVERIFY(mode.size.width() <= 4096 && mode.size.height() <= 4096);
QVERIFY(mode.size.width() <= source.width() && mode.size.height() <= source.height());
QVERIFY(!(mode.size.width() % 2) && !(mode.size.height() % 2));
double scale = std::min(double(mode.size.width()) / source.width(), double(mode.size.height()) / source.height());
QVERIFY(std::abs(mode.size.width() - source.width() * scale) < 8);
QVERIFY(std::abs(mode.size.height() - source.height() * scale) < 8);
QVERIFY(!seen.contains(mode)); seen.append(mode);
}
int selected = stream_video_select_mode(modes, {reduced, 60}, true);
QCOMPARE(modes[selected].size, maximum); QCOMPARE(modes[selected].fps, 60);
selected = stream_video_select_mode(modes, {reduced, 60}, false);
QCOMPARE(modes[selected].size, reduced); QCOMPARE(modes[selected].fps, 60);
}
void cameraAdvertisedModes() {
QList<StreamVideoMode> modes;
stream_video_add_camera_modes(modes, {1920, 1080}, 29.97, 29.97);
QCOMPARE(modes.size(), 1); QCOMPARE(modes.front().fps, 30);
stream_video_add_camera_modes(modes, {1920, 1080}, 29.97, 29.97); QCOMPARE(modes.size(), 1);
stream_video_add_camera_modes(modes, {1280, 720}, 15, 60);
QVERIFY(modes.contains({{1280, 720}, 15})); QVERIFY(modes.contains({{1280, 720}, 60}));
QVERIFY(!modes.contains({{1920, 1080}, 60})); QVERIFY(!modes.contains({{1920, 1080}, 15}));
stream_video_add_camera_modes(modes, {640, 480}, 7, 7); QVERIFY(modes.contains({{640, 480}, 7}));
int count = modes.size();
stream_video_add_camera_modes(modes, {5120, 2880}, 30, 30);
stream_video_add_camera_modes(modes, {1281, 720}, 30, 30);
stream_video_add_camera_modes(modes, {1280, 720}, 120, 120);
stream_video_add_camera_modes(modes, {1280, 720}, 60, 30);
QCOMPARE(modes.size(), count);
QVERIFY(!stream_video_camera_supports({640, 480}, 30, 30, {{1280, 720}, 30}));
}
void screenSelectionAndRefresh() {
StreamPublishEngine engine; StreamPublishWindow controls(&engine, group);
auto* sourceBox = controls.findChild<QComboBox*>("streamVideoSource");
auto* modeBox = controls.findChild<QComboBox*>("streamVideoMode");
auto sources = StreamPublishEngine::sources();
QSignalSpy changes(&controls, &StreamPublishWindow::settingsChanged);
unsigned screens = 0;
for (int i = 0; i < sources.size(); i++) {
if (sources[i].kind != StreamVideoSource::Screen || sources[i].size.isEmpty()) continue;
screens++; sourceBox->setCurrentIndex(-1); changes.clear(); sourceBox->setCurrentIndex(i);
QSize size = stream_video_screen_size(sources[i].size);
auto mode = modeBox->currentData().toList();
QCOMPARE(mode[0].toInt(), size.width()); QCOMPARE(mode[1].toInt(), size.height());
QVERIFY(modeBox->currentData(Qt::UserRole + 1).toBool()); QCOMPARE(changes.size(), 1);
QVERIFY(sourceBox->currentText().contains(sources[i].name));
modeBox->setCurrentIndex(modeBox->count() - 1); mode = modeBox->currentData().toList(); changes.clear();
emit engine.sourcesChanged(); QCOMPARE(modeBox->currentData().toList(), mode); QVERIFY(changes.isEmpty());
}
if (!screens) QSKIP("No enumerated screens");
}
void broadcastAllowsRadioMicrophone() {
AudioCaptureLease call, radio(AudioCaptureLease::Mode::Radio), recorder, publisher(AudioCaptureLease::Mode::Exclusive);
QVERIFY(call.acquire()); QVERIFY(radio.acquire()); QVERIFY(recorder.acquire()); QVERIFY(!publisher.acquire());