diff --git a/tools/chatgui/src/stream_publish_engine.cpp b/tools/chatgui/src/stream_publish_engine.cpp index 322407b3a..1e70baffc 100644 --- a/tools/chatgui/src/stream_publish_engine.cpp +++ b/tools/chatgui/src/stream_publish_engine.cpp @@ -6,15 +6,8 @@ #include "../transport/stream_bridge.h" #include "debug_config.h" #include -#include -#include #include -#include -#include -#include -#include #include -#include #include #include #include @@ -32,20 +25,7 @@ extern "C" { namespace { int64_t monotonicUs() { return call_video_playout_now_us(); } QString text(const char* value) { return QCoreApplication::translate("ChatGui", value); } -#ifdef HAVE_STREAM_CAPTURE -/* Имя QScreen не уникально (например, несколько X11 screens); номер различает одинаковые дескрипторы. */ -QByteArray screenId(QScreen* screen) { - int duplicate = 0; - for (auto* other : QGuiApplication::screens()) { - if (other == screen) break; - if (other->name() == screen->name() && other->manufacturer() == screen->manufacturer() - && other->model() == screen->model() && other->serialNumber() == screen->serialNumber()) duplicate++; - } - return QJsonDocument(QJsonArray{screen->name(), screen->manufacturer(), screen->model(), screen->serialNumber(), duplicate}) - .toJson(QJsonDocument::Compact); -} -#endif } struct StreamPublishEngine::Impl { struct Audio { int64_t pts = 0, created = 0; uint64_t sequence = 0; unsigned frames = 0; int16_t pcm[1920]{}; }; @@ -106,8 +86,6 @@ StreamPublishEngine::StreamPublishEngine(QObject* parent) : QObject(parent), m(s source.kind = static_cast(device.kind); source.id = device.id; source.name = device.id == "portal" ? text("Screen (system selection)") : device.name; source.size = device.size; source.modes = device.modes; - if (source.kind == StreamVideoSource::Screen) - for (auto* screen : QGuiApplication::screens()) if (screenId(screen) == source.id) source.screen = screen; m->sources.append(source); } DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop capture source snapshot count=%lld", (long long)m->sources.size()); diff --git a/tools/chatgui/src/stream_publish_engine.h b/tools/chatgui/src/stream_publish_engine.h index 14020adae..34ea4d982 100644 --- a/tools/chatgui/src/stream_publish_engine.h +++ b/tools/chatgui/src/stream_publish_engine.h @@ -12,8 +12,6 @@ #include #include #include -#include -#include #include #include "live_video_encoder.h" #include "stream_video_modes.h" @@ -28,9 +26,8 @@ struct StreamVideoSource { QByteArray id; // Camera device-id, дескриптор screen либо "portal" для системного выбора Wayland. QSize size; // Физический размер screen; может быть неизвестен до первого кадра portal. QList modes; // Режимы камеры из объявленных возможностей; screen-режимы строятся отдельно. - QPointer screen; // Живой монитор; удаление устройства не должно переключать захват на его одноимённого соседа. bool sameSource(const StreamVideoSource& other) const { // Сравнивает backend/handles, без имени, разрешения и списка режимов. - return kind == other.kind && id == other.id && screen == other.screen; + return kind == other.kind && id == other.id; } }; struct StreamPublishConfig { diff --git a/tools/chatgui/tests/test_stream_publish.cpp b/tools/chatgui/tests/test_stream_publish.cpp index c7f670ebd..3ceea7344 100644 --- a/tools/chatgui/tests/test_stream_publish.cpp +++ b/tools/chatgui/tests/test_stream_publish.cpp @@ -156,6 +156,22 @@ static void captureLifecycleLog(int, const char*, const char* message) { captureDiagnostics.append(QString::fromUtf8(message)); } +/* Только native-capture тесты сопоставляют внешний snapshot с экранами своего Qt application. */ +static QScreen* testScreen(const StreamVideoSource& source) { + for (auto* screen : QGuiApplication::screens()) { + int duplicate = 0; + for (auto* other : QGuiApplication::screens()) { + if (other == screen) break; + if (other->name() == screen->name() && other->manufacturer() == screen->manufacturer() + && other->model() == screen->model() && other->serialNumber() == screen->serialNumber()) ++duplicate; + } + const auto id = QJsonDocument(QJsonArray{screen->name(), screen->manufacturer(), screen->model(), + screen->serialNumber(), duplicate}).toJson(QJsonDocument::Compact); + if (id == source.id) return screen; + } + return nullptr; +} + class StreamPublishTest : public QObject { Q_OBJECT UASYNC* ua = nullptr; @@ -581,10 +597,10 @@ private slots: StreamPublishEngine engine; if (StreamPublishEngine::supported()) QTRY_VERIFY_WITH_TIMEOUT(!engine.sources().isEmpty(), 5000); StreamPublishConfig config; config.startMuted = true; config.video.width = 320; config.video.height = 180; config.video.fps = 15; - for (const auto& source : engine.sources()) if (source.kind == StreamVideoSource::Screen && source.screen) { + for (const auto& source : engine.sources()) if (source.kind == StreamVideoSource::Screen && testScreen(source)) { config.source = source; break; } - QVERIFY(config.source.screen); + QVERIFY(testScreen(config.source)); connect(&engine, &StreamPublishEngine::softwareEncodingRequested, &engine, [&engine](uint64_t generation) { engine.answerSoftwareEncoding(generation, true); }); QVERIFY(engine.start(group, config)); QVERIFY(engine.muted()); @@ -621,7 +637,7 @@ private slots: for (const auto& source : engine.sources()) { if (source.kind != StreamVideoSource::Screen || source.size.isEmpty()) continue; // При дробном DPI size*dpr округляется; сверяемся с физическими пикселями снимка. - QSize capturedSize = source.screen->grabWindow(0).size(); QVERIFY(!capturedSize.isEmpty()); + QSize capturedSize = testScreen(source)->grabWindow(0).size(); QVERIFY(!capturedSize.isEmpty()); QSize expected = stream_video_screen_size(capturedSize); config.source = source; config.source.size = {}; // Как portal: размер станет известен только из первого кадра. if (!engine.active()) { @@ -691,8 +707,8 @@ private slots: StreamPublishEngine engine; if (StreamPublishEngine::supported()) QTRY_VERIFY_WITH_TIMEOUT(!engine.sources().isEmpty(), 5000); QTRY_VERIFY_WITH_TIMEOUT(!engine.codecs().isEmpty(), 15000); StreamPublishConfig config; config.audio = false; - for (const auto& source : engine.sources()) if (source.kind == StreamVideoSource::Screen && source.screen == screen) config.source = source; - QVERIFY(config.source.screen); QVERIFY(config.showCursor); + for (const auto& source : engine.sources()) if (source.kind == StreamVideoSource::Screen && testScreen(source) == screen) config.source = source; + QVERIFY(testScreen(config.source)); QVERIFY(config.showCursor); config.video.width = 320; config.video.height = 180; QSignalSpy errors(&engine, &StreamPublishEngine::errorOccurred); QVERIFY(engine.start(group, config)); @@ -762,7 +778,7 @@ private slots: StreamPublishEngine engine; if (StreamPublishEngine::supported()) QTRY_VERIFY_WITH_TIMEOUT(!engine.sources().isEmpty(), 5000); for (const auto& source : engine.sources()) { - if (source.kind != StreamVideoSource::Screen || source.screen != screen) continue; + if (source.kind != StreamVideoSource::Screen || testScreen(source) != screen) continue; QVERIFY(StreamPublishEngine::cursorSupported(source)); StreamPublishWindow controls(&engine, group); auto* sources = menu(controls, "streamVideoSource"); QVERIFY(sources);