Refresh capture devices through IPC and diagnose unexpected backend stops
This commit is contained in:
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; ошибку смены трактует как отказ публикации.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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-пакета, без префикса длины. */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user