Pass only immutable source identifiers into the publisher
This commit is contained in:
1 parent
61663e261d
commit
14bdd2a58f
3 files changed
+23
-32
No files matched your search
@@ -6,15 +6,8 @@
|
||||
#include "../transport/stream_bridge.h"
|
||||
#include "debug_config.h"
|
||||
#include <QGuiApplication>
|
||||
#include <QThread>
|
||||
#include <QScreen>
|
||||
#include <QRandomGenerator>
|
||||
#include <QTransform>
|
||||
#include <QPixmap>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QEventLoop>
|
||||
#include <QScopeGuard>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
@@ -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<StreamVideoSource::Kind>(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());
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include <QVariant>
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include <QPointer>
|
||||
#include <QScreen>
|
||||
#include <memory>
|
||||
#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<StreamVideoMode> modes; // Режимы камеры из объявленных возможностей; screen-режимы строятся отдельно.
|
||||
QPointer<QScreen> 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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user