-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
examples: Add screen cast with pipewire (#247)
Fixes #155
- Loading branch information
Showing
5 changed files
with
241 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
[default.extend-words] | ||
# Ignore false-positives | ||
eis = "eis" | ||
datas = "datas" |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
use std::os::fd::{IntoRawFd, OwnedFd}; | ||
|
||
use ashpd::desktop::{ | ||
screencast::{CursorMode, Screencast, SourceType, Stream as ScreencastStream}, | ||
PersistMode, | ||
}; | ||
use pipewire as pw; | ||
use pw::{properties::properties, spa}; | ||
|
||
struct UserData { | ||
format: spa::param::video::VideoInfoRaw, | ||
} | ||
|
||
async fn open_portal() -> ashpd::Result<(ScreencastStream, OwnedFd)> { | ||
let proxy = Screencast::new().await?; | ||
let session = proxy.create_session().await?; | ||
proxy | ||
.select_sources( | ||
&session, | ||
CursorMode::Hidden, | ||
SourceType::Monitor.into(), | ||
false, | ||
None, | ||
PersistMode::DoNot, | ||
) | ||
.await?; | ||
|
||
let response = proxy.start(&session, None).await?.response()?; | ||
let stream = response | ||
.streams() | ||
.first() | ||
.expect("no stream found / selected") | ||
.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_fd(fd, 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 _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!( | ||
"\tformat: {} ({:?})", | ||
user_data.format.format().as_raw(), | ||
user_data.format.format() | ||
); | ||
println!( | ||
"\tsize: {}x{}", | ||
user_data.format.size().width, | ||
user_data.format.size().height | ||
); | ||
println!( | ||
"\tframerate: {}/{}", | ||
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 = [spa::pod::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(()) | ||
} | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
let (stream, fd) = open_portal().await.expect("failed to open portal"); | ||
let pipewire_node_id = stream.pipe_wire_node_id(); | ||
|
||
println!( | ||
"node id {}, fd {}", | ||
pipewire_node_id, | ||
&fd.try_clone().unwrap().into_raw_fd() | ||
); | ||
|
||
if let Err(e) = start_streaming(pipewire_node_id, fd).await { | ||
eprintln!("Error: {}", e); | ||
}; | ||
} |
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
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