Skip to content

Instantly share code, notes, and snippets.

@SyedAhkam
Last active November 17, 2024 20:16
Show Gist options
  • Save SyedAhkam/6131487ea9caaf6113426ecfb7104a75 to your computer and use it in GitHub Desktop.
Save SyedAhkam/6131487ea9caaf6113426ecfb7104a75 to your computer and use it in GitHub Desktop.
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