Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
vulkan-validation-layers
vulkan-tools
wayland
libxkbcommon
zlib
glib

Expand Down Expand Up @@ -133,7 +134,10 @@
# Required for linux tray icon.
pkgs.gtk3
pkgs.libayatana-appindicator

# SDL runtime libs
pkgs.wayland
pkgs.libxkbcommon
];
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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{
Expand Down
100 changes: 76 additions & 24 deletions src/capture/video/linux/pipewire/pipewire_video.zig
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -384,44 +385,53 @@ 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);

if (tmp == null) {
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
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}

Expand Down Expand Up @@ -636,28 +676,23 @@ 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,
pw.SPA_PARAM_Meta,
.{
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,
Expand All @@ -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",
Expand Down
Loading