Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qpipewirecapturehelper.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 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 <QtCore/QMutexLocker>
8#include <QtCore/QUuid>
9#include <QtCore/QRandomGenerator>
10
11#include <QtCore/QVariantMap>
12
13#if QT_CONFIG(dbus)
14// These QtCore includes are needed for xdg-desktop-portal support
15#include <QtCore/private/qcore_unix_p.h>
16
17#include <QtCore/QFileInfo>
18#include <QtCore/QUrlQuery>
19
20#include <QtDBus/QDBusConnection>
21#include <QtDBus/QDBusInterface>
22#include <QtDBus/QDBusMessage>
23#include <QtDBus/QDBusReply>
24#include <QtDBus/QDBusPendingCall>
25#include <QtDBus/QDBusPendingCallWatcher>
26#include <QtDBus/QDBusPendingReply>
27#include <QtDBus/QDBusUnixFileDescriptor>
28
29#include <fcntl.h>
30
31#endif // QT_CONFIG(dbus)
32
34#include <qscreen.h>
35#include <qwindow.h>
36#include <qdebug.h>
37#include <qguiapplication.h>
38#include <qloggingcategory.h>
39
40#include "private/qvideoframe_p.h"
41#include "private/qcapturablewindow_p.h"
42#include "private/qmemoryvideobuffer_p.h"
43#include "private/qvideoframeconversionhelper_p.h"
44
46
47extern bool isPipewireLoaded();
48
49using namespace Qt::StringLiterals;
50
51static Q_LOGGING_CATEGORY(qLcPipeWireCapture, "qt.multimedia.ffmpeg.pipewirecapture");
52static Q_LOGGING_CATEGORY(qLcPipeWireCaptureMore, "qt.multimedia.ffmpeg.pipewirecapture.more");
53
54namespace QtPipeWire {
55
57{
58public:
60 pw_init(nullptr, nullptr);
61 };
63 pw_deinit();
64 }
65
66 Q_DISABLE_COPY(Pipewire)
67};
68
70{
73 QDBusInterface *interface = new QDBusInterface("org.freedesktop.portal.Desktop"_L1,
74 "/org/freedesktop/portal/desktop"_L1,
75 "org.freedesktop.DBus.Properties"_L1,
76 bus,
77 qGuiApp);
78
79 QList<QVariant> args;
80 args << "org.freedesktop.portal.ScreenCast"_L1
81 << "version"_L1;
82
83 QDBusMessage reply = interface->callWithArgumentList(QDBus::Block, "Get"_L1, args);
84 qCDebug(qLcPipeWireCapture) << "v1=" << reply.type()
85 << "v2=" << reply.arguments().size()
86 << "v3=" << reply.arguments().at(0).toUInt();
88 && reply.arguments().size() == 1
89 // && reply.arguments().at(0).toUInt() >= 2
90 ) {
92 }
93 qCDebug(qLcPipeWireCapture) << Q_FUNC_INFO << "hasScreenCastPortal=" << hasScreenCastPortal;
94 }
95
96 bool hasScreenCastPortal = false;
97
98 std::weak_ptr<Pipewire> pipewire;
99};
100
101}
102using namespace QtPipeWire;
104
106{
107 if (!globalState->hasScreenCastPortal)
108 return;
109
110 m_pipewire = globalState->pipewire.lock();
111 if (!m_pipewire) {
112 m_pipewire = std::make_shared<Pipewire>();
113 globalState->pipewire = m_pipewire;
114 }
115}
116
118{
119 m_pipewire.reset();
120}
121
123{
124 if (isSupported()) {
125 if (active && m_state == NoState)
126 createInterface();
127 if (!active && m_state == Streaming)
128 destroy();
129
130 return true;
131 }
132
134 "There is no ScreenCast service available in org.freedesktop.portal!"_L1);
135
136 return false;
137}
138
140 const QString &description)
141{
142 m_capture.updateError(error, description);
143}
144
146{
147 if (!isPipewireLoaded())
148 return false;
149
151}
152
154 : QObject()
155 , m_capture(capture)
156 , m_requestTokenPrefix(QUuid::createUuid().toString(QUuid::WithoutBraces).left(8))
157{
158}
159
161{
162 if (m_state != NoState)
163 destroy();
164}
165
167{
168 return m_videoFrameFormat;
169}
170
172{
173 if (!globalState)
174 return false;
175
176 return globalState->hasScreenCastPortal;
177}
178
179void QPipeWireCaptureHelper::gotRequestResponse(uint result, const QVariantMap &map)
180{
181 Q_UNUSED(map);
182 qCDebug(qLcPipeWireCapture) << Q_FUNC_INFO << "result=" << result << "map=" << map;
183 if (result != 0) {
184 m_operationState = NoOperation;
185 qWarning() << "Failed to capture screen via pipewire, perhaps because user cancelled the operation.";
186 m_requestToken = -1;
187 return;
188 }
189
190 switch (m_operationState) {
191 case CreateSession:
192 selectSources(map["session_handle"].toString());
193 break;
194 case SelectSources:
195 startStream();
196 break;
197 case StartStream:
198 updateStreams(map["streams"].value<QDBusArgument>());
199 openPipeWireRemote();
200 m_operationState = NoOperation;
201 m_state = Streaming;
202 break;
203 case OpenPipeWireRemote:
204 m_operationState = NoOperation;
205 break;
206 default:
207 break;
208 }
209}
210
211QString QPipeWireCaptureHelper::getRequestToken()
212{
213 if (m_requestToken <= 0)
214 m_requestToken = generateRequestToken();
215 return QStringLiteral("u%1%2").arg(m_requestTokenPrefix).arg(m_requestToken);
216}
217
218int QPipeWireCaptureHelper::generateRequestToken()
219{
220 return QRandomGenerator::global()->bounded(1, 25600);
221}
222
223void QPipeWireCaptureHelper::createInterface()
224{
225 if (!globalState)
226 return;
227 if (!globalState->hasScreenCastPortal)
228 return;
229
230 m_operationState = NoOperation;
231
232 if (!m_screenCastInterface) {
233 m_screenCastInterface = std::make_unique<QDBusInterface>("org.freedesktop.portal.Desktop"_L1,
234 "/org/freedesktop/portal/desktop"_L1,
235 "org.freedesktop.portal.ScreenCast"_L1,
237 bool ok = m_screenCastInterface->connection()
238 .connect("org.freedesktop.portal.Desktop"_L1, ""_L1,
239 "org.freedesktop.portal.Request"_L1, "Response"_L1,
240 this, SLOT(gotRequestResponse(uint, QVariantMap)));
241
242 if (!ok) {
244 "Failed to connect to org.freedesktop.portal.ScreenCast dbus interface."_L1);
245 return;
246 }
247 }
248 createSession();
249}
250
251void QPipeWireCaptureHelper::createSession()
252{
253 if (!m_screenCastInterface)
254 return;
255
256 QVariantMap options {
257 //{"handle_token"_L1 , getRequestToken()},
258 {"session_handle_token"_L1, getRequestToken()},
259 };
260 QDBusMessage reply = m_screenCastInterface->call("CreateSession"_L1, options);
261 if (!reply.errorMessage().isEmpty()) {
263 "Failed to create session for org.freedesktop.portal.ScreenCast. Error: "_L1 + reply.errorName()
264 + ": "_L1 + reply.errorMessage());
265 return;
266 }
267
268 m_operationState = CreateSession;
269}
270
271void QPipeWireCaptureHelper::selectSources(const QString &sessionHandle)
272{
273 if (!m_screenCastInterface)
274 return;
275
276 m_sessionHandle = sessionHandle;
277 QVariantMap options {
278 {"handle_token"_L1 , getRequestToken()},
279 {"types"_L1 , (uint)1},
280 {"multiple"_L1 , false},
281 {"cursor_mode"_L1 , (uint)1},
282 {"persist_mode"_L1 , (uint)0},
283 };
284 QDBusMessage reply = m_screenCastInterface->call("SelectSources"_L1,
285 QDBusObjectPath(sessionHandle), options);
286 if (!reply.errorMessage().isEmpty()) {
288 "Failed to select sources for org.freedesktop.portal.ScreenCast. Error: "_L1 + reply.errorName()
289 + ": "_L1 + reply.errorMessage());
290 return;
291 }
292
293 m_operationState = SelectSources;
294}
295
296void QPipeWireCaptureHelper::startStream()
297{
298 if (!m_screenCastInterface)
299 return;
300
301 QVariantMap options {
302 {"handle_token"_L1 , getRequestToken()},
303 };
304 QDBusMessage reply = m_screenCastInterface->call("Start"_L1,
305 QDBusObjectPath(m_sessionHandle), "", options);
306 if (!reply.errorMessage().isEmpty()) {
308 "Failed to start stream for org.freedesktop.portal.ScreenCast. Error: "_L1 + reply.errorName()
309 + ": "_L1 + reply.errorMessage());
310 return;
311 }
312
313 m_operationState = StartStream;
314}
315
316void QPipeWireCaptureHelper::updateStreams(const QDBusArgument &streamsInfo)
317{
318 m_streams.clear();
319
320 streamsInfo.beginStructure();
321 streamsInfo.beginArray();
322
323 while (!streamsInfo.atEnd()) {
324 quint32 nodeId = 0;
325 streamsInfo >> nodeId;
326 QMap<QString, QVariant> properties;
327 streamsInfo >> properties;
328
329 qint32 x = 0;
330 qint32 y = 0;
331 if (properties.contains("position")) {
332 const QDBusArgument position = properties["position"].value<QDBusArgument>();
333 position.beginStructure();
334 position >> x;
335 position >> y;
336 position.endStructure();
337 }
338
339 qint32 width = 0;
340 qint32 height = 0;
341 if (properties.contains("size")) {
342 const QDBusArgument size = properties["size"].value<QDBusArgument>();
344 size >> width;
345 size >> height;
346 size.endStructure();
347 }
348
349 uint sourceType = 0;
350 if (properties.contains("source_type"))
351 sourceType = properties["source_type"].toUInt();
352
353 StreamInfo streamInfo;
354 streamInfo.nodeId = nodeId;
355 streamInfo.sourceType = sourceType;
356 streamInfo.rect = {x, y, width, height};
357 m_streams << streamInfo;
358 }
359
360 streamsInfo.endArray();
361 streamsInfo.endStructure();
362
363}
364
365void QPipeWireCaptureHelper::openPipeWireRemote()
366{
367 if (!m_screenCastInterface)
368 return;
369
370 QVariantMap options;
371 QDBusReply<QDBusUnixFileDescriptor> reply = m_screenCastInterface->call("OpenPipeWireRemote"_L1,
372 QDBusObjectPath(m_sessionHandle), options);
373 if (!reply.isValid()) {
375 "Failed to open pipewire remote for org.freedesktop.portal.ScreenCast. Error: name="_L1
376 + reply.error().name() + ", message="_L1 + reply.error().message());
377 return;
378 }
379
380 m_pipewireFd = reply.value().fileDescriptor();
381 bool ok = open(m_pipewireFd);
382 qCDebug(qLcPipeWireCapture) << "open(" << m_pipewireFd << ") result=" << ok;
383
384 m_operationState = OpenPipeWireRemote;
385}
386
387namespace {
388class LoopLocker
389{
390public:
391 LoopLocker(pw_thread_loop *threadLoop)
392 : m_threadLoop(threadLoop) {
393 lock();
394 }
395 ~LoopLocker() {
396 unlock();
397 }
398
399 void lock() {
400 if (m_threadLoop)
401 pw_thread_loop_lock(m_threadLoop);
402 }
403
404 void unlock() {
405 if (m_threadLoop) {
406 pw_thread_loop_unlock(m_threadLoop);
407 m_threadLoop = nullptr;
408 }
409 }
410
411private:
412 pw_thread_loop *m_threadLoop = nullptr;
413};
414}
415
417{
418 if (m_streams.isEmpty())
419 return false;
420
421 if (!globalState)
422 return false;
423 if (!m_pipewire)
424 initPipeWire();
425
426 static const pw_core_events coreEvents = {
427 .version = PW_VERSION_CORE_EVENTS,
428 .info = [](void *data, const struct pw_core_info *info) {
431 },
432 .done = [](void *object, uint32_t id, int seq) {
433 reinterpret_cast<QPipeWireCaptureHelper *>(object)->onCoreEventDone(id, seq);
434 },
435 .ping = [](void *data, uint32_t id, int seq) {
437 Q_UNUSED(id)
438 Q_UNUSED(seq)
439 },
440 .error = [](void *data, uint32_t id, int seq, int res, const char *message) {
442 Q_UNUSED(id)
443 Q_UNUSED(seq)
446 },
447 .remove_id = [](void *data, uint32_t id) {
449 Q_UNUSED(id)
450 },
451 .bound_id = [](void *data, uint32_t id, uint32_t global_id) {
453 Q_UNUSED(id)
454 Q_UNUSED(global_id)
455 },
456 .add_mem = [](void *data, uint32_t id, uint32_t type, int fd, uint32_t flags) {
458 Q_UNUSED(id)
460 Q_UNUSED(fd)
462 },
463 .remove_mem = [](void *data, uint32_t id) {
465 Q_UNUSED(id)
466 },
467#if defined(PW_CORE_EVENT_BOUND_PROPS)
468 .bound_props = [](void *data, uint32_t id, uint32_t global_id, const struct spa_dict *props) {
470 Q_UNUSED(id)
471 Q_UNUSED(global_id)
473 },
474#endif // PW_CORE_EVENT_BOUND_PROPS
475 };
476
477 static const pw_registry_events registryEvents = {
478 .version = PW_VERSION_REGISTRY_EVENTS,
479 .global = [](void *object, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props) {
480 reinterpret_cast<QPipeWireCaptureHelper *>(object)->onRegistryEventGlobal(id, permissions, type, version, props);
481 },
482 .global_remove = [](void *data, uint32_t id) {
484 Q_UNUSED(id)
485 },
486 };
487
488 m_threadLoop = pw_thread_loop_new("qt-multimedia-pipewire-loop", nullptr);
489 if (!m_threadLoop) {
490 m_err = true;
492 "QPipeWireCaptureHelper failed at pw_thread_loop_new()."_L1);
493 return false;
494 }
495
496 m_context = pw_context_new(pw_thread_loop_get_loop(m_threadLoop), nullptr, 0);
497 if (!m_context) {
498 m_err = true;
500 "QPipeWireCaptureHelper failed at pw_context_new()."_L1);
501 return false;
502 }
503
504 m_core = pw_context_connect_fd(m_context,
505 fcntl(pipewireFd, F_DUPFD_CLOEXEC, 5),
506 nullptr,
507 0);
508 if (!m_core) {
509 m_err = true;
511 "QPipeWireCaptureHelper failed at pw_context_connect_fd()."_L1);
512 return false;
513 }
514
515 pw_core_add_listener(m_core, &m_coreListener, &coreEvents, this);
516
517 m_registry = pw_core_get_registry(m_core, PW_VERSION_REGISTRY, 0);
518 if (!m_registry) {
519 m_err = true;
521 "QPipeWireCaptureHelper failed at pw_core_get_registry()."_L1);
522 return false;
523 }
524 pw_registry_add_listener(m_registry, &m_registryListener, &registryEvents, this);
525
526 updateCoreInitSeq();
527
528 if (pw_thread_loop_start(m_threadLoop) != 0) {
529 m_err = true;
531 "QPipeWireCaptureHelper failed at pw_thread_loop_start()."_L1);
532 return false;
533 }
534
535 LoopLocker locker(m_threadLoop);
536 while (!m_initDone) {
537 if (pw_thread_loop_timed_wait(m_threadLoop, 2) != 0)
538 break;
539 }
540
541 return m_initDone && m_hasSource;
542}
543
544void QPipeWireCaptureHelper::updateCoreInitSeq()
545{
546 m_coreInitSeq = pw_core_sync(m_core, PW_ID_CORE, m_coreInitSeq);
547}
548
549void QPipeWireCaptureHelper::onCoreEventDone(uint32_t id, int seq)
550{
551 if (id == PW_ID_CORE && seq == m_coreInitSeq) {
552 spa_hook_remove(&m_registryListener);
553 spa_hook_remove(&m_coreListener);
554
555 m_initDone = true;
556 pw_thread_loop_signal(m_threadLoop, false);
557 }
558}
559
560void QPipeWireCaptureHelper::onRegistryEventGlobal(uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props)
561{
562 Q_UNUSED(id)
563 Q_UNUSED(permissions)
564 Q_UNUSED(version)
565
566 if (qstrcmp(type, PW_TYPE_INTERFACE_Node) != 0)
567 return;
568
569 auto media_class = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
570 if (!media_class)
571 return;
572
573 if (qstrcmp(media_class, "Stream/Output/Video") != 0)
574 return;
575
576 m_hasSource = true;
577
578 updateCoreInitSeq();
579
580 recreateStream();
581}
582
583void QPipeWireCaptureHelper::recreateStream()
584{
585 static const pw_stream_events streamEvents = {
586 .version = PW_VERSION_STREAM_EVENTS,
587 .destroy = [](void *data) {
589 },
590 .state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) {
591 reinterpret_cast<QPipeWireCaptureHelper *>(data)->onStateChanged(old, state, error);
592 },
593 .control_info = [](void *data, uint32_t id, const struct pw_stream_control *control) {
595 Q_UNUSED(id)
596 Q_UNUSED(control)
597 },
598 .io_changed = [](void *data, uint32_t id, void *area, uint32_t size) {
600 Q_UNUSED(id)
603 },
604 .param_changed = [](void *data, uint32_t id, const struct spa_pod *param) {
605 reinterpret_cast<QPipeWireCaptureHelper *>(data)->onParamChanged(id, param);
606 },
607 .add_buffer = [](void *data, struct pw_buffer *buffer) {
610 },
611 .remove_buffer = [](void *data, struct pw_buffer *buffer) {
614 },
615 .process = [](void *data) {
616 reinterpret_cast<QPipeWireCaptureHelper *>(data)->onProcess();
617 },
618 .drained = [](void *data) {
620 },
621#if PW_VERSION_STREAM_EVENTS >= 1
622 .command = [](void *data, const struct spa_command *command) {
624 Q_UNUSED(command)
625 },
626#endif
627#if PW_VERSION_STREAM_EVENTS >= 2
628 .trigger_done = [](void *data) {
630 },
631#endif
632 };
633
634 destroyStream(true);
635
636 auto streamInfo = m_streams[0];
637 struct spa_dict_item items[4];
638 struct spa_dict info;
639 items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_TYPE, "Video");
640 items[1] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_CATEGORY, "Capture");
641 items[2] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_ROLE, "Screen");
642 info = SPA_DICT_INIT(items, 3);
643 auto props = pw_properties_new_dict(&info);
644
645 LoopLocker locker(m_threadLoop);
646
647 m_stream = pw_stream_new(m_core, "video-capture", props);
648 if (!m_stream) {
649 m_err = true;
650 locker.unlock();
652 "QPipeWireCaptureHelper failed at pw_stream_new()."_L1);
653 return;
654 }
655
656 m_streamListener = {};
657 pw_stream_add_listener(m_stream, &m_streamListener, &streamEvents, this);
658
659 uint8_t buffer[4096];
660 struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
661 const struct spa_pod *params[1];
662 struct spa_rectangle defsize = SPA_RECTANGLE(quint32(streamInfo.rect.width()), quint32(streamInfo.rect.height()));
663 struct spa_rectangle maxsize = SPA_RECTANGLE(4096, 4096);
664 struct spa_rectangle minsize = SPA_RECTANGLE(1,1);
665 struct spa_fraction defrate = SPA_FRACTION(25, 1);
666 struct spa_fraction maxrate = SPA_FRACTION(1000, 1);
667 struct spa_fraction minrate = SPA_FRACTION(0, 1);
668
669 params[0] = static_cast<const spa_pod*>(spa_pod_builder_add_object(
670 &b,
671 SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
672 SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
673 SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
674 SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(6,
675 SPA_VIDEO_FORMAT_RGB,
676 SPA_VIDEO_FORMAT_BGR,
677 SPA_VIDEO_FORMAT_RGBA,
678 SPA_VIDEO_FORMAT_BGRA,
679 SPA_VIDEO_FORMAT_RGBx,
680 SPA_VIDEO_FORMAT_BGRx),
681 SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
682 &defsize, &minsize, &maxsize),
683 SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(
684 &defrate, &minrate, &maxrate))
685 );
686
687 const int connectErr = pw_stream_connect(
688 m_stream,
689 PW_DIRECTION_INPUT,
690 streamInfo.nodeId,
691 static_cast<pw_stream_flags>(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS),
692 params,
693 1
694 );
695 if (connectErr != 0) {
696 m_err = true;
697 locker.unlock();
699 "QPipeWireCaptureHelper failed at pw_stream_connect()."_L1);
700 return;
701 }
702}
703void QPipeWireCaptureHelper::destroyStream(bool forceDrain)
704{
705 if (!m_stream)
706 return;
707
708 if (forceDrain) {
709 LoopLocker locker(m_threadLoop);
710 while (!m_streamPaused && !m_silence && !m_err) {
711 if (pw_thread_loop_timed_wait(m_threadLoop, 1) != 0)
712 break;
713 }
714 }
715
716 LoopLocker locker(m_threadLoop);
717 m_ignoreStateChange = true;
718 pw_stream_disconnect(m_stream);
719 pw_stream_destroy(m_stream);
720 m_ignoreStateChange = false;
721
722 m_stream = nullptr;
723 m_requestToken = -1;
724}
725
726void QPipeWireCaptureHelper::signalLoop(bool onProcessDone, bool err)
727{
728 if (err)
729 m_err = true;
730 if (onProcessDone)
731 m_processed = true;
732 pw_thread_loop_signal(m_threadLoop, false);
733}
734
735void QPipeWireCaptureHelper::onStateChanged(pw_stream_state old, pw_stream_state state, const char *error)
736{
737 Q_UNUSED(old)
739
740 if (m_ignoreStateChange)
741 return;
742
743 switch (state)
744 {
745 case PW_STREAM_STATE_UNCONNECTED:
746 signalLoop(false, true);
747 break;
748 case PW_STREAM_STATE_PAUSED:
749 m_streamPaused = true;
750 signalLoop(false, false);
751 break;
752 case PW_STREAM_STATE_STREAMING:
753 m_streamPaused = false;
754 signalLoop(false, false);
755 break;
756 default:
757 break;
758 }
759}
760void QPipeWireCaptureHelper::onProcess()
761{
762 struct pw_buffer *b;
763 struct spa_buffer *buf;
764 int sstride = 0;
765 void *sdata;
766 qsizetype size = 0;
767
768 if ((b = pw_stream_dequeue_buffer(m_stream)) == NULL) {
770 "Out of buffers in pipewire stream dequeue."_L1);
771 return;
772 }
773
774 buf = b->buffer;
775 if ((sdata = buf->datas[0].data) == NULL)
776 return;
777
778 sstride = buf->datas[0].chunk->stride;
779 if (sstride == 0)
780 sstride = buf->datas[0].chunk->size / m_size.height();
781 size = buf->datas[0].chunk->size;
782
783 if (m_videoFrameFormat.frameSize() != m_size || m_videoFrameFormat.pixelFormat() != m_pixelFormat)
784 m_videoFrameFormat = QVideoFrameFormat(m_size, m_pixelFormat);
785
786 m_currentFrame = QVideoFramePrivate::createFrame(
787 std::make_unique<QMemoryVideoBuffer>(QByteArray(static_cast<const char *>(sdata), size), sstride),
788 m_videoFrameFormat);
789 emit m_capture.newVideoFrame(m_currentFrame);
790 qCDebug(qLcPipeWireCaptureMore) << "got a frame of size " << buf->datas[0].chunk->size;
791
792 pw_stream_queue_buffer(m_stream, b);
793
794 signalLoop(true, false);
795}
796
797void QPipeWireCaptureHelper::destroy()
798{
799 if (!globalState)
800 return;
801 m_state = Stopping;
802 destroyStream(false);
803
804 pw_thread_loop_stop(m_threadLoop);
805
806 if (m_registry)
807 pw_proxy_destroy(reinterpret_cast<pw_proxy *>(m_registry));
808
809 if (m_core)
810 pw_core_disconnect(m_core);
811
812 if (m_context)
813 pw_context_destroy(m_context);
814
815 pw_thread_loop_destroy(m_threadLoop);
816
817 if (m_pipewire)
819
820 m_state = NoState;
821}
822
823void QPipeWireCaptureHelper::onParamChanged(uint32_t id, const struct spa_pod *param)
824{
825 if (param == NULL || id != SPA_PARAM_Format)
826 return;
827
828 if (spa_format_parse(param,
829 &m_format.media_type,
830 &m_format.media_subtype) < 0)
831 return;
832
833 if (m_format.media_type != SPA_MEDIA_TYPE_video
834 || m_format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
835 return;
836
837 if (spa_format_video_raw_parse(param, &m_format.info.raw) < 0)
838 return;
839
840 qCDebug(qLcPipeWireCapture) << "got video format:";
841 qCDebug(qLcPipeWireCapture) << " format: " << m_format.info.raw.format
842 << " (" << spa_debug_type_find_name(spa_type_video_format, m_format.info.raw.format) << ")";
843 qCDebug(qLcPipeWireCapture) << " size: " << m_format.info.raw.size.width
844 << " x " << m_format.info.raw.size.height;
845 qCDebug(qLcPipeWireCapture) << " framerate: " << m_format.info.raw.framerate.num
846 << " / " << m_format.info.raw.framerate.denom;
847
848 m_size = QSize(m_format.info.raw.size.width, m_format.info.raw.size.height);
849 m_pixelFormat = QPipeWireCaptureHelper::toQtPixelFormat(m_format.info.raw.format);
850 qCDebug(qLcPipeWireCapture) << "m_pixelFormat=" << m_pixelFormat;
851}
852
853// align with qt_videoFormatLookup in src/plugins/multimedia/gstreamer/common/qgst.cpp
854// https://docs.pipewire.org/group__spa__param.html#gacb274daea0abcce261955323e7d0b1aa
855// Most of the formats are identical to their GStreamer equivalent.
856QVideoFrameFormat::PixelFormat QPipeWireCaptureHelper::toQtPixelFormat(spa_video_format spaVideoFormat)
857{
858 switch (spaVideoFormat) {
859 default:
860 break;
861 case SPA_VIDEO_FORMAT_I420:
863 case SPA_VIDEO_FORMAT_Y42B:
865 case SPA_VIDEO_FORMAT_YV12:
867 case SPA_VIDEO_FORMAT_UYVY:
869 case SPA_VIDEO_FORMAT_YUY2:
871 case SPA_VIDEO_FORMAT_NV12:
873 case SPA_VIDEO_FORMAT_NV21:
875 case SPA_VIDEO_FORMAT_AYUV:
877 case SPA_VIDEO_FORMAT_GRAY8:
879 case SPA_VIDEO_FORMAT_xRGB:
881 case SPA_VIDEO_FORMAT_xBGR:
883 case SPA_VIDEO_FORMAT_RGBx:
885 case SPA_VIDEO_FORMAT_BGRx:
887 case SPA_VIDEO_FORMAT_ARGB:
889 case SPA_VIDEO_FORMAT_ABGR:
891 case SPA_VIDEO_FORMAT_RGBA:
893 case SPA_VIDEO_FORMAT_BGRA:
895#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
896 case SPA_VIDEO_FORMAT_GRAY16_LE:
898 case SPA_VIDEO_FORMAT_P010_10LE:
900#else
901 case SPA_VIDEO_FORMAT_GRAY16_BE:
903 case SPA_VIDEO_FORMAT_P010_10BE:
905#endif
906 }
907
909}
910
911spa_video_format QPipeWireCaptureHelper::toSpaVideoFormat(QVideoFrameFormat::PixelFormat pixelFormat)
912{
913 switch (pixelFormat) {
914 default:
915 break;
917 return SPA_VIDEO_FORMAT_I420;
919 return SPA_VIDEO_FORMAT_Y42B;
921 return SPA_VIDEO_FORMAT_YV12;
923 return SPA_VIDEO_FORMAT_UYVY;
925 return SPA_VIDEO_FORMAT_YUY2;
927 return SPA_VIDEO_FORMAT_NV12;
929 return SPA_VIDEO_FORMAT_NV21;
931 return SPA_VIDEO_FORMAT_AYUV;
933 return SPA_VIDEO_FORMAT_GRAY8;
935 return SPA_VIDEO_FORMAT_xRGB;
937 return SPA_VIDEO_FORMAT_xBGR;
939 return SPA_VIDEO_FORMAT_RGBx;
941 return SPA_VIDEO_FORMAT_BGRx;
943 return SPA_VIDEO_FORMAT_ARGB;
945 return SPA_VIDEO_FORMAT_ABGR;
947 return SPA_VIDEO_FORMAT_RGBA;
949 return SPA_VIDEO_FORMAT_BGRA;
950#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
952 return SPA_VIDEO_FORMAT_GRAY16_LE;
954 return SPA_VIDEO_FORMAT_P010_10LE;
955#else
957 return SPA_VIDEO_FORMAT_GRAY16_BE;
959 return SPA_VIDEO_FORMAT_P010_10BE;
960#endif
961 }
962
963 return SPA_VIDEO_FORMAT_UNKNOWN;
964}
965
\inmodule QtDBus
void beginStructure()
Opens a new D-Bus structure suitable for appending new arguments.
\inmodule QtDBus
static QDBusConnection sessionBus()
Returns a QDBusConnection object opened with the session bus.
\inmodule QtDBus
\inmodule QtDBus
\inmodule QtDBus
virtual qint64 size() const
For open random-access devices, this function returns the size of the device.
NetworkError error() const
Returns the error that was found during the processing of this request.
\inmodule QtCore
Definition qobject.h:103
QVideoFrameFormat frameFormat() const
QPipeWireCaptureHelper(QPipeWireCapture &capture)
void updateError(QPlatformSurfaceCapture::Error error, const QString &description={})
void updateError(Error error, const QString &errorString)
void newVideoFrame(const QVideoFrame &)
static Q_DECL_CONST_FUNCTION QRandomGenerator * global()
\threadsafe
Definition qrandom.h:275
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:133
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
\inmodule QtCore
Definition quuid.h:32
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
PixelFormat
Enumerates video data types.
QVideoFrameFormat::PixelFormat pixelFormat() const
Returns the pixel format of frames in a video stream.
QSize frameSize() const
Returns the dimensions of frames in a video stream.
static QVideoFrame createFrame(std::unique_ptr< Buffer > buffer, QVideoFrameFormat format)
QMap< QString, QString > map
[6]
else opt state
[0]
Combined button and popup list for selecting options.
Q_CORE_EXPORT int qstrcmp(const char *str1, const char *str2)
#define Q_FUNC_INFO
static const QCssKnownValue properties[NumProperties - 1]
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char * interface
DBusConnection const char DBusError * error
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
#define qGuiApp
static qint64 area(const QSize &s)
Definition qicon.cpp:183
#define qWarning
Definition qlogging.h:167
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
#define SLOT(a)
Definition qobjectdefs.h:52
GLboolean GLboolean GLboolean b
GLint GLint GLint GLint GLint x
[0]
GLint GLsizei GLsizei height
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint id
[7]
GLuint object
[3]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLint GLsizei width
GLint left
GLenum type
GLenum GLuint GLenum GLsizei const GLchar * buf
GLbitfield flags
GLuint GLsizei const GLchar * message
GLenum GLuint GLsizei const GLenum * props
GLenum const GLint * param
GLuint64 GLenum GLint fd
GLint y
void ** params
GLuint res
GLuint64EXT * result
[6]
QT_BEGIN_NAMESPACE bool isPipewireLoaded()
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
#define QStringLiteral(str)
#define emit
#define Q_UNUSED(x)
unsigned int quint32
Definition qtypes.h:50
int qint32
Definition qtypes.h:49
ptrdiff_t qsizetype
Definition qtypes.h:165
unsigned int uint
Definition qtypes.h:34
QReadWriteLock lock
[0]
mutex unlock()
QList< QTreeWidgetItem * > items
QNetworkReply * reply
QHostInfo info
[0]
char * toString(const MyType &t)
[31]
QJSValueList args