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();
315 Logger::Instance()->Log(
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;
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) {
687 Logger::Instance()->Log(
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);
793 Logger::Instance()->Log(
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) {
918 Logger::Instance()->Log(
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;
964const 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)
bool IsOpen() const override
WaylandScreenCaptureReader(const ScreenCaptureSettings &new_settings, ReaderInfo &new_info)
~WaylandScreenCaptureReader() override
CaptureReaderStats GetStats() const override
std::shared_ptr< Frame > GetFrame(int64_t number) override
This class represents a fraction.
int num
Numerator for the fraction.
void Reduce()
Reduce this fraction (i.e. 640/480 = 4/3)
int den
Denominator for the 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.
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,...
int width
The width of the video (in pixesl)
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
openshot::Fraction display_ratio
The ratio of width to height of the video stream (i.e. 640x480 has a ratio of 4/3)
int height
The height of the video (in pixels)