|
|
|
|
@ -31,6 +31,9 @@ extern "C" {
|
|
|
|
|
#include <QWindowCapture> |
|
|
|
|
#include <QVideoFrame> |
|
|
|
|
#endif |
|
|
|
|
#ifdef UTUN_STREAM_X11_CURSOR |
|
|
|
|
#include "stream_screen_cursor.h" |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
namespace { |
|
|
|
|
int64_t monotonicUs() { return std::chrono::duration_cast<std::chrono::microseconds>( |
|
|
|
|
@ -75,6 +78,9 @@ struct StreamPublishEngine::Impl {
|
|
|
|
|
std::unique_ptr<QCamera> camera; |
|
|
|
|
std::unique_ptr<QScreenCapture> screen; |
|
|
|
|
std::unique_ptr<QTimer> screenGrabTimer; |
|
|
|
|
#ifdef UTUN_STREAM_X11_CURSOR |
|
|
|
|
std::unique_ptr<StreamScreenCursor> cursor; |
|
|
|
|
#endif |
|
|
|
|
std::unique_ptr<QWindowCapture> window; |
|
|
|
|
QVideoFrame frame; |
|
|
|
|
QVideoFrame previewFrame; |
|
|
|
|
@ -141,6 +147,13 @@ QVariant StreamPublishEngine::previewFrame() const {
|
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
QList<int> StreamPublishEngine::codecs() const { std::lock_guard<std::mutex> guard(m->lock); return m->codecs; } |
|
|
|
|
bool StreamPublishEngine::cursorSupported(const StreamVideoSource& source) { |
|
|
|
|
#ifdef UTUN_STREAM_X11_CURSOR |
|
|
|
|
return source.kind == StreamVideoSource::Screen && source.screen && source.id != "portal" && QGuiApplication::platformName() == "xcb"; |
|
|
|
|
#else |
|
|
|
|
Q_UNUSED(source); return false; |
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
bool StreamPublishEngine::muted() const { return m->muted.load(); } |
|
|
|
|
void StreamPublishEngine::setMuted(bool muted) { |
|
|
|
|
if (!m->running || !config().audio) { |
|
|
|
|
@ -256,6 +269,9 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) {
|
|
|
|
|
} |
|
|
|
|
// Qt X11 screen capture ищет root по имени и путает мониторы; QScreen захватывает именно выбранный экран.
|
|
|
|
|
if (selected && QGuiApplication::platformName() == "xcb") { |
|
|
|
|
#ifdef UTUN_STREAM_X11_CURSOR |
|
|
|
|
m->cursor = std::make_unique<StreamScreenCursor>(selected); |
|
|
|
|
#endif |
|
|
|
|
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; |
|
|
|
|
@ -266,6 +282,12 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) {
|
|
|
|
|
m->screenGrabTimer->stop(); |
|
|
|
|
QMetaObject::invokeMethod(this, [failed] { failed(text("Video capture stopped")); }); return; |
|
|
|
|
} |
|
|
|
|
#ifdef UTUN_STREAM_X11_CURSOR |
|
|
|
|
if (this->config().showCursor && !m->cursor->draw(image)) { |
|
|
|
|
m->screenGrabTimer->stop(); |
|
|
|
|
QMetaObject::invokeMethod(this, [failed] { failed(text("Could not capture the mouse cursor")); }); return; |
|
|
|
|
} |
|
|
|
|
#endif |
|
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) |
|
|
|
|
QVideoFrame frame(image); |
|
|
|
|
#else |
|
|
|
|
@ -284,8 +306,8 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) {
|
|
|
|
|
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()); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop X11 screen grab started source=%s interval_ms=%d cursor=%d supported=%d", |
|
|
|
|
qPrintable(config.source.name), m->screenGrabTimer->interval(), config.showCursor, cursorSupported(config.source)); |
|
|
|
|
} else { |
|
|
|
|
m->screen = std::make_unique<QScreenCapture>(); |
|
|
|
|
m->screen->setScreen(selected); |
|
|
|
|
@ -318,6 +340,9 @@ bool StreamPublishEngine::openSource(const StreamPublishConfig& config) {
|
|
|
|
|
void StreamPublishEngine::closeSource() { |
|
|
|
|
#ifdef HAVE_STREAM_CAPTURE |
|
|
|
|
m->screenGrabTimer.reset(); |
|
|
|
|
#ifdef UTUN_STREAM_X11_CURSOR |
|
|
|
|
m->cursor.reset(); |
|
|
|
|
#endif |
|
|
|
|
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(); } |
|
|
|
|
@ -367,6 +392,8 @@ bool StreamPublishEngine::reconfigure(const StreamPublishConfig& config) {
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_STREAM, "desktop microphone selection changed during live publication"); |
|
|
|
|
emit errorOccurred(text("Change the microphone before starting the stream")); return false; |
|
|
|
|
} |
|
|
|
|
if (config.showCursor != previous.showCursor) |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop cursor selected enabled=%d supported=%d", config.showCursor, cursorSupported(config.source)); |
|
|
|
|
bool sourceChange = !config.source.sameSource(previous.source) || (config.source.kind == StreamVideoSource::Camera |
|
|
|
|
&& (config.video.width != previous.video.width || config.video.height != previous.video.height || config.video.fps != previous.video.fps)); |
|
|
|
|
{ std::lock_guard<std::mutex> guard(m->lock); m->wanted = config; m->generation++; m->started = monotonicUs(); |
|
|
|
|
|