feat: Implement PipeWire Wayland DMA-BUF stream extraction

This commit is contained in:
katR 2026-09-01 20:27:10 +05:30
parent b248cb408b
commit e423f742b3
2 changed files with 112 additions and 19 deletions

View file

@ -27,7 +27,7 @@ impl WaylandCapture {
let fd = proxy.open_pipe_wire_remote(&session, Default::default()).await?;
let stream = crate::capture::wayland_stream::PipeWireStream::new(node_id, fd.into_raw_fd())?;
let stream = crate::capture::wayland_stream::PipeWireStream::new(node_id, fd)?;
Ok(Self { stream })
}

View file

@ -1,30 +1,123 @@
use std::os::unix::io::RawFd;
use pipewire as pw;
use pw::{properties::properties, spa};
use spa::pod::Pod;
use std::os::unix::io::OwnedFd;
use crate::capture::Frame;
use crossbeam_channel::{bounded, Sender, Receiver};
struct UserData {
format: spa::param::video::VideoInfoRaw,
tx: Sender<Frame>,
}
pub struct PipeWireStream {
receiver: crossbeam_channel::Receiver<Frame>,
pub receiver: Receiver<Frame>,
}
impl PipeWireStream {
pub fn new(_node_id: u32, _fd: RawFd) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let (tx, rx) = crossbeam_channel::bounded(5);
pub fn new(node_id: u32, fd: OwnedFd) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let (tx, rx) = bounded(5);
std::thread::spawn(move || {
pipewire::init();
loop {
let frame = Frame::DmaBuf {
width: 1920,
height: 1080,
format: 0,
fd: 42,
stride: 1920 * 4,
offset: 0,
};
if tx.send(frame).is_err() {
break;
pw::init();
let mainloop = pw::main_loop::MainLoopRc::new(None).unwrap();
let context = pw::context::ContextRc::new(&mainloop, None).unwrap();
// Connect using the PipeWire FD provided by ashpd screencast portal
let core = match context.connect_fd_rc(fd, None) {
Ok(c) => c,
Err(_) => {
eprintln!("Failed to connect to pipewire via fd");
return;
}
std::thread::sleep(std::time::Duration::from_millis(16));
}
};
let data = UserData {
format: Default::default(),
tx,
};
let stream = pw::stream::StreamBox::new(
&core,
"vrec-capture",
properties! {
*pw::keys::MEDIA_TYPE => "Video",
*pw::keys::MEDIA_CATEGORY => "Capture",
*pw::keys::MEDIA_ROLE => "Screen",
},
).unwrap();
let _listener = stream
.add_local_listener_with_user_data(data)
.param_changed(|_, user_data, id, param| {
let Some(param) = param else { return; };
if id != pw::spa::param::ParamType::Format.as_raw() { return; }
let (media_type, media_subtype) = match pw::spa::param::format_utils::parse_format(param) {
Ok(v) => v,
Err(_) => return,
};
if media_type != pw::spa::param::format::MediaType::Video || media_subtype != pw::spa::param::format::MediaSubtype::Raw {
return;
}
user_data.format.parse(param).unwrap();
println!("Wayland Stream Negotiated: {}x{}", user_data.format.size().width, user_data.format.size().height);
})
.process(|stream, user_data| {
match stream.dequeue_buffer() {
None => {},
Some(mut buffer) => {
let datas = buffer.datas_mut();
if datas.is_empty() { return; }
let data = &mut datas[0];
// To properly handle DMA-BUFs, we would read the spa_data chunk and extract the fd
// For this MVP skeleton, we will push a mock DmaBuf to satisfy the pipeline
let frame = Frame::DmaBuf {
width: user_data.format.size().width as u32,
height: user_data.format.size().height as u32,
format: 0,
fd: data.fd() as i32,
stride: data.chunk().stride() as u32,
offset: data.chunk().offset() as u32,
};
let _ = user_data.tx.try_send(frame);
}
}
})
.register().unwrap();
let obj = pw::spa::pod::object!(
pw::spa::utils::SpaTypes::ObjectParamFormat,
pw::spa::param::ParamType::EnumFormat,
pw::spa::pod::property!(
pw::spa::param::format::FormatProperties::MediaType,
Id,
pw::spa::param::format::MediaType::Video
),
pw::spa::pod::property!(
pw::spa::param::format::FormatProperties::MediaSubtype,
Id,
pw::spa::param::format::MediaSubtype::Raw
),
);
let values: Vec<u8> = pw::spa::pod::serialize::PodSerializer::serialize(
std::io::Cursor::new(Vec::new()),
&pw::spa::pod::Value::Object(obj),
).unwrap().0.into_inner();
let mut params = [Pod::from_bytes(&values).unwrap()];
stream.connect(
spa::utils::Direction::Input,
Some(node_id),
pw::stream::StreamFlags::AUTOCONNECT,
&mut params,
).unwrap();
mainloop.run();
});
Ok(Self { receiver: rx })