Sunshine0.0.5917
Sunshine 0.0.5917
Self-hosted game stream host for Moonlight.
Loading...
Searching...
No Matches
pipewire.cpp
Go to the documentation of this file.
1
5// standard includes
6#include <fstream>
7
8// lib includes
9#include <gio/gio.h>
10#include <gio/gunixfdlist.h>
11#include <libdrm/drm_fourcc.h>
12#include <pipewire/pipewire.h>
13#include <spa/param/video/format-utils.h>
14#include <spa/param/video/type-info.h>
15#include <spa/pod/builder.h>
16
17// local includes
18#include "cuda.h"
19#include "graphics.h"
20#include "src/main.h"
21#include "src/platform/common.h"
22#include "src/video.h"
23#include "vaapi.h"
24#include "vulkan_encode.h"
25#include "wayland.h"
26
27#if !PW_CHECK_VERSION(1, 6, 0)
28constexpr int SPA_VIDEO_TRANSFER_SMPTE2084 = 14;
29#endif
30
31#if PW_CHECK_VERSION(0, 3, 75)
32// Runtime linked library version checks are available. Check for pipewire 0.3.64 which documented object serial support and deprecated node id.
33const bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = pw_check_library_version(0, 3, 64);
34#elifdef PW_KEY_TARGET_OBJECT
35// Runtime linked library version checks are UNAVAILABLE but necessary PW_KEY_TARGET_OBJECT for object serial support is available.
36constexpr bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL = true;
37#else
38// Pipewire object serials are unsupported without PW_KEY_TARGET_OBJECT (we define it here so compilation won't break but don't use it).
44 #define PW_KEY_TARGET_OBJECT "target.object"
45#endif
46
47namespace {
48 // Buffer and limit constants
49 constexpr int SPA_POD_BUFFER_SIZE = 4096;
50 constexpr int MAX_PARAMS = 200;
51 constexpr int MAX_DMABUF_FORMATS = 200;
52 constexpr int MAX_DMABUF_MODIFIERS = 200;
53} // namespace
54
55using namespace std::literals;
56
57namespace pipewire {
61 struct format_map_t {
62 uint64_t fourcc;
63 int32_t pw_format;
64 };
65
66 static constexpr std::array<format_map_t, 7> format_map = {{
67 {DRM_FORMAT_XBGR2101010, SPA_VIDEO_FORMAT_xBGR_210LE},
68 {DRM_FORMAT_BGRA1010102, SPA_VIDEO_FORMAT_ARGB_210LE},
69 {DRM_FORMAT_RGBA1010102, SPA_VIDEO_FORMAT_ABGR_210LE},
70 {DRM_FORMAT_ABGR2101010, SPA_VIDEO_FORMAT_RGBA_102LE},
71 {DRM_FORMAT_ARGB2101010, SPA_VIDEO_FORMAT_BGRA_102LE},
72 {DRM_FORMAT_ARGB8888, SPA_VIDEO_FORMAT_BGRA},
73 {DRM_FORMAT_XRGB8888, SPA_VIDEO_FORMAT_BGRx},
74 }};
75
80 std::atomic<int> negotiated_width {0};
81 std::atomic<int> negotiated_height {0};
82 std::atomic<int> color_primaries {0};
83 std::atomic<int> transfer_function {0};
84 std::atomic<bool> stream_dead {false};
85 pw_stream_state previous_state;
86 pw_stream_state current_state;
87 std::string err_msg;
88 };
89
93 struct stream_data_t {
94 struct pw_stream *stream;
95 struct spa_hook stream_listener;
96 struct spa_video_info format;
97 struct pw_buffer *current_buffer;
98 uint64_t drm_format;
99 std::shared_ptr<shared_state_t> shared;
100 std::mutex frame_mutex;
101 std::condition_variable frame_cv;
102 size_t local_stride = 0;
103 bool frame_ready = false;
104 // Two distinct memory pools
105 std::vector<uint8_t> buffer_a;
106 std::vector<uint8_t> buffer_b;
107 // Points to the buffer currently owned by fill_img
108 std::vector<uint8_t> *front_buffer;
109 // Points to the buffer currently being written by on_process
110 std::vector<uint8_t> *back_buffer;
111
112 stream_data_t():
115 };
116
121 int32_t format;
122 uint64_t *modifiers;
124 };
125
130 ~img_descriptor_t() override {
131 // Only free buffers this image actually owns. The memory-buffer capture
132 // path points img->data at the PipeWire staging vector (front_buffer),
133 // which is owned by pipewire_t -- deleting it here corrupts the heap.
134 if (data && data_owned) {
135 delete[] data;
136 }
137 data = nullptr;
138 data_owned = false;
139 }
140
141 bool data_owned = false;
142 };
143
147 class pipewire_t {
148 public:
149 pipewire_t():
150 loop(pw_thread_loop_new("Pipewire thread", nullptr)) {
151 BOOST_LOG(debug) << "[pipewire] Start PW thread loop"sv;
152 pw_thread_loop_start(loop);
153 }
154
155 ~pipewire_t() {
156 BOOST_LOG(debug) << "[pipewire] Destroying pipewire_t"sv;
157 pw_thread_loop_lock(loop);
158
159 // Lock the frame mutex to stop fill_img
160 BOOST_LOG(debug) << "[pipewire] Stop fill_img"sv;
161 {
162 std::scoped_lock lock(stream_data.frame_mutex);
163 stream_data.frame_ready = false;
164 stream_data.current_buffer = nullptr;
165 }
166
167 // Release pipewire stream
168 if (stream_data.stream) {
169 BOOST_LOG(debug) << "[pipewire] Disconnect stream"sv;
170 pw_stream_disconnect(stream_data.stream);
171 BOOST_LOG(debug) << "[pipewire] Destroy stream"sv;
172 pw_stream_destroy(stream_data.stream);
173 stream_data.stream = nullptr;
174 }
175 // Release pipewire core
176 if (core) {
177 BOOST_LOG(debug) << "[pipewire] Disconnect PW core"sv;
178 pw_core_disconnect(core);
179 core = nullptr;
180 }
181 // Release pipewire context
182 if (context) {
183 BOOST_LOG(debug) << "[pipewire] Destroy PW context"sv;
184 pw_context_destroy(context);
185 context = nullptr;
186 }
187 // Release pipewire file descriptor
188 if (fd >= 0) {
189 BOOST_LOG(debug) << "[pipewire] Close pipewire_fd"sv;
190 close(fd);
191 }
192 // Release pipewire thread loop
193 BOOST_LOG(debug) << "[pipewire] Stop PW thread loop"sv;
194 pw_thread_loop_unlock(loop);
195 pw_thread_loop_stop(loop);
196 BOOST_LOG(debug) << "[pipewire] Destroy PW thread loop"sv;
197 pw_thread_loop_destroy(loop);
198 }
199
205 std::mutex &frame_mutex() {
206 return stream_data.frame_mutex;
207 }
208
214 std::condition_variable &frame_cv() {
215 return stream_data.frame_cv;
216 }
217
223 bool is_frame_ready() const {
224 return stream_data.frame_ready;
225 }
226
234 bool is_pacing_required(AVRational requested_framerate, std::chrono::nanoseconds requested_delay) {
235 AVRational negotiated_rate =
236 {
237 static_cast<int32_t>(stream_data.format.info.raw.max_framerate.num),
238 static_cast<int32_t>(stream_data.format.info.raw.max_framerate.denom)
239 };
240 int rate_comparison = av_cmp_q(negotiated_rate, requested_framerate);
241 bool variable_rate = negotiated_rate.num == 0 && negotiated_rate.den == 1;
242 bool pacing_required = variable_rate || rate_comparison > 0;
243
244 if (!variable_rate && rate_comparison < 0) {
245 BOOST_LOG(warning)
246 << "[pipewire] Sunshine frame pacing: disabled (negotiated rate lower than requested rate)"sv;
247 } else {
248 BOOST_LOG(info) << "[pipewire] Sunshine frame pacing: "sv
249 << (pacing_required ? std::format("enabled ({}ms)", std::chrono::duration<double, std::milli>(requested_delay).count()) : "disabled (event-driven capture)");
250 }
251
252 return pacing_required;
253 }
254
260 void set_frame_ready(bool ready) {
261 stream_data.frame_ready = ready;
262 }
263
273 int init(const int stream_fd, const uint32_t stream_node, const uint64_t stream_object_serial, std::shared_ptr<shared_state_t> shared_state) {
274 fd = stream_fd;
275 node = stream_node;
276 object_serial = stream_object_serial;
277 stream_data.shared = std::move(shared_state);
278
279 pw_thread_loop_lock(loop);
280 BOOST_LOG(debug) << "[pipewire] Setup PW context"sv;
281 context = pw_context_new(pw_thread_loop_get_loop(loop), nullptr, 0);
282 if (context) {
283 BOOST_LOG(debug) << "[pipewire] Connect PW context to fd"sv;
284 if (fd >= 0) {
285 core = pw_context_connect_fd(context, fd, nullptr, 0);
286 } else {
287 core = pw_context_connect(context, nullptr, 0);
288 }
289 if (core) {
290 pw_core_add_listener(core, &core_listener, &core_events, nullptr);
291 } else {
292 BOOST_LOG(debug) << "[pipewire] Failed to connect to PW core. Error: "sv << errno << "(" << strerror(errno) << ")"sv;
293 return -1;
294 }
295 } else {
296 BOOST_LOG(debug) << "[pipewire] Failed to setup PW context. Error: "sv << errno << "(" << strerror(errno) << ")"sv;
297 return -1;
298 }
299
300 pw_thread_loop_unlock(loop);
301 return 0;
302 }
303
316 int ensure_stream(const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const AVRational target_framerate, const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia) {
317 pw_thread_loop_lock(loop);
318 int result = 0;
319 if (!stream_data.stream) {
320 if (!core) {
321 BOOST_LOG(debug) << "[pipewire] PW core not available. Cannot ensure stream."sv;
322 pw_thread_loop_unlock(loop);
323 return -1;
324 }
325
326 struct pw_properties *props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Screen", nullptr);
327
328 BOOST_LOG(debug) << "[pipewire] Create PW stream"sv;
329 stream_data.stream = pw_stream_new(core, "Sunshine Video Capture", props);
330 pw_stream_add_listener(stream_data.stream, &stream_data.stream_listener, &stream_events, &stream_data);
331
332 std::array<uint8_t, SPA_POD_BUFFER_SIZE> buffer;
333 struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
334
335 int n_params = 0;
336 std::array<const struct spa_pod *, MAX_PARAMS> params;
337
338 // Add preferred parameters for DMA-BUF with modifiers
339 // Use DMA-BUF for VAAPI, or for CUDA when the display GPU is NVIDIA (pure NVIDIA system).
340 // On hybrid GPU systems (Intel+NVIDIA), DMA-BUFs come from the Intel GPU and cannot
341 // be imported into CUDA, so we fall back to memory buffers in that case.
342 bool use_dmabuf = n_dmabuf_infos > 0 && (mem_type == platf::mem_type_e::vaapi ||
343 mem_type == platf::mem_type_e::vulkan ||
344 (mem_type == platf::mem_type_e::cuda && display_is_nvidia));
345 if (use_dmabuf) {
346 for (int i = 0; i < n_dmabuf_infos; i++) {
347 auto format_param = build_format_parameter(&pod_builder, width, height, target_framerate, dmabuf_infos[i].format, dmabuf_infos[i].modifiers, dmabuf_infos[i].n_modifiers);
348 params[n_params] = format_param;
349 n_params++;
350 }
351 }
352
353 // Add fallback for memptr
354 for (const auto &fmt : format_map) {
355 auto format_param = build_format_parameter(&pod_builder, width, height, target_framerate, fmt.pw_format, nullptr, 0);
356 params[n_params] = format_param;
357 n_params++;
358 }
359
360 // Connection via pipewire object serial if it is supported and the serial is valid (lower 32-bits != SPA_ID_INVALID, see also PW_KEY_OBJECT_SERIAL docs)
361 if (SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL && (object_serial & SPA_ID_INVALID) != SPA_ID_INVALID) {
362 pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%" PRIu64, object_serial);
363 BOOST_LOG(debug) << "[pipewire] Connect PW stream - fd: "sv << fd << " object serial: "sv << object_serial;
364 result = pw_stream_connect(stream_data.stream, PW_DIRECTION_INPUT, PW_ID_ANY, (enum pw_stream_flags)(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS), params.data(), n_params);
365 if (result < 0) {
366 // Unset object serial for retry with node id
367 pw_properties_set(props, PW_KEY_TARGET_OBJECT, nullptr);
368 }
369 } else {
370 result = -1; // Mark failed so we try to connect via node id
371 }
372 // Connection via legacy (and deprecated) pipewire node id
373 if (result < 0) {
374 BOOST_LOG(debug) << "[pipewire] Connect PW stream - fd: "sv << fd << " node: "sv << node;
375 result = pw_stream_connect(stream_data.stream, PW_DIRECTION_INPUT, node, (enum pw_stream_flags)(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS), params.data(), n_params);
376 }
377 }
378
379 pw_thread_loop_unlock(loop);
380 return result;
381 }
382
388 static void close_img_fds(egl::img_descriptor_t *img_descriptor) {
389 for (int &fd : img_descriptor->sd.fds) {
390 if (fd >= 0) {
391 close(fd);
392 fd = -1;
393 }
394 }
395 }
396
400 enum class compositor_type_e {
401 unknown,
402 gnome,
403 kwin
404 };
405
413
420 using enum compositor_type_e;
421
422 if (auto version = get_running_compositor_version(kwin); !version.empty()) {
423 return {kwin, std::move(version)};
424 }
425
426 if (auto version = get_running_compositor_version(gnome); !version.empty()) {
427 return {gnome, std::move(version)};
428 }
429
430 return {};
431 }
432
439 static std::vector<int> get_running_compositor_version(enum compositor_type_e compositor_type) {
440#if !GLIB_CHECK_VERSION(2, 74, 0)
441 // Compatibility for Ubuntu 22.04 (Glib 2.72)
442 constexpr auto G_REGEX_DEFAULT = static_cast<GRegexCompileFlags>(0);
443 constexpr auto G_REGEX_MATCH_DEFAULT = static_cast<GRegexMatchFlags>(0);
444#endif
445 auto conn = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
446 std::vector<int> result;
447
448 if (!conn) {
449 return result;
450 }
451
452 const gchar *version_regex;
453 GVariant *reply;
454
455 using enum compositor_type_e;
456 if (compositor_type == kwin) {
457 version_regex = "KWin version: ([0-9]+)\\.([0-9]+)\\.([0-9]+)";
458 reply = g_dbus_connection_call_sync(
459 conn,
460 "org.kde.KWin",
461 "/KWin",
462 "org.kde.KWin",
463 "supportInformation",
464 nullptr,
465 G_VARIANT_TYPE("(s)"),
466 G_DBUS_CALL_FLAGS_NONE,
467 -1,
468 nullptr,
469 nullptr
470 );
471 } else {
472 version_regex = "([0-9]+)\\.([0-9]+)(?:\\.([0-9]+))?";
473 reply = g_dbus_connection_call_sync(
474 conn,
475 "org.gnome.Shell",
476 "/org/gnome/Shell",
477 "org.freedesktop.DBus.Properties",
478 "Get",
479 g_variant_new("(ss)", "org.gnome.Shell", "ShellVersion"),
480 G_VARIANT_TYPE("(v)"),
481 G_DBUS_CALL_FLAGS_NONE,
482 -1,
483 nullptr,
484 nullptr
485 );
486 }
487
488 if (!reply) {
489 g_clear_object(&conn);
490 return result;
491 }
492
493 g_autofree gchar *version_str = nullptr;
494
495 if (compositor_type == kwin) {
496 g_variant_get(reply, "(s)", &version_str);
497 } else {
498 GVariant *inner_variant = nullptr;
499 g_variant_get(reply, "(v)", &inner_variant);
500 if (inner_variant) {
501 version_str = g_variant_dup_string(inner_variant, nullptr);
502 g_variant_unref(inner_variant);
503 }
504 }
505
506 if (!version_str) {
507 g_variant_unref(reply);
508 g_clear_object(&conn);
509 return result;
510 }
511
512 if (auto *regex = g_regex_new(version_regex, G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, nullptr); regex) {
513 GMatchInfo *match_info = nullptr;
514 g_regex_match(regex, version_str, G_REGEX_MATCH_DEFAULT, &match_info);
515
516 if (g_match_info_matches(match_info)) {
517 g_autofree const gchar *major = g_match_info_fetch(match_info, 1);
518 g_autofree const gchar *minor = g_match_info_fetch(match_info, 2);
519 g_autofree const gchar *micro = g_match_info_fetch(match_info, 3);
520
521 if (major) {
522 result.emplace_back(std::atoi(major));
523 }
524 if (minor) {
525 result.emplace_back(std::atoi(minor));
526 }
527 if (micro && *micro != '\0') {
528 result.emplace_back(std::atoi(micro));
529 }
530 }
531 g_match_info_free(match_info);
532 g_regex_unref(regex);
533 }
534
535 g_variant_unref(reply);
536 g_clear_object(&conn);
537 return result;
538 }
539
547 static bool use_pipewire_pts(const compositor_version_t &compositor, const std::string &selected_display_name) {
548 // KWin: use Pipewire pts metadata for versions 6.7.80+ (6.8 beta) or newer.
549 // Mutter: use Pipewire pts metadata for Mutter 51 onwards, but pts is reliable only for virtual monitors (Meta-).
550 // All other cases: don't use Pipewire pts metadata directly.
551
552 bool use_pts = false;
553 bool using_virtual_monitor = false;
554
555 using enum compositor_type_e;
556 switch (compositor.type) {
557 case gnome:
558 using_virtual_monitor = selected_display_name.starts_with("Meta-");
559 use_pts = (using_virtual_monitor && compositor.version[0] >= 51);
560 break;
561 case kwin:
562 use_pts = (compositor.version[0] > 6 || (compositor.version[0] == 6 && (compositor.version[1] > 7 || (compositor.version[1] == 7 && compositor.version[2] > 79))));
563 break;
564 default:
565 break;
566 }
567
568 BOOST_LOG(info) << "[pipewire] Using frame_timestamp (pts) metadata source: "sv
569 << (use_pts ? "Pipewire (via compositor)" : PROJECT_NAME);
570 return use_pts;
571 }
572
579 static bool
581 // If the active compositor is KWin, request variable rate (0, 1) capture for versions 5.x-6.7.79 (up to 6.7 stable series).
582 // Issue: KWin <=6.7 has a ~3% fixed-rate pacing deficit vs the requested framerate; variable rate avoids this and prioritizes gaming smoothness.
583 // KWin 6.7 regresses variable rate (desktop animations run at half speed, but doesn't affect in-game pacing). Ref: https://bugs.kde.org/show_bug.cgi?id=524129
584 // Issue: KWin 6.8 still has desktop animation pacing issues with variable rate, but fixes the 3% fixed-rate pacing deficit. Fixed-rate pacing has
585 // new regression tied to 'commit-timing'/VK_KHR_present_timing support when Vsync/FIFO is enabled. Ref: https://bugs.kde.org/show_bug.cgi?id=525619
586 // Summary: KWin 5.5-6.6 have excellent (variable) pacing. KWin 6.7 has poor desktop animation pacing (variable) but good game pacing.
587 // KWin 6.8+ will have good overall (fixed) pacing if #525619 can be resolved, otherwise we will update docs advising to disable VSync in games.
588
589 // All other compositors (including Mutter) will default to variable rate.
590 bool variable_rate = true;
591
592 using enum compositor_type_e;
593 if (compositor.type == kwin) {
594 variable_rate = (compositor.version[0] == 5 || (compositor.version[0] == 6 && (compositor.version[1] < 7 || (compositor.version[1] == 7 && compositor.version[2] < 80))));
595 }
596
597 return variable_rate;
598 }
599
606 static void fill_img_metadata(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf) {
607 struct spa_meta_header *h = static_cast<struct spa_meta_header *>(
608 spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h))
609 );
610
611 img_descriptor->seq.reset();
612 img_descriptor->pts.reset();
613 if (h) {
614 img_descriptor->seq = h->seq;
615 if (h->pts > 0) {
616 img_descriptor->pts = h->pts;
617 }
618 }
619 img_descriptor->frame_timestamp = (img_descriptor->pts.has_value() && prefer_pipewire_pts) ?
620 std::chrono::steady_clock::time_point(std::chrono::nanoseconds(img_descriptor->pts.value())) :
621 std::chrono::steady_clock::now();
622
623 if (buf->n_datas > 0) {
624 img_descriptor->pw_flags = buf->datas[0].chunk->flags;
625 }
626
627 struct spa_meta_region *damage = static_cast<struct spa_meta_region *>(
628 spa_buffer_find_meta_data(buf, SPA_META_VideoDamage, sizeof(*damage))
629 );
630 img_descriptor->pw_damage = (damage && damage->region.size.width > 0 && damage->region.size.height > 0) ? std::optional<bool>(true) : std::nullopt;
631 }
632
640 static void fill_img_dmabuf(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf, const stream_data_t &d) {
641 img_descriptor->sd.width = d.format.info.raw.size.width;
642 img_descriptor->sd.height = d.format.info.raw.size.height;
643 img_descriptor->sd.modifier = d.format.info.raw.modifier;
644 img_descriptor->sd.fourcc = d.drm_format;
645 for (int i = 0; i < MIN(buf->n_datas, 4); i++) {
646 img_descriptor->sd.fds[i] = dup(buf->datas[i].fd);
647 img_descriptor->sd.pitches[i] = buf->datas[i].chunk->stride;
648 img_descriptor->sd.offsets[i] = buf->datas[i].chunk->offset;
649 }
650 }
651
658 pw_thread_loop_lock(loop);
659 std::scoped_lock lock(stream_data.frame_mutex);
660
661 if (stream_data.shared && stream_data.shared->stream_dead.load()) {
662 img->data = nullptr;
663 close_img_fds(static_cast<egl::img_descriptor_t *>(img));
664 pw_thread_loop_unlock(loop);
665 return;
666 }
667
668 if (!stream_data.current_buffer) {
669 img->data = nullptr;
670 pw_thread_loop_unlock(loop);
671 return;
672 }
673
674 struct spa_buffer *buf = stream_data.current_buffer->buffer;
675 if (buf->datas[0].chunk->size != 0) {
676 auto *img_descriptor = static_cast<img_descriptor_t *>(img);
677 fill_img_metadata(img_descriptor, buf);
678 if (buf->datas[0].type == SPA_DATA_DmaBuf) {
679 fill_img_dmabuf(img_descriptor, buf, stream_data);
680 } else {
681 img->data = stream_data.front_buffer->data();
682 img_descriptor->data_owned = false;
683 img->row_pitch = stream_data.local_stride;
684 // NV12 is the only 1-byte-per-pixel format delivered on the memory
685 // path; every other negotiated format is packed 4 bytes per pixel.
686 img->pixel_pitch = (stream_data.format.info.raw.format == SPA_VIDEO_FORMAT_NV12) ? 1 : 4;
687 }
688 }
689
690 pw_thread_loop_unlock(loop);
691 }
692
698 void set_negotiate_maxframerate(bool negotiate_maxframerate) {
699 negotiate_maxframerate_ = negotiate_maxframerate;
700 }
701
702 inline static std::atomic prefer_pipewire_pts {false};
703 inline static std::atomic negotiate_variable_rate {true};
704
705 private:
706 struct pw_thread_loop *loop;
707 struct pw_context *context;
708 struct pw_core *core;
709 struct spa_hook core_listener;
710 struct stream_data_t stream_data;
711 int fd;
712 uint32_t node;
713 uint64_t object_serial;
714 bool negotiate_maxframerate_ = true;
715
716 struct spa_pod *build_format_parameter(struct spa_pod_builder *b, uint32_t width, uint32_t height, AVRational target_framerate, int32_t format, uint64_t *modifiers, int n_modifiers) {
717 struct spa_pod_frame object_frame;
718 struct spa_pod_frame modifier_frame;
719 std::array<struct spa_rectangle, 3> sizes;
720 std::array<struct spa_fraction, 3> framerates;
721
722 sizes[0] = SPA_RECTANGLE(width, height); // Preferred
723 sizes[1] = SPA_RECTANGLE(1, 1);
724 sizes[2] = SPA_RECTANGLE(8192, 4096);
725
726 framerates[0] = SPA_FRACTION(uint32_t(target_framerate.num), uint32_t(target_framerate.den)); // default/preferred
727 framerates[1] = SPA_FRACTION(0, 1); // min
728 framerates[2] = SPA_FRACTION(1000, 1); // max
729
730 spa_pod_builder_push_object(b, &object_frame, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
731 spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0);
732 spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
733 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
734 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&sizes[0], &sizes[1], &sizes[2]), 0);
735 if (negotiate_maxframerate_) {
736 // Always request variable rate (0, 1) for framerate when populating maxFramerate with default,min,max values
737 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerates[1]), 0);
738 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&framerates[0], &framerates[1], &framerates[2]), 0);
739 } else {
740 // Request target framerate (target_framerate) for framerate in fallback case
741 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&framerates[0]), 0);
742 }
743
744 if (format == SPA_VIDEO_FORMAT_xBGR_210LE) {
745 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_colorPrimaries, SPA_POD_Id(SPA_VIDEO_COLOR_PRIMARIES_BT2020), 0);
746 spa_pod_builder_add(b, SPA_FORMAT_VIDEO_transferFunction, SPA_POD_Id(SPA_VIDEO_TRANSFER_SMPTE2084), 0);
747 }
748
749 if (n_modifiers) {
750 spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
751 spa_pod_builder_push_choice(b, &modifier_frame, SPA_CHOICE_Enum, 0);
752
753 // Preferred value, we pick the first modifier be the preferred one
754 spa_pod_builder_long(b, modifiers[0]);
755 for (uint32_t i = 0; i < n_modifiers; i++) {
756 spa_pod_builder_long(b, modifiers[i]);
757 }
758
759 spa_pod_builder_pop(b, &modifier_frame);
760 }
761
762 return static_cast<struct spa_pod *>(spa_pod_builder_pop(b, &object_frame));
763 }
764
765 static void on_core_info_cb([[maybe_unused]] void *user_data, const struct pw_core_info *pw_info) { // NOSONAR(cpp:S5008): PipeWire event callbacks require a void pointer for user data.
766 BOOST_LOG(info) << "[pipewire] Connected to pipewire version "sv << pw_info->version;
767 }
768
769 static void on_core_error_cb([[maybe_unused]] void *user_data, const uint32_t id, const int seq, [[maybe_unused]] int res, const char *message) { // NOSONAR(cpp:S5008): PipeWire event callbacks require a void pointer for user data.
770 BOOST_LOG(info) << "[pipewire] Pipewire Error, id:"sv << id << " seq:"sv << seq << " message: "sv << message;
771 }
772
773 constexpr static const struct pw_core_events core_events = {
774 .version = PW_VERSION_CORE_EVENTS,
775 .info = on_core_info_cb,
776 .error = on_core_error_cb,
777 };
778
779 static void on_stream_state_changed(void *user_data, enum pw_stream_state old, enum pw_stream_state state, const char *err_msg) { // NOSONAR(cpp:S5008): PipeWire event callbacks require a void pointer for user data.
780 if (err_msg != nullptr) {
781 BOOST_LOG(info) << "[pipewire] PipeWire stream error '" << err_msg << "' on state: " << pw_stream_state_as_string(old)
782 << " -> " << pw_stream_state_as_string(state);
783 } else {
784 BOOST_LOG(info) << "[pipewire] PipeWire stream state: " << pw_stream_state_as_string(old)
785 << " -> " << pw_stream_state_as_string(state);
786 }
787
788 auto *d = static_cast<stream_data_t *>(user_data);
789
790 switch (state) {
791 case PW_STREAM_STATE_PAUSED:
792 if (d->shared && old == PW_STREAM_STATE_STREAMING) {
793 {
794 std::scoped_lock lock(d->frame_mutex);
795 d->frame_ready = false;
796 d->current_buffer = nullptr;
797 d->shared->stream_dead.store(true);
798 d->shared->current_state = state;
799 d->shared->previous_state = old;
800 d->shared->err_msg = "";
801 }
802 d->frame_cv.notify_all();
803 }
804 break;
805 case PW_STREAM_STATE_ERROR:
806 {
807 std::scoped_lock lock(d->frame_mutex);
808 d->shared->current_state = state;
809 d->shared->previous_state = old;
810 d->shared->err_msg = std::string(err_msg);
811 }
812 [[fallthrough]];
813 case PW_STREAM_STATE_UNCONNECTED:
814 if (d->shared) {
815 d->shared->stream_dead.store(true);
816 d->frame_cv.notify_all();
817 }
818 break;
819 default:
820 break;
821 }
822 }
823
824 static void on_process(void *user_data) { // NOSONAR(cpp:S5008): PipeWire event callbacks require a void pointer for user data.
825 const auto d = static_cast<struct stream_data_t *>(user_data);
826 struct pw_buffer *b = nullptr;
827
828 // 1. Drain the queue: Always grab the most recent buffer
829 while (struct pw_buffer *aux = pw_stream_dequeue_buffer(d->stream)) {
830 if (b) {
831 pw_stream_queue_buffer(d->stream, b); // Return the older, unused buffer
832 }
833 b = aux;
834 }
835
836 if (!b) {
837 return;
838 }
839
840 // 2. Fast Path: DMA-BUF
841 if (b->buffer->datas[0].type == SPA_DATA_DmaBuf) {
842 std::scoped_lock lock(d->frame_mutex);
843 if (d->current_buffer) {
844 pw_stream_queue_buffer(d->stream, d->current_buffer);
845 }
846 d->current_buffer = b;
847 d->frame_ready = true;
848 }
849 // 3. Optimized Path: Software/MemPtr
850 else if (b->buffer->datas[0].data != nullptr) {
851 size_t size = b->buffer->datas[0].chunk->size;
852
853 // Perform the copy to the BACK buffer while NOT holding the lock
854 if (d->back_buffer->size() < size) {
855 d->back_buffer->resize(size);
856 }
857 std::memcpy(d->back_buffer->data(), b->buffer->datas[0].data, size);
858
859 {
860 // Lock only for the pointer swap and state update
861 std::scoped_lock lock(d->frame_mutex);
862 std::swap(d->front_buffer, d->back_buffer);
863
864 d->local_stride = b->buffer->datas[0].chunk->stride;
865 d->frame_ready = true;
866 d->current_buffer = b;
867 }
868
869 // Release the PW buffer immediately after copy
870 pw_stream_queue_buffer(d->stream, b);
871 }
872
873 d->frame_cv.notify_one();
874 }
875
876 static void on_param_changed(void *user_data, uint32_t id, const struct spa_pod *param) { // NOSONAR(cpp:S5008): PipeWire event callbacks require a void pointer for user data.
877 const auto d = static_cast<struct stream_data_t *>(user_data);
878
879 d->current_buffer = nullptr;
880
881 if (param == nullptr || id != SPA_PARAM_Format) {
882 return;
883 }
884 if (spa_format_parse(param, &d->format.media_type, &d->format.media_subtype) < 0) {
885 return;
886 }
887 if (d->format.media_type != SPA_MEDIA_TYPE_video || d->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) {
888 return;
889 }
890 if (spa_format_video_raw_parse(param, &d->format.info.raw) < 0) {
891 return;
892 }
893
894 BOOST_LOG(info) << "[pipewire] Video format: "sv << d->format.info.raw.format;
895 BOOST_LOG(info) << "[pipewire] Size: "sv << d->format.info.raw.size.width << "x"sv << d->format.info.raw.size.height;
896 BOOST_LOG(info) << "[pipewire] Color primaries: "sv << d->format.info.raw.color_primaries;
897 BOOST_LOG(info) << "[pipewire] Transfer function: "sv << d->format.info.raw.transfer_function;
898 if (d->format.info.raw.max_framerate.num == 0 && d->format.info.raw.max_framerate.denom == 1) {
899 BOOST_LOG(info) << "[pipewire] Compositor negotiated frame rate: 0/1 (variable rate capture)"sv;
900 } else {
901 BOOST_LOG(info) << "[pipewire] Compositor negotiated frame rate: "sv
902 << d->format.info.raw.framerate.num << "/"sv << d->format.info.raw.framerate.denom
903 << ", max: "sv << d->format.info.raw.max_framerate.num << "/"sv << d->format.info.raw.max_framerate.denom;
904 }
905
906 int physical_w = d->format.info.raw.size.width;
907 int physical_h = d->format.info.raw.size.height;
908
909 if (d->shared) {
910 int old_w = d->shared->negotiated_width.load();
911 int old_h = d->shared->negotiated_height.load();
912 int old_color_primaries = d->shared->color_primaries.load();
913 int old_transfer_function = d->shared->transfer_function.load();
914
915 if (physical_w != old_w || physical_h != old_h) {
916 d->shared->negotiated_width.store(physical_w);
917 d->shared->negotiated_height.store(physical_h);
918 }
919
920 if (d->format.info.raw.color_primaries != old_color_primaries || d->format.info.raw.transfer_function != old_transfer_function) {
921 d->shared->color_primaries.store(d->format.info.raw.color_primaries);
922 d->shared->transfer_function.store(d->format.info.raw.transfer_function);
923 }
924 }
925
926 uint64_t drm_format = 0;
927 for (const auto &fmt : format_map) {
928 if (fmt.pw_format == d->format.info.raw.format) {
929 drm_format = fmt.fourcc;
930 }
931 }
932 d->drm_format = drm_format;
933
934 uint32_t buffer_types = 0;
935 if (spa_pod_find_prop(param, nullptr, SPA_FORMAT_VIDEO_modifier) != nullptr && d->drm_format) {
936 BOOST_LOG(info) << "[pipewire] using DMA-BUF buffers"sv;
937 buffer_types |= 1 << SPA_DATA_DmaBuf;
938 } else {
939 BOOST_LOG(info) << "[pipewire] using memory buffers"sv;
940 buffer_types |= 1 << SPA_DATA_MemPtr;
941 }
942
943 // Ack the buffer type and metadata
944 std::array<uint8_t, SPA_POD_BUFFER_SIZE> buffer;
945 std::array<const struct spa_pod *, 3> params;
946 int n_params = 0;
947 struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
948 auto buffer_param = static_cast<const struct spa_pod *>(spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_dataType, SPA_POD_Int(buffer_types)));
949 params[n_params] = buffer_param;
950 n_params++;
951 auto meta_param = static_cast<const struct spa_pod *>(spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))));
952 params[n_params] = meta_param;
953 n_params++;
954 int videoDamageRegionCount = 16;
955 auto damage_param = static_cast<const struct spa_pod *>(spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int(sizeof(struct spa_meta_region) * videoDamageRegionCount, sizeof(struct spa_meta_region) * 1, sizeof(struct spa_meta_region) * videoDamageRegionCount)));
956 params[n_params] = damage_param;
957 n_params++;
958
959 pw_stream_update_params(d->stream, params.data(), n_params);
960 }
961
962 constexpr static const struct pw_stream_events stream_events = {
963 .version = PW_VERSION_STREAM_EVENTS,
964 .state_changed = on_stream_state_changed,
965 .param_changed = on_param_changed,
966 .process = on_process,
967 };
968 };
969
974 public:
982 // Initialize pipewire to load necessary modules
983 pw_init(nullptr, nullptr);
984
985 // Check if we have a matching hwdevice_type
986 switch (hwdevice_type) {
987 using enum platf::mem_type_e;
988 case system:
989 case vaapi:
990 case cuda:
991 case vulkan:
992 return true;
993 default:
994 return false;
995 }
996 }
997
1006 virtual int configure_stream(const std::string &display_name, int &out_pipewire_fd, uint32_t &out_pipewire_node, uint64_t &out_pipewire_objectserial) = 0;
1007
1012 // Query outputs directly using wayland wl::monitors()
1013 if (logical_height <= 0 || logical_width <= 0 || env_logical_height <= 0 || env_logical_width <= 0 || env_height <= 0 || env_width <= 0) {
1014 int desktop_width = 0;
1015 int desktop_height = 0;
1016 int desktop_logical_width = 0;
1017 int desktop_logical_height = 0;
1018 for (const auto &monitor : wl::monitors()) {
1019 BOOST_LOG(debug) << "[pipewire] Found output: '"sv << monitor->name << "' offset: "sv << monitor->viewport.offset_x << 'x' << monitor->viewport.offset_y << " resolution: "sv << monitor->viewport.width << 'x' << monitor->viewport.height << " logical resolution: "sv << monitor->viewport.logical_width << 'x' << monitor->viewport.logical_height;
1020 // If logical_width and logical_height are not valid try to update them to correct values by matching to monitor
1021 // position/dimension or position/logical dimensions here since we're iterating for maximum environment size anyway
1022 if ((logical_width <= 0 || logical_height <= 0) && monitor->viewport.offset_x == offset_x && monitor->viewport.offset_y == offset_y && ((monitor->viewport.width == width && monitor->viewport.height == height) || (monitor->viewport.logical_width == width && monitor->viewport.logical_height == height))) {
1023 this->logical_width = monitor->viewport.logical_width;
1024 this->logical_height = monitor->viewport.logical_height;
1025 BOOST_LOG(debug) << "[pipewire] Set logical resolution: "sv << logical_width << 'x' << logical_height;
1026 }
1027 // Update desktop dimensions to setup maximum environment size over all screens
1028 desktop_width = std::max(desktop_width, monitor->viewport.offset_x + monitor->viewport.width);
1029 desktop_height = std::max(desktop_height, monitor->viewport.offset_y + monitor->viewport.height);
1030 // Update desktop logical dimensions to setup maximum logical environment size over all screens
1031 desktop_logical_width = std::max(desktop_logical_width, monitor->viewport.offset_x + monitor->viewport.logical_width);
1032 desktop_logical_height = std::max(desktop_logical_height, monitor->viewport.offset_y + monitor->viewport.logical_height);
1033 }
1034 if (env_height <= 0 || env_width <= 0) {
1035 this->env_width = desktop_width;
1036 this->env_height = desktop_height;
1037 BOOST_LOG(debug) << "[pipewire] Set desktop resolution: "sv << env_width << 'x' << env_height;
1038 }
1039 if (env_logical_height <= 0 || env_logical_width <= 0) {
1040 this->env_logical_width = desktop_logical_width;
1041 this->env_logical_height = desktop_logical_height;
1042 BOOST_LOG(debug) << "[pipewire] Set desktop logical resolution: "sv << env_logical_width << 'x' << env_logical_height;
1043 }
1044 }
1045 }
1046
1055 int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config) {
1056 // calculate frame interval we should capture at
1057 delay = ::video::capture_frame_interval(config);
1058
1059 const static auto compositor = pipewire.get_running_compositor();
1060
1061 // Determine if variable rate should be negotiated based on compositor type/versioning.
1063
1064 const AVRational fps = (pipewire.negotiate_variable_rate ? AVRational {0, 1} : ::video::framerate_to_rational(config));
1065 if (fps.den != 1) {
1066 BOOST_LOG(info) << "[pipewire] Requested frame rate: "sv << fps.num << "/"sv << fps.den << ", approx. "sv << av_q2d(fps) << " fps"sv;
1067 } else if (fps.num == 0 && fps.den == 1) {
1068 BOOST_LOG(info) << "[pipewire] Requested variable frame rate (Sunshine pacing required: "sv << std::chrono::duration<double, std::milli>(delay).count() << "ms)"sv;
1069 } else {
1070 BOOST_LOG(info) << "[pipewire] Requested frame rate: "sv << fps.num << "fps"sv;
1071 }
1072 this->target_framerate = fps;
1073 mem_type = hwdevice_type;
1074
1075 if (get_dmabuf_modifiers() < 0) {
1076 return -1;
1077 }
1078
1079 int pipewire_fd = -1;
1080 auto pipewire_node = PW_ID_ANY; // Default for invalid stream from pipewire docs
1081 uint64_t pipewire_object_serial = SPA_ID_INVALID; // Default for invalid stream from pipewire docs for PW_KEY_OBJECT_SERIAL
1082 // Fetch stream info
1083 if (configure_stream(display_name, pipewire_fd, pipewire_node, pipewire_object_serial) < 0 || (pipewire_node == PW_ID_ANY && (pipewire_object_serial & SPA_ID_INVALID) == SPA_ID_INVALID)) {
1084 BOOST_LOG(error) << "[pipewire] Could not find display with name: '"sv << display_name << "'";
1085 return -1;
1086 }
1087 BOOST_LOG(info) << "[pipewire] Streaming display '"sv << display_name << "' offset: "sv << offset_x << "x"sv << offset_y << " resolution: "sv << width << "x"sv << height;
1088
1089 // Verify or update display parameters for streaming to ensure absolute touch inputs work as expected
1091
1092 // Determine if pts metadata should be sourced from Pipewire or sampled by Sunshine at time of capture.
1093 pipewire.prefer_pipewire_pts = pipewire.use_pipewire_pts(compositor, display_name);
1094
1095 if (!shared_state) {
1096 shared_state = std::make_shared<shared_state_t>();
1097 } else {
1098 shared_state->stream_dead.store(false);
1099 shared_state->negotiated_width.store(0);
1100 shared_state->negotiated_height.store(0);
1101 shared_state->color_primaries.store(0);
1102 shared_state->transfer_function.store(0);
1103 }
1104
1105 if (pipewire.init(pipewire_fd, pipewire_node, pipewire_object_serial, shared_state) < 0) {
1106 BOOST_LOG(error) << "[pipewire] Failed to init pipewire. pipewire_t::init() failed.";
1107 return -1;
1108 }
1109
1110 // Start PipeWire now so format negotiation can proceed before capture start
1111 if (pipewire.ensure_stream(mem_type, width, height, target_framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia) < 0) {
1112 BOOST_LOG(error) << "[pipewire] Failed to ensure pipewire stream. pipewire_t::init() failed.";
1113 return -1;
1114 }
1115
1116 // Wait for pipewire negotiation to finish so we have the proper negotiated dimensions
1117 int timeout_ms = 1500;
1118 int negotiated_w = 0;
1119 int negotiated_h = 0;
1120 while (timeout_ms > 0) {
1121 negotiated_w = shared_state->negotiated_width.load();
1122 negotiated_h = shared_state->negotiated_height.load();
1123 if (negotiated_w > 0 && negotiated_h > 0) {
1124 break;
1125 }
1126 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1127 timeout_ms -= 10;
1128 }
1129 // Set width and height to the values negotiated by pipewire
1130 if (negotiated_w > 0 && negotiated_h > 0 && (negotiated_w != width || negotiated_h != height)) {
1131 width = negotiated_w;
1132 height = negotiated_h;
1133 BOOST_LOG(info) << "[pipewire] Using negotiated Resolution: "sv << width << "x" << height;
1134
1135 // Reset and update display parameters for negotiated resolution
1136 env_width = 0;
1137 env_height = 0;
1138 logical_height = 0;
1139 logical_width = 0;
1143 }
1144
1145 return 0;
1146 }
1147
1157 platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr<platf::img_t> &img_out, std::chrono::milliseconds timeout, bool show_cursor) {
1158 // FIXME: show_cursor is ignored
1159 auto deadline = std::chrono::steady_clock::now() + timeout;
1160 int retries = 0;
1161
1162 while (std::chrono::steady_clock::now() < deadline) {
1163 if (!wait_for_frame(deadline)) {
1165 }
1166
1167 if (!pull_free_image_cb(img_out)) {
1169 }
1170
1171 auto *img_egl = static_cast<egl::img_descriptor_t *>(img_out.get());
1172 img_egl->reset();
1173 pipewire.fill_img(img_egl);
1174
1175 // Check if we got valid data (either DMA-BUF fd or memory pointer), then filter duplicates
1176 if ((img_egl->sd.fds[0] >= 0 || img_egl->data != nullptr) && !is_buffer_redundant(img_egl)) {
1177 // Update frame metadata
1178 update_metadata(img_egl, retries);
1179 return platf::capture_e::ok;
1180 }
1181
1182 // No valid frame yet, or it was a duplicate
1183 retries++;
1184 }
1186 }
1187
1193 std::shared_ptr<platf::img_t> alloc_img() override {
1194 // Note: this img_t type is also used for memory buffers
1195 auto img = std::make_shared<img_descriptor_t>();
1196
1197 img->width = width;
1198 img->height = height;
1199 img->pixel_pitch = 4;
1200 img->row_pitch = img->pixel_pitch * width;
1201 img->sequence = 0;
1202 img->serial = std::numeric_limits<decltype(img->serial)>::max();
1203 img->data = nullptr;
1204 img->data_owned = false;
1205 img->sd.fds.fill(-1);
1206
1207 return img;
1208 }
1209
1216 virtual bool check_stream_dead(platf::capture_e &out_status) {
1217 return false; // Return to default stream dead handling.
1218 }
1219
1228 platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override {
1229 using enum platf::capture_e;
1230
1231 auto next_frame = std::chrono::steady_clock::now();
1232
1233 if (pipewire.ensure_stream(mem_type, width, height, target_framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia) < 0) {
1234 BOOST_LOG(error) << "[pipewire] Failed to ensure pipewire stream. capture() failed with error.";
1235 return capture_error;
1236 }
1238
1239 // Check if pacing is required
1240 bool pacing_required = pipewire.is_pacing_required(target_framerate, delay);
1241
1242 while (true) {
1243 // Check if PipeWire signaled a dead stream
1244 if (shared_state->stream_dead.exchange(false)) {
1245 // Additional custom error-handling for subclasses on stream dead event
1246 if (platf::capture_e status; check_stream_dead(status)) {
1247 return status;
1248 }
1249 // Re-init the capture if the stream is dead for any other reason
1250 BOOST_LOG(warning) << "[pipewire] PipeWire stream disconnected. Forcing session reset."sv;
1251 return reinit;
1252 }
1253
1254 // Use unpaced event driven capture when possible
1255 if (pacing_required) {
1256 platf::handle_pacing(next_frame, delay, sleep_overshoot_logger);
1257 }
1258
1259 std::shared_ptr<platf::img_t> img_out;
1260 switch (const auto status = snapshot(pull_free_image_cb, img_out, 1000ms, *cursor)) {
1261 case reinit:
1262 case capture_error:
1263 case interrupted:
1264 pipewire.frame_cv().notify_all();
1265 return status;
1266 case timeout:
1267 if (!pull_free_image_cb(img_out)) {
1268 // Detect if shutdown is pending
1269 BOOST_LOG(debug) << "[pipewire] PipeWire: timeout -> shutdown pending -> interrupt nudge";
1270 pipewire.frame_cv().notify_all();
1271 return interrupted;
1272 }
1273 if (!push_captured_image_cb(std::move(img_out), false)) {
1274 BOOST_LOG(debug) << "[pipewire] PipeWire: timeout -> !push_captured_image_cb -> ok";
1275 return ok;
1276 }
1277 break;
1278 case ok:
1279 if (!push_captured_image_cb(std::move(img_out), true)) {
1280 BOOST_LOG(debug) << "[pipewire] PipeWire: ok -> !push_captured_image_cb -> ok";
1281 return ok;
1282 }
1283 break;
1284 default:
1285 BOOST_LOG(error) << "[pipewire] Unrecognized capture status ["sv << std::to_underlying(status) << ']';
1286 return status;
1287 }
1288 }
1289
1290 return ok;
1291 }
1292
1299 std::unique_ptr<platf::avcodec_encode_device_t> make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override {
1300#ifdef SUNSHINE_BUILD_VAAPI
1301 if (mem_type == platf::mem_type_e::vaapi) {
1302 return va::make_avcodec_encode_device(width, height, n_dmabuf_infos > 0);
1303 }
1304#endif
1305
1306#ifdef SUNSHINE_BUILD_VULKAN
1307 if (mem_type == platf::mem_type_e::vulkan && n_dmabuf_infos > 0) {
1309 }
1310#endif
1311
1312#ifdef SUNSHINE_BUILD_CUDA
1313 if (mem_type == platf::mem_type_e::cuda) {
1314 if (display_is_nvidia && n_dmabuf_infos > 0) {
1315 // Display GPU is NVIDIA - can use DMA-BUF directly
1317 } else {
1318 // Hybrid system (Intel display + NVIDIA encode) - use memory buffer path
1319 // DMA-BUFs from Intel GPU cannot be imported into CUDA
1321 }
1322 }
1323#endif
1324
1325 return std::make_unique<platf::avcodec_encode_device_t>();
1326 }
1327
1334 int dummy_img(platf::img_t *img) override {
1335 // Software encoders convert the dummy image immediately; provide a valid
1336 // (black) buffer instead of leaving img->data null, which makes sws fail
1337 // with EINVAL. The buffer is new[]-allocated and marked as owned so the
1338 // destructor releases it.
1339 if (img->data == nullptr) {
1340 const auto w = img->width;
1341 const auto h = img->height;
1342 if (w > 0 && h > 0) {
1343 img->data = new uint8_t[static_cast<size_t>(w) * h * 4](); // NOSONAR(cpp:S5025) - buffer is owned by the image and freed by img_descriptor_t's destructor
1344 static_cast<img_descriptor_t *>(img)->data_owned = true;
1345 img->row_pitch = w * 4;
1346 img->pixel_pitch = 4;
1347 }
1348 }
1349 return 0;
1350 }
1351
1357 bool is_hdr() override {
1358 int color_primaries = shared_state->color_primaries.load();
1359 int transfer_function = shared_state->transfer_function.load();
1360
1361 if (color_primaries == SPA_VIDEO_COLOR_PRIMARIES_BT2020 && transfer_function == SPA_VIDEO_TRANSFER_SMPTE2084) {
1362 return true;
1363 }
1364
1365 return false;
1366 }
1367
1374 bool get_hdr_metadata(SS_HDR_METADATA &metadata) override {
1375 int color_primaries = shared_state->color_primaries.load();
1376 int transfer_function = shared_state->transfer_function.load();
1377
1378 if (color_primaries == SPA_VIDEO_COLOR_PRIMARIES_BT2020 && transfer_function == SPA_VIDEO_TRANSFER_SMPTE2084) {
1379 // Report Rec 2020 primaries
1380 metadata.displayPrimaries[0].x = 0.708f * 50000;
1381 metadata.displayPrimaries[0].y = 0.292f * 50000;
1382 metadata.displayPrimaries[1].x = 0.170f * 50000;
1383 metadata.displayPrimaries[1].y = 0.797f * 50000;
1384 metadata.displayPrimaries[2].x = 0.131f * 50000;
1385 metadata.displayPrimaries[2].y = 0.046f * 50000;
1386 metadata.whitePoint.x = 0.3127f * 50000;
1387 metadata.whitePoint.y = 0.3290f * 50000;
1388
1389 // This is according to HDR10+ standards, should probably be based on actual data
1390 metadata.maxDisplayLuminance = 4000;
1391 metadata.minDisplayLuminance = 1;
1392
1393 // These are content-specific metadata parameters that this interface doesn't give us
1394 metadata.maxContentLightLevel = 0;
1395 metadata.maxFrameAverageLightLevel = 0;
1396 metadata.maxFullFrameLuminance = 0;
1397
1398 return true;
1399 }
1400
1401 return false;
1402 }
1403
1404 private:
1405 bool is_buffer_redundant(const egl::img_descriptor_t *img) {
1406 // Check for corrupted frame
1407 if (img->pw_flags.has_value() && (img->pw_flags.value() & SPA_CHUNK_FLAG_CORRUPTED)) {
1408 return true;
1409 }
1410
1411 // If PTS is identical, only drop if damage metadata confirms no change
1412 if (img->pts.has_value() && last_pts.has_value() && img->pts.value() == last_pts.value()) {
1413 return img->pw_damage.has_value() && !img->pw_damage.value();
1414 }
1415
1416 return false;
1417 }
1418
1419 void update_metadata(egl::img_descriptor_t *img, int retries) {
1420 last_seq = img->seq;
1421 last_pts = img->pts;
1422 img->sequence = ++sequence;
1423
1424 if (retries > 0) {
1425 BOOST_LOG(debug) << "[pipewire] Processed frame after " << retries << " redundant events."sv;
1426 }
1427 }
1428
1429 bool wait_for_frame(std::chrono::steady_clock::time_point deadline) {
1430 std::unique_lock<std::mutex> lock(pipewire.frame_mutex());
1431
1432 bool success = pipewire.frame_cv().wait_until(lock, deadline, [&] {
1433 return pipewire.is_frame_ready() || shared_state->stream_dead.load();
1434 });
1435
1436 if (success) {
1438 return true;
1439 }
1440 return false;
1441 }
1442
1443 static bool pw_format_supported(uint64_t fourcc, std::array<EGLint, MAX_DMABUF_FORMATS> dmabuf_formats) {
1444 for (const auto &drm_format : dmabuf_formats) {
1445 if (drm_format == fourcc) {
1446 return true;
1447 }
1448 }
1449 return false;
1450 }
1451
1452 void query_dmabuf_formats(EGLDisplay egl_display) {
1453 EGLint num_dmabuf_formats = 0;
1454 std::array<EGLint, MAX_DMABUF_FORMATS> dmabuf_formats = {0};
1455 eglQueryDmaBufFormatsEXT(egl_display, MAX_DMABUF_FORMATS, dmabuf_formats.data(), &num_dmabuf_formats);
1456
1457 if (num_dmabuf_formats > MAX_DMABUF_FORMATS) {
1458 BOOST_LOG(warning) << "[pipewire] Some DMA-BUF formats are being ignored"sv;
1459 }
1460
1461 for (const auto &fmt : format_map) {
1462 if (n_dmabuf_infos >= MAX_DMABUF_FORMATS) {
1463 break;
1464 }
1465
1466 if (!pw_format_supported(fmt.fourcc, dmabuf_formats)) {
1467 continue;
1468 }
1469
1470 EGLint num_modifiers = 0;
1471 std::array<EGLuint64KHR, MAX_DMABUF_MODIFIERS> mods = {0};
1472 eglQueryDmaBufModifiersEXT(egl_display, fmt.fourcc, MAX_DMABUF_MODIFIERS, mods.data(), nullptr, &num_modifiers);
1473
1474 if (num_modifiers > MAX_DMABUF_MODIFIERS) {
1475 BOOST_LOG(warning) << "[pipewire] Some DMA-BUF modifiers are being ignored"sv;
1476 }
1477
1478 dmabuf_infos[n_dmabuf_infos].format = fmt.pw_format;
1479 dmabuf_infos[n_dmabuf_infos].n_modifiers = MIN(num_modifiers, MAX_DMABUF_MODIFIERS);
1480 dmabuf_infos[n_dmabuf_infos].modifiers =
1481 static_cast<uint64_t *>(g_memdup2(mods.data(), sizeof(uint64_t) * dmabuf_infos[n_dmabuf_infos].n_modifiers));
1482 ++n_dmabuf_infos;
1483 }
1484 }
1485
1486 int get_dmabuf_modifiers() {
1487 if (wl_display.init() < 0) {
1488 return -1;
1489 }
1490
1491 auto egl_display = egl::make_display(wl_display.get());
1492 if (!egl_display) {
1493 return -1;
1494 }
1495
1496 // Detect if this is a pure NVIDIA system (not hybrid Intel+NVIDIA)
1497 // On hybrid systems, the wayland compositor typically runs on Intel,
1498 // so DMA-BUFs from portal will come from Intel and cannot be imported into CUDA.
1499 // Check if Intel GPU exists - if so, assume hybrid system and disable CUDA DMA-BUF.
1500 bool has_intel_gpu = std::ifstream("/sys/class/drm/card0/device/vendor").good() ||
1501 std::ifstream("/sys/class/drm/card1/device/vendor").good();
1502 if (has_intel_gpu) {
1503 // Read vendor IDs to check for Intel (0x8086)
1504 auto check_intel = [](const std::string &path) {
1505 if (std::ifstream f(path); f.good()) {
1506 std::string vendor;
1507 f >> vendor;
1508 return vendor == "0x8086";
1509 }
1510 return false;
1511 };
1512 bool intel_present = check_intel("/sys/class/drm/card0/device/vendor") ||
1513 check_intel("/sys/class/drm/card1/device/vendor");
1514 if (intel_present) {
1515 BOOST_LOG(info) << "[pipewire] Hybrid GPU system detected (Intel + discrete) - CUDA will use memory buffers"sv;
1516 display_is_nvidia = false;
1517 } else {
1518 // No Intel GPU found, check if NVIDIA is present
1519 const char *vendor = eglQueryString(egl_display.get(), EGL_VENDOR);
1520 if (vendor && std::string_view(vendor).contains("NVIDIA")) {
1521 BOOST_LOG(info) << "[pipewire] Pure NVIDIA system - DMA-BUF will be enabled for CUDA"sv;
1522 display_is_nvidia = true;
1523 }
1524 }
1525 }
1526
1527 if (eglQueryDmaBufFormatsEXT && eglQueryDmaBufModifiersEXT) {
1528 query_dmabuf_formats(egl_display.get());
1529 }
1530
1531 return 0;
1532 }
1533
1534 platf::mem_type_e mem_type;
1535 wl::display_t wl_display;
1536 std::array<struct dmabuf_format_info_t, MAX_DMABUF_FORMATS> dmabuf_infos;
1537 int n_dmabuf_infos;
1538 bool display_is_nvidia = false; // Track if display GPU is NVIDIA
1539 std::chrono::nanoseconds delay;
1540 std::optional<std::uint64_t> last_pts {};
1541 std::optional<std::uint64_t> last_seq {};
1542 std::uint64_t sequence {};
1543 AVRational target_framerate;
1544
1545 protected:
1546 // Allow subclasses to access for pipewire requirements setup and stream dead checks
1548 std::shared_ptr<shared_state_t> shared_state;
1549 };
1550} // namespace pipewire
Captured image descriptor shared by EGL conversion paths.
Definition graphics.h:609
std::optional< bool > pw_damage
Whether PipeWire damage tracking should be used.
Definition graphics.h:639
std::optional< uint32_t > pw_flags
PipeWire frame flags reported with the buffer.
Definition graphics.h:640
std::optional< uint64_t > seq
PipeWire frame sequence number.
Definition graphics.h:638
std::uint64_t sequence
Monotonic value used to detect when GL resources must be recreated.
Definition graphics.h:631
surface_descriptor_t sd
DMA-BUF surface descriptor for the captured image.
Definition graphics.h:628
void reset()
Reset the object to its initial empty state.
Definition graphics.h:618
std::optional< uint64_t > pts
PipeWire presentation timestamp.
Definition graphics.h:637
void reset()
Reset the object to its initial empty state.
Definition logging.h:309
Display capture backend that consumes frames from a PipeWire stream.
Definition pipewire.cpp:973
bool is_hdr() override
Report whether the active display mode is HDR.
Definition pipewire.cpp:1357
virtual void verify_and_update_display_parameters()
Verify and update display parameters for logical dimensions, desktop dimensions and logical desktop d...
Definition pipewire.cpp:1011
std::shared_ptr< platf::img_t > alloc_img() override
Allocate an image buffer compatible with this display backend.
Definition pipewire.cpp:1193
platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr< platf::img_t > &img_out, std::chrono::milliseconds timeout, bool show_cursor)
Capture a display frame into the provided image object.
Definition pipewire.cpp:1157
platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override
Capture PipeWire frames until capture stops or the stream needs reinitialization.
Definition pipewire.cpp:1228
bool get_hdr_metadata(SS_HDR_METADATA &metadata) override
Read HDR metadata for the active display mode.
Definition pipewire.cpp:1374
pipewire_t pipewire
Pipewire.
Definition pipewire.cpp:1547
int dummy_img(platf::img_t *img) override
Populate a fallback image when real capture data is unavailable.
Definition pipewire.cpp:1334
std::shared_ptr< shared_state_t > shared_state
Shared state.
Definition pipewire.cpp:1548
virtual bool check_stream_dead(platf::capture_e &out_status)
Check stream dead.
Definition pipewire.cpp:1216
static bool init_pipewire_and_check_hwdevice_type(platf::mem_type_e hwdevice_type)
Initialize pipewire and check hwdevice type.
Definition pipewire.cpp:981
std::unique_ptr< platf::avcodec_encode_device_t > make_avcodec_encode_device(platf::pix_fmt_e pix_fmt) override
Create AVCodec encode device.
Definition pipewire.cpp:1299
int init(platf::mem_type_e hwdevice_type, const std::string &display_name, const ::video::config_t &config)
Initialize the PipeWire display backend for a selected stream.
Definition pipewire.cpp:1055
virtual int configure_stream(const std::string &display_name, int &out_pipewire_fd, uint32_t &out_pipewire_node, uint64_t &out_pipewire_objectserial)=0
Configure the pipewire stream.
PipeWire core, context, and stream setup used for screencast capture.
Definition pipewire.cpp:147
static bool use_pipewire_pts(const compositor_version_t &compositor, const std::string &selected_display_name)
Determine if Pipewire's pts metadata is suitable for client pacing based on compositor type/version w...
Definition pipewire.cpp:547
static std::atomic prefer_pipewire_pts
Whether the session should directly passthrough Pipewire pts metadata for client pacing.
Definition pipewire.cpp:702
void fill_img(platf::img_t *img)
Copy the latest PipeWire frame into Sunshine's image buffer.
Definition pipewire.cpp:657
bool is_pacing_required(AVRational requested_framerate, std::chrono::nanoseconds requested_delay)
Check and log whether the active session will require Sunshine to perform pacing.
Definition pipewire.cpp:234
std::condition_variable & frame_cv()
Return the condition variable signaled when frame state changes.
Definition pipewire.cpp:214
static compositor_version_t get_running_compositor()
Fetch compositor type and version for processing.
Definition pipewire.cpp:419
compositor_type_e
Compositors that require version-based checks for optimal stream setup.
Definition pipewire.cpp:400
@ unknown
Unknown.
Definition pipewire.cpp:401
bool is_frame_ready() const
Check whether frame ready.
Definition pipewire.cpp:223
static std::vector< int > get_running_compositor_version(enum compositor_type_e compositor_type)
Fetch compositor version information using DBus calls.
Definition pipewire.cpp:439
static void fill_img_dmabuf(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf, const stream_data_t &d)
Populate a Sunshine image descriptor from PipeWire DMA-BUF planes.
Definition pipewire.cpp:640
void set_negotiate_maxframerate(bool negotiate_maxframerate)
Set negotiate maxframerate.
Definition pipewire.cpp:698
void set_frame_ready(bool ready)
Set frame ready.
Definition pipewire.cpp:260
int ensure_stream(const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const AVRational target_framerate, const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia)
Create the PipeWire stream if it is not already active.
Definition pipewire.cpp:316
static bool use_variable_rate(const compositor_version_t &compositor)
Determine if the active compositor is suited for variable rate capture based on type/version whitelis...
Definition pipewire.cpp:580
std::mutex & frame_mutex()
Return the mutex protecting PipeWire frame state.
Definition pipewire.cpp:205
int init(const int stream_fd, const uint32_t stream_node, const uint64_t stream_object_serial, std::shared_ptr< shared_state_t > shared_state)
Initialize PipeWire core objects and optional stream negotiation.
Definition pipewire.cpp:273
static std::atomic negotiate_variable_rate
Whether the session should request variable rate (maxFrameRate = 0/1) capture.
Definition pipewire.cpp:703
static void fill_img_metadata(egl::img_descriptor_t *img_descriptor, struct spa_buffer *buf)
Copy PipeWire metadata into the Sunshine image descriptor.
Definition pipewire.cpp:606
static void close_img_fds(egl::img_descriptor_t *img_descriptor)
Close img fds.
Definition pipewire.cpp:388
Abstract display capture backend used by the streaming pipeline.
Definition common.h:694
std::function< bool(std::shared_ptr< img_t > &&img, bool frame_captured)> push_captured_image_cb_t
Callback for when a new image is ready. When display has a new image ready or a timeout occurs,...
Definition common.h:703
int env_height
Height of the full capture environment in physical pixels.
Definition common.h:803
int env_logical_height
Height of the full capture environment after display scaling.
Definition common.h:805
std::function< bool(std::shared_ptr< img_t > &img_out)> pull_free_image_cb_t
Get free image from pool. Calls must be synchronized. Blocks until there is free image in the pool or...
Definition common.h:712
logging::time_delta_periodic_logger sleep_overshoot_logger
Periodic logger for capture sleep overshoot measurements.
Definition common.h:813
int offset_y
Vertical capture offset in physical pixels.
Definition common.h:799
int width
Width of the captured display in physical pixels.
Definition common.h:806
int logical_height
Height of the captured display after display scaling.
Definition common.h:809
int env_width
Width of the full capture environment in physical pixels.
Definition common.h:802
int height
Height of the captured display in physical pixels.
Definition common.h:807
int offset_x
Horizontal capture offset in physical pixels.
Definition common.h:798
int env_logical_width
Width of the full capture environment after display scaling.
Definition common.h:804
int logical_width
Width of the captured display after display scaling.
Definition common.h:808
Declarations for common platform specific utilities.
mem_type_e
Enumerates supported mem type options.
Definition common.h:329
@ cuda
CUDA.
Definition common.h:333
@ vulkan
Vulkan.
Definition common.h:335
@ vaapi
VAAPI.
Definition common.h:331
pix_fmt_e
Enumerates supported pix fmt options.
Definition common.h:342
capture_e
Enumerates supported capture options.
Definition common.h:683
@ ok
Success.
Definition common.h:684
@ timeout
Timeout.
Definition common.h:686
@ interrupted
Capture was interrupted.
Definition common.h:687
std::unique_ptr< platf::avcodec_encode_device_t > make_avcodec_encode_device(int width, int height, bool vram)
Create AVCodec encode device.
Definition cuda.cpp:655
std::unique_ptr< platf::avcodec_encode_device_t > make_avcodec_gl_encode_device(int width, int height, int offset_x, int offset_y)
Create a GL->CUDA encoding device for consuming captured dmabufs.
Definition cuda.cpp:683
Definitions for CUDA implementation.
display_t make_display(std::variant< gbm::gbm_t::pointer, wl_display *, _XDisplay * > native_display)
Open and initialize the display connection used for capture.
Definition graphics.cpp:495
Declarations for graphics related functions.
int close(int __fd)
Release the native resource held by the RAII wrapper.
void handle_pacing(std::chrono::steady_clock::time_point &next_frame, std::chrono::nanoseconds delay, auto &logger)
Generic frame pacing logic for Linux capture methods.
Definition misc.h:152
bl::sources::severity_logger< int > debug
Follow what is happening.
Definition logging.cpp:43
bl::sources::severity_logger< int > error
Recoverable errors.
Definition logging.cpp:46
bl::sources::severity_logger< int > info
Should be informed about.
Definition logging.cpp:44
bl::sources::severity_logger< int > warning
Strange events.
Definition logging.cpp:45
Declarations for the main entry point for Sunshine.
constexpr int SPA_VIDEO_TRANSFER_SMPTE2084
Protocol or platform constant for spa video transfer smpte2084.
Definition pipewire.cpp:28
constexpr bool SUNSHINE_USE_PIPEWIRE_OBJECT_SERIAL
Whether PipeWire object serials should be used for matching.
Definition pipewire.cpp:39
#define PW_KEY_TARGET_OBJECT
Macro for PW KEY TARGET OBJECT.
Definition pipewire.cpp:44
state_e state(session_t &session)
Platform handle returned from stream setup.
Definition stream.cpp:2200
std::array< std::uint32_t, 4 > pitches
Row stride in bytes for each DMA-BUF plane.
Definition graphics.h:503
std::uint32_t fourcc
DRM fourcc pixel format for the buffer.
Definition graphics.h:501
std::array< std::uint32_t, 4 > offsets
Byte offset to the first pixel for each DMA-BUF plane.
Definition graphics.h:504
std::uint64_t modifier
DRM format modifier describing the buffer layout.
Definition graphics.h:502
std::array< int, 4 > fds
DMA-BUF file descriptors for up to four planes.
Definition graphics.h:500
int height
Frame or display height in pixels.
Definition graphics.h:499
int width
Frame or display width in pixels.
Definition graphics.h:498
DMA-BUF format and modifier list advertised by PipeWire.
Definition pipewire.cpp:120
int32_t format
PipeWire SPA video format being advertised.
Definition pipewire.cpp:121
int n_modifiers
Number of entries in modifiers.
Definition pipewire.cpp:123
uint64_t * modifiers
DRM format modifiers supported for the format.
Definition pipewire.cpp:122
PipeWire SPA format mapped to Sunshine pixel format.
Definition pipewire.cpp:61
uint64_t fourcc
DRM fourcc pixel format.
Definition pipewire.cpp:62
int32_t pw_format
Matching PipeWire SPA video format.
Definition pipewire.cpp:63
Pipewire image assembled for encoding.
Definition pipewire.cpp:129
bool data_owned
Whether img->data is owned by this image and must be freed.
Definition pipewire.cpp:141
Compositor type and version information.
Definition pipewire.cpp:409
compositor_type_e type
Compositor type (gnome, kde...).
Definition pipewire.cpp:410
std::vector< int > version
Compositor version.
Definition pipewire.cpp:411
PipeWire capture state shared with callback threads.
Definition pipewire.cpp:79
std::atomic< int > negotiated_height
Height negotiated with PipeWire for the stream.
Definition pipewire.cpp:81
std::atomic< bool > stream_dead
Whether the PipeWire stream has been destroyed.
Definition pipewire.cpp:84
std::atomic< int > color_primaries
PipeWire color-primaries metadata for the stream.
Definition pipewire.cpp:82
pw_stream_state previous_state
Previous PipeWire stream state reported by callbacks.
Definition pipewire.cpp:85
std::atomic< int > transfer_function
PipeWire transfer-function metadata for the stream.
Definition pipewire.cpp:83
pw_stream_state current_state
Current PipeWire stream state reported by callbacks.
Definition pipewire.cpp:86
std::atomic< int > negotiated_width
Width negotiated with PipeWire for the stream.
Definition pipewire.cpp:80
std::string err_msg
Last PipeWire error message reported by the stream.
Definition pipewire.cpp:87
PipeWire stream handle, format, and shared state pointer.
Definition pipewire.cpp:93
struct spa_hook stream_listener
Hook registering callbacks on the PipeWire stream.
Definition pipewire.cpp:95
std::condition_variable frame_cv
Signals arrival or release of a PipeWire frame.
Definition pipewire.cpp:101
std::mutex frame_mutex
Synchronizes access to the current PipeWire frame.
Definition pipewire.cpp:100
struct spa_video_info format
Negotiated PipeWire video format.
Definition pipewire.cpp:96
std::shared_ptr< shared_state_t > shared
State shared between PipeWire callbacks and the capture backend.
Definition pipewire.cpp:99
struct pw_stream * stream
PipeWire stream handle used for screencast frames.
Definition pipewire.cpp:94
std::vector< uint8_t > buffer_a
First staging buffer used for CPU-copy PipeWire frames.
Definition pipewire.cpp:105
std::vector< uint8_t > buffer_b
Second staging buffer used for CPU-copy PipeWire frames.
Definition pipewire.cpp:106
std::vector< uint8_t > * front_buffer
Staging buffer currently readable by fill_img.
Definition pipewire.cpp:108
std::vector< uint8_t > * back_buffer
Staging buffer currently writable by PipeWire callbacks.
Definition pipewire.cpp:110
uint64_t drm_format
DRM format.
Definition pipewire.cpp:98
size_t local_stride
Local stride.
Definition pipewire.cpp:102
struct pw_buffer * current_buffer
PipeWire buffer currently exposed to the capture thread.
Definition pipewire.cpp:97
bool frame_ready
Whether a PipeWire frame is ready to consume.
Definition pipewire.cpp:103
Captured frame buffer shared between capture and encode stages.
Definition common.h:530
std::int32_t row_pitch
Bytes between consecutive image rows.
Definition common.h:543
std::uint8_t * data
Pointer to the captured image buffer.
Definition common.h:539
std::int32_t pixel_pitch
Bytes per pixel in the image buffer.
Definition common.h:542
std::int32_t height
Image height in pixels.
Definition common.h:541
std::int32_t width
Image width in pixels.
Definition common.h:540
std::optional< std::chrono::steady_clock::time_point > frame_timestamp
Capture timestamp associated with the frame.
Definition common.h:545
auto lock(const std::weak_ptr< void > &wp)
Acquire the underlying lock or keyed mutex.
Definition thread_safe.h:794
std::unique_ptr< platf::avcodec_encode_device_t > make_avcodec_encode_device(int width, int height, file_t &&card, int offset_x, int offset_y, bool vram)
Create AVCodec encode device.
Definition vaapi.cpp:798
Declarations for VA-API hardware accelerated capture.
Declarations for video.
std::chrono::nanoseconds capture_frame_interval(const config_t &config)
Capture frame interval for the requested framerate. Uses the exact fractional rate when the client pr...
Definition video.h:781
AVRational framerate_to_rational(const config_t &config)
Requested framerate as an exact rational. Uses the exact fractional rate when the client provided an ...
Definition video.h:767
std::unique_ptr< platf::avcodec_encode_device_t > make_avcodec_encode_device_vram(int w, int h, int offset_x, int offset_y)
Create a Vulkan encode device for VRAM capture.
Definition vulkan_encode.cpp:1427
Declarations for FFmpeg Vulkan Video encoder.
std::vector< std::unique_ptr< monitor_t > > monitors(const char *display_name)
Refresh the monitor list reported by the display server.
Definition wayland.cpp:687
Declarations for Wayland capture.