diff --git a/src/platform/linux/graphics.h b/src/platform/linux/graphics.h index 7abbdc0b..89548a23 100644 --- a/src/platform/linux/graphics.h +++ b/src/platform/linux/graphics.h @@ -301,6 +301,8 @@ namespace egl { // PipeWire metadata std::optional pts; std::optional seq; + std::optional pw_damage; + std::optional pw_flags; }; class sws_t { diff --git a/src/platform/linux/portalgrab.cpp b/src/platform/linux/portalgrab.cpp index e4d78fae..9c5ed398 100644 --- a/src/platform/linux/portalgrab.cpp +++ b/src/platform/linux/portalgrab.cpp @@ -723,6 +723,24 @@ namespace portal { ~pipewire_t() { cleanup_stream(); + + pw_thread_loop_lock(loop); + + if (core) { + pw_core_disconnect(core); + core = nullptr; + } + if (context) { + pw_context_destroy(context); + context = nullptr; + } + + pw_thread_loop_unlock(loop); + + pw_thread_loop_stop(loop); + if (fd >= 0) { + close(fd); + } pw_thread_loop_destroy(loop); } @@ -734,6 +752,14 @@ namespace portal { return stream_data.frame_cv; } + bool is_frame_ready() const { + return stream_data.frame_ready; + } + + void set_frame_ready(bool ready) { + stream_data.frame_ready = ready; + } + void init(int stream_fd, int stream_node, std::shared_ptr shared_state) { fd = stream_fd; node = stream_node; @@ -767,21 +793,8 @@ namespace portal { pw_stream_destroy(stream_data.stream); stream_data.stream = nullptr; } - if (core) { - pw_core_disconnect(core); - core = nullptr; - } - if (context) { - pw_context_destroy(context); - context = nullptr; - } pw_thread_loop_unlock(loop); - - pw_thread_loop_stop(loop); - if (fd >= 0) { - close(fd); - } } session_cache_t::instance().invalidate(); } @@ -843,14 +856,14 @@ namespace portal { } // 2. Validate we have a buffer and a signal that it's "new" - if (stream_data.current_buffer && stream_data.frame_ready) { + if (stream_data.current_buffer) { struct spa_buffer *buf = stream_data.current_buffer->buffer; if (buf->datas[0].chunk->size != 0) { const auto img_descriptor = static_cast(img); img_descriptor->frame_timestamp = std::chrono::steady_clock::now(); - // Passthrough PipeWire metadata + // PipeWire header metadata struct spa_meta_header *h = static_cast( spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)) ); @@ -859,6 +872,23 @@ namespace portal { img_descriptor->pts = h->pts; } + // PipeWire flags + if (buf->n_datas > 0) { + img_descriptor->pw_flags = buf->datas[0].chunk->flags; + } + + // PipeWire damage metadata + struct spa_meta_region *damage = (struct spa_meta_region *) spa_buffer_find_meta_data( + stream_data.current_buffer->buffer, + SPA_META_VideoDamage, + sizeof(*damage) + ); + if (damage) { + img_descriptor->pw_damage = (damage->region.size.width > 0 && damage->region.size.height > 0); + } else { + img_descriptor->pw_damage = std::nullopt; + } + if (buf->datas[0].type == SPA_DATA_DmaBuf) { img_descriptor->sd.width = stream_data.format.info.raw.size.width; img_descriptor->sd.height = stream_data.format.info.raw.size.height; @@ -874,10 +904,6 @@ namespace portal { // Point the encoder to the front buffer img->data = stream_data.front_buffer->data(); img->row_pitch = stream_data.local_stride; - - // Reset flags - stream_data.frame_ready = false; - stream_data.current_buffer = nullptr; } } } else { @@ -1088,7 +1114,7 @@ namespace portal { // Ack the buffer type and metadata std::array buffer; - std::array params; + std::array params; int n_params = 0; struct spa_pod_builder pod_builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size()); auto buffer_param = static_cast(spa_pod_builder_add_object(&pod_builder, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_dataType, SPA_POD_Int(buffer_types))); @@ -1097,6 +1123,10 @@ namespace portal { auto meta_param = static_cast(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)))); params[n_params] = meta_param; n_params++; + int videoDamageRegionCount = 16; + auto damage_param = static_cast(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))); + params[n_params] = damage_param; + n_params++; pw_stream_update_params(d->stream, params.data(), n_params); } @@ -1192,48 +1222,33 @@ namespace portal { platf::capture_e snapshot(const pull_free_image_cb_t &pull_free_image_cb, std::shared_ptr &img_out, std::chrono::milliseconds timeout, bool show_cursor) { // FIXME: show_cursor is ignored - auto start_time = std::chrono::steady_clock::now(); + auto deadline = std::chrono::steady_clock::now() + timeout; + int retries = 0; + + while (std::chrono::steady_clock::now() < deadline) { + if (!wait_for_frame(deadline)) { + return stream_stopped.load() ? platf::capture_e::interrupted : platf::capture_e::timeout; + } - while (true) { if (!pull_free_image_cb(img_out)) { return platf::capture_e::interrupted; } - const auto img_egl = static_cast(img_out.get()); + auto *img_egl = static_cast(img_out.get()); img_egl->reset(); pipewire.fill_img(img_egl); - // Check if we got valid data (either DMA-BUF fd or memory pointer) - bool is_valid_data = (img_egl->sd.fds[0] >= 0 || img_egl->data != nullptr); - - // Duplicate detection: PipeWire seq increments on each new frame, - // pts advances with each buffer update. Both must advance to accept frame. - bool is_duplicate = (img_egl->seq.has_value() && img_egl->pts.has_value() && last_pts.has_value() && last_seq.has_value() && img_egl->pts.value() == last_pts.value() && img_egl->seq.value() == last_seq.value()); - - if (is_valid_data && !is_duplicate) { - // Frame found; check deadline - auto end_time = std::chrono::steady_clock::now(); - if (end_time - start_time > timeout) { - return platf::capture_e::timeout; - } - - if (img_egl->seq.has_value() && img_egl->pts.has_value()) { - last_seq = img_egl->seq.value(); - last_pts = img_egl->pts.value(); - } - img_egl->sequence = ++sequence; + // Check if we got valid data (either DMA-BUF fd or memory pointer), then filter duplicates + if ((img_egl->sd.fds[0] >= 0 || img_egl->data != nullptr) && !is_buffer_redundant(img_egl)) { + // Update frame metadata + update_metadata(img_egl, retries); return platf::capture_e::ok; } // No valid frame yet, or it was a duplicate - auto now = std::chrono::steady_clock::now(); - if (now - start_time >= timeout) { - return platf::capture_e::timeout; - } - - std::unique_lock lock(pipewire.frame_mutex()); - pipewire.frame_cv().wait_until(lock, start_time + timeout); + retries++; } + return platf::capture_e::timeout; } std::shared_ptr alloc_img() override { @@ -1274,6 +1289,8 @@ namespace portal { stream_stopped.store(false); previous_height.store(0); previous_width.store(0); + // Delay interrupt signal to give Portal time to detect change + std::this_thread::sleep_for(std::chrono::milliseconds(1500)); return platf::capture_e::interrupted; } else { BOOST_LOG(warning) << "PipeWire stream disconnected. Forcing session reset."sv; @@ -1349,6 +1366,44 @@ namespace portal { } private: + bool is_buffer_redundant(const egl::img_descriptor_t *img) { + // Check for corrupted frame + if (img->pw_flags.has_value() && (img->pw_flags.value() & SPA_CHUNK_FLAG_CORRUPTED)) { + return true; + } + + // If PTS is identical, only drop if damage metadata confirms no change + if (img->pts.has_value() && last_pts.has_value() && img->pts.value() == last_pts.value()) { + return img->pw_damage.has_value() && !img->pw_damage.value(); + } + + return false; + } + + void update_metadata(egl::img_descriptor_t *img, int retries) { + last_seq = img->seq; + last_pts = img->pts; + img->sequence = ++sequence; + + if (retries > 0) { + BOOST_LOG(debug) << "Processed frame after " << retries << " redundant events."sv; + } + } + + bool wait_for_frame(std::chrono::steady_clock::time_point deadline) { + std::unique_lock lock(pipewire.frame_mutex()); + + bool success = pipewire.frame_cv().wait_until(lock, deadline, [&] { + return pipewire.is_frame_ready() || stream_stopped.load(); + }); + + if (success && !stream_stopped.load()) { + pipewire.set_frame_ready(false); + return true; + } + return false; + } + static uint32_t lookup_pw_format(uint64_t fourcc) { for (const auto &fmt : format_map) { if (fmt.fourcc == 0) {