17 #include <condition_variable>
27 #include <gio/gunixfdlist.h>
28 #include <pipewire/pipewire.h>
29 #include <spa/buffer/buffer.h>
30 #include <spa/buffer/meta.h>
31 #include <spa/param/buffers.h>
32 #include <spa/pod/builder.h>
33 #include <spa/param/video/format-utils.h>
44 const char* PORTAL_BUS =
"org.freedesktop.portal.Desktop";
45 const char* PORTAL_PATH =
"/org/freedesktop/portal/desktop";
46 const char* SCREENCAST_IFACE =
"org.freedesktop.portal.ScreenCast";
48 std::string gerror_message(GError* error)
51 return "unknown error";
53 std::string message = error->message ? error->message :
"unknown error";
58 std::string token_for(
const char* prefix)
60 static uint64_t counter = 0;
61 std::stringstream token;
62 token << prefix <<
"_" << ++counter;
69 bool timed_out =
false;
71 GVariant* results =
nullptr;
72 GMainLoop* loop =
nullptr;
77 g_variant_unref(results);
82 void portal_response_callback(
91 auto* response =
static_cast<PortalResponse*
>(user_data);
92 GVariant* results =
nullptr;
93 g_variant_get(parameters,
"(u@a{sv})", &response->code, &results);
94 response->results = results;
95 response->done =
true;
97 g_main_loop_quit(response->loop);
101 gboolean portal_response_timeout(gpointer user_data)
103 auto* response =
static_cast<PortalResponse*
>(user_data);
104 response->timed_out =
true;
105 if (response->loop) {
106 g_main_loop_quit(response->loop);
108 return G_SOURCE_REMOVE;
111 GVariant* wait_for_portal_response(GDBusConnection* connection,
const std::string& handle)
113 PortalResponse response;
114 response.loop = g_main_loop_new(
nullptr, FALSE);
115 const guint timeout = g_timeout_add_seconds(60, portal_response_timeout, &response);
116 const guint subscription = g_dbus_connection_signal_subscribe(
119 "org.freedesktop.portal.Request",
123 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
124 portal_response_callback,
128 g_main_loop_run(response.loop);
129 g_dbus_connection_signal_unsubscribe(connection, subscription);
130 if (!response.timed_out) {
131 g_source_remove(timeout);
133 g_main_loop_unref(response.loop);
134 response.loop =
nullptr;
136 if (response.timed_out) {
137 throw InvalidOptions(
"Timed out waiting for Wayland screen capture permission.");
139 if (!response.done || response.code != 0) {
140 throw InvalidOptions(
"Wayland screen capture permission was denied or cancelled.");
142 GVariant* results = response.results;
143 response.results =
nullptr;
147 std::string call_portal_request(
148 GDBusConnection* connection,
150 GVariant* parameters)
152 GError* error =
nullptr;
153 GVariant* result = g_dbus_connection_call_sync(
160 G_VARIANT_TYPE(
"(o)"),
161 G_DBUS_CALL_FLAGS_NONE,
166 throw InvalidOptions(
"Wayland portal " + std::string(method) +
" failed: " + gerror_message(error));
169 const char* handle =
nullptr;
170 g_variant_get(result,
"(&o)", &handle);
171 std::string handle_path = handle ? handle :
"";
172 g_variant_unref(result);
176 struct PortalStreamInfo
178 uint32_t node_id = 0;
179 uint64_t pipewire_serial = 0;
180 uint32_t source_type = 0;
185 PortalStreamInfo stream_info_from_results(GVariant* results)
187 GVariant* streams = g_variant_lookup_value(results,
"streams", G_VARIANT_TYPE(
"a(ua{sv})"));
188 if (!streams || g_variant_n_children(streams) == 0) {
190 g_variant_unref(streams);
192 throw InvalidOptions(
"Wayland portal did not return a PipeWire stream.");
195 PortalStreamInfo stream_info;
196 GVariant* properties =
nullptr;
197 GVariant* child = g_variant_get_child_value(streams, 0);
198 g_variant_get(child,
"(u@a{sv})", &stream_info.node_id, &properties);
201 uint32_t source_type = 0;
204 if (g_variant_lookup(properties,
"pipewire-serial",
"t", &serial)) {
205 stream_info.pipewire_serial = serial;
207 if (g_variant_lookup(properties,
"source_type",
"u", &source_type)) {
208 stream_info.source_type = source_type;
210 if (g_variant_lookup(properties,
"size",
"(ii)", &width, &height)) {
211 stream_info.width = width;
212 stream_info.height = height;
214 g_variant_unref(properties);
216 g_variant_unref(child);
217 g_variant_unref(streams);
221 int open_pipewire_remote(GDBusConnection* connection,
const std::string& session_handle)
223 GVariantBuilder options;
224 g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
226 GError* error =
nullptr;
227 GUnixFDList* out_fds =
nullptr;
228 GVariant* result = g_dbus_connection_call_with_unix_fd_list_sync(
233 "OpenPipeWireRemote",
234 g_variant_new(
"(oa{sv})", session_handle.c_str(), &options),
235 G_VARIANT_TYPE(
"(h)"),
236 G_DBUS_CALL_FLAGS_NONE,
243 throw InvalidOptions(
"Wayland portal OpenPipeWireRemote failed: " + gerror_message(error));
247 g_variant_get(result,
"(h)", &fd_index);
248 g_variant_unref(result);
249 int fd = g_unix_fd_list_get(out_fds, fd_index, &error);
250 g_object_unref(out_fds);
252 throw InvalidOptions(
"Unable to obtain PipeWire remote file descriptor: " + gerror_message(error));
261 std::vector<unsigned char> rgba;
269 : settings(new_settings)
271 , connection(nullptr)
272 , thread_loop(nullptr)
276 , session_closed_subscription(0)
281 , stream_error(false)
283 , have_last_header_sequence(false)
284 , last_header_sequence(0)
285 , header_sequence_ordering_active(false)
286 , have_last_header_pts(false)
288 , header_drop_log_count(0)
303 GError* error =
nullptr;
304 connection = g_bus_get_sync(G_BUS_TYPE_SESSION,
nullptr, &error);
306 throw InvalidOptions(
"Unable to connect to the session bus for Wayland capture: " + gerror_message(error));
309 session_handle = CreatePortalSession();
310 SubscribePortalSessionClosed();
311 SelectPortalSources(session_handle);
312 stream_info = StartPortalSession(session_handle);
313 ApplyPortalStreamInfo();
315 "Wayland portal stream selected: node_id=" + std::to_string(stream_info.node_id) +
316 " pipewire_serial=" + std::to_string(stream_info.pipewire_serial) +
317 " source_type=" + std::to_string(stream_info.source_type) +
318 " portal_size=" + std::to_string(stream_info.width) +
"x" + std::to_string(stream_info.height));
319 const int pipewire_fd = open_pipewire_remote(connection, session_handle);
320 OpenPipeWireStream(pipewire_fd);
330 pw_thread_loop_stop(thread_loop);
332 if (connection && session_closed_subscription) {
333 g_dbus_connection_signal_unsubscribe(connection, session_closed_subscription);
334 session_closed_subscription = 0;
336 if (connection && !session_handle.empty()) {
337 GError* error =
nullptr;
338 GVariant* result = g_dbus_connection_call_sync(
341 session_handle.c_str(),
342 "org.freedesktop.portal.Session",
346 G_DBUS_CALL_FLAGS_NONE,
351 g_variant_unref(result);
355 session_handle.clear();
358 pw_stream_destroy(stream);
362 pw_core_disconnect(core);
366 pw_context_destroy(context);
370 pw_thread_loop_destroy(thread_loop);
371 thread_loop =
nullptr;
374 g_object_unref(connection);
375 connection =
nullptr;
390 const double fps = settings.fps.den != 0 ?
static_cast<double>(settings.fps.num) /
static_cast<double>(settings.fps.den) : 0.0;
391 stats.
duration = fps > 0.0 ?
static_cast<double>(frames_read) / fps : 0.0;
395 std::shared_ptr<Frame>
GetFrame(int64_t number)
override
397 CapturedFrame captured;
399 std::unique_lock<std::mutex> lock(queue_mutex);
400 const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
401 while (frame_queue.empty() && !stream_error && open && std::chrono::steady_clock::now() < deadline) {
403 while (g_main_context_iteration(
nullptr, FALSE)) {
406 queue_condition.wait_for(lock, std::chrono::milliseconds(100));
408 if (frame_queue.empty() && !stream_error && open) {
409 throw InvalidFile(
"Timed out waiting for a Wayland capture frame.",
"wayland");
412 throw InvalidFile(
"Wayland capture stream failed.",
"wayland");
414 if (frame_queue.empty()) {
415 throw ReaderClosed(
"The Wayland screen capture stream is closed.");
417 captured = std::move(frame_queue.front());
418 frame_queue.pop_front();
421 const int bytes_per_pixel = 4;
422 const size_t buffer_size =
static_cast<size_t>(captured.width) * captured.height * bytes_per_pixel;
423 unsigned char* buffer =
static_cast<unsigned char*
>(malloc(buffer_size));
425 throw OutOfMemory(
"Unable to allocate Wayland capture frame buffer.",
"wayland");
427 std::memcpy(buffer, captured.rgba.data(), buffer_size);
429 auto frame = std::make_shared<Frame>(number, captured.width, captured.height,
"#000000");
430 frame->AddImage(captured.width, captured.height, bytes_per_pixel, QImage::Format_RGBA8888, buffer);
436 std::string CreatePortalSession()
438 GVariantBuilder options;
439 g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
440 g_variant_builder_add(&options,
"{sv}",
"handle_token", g_variant_new_string(token_for(
"openshot_create").c_str()));
441 g_variant_builder_add(&options,
"{sv}",
"session_handle_token", g_variant_new_string(token_for(
"openshot_session").c_str()));
443 const std::string handle = call_portal_request(connection,
"CreateSession", g_variant_new(
"(a{sv})", &options));
444 GVariant* results = wait_for_portal_response(connection, handle);
446 const char* session =
nullptr;
447 if (!g_variant_lookup(results,
"session_handle",
"&s", &session) || !session) {
448 g_variant_unref(results);
449 throw InvalidOptions(
"Wayland portal did not return a screencast session handle.");
451 std::string session_handle = session;
452 g_variant_unref(results);
453 return session_handle;
456 void SubscribePortalSessionClosed()
458 if (!connection || session_handle.empty()) {
461 session_closed_subscription = g_dbus_connection_signal_subscribe(
464 "org.freedesktop.portal.Session",
466 session_handle.c_str(),
468 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
469 OnPortalSessionClosed,
474 void SelectPortalSources(
const std::string& session_handle)
476 const uint32_t source_monitor = 1;
477 const uint32_t source_window = 2;
478 const uint32_t cursor_hidden = 1;
479 const uint32_t cursor_embedded = 2;
481 GVariantBuilder options;
482 g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
483 g_variant_builder_add(&options,
"{sv}",
"handle_token", g_variant_new_string(token_for(
"openshot_select").c_str()));
484 g_variant_builder_add(&options,
"{sv}",
"types", g_variant_new_uint32(source_monitor | source_window));
485 g_variant_builder_add(&options,
"{sv}",
"multiple", g_variant_new_boolean(FALSE));
486 g_variant_builder_add(&options,
"{sv}",
"cursor_mode", g_variant_new_uint32(settings.include_cursor ? cursor_embedded : cursor_hidden));
488 const std::string handle = call_portal_request(
491 g_variant_new(
"(oa{sv})", session_handle.c_str(), &options));
492 GVariant* results = wait_for_portal_response(connection, handle);
493 g_variant_unref(results);
496 PortalStreamInfo StartPortalSession(
const std::string& session_handle)
498 GVariantBuilder options;
499 g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
500 g_variant_builder_add(&options,
"{sv}",
"handle_token", g_variant_new_string(token_for(
"openshot_start").c_str()));
502 const std::string handle = call_portal_request(
505 g_variant_new(
"(osa{sv})", session_handle.c_str(),
"", &options));
506 GVariant* results = wait_for_portal_response(connection, handle);
507 const PortalStreamInfo result_stream_info = stream_info_from_results(results);
508 g_variant_unref(results);
509 return result_stream_info;
512 void ApplyPortalStreamInfo()
514 if (stream_info.width <= 0 || stream_info.height <= 0) {
517 info.width = stream_info.width;
518 info.height = stream_info.height;
519 info.display_ratio =
Fraction(stream_info.width, stream_info.height);
520 info.display_ratio.Reduce();
523 void OpenPipeWireStream(
int pipewire_fd)
525 pw_init(
nullptr,
nullptr);
527 thread_loop = pw_thread_loop_new(
"openshot-wayland-capture",
nullptr);
531 context = pw_context_new(pw_thread_loop_get_loop(thread_loop),
nullptr, 0);
535 core = pw_context_connect_fd(context, pipewire_fd,
nullptr, 0);
538 throw InvalidOptions(
"Unable to connect to the portal PipeWire remote.");
541 pw_properties* props = pw_properties_new(
542 PW_KEY_MEDIA_TYPE,
"Video",
543 PW_KEY_MEDIA_CATEGORY,
"Capture",
544 PW_KEY_MEDIA_ROLE,
"Screen",
546 if (stream_info.pipewire_serial > 0) {
547 pw_properties_set(props, PW_KEY_TARGET_OBJECT, std::to_string(stream_info.pipewire_serial).c_str());
549 stream = pw_stream_new(core,
"OpenShot Wayland Screen Capture", props);
551 throw InvalidOptions(
"Unable to create PipeWire capture stream.");
554 pw_stream_add_listener(stream, &stream_listener, &stream_events,
this);
556 if (pw_thread_loop_start(thread_loop) < 0) {
557 throw InvalidOptions(
"Unable to start PipeWire capture thread loop.");
560 uint8_t format_buffer[1024];
561 spa_pod_builder builder = SPA_POD_BUILDER_INIT(format_buffer,
sizeof(format_buffer));
562 const spa_fraction requested_framerate = SPA_FRACTION(
563 static_cast<uint32_t
>(std::max(1, settings.fps.num)),
564 static_cast<uint32_t
>(std::max(1, settings.fps.den)));
565 const spa_fraction variable_framerate = SPA_FRACTION(0, 1);
566 const spa_pod* params[1];
567 params[0] =
static_cast<const spa_pod*
>(spa_pod_builder_add_object(
569 SPA_TYPE_OBJECT_Format,
570 SPA_PARAM_EnumFormat,
571 SPA_FORMAT_mediaType,
572 SPA_POD_Id(SPA_MEDIA_TYPE_video),
573 SPA_FORMAT_mediaSubtype,
574 SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
575 SPA_FORMAT_VIDEO_format,
576 SPA_POD_CHOICE_ENUM_Id(
578 SPA_VIDEO_FORMAT_BGRx,
579 SPA_VIDEO_FORMAT_RGBA,
580 SPA_VIDEO_FORMAT_BGRA,
581 SPA_VIDEO_FORMAT_RGBx),
582 SPA_FORMAT_VIDEO_framerate,
583 SPA_POD_Fraction(&variable_framerate),
584 SPA_FORMAT_VIDEO_maxFramerate,
585 SPA_POD_Fraction(&requested_framerate)));
587 pw_thread_loop_lock(thread_loop);
588 const uint32_t target_id = stream_info.pipewire_serial > 0 ? PW_ID_ANY : stream_info.node_id;
589 const int result = pw_stream_connect(
593 static_cast<pw_stream_flags
>(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS),
597 pw_thread_loop_unlock(thread_loop);
598 throw InvalidOptions(
"Unable to connect to PipeWire capture stream.");
601 while (!streaming && !stream_error) {
602 if (pw_thread_loop_timed_wait(thread_loop, 10) < 0) {
607 pw_thread_loop_unlock(thread_loop);
610 throw InvalidOptions(
"PipeWire capture stream failed to start or timed out.");
614 static void OnPortalSessionClosed(
625 self->stream_error =
true;
626 if (self->thread_loop) {
627 pw_thread_loop_signal(self->thread_loop,
false);
629 self->queue_condition.notify_all();
632 static void OnStreamStateChanged(
void* data, pw_stream_state, pw_stream_state state,
const char*)
635 if (state == PW_STREAM_STATE_STREAMING) {
636 self->streaming =
true;
637 pw_thread_loop_signal(self->thread_loop,
false);
638 }
else if (state == PW_STREAM_STATE_ERROR || state == PW_STREAM_STATE_UNCONNECTED) {
639 self->stream_error =
true;
640 pw_thread_loop_signal(self->thread_loop,
false);
641 self->queue_condition.notify_all();
645 static void OnStreamParamChanged(
void* data, uint32_t
id,
const spa_pod* param)
647 if (
id != SPA_PARAM_Format || !param) {
651 spa_video_info info = {};
652 if (spa_format_parse(param, &info.media_type, &info.media_subtype) < 0 ||
653 info.media_type != SPA_MEDIA_TYPE_video ||
654 info.media_subtype != SPA_MEDIA_SUBTYPE_raw ||
655 spa_format_video_raw_parse(param, &info.info.raw) < 0) {
659 self->video_format = info.info.raw.format;
660 self->stream_width =
static_cast<int>(info.info.raw.size.width);
661 self->stream_height =
static_cast<int>(info.info.raw.size.height);
662 if (info.info.raw.framerate.num > 0 && info.info.raw.framerate.denom > 0) {
664 static_cast<int>(info.info.raw.framerate.num),
665 static_cast<int>(info.info.raw.framerate.denom));
666 self->info.video_timebase =
self->info.fps.Reciprocal();
668 if (self->stream_width > 0 && self->stream_height > 0) {
670 "Wayland PipeWire stream format: " +
671 std::to_string(self->stream_width) +
"x" + std::to_string(self->stream_height) +
672 " format=" + std::to_string(self->video_format) +
673 " framerate=" + std::to_string(info.info.raw.framerate.num) +
674 "/" + std::to_string(info.info.raw.framerate.denom) +
675 " max_framerate=" + std::to_string(info.info.raw.max_framerate.num) +
676 "/" + std::to_string(info.info.raw.max_framerate.denom));
677 self->info.width =
self->stream_width;
678 self->info.height =
self->stream_height;
679 self->info.display_ratio =
Fraction(self->stream_width, self->stream_height);
680 self->info.display_ratio.Reduce();
683 uint8_t params_buffer[256];
684 spa_pod_builder builder = SPA_POD_BUILDER_INIT(params_buffer,
sizeof(params_buffer));
685 const spa_pod* params[2];
686 params[0] =
static_cast<const spa_pod*
>(spa_pod_builder_add_object(
688 SPA_TYPE_OBJECT_ParamMeta,
691 SPA_POD_Id(SPA_META_Header),
693 SPA_POD_Int(
sizeof(spa_meta_header))));
694 params[1] =
static_cast<const spa_pod*
>(spa_pod_builder_add_object(
696 SPA_TYPE_OBJECT_ParamMeta,
699 SPA_POD_Id(SPA_META_VideoCrop),
701 SPA_POD_Int(
sizeof(spa_meta_region))));
702 pw_stream_update_params(self->stream, params, 2);
705 static void OnStreamProcess(
void* data)
708 pw_buffer* buffer = pw_stream_dequeue_buffer(self->stream);
713 self->CopyPipeWireBuffer(buffer);
714 pw_stream_queue_buffer(self->stream, buffer);
717 void CopyPipeWireBuffer(pw_buffer* buffer)
719 spa_buffer* spa_buffer = buffer->buffer;
720 if (!spa_buffer || spa_buffer->n_datas == 0 || !spa_buffer->datas[0].data || stream_width <= 0 || stream_height <= 0) {
725 auto* header =
static_cast<spa_meta_header*
>(
726 spa_buffer_find_meta_data(spa_buffer, SPA_META_Header,
sizeof(spa_meta_header)));
727 if (header && !AcceptHeader(*header)) {
732 spa_data& data = spa_buffer->datas[0];
733 const spa_chunk* chunk = data.chunk;
734 const uint8_t* src =
static_cast<const uint8_t*
>(data.data) + (chunk ? chunk->offset : 0);
735 const int stride = chunk && chunk->stride > 0 ? chunk->stride : stream_width * 4;
736 const int readable_rows = chunk && chunk->size > 0 ?
static_cast<int>(chunk->size) / stride : stream_height;
739 int crop_width = stream_width;
740 int crop_height = stream_height;
741 auto* crop =
static_cast<spa_meta_region*
>(
742 spa_buffer_find_meta_data(spa_buffer, SPA_META_VideoCrop,
sizeof(spa_meta_region)));
743 if (crop && spa_meta_region_is_valid(crop)) {
744 crop_x = std::max(0, crop->region.position.x);
745 crop_y = std::max(0, crop->region.position.y);
746 crop_width = std::min(
static_cast<int>(crop->region.size.width), stream_width - crop_x);
747 crop_height = std::min(
static_cast<int>(crop->region.size.height), stream_height - crop_y);
750 "Wayland PipeWire video crop: x=" + std::to_string(crop_x) +
751 " y=" + std::to_string(crop_y) +
752 " width=" + std::to_string(crop_width) +
753 " height=" + std::to_string(crop_height));
757 if (crop_width <= 0 || crop_height <= 0) {
761 if (crop_width % 2 != 0) {
764 if (crop_height % 2 != 0) {
767 if (crop_width <= 0 || crop_height <= 0) {
771 const int rows = std::min(crop_height, readable_rows - crop_y);
778 frame.width = crop_width;
779 frame.height = crop_height;
780 frame.rgba.assign(
static_cast<size_t>(frame.width) * frame.height * 4, 0);
782 for (
int y = 0; y < rows; ++y) {
783 const uint8_t* row = src +
static_cast<size_t>(y + crop_y) * stride;
784 for (
int x = 0; x < crop_width; ++x) {
785 const uint8_t* pixel = row +
static_cast<size_t>(x + crop_x) * 4;
790 switch (video_format) {
791 case SPA_VIDEO_FORMAT_RGBA:
792 r = pixel[0]; g = pixel[1]; b = pixel[2]; a = pixel[3];
794 case SPA_VIDEO_FORMAT_RGBx:
795 r = pixel[0]; g = pixel[1]; b = pixel[2];
797 case SPA_VIDEO_FORMAT_BGRA:
798 b = pixel[0]; g = pixel[1]; r = pixel[2]; a = pixel[3];
800 case SPA_VIDEO_FORMAT_BGRx:
802 b = pixel[0]; g = pixel[1]; r = pixel[2];
805 const size_t dst = (
static_cast<size_t>(y) * crop_width + x) * 4;
806 frame.rgba[dst + 0] = r;
807 frame.rgba[dst + 1] = g;
808 frame.rgba[dst + 2] = b;
809 frame.rgba[dst + 3] = a;
814 std::lock_guard<std::mutex> lock(queue_mutex);
815 if (frame_queue.size() > 3) {
816 frame_queue.pop_front();
819 frame_queue.push_back(std::move(frame));
821 if (info.width != crop_width || info.height != crop_height) {
822 info.width = crop_width;
823 info.height = crop_height;
824 info.display_ratio =
Fraction(crop_width, crop_height);
825 info.display_ratio.Reduce();
827 queue_condition.notify_one();
830 bool AcceptHeader(
const spa_meta_header& header)
832 if (header.flags & (SPA_META_HEADER_FLAG_CORRUPTED | SPA_META_HEADER_FLAG_GAP)) {
833 LogDroppedHeader(
"flags", header);
837 const bool discontinuity = (header.flags & SPA_META_HEADER_FLAG_DISCONT) != 0;
838 if (!discontinuity && have_last_header_sequence && header.seq > last_header_sequence) {
839 header_sequence_ordering_active =
true;
841 if (!discontinuity && header_sequence_ordering_active && header.seq <= last_header_sequence) {
842 LogDroppedHeader(
"sequence", header);
845 if (!discontinuity && header.pts >= 0 && have_last_header_pts && header.pts < last_header_pts) {
846 LogDroppedHeader(
"pts", header);
850 have_last_header_sequence =
true;
851 last_header_sequence = header.seq;
852 if (header.pts >= 0) {
853 have_last_header_pts =
true;
854 last_header_pts = header.pts;
859 void LogDroppedHeader(
const std::string& reason,
const spa_meta_header& header)
861 if (header_drop_log_count >= 5) {
865 "Wayland PipeWire dropped non-monotonic frame: reason=" + reason +
866 " flags=" + std::to_string(header.flags) +
867 " seq=" + std::to_string(header.seq) +
868 " last_seq=" + (have_last_header_sequence ? std::to_string(last_header_sequence) : std::string(
"none")) +
869 " seq_active=" + std::to_string(header_sequence_ordering_active ? 1 : 0) +
870 " pts=" + std::to_string(header.pts) +
871 " last_pts=" + (have_last_header_pts ? std::to_string(last_header_pts) : std::string(
"none")));
872 header_drop_log_count++;
877 GDBusConnection* connection;
878 pw_thread_loop* thread_loop;
882 spa_hook stream_listener;
883 PortalStreamInfo stream_info;
884 std::string session_handle;
885 guint session_closed_subscription;
886 int stream_width = 0;
887 int stream_height = 0;
888 spa_video_format video_format = SPA_VIDEO_FORMAT_BGRx;
895 bool have_last_header_sequence;
896 uint64_t last_header_sequence;
897 bool header_sequence_ordering_active;
898 bool have_last_header_pts;
899 int64_t last_header_pts;
900 int header_drop_log_count;
901 std::mutex queue_mutex;
902 std::condition_variable queue_condition;
903 std::deque<CapturedFrame> frame_queue;
905 static const pw_stream_events stream_events;
908 const pw_stream_events WaylandScreenCaptureReader::stream_events = {
909 PW_VERSION_STREAM_EVENTS,
911 WaylandScreenCaptureReader::OnStreamStateChanged,
914 WaylandScreenCaptureReader::OnStreamParamChanged,
917 WaylandScreenCaptureReader::OnStreamProcess,
927 return std::make_unique<WaylandScreenCaptureReader>(settings, info);