@@ -137,6 +137,8 @@ namespace portal {
137137 struct pw_buffer *current_buffer;
138138 uint64_t drm_format;
139139 std::shared_ptr<shared_state_t > shared;
140+ std::mutex frame_mutex;
141+ std::condition_variable frame_cv;
140142 };
141143
142144 struct dmabuf_format_info_t {
@@ -690,6 +692,14 @@ namespace portal {
690692 pw_thread_loop_destroy (loop);
691693 }
692694
695+ std::mutex &frame_mutex () {
696+ return stream_data.frame_mutex ;
697+ }
698+
699+ std::condition_variable &frame_cv () {
700+ return stream_data.frame_cv ;
701+ }
702+
693703 void init (int stream_fd, int stream_node, std::shared_ptr<shared_state_t > shared_state) {
694704 fd = stream_fd;
695705 node = stream_node;
@@ -768,9 +778,21 @@ namespace portal {
768778 if (stream_data.current_buffer ) {
769779 struct spa_buffer *buf;
770780 buf = stream_data.current_buffer ->buffer ;
781+ struct spa_meta_header *h;
782+ h = static_cast <struct spa_meta_header *>(
783+ spa_buffer_find_meta_data (buf, SPA_META_Header, sizeof (*h))
784+ );
785+
771786 if (buf->datas [0 ].chunk ->size != 0 ) {
772787 const auto img_descriptor = static_cast <egl::img_descriptor_t *>(img);
773788 img_descriptor->frame_timestamp = std::chrono::steady_clock::now ();
789+
790+ // Passthrough PipeWire metadata
791+ if (h) {
792+ img_descriptor->seq = h->seq ;
793+ img_descriptor->pts = h->pts ;
794+ }
795+
774796 if (buf->datas [0 ].type == SPA_DATA_DmaBuf) {
775797 img_descriptor->sd .width = stream_data.format .info .raw .size .width ;
776798 img_descriptor->sd .height = stream_data.format .info .raw .size .height ;
@@ -898,10 +920,17 @@ namespace portal {
898920 return ;
899921 }
900922
901- if (d->current_buffer ) {
902- pw_stream_queue_buffer (d->stream , d->current_buffer );
923+ // Update current_buffer atomically
924+ {
925+ std::scoped_lock lock (d->frame_mutex );
926+
927+ if (d->current_buffer ) {
928+ pw_stream_queue_buffer (d->stream , d->current_buffer );
929+ }
930+
931+ d->current_buffer = b;
903932 }
904- d->current_buffer = b ;
933+ d->frame_cv . notify_one () ;
905934 }
906935
907936 static void on_param_changed (void *user_data, uint32_t id, const struct spa_pod *param) {
@@ -964,14 +993,18 @@ namespace portal {
964993 buffer_types |= 1 << SPA_DATA_MemPtr;
965994 }
966995
967- // Ack the buffer type
996+ // Ack the buffer type and metadata
968997 std::array<uint8_t , SPA_POD_BUFFER_SIZE> buffer;
969- std::array<const struct spa_pod *, 1 > params;
998+ std::array<const struct spa_pod *, 2 > params;
970999 int n_params = 0 ;
9711000 struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT (buffer.data (), buffer.size ());
9721001 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)));
9731002 params[n_params] = buffer_param;
9741003 n_params++;
1004+ 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 ))));
1005+ params[n_params] = meta_param;
1006+ n_params++;
1007+
9751008 pw_stream_update_params (d->stream , params.data (), n_params);
9761009 }
9771010
@@ -1049,23 +1082,56 @@ namespace portal {
10491082
10501083 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) {
10511084 // FIXME: show_cursor is ignored
1052- if (!pull_free_image_cb (img_out)) {
1053- return platf::capture_e::interrupted;
1054- }
1085+ auto start_time = std::chrono::steady_clock::now ();
1086+ auto end_time = start_time;
1087+
1088+ while (true ) {
1089+ if (!pull_free_image_cb (img_out)) {
1090+ return platf::capture_e::interrupted;
1091+ }
10551092
1056- const auto img_egl = static_cast <egl::img_descriptor_t *>(img_out.get ());
1057- img_egl->reset ();
1058- pipewire.fill_img (img_egl);
1093+ const auto img_egl = static_cast <egl::img_descriptor_t *>(img_out.get ());
1094+ img_egl->reset ();
1095+ pipewire.fill_img (img_egl);
10591096
1060- // Check if we got valid data (either DMA-BUF fd or memory pointer)
1061- if (img_egl->sd .fds [0 ] < 0 && img_egl->data == nullptr ) {
1062- // No buffer available yet from pipewire
1063- return platf::capture_e::timeout ;
1064- }
1097+ // Check if we got valid data (either DMA-BUF fd or memory pointer)
1098+ if (img_egl->sd .fds [0 ] < 0 && img_egl->data == nullptr ) {
1099+ // No buffer available yet from pipewire
1100+ goto retry_logic ;
1101+ }
10651102
1066- img_egl->sequence = ++sequence;
1103+ // Duplicate detection: PipeWire seq increments on each new frame,
1104+ // pts advances with each buffer update. Both must advance to accept frame.
1105+ if (img_egl->seq .has_value () && img_egl->pts .has_value ()) {
1106+ if (last_pts.has_value () && last_seq.has_value () &&
1107+ img_egl->pts .value () == last_pts.value () &&
1108+ img_egl->seq .value () == last_seq.value ()) {
1109+ goto retry_logic;
1110+ }
1111+ }
10671112
1068- return platf::capture_e::ok;
1113+ // Frame found; check deadline
1114+ end_time = std::chrono::steady_clock::now ();
1115+ if (end_time - start_time > timeout) {
1116+ return platf::capture_e::timeout;
1117+ } else {
1118+ if (img_egl->seq .has_value () && img_egl->pts .has_value ()) {
1119+ last_seq = img_egl->seq .value ();
1120+ last_pts = img_egl->pts .value ();
1121+ }
1122+ img_egl->sequence = ++sequence;
1123+ return platf::capture_e::ok;
1124+ }
1125+
1126+ retry_logic:
1127+ end_time = std::chrono::steady_clock::now ();
1128+ if (end_time - start_time >= timeout) {
1129+ return platf::capture_e::timeout;
1130+ }
1131+
1132+ std::unique_lock lock (pipewire.frame_mutex ());
1133+ pipewire.frame_cv ().wait_until (lock, start_time + timeout);
1134+ }
10691135 }
10701136
10711137 std::shared_ptr<platf::img_t > alloc_img () override {
@@ -1113,19 +1179,18 @@ namespace portal {
11131179 }
11141180 }
11151181
1182+ // Advance to (or catch up with) next delay interval
11161183 auto now = std::chrono::steady_clock::now ();
1184+ while (next_frame < now) {
1185+ next_frame += delay;
1186+ }
11171187
11181188 if (next_frame > now) {
1119- std::this_thread::sleep_for (next_frame - now );
1189+ std::this_thread::sleep_until (next_frame);
11201190 sleep_overshoot_logger.first_point (next_frame);
11211191 sleep_overshoot_logger.second_point_now_and_log ();
11221192 }
11231193
1124- next_frame += delay;
1125- if (next_frame < now) { // some major slowdown happened; we couldn't keep up
1126- next_frame = now + delay;
1127- }
1128-
11291194 std::shared_ptr<platf::img_t > img_out;
11301195 switch (const auto status = snapshot (pull_free_image_cb, img_out, 1000ms, *cursor)) {
11311196 case platf::capture_e::reinit:
@@ -1281,6 +1346,8 @@ namespace portal {
12811346 int n_dmabuf_infos;
12821347 bool display_is_nvidia = false ; // Track if display GPU is NVIDIA
12831348 std::chrono::nanoseconds delay;
1349+ std::optional<std::uint64_t > last_pts {};
1350+ std::optional<std::uint64_t > last_seq {};
12841351 std::uint64_t sequence {};
12851352 uint32_t framerate;
12861353 static inline std::atomic<uint32_t > previous_height {0 };
0 commit comments