Refresh capture devices through IPC and diagnose unexpected backend stops

This commit is contained in:
evgeny committed 2026-11-01 20:14:07 +01:00
1 parent 8faaa34e24
commit cf356ee647
7 files changed
+27 -5

No files matched your search

@@ -236,6 +236,11 @@ QList<StreamVideoSource> StreamPublishEngine::sources() const {
return {};
#endif
}
void StreamPublishEngine::refreshSources() {
#ifdef HAVE_STREAM_CAPTURE
m->capture->refresh();
#endif
}
/* Команда не обращается к Qt capture: создание/замена backend происходит в отдельном процессе. */
bool StreamPublishEngine::openSource(const StreamPublishConfig& config) {
@@ -60,6 +60,7 @@ public:
QVariant previewFrame() const; // Собственный CPU QImage; пустой QVariant без кадра, lifetime не связан с capture.
QList<int> codecs() const; // Кодеки LiveVideoEncoder, найденные при создании; пусто без поддержки capture.
QList<StreamVideoSource> sources() const; // Последний асинхронный snapshot worker; до sourcesChanged список может быть пуст.
void refreshSources(); // Запросить новый snapshot через IPC; GUI не опрашивает аппаратный backend.
static bool supported(); // Возможности сборки HAVE_STREAM_CAPTURE; не гарантия наличия устройства или рабочего encoder.
static bool cursorSupported(const StreamVideoSource& source); // Наличие cursor backend для выбранного screen/платформы.
void setMuted(bool muted); // Только running+audio: меняет флаг actor и обнуляет исходящий PCM; ошибку смены трактует как отказ публикации.
+1 -1
View File
@@ -338,7 +338,7 @@ void StreamPublishWindow::refreshSources() {
auto* group = m_source->findChild<QActionGroup*>(); group->setExclusive(false); action->setChecked(false); group->setExclusive(true);
}
m_source->addSeparator();
connect(m_source->addAction(text("Refresh")), &QAction::triggered, this, &StreamPublishWindow::refreshSources);
connect(m_source->addAction(text("Refresh")), &QAction::triggered, m_engine, &StreamPublishEngine::refreshSources);
refreshModes(config, changed && selected >= 0 && m_sources[selected].kind == StreamVideoSource::Screen);
synchronize();
}
+5
View File
@@ -243,3 +243,8 @@ VideoCapture::~VideoCapture() { m->shutdown(); }
void VideoCapture::configure(const VideoCaptureConfig& config) { m->configure(config); }
void VideoCapture::stop() { m->stop(); }
bool VideoCapture::closing() const { return m->stopping; }
void VideoCapture::refresh() {
if (m->exiting || m->stopping) return;
if (m->process.state() == QProcess::NotRunning) m->launch();
else m->send(packet(Refresh, m->generation));
}
+1
View File
@@ -44,6 +44,7 @@ public:
void configure(const VideoCaptureConfig& config);
void stop(); // Запросить выход процесса; deadline 1.5 с не продлевается сообщениями worker. Повтор безопасен.
bool closing() const; // true до подтверждённого выхода процесса после stop; GUI никогда не ждёт его синхронно.
void refresh(); // Асинхронный опрос источников в worker; без capture-процесса запускает его заново.
signals:
/* Полный список устройств/режимов после опроса и при изменениях backend, независимо от enabled. */
void devicesChanged(const VideoCaptureDevices& devices);
+1 -1
View File
@@ -12,7 +12,7 @@ namespace VideoCaptureProtocol {
/* GUI -> worker: Configure(bool,QByteArray device,qint32 w/h/fps), Quit(), Ack(), Buffer(QString name).
* Worker -> GUI: Devices(count,[name,id,modes(w,h,fps)]), Frame(qint64 pts/correction,qint32 w/h), Stage/Error(QString).
* Buffer/Devices используют поколение 0; остальные — поколение конфигурации/подтверждаемого кадра. */
enum Type : quint8 { Configure = 1, Quit, Ack, Devices, Frame, Stage, Error, Buffer, Heartbeat };
enum Type : quint8 { Configure = 1, Quit, Ack, Devices, Frame, Stage, Error, Buffer, Heartbeat, Refresh };
constexpr quint32 FrameBytes = 1920 * 1080 * 4; /* Буфер Call; Stream согласует буфер до MaxFrameBytes. */
constexpr quint32 MaxFrameBytes = 4096u * 4096u * 4u; /* Проверяемый верхний предел RGB32. */
constexpr quint32 MaxPacket = 65536; /* Максимум тела IPC-пакета, без префикса длины. */
+13 -3
View File
@@ -75,7 +75,12 @@ class QtVideoCaptureSource : public VideoCaptureSource {
public:
QtVideoCaptureSource() {
connect(&devicesMonitor, &QMediaDevices::videoInputsChanged, this, &VideoCaptureSource::devicesChanged);
connect(qGuiApp, &QGuiApplication::screenAdded, this, &VideoCaptureSource::devicesChanged);
auto watchScreen = [this](QScreen* item) {
connect(item, &QScreen::geometryChanged, this, &VideoCaptureSource::devicesChanged);
connect(item, &QScreen::physicalDotsPerInchChanged, this, &VideoCaptureSource::devicesChanged);
};
for (auto* item : QGuiApplication::screens()) watchScreen(item);
connect(qGuiApp, &QGuiApplication::screenAdded, this, [this, watchScreen](QScreen* item) { watchScreen(item); emit devicesChanged(); });
connect(qGuiApp, &QGuiApplication::screenRemoved, this, [this](QScreen* removed) {
if (selectedScreen == removed) fail(QStringLiteral("Selected screen was disconnected"));
emit devicesChanged();
@@ -132,7 +137,7 @@ public:
if (best.isNull()) { fail(QStringLiteral("Selected camera mode is unavailable")); return false; }
camera->setCameraFormat(best); session->setCamera(camera.get());
connect(camera.get(), &QCamera::activeChanged, this, [this, id = config.device](bool active) {
if (active) rate.activate(id); else rate.restore();
if (active) rate.activate(id); else { rate.restore(); fail(QStringLiteral("Camera capture stopped unexpectedly")); }
});
connect(camera.get(), &QCamera::errorOccurred, this, [this](QCamera::Error, const QString& message) { fail(message); });
emit stage(QStringLiteral("camera.start")); camera->start();
@@ -172,6 +177,8 @@ public:
screen = std::make_unique<QScreenCapture>(); screen->setScreen(selectedScreen); session->setScreenCapture(screen.get());
connect(screen.get(), &QScreenCapture::errorOccurred, this,
[this](QScreenCapture::Error, const QString& message) { fail(message); });
connect(screen.get(), &QScreenCapture::activeChanged, this,
[this](bool active) { if (!active) fail(QStringLiteral("Screen capture stopped unexpectedly")); });
emit stage(QStringLiteral("screen.start")); screen->start();
}
} else {
@@ -182,6 +189,8 @@ public:
session->setWindowCapture(window.get());
connect(window.get(), &QWindowCapture::errorOccurred, this,
[this](QWindowCapture::Error, const QString& message) { fail(message); });
connect(window.get(), &QWindowCapture::activeChanged, this,
[this](bool active) { if (!active) fail(QStringLiteral("Window capture stopped unexpectedly")); });
emit stage(QStringLiteral("window.start")); window->start();
}
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "capture started kind=%u source=%s size=%dx%d fps=%d cursor=%d",
@@ -339,7 +348,8 @@ class VideoCaptureWorker : public QObject {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "camera frame mapping failed: %s", qPrintable(buffer.errorString())); return false;
}
pixels = buffer.data();
} else if (type == Quit) { generation = revision; stop(); stage(QStringLiteral("process.exit")); QCoreApplication::quit(); }
} else if (type == Refresh) enumerate();
else if (type == Quit) { generation = revision; stop(); stage(QStringLiteral("process.exit")); QCoreApplication::quit(); }
else if (type == Ack) {
if (sending && revision == inFlight) { sending = false; if (pending) convert(pending, generation); }
} else if (type == Configure) {