Instantly share code, notes, and snippets.
Last active
November 17, 2024 20:16
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save SyedAhkam/6131487ea9caaf6113426ecfb7104a75 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::os::fd::{IntoRawFd, OwnedFd}; | |
use ashpd::desktop::{ | |
screencast::{CursorMode, Screencast, SourceType, Stream as AshStream}, | |
PersistMode, | |
}; | |
use pipewire as pw; | |
use pw::{properties::properties, spa}; | |
use spa::pod::Pod; | |
struct UserData { | |
format: spa::param::video::VideoInfoRaw, | |
} | |
async fn open_portal() -> ashpd::Result<(AshStream, OwnedFd)> { | |
let proxy = Screencast::new().await?; | |
let session = proxy.create_session().await?; | |
proxy | |
.select_sources( | |
&session, | |
CursorMode::Embedded, | |
SourceType::Monitor.into(), | |
false, | |
None, | |
PersistMode::DoNot, | |
) | |
.await?; | |
let response = proxy.start(&session, None).await?.response()?; | |
let stream = response.streams().first().unwrap().to_owned(); | |
let fd = proxy.open_pipe_wire_remote(&session).await?; | |
Ok((stream, fd)) | |
} | |
async fn start_streaming(node_id: u32, _fd: OwnedFd) -> Result<(), pw::Error> { | |
pw::init(); | |
let mainloop = pw::main_loop::MainLoop::new(None)?; | |
let context = pw::context::Context::new(&mainloop)?; | |
let core = context.connect(None)?; | |
let data = UserData { | |
format: Default::default(), | |
}; | |
let stream = pw::stream::Stream::new( | |
&core, | |
"video-test", | |
properties! { | |
*pw::keys::MEDIA_TYPE => "Video", | |
*pw::keys::MEDIA_CATEGORY => "Capture", | |
*pw::keys::MEDIA_ROLE => "Screen", | |
}, | |
)?; | |
/*let stream = pw::stream::Stream::<UserData>::with_user_data( | |
&mainloop, | |
"video-test", | |
, | |
data, | |
)*/ | |
let _listener = stream | |
.add_local_listener_with_user_data(data) | |
.state_changed(|_, _, old, new| { | |
println!("State changed: {:?} -> {:?}", old, new); | |
}) | |
.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) | |
.expect("Failed to parse param changed to VideoInfoRaw"); | |
println!("got video format:"); | |
println!( | |
" format: {} ({:?})", | |
user_data.format.format().as_raw(), | |
user_data.format.format() | |
); | |
println!( | |
" size: {}x{}", | |
user_data.format.size().width, | |
user_data.format.size().height | |
); | |
println!( | |
" framerate: {}/{}", | |
user_data.format.framerate().num, | |
user_data.format.framerate().denom | |
); | |
// prepare to render video of this size | |
}) | |
.process(|stream, _| { | |
match stream.dequeue_buffer() { | |
None => println!("out of buffers"), | |
Some(mut buffer) => { | |
let datas = buffer.datas_mut(); | |
if datas.is_empty() { | |
return; | |
} | |
// copy frame data to screen | |
let data = &mut datas[0]; | |
println!("got a frame of size {}", data.chunk().size()); | |
} | |
} | |
}) | |
.register()?; | |
println!("Created stream {:#?}", stream); | |
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 | |
), | |
pw::spa::pod::property!( | |
pw::spa::param::format::FormatProperties::VideoFormat, | |
Choice, | |
Enum, | |
Id, | |
pw::spa::param::video::VideoFormat::RGB, | |
pw::spa::param::video::VideoFormat::RGB, | |
pw::spa::param::video::VideoFormat::RGBA, | |
pw::spa::param::video::VideoFormat::RGBx, | |
pw::spa::param::video::VideoFormat::BGRx, | |
pw::spa::param::video::VideoFormat::YUY2, | |
pw::spa::param::video::VideoFormat::I420, | |
), | |
pw::spa::pod::property!( | |
pw::spa::param::format::FormatProperties::VideoSize, | |
Choice, | |
Range, | |
Rectangle, | |
pw::spa::utils::Rectangle { | |
width: 320, | |
height: 240 | |
}, | |
pw::spa::utils::Rectangle { | |
width: 1, | |
height: 1 | |
}, | |
pw::spa::utils::Rectangle { | |
width: 4096, | |
height: 4096 | |
} | |
), | |
pw::spa::pod::property!( | |
pw::spa::param::format::FormatProperties::VideoFramerate, | |
Choice, | |
Range, | |
Fraction, | |
pw::spa::utils::Fraction { num: 25, denom: 1 }, | |
pw::spa::utils::Fraction { num: 0, denom: 1 }, | |
pw::spa::utils::Fraction { | |
num: 1000, | |
denom: 1 | |
} | |
), | |
); | |
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 | pw::stream::StreamFlags::MAP_BUFFERS, | |
&mut params, | |
)?; | |
println!("Connected stream"); | |
mainloop.run(); | |
Ok(()) | |
} | |
smol_macros::main! { | |
async fn main() { | |
let (stream, fd) = open_portal().await.expect("failed to open portal"); | |
println!("node id {}, fd {}", stream.pipe_wire_node_id(), fd.try_clone().unwrap().into_raw_fd()); | |
if let Err(e) = start_streaming(stream.pipe_wire_node_id(), fd).await { | |
eprintln!("Error: {}", e); | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment