diff --git a/README.md b/README.md index bd11a6c..720d0ab 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ reboot. ```sh # Delete -rm ~/.local/share/flatpak/db/screencast ~/.local/share/flatpak/db/screencast.bak +rm ~/.local/share/flatpak/db/screencast # OR move it to a backup mv ~/.local/share/flatpak/db/screencast ~/.local/share/flatpak/db/screencast.bak diff --git a/flake.nix b/flake.nix index 412bc63..2d4c3d3 100644 --- a/flake.nix +++ b/flake.nix @@ -102,6 +102,7 @@ vulkan-validation-layers vulkan-tools wayland + libxkbcommon zlib glib @@ -133,7 +134,10 @@ # Required for linux tray icon. pkgs.gtk3 pkgs.libayatana-appindicator + + # SDL runtime libs pkgs.wayland + pkgs.libxkbcommon ]; }; } diff --git a/src/capture/video/linux/pipewire/pipewire_frame_buffer_manager.zig b/src/capture/video/linux/pipewire/pipewire_frame_buffer_manager.zig index b1d7d64..52624b6 100644 --- a/src/capture/video/linux/pipewire/pipewire_frame_buffer_manager.zig +++ b/src/capture/video/linux/pipewire/pipewire_frame_buffer_manager.zig @@ -2,10 +2,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const pw = @import("pipewire").c; -const c = @import("../../../../common/linux/pipewire_include.zig").c; const vk = @import("vulkan"); const Vulkan = @import("../../../../vulkan/vulkan.zig").Vulkan; -const BufferedChan = @import("../../../../channel.zig").BufferedChan; const pipewire_util = @import("./pipewire_util.zig"); const PipewireFrameBufferImage = struct { @@ -86,17 +84,18 @@ pub const PipewireFrameBufferManager = struct { self: *Self, pwb: *pw.struct_pw_buffer, info: pw.spa_video_info_raw, + plane_count: u32, ) !struct { frame_buffer: *PipewireFrameBuffer, wait_semaphore: vk.Semaphore } { + assert(plane_count > 0); const _frame_buffer = self.frame_buffers.getPtr(pwb); // Should never be null here. If it is, there are big problems. assert(_frame_buffer != null); const frame_buffer = _frame_buffer.?; - const n_datas = frame_buffer.pwb.buffer[0].n_datas; - var subresource_layouts = try std.ArrayList(vk.SubresourceLayout).initCapacity(self.allocator, n_datas); + var subresource_layouts = try std.ArrayList(vk.SubresourceLayout).initCapacity(self.allocator, plane_count); defer subresource_layouts.deinit(self.allocator); - for (0..n_datas) |i| { + for (0..plane_count) |i| { const buf_data = frame_buffer.pwb.buffer[0].datas[i]; const row_pitch: u64 = @intCast(buf_data.chunk[0].stride); const subresource_layout = vk.SubresourceLayout{ diff --git a/src/capture/video/linux/pipewire/pipewire_video.zig b/src/capture/video/linux/pipewire/pipewire_video.zig index e86952f..2dc3938 100644 --- a/src/capture/video/linux/pipewire/pipewire_video.zig +++ b/src/capture/video/linux/pipewire/pipewire_video.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const assert = std.debug.assert; const Allocator = std.mem.Allocator; const Arc = @import("../../../../arc.zig").Arc; const pw = @import("pipewire").c; @@ -384,8 +385,10 @@ pub const PipewireVideo = struct { return; } - // Grab the newest buffer. + // Grab the most recent buffer. var pipewire_buffer: ?*pw.struct_pw_buffer = null; + var pipewire_buffer_plane_count: u32 = 0; + while (true) { const tmp: ?*pw.struct_pw_buffer = pw.pw_stream_dequeue_buffer(self.stream); @@ -393,35 +396,42 @@ pub const PipewireVideo = struct { break; } - // Only keep buffers that are dmabuf. - if (tmp.?.buffer == null or tmp.?.buffer[0].datas[0].type != pw.SPA_DATA_DmaBuf) { + const plane_count = get_dmabuf_plane_count(tmp.?.buffer); + + // Only keep dma buffers that have at least one plane. + if (plane_count == 0) { _ = pw.pw_stream_queue_buffer(self.stream.?, tmp.?); continue; } + assert(plane_count > 0); + if (pipewire_buffer) |pwb| { _ = pw.pw_stream_queue_buffer(self.stream.?, pwb); } pipewire_buffer = tmp; + pipewire_buffer_plane_count = plane_count; } - const pwb = pipewire_buffer.?; + const pwb = pipewire_buffer orelse return; + + assert(pipewire_buffer_plane_count > 0); // TODO: Should gracefully handle these errors. defer _ = pw.pw_stream_queue_buffer(self.stream.?, pwb); - const vulkan_image = self.pipewire_frame_buffer_manager.?.get_vulkan_image(pwb, self.info.?) catch |err| { - log.err("[stream_process_callback] unable to get buffer: {}", .{err}); - return; - }; - const header = pw.spa_buffer_find_meta_data(pwb.buffer, pw.SPA_META_Header, @sizeOf(pw.spa_meta_header)); if (header == null) { log.err("[stream_process_callback] unable to get metadata header. This should never happen.", .{}); return; } - const metadata = @as(*pw.spa_meta_header, @ptrCast(@alignCast(header.?))); + const metadata: *pw.spa_meta_header = @ptrCast(@alignCast(header.?)); + + if (metadata.flags & pw.SPA_META_HEADER_FLAG_CORRUPTED != 0) { + return; + } + var timestamp_ns = self.select_best_timestamp(metadata); // Pipewire can occasionally queue a buffer with the same timestamp @@ -435,6 +445,11 @@ pub const PipewireVideo = struct { self.previous_frame_timestamp_ns = timestamp_ns; + const vulkan_image = self.pipewire_frame_buffer_manager.?.get_vulkan_image(pwb, self.info.?, pipewire_buffer_plane_count) catch |err| { + log.err("[stream_process_callback] unable to get buffer: {}", .{err}); + return; + }; + const copy_data = blk: { const capture_ring_buffer = self.vulkan.capture_ring_buffer.lock(); defer capture_ring_buffer.unlock(); @@ -473,6 +488,37 @@ pub const PipewireVideo = struct { } } + /// Get the plane count from a buffer if it is a valid dmabuf, otherwise + /// return 0. + fn get_dmabuf_plane_count(buffer: ?[*c]pw.spa_buffer) u32 { + const _buffer = buffer orelse return 0; + if (_buffer[0].n_datas == 0 or + _buffer[0].datas[0].chunk == null or + _buffer[0].datas[0].chunk[0].size == 0) + { + return 0; + } + + var plane_count: u32 = 0; + while (plane_count < _buffer[0].n_datas) : (plane_count += 1) { + if (_buffer[0].datas[plane_count].type != pw.SPA_DATA_DmaBuf) { + break; + } + } + + for (0..plane_count) |i| { + const data = _buffer[0].datas[i]; + if (data.chunk == null) { + return 0; + } + if (data.chunk[0].flags & pw.SPA_CHUNK_FLAG_CORRUPTED != 0) { + return 0; + } + } + + return plane_count; + } + /// Some DE/compositors vary on where they store the presentation timestamp. fn select_best_timestamp(self: *Self, metadata: *const pw.spa_meta_header) i128 { const raw_metadata_pts_ns: i128 = @intCast(metadata.pts); @@ -490,12 +536,6 @@ pub const PipewireVideo = struct { source = .meta_pts; } - // Limit the logging otherwise it will get spammed. - if (self.timestamp_source_log_count >= 10) { - log.info("[select_best_timestamp] video timestamp source: {}", .{source}); - self.timestamp_source_log_count += 1; - } - return timestamp_ns; } @@ -636,7 +676,6 @@ pub const PipewireVideo = struct { return; }; - // damage spa_pod_params.params.append(self.allocator, @ptrCast(@alignCast(c_def.spa_pod_builder_add_object( &builder, pw.SPA_TYPE_OBJECT_ParamMeta, @@ -644,20 +683,16 @@ pub const PipewireVideo = struct { .{ pw.SPA_PARAM_META_type, "I", - pw.SPA_META_VideoDamage, + pw.SPA_META_VideoCrop, pw.SPA_PARAM_META_size, - "?ri", - @as(i32, 3), - @as(i32, @sizeOf(pw.spa_meta_region) * 16), - @as(i32, @sizeOf(pw.spa_meta_region) * 1), - @as(i32, @sizeOf(pw.spa_meta_region) * 16), + "i", + @as(i32, @intCast(@sizeOf(pw.spa_meta_region))), }, )))) catch |err| { log.err("[send_stream_params] spa_pod_params.params.append error: {}", .{err}); return; }; - // cursor spa_pod_params.params.append(self.allocator, @ptrCast(@alignCast(c_def.spa_pod_builder_add_object( &builder, pw.SPA_TYPE_OBJECT_ParamMeta, @@ -678,6 +713,23 @@ pub const PipewireVideo = struct { return; }; + spa_pod_params.params.append(self.allocator, @ptrCast(@alignCast(c_def.spa_pod_builder_add_object( + &builder, + pw.SPA_TYPE_OBJECT_ParamMeta, + pw.SPA_PARAM_Meta, + .{ + pw.SPA_PARAM_META_type, + "I", + pw.SPA_META_VideoTransform, + pw.SPA_PARAM_META_size, + "i", + @as(i32, @intCast(@sizeOf(pw.spa_meta_videotransform))), + }, + )))) catch |err| { + log.err("[send_stream_params] spa_pod_params.params.append error: {}", .{err}); + return; + }; + spa_pod_params.params.append(self.allocator, @ptrCast(@alignCast(c_def.spa_pod_builder_add_object(&builder, pw.SPA_TYPE_OBJECT_ParamBuffers, pw.SPA_PARAM_Buffers, .{ pw.SPA_PARAM_BUFFERS_dataType, "i",