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>
45 const char* PORTAL_BUS =
"org.freedesktop.portal.Desktop";
46 const char* PORTAL_PATH =
"/org/freedesktop/portal/desktop";
47 const char* SCREENCAST_IFACE =
"org.freedesktop.portal.ScreenCast";
49 std::string gerror_message(GError* error)
52 return "unknown error";
54 std::string message = error->message ? error->message :
"unknown error";
59 std::string token_for(
const char* prefix)
61 static uint64_t counter = 0;
62 std::stringstream token;
63 token << prefix <<
"_" << ++counter;
70 bool timed_out =
false;
72 GVariant* results =
nullptr;
73 GMainLoop* loop =
nullptr;
78 g_variant_unref(results);
83 void portal_response_callback(
92 auto* response =
static_cast<PortalResponse*
>(user_data);
93 GVariant* results =
nullptr;
94 g_variant_get(parameters,
"(u@a{sv})", &response->code, &results);
95 response->results = results;
96 response->done =
true;
98 g_main_loop_quit(response->loop);
102 gboolean portal_response_timeout(gpointer user_data)
104 auto* response =
static_cast<PortalResponse*
>(user_data);
105 response->timed_out =
true;
106 if (response->loop) {
107 g_main_loop_quit(response->loop);
109 return G_SOURCE_REMOVE;
112 GVariant* wait_for_portal_response(GDBusConnection* connection,
const std::string& handle)
114 PortalResponse response;
115 response.loop = g_main_loop_new(
nullptr, FALSE);
116 const guint timeout = g_timeout_add_seconds(60, portal_response_timeout, &response);
117 const guint subscription = g_dbus_connection_signal_subscribe(
120 "org.freedesktop.portal.Request",
124 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
125 portal_response_callback,
129 g_main_loop_run(response.loop);
130 g_dbus_connection_signal_unsubscribe(connection, subscription);
131 if (!response.timed_out) {
132 g_source_remove(timeout);
134 g_main_loop_unref(response.loop);
135 response.loop =
nullptr;
137 if (response.timed_out) {
138 throw InvalidOptions(
"Timed out waiting for Wayland screen capture permission.");
140 if (!response.done || response.code != 0) {
141 throw InvalidOptions(
"Wayland screen capture permission was denied or cancelled.");
143 GVariant* results = response.results;
144 response.results =
nullptr;
148 std::string call_portal_request(
149 GDBusConnection* connection,
151 GVariant* parameters)
153 GError* error =
nullptr;
154 GVariant* result = g_dbus_connection_call_sync(
161 G_VARIANT_TYPE(
"(o)"),
162 G_DBUS_CALL_FLAGS_NONE,
167 throw InvalidOptions(
"Wayland portal " + std::string(method) +
" failed: " + gerror_message(error));
170 const char* handle =
nullptr;
171 g_variant_get(result,
"(&o)", &handle);
172 std::string handle_path = handle ? handle :
"";
173 g_variant_unref(result);
177 struct PortalStreamInfo
179 uint32_t node_id = 0;
180 uint64_t pipewire_serial = 0;
181 uint32_t source_type = 0;
186 PortalStreamInfo stream_info_from_results(GVariant* results)
188 GVariant* streams = g_variant_lookup_value(results,
"streams", G_VARIANT_TYPE(
"a(ua{sv})"));
189 if (!streams || g_variant_n_children(streams) == 0) {
191 g_variant_unref(streams);
193 throw InvalidOptions(
"Wayland portal did not return a PipeWire stream.");
196 PortalStreamInfo stream_info;
197 GVariant* properties =
nullptr;
198 GVariant* child = g_variant_get_child_value(streams, 0);
199 g_variant_get(child,
"(u@a{sv})", &stream_info.node_id, &properties);
202 uint32_t source_type = 0;
205 if (g_variant_lookup(properties,
"pipewire-serial",
"t", &serial)) {
206 stream_info.pipewire_serial = serial;
208 if (g_variant_lookup(properties,
"source_type",
"u", &source_type)) {
209 stream_info.source_type = source_type;
211 if (g_variant_lookup(properties,
"size",
"(ii)", &width, &height)) {
212 stream_info.width = width;
213 stream_info.height = height;
215 g_variant_unref(properties);
217 g_variant_unref(child);
218 g_variant_unref(streams);
222 int open_pipewire_remote(GDBusConnection* connection,
const std::string& session_handle)
224 GVariantBuilder options;
225 g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
227 GError* error =
nullptr;
228 GUnixFDList* out_fds =
nullptr;
229 GVariant* result = g_dbus_connection_call_with_unix_fd_list_sync(
234 "OpenPipeWireRemote",
235 g_variant_new(
"(oa{sv})", session_handle.c_str(), &options),
236 G_VARIANT_TYPE(
"(h)"),
237 G_DBUS_CALL_FLAGS_NONE,
244 throw InvalidOptions(
"Wayland portal OpenPipeWireRemote failed: " + gerror_message(error));
248 g_variant_get(result,
"(h)", &fd_index);
249 g_variant_unref(result);
250 int fd = g_unix_fd_list_get(out_fds, fd_index, &error);
251 g_object_unref(out_fds);
253 throw InvalidOptions(
"Unable to obtain PipeWire remote file descriptor: " + gerror_message(error));
262 std::vector<unsigned char> rgba;
270 : settings(new_settings)
272 , connection(nullptr)
273 , thread_loop(nullptr)
277 , session_closed_subscription(0)
282 , stream_error(false)
284 , have_last_header_sequence(false)
285 , last_header_sequence(0)
286 , header_sequence_ordering_active(false)
287 , have_last_header_pts(false)
289 , header_drop_log_count(0)
304 GError* error =
nullptr;
305 connection = g_bus_get_sync(G_BUS_TYPE_SESSION,
nullptr, &error);
307 throw InvalidOptions(
"Unable to connect to the session bus for Wayland capture: " + gerror_message(error));
310 session_handle = CreatePortalSession();
311 SubscribePortalSessionClosed();
312 SelectPortalSources(session_handle);
313 stream_info = StartPortalSession(session_handle);
314 ApplyPortalStreamInfo();
316 "Wayland portal stream selected: node_id=" + std::to_string(stream_info.node_id) +
317 " pipewire_serial=" + std::to_string(stream_info.pipewire_serial) +
318 " source_type=" + std::to_string(stream_info.source_type) +
319 " portal_size=" + std::to_string(stream_info.width) +
"x" + std::to_string(stream_info.height));
320 const int pipewire_fd = open_pipewire_remote(connection, session_handle);
321 OpenPipeWireStream(pipewire_fd);
331 pw_thread_loop_stop(thread_loop);
333 if (connection && session_closed_subscription) {
334 g_dbus_connection_signal_unsubscribe(connection, session_closed_subscription);
335 session_closed_subscription = 0;
337 if (connection && !session_handle.empty()) {
338 GError* error =
nullptr;
339 GVariant* result = g_dbus_connection_call_sync(
342 session_handle.c_str(),
343 "org.freedesktop.portal.Session",
347 G_DBUS_CALL_FLAGS_NONE,
352 g_variant_unref(result);
356 session_handle.clear();
359 pw_stream_destroy(stream);
363 pw_core_disconnect(core);
367 pw_context_destroy(context);
371 pw_thread_loop_destroy(thread_loop);
372 thread_loop =
nullptr;
375 g_object_unref(connection);
376 connection =
nullptr;
391 const double fps = settings.fps.den != 0 ?
static_cast<double>(settings.fps.num) /
static_cast<double>(settings.fps.den) : 0.0;
392 stats.
duration = fps > 0.0 ?
static_cast<double>(frames_read) / fps : 0.0;
396 std::shared_ptr<Frame>
GetFrame(int64_t number)
override
398 CapturedFrame captured;
400 std::unique_lock<std::mutex> lock(queue_mutex);
401 const auto wait_duration = std::chrono::milliseconds(
406 const auto deadline = std::chrono::steady_clock::now() + wait_duration;
407 while (frame_queue.empty() && !stream_error && open && std::chrono::steady_clock::now() < deadline) {
409 while (g_main_context_iteration(
nullptr, FALSE)) {
412 queue_condition.wait_for(lock, std::chrono::milliseconds(100));
414 if (frame_queue.empty() && !stream_error && open) {
415 if (have_last_frame) {
416 captured = last_frame;
418 throw InvalidFile(
"Timed out waiting for the first Wayland capture frame.",
"wayland");
422 throw InvalidFile(
"Wayland capture stream failed.",
"wayland");
424 if (frame_queue.empty()) {
426 throw ReaderClosed(
"The Wayland screen capture stream is closed.");
428 if (!have_last_frame) {
429 throw InvalidFile(
"Timed out waiting for the first Wayland capture frame.",
"wayland");
432 captured = std::move(frame_queue.front());
433 frame_queue.pop_front();
434 last_frame = captured;
435 have_last_frame =
true;
439 const int bytes_per_pixel = 4;
440 const size_t buffer_size =
static_cast<size_t>(captured.width) * captured.height * bytes_per_pixel;
441 unsigned char* buffer =
static_cast<unsigned char*
>(malloc(buffer_size));
443 throw OutOfMemory(
"Unable to allocate Wayland capture frame buffer.",
"wayland");
445 std::memcpy(buffer, captured.rgba.data(), buffer_size);
447 auto frame = std::make_shared<Frame>(number, captured.width, captured.height,
"#000000");
448 frame->AddImage(captured.width, captured.height, bytes_per_pixel, QImage::Format_RGBA8888, buffer);
454 std::string CreatePortalSession()
456 GVariantBuilder options;
457 g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
458 g_variant_builder_add(&options,
"{sv}",
"handle_token", g_variant_new_string(token_for(
"openshot_create").c_str()));
459 g_variant_builder_add(&options,
"{sv}",
"session_handle_token", g_variant_new_string(token_for(
"openshot_session").c_str()));
461 const std::string handle = call_portal_request(connection,
"CreateSession", g_variant_new(
"(a{sv})", &options));
462 GVariant* results = wait_for_portal_response(connection, handle);
464 const char* session =
nullptr;
465 if (!g_variant_lookup(results,
"session_handle",
"&s", &session) || !session) {
466 g_variant_unref(results);
467 throw InvalidOptions(
"Wayland portal did not return a screencast session handle.");
469 std::string session_handle = session;
470 g_variant_unref(results);
471 return session_handle;
474 void SubscribePortalSessionClosed()
476 if (!connection || session_handle.empty()) {
479 session_closed_subscription = g_dbus_connection_signal_subscribe(
482 "org.freedesktop.portal.Session",
484 session_handle.c_str(),
486 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
487 OnPortalSessionClosed,
492 void SelectPortalSources(
const std::string& session_handle)
494 const uint32_t source_monitor = 1;
495 const uint32_t source_window = 2;
496 const uint32_t cursor_hidden = 1;
497 const uint32_t cursor_embedded = 2;
499 GVariantBuilder options;
500 g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
501 g_variant_builder_add(&options,
"{sv}",
"handle_token", g_variant_new_string(token_for(
"openshot_select").c_str()));
502 g_variant_builder_add(&options,
"{sv}",
"types", g_variant_new_uint32(source_monitor | source_window));
503 g_variant_builder_add(&options,
"{sv}",
"multiple", g_variant_new_boolean(FALSE));
504 g_variant_builder_add(&options,
"{sv}",
"cursor_mode", g_variant_new_uint32(settings.include_cursor ? cursor_embedded : cursor_hidden));
506 const std::string handle = call_portal_request(
509 g_variant_new(
"(oa{sv})", session_handle.c_str(), &options));
510 GVariant* results = wait_for_portal_response(connection, handle);
511 g_variant_unref(results);
514 PortalStreamInfo StartPortalSession(
const std::string& session_handle)
516 GVariantBuilder options;
517 g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
518 g_variant_builder_add(&options,
"{sv}",
"handle_token", g_variant_new_string(token_for(
"openshot_start").c_str()));
520 const std::string handle = call_portal_request(
523 g_variant_new(
"(osa{sv})", session_handle.c_str(),
"", &options));
524 GVariant* results = wait_for_portal_response(connection, handle);
525 const PortalStreamInfo result_stream_info = stream_info_from_results(results);
526 g_variant_unref(results);
527 return result_stream_info;
530 void ApplyPortalStreamInfo()
532 if (stream_info.width <= 0 || stream_info.height <= 0) {
535 info.width = stream_info.width;
536 info.height = stream_info.height;
537 info.display_ratio =
Fraction(stream_info.width, stream_info.height);
538 info.display_ratio.Reduce();
541 void OpenPipeWireStream(
int pipewire_fd)
543 pw_init(
nullptr,
nullptr);
545 thread_loop = pw_thread_loop_new(
"openshot-wayland-capture",
nullptr);
549 context = pw_context_new(pw_thread_loop_get_loop(thread_loop),
nullptr, 0);
553 core = pw_context_connect_fd(context, pipewire_fd,
nullptr, 0);
556 throw InvalidOptions(
"Unable to connect to the portal PipeWire remote.");
559 pw_properties* props = pw_properties_new(
560 PW_KEY_MEDIA_TYPE,
"Video",
561 PW_KEY_MEDIA_CATEGORY,
"Capture",
562 PW_KEY_MEDIA_ROLE,
"Screen",
564 if (stream_info.pipewire_serial > 0) {
565 pw_properties_set(props, PW_KEY_TARGET_OBJECT, std::to_string(stream_info.pipewire_serial).c_str());
567 stream = pw_stream_new(core,
"OpenShot Wayland Screen Capture", props);
569 throw InvalidOptions(
"Unable to create PipeWire capture stream.");
572 pw_stream_add_listener(stream, &stream_listener, &stream_events,
this);
574 if (pw_thread_loop_start(thread_loop) < 0) {
575 throw InvalidOptions(
"Unable to start PipeWire capture thread loop.");
578 uint8_t format_buffer[1024];
579 spa_pod_builder builder = SPA_POD_BUILDER_INIT(format_buffer,
sizeof(format_buffer));
580 const spa_fraction requested_framerate = SPA_FRACTION(
581 static_cast<uint32_t
>(std::max(1, settings.fps.num)),
582 static_cast<uint32_t
>(std::max(1, settings.fps.den)));
583 const spa_fraction variable_framerate = SPA_FRACTION(0, 1);
584 const spa_pod* params[1];
585 params[0] =
static_cast<const spa_pod*
>(spa_pod_builder_add_object(
587 SPA_TYPE_OBJECT_Format,
588 SPA_PARAM_EnumFormat,
589 SPA_FORMAT_mediaType,
590 SPA_POD_Id(SPA_MEDIA_TYPE_video),
591 SPA_FORMAT_mediaSubtype,
592 SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
593 SPA_FORMAT_VIDEO_format,
594 SPA_POD_CHOICE_ENUM_Id(
596 SPA_VIDEO_FORMAT_BGRx,
597 SPA_VIDEO_FORMAT_RGBA,
598 SPA_VIDEO_FORMAT_BGRA,
599 SPA_VIDEO_FORMAT_RGBx),
600 SPA_FORMAT_VIDEO_framerate,
601 SPA_POD_Fraction(&variable_framerate),
602 SPA_FORMAT_VIDEO_maxFramerate,
603 SPA_POD_Fraction(&requested_framerate)));
605 pw_thread_loop_lock(thread_loop);
606 const uint32_t target_id = stream_info.pipewire_serial > 0 ? PW_ID_ANY : stream_info.node_id;
607 const int result = pw_stream_connect(
611 static_cast<pw_stream_flags
>(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS),
615 pw_thread_loop_unlock(thread_loop);
616 throw InvalidOptions(
"Unable to connect to PipeWire capture stream.");
619 while (!streaming && !stream_error) {
620 if (pw_thread_loop_timed_wait(thread_loop, 10) < 0) {
625 pw_thread_loop_unlock(thread_loop);
628 throw InvalidOptions(
"PipeWire capture stream failed to start or timed out.");
632 static void OnPortalSessionClosed(
643 self->stream_error =
true;
644 if (self->thread_loop) {
645 pw_thread_loop_signal(self->thread_loop,
false);
647 self->queue_condition.notify_all();
650 static void OnStreamStateChanged(
void* data, pw_stream_state, pw_stream_state state,
const char*)
653 if (state == PW_STREAM_STATE_STREAMING) {
654 self->streaming =
true;
655 pw_thread_loop_signal(self->thread_loop,
false);
656 }
else if (state == PW_STREAM_STATE_ERROR || state == PW_STREAM_STATE_UNCONNECTED) {
657 self->stream_error =
true;
658 pw_thread_loop_signal(self->thread_loop,
false);
659 self->queue_condition.notify_all();
663 static void OnStreamParamChanged(
void* data, uint32_t
id,
const spa_pod* param)
665 if (
id != SPA_PARAM_Format || !param) {
669 spa_video_info info = {};
670 if (spa_format_parse(param, &info.media_type, &info.media_subtype) < 0 ||
671 info.media_type != SPA_MEDIA_TYPE_video ||
672 info.media_subtype != SPA_MEDIA_SUBTYPE_raw ||
673 spa_format_video_raw_parse(param, &info.info.raw) < 0) {
677 self->video_format = info.info.raw.format;
678 self->stream_width =
static_cast<int>(info.info.raw.size.width);
679 self->stream_height =
static_cast<int>(info.info.raw.size.height);
680 if (info.info.raw.framerate.num > 0 && info.info.raw.framerate.denom > 0) {
682 static_cast<int>(info.info.raw.framerate.num),
683 static_cast<int>(info.info.raw.framerate.denom));
684 self->info.video_timebase =
self->info.fps.Reciprocal();
686 if (self->stream_width > 0 && self->stream_height > 0) {
688 "Wayland PipeWire stream format: " +
689 std::to_string(self->stream_width) +
"x" + std::to_string(self->stream_height) +
690 " format=" + std::to_string(self->video_format) +
691 " framerate=" + std::to_string(info.info.raw.framerate.num) +
692 "/" + std::to_string(info.info.raw.framerate.denom) +
693 " max_framerate=" + std::to_string(info.info.raw.max_framerate.num) +
694 "/" + std::to_string(info.info.raw.max_framerate.denom));
695 self->info.width =
self->stream_width;
696 self->info.height =
self->stream_height;
697 self->info.display_ratio =
Fraction(self->stream_width, self->stream_height);
698 self->info.display_ratio.Reduce();
701 uint8_t params_buffer[1024];
702 spa_pod_builder builder = SPA_POD_BUILDER_INIT(params_buffer,
sizeof(params_buffer));
703 const int stride =
self->stream_width * 4;
704 const int buffer_size = stride *
self->stream_height;
705 const spa_pod* params[3];
706 params[0] =
static_cast<const spa_pod*
>(spa_pod_builder_add_object(
708 SPA_TYPE_OBJECT_ParamBuffers,
710 SPA_PARAM_BUFFERS_buffers,
711 SPA_POD_CHOICE_RANGE_Int(8, 2, 32),
712 SPA_PARAM_BUFFERS_blocks,
714 SPA_PARAM_BUFFERS_size,
715 SPA_POD_Int(buffer_size),
716 SPA_PARAM_BUFFERS_stride,
718 SPA_PARAM_BUFFERS_dataType,
719 SPA_POD_CHOICE_FLAGS_Int(
720 (1 << SPA_DATA_MemPtr) | (1 << SPA_DATA_MemFd))));
721 params[1] =
static_cast<const spa_pod*
>(spa_pod_builder_add_object(
723 SPA_TYPE_OBJECT_ParamMeta,
726 SPA_POD_Id(SPA_META_Header),
728 SPA_POD_Int(
sizeof(spa_meta_header))));
729 params[2] =
static_cast<const spa_pod*
>(spa_pod_builder_add_object(
731 SPA_TYPE_OBJECT_ParamMeta,
734 SPA_POD_Id(SPA_META_VideoCrop),
736 SPA_POD_Int(
sizeof(spa_meta_region))));
737 pw_stream_update_params(self->stream, params, 3);
740 static void OnStreamProcess(
void* data)
746 pw_buffer* buffer =
nullptr;
747 pw_buffer* next_buffer = pw_stream_dequeue_buffer(self->stream);
748 while (next_buffer) {
750 pw_stream_queue_buffer(self->stream, buffer);
751 self->dropped_packets++;
753 buffer = next_buffer;
754 next_buffer = pw_stream_dequeue_buffer(self->stream);
760 self->CopyPipeWireBuffer(buffer);
761 pw_stream_queue_buffer(self->stream, buffer);
764 void CopyPipeWireBuffer(pw_buffer* buffer)
766 spa_buffer* spa_buffer = buffer->buffer;
767 if (!spa_buffer || spa_buffer->n_datas == 0 || !spa_buffer->datas[0].data || stream_width <= 0 || stream_height <= 0) {
772 auto* header =
static_cast<spa_meta_header*
>(
773 spa_buffer_find_meta_data(spa_buffer, SPA_META_Header,
sizeof(spa_meta_header)));
774 if (header && !AcceptHeader(*header)) {
779 spa_data& data = spa_buffer->datas[0];
780 const spa_chunk* chunk = data.chunk;
783 int crop_width = stream_width;
784 int crop_height = stream_height;
785 auto* crop =
static_cast<spa_meta_region*
>(
786 spa_buffer_find_meta_data(spa_buffer, SPA_META_VideoCrop,
sizeof(spa_meta_region)));
787 if (crop && spa_meta_region_is_valid(crop)) {
788 crop_x = std::max(0, crop->region.position.x);
789 crop_y = std::max(0, crop->region.position.y);
790 crop_width = std::min(
static_cast<int>(crop->region.size.width), stream_width - crop_x);
791 crop_height = std::min(
static_cast<int>(crop->region.size.height), stream_height - crop_y);
794 "Wayland PipeWire video crop: x=" + std::to_string(crop_x) +
795 " y=" + std::to_string(crop_y) +
796 " width=" + std::to_string(crop_width) +
797 " height=" + std::to_string(crop_height));
803 static_cast<size_t>(data.maxsize),
804 chunk ?
static_cast<size_t>(chunk->offset) : 0,
805 chunk ?
static_cast<size_t>(chunk->size) :
static_cast<size_t>(data.maxsize),
806 chunk ? chunk->stride : 0,
819 frame.width = layout.width;
820 frame.height = layout.height;
821 frame.rgba.assign(
static_cast<size_t>(frame.width) * frame.height * 4, 0);
823 std::vector<uint8_t> source_row(
static_cast<size_t>(frame.width) * 4);
824 for (
int y = 0; y < frame.height; ++y) {
825 const size_t logical_offset =
826 static_cast<size_t>(y + layout.crop_y) * layout.stride
827 +
static_cast<size_t>(layout.crop_x) * 4;
829 static_cast<const uint8_t*
>(data.data),
830 static_cast<size_t>(data.maxsize),
834 source_row.size())) {
838 for (
int x = 0; x < frame.width; ++x) {
839 const uint8_t* pixel = source_row.data() +
static_cast<size_t>(x) * 4;
844 switch (video_format) {
845 case SPA_VIDEO_FORMAT_RGBA:
846 r = pixel[0]; g = pixel[1]; b = pixel[2]; a = pixel[3];
848 case SPA_VIDEO_FORMAT_RGBx:
849 r = pixel[0]; g = pixel[1]; b = pixel[2];
851 case SPA_VIDEO_FORMAT_BGRA:
852 b = pixel[0]; g = pixel[1]; r = pixel[2]; a = pixel[3];
854 case SPA_VIDEO_FORMAT_BGRx:
856 b = pixel[0]; g = pixel[1]; r = pixel[2];
859 const size_t dst = (
static_cast<size_t>(y) * crop_width + x) * 4;
860 frame.rgba[dst + 0] = r;
861 frame.rgba[dst + 1] = g;
862 frame.rgba[dst + 2] = b;
863 frame.rgba[dst + 3] = a;
868 std::lock_guard<std::mutex> lock(queue_mutex);
869 if (frame_queue.size() > 3) {
870 frame_queue.pop_front();
873 frame_queue.push_back(std::move(frame));
875 if (info.width != frame.width || info.height != frame.height) {
876 info.width = frame.width;
877 info.height = frame.height;
878 info.display_ratio =
Fraction(frame.width, frame.height);
879 info.display_ratio.Reduce();
881 queue_condition.notify_one();
884 bool AcceptHeader(
const spa_meta_header& header)
886 if (header.flags & (SPA_META_HEADER_FLAG_CORRUPTED | SPA_META_HEADER_FLAG_GAP)) {
887 LogDroppedHeader(
"flags", header);
891 const bool discontinuity = (header.flags & SPA_META_HEADER_FLAG_DISCONT) != 0;
892 if (!discontinuity && have_last_header_sequence && header.seq > last_header_sequence) {
893 header_sequence_ordering_active =
true;
895 if (!discontinuity && header_sequence_ordering_active && header.seq <= last_header_sequence) {
896 LogDroppedHeader(
"sequence", header);
899 if (!discontinuity && header.pts >= 0 && have_last_header_pts && header.pts < last_header_pts) {
900 LogDroppedHeader(
"pts", header);
904 have_last_header_sequence =
true;
905 last_header_sequence = header.seq;
906 if (header.pts >= 0) {
907 have_last_header_pts =
true;
908 last_header_pts = header.pts;
913 void LogDroppedHeader(
const std::string& reason,
const spa_meta_header& header)
915 if (header_drop_log_count >= 5) {
919 "Wayland PipeWire dropped non-monotonic frame: reason=" + reason +
920 " flags=" + std::to_string(header.flags) +
921 " seq=" + std::to_string(header.seq) +
922 " last_seq=" + (have_last_header_sequence ? std::to_string(last_header_sequence) : std::string(
"none")) +
923 " seq_active=" + std::to_string(header_sequence_ordering_active ? 1 : 0) +
924 " pts=" + std::to_string(header.pts) +
925 " last_pts=" + (have_last_header_pts ? std::to_string(last_header_pts) : std::string(
"none")));
926 header_drop_log_count++;
931 GDBusConnection* connection;
932 pw_thread_loop* thread_loop;
936 spa_hook stream_listener;
937 PortalStreamInfo stream_info;
938 std::string session_handle;
939 guint session_closed_subscription;
940 int stream_width = 0;
941 int stream_height = 0;
942 spa_video_format video_format = SPA_VIDEO_FORMAT_BGRx;
949 bool have_last_header_sequence;
950 uint64_t last_header_sequence;
951 bool header_sequence_ordering_active;
952 bool have_last_header_pts;
953 int64_t last_header_pts;
954 int header_drop_log_count;
955 std::mutex queue_mutex;
956 std::condition_variable queue_condition;
957 std::deque<CapturedFrame> frame_queue;
958 CapturedFrame last_frame;
959 bool have_last_frame =
false;
961 static const pw_stream_events stream_events;
964 const pw_stream_events WaylandScreenCaptureReader::stream_events = {
965 PW_VERSION_STREAM_EVENTS,
967 WaylandScreenCaptureReader::OnStreamStateChanged,
970 WaylandScreenCaptureReader::OnStreamParamChanged,
973 WaylandScreenCaptureReader::OnStreamProcess,
983 return std::make_unique<WaylandScreenCaptureReader>(settings, info);
Header file for all Exception classes.
Header file for Frame class.
Header file for live screen capture readers.
Bounds-safe helpers for mapped PipeWire video buffers.
std::unique_ptr< ScreenCaptureReader::CaptureBackendReader > OpenShotCreateWaylandScreenCaptureReader(const ScreenCaptureSettings &settings, ReaderInfo &info)
Header file for ZeroMQ-based Logger class.
std::shared_ptr< Frame > GetFrame(int64_t number) override
bool IsOpen() const override
WaylandScreenCaptureReader(const ScreenCaptureSettings &new_settings, ReaderInfo &new_info)
~WaylandScreenCaptureReader() override
CaptureReaderStats GetStats() const override
This class represents a fraction.
Exception for files that can not be found or opened.
Exception when invalid encoding options are used.
Exception when memory could not be allocated.
Exception when a reader is closed, and a frame is requested.
void Log(std::string message)
Log message to all subscribers of this logger (if any)
static ZmqLogger * Instance()
Create or get an instance of this logger singleton (invoke the class with this method)
bool CopyWrappedBytes(const uint8_t *source, size_t max_size, size_t chunk_offset, size_t logical_offset, uint8_t *destination, size_t byte_count)
PackedVideoLayout ResolvePackedVideoLayout(size_t max_size, size_t chunk_offset, size_t chunk_size, int chunk_stride, int stream_width, int stream_height, int crop_x, int crop_y, int crop_width, int crop_height)
int DamageFrameWaitMilliseconds(int fps_num, int fps_den, bool have_last_frame)
This namespace is the default namespace for all code in the openshot library.
This struct contains info about a media file, such as height, width, frames per second,...