|
|
|
|
@ -7,6 +7,7 @@
|
|
|
|
|
#include <QScreen> |
|
|
|
|
#include <QRandomGenerator> |
|
|
|
|
#include <QTransform> |
|
|
|
|
#include <QPixmap> |
|
|
|
|
#include <QJsonArray> |
|
|
|
|
#include <QJsonDocument> |
|
|
|
|
#include <thread> |
|
|
|
|
@ -73,6 +74,7 @@ struct StreamPublishEngine::Impl {
|
|
|
|
|
std::unique_ptr<QVideoSink> sink; |
|
|
|
|
std::unique_ptr<QCamera> camera; |
|
|
|
|
std::unique_ptr<QScreenCapture> screen; |
|
|
|
|
std::unique_ptr<QTimer> screenGrabTimer; |
|
|
|
|
std::unique_ptr<QWindowCapture> window; |
|
|
|
|
QVideoFrame frame; |
|
|
|
|
QVideoFrame previewFrame; |
|
|
|
|
@ -239,21 +241,55 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) {
|
|
|
|
|
connect(m->camera.get(), &QCamera::errorOccurred, this, [failed](QCamera::Error, const QString& error) { failed(error); }, Qt::QueuedConnection); |
|
|
|
|
m->camera->start(); |
|
|
|
|
} else if (config.source.kind == StreamVideoSource::Screen) { |
|
|
|
|
m->screen = std::make_unique<QScreenCapture>(); |
|
|
|
|
QScreen* selected = nullptr; |
|
|
|
|
if (config.source.id != "portal") { |
|
|
|
|
QScreen* selected = nullptr; |
|
|
|
|
for (auto* screen : QGuiApplication::screens()) |
|
|
|
|
if (screen == config.source.screen && screenId(screen) == config.source.id) { selected = screen; break; } |
|
|
|
|
if (!selected) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop screen disappeared"); return false; } |
|
|
|
|
} |
|
|
|
|
// Qt X11 screen capture ищет root по имени и путает мониторы; QScreen захватывает именно выбранный экран.
|
|
|
|
|
if (selected && QGuiApplication::platformName() == "xcb") { |
|
|
|
|
m->screenGrabTimer = std::make_unique<QTimer>(); m->screenGrabTimer->setTimerType(Qt::PreciseTimer); |
|
|
|
|
connect(m->screenGrabTimer.get(), &QTimer::timeout, this, [this, generation, screen = QPointer<QScreen>(selected), failed] { |
|
|
|
|
if (generation != m->sourceGeneration || !m->running) return; |
|
|
|
|
int64_t captured = monotonicUs(); |
|
|
|
|
QImage image = screen ? screen->grabWindow(0).toImage() : QImage(); |
|
|
|
|
if (image.isNull()) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop X11 screen grab failed present=%d", !screen.isNull()); |
|
|
|
|
m->screenGrabTimer->stop(); |
|
|
|
|
QMetaObject::invokeMethod(this, [failed] { failed(text("Video capture stopped")); }); return; |
|
|
|
|
} |
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) |
|
|
|
|
QVideoFrame frame(image); |
|
|
|
|
#else |
|
|
|
|
image = image.convertToFormat(QImage::Format_RGBA8888); |
|
|
|
|
QVideoFrame frame(QVideoFrameFormat(image.size(), QVideoFrameFormat::Format_RGBA8888)); |
|
|
|
|
if (frame.map(QVideoFrame::WriteOnly)) { |
|
|
|
|
for (int y = 0; y < image.height(); y++) std::memcpy(frame.bits(0) + y * frame.bytesPerLine(0), image.constScanLine(y), image.width() * 4); |
|
|
|
|
frame.unmap(); |
|
|
|
|
} else frame = {}; |
|
|
|
|
#endif |
|
|
|
|
if (!frame.isValid()) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop X11 video frame allocation failed size=%dx%d", image.width(), image.height()); |
|
|
|
|
m->screenGrabTimer->stop(); |
|
|
|
|
QMetaObject::invokeMethod(this, [failed] { failed(text("Video frame conversion failed")); }); return; |
|
|
|
|
} |
|
|
|
|
frame.setStartTime(captured); m->sink->setVideoFrame(frame); |
|
|
|
|
}); |
|
|
|
|
m->screenGrabTimer->start(std::max(1, 1000 / std::clamp(config.video.fps, 1, 60))); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop X11 screen grab started source=%s interval_ms=%d", |
|
|
|
|
qPrintable(config.source.name), m->screenGrabTimer->interval()); |
|
|
|
|
} else { |
|
|
|
|
m->screen = std::make_unique<QScreenCapture>(); |
|
|
|
|
m->screen->setScreen(selected); |
|
|
|
|
m->session->setScreenCapture(m->screen.get()); |
|
|
|
|
connect(m->screen.get(), &QScreenCapture::activeChanged, this, [this, generation](bool active) { |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop screen capture active=%d generation=%llu", active, (unsigned long long)generation); |
|
|
|
|
if (!active && generation == m->sourceGeneration) sourceFailed(m->generation, text("Video capture stopped")); |
|
|
|
|
}, Qt::QueuedConnection); |
|
|
|
|
connect(m->screen.get(), &QScreenCapture::errorOccurred, this, [failed](QScreenCapture::Error, const QString& error) { failed(error); }, Qt::QueuedConnection); |
|
|
|
|
m->screen->start(); |
|
|
|
|
} |
|
|
|
|
m->session->setScreenCapture(m->screen.get()); |
|
|
|
|
connect(m->screen.get(), &QScreenCapture::activeChanged, this, [this, generation](bool active) { |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop screen capture active=%d generation=%llu", active, (unsigned long long)generation); |
|
|
|
|
if (!active && generation == m->sourceGeneration) sourceFailed(m->generation, text("Video capture stopped")); |
|
|
|
|
}, Qt::QueuedConnection); |
|
|
|
|
connect(m->screen.get(), &QScreenCapture::errorOccurred, this, [failed](QScreenCapture::Error, const QString& error) { failed(error); }, Qt::QueuedConnection); |
|
|
|
|
m->screen->start(); |
|
|
|
|
} else { |
|
|
|
|
auto window = config.source.window.value<QCapturableWindow>(); |
|
|
|
|
if (!window.isValid()) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop window is unavailable"); return false; } |
|
|
|
|
@ -274,6 +310,7 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) {
|
|
|
|
|
} |
|
|
|
|
void StreamPublishEngine::closeSource() { |
|
|
|
|
#ifdef HAVE_STREAM_CAPTURE |
|
|
|
|
m->screenGrabTimer.reset(); |
|
|
|
|
emit previewVideoChanged(QVariant()); |
|
|
|
|
if (m->sink) disconnect(m->sink.get(), nullptr, this, nullptr); |
|
|
|
|
if (m->camera) { disconnect(m->camera.get(), nullptr, this, nullptr); m->camera->stop(); } |
|
|
|
|
@ -331,6 +368,9 @@ bool StreamPublishEngine::reconfigure(const StreamPublishConfig& config) {
|
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
if (sourceChange && !openSource(config)) { sourceFailed(m->generation, text("Could not open the selected video source")); return false; } |
|
|
|
|
#ifdef HAVE_STREAM_CAPTURE |
|
|
|
|
if (m->screenGrabTimer) m->screenGrabTimer->setInterval(std::max(1, 1000 / std::clamp(config.video.fps, 1, 60))); |
|
|
|
|
#endif |
|
|
|
|
m->wake.notify_all(); emit stateChanged(text("Applying video settings…")); return true; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|