Skip to content

fix: Add handling for missing VideoFormat on Linux #153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
43 changes: 41 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ objc = "0.2.7"
pipewire = "0.8.0"
dbus = "0.9.7"
rand = "0.8.5"
rustix = { version = "1.0.2", features = ["mm", "param", "fs"] }
6 changes: 6 additions & 0 deletions src/capturer/engine/linux/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,9 @@ impl<T> From<PoisonError<T>> for LinCapError {
Self::new(e.to_string())
}
}

impl From<rustix::io::Errno> for LinCapError {
fn from(e: rustix::io::Errno) -> Self {
Self::new(e.to_string())
}
}
55 changes: 55 additions & 0 deletions src/capturer/engine/linux/ioctl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// https://github.com/mripard/dma-buf/blob/main/src/ioctl.rs

use std::os::fd::BorrowedFd;

use rustix::{
io::Errno,
ioctl::{ioctl, opcode, Setter},
};

use super::error::LinCapError;

const DMA_BUF_BASE: u8 = b'b';
const DMA_BUF_IOCTL_SYNC: u8 = 0;

const DMA_BUF_SYNC_READ: u64 = 1 << 0;
const DMA_BUF_SYNC_START: u64 = 0 << 2;
const DMA_BUF_SYNC_END: u64 = 1 << 2;

#[derive(Default)]
#[repr(C)]
struct DmaBufSync {
flags: u64,
}

fn dma_buf_sync_ioctl(fd: BorrowedFd<'_>, flags: u64) -> Result<(), Errno> {
let sync = DmaBufSync { flags };

// SAFETY: This function is unsafe because the opcode has to be valid, and the value type must
// match. We have checked those, so we're good.
let ioctl_type = unsafe {
Setter::<{ opcode::write::<DmaBufSync>(DMA_BUF_BASE, DMA_BUF_IOCTL_SYNC) }, DmaBufSync>::new(
sync,
)
};

// SAFETY: This function is unsafe because the driver isn't guaranteed to implement the ioctl,
// and to implement it properly. We don't have much of a choice and still have to trust the
// kernel there.
unsafe { ioctl(fd, ioctl_type) }
}

fn dma_buf_sync(fd: BorrowedFd<'_>, flags: u64) -> Result<(), LinCapError> {
dma_buf_sync_ioctl(fd, flags)?;
Ok(())
}

pub(crate) fn dma_buf_begin_cpu_read_access(fd: BorrowedFd<'_>) -> Result<(), LinCapError> {
dma_buf_sync(fd, DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ)?;
Ok(())
}

pub(crate) fn dma_buf_end_cpu_read_access(fd: BorrowedFd<'_>) -> Result<(), LinCapError> {
dma_buf_sync(fd, DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ)?;
Ok(())
}
83 changes: 75 additions & 8 deletions src/capturer/engine/linux/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{
mem::size_of,
os::unix::io::RawFd,
sync::{
atomic::{AtomicBool, AtomicU8},
mpsc::{self, sync_channel, SyncSender},
Expand All @@ -22,13 +23,20 @@ use pw::{
},
pod::{Pod, Property},
sys::{
spa_buffer, spa_meta_header, SPA_META_Header, SPA_PARAM_META_size, SPA_PARAM_META_type,
spa_buffer, spa_meta_header, SPA_DATA_DmaBuf as SPA_DATA_DMA_BUF,
SPA_DATA_MemFd as SPA_DATA_MEM_FD, SPA_DATA_MemPtr as SPA_DATA_MEM_PTR,
SPA_META_Header, SPA_PARAM_META_size, SPA_PARAM_META_type,
},
utils::{Direction, SpaTypes},
},
stream::{StreamRef, StreamState},
};

use rustix::{
fd::BorrowedFd,
mm::{mmap, munmap, MapFlags, ProtFlags},
};

use crate::{
capturer::Options,
frame::{BGRxFrame, Frame, RGBFrame, RGBxFrame, XBGRFrame},
Expand All @@ -37,6 +45,7 @@ use crate::{
use self::{error::LinCapError, portal::ScreenCastPortal};

mod error;
mod ioctl;
mod portal;

static CAPTURER_STATE: AtomicU8 = AtomicU8::new(0);
Expand Down Expand Up @@ -110,6 +119,41 @@ unsafe fn get_timestamp(buffer: *mut spa_buffer) -> i64 {
}
}

unsafe fn fd_read(buffer: *mut spa_buffer, is_dma_buff: bool) -> Result<Vec<u8>, LinCapError> {
let borrowed_fd = BorrowedFd::borrow_raw((*(*buffer).datas).fd as RawFd);
let offset = u64::try_from((*(*(*buffer).datas).chunk).offset).unwrap();

let stat = rustix::fs::fstat(borrowed_fd)?;

let len = usize::try_from(stat.st_size)
.unwrap()
.next_multiple_of(rustix::param::page_size());

let mmap_ptr = mmap(
std::ptr::null_mut(),
len,
ProtFlags::READ,
MapFlags::SHARED,
borrowed_fd,
offset,
)?;

if is_dma_buff {
ioctl::dma_buf_begin_cpu_read_access(borrowed_fd)?;
}

let data_slice = std::slice::from_raw_parts(mmap_ptr as *mut u8, len);
let frame_vec = data_slice.to_vec();

if is_dma_buff {
ioctl::dma_buf_end_cpu_read_access(borrowed_fd)?;
}

munmap(mmap_ptr, len)?;

Ok(frame_vec)
}

fn process_callback(stream: &StreamRef, user_data: &mut ListenerUserData) {
let buffer = unsafe { stream.dequeue_raw_buffer() };
if !buffer.is_null() {
Expand All @@ -125,14 +169,23 @@ fn process_callback(stream: &StreamRef, user_data: &mut ListenerUserData) {
return;
}
let frame_size = user_data.format.size();
let frame_data: Vec<u8> = unsafe {
std::slice::from_raw_parts(
(*(*buffer).datas).data as *mut u8,
(*(*buffer).datas).maxsize as usize,
)
.to_vec()
let frame_data: Vec<u8> = match unsafe { (*(*buffer).datas).type_ } {
SPA_DATA_DMA_BUF => {
if user_data.format.modifier() != 0 {
panic!("Unsupported modifier, only linear modifier is supported");
}

unsafe { fd_read(buffer, true) }.unwrap()
}
SPA_DATA_MEM_FD | SPA_DATA_MEM_PTR => unsafe {
std::slice::from_raw_parts(
(*(*buffer).datas).data as *mut u8,
(*(*buffer).datas).maxsize as usize,
)
.to_vec()
},
_ => panic!("Unsupported spa data received"),
};

if let Err(e) = match user_data.format.format() {
VideoFormat::RGBx => user_data.tx.send(Frame::RGBx(RGBxFrame {
display_time: timestamp as u64,
Expand All @@ -158,6 +211,12 @@ fn process_callback(stream: &StreamRef, user_data: &mut ListenerUserData) {
height: frame_size.height as i32,
data: frame_data,
})),
VideoFormat::RGBA => user_data.tx.send(Frame::RGBx(RGBxFrame {
display_time: timestamp as u64,
width: frame_size.width as i32,
height: frame_size.height as i32,
data: frame_data,
})),
_ => panic!("Unsupported frame format received"),
} {
eprintln!("{e}");
Expand Down Expand Up @@ -256,6 +315,14 @@ fn pipewire_capturer(
denom: 1
}
),
// Ask linear modifier from pipewire.
// Nothing make sure that pipewire will give us linear modifier,
// it is determined by how xdg portal backend is implemented.
pw::spa::pod::property!(
pw::spa::param::format::FormatProperties::VideoModifier,
Long,
0 // Linear modifier, found in link https://github.com/dzfranklin/drm-fourcc-rs/blob/main/src/consts.rs#L134
),
);

let metas_obj = pw::spa::pod::object!(
Expand Down