Commit 530ed29a authored by Jan Grulich's avatar Jan Grulich
Browse files

Merge screencastcommon into waylandintegration

Push buffers to stream based on framerate
parent 3f8cf28d
......@@ -25,7 +25,6 @@ if (SCREENCAST_ENABLED)
set (xdg_desktop_portal_kde_SRCS
${xdg_desktop_portal_kde_SRCS}
screencast.cpp
screencastcommon.cpp
screencaststream.cpp
screencastwidget.cpp
screenchooserdialog.cpp
......
......@@ -30,15 +30,11 @@ Q_LOGGING_CATEGORY(XdgDesktopPortalKdeRemoteDesktop, "xdp-kde-remotedesktop")
RemoteDesktopPortal::RemoteDesktopPortal(QObject *parent)
: QDBusAbstractAdaptor(parent)
, m_screenCastCommon(new ScreenCastCommon())
{
}
RemoteDesktopPortal::~RemoteDesktopPortal()
{
if (m_screenCastCommon) {
delete m_screenCastCommon;
}
}
uint RemoteDesktopPortal::CreateSession(const QDBusObjectPath &handle,
......@@ -60,7 +56,7 @@ uint RemoteDesktopPortal::CreateSession(const QDBusObjectPath &handle,
}
connect(session, &Session::closed, [this] () {
m_screenCastCommon->stopStreaming();
WaylandIntegration::stopStreaming();
});
return 0;
......@@ -128,7 +124,11 @@ uint RemoteDesktopPortal::Start(const QDBusObjectPath &handle,
if (session->screenSharingEnabled()) {
WaylandIntegration::WaylandOutput selectedOutput = WaylandIntegration::screens().value(remoteDesktopDialog->selectedScreens().first());
QVariant streams = m_screenCastCommon->startStreaming(selectedOutput);
if (!WaylandIntegration::startStreaming(selectedOutput)) {
return 2;
}
QVariant streams = WaylandIntegration::streams();
if (!streams.isValid()) {
qCWarning(XdgDesktopPortalKdeRemoteDesktop()) << "Pipewire stream is not ready to be streamed";
......
......@@ -119,10 +119,6 @@ public Q_SLOTS:
void NotifyTouchUp(const QDBusObjectPath &session_handle,
const QVariantMap &options,
uint slot);
private:
ScreenCastCommon *m_screenCastCommon;
};
#endif // XDG_DESKTOP_PORTAL_KDE_REMOTEDESKTOP_H
......
......@@ -30,15 +30,11 @@ Q_LOGGING_CATEGORY(XdgDesktopPortalKdeScreenCast, "xdp-kde-screencast")
ScreenCastPortal::ScreenCastPortal(QObject *parent)
: QDBusAbstractAdaptor(parent)
, m_screenCastCommon(new ScreenCastCommon())
{
}
ScreenCastPortal::~ScreenCastPortal()
{
if (m_screenCastCommon) {
delete m_screenCastCommon;
}
}
uint ScreenCastPortal::CreateSession(const QDBusObjectPath &handle,
......@@ -62,9 +58,7 @@ uint ScreenCastPortal::CreateSession(const QDBusObjectPath &handle,
}
connect(session, &Session::closed, [this] () {
if (m_screenCastCommon) {
m_screenCastCommon->stopStreaming();
}
WaylandIntegration::stopStreaming();
});
return 0;
......@@ -155,7 +149,11 @@ uint ScreenCastPortal::Start(const QDBusObjectPath &handle,
if (screenDialog->exec()) {
WaylandIntegration::WaylandOutput selectedOutput = WaylandIntegration::screens().value(screenDialog->selectedScreens().first());
QVariant streams = m_screenCastCommon->startStreaming(selectedOutput);
if (!WaylandIntegration::startStreaming(selectedOutput)) {
return 2;
}
QVariant streams = WaylandIntegration::streams();
if (!streams.isValid()) {
qCWarning(XdgDesktopPortalKdeScreenCast) << "Pipewire stream is not ready to be streamed";
......
......@@ -64,9 +64,6 @@ public Q_SLOTS:
const QString &parent_window,
const QVariantMap &options,
QVariantMap &results);
private:
ScreenCastCommon *m_screenCastCommon;
};
#endif // XDG_DESKTOP_PORTAL_KDE_SCREENCAST_H
......
/*
* Copyright © 2018 Red Hat, Inc
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Jan Grulich <jgrulich@redhat.com>
*/
#include "screencastcommon.h"
#include "screencaststream.h"
#include <QDBusArgument>
#include <QDBusMetaType>
#include <QEventLoop>
#include <QTimer>
const QDBusArgument &operator >> (const QDBusArgument &arg, ScreenCastCommon::Stream &stream)
{
arg.beginStructure();
arg >> stream.nodeId;
arg.beginMap();
while (!arg.atEnd()) {
QString key;
QVariant map;
arg.beginMapEntry();
arg >> key >> map;
arg.endMapEntry();
stream.map.insert(key, map);
}
arg.endMap();
arg.endStructure();
return arg;
}
const QDBusArgument &operator << (QDBusArgument &arg, const ScreenCastCommon::Stream &stream)
{
arg.beginStructure();
arg << stream.nodeId;
arg << stream.map;
arg.endStructure();
return arg;
}
Q_DECLARE_METATYPE(ScreenCastCommon::Stream);
Q_DECLARE_METATYPE(ScreenCastCommon::Streams);
ScreenCastCommon::ScreenCastCommon(QObject *parent)
: QObject(parent)
, m_streamingEnabled(false)
{
qDBusRegisterMetaType<ScreenCastCommon::Stream>();
qDBusRegisterMetaType<ScreenCastCommon::Streams>();
}
ScreenCastCommon::~ScreenCastCommon()
{
}
QVariant ScreenCastCommon::startStreaming(const WaylandIntegration::WaylandOutput &output)
{
m_stream = new ScreenCastStream(output.resolution());
m_stream->init();
connect(WaylandIntegration::waylandIntegration(), &WaylandIntegration::WaylandIntegration::newBuffer, m_stream, &ScreenCastStream::recordFrame);
connect(m_stream, &ScreenCastStream::startStreaming, this, [this] {
m_streamingEnabled = true;
WaylandIntegration::startStreaming();
});
connect(m_stream, &ScreenCastStream::stopStreaming, this, &ScreenCastCommon::stopStreaming);
bool streamReady = false;
QEventLoop loop;
connect(m_stream, &ScreenCastStream::streamReady, this, [&loop, &streamReady] {
loop.quit();
streamReady = true;
});
// HACK wait for stream to be ready
QTimer::singleShot(3000, &loop, &QEventLoop::quit);
loop.exec();
disconnect(m_stream, &ScreenCastStream::streamReady, this, nullptr);
if (!streamReady) {
return QVariant();
}
// TODO support multiple outputs
WaylandIntegration::bindOutput(output.waylandOutputName(), output.waylandOutputVersion());
Stream stream;
stream.nodeId = m_stream->nodeId();
stream.map = QVariantMap({{QLatin1String("size"), output.resolution()}});
return QVariant::fromValue<ScreenCastCommon::Streams>({stream});
}
void ScreenCastCommon::stopStreaming()
{
if (m_streamingEnabled) {
WaylandIntegration::stopStreaming();
m_streamingEnabled = false;
if (m_stream) {
delete m_stream;
m_stream = nullptr;
}
}
}
/*
* Copyright © 2018 Red Hat, Inc
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Jan Grulich <jgrulich@redhat.com>
*/
#ifndef XDG_DESKTOP_PORTAL_KDE_SCREENCAST_COMMON_H
#define XDG_DESKTOP_PORTAL_KDE_SCREENCAST_COMMON_H
#include <QObject>
#include <QMap>
#include <QSize>
#include "waylandintegration.h"
class ScreenCastStream;
class ScreenCastCommon : public QObject
{
public:
typedef struct {
uint nodeId;
QVariantMap map;
} Stream;
typedef QList<Stream> Streams;
explicit ScreenCastCommon(QObject *parent = nullptr);
~ScreenCastCommon();
QVariant startStreaming(const WaylandIntegration::WaylandOutput &output);
public Q_SLOTS:
void stopStreaming();
private:
bool m_streamingEnabled;
ScreenCastStream *m_stream;
};
#endif // XDG_DESKTOP_PORTAL_KDE_SCREENCAST_COMMON_H
......@@ -311,6 +311,15 @@ void ScreenCastStream::init()
pw_remote_connect(pwRemote);
}
uint ScreenCastStream::framerate()
{
if (pwStream) {
return videoFormat.max_framerate.num / videoFormat.max_framerate.denom;
}
return 0;
}
uint ScreenCastStream::nodeId()
{
if (pwStream) {
......
......@@ -66,6 +66,7 @@ public:
// Public
void init();
uint framerate();
uint nodeId();
// Public because we need access from static functions
......
......@@ -20,6 +20,10 @@
#include "waylandintegration.h"
#include "waylandintegration_p.h"
#include "screencaststream.h"
#include <QDBusArgument>
#include <QDBusMetaType>
#include <QEventLoop>
#include <QLoggingCategory>
......@@ -54,14 +58,9 @@ bool WaylandIntegration::isEGLInitialized()
return globalWaylandIntegration->isEGLInitialized();
}
void WaylandIntegration::bindOutput(int outputName, int outputVersion)
{
globalWaylandIntegration->bindOutput(outputName, outputVersion);
}
void WaylandIntegration::startStreaming()
bool WaylandIntegration::startStreaming(const WaylandOutput &output)
{
globalWaylandIntegration->startStreaming();
return globalWaylandIntegration->startStreaming(output);
}
void WaylandIntegration::stopStreaming()
......@@ -74,6 +73,11 @@ QMap<quint32, WaylandIntegration::WaylandOutput> WaylandIntegration::screens()
return globalWaylandIntegration->screens();
}
QVariant WaylandIntegration::streams()
{
return globalWaylandIntegration->streams();
}
WaylandIntegration::WaylandIntegration * WaylandIntegration::waylandIntegration()
{
return globalWaylandIntegration;
......@@ -126,6 +130,39 @@ void WaylandIntegration::WaylandOutput::setOutputType(const QString &type)
}
}
const QDBusArgument &operator >> (const QDBusArgument &arg, WaylandIntegration::WaylandIntegrationPrivate::Stream &stream)
{
arg.beginStructure();
arg >> stream.nodeId;
arg.beginMap();
while (!arg.atEnd()) {
QString key;
QVariant map;
arg.beginMapEntry();
arg >> key >> map;
arg.endMapEntry();
stream.map.insert(key, map);
}
arg.endMap();
arg.endStructure();
return arg;
}
const QDBusArgument &operator << (QDBusArgument &arg, const WaylandIntegration::WaylandIntegrationPrivate::Stream &stream)
{
arg.beginStructure();
arg << stream.nodeId;
arg << stream.map;
arg.endStructure();
return arg;
}
Q_DECLARE_METATYPE(WaylandIntegration::WaylandIntegrationPrivate::Stream)
Q_DECLARE_METATYPE(WaylandIntegration::WaylandIntegrationPrivate::Streams)
WaylandIntegration::WaylandIntegrationPrivate::WaylandIntegrationPrivate()
: WaylandIntegration()
, m_eglInitialized(false)
......@@ -135,6 +172,8 @@ WaylandIntegration::WaylandIntegrationPrivate::WaylandIntegrationPrivate()
, m_registry(nullptr)
, m_remoteAccessManager(nullptr)
{
qDBusRegisterMetaType<WaylandIntegrationPrivate::Stream>();
qDBusRegisterMetaType<WaylandIntegrationPrivate::Streams>();
}
WaylandIntegration::WaylandIntegrationPrivate::~WaylandIntegrationPrivate()
......@@ -160,19 +199,44 @@ void WaylandIntegration::WaylandIntegrationPrivate::bindOutput(int outputName, i
m_bindOutputs << output;
}
void WaylandIntegration::WaylandIntegrationPrivate::startStreaming()
bool WaylandIntegration::WaylandIntegrationPrivate::startStreaming(const WaylandOutput &output)
{
m_streamingEnabled = true;
m_stream = new ScreenCastStream(output.resolution());
m_stream->init();
if (!m_registryInitialized) {
qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Cannot start stream because registry is not initialized yet";
return;
connect(m_stream, &ScreenCastStream::startStreaming, this, [this, output] {
m_streamingEnabled = true;
bindOutput(output.waylandOutputName(), output.waylandOutputVersion());
});
connect(m_stream, &ScreenCastStream::stopStreaming, this, &WaylandIntegrationPrivate::stopStreaming);
bool streamReady = false;
QEventLoop loop;
connect(m_stream, &ScreenCastStream::streamReady, this, [&loop, &streamReady] {
loop.quit();
streamReady = true;
});
// HACK wait for stream to be ready
QTimer::singleShot(3000, &loop, &QEventLoop::quit);
loop.exec();
disconnect(m_stream, &ScreenCastStream::streamReady, this, nullptr);
if (!streamReady) {
delete m_stream;
m_stream = nullptr;
return false;
}
// TODO support multiple outputs
if (m_registry->hasInterface(KWayland::Client::Registry::Interface::RemoteAccessManager)) {
KWayland::Client::Registry::AnnouncedInterface interface = m_registry->interface(KWayland::Client::Registry::Interface::RemoteAccessManager);
if (!interface.name && !interface.version) {
qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Cannot start stream because remote access interface is not initialized yet";
return;
qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to start streaming: remote access manager interface is not initialized yet";
return false;
}
m_remoteAccessManager = m_registry->createRemoteAccessManager(interface.name, interface.version);
connect(m_remoteAccessManager, &KWayland::Client::RemoteAccessManager::bufferReady, this, [this] (const void *output, const KWayland::Client::RemoteBuffer * rbuf) {
......@@ -181,20 +245,34 @@ void WaylandIntegration::WaylandIntegrationPrivate::startStreaming()
processBuffer(rbuf);
});
});
m_output = output.waylandOutputName();
return true;
}
delete m_stream;
m_stream = nullptr;
qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to start streaming: no remote access manager interface";
return false;
}
void WaylandIntegration::WaylandIntegrationPrivate::stopStreaming()
{
if (m_remoteAccessManager) {
m_remoteAccessManager->release();
m_remoteAccessManager->destroy();
}
m_streamingEnabled = false;
if (m_streamingEnabled) {
// First unbound outputs and destroy remote access manager so we no longer recieve buffers
if (m_remoteAccessManager) {
m_remoteAccessManager->release();
m_remoteAccessManager->destroy();
}
qDeleteAll(m_bindOutputs);
m_bindOutputs.clear();
qDeleteAll(m_bindOutputs);
m_bindOutputs.clear();
if (m_stream) {
delete m_stream;
m_stream = nullptr;
}
m_streamingEnabled = false;
}
}
QMap<quint32, WaylandIntegration::WaylandOutput> WaylandIntegration::WaylandIntegrationPrivate::screens()
......@@ -202,6 +280,14 @@ QMap<quint32, WaylandIntegration::WaylandOutput> WaylandIntegration::WaylandInte
return m_outputMap;
}
QVariant WaylandIntegration::WaylandIntegrationPrivate::streams()
{
Stream stream;
stream.nodeId = m_stream->nodeId();
stream.map = QVariantMap({{QLatin1String("size"), m_outputMap.value(m_output).resolution()}});
return QVariant::fromValue<WaylandIntegrationPrivate::Streams>({stream});
}
void WaylandIntegration::WaylandIntegrationPrivate::initDrm()
{
m_drmFd = open("/dev/dri/renderD128", O_RDWR);
......@@ -275,8 +361,6 @@ void WaylandIntegration::WaylandIntegrationPrivate::initEGL()
void WaylandIntegration::WaylandIntegrationPrivate::initWayland()
{
qCDebug(XdgDesktopPortalKdeWaylandIntegration) << "InitWayland()";
m_thread = new QThread(this);
m_connection = new KWayland::Client::ConnectionThread;
......@@ -361,15 +445,25 @@ void WaylandIntegration::WaylandIntegrationPrivate::processBuffer(const KWayland
return;
}
if (m_lastFrameTime.isValid() &&
m_lastFrameTime.msecsTo(QDateTime::currentDateTime()) < (1000 / m_stream->framerate())) {
close(gbmHandle);
return;
}
if (!gbm_device_is_format_supported(m_gbmDevice, format, GBM_BO_USE_SCANOUT)) {
qCritical() << "GBM format is not supported by device!";
qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: GBM format is not supported by device!";
close(gbmHandle);
return;
}
// import GBM buffer that was passed from KWin
gbm_import_fd_data importInfo = {gbmHandle, width, height, stride, format};
gbm_bo *imported = gbm_bo_import(m_gbmDevice, GBM_BO_IMPORT_FD, &importInfo, GBM_BO_USE_SCANOUT);
if (!imported) {
qCritical() << "Cannot import passed GBM fd:" << strerror(errno);
qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: Cannot import passed GBM fd - " << strerror(errno);
close(gbmHandle);
return;
}
// bind context to render thread
......@@ -377,10 +471,16 @@ void WaylandIntegration::WaylandIntegrationPrivate::processBuffer(const KWayland
// create EGL image from imported BO
EGLImageKHR image = eglCreateImageKHR(m_egl.display, nullptr, EGL_NATIVE_PIXMAP_KHR, imported, nullptr);
// We can already close gbm handle
gbm_bo_destroy(imported);
close(gbmHandle);
if (image == EGL_NO_IMAGE_KHR) {
qCritical() << "Error creating EGLImageKHR" << formatGLError(glGetError());
qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: Error creating EGLImageKHR - " << formatGLError(glGetError());
return;
}
// create GL 2D texture for framebuffer
GLuint texture;
glGenTextures(1, &texture);
......@@ -398,7 +498,7 @@ void WaylandIntegration::WaylandIntegrationPrivate::processBuffer(const KWayland
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
qCritical() << "glCheckFramebufferStatus failed:" << formatGLError(glGetError());
qCWarning(XdgDesktopPortalKdeWaylandIntegration) << "Failed to process buffer: glCheckFramebufferStatus failed - " << formatGLError(glGetError());
glDeleteTextures(1, &texture);
glDeleteFramebuffers(1, &framebuffer);
eglDestroyImageKHR(m_egl.display, image);
......@@ -409,16 +509,15 @@ void WaylandIntegration::WaylandIntegrationPrivate::processBuffer(const KWayland
glViewport(0, 0, width, height);
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, capture->bits());
Q_EMIT newBuffer(capture->bits());
if (m_stream->recordFrame(capture->bits())) {
m_lastFrameTime = QDateTime::currentDateTime();
}
gbm_bo_destroy(imported);
glDeleteTextures(1, &texture);
glDeleteFramebuffers(1, &framebuffer);
eglDestroyImageKHR(m_egl.display, image);
delete capture;
close(gbmHandle);
}
void WaylandIntegration::WaylandIntegrationPrivate::setupRegistry()
......
......@@ -23,6 +23,7 @@
#include <QObject>
#include <QSize>
#include <QVariant>
#include <QtCore/QtGlobal>
......@@ -77,10 +78,11 @@ Q_SIGNALS:
bool isEGLInitialized();
void bindOutput(int outputName, int outputVersion);
void startStreaming();
bool startStreaming(const WaylandOutput &output);
void stopStreaming();
QMap<quint32, WaylandOutput> screens();
QVariant streams();
WaylandIntegration *waylandIntegration();
......
......@@ -23,6 +23,7 @@
#include "waylandintegration.h"
#include <QDateTime>
#include <QObject>
#include <QMap>
......@@ -31,6 +32,8 @@