Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qx11surfacecapture.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6
7#include <qvideoframe.h>
8#include <qscreen.h>
9#include <qwindow.h>
10#include <qdebug.h>
11#include <qguiapplication.h>
12#include <qloggingcategory.h>
13
14#include "private/qabstractvideobuffer_p.h"
15#include "private/qcapturablewindow_p.h"
16
17#include <X11/Xlib.h>
18#include <sys/shm.h>
19#include <X11/extensions/XShm.h>
20#include <X11/Xutil.h>
21#include <X11/extensions/Xrandr.h>
22
23#include <optional>
24
26
27static Q_LOGGING_CATEGORY(qLcX11SurfaceCapture, "qt.multimedia.ffmpeg.qx11surfacecapture");
28
29namespace {
30
31void destroyXImage(XImage* image) {
32 XDestroyImage(image); // macro
33}
34
35template <typename T, typename D>
36std::unique_ptr<T, D> makeXUptr(T* ptr, D deleter) {
37 return std::unique_ptr<T, D>(ptr, deleter);
38}
39
40int screenNumberByName(Display *display, const QString &name)
41{
42 int size = 0;
43 auto monitors = makeXUptr(
44 XRRGetMonitors(display, XDefaultRootWindow(display), true, &size),
45 &XRRFreeMonitors);
46 const auto end = monitors.get() + size;
47 auto found = std::find_if(monitors.get(), end, [&](const XRRMonitorInfo &info) {
48 auto atomName = makeXUptr(XGetAtomName(display, info.name), &XFree);
49 return atomName && name == QString::fromUtf8(atomName.get());
50 });
51
52 return found == end ? -1 : std::distance(monitors.get(), found);
53}
54
55QVideoFrameFormat::PixelFormat xImagePixelFormat(const XImage &image)
56{
57 if (image.bits_per_pixel != 32) return QVideoFrameFormat::Format_Invalid;
58
59 if (image.red_mask == 0xff0000 &&
60 image.green_mask == 0xff00 &&
61 image.blue_mask == 0xff)
63
64 if (image.red_mask == 0xff00 &&
65 image.green_mask == 0xff0000 &&
66 image.blue_mask == 0xff000000)
68
69 if (image.blue_mask == 0xff0000 &&
70 image.green_mask == 0xff00 &&
71 image.red_mask == 0xff)
73
74 if (image.red_mask == 0xff00 &&
75 image.green_mask == 0xff0000 &&
76 image.blue_mask == 0xff000000)
78
80}
81
82class DataVideoBuffer : public QAbstractVideoBuffer
83{
84public:
85 DataVideoBuffer(const char *data, int bytesPerLine, int size)
88 m_size(size),
89 m_bytesPerLine(bytesPerLine)
90 {
91 }
92
93 QVideoFrame::MapMode mapMode() const override { return m_mapMode; }
94
95 MapData map(QVideoFrame::MapMode mode) override
96 {
97 MapData mapData;
98 if (m_mapMode == QVideoFrame::NotMapped) {
99 m_mapMode = mode;
100
101 mapData.nPlanes = 1;
102 mapData.bytesPerLine[0] = m_bytesPerLine;
103 mapData.data[0] = (uchar *)m_data.data();
104 mapData.size[0] = m_size;
105 }
106
107 return mapData;
108 }
109
110 void unmap() override { m_mapMode = QVideoFrame::NotMapped; }
111
112private:
115 int m_size;
116 int m_bytesPerLine;
117};
118
119} // namespace
120
122{
123public:
124 static std::unique_ptr<Grabber> create(QX11SurfaceCapture &capture, QScreen *screen)
125 {
126 std::unique_ptr<Grabber> result(new Grabber(capture));
127 return result->init(screen) ? std::move(result) : nullptr;
128 }
129
130 static std::unique_ptr<Grabber> create(QX11SurfaceCapture &capture, WId wid)
131 {
132 std::unique_ptr<Grabber> result(new Grabber(capture));
133 return result->init(wid) ? std::move(result) : nullptr;
134 }
135
136 ~Grabber() override
137 {
138 stop();
139
140 detachShm();
141 }
142
143 const QVideoFrameFormat &format() const { return m_format; }
144
145private:
146 Grabber(QX11SurfaceCapture &capture) : m_capture(capture)
147 {
150 }
151
152 bool createDisplay()
153 {
154 if (!m_display)
155 m_display.reset(XOpenDisplay(nullptr));
156
157 if (!m_display)
159 QLatin1String("Cannot open X11 display"));
160
161 return m_display != nullptr;
162 }
163
164 bool init(WId wid)
165 {
168
169 return createDisplay() && initWithXID(static_cast<XID>(wid));
170 }
171
172 bool init(QScreen *screen)
173 {
174 if (!screen) {
176 return false;
177 }
178
179 if (!createDisplay())
180 return false;
181
182 auto screenNumber = screenNumberByName(m_display.get(), screen->name());
183
184 if (screenNumber < 0)
185 return false;
186
188
189 return initWithXID(RootWindow(m_display.get(), screenNumber));
190 }
191
192 bool initWithXID(XID xid)
193 {
194 m_xid = xid;
195
196 if (update()) {
197 start();
198 return true;
199 }
200
201 return false;
202 }
203
204 void detachShm()
205 {
206 if (std::exchange(m_attached, false)) {
207 XShmDetach(m_display.get(), &m_shmInfo);
208 shmdt(m_shmInfo.shmaddr);
209 shmctl(m_shmInfo.shmid, IPC_RMID, 0);
210 }
211 }
212
213 void attachShm()
214 {
215 Q_ASSERT(!m_attached);
216
217 m_shmInfo.shmid =
218 shmget(IPC_PRIVATE, m_xImage->bytes_per_line * m_xImage->height, IPC_CREAT | 0777);
219
220 if (m_shmInfo.shmid == -1)
221 return;
222
223 m_shmInfo.readOnly = false;
224 m_shmInfo.shmaddr = m_xImage->data = (char *)shmat(m_shmInfo.shmid, 0, 0);
225
226 m_attached = XShmAttach(m_display.get(), &m_shmInfo);
227 }
228
229 bool update()
230 {
231 XWindowAttributes wndattr = {};
232 if (XGetWindowAttributes(m_display.get(), m_xid, &wndattr) == 0) {
234 QLatin1String("Cannot get window attributes"));
235 return false;
236 }
237
238 // TODO: if capture windows, we should adjust offsets and size if
239 // the window is out of the screen borders
240 // m_xOffset = ...
241 // m_yOffset = ...
242
243 // check window params for the root window as well since
244 // it potentially can be changed (e.g. on VM with resizing)
245 if (!m_xImage || wndattr.width != m_xImage->width || wndattr.height != m_xImage->height
246 || wndattr.depth != m_xImage->depth || wndattr.visual->visualid != m_visualID) {
247
248 qCDebug(qLcX11SurfaceCapture) << "recreate ximage: " << wndattr.width << wndattr.height
249 << wndattr.depth << wndattr.visual->visualid;
250
251 detachShm();
252 m_xImage.reset();
253
254 m_visualID = wndattr.visual->visualid;
255 m_xImage.reset(XShmCreateImage(m_display.get(), wndattr.visual, wndattr.depth, ZPixmap,
256 nullptr, &m_shmInfo, wndattr.width, wndattr.height));
257
258 if (!m_xImage) {
260 QLatin1String("Cannot create image"));
261 return false;
262 }
263
264 const auto pixelFormat = xImagePixelFormat(*m_xImage);
265
266 // TODO: probably, add a converter instead
267 if (pixelFormat == QVideoFrameFormat::Format_Invalid) {
269 QLatin1String("Not handled pixel format, bpp=")
270 + QString::number(m_xImage->bits_per_pixel));
271 return false;
272 }
273
274 attachShm();
275
276 if (!m_attached) {
278 QLatin1String("Cannot attach shared memory"));
279 return false;
280 }
281
282 m_format = QVideoFrameFormat(QSize(m_xImage->width, m_xImage->height), pixelFormat);
283 m_format.setFrameRate(frameRate());
284 }
285
286 return m_attached;
287 }
288
289protected:
291 {
292 if (!update())
293 return {};
294
295 if (!XShmGetImage(m_display.get(), m_xid, m_xImage.get(), m_xOffset, m_yOffset,
296 AllPlanes)) {
299 "Cannot get ximage; the window may be out of the screen borders"));
300 return {};
301 }
302
303 auto buffer = new DataVideoBuffer(m_xImage->data, m_xImage->bytes_per_line,
304 m_xImage->bytes_per_line * m_xImage->height);
305
306 return QVideoFrame(buffer, m_format);
307 }
308
309private:
310 QX11SurfaceCapture &m_capture;
311 std::optional<QPlatformSurfaceCapture::Error> m_prevGrabberError;
312 XID m_xid = None;
313 int m_xOffset = 0;
314 int m_yOffset = 0;
315 std::unique_ptr<Display, decltype(&XCloseDisplay)> m_display{ nullptr, &XCloseDisplay };
316 std::unique_ptr<XImage, decltype(&destroyXImage)> m_xImage{ nullptr, &destroyXImage };
317 XShmSegmentInfo m_shmInfo;
318 bool m_attached = false;
319 VisualID m_visualID = None;
320 QVideoFrameFormat m_format;
321};
322
324 : QPlatformSurfaceCapture(initialSource)
325{
326 // For debug
327 // XSetErrorHandler([](Display *, XErrorEvent * e) {
328 // qDebug() << "error handler" << e->error_code;
329 // return 0;
330 // });
331}
332
334
336{
337 return m_grabber ? m_grabber->format() : QVideoFrameFormat{};
338}
339
341{
342 qCDebug(qLcX11SurfaceCapture) << "set active" << active;
343
344 if (m_grabber)
345 m_grabber.reset();
346 else
347 std::visit([this](auto source) { activate(source); }, source());
348
349 return static_cast<bool>(m_grabber) == active;
350}
351
352void QX11SurfaceCapture::activate(ScreenSource screen)
353{
355 m_grabber = Grabber::create(*this, screen);
356}
357
358void QX11SurfaceCapture::activate(WindowSource window)
359{
361 m_grabber = Grabber::create(*this, handle ? handle->id : 0);
362}
363
365{
366 return qgetenv("XDG_SESSION_TYPE").compare(QLatin1String("x11"), Qt::CaseInsensitive) == 0;
367}
368
NSData * m_data
The QAbstractVideoBuffer class is an abstraction for video data. \inmodule QtMultimedia.
virtual void unmap()=0
Releases the memory mapped by the map() function.
virtual QVideoFrame::MapMode mapMode() const =0
\inmodule QtCore
Definition qbytearray.h:57
int compare(QByteArrayView a, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
Definition qbytearray.h:587
static const QCapturableWindowPrivate * handle(const QCapturableWindow &window)
void addFrameCallback(Object &object, Method method)
void errorUpdated(QPlatformSurfaceCapture::Error error, const QString &description)
QScreen * primaryScreen
the primary (or default) screen of the application.
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
void updateError(Error error, const QString &errorString)
std::variant< ScreenSource, WindowSource > Source
bool checkScreenWithError(ScreenSource &screen)
void newVideoFrame(const QVideoFrame &)
The QScreen class is used to query screen properties. \inmodule QtGui.
Definition qscreen.h:32
qreal refreshRate
the approximate vertical refresh rate of the screen in Hz
Definition qscreen.h:64
QString name
a user presentable string representing the screen
Definition qscreen.h:36
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
static QString number(int, int base=10)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:7822
void start(Priority=InheritPriority)
Definition qthread.cpp:923
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
PixelFormat
Enumerates video data types.
void setFrameRate(qreal rate)
Sets the frame rate of a video stream in frames per second.
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:26
MapMode
Enumerates how a video buffer's data is mapped to system memory.
Definition qvideoframe.h:36
static std::unique_ptr< Grabber > create(QX11SurfaceCapture &capture, QScreen *screen)
const QVideoFrameFormat & format() const
QVideoFrame grabFrame() override
static std::unique_ptr< Grabber > create(QX11SurfaceCapture &capture, WId wid)
QVideoFrameFormat frameFormat() const override
~QX11SurfaceCapture() override
bool setActiveInternal(bool active) override
QX11SurfaceCapture(Source initialSource)
QMap< QString, QString > map
[6]
struct wl_display * display
Definition linuxdmabuf.h:41
Combined button and popup list for selecting options.
@ CaseInsensitive
Definition image.cpp:4
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
static ControlElement< T > * ptr(QWidget *widget)
GLuint64 GLenum void * handle
GLenum mode
GLuint * monitors
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint GLuint end
GLenum GLuint buffer
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint name
GLsizei GLsizei GLchar * source
GLuint64EXT * result
[6]
unsigned long XID
static QAbstractVideoBuffer::MapData mapData(const camera_frame_nv12_t &frame, unsigned char *baseAddress)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
QScreen * screen
[1]
Definition main.cpp:29
Q_CORE_EXPORT QByteArray qgetenv(const char *varName)
struct _XDisplay Display
unsigned char uchar
Definition qtypes.h:27
QFileInfo info(fileName)
[8]
aWidget window() -> setWindowTitle("New Window Title")
[2]