diff --git a/src/format.rs b/src/format.rs index 5e09e918..3ae453f3 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,5 +1,8 @@ use { crate::{ + pipewire::pw_pod::{ + SPA_VIDEO_FORMAT_BGRx, SpaVideoFormat, SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_NV12, + }, render::sys::{GLint, GL_BGRA_EXT, GL_UNSIGNED_BYTE}, utils::debug_fn::debug_fn, }, @@ -19,6 +22,7 @@ pub struct Format { pub external_only_guess: bool, pub has_alpha: bool, pub shm_supported: bool, + pub pipewire: SpaVideoFormat, } static FORMATS_MAP: Lazy> = Lazy::new(|| { @@ -29,10 +33,22 @@ static FORMATS_MAP: Lazy> = Lazy::new(|| { map }); +static PW_FORMATS_MAP: Lazy> = Lazy::new(|| { + let mut map = AHashMap::new(); + for format in FORMATS { + assert!(map.insert(format.pipewire, format).is_none()); + } + map +}); + pub fn formats() -> &'static AHashMap { &*FORMATS_MAP } +pub fn pw_formats() -> &'static AHashMap { + &*PW_FORMATS_MAP +} + const fn fourcc_code(a: char, b: char, c: char, d: char) -> u32 { (a as u32) | ((b as u32) << 8) | ((c as u32) << 16) | ((d as u32) << 24) } @@ -77,6 +93,7 @@ pub static FORMATS: &[Format] = &[ external_only_guess: false, has_alpha: true, shm_supported: true, + pipewire: SPA_VIDEO_FORMAT_BGRA, }, Format { name: "xrgb8888", @@ -88,6 +105,7 @@ pub static FORMATS: &[Format] = &[ external_only_guess: false, has_alpha: false, shm_supported: true, + pipewire: SPA_VIDEO_FORMAT_BGRx, }, Format { name: "nv12", @@ -99,6 +117,7 @@ pub static FORMATS: &[Format] = &[ external_only_guess: true, has_alpha: false, shm_supported: false, + pipewire: SPA_VIDEO_FORMAT_NV12, }, // Format { // id: fourcc_code('C', '8', ' ', ' '), diff --git a/src/ifs/xdg_positioner.rs b/src/ifs/xdg_positioner.rs index 5c645464..2275e43c 100644 --- a/src/ifs/xdg_positioner.rs +++ b/src/ifs/xdg_positioner.rs @@ -8,7 +8,6 @@ use { utils::buffd::{MsgParser, MsgParserError}, wire::{xdg_positioner::*, XdgPositionerId}, }, - bitflags::bitflags, std::{cell::RefCell, rc::Rc}, thiserror::Error, }; @@ -53,7 +52,7 @@ impl Edge { } } -bitflags! { +bitflags::bitflags! { #[derive(Default)] pub struct CA: u32 { const NONE = 0; diff --git a/src/macros.rs b/src/macros.rs index 193342e0..da5a4db5 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -489,3 +489,181 @@ macro_rules! containing_node_impl { } }; } + +macro_rules! bitflags { + ($name:ident: $rep:ty; $($var:ident = $val:expr,)*) => { + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct $name(pub $rep); + + $( + #[allow(dead_code)] + pub const $var: $name = $name($val); + )* + + #[allow(dead_code)] + impl $name { + pub fn none() -> Self { + Self(0) + } + + pub fn is_some(self) -> bool { + self.0 != 0 + } + } + + impl crate::utils::bitflags::BitflagsExt for $name { + fn contains(self, other: Self) -> bool { + self.0 & other.0 == other.0 + } + + fn not_contains(self, other: Self) -> bool { + self.0 & other.0 != other.0 + } + + fn intersects(self, other: Self) -> bool { + self.0 & other.0 != 0 + } + } + + impl std::ops::BitOr for $name { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + Self(self.0 | rhs.0) + } + } + + impl std::ops::BitAnd for $name { + type Output = Self; + + fn bitand(self, rhs: Self) -> Self::Output { + Self(self.0 & rhs.0) + } + } + + impl std::ops::BitOrAssign for $name { + fn bitor_assign(&mut self, rhs: Self) { + self.0 |= rhs.0; + } + } + + impl std::ops::BitAndAssign for $name { + fn bitand_assign(&mut self, rhs: Self) { + self.0 &= rhs.0; + } + } + + impl std::ops::Not for $name { + type Output = Self; + + fn not(self) -> Self::Output { + Self(!self.0) + } + } + + impl std::fmt::Debug for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut any = false; + let mut v = self.0; + $( + if v & $val == $val { + if any { + write!(f, "|")?; + } + any = true; + write!(f, "{}", stringify!($var))?; + v &= !$val; + } + )* + if !any || v != 0 { + if any { + write!(f, "|")?; + } + write!(f, "0x{:x}", v)?; + } + Ok(()) + } + } + } +} + +macro_rules! pw_opcodes { + ($name:ident; $($var:ident = $val:expr,)*) => { + #[derive(Copy, Clone, Debug)] + #[allow(dead_code)] + pub enum $name { + $( + $var, + )* + } + + #[allow(dead_code)] + impl $name { + pub fn from_id(id: u8) -> Option { + let v = match id { + $($val => Self::$var,)* + _ => return None, + }; + Some(v) + } + + pub fn name(self) -> &'static str { + match self { + $(Self::$var => stringify!($var),)* + } + } + } + + impl crate::pipewire::pw_object::PwOpcode for $name { + fn id(&self) -> u8 { + match self { + $(Self::$var => $val,)* + } + } + } + } +} + +macro_rules! pw_object_base { + ($name:ident, $if:expr, $events:ident; $($event:ident => $method:ident,)*) => { + impl crate::pipewire::pw_object::PwObjectBase for $name { + fn data(&self) -> &crate::pipewire::pw_object::PwObjectData { + &self.data + } + + fn interface(&self) -> &str { + $if + } + + fn handle_msg(self: std::rc::Rc, opcode: u8, parser: crate::pipewire::pw_parser::PwParser<'_>) -> Result<(), crate::pipewire::pw_object::PwObjectError> { + match $events::from_id(opcode) { + None => Err(crate::pipewire::pw_object::PwObjectError { + interface: $if, + source: crate::pipewire::pw_object::PwObjectErrorType::UnknownEvent(opcode), + }), + Some(m) => { + let (res, method) = match m { + $( + $events::$event => (self.$method(parser), stringify!($event)), + )* + }; + match res { + Ok(_) => Ok(()), + Err(source) => Err(crate::pipewire::pw_object::PwObjectError { + interface: $if, + source: crate::pipewire::pw_object::PwObjectErrorType::EventError { + method, + source: Box::new(source), + }, + }) + } + }, + } + } + + fn event_name(&self, opcode: u8) -> Option<&'static str> { + $events::from_id(opcode).map(|o| o.name()) + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 6a577302..f7276334 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,6 +59,7 @@ mod logger; mod logind; mod object; mod pango; +mod pipewire; mod rect; mod render; mod screenshoter; diff --git a/src/pipewire.rs b/src/pipewire.rs new file mode 100644 index 00000000..2c635bc8 --- /dev/null +++ b/src/pipewire.rs @@ -0,0 +1,9 @@ +#![allow(dead_code)] + +pub mod pw_con; +pub mod pw_formatter; +pub mod pw_ifs; +pub mod pw_mem; +pub mod pw_object; +pub mod pw_parser; +pub mod pw_pod; diff --git a/src/pipewire/pw_con.rs b/src/pipewire/pw_con.rs new file mode 100644 index 00000000..718a386a --- /dev/null +++ b/src/pipewire/pw_con.rs @@ -0,0 +1,451 @@ +use { + crate::{ + async_engine::{AsyncEngine, SpawnedFuture}, + io_uring::IoUring, + pipewire::{ + pw_formatter::{format, PwFormatter}, + pw_ifs::{ + pw_client::{PwClient, PwClientMethods}, + pw_client_node::{ + PwClientNode, PW_CLIENT_NODE_FACTORY, PW_CLIENT_NODE_INTERFACE, + PW_CLIENT_NODE_VERSION, + }, + pw_core::{PwCore, PwCoreMethods, PW_CORE_VERSION}, + pw_registry::{PwRegistry, PW_REGISTRY_VERSION}, + }, + pw_mem::PwMemPool, + pw_object::{PwObject, PwObjectData, PwObjectError, PwOpcode}, + pw_parser::{PwParser, PwParserError}, + }, + utils::{ + bitfield::Bitfield, + bufio::{BufIo, BufIoError, BufIoIncoming, BufIoMessage}, + clonecell::CloneCell, + copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, + numcell::NumCell, + oserror::OsError, + xrd::xrd, + }, + }, + std::{ + cell::{Cell, RefCell}, + fmt::Display, + io::Write, + rc::{Rc, Weak}, + }, + thiserror::Error, + uapi::{c, OwnedFd}, +}; + +#[derive(Debug, Error)] +pub enum PwConError { + #[error("Could not create a unix socket")] + CreateSocket(#[source] OsError), + #[error("Could not connect to the pipewire daemon")] + ConnectSocket(#[source] OsError), + #[error(transparent)] + BufIoError(#[from] BufIoError), + #[error("Server did not sent a required fd")] + MissingFd, + #[error("XDG_RUNTIME_DIR is not set")] + XrdNotSet, + #[error(transparent)] + PwObjectError(#[from] PwObjectError), + #[error(transparent)] + PwParserError(#[from] PwParserError), +} + +pub struct PwConHolder { + pub con: Rc, + outgoing: Cell>>, + incoming: Cell>>, +} + +pub struct PwCon { + send_seq: NumCell, + pub io: Rc, + holder: CloneCell>, + dead: Cell, + pub objects: CopyHashMap>, + pub ids: RefCell, + pub mem: PwMemPool, + pub ring: Rc, + pub eng: Rc, + pub owner: CloneCell>>, + + registry_generation: Cell, + ack_registry_generation: Cell, +} + +pub trait PwConOwner { + fn killed(&self) {} +} + +impl PwCon { + pub fn create_client_node(self: &Rc, props: &[(String, String)]) -> Rc { + let node = Rc::new(PwClientNode { + data: self.proxy_data(), + con: self.clone(), + ios: Default::default(), + owner: CloneCell::new(None), + ports: Default::default(), + port_out_free: RefCell::new(Default::default()), + port_in_free: RefCell::new(Default::default()), + activation: Default::default(), + transport_in: Cell::new(None), + transport_out: Default::default(), + activations: Default::default(), + }); + if !self.dead.get() { + self.objects.set(node.data.id, node.clone()); + } + self.create_object( + PW_CLIENT_NODE_FACTORY, + PW_CLIENT_NODE_INTERFACE, + PW_CLIENT_NODE_VERSION, + props, + node.data.id, + ); + node.send_update(); + node + } + + pub fn destroy_obj(&self, obj: &impl PwObject) { + obj.break_loops(); + self.send2(0, "core", PwCoreMethods::Destroy, |f| { + f.write_struct(|f| { + f.write_uint(obj.data().id); + }); + }); + self.objects.remove(&obj.data().id); + } + + pub fn kill(&self) { + for (_, obj) in self.objects.lock().drain() { + obj.break_loops(); + } + self.io.shutdown(); + self.dead.set(true); + if let Some(con) = self.holder.get().upgrade() { + con.outgoing.take(); + con.incoming.take(); + } + if let Some(owner) = self.owner.take() { + owner.killed(); + } + } + + pub fn id(&self) -> u32 { + self.ids.borrow_mut().acquire() + } + + pub fn proxy_data(&self) -> PwObjectData { + PwObjectData { + id: self.id(), + bound_id: Cell::new(None), + sync_id: Default::default(), + } + } + + pub fn send(&self, proxy: &P, opcode: O, f: F) + where + P: PwObject, + O: PwOpcode, + F: FnOnce(&mut PwFormatter), + { + self.send2(proxy.data().id, proxy.interface(), opcode, f); + } + + pub fn send2(&self, id: u32, interface: &str, opcode: O, f: F) + where + O: PwOpcode, + F: FnOnce(&mut PwFormatter), + { + if self.dead.get() { + return; + } + let mut buf = self.io.buf(); + let mut fds = vec![]; + format( + &mut buf, + &mut fds, + id, + opcode.id(), + self.send_seq.fetch_add(1), + |fmt| { + f(fmt); + if self.ack_registry_generation.get() != self.registry_generation.get() { + let gen = self.registry_generation.get(); + fmt.write_struct(|f| { + f.write_id(FOOTER_REGISTRY_GENERATION); + f.write_struct(|f| { + f.write_ulong(gen); + }); + }); + self.ack_registry_generation.set(gen); + } + }, + ); + if log::log_enabled!(log::Level::Trace) { + log::trace!("CALL {}@{}: `{:?}`:", interface, id, opcode); + let mut parser = PwParser::new(&buf[16..], &fds); + while parser.len() > 0 { + log::trace!("{:#?}", parser.read_pod().unwrap()); + } + } + self.io.send(BufIoMessage { fds, buf }); + } + + #[allow(dead_code)] + pub fn sync(&self, p: &P) { + let seq = p.data().sync_id.fetch_add(1) + 1; + self.send2(0, "core", PwCoreMethods::Sync, |f| { + f.write_struct(|f| { + f.write_uint(p.data().id); + f.write_uint(seq); + }); + }); + } + + pub fn send_hello(&self) { + self.send2(0, "core", PwCoreMethods::Hello, |f| { + f.write_struct(|f| f.write_int(PW_CORE_VERSION)); + }); + } + + #[allow(dead_code)] + pub fn get_registry(self: &Rc) -> Rc { + let registry = Rc::new(PwRegistry { + data: self.proxy_data(), + con: self.clone(), + }); + if !self.dead.get() { + self.objects.set(registry.data.id, registry.clone()); + } + self.send2(0, "core", PwCoreMethods::GetRegistry, |f| { + f.write_struct(|f| { + f.write_int(PW_REGISTRY_VERSION); + f.write_uint(registry.data.id); + }); + }); + registry + } + + pub fn create_object( + &self, + factory: &str, + ty: &str, + version: i32, + props: &[(String, String)], + new_id: u32, + ) { + self.send2(0, "core", PwCoreMethods::CreateObject, |f| { + f.write_struct(|f| { + f.write_string(factory); + f.write_string(ty); + f.write_int(version); + f.write_struct(|f| { + f.write_int(props.len() as _); + for (key, val) in props { + f.write_string(key); + f.write_string(val); + } + }); + f.write_uint(new_id); + }); + }); + } + + pub fn send_properties(&self) { + self.send2(1, "client", PwClientMethods::UpdateProperties, |f| { + f.write_struct(|f| { + f.write_struct(|f| { + f.write_int(1); + f.write_string("application.name"); + f.write_string("jay-portal"); + }); + }); + }); + } + + async fn handle_outgoing(self: Rc) { + if let Err(e) = self.io.clone().outgoing().await { + log::error!("{}", ErrorFmt(e)); + } + self.kill(); + } + + async fn handle_incoming(self: Rc) { + let incoming = Incoming { + incoming: self.io.clone().incoming(), + con: self.clone(), + buf: vec![], + fds: vec![], + }; + incoming.run().await; + } +} + +impl Drop for PwConHolder { + fn drop(&mut self) { + self.con.owner.take(); + self.con.kill(); + } +} + +impl PwConHolder { + #[allow(dead_code)] + pub fn new(eng: &Rc, ring: &Rc) -> Result, PwConError> { + let fd = match uapi::socket( + c::AF_UNIX, + c::SOCK_STREAM | c::SOCK_CLOEXEC | c::SOCK_NONBLOCK, + 0, + ) { + Ok(fd) => Rc::new(fd), + Err(e) => return Err(PwConError::CreateSocket(e.into())), + }; + let mut addr = c::sockaddr_un { + sun_family: c::AF_UNIX as _, + ..uapi::pod_zeroed() + }; + let xrd = match xrd() { + Some(xrd) => xrd, + _ => return Err(PwConError::XrdNotSet), + }; + { + let mut path = uapi::as_bytes_mut(&mut addr.sun_path[..]); + let _ = write!(path, "{}/pipewire-0", xrd); + } + if let Err(e) = uapi::connect(fd.raw(), &addr) { + return Err(PwConError::ConnectSocket(e.into())); + } + let io = Rc::new(BufIo::new(&fd, ring)); + let data = Rc::new(PwCon { + send_seq: Default::default(), + io, + holder: Default::default(), + dead: Cell::new(false), + objects: Default::default(), + ids: Default::default(), + mem: Default::default(), + ring: ring.clone(), + eng: eng.clone(), + owner: Default::default(), + registry_generation: Cell::new(0), + ack_registry_generation: Cell::new(0), + }); + let core = Rc::new(PwCore { + data: data.proxy_data(), + con: data.clone(), + }); + let client = Rc::new(PwClient { + data: data.proxy_data(), + con: data.clone(), + }); + data.objects.set(0, core.clone()); + data.objects.set(1, client.clone()); + data.send_hello(); + data.send_properties(); + let con = Rc::new(PwConHolder { + outgoing: Cell::new(Some(eng.spawn(data.clone().handle_outgoing()))), + incoming: Cell::new(Some(eng.spawn(data.clone().handle_incoming()))), + con: data, + }); + con.con.holder.set(Rc::downgrade(&con)); + Ok(con) + } +} + +struct Incoming { + con: Rc, + incoming: BufIoIncoming, + buf: Vec, + fds: Vec>, +} + +impl Incoming { + async fn run(mut self) { + loop { + if let Err(e) = self.handle_msg().await { + log::error!("Could not handle incoming message: {}", ErrorFmt(e)); + self.con.kill(); + return; + } + } + } + + async fn handle_msg(&mut self) -> Result<(), PwConError> { + self.buf.clear(); + self.incoming.fill_msg_buf(16, &mut self.buf).await?; + let id: u32 = uapi::pod_read(&self.buf[0..4]).unwrap(); + let p2: u32 = uapi::pod_read(&self.buf[4..8]).unwrap(); + let opcode = (p2 >> 24) as u8; + let size = (p2 & 0xff_ffff) as usize; + let _seq: u32 = uapi::pod_read(&self.buf[8..12]).unwrap(); + let n_fds: u32 = uapi::pod_read(&self.buf[12..16]).unwrap(); + for _ in 0..n_fds { + match self.incoming.fds.pop_front() { + Some(fd) => self.fds.push(fd), + _ => return Err(PwConError::MissingFd), + } + } + self.buf.clear(); + self.incoming.fill_msg_buf(size, &mut self.buf).await?; + if let Err(e) = self.handle_msg_data(id, opcode) { + log::warn!("Could not handle incoming message: {}", ErrorFmt(e)); + } + self.fds.clear(); + Ok(()) + } + + fn handle_msg_data(&self, id: u32, opcode: u8) -> Result<(), PwConError> { + let parser = PwParser::new(&self.buf, &self.fds); + { + let mut parser = parser; + parser.skip()?; + if parser.len() > 0 { + let s1 = parser.read_struct()?; + let mut p2 = s1.fields; + while p2.len() > 0 { + let opcode = p2.read_id()?; + let s2 = p2.read_struct()?; + if opcode == FOOTER_REGISTRY_GENERATION { + let mut p3 = s2.fields; + let gen = p3.read_ulong()?; + self.con.registry_generation.set(gen); + log::debug!("registry generation = {}", gen); + } else { + log::warn!("Unknown message footer: {}", opcode); + } + } + } + } + if let Some(obj) = self.con.objects.get(&id) { + 'log: { + if log::log_enabled!(log::Level::Trace) { + let s; + let op: &dyn Display = match obj.event_name(opcode) { + Some(e) => { + s = e; + if e == "Done" && obj.interface() == "core" { + break 'log; + } + &s + } + _ => &opcode, + }; + log::trace!("EVENT {}@{}: `{}`:", obj.interface(), obj.data().id, op); + let mut parser = parser; + while parser.len() > 0 { + log::trace!("{:#?}", parser.read_pod().unwrap()); + } + } + } + obj.handle_msg(opcode, parser)?; + } + Ok(()) + } +} + +const FOOTER_REGISTRY_GENERATION: u32 = 0; diff --git a/src/pipewire/pw_formatter.rs b/src/pipewire/pw_formatter.rs new file mode 100644 index 00000000..6fd250a5 --- /dev/null +++ b/src/pipewire/pw_formatter.rs @@ -0,0 +1,304 @@ +use { + crate::pipewire::pw_pod::{ + PW_TYPE_Array, PW_TYPE_Bitmap, PW_TYPE_Bool, PW_TYPE_Bytes, PW_TYPE_Choice, PW_TYPE_Double, + PW_TYPE_Fd, PW_TYPE_Float, PW_TYPE_Fraction, PW_TYPE_Id, PW_TYPE_Int, PW_TYPE_Long, + PW_TYPE_None, PW_TYPE_Object, PW_TYPE_Rectangle, PW_TYPE_String, PW_TYPE_Struct, + PwChoiceType, PwPodObjectType, PwPodType, PwPropFlag, + }, + std::rc::Rc, + uapi::OwnedFd, +}; + +pub struct PwFormatter<'a> { + data: &'a mut Vec, + fds: &'a mut Vec>, + array: bool, + first: bool, +} + +impl<'a> PwFormatter<'a> { + pub fn write_bool(&mut self, b: bool) { + if !self.array || self.first { + self.data.extend_from_slice(uapi::as_bytes(&4u32)); + self.data.extend_from_slice(uapi::as_bytes(&PW_TYPE_Bool.0)); + } + self.data.extend_from_slice(uapi::as_bytes(&(b as u32))); + if !self.array { + self.data.extend_from_slice(uapi::as_bytes(&0u32)); + } + self.first = false; + } + + pub fn write_id(&mut self, id: u32) { + if !self.array || self.first { + self.data.extend_from_slice(uapi::as_bytes(&4u32)); + self.data.extend_from_slice(uapi::as_bytes(&PW_TYPE_Id.0)); + } + self.data.extend_from_slice(uapi::as_bytes(&id)); + if !self.array { + self.data.extend_from_slice(uapi::as_bytes(&0u32)); + } + self.first = false; + } + + pub fn write_object(&mut self, ty: PwPodObjectType, id: u32, f: F) + where + F: FnOnce(&mut PwObjectFormatter), + { + let start = self.data.len(); + self.data.extend_from_slice(uapi::as_bytes(&0u32)); + self.data + .extend_from_slice(uapi::as_bytes(&PW_TYPE_Object.0)); + self.data.extend_from_slice(uapi::as_bytes(&ty.0)); + self.data.extend_from_slice(uapi::as_bytes(&id)); + let mut fmt = PwObjectFormatter { + data: self.data, + fds: self.fds, + }; + f(&mut fmt); + let len = (self.data.len() - start - 8) as u32; + self.data[start..start + 4].copy_from_slice(uapi::as_bytes(&len)); + } + + pub fn write_uint(&mut self, int: u32) { + self.write_int(int as _) + } + + pub fn write_int(&mut self, int: i32) { + if !self.array || self.first { + self.data.extend_from_slice(uapi::as_bytes(&4u32)); + self.data.extend_from_slice(uapi::as_bytes(&PW_TYPE_Int.0)); + } + self.data.extend_from_slice(uapi::as_bytes(&int)); + if !self.array { + self.data.extend_from_slice(uapi::as_bytes(&0u32)); + } + self.first = false; + } + + pub fn write_ulong(&mut self, long: u64) { + self.write_long(long as _) + } + + pub fn write_long(&mut self, long: i64) { + if !self.array || self.first { + self.data.extend_from_slice(uapi::as_bytes(&8u32)); + self.data.extend_from_slice(uapi::as_bytes(&PW_TYPE_Long.0)); + } + self.data.extend_from_slice(uapi::as_bytes(&long)); + self.first = false; + } + + #[allow(dead_code)] + pub fn write_float(&mut self, float: f32) { + if !self.array || self.first { + self.data.extend_from_slice(uapi::as_bytes(&4u32)); + self.data + .extend_from_slice(uapi::as_bytes(&PW_TYPE_Float.0)); + } + self.data.extend_from_slice(uapi::as_bytes(&float)); + if !self.array { + self.data.extend_from_slice(uapi::as_bytes(&0u32)); + } + self.first = false; + } + + #[allow(dead_code)] + pub fn write_double(&mut self, double: f64) { + if !self.array || self.first { + self.data.extend_from_slice(uapi::as_bytes(&8u32)); + self.data + .extend_from_slice(uapi::as_bytes(&PW_TYPE_Double.0)); + } + self.data.extend_from_slice(uapi::as_bytes(&double)); + } + + pub fn write_string + ?Sized>(&mut self, s: &S) { + let s = s.as_ref(); + self.data + .extend_from_slice(uapi::as_bytes(&(s.len() as u32 + 1))); + self.data + .extend_from_slice(uapi::as_bytes(&PW_TYPE_String.0)); + self.data.extend_from_slice(s); + self.data.push(0); + self.pad(); + } + + #[allow(dead_code)] + pub fn write_bytes(&mut self, s: &[u8]) { + self.data + .extend_from_slice(uapi::as_bytes(&(s.len() as u32))); + self.data + .extend_from_slice(uapi::as_bytes(&PW_TYPE_Bytes.0)); + self.data.extend_from_slice(s); + self.pad(); + } + + pub fn write_rectangle(&mut self, width: u32, height: u32) { + if !self.array || self.first { + self.data.extend_from_slice(uapi::as_bytes(&8u32)); + self.data + .extend_from_slice(uapi::as_bytes(&PW_TYPE_Rectangle.0)); + } + self.data.extend_from_slice(uapi::as_bytes(&width)); + self.data.extend_from_slice(uapi::as_bytes(&height)); + self.first = false; + } + + #[allow(dead_code)] + pub fn write_fraction(&mut self, num: i32, denom: i32) { + if !self.array || self.first { + self.data.extend_from_slice(uapi::as_bytes(&8u32)); + self.data + .extend_from_slice(uapi::as_bytes(&PW_TYPE_Fraction.0)); + } + self.data.extend_from_slice(uapi::as_bytes(&num)); + self.data.extend_from_slice(uapi::as_bytes(&denom)); + self.first = false; + } + + pub fn write_none(&mut self) { + if !self.array || self.first { + self.data.extend_from_slice(uapi::as_bytes(&0u32)); + self.data.extend_from_slice(uapi::as_bytes(&PW_TYPE_None.0)); + } + self.first = false; + } + + #[allow(dead_code)] + pub fn write_bitmap(&mut self, s: &[u8]) { + self.data + .extend_from_slice(uapi::as_bytes(&(s.len() as u32))); + self.data + .extend_from_slice(uapi::as_bytes(&PW_TYPE_Bitmap.0)); + self.data.extend_from_slice(s); + self.pad(); + } + + #[allow(dead_code)] + pub fn write_fd(&mut self, fd: &Rc) { + let pos = self.fds.len() as u64; + self.fds.push(fd.clone()); + if !self.array || self.first { + self.data.extend_from_slice(uapi::as_bytes(&8u32)); + self.data.extend_from_slice(uapi::as_bytes(&PW_TYPE_Fd.0)); + } + self.data.extend_from_slice(uapi::as_bytes(&pos)); + self.first = false; + } + + pub fn write_struct(&mut self, f: F) + where + F: FnOnce(&mut PwFormatter), + { + self.write_compound(PW_TYPE_Struct, |fmt| { + let mut fmt = PwFormatter { + data: fmt.data, + fds: fmt.fds, + array: false, + first: false, + }; + f(&mut fmt); + }); + } + + #[allow(dead_code)] + pub fn write_array(&mut self, f: F) + where + F: FnOnce(&mut PwFormatter), + { + self.write_compound(PW_TYPE_Array, |fmt| { + fmt.write_array_body(f); + }); + self.pad(); + } + + fn write_array_body(&mut self, f: F) + where + F: FnOnce(&mut PwFormatter), + { + let mut fmt = PwFormatter { + data: self.data, + fds: self.fds, + array: true, + first: true, + }; + f(&mut fmt); + if fmt.first { + fmt.write_none(); + } + } + + pub fn write_choice(&mut self, ty: PwChoiceType, flags: u32, f: F) + where + F: FnOnce(&mut PwFormatter), + { + self.write_compound(PW_TYPE_Choice, |fmt| { + fmt.data.extend_from_slice(uapi::as_bytes(&ty.0)); + fmt.data.extend_from_slice(uapi::as_bytes(&flags)); + fmt.write_array_body(f); + }); + self.pad(); + } + + fn write_compound(&mut self, ty: PwPodType, f: F) + where + F: FnOnce(&mut PwFormatter), + { + let start = self.data.len(); + self.data.extend_from_slice(uapi::as_bytes(&0u32)); + self.data.extend_from_slice(uapi::as_bytes(&ty.0)); + f(self); + let len = (self.data.len() - start - 8) as u32; + self.data[start..start + 4].copy_from_slice(uapi::as_bytes(&len)); + } + + fn pad(&mut self) { + let todo = self.data.len().wrapping_neg() & 7; + self.data.extend_from_slice(&uapi::as_bytes(&0u64)[..todo]); + } +} + +pub struct PwObjectFormatter<'a> { + data: &'a mut Vec, + fds: &'a mut Vec>, +} + +impl<'a> PwObjectFormatter<'a> { + pub fn write_property(&mut self, key: u32, flags: PwPropFlag, f: F) + where + F: FnOnce(&mut PwFormatter), + { + self.data.extend_from_slice(uapi::as_bytes(&key)); + self.data.extend_from_slice(uapi::as_bytes(&flags.0)); + let mut fmt = PwFormatter { + data: self.data, + fds: self.fds, + array: false, + first: false, + }; + f(&mut fmt); + } +} + +pub fn format(buf: &mut Vec, fds: &mut Vec>, id: u32, opcode: u8, seq: u32, f: F) +where + F: FnOnce(&mut PwFormatter), +{ + buf.clear(); + buf.extend_from_slice(uapi::as_bytes(&id)); + buf.extend_from_slice(uapi::as_bytes(&0u32)); + buf.extend_from_slice(uapi::as_bytes(&seq)); + buf.extend_from_slice(uapi::as_bytes(&0u32)); + let mut fmt = PwFormatter { + data: buf, + fds, + array: false, + first: false, + }; + f(&mut fmt); + let p2 = (buf.len() - 16) as u32 | ((opcode as u32) << 24); + buf[4..8].copy_from_slice(uapi::as_bytes(&p2)); + let nfds = fds.len() as u32; + buf[12..16].copy_from_slice(uapi::as_bytes(&nfds)); +} diff --git a/src/pipewire/pw_ifs.rs b/src/pipewire/pw_ifs.rs new file mode 100644 index 00000000..91651f17 --- /dev/null +++ b/src/pipewire/pw_ifs.rs @@ -0,0 +1,4 @@ +pub mod pw_client; +pub mod pw_client_node; +pub mod pw_core; +pub mod pw_registry; diff --git a/src/pipewire/pw_ifs/pw_client.rs b/src/pipewire/pw_ifs/pw_client.rs new file mode 100644 index 00000000..579ac6b6 --- /dev/null +++ b/src/pipewire/pw_ifs/pw_client.rs @@ -0,0 +1,61 @@ +use { + crate::pipewire::{ + pw_con::PwCon, + pw_object::{PwObject, PwObjectData}, + pw_parser::{PwParser, PwParserError}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pw_opcodes! { + PwClientMethods; + + Error = 1, + UpdateProperties = 2, + GetPermissions = 3, + UpdatePermissions = 4, +} + +pw_opcodes! { + PwClientEvents; + + Info = 0, + Permissions = 1, +} + +pub struct PwClient { + pub data: PwObjectData, + pub con: Rc, +} + +impl PwClient { + fn handle_info(&self, mut p: PwParser<'_>) -> Result<(), PwClientError> { + let s1 = p.read_struct()?; + let mut p2 = s1.fields; + let _id = p2.read_int()?; + let _change_mask = p2.read_long()?; + let props = p2.read_dict_struct()?; + log::debug!("Pipewire properties: {:#?}", props); + Ok(()) + } + + fn handle_permissions(&self, _p: PwParser<'_>) -> Result<(), PwClientError> { + Ok(()) + } +} + +pw_object_base! { + PwClient, "client", PwClientEvents; + + Info => handle_info, + Permissions => handle_permissions, +} + +impl PwObject for PwClient {} + +#[derive(Debug, Error)] +pub enum PwClientError { + #[error(transparent)] + PwParserError(#[from] PwParserError), +} diff --git a/src/pipewire/pw_ifs/pw_client_node.rs b/src/pipewire/pw_ifs/pw_client_node.rs new file mode 100644 index 00000000..f6d6dd6c --- /dev/null +++ b/src/pipewire/pw_ifs/pw_client_node.rs @@ -0,0 +1,874 @@ +#![allow(non_upper_case_globals)] + +use { + crate::{ + async_engine::SpawnedFuture, + format::{pw_formats, Format}, + pipewire::{ + pw_con::PwCon, + pw_mem::{PwMemError, PwMemMap, PwMemSlice, PwMemTyped}, + pw_object::{PwObject, PwObjectData}, + pw_parser::{PwParser, PwParserError}, + pw_pod::{ + pw_node_activation, spa_chunk, spa_io_buffers, spa_meta_bitmap, spa_meta_busy, + spa_meta_cursor, spa_meta_header, spa_meta_region, PW_CHOICE_Enum, PW_CHOICE_Flags, + PW_OBJECT_Format, PW_OBJECT_ParamBuffers, PW_OBJECT_ParamMeta, PwIoType, + PwPodFraction, PwPodObject, PwPodRectangle, PwPropFlag, SPA_DATA_DmaBuf, + SPA_DATA_MemFd, SPA_DATA_MemPtr, SPA_FORMAT_VIDEO_format, + SPA_FORMAT_VIDEO_framerate, SPA_FORMAT_VIDEO_modifier, SPA_FORMAT_VIDEO_size, + SPA_FORMAT_mediaSubtype, SPA_FORMAT_mediaType, SPA_IO_Buffers, SPA_META_Bitmap, + SPA_META_Busy, SPA_META_Control, SPA_META_Cursor, SPA_META_Header, + SPA_META_VideoCrop, SPA_META_VideoDamage, SPA_NODE_COMMAND_Pause, + SPA_NODE_COMMAND_Start, SPA_NODE_COMMAND_Suspend, SPA_PARAM_BUFFERS_blocks, + SPA_PARAM_BUFFERS_buffers, SPA_PARAM_BUFFERS_dataType, SPA_PARAM_Buffers, + SPA_PARAM_EnumFormat, SPA_PARAM_Format, SPA_PARAM_META_size, SPA_PARAM_META_type, + SPA_PARAM_Meta, SpaDataFlags, SpaDataType, SpaDirection, SpaIoType, + SpaMediaSubtype, SpaMediaType, SpaMetaType, SpaNodeBuffersFlags, SpaNodeCommand, + SpaParamType, SpaVideoFormat, SPA_DATA_FLAG_READABLE, SPA_DIRECTION_INPUT, + SPA_DIRECTION_OUTPUT, SPA_NODE_BUFFERS_FLAG_ALLOC, SPA_PARAM_INFO_READ, + SPA_PORT_FLAG, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS, + }, + }, + utils::{ + bitfield::Bitfield, bitflags::BitflagsExt, clonecell::CloneCell, + copyhashmap::CopyHashMap, errorfmt::ErrorFmt, + }, + video::dmabuf::DmaBuf, + }, + std::{ + cell::{Cell, RefCell}, + mem, + ops::Deref, + rc::Rc, + }, + thiserror::Error, + uapi::OwnedFd, +}; + +pw_opcodes! { + PwClientNodeMethods; + + GetNode = 1, + Update = 2, + PortUpdate = 3, + SetActive = 4, + Event = 5, + PortBuffers = 6, +} + +pw_opcodes! { + PwClientNodeEvents; + + Transport = 0, + SetParam = 1, + SetIo = 2, + Event = 3, + Command = 4, + AddPort = 5, + RemovePort = 6, + PortSetParam = 7, + PortUseBuffers = 8, + PortSetIo = 9, + SetActivation = 10, + PortSetMixInfo = 11, +} + +pub trait PwClientNodeOwner { + fn port_format_changed(&self, port: &Rc) { + let _ = port; + } + fn use_buffers(&self, port: &Rc) { + let _ = port; + } + fn start(self: Rc) {} + fn pause(self: Rc) {} + fn suspend(self: Rc) {} + fn bound_id(&self, id: u32) { + let _ = id; + } +} + +bitflags! { + PwClientNodePortChanges: u32; + + CHANGED_SUPPORTED_PARAMS = 1 << 0, +} + +bitflags! { + PwClientNodePortSupportedMetas: u32; + + SUPPORTED_META_HEADER = 1 << 0, + SUPPORTED_META_BUSY = 1 << 1, + SUPPORTED_META_VIDEO_CROP = 1 << 2, +} + +pub struct PwClientNodePort { + pub node: Rc, + + pub direction: SpaDirection, + pub id: u32, + + pub destroyed: Cell, + + pub effective_format: Cell, + pub supported_formats: RefCell>, + pub supported_metas: Cell, + pub can_alloc_buffers: Cell, + + pub buffers: RefCell>>, + + pub buffer_config: Cell>, + + pub io_buffers: CopyHashMap>>, +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct PwClientNodeBufferConfig { + pub num_buffers: usize, + pub planes: usize, + pub size: Option, + pub stride: Option, + pub align: usize, + pub data_type: SpaDataType, +} + +pub struct PwClientNodeBuffer { + pub meta_header: Option>>, + pub meta_busy: Option>>, + pub meta_video_crop: Option>>, + pub chunks: Vec>>, + pub slices: Vec>, +} + +#[derive(Clone, Debug, Default)] +pub struct PwClientNodePortSupportedFormats { + pub media_type: Option, + pub media_sub_type: Option, + pub video_size: Option, + pub formats: Vec<&'static Format>, + pub modifiers: Vec, +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct PwClientNodePortFormat { + pub media_type: Option, + pub media_sub_type: Option, + pub video_size: Option, + pub format: Option<&'static Format>, + pub framerate: Option, +} + +pub struct PwClientNode { + pub data: PwObjectData, + pub con: Rc, + pub ios: CopyHashMap>, + + pub owner: CloneCell>>, + + pub ports: CopyHashMap<(SpaDirection, u32), Rc>, + + pub port_out_free: RefCell, + pub port_in_free: RefCell, + + pub activation: CloneCell>>>, + pub transport_in: Cell>>, + pub transport_out: CloneCell>>, + + pub activations: CopyHashMap>, +} + +pub struct PwNodeActivation { + pub activation: Rc>, + pub fd: Rc, +} + +// pub struct PwNodeBuffer { +// pub width: i32, +// pub height: i32, +// pub stride: i32, +// pub offset: i32, +// pub fd: Rc, +// } + +pub const PW_CLIENT_NODE_FACTORY: &str = "client-node"; +pub const PW_CLIENT_NODE_INTERFACE: &str = "PipeWire:Interface:ClientNode"; +pub const PW_CLIENT_NODE_VERSION: i32 = 4; + +#[allow(dead_code)] +const PW_CLIENT_NODE_UPDATE_PARAMS: u32 = 1 << 0; +const PW_CLIENT_NODE_UPDATE_INFO: u32 = 1 << 1; + +const SPA_NODE_CHANGE_MASK_FLAGS: u64 = 1 << 0; +#[allow(dead_code)] +const SPA_NODE_CHANGE_MASK_PROPS: u64 = 1 << 1; +const SPA_NODE_CHANGE_MASK_PARAMS: u64 = 1 << 2; + +const PW_CLIENT_NODE_PORT_UPDATE_PARAMS: u32 = 1 << 0; +const PW_CLIENT_NODE_PORT_UPDATE_INFO: u32 = 1 << 1; + +const SPA_PORT_CHANGE_MASK_FLAGS: u64 = 1 << 0; +const SPA_PORT_CHANGE_MASK_RATE: u64 = 1 << 1; +#[allow(dead_code)] +const SPA_PORT_CHANGE_MASK_PROPS: u64 = 1 << 2; +const SPA_PORT_CHANGE_MASK_PARAMS: u64 = 1 << 3; + +impl PwClientNode { + pub fn send_update(&self) { + self.con.send(self, PwClientNodeMethods::Update, |f| { + f.write_struct(|f| { + f.write_uint(PW_CLIENT_NODE_UPDATE_INFO); + f.write_uint(0); + f.write_struct(|f| { + f.write_uint(0); + f.write_uint(1); + f.write_ulong(SPA_NODE_CHANGE_MASK_PARAMS | SPA_NODE_CHANGE_MASK_FLAGS); + f.write_ulong(0); + f.write_uint(0); + f.write_uint(0); + }); + }); + }); + } + + pub fn send_active(&self, active: bool) { + self.con.send(self, PwClientNodeMethods::SetActive, |f| { + f.write_struct(|f| { + f.write_bool(active); + }); + }); + } + + pub fn create_port(self: &Rc, output: bool) -> Rc { + let (ids, direction) = match output { + true => (&self.port_out_free, SPA_DIRECTION_OUTPUT), + false => (&self.port_in_free, SPA_DIRECTION_INPUT), + }; + let port = Rc::new(PwClientNodePort { + node: self.clone(), + direction, + id: ids.borrow_mut().acquire(), + destroyed: Cell::new(false), + effective_format: Cell::new(Default::default()), + supported_formats: RefCell::new(None), + supported_metas: Cell::new(PwClientNodePortSupportedMetas::none()), + can_alloc_buffers: Cell::new(false), + buffers: RefCell::new(vec![]), + buffer_config: Cell::new(None), + io_buffers: Default::default(), + }); + self.ports.set((direction, port.id), port.clone()); + port + } + + pub fn send_port_output_buffers(&self, port: &PwClientNodePort, buffers: &[DmaBuf]) { + self.con.send(self, PwClientNodeMethods::PortBuffers, |f| { + f.write_struct(|f| { + // direction + f.write_uint(port.direction.0); + // id + f.write_uint(port.id); + // mix_id + f.write_int(-1); + // n_buffers + f.write_uint(buffers.len() as _); + for buffer in buffers.deref() { + // n_datas + f.write_uint(buffer.planes.len() as _); + for plane in &buffer.planes { + // type + f.write_id(SPA_DATA_DmaBuf.0); + // fd + f.write_fd(&plane.fd); + // flags + f.write_uint(SPA_DATA_FLAG_READABLE.0); + // offset + f.write_uint(plane.offset); + // size + f.write_uint(plane.stride * buffer.height as u32); + } + } + }); + }); + } + + pub fn send_port_update(&self, port: &PwClientNodePort) { + self.con.send(self, PwClientNodeMethods::PortUpdate, |f| { + f.write_struct(|f| { + // direction + f.write_uint(port.direction.0); + // id + f.write_uint(port.id); + // change flags + f.write_uint(PW_CLIENT_NODE_PORT_UPDATE_PARAMS | PW_CLIENT_NODE_PORT_UPDATE_INFO); + let sm = port.supported_metas.get(); + let mut metas = vec![]; + if sm.contains(SUPPORTED_META_HEADER) { + metas.push((SPA_META_Header, mem::size_of::())); + } + if sm.contains(SUPPORTED_META_BUSY) { + metas.push((SPA_META_Busy, mem::size_of::())); + } + if sm.contains(SUPPORTED_META_VIDEO_CROP) { + metas.push((SPA_META_VideoCrop, mem::size_of::())); + } + let sf = port.supported_formats.borrow_mut(); + let bc = port.buffer_config.get(); + let mut num_params = metas.len() as u32; + if sf.is_some() { + num_params += 1; + } + if bc.is_some() { + num_params += 1; + } + // num params + f.write_uint(num_params); + if let Some(sf) = sf.deref() { + f.write_object(PW_OBJECT_Format, SPA_PARAM_EnumFormat.0, |f| { + if let Some(mt) = sf.media_type { + f.write_property(SPA_FORMAT_mediaType.0, PwPropFlag::none(), |f| { + f.write_id(mt.0); + }); + } + if let Some(mst) = sf.media_sub_type { + f.write_property(SPA_FORMAT_mediaSubtype.0, PwPropFlag::none(), |f| { + f.write_id(mst.0); + }); + } + if sf.formats.len() > 0 { + f.write_property(SPA_FORMAT_VIDEO_format.0, PwPropFlag::none(), |f| { + f.write_choice(PW_CHOICE_Enum, 0, |f| { + f.write_id(sf.formats[0].pipewire.0); + for format in &sf.formats { + f.write_id(format.pipewire.0); + } + }); + }); + } + if sf.modifiers.len() > 0 { + f.write_property( + SPA_FORMAT_VIDEO_modifier.0, + PwPropFlag::none(), + |f| { + f.write_choice(PW_CHOICE_Enum, 0, |f| { + f.write_ulong(sf.modifiers[0]); + for modifier in &sf.modifiers { + f.write_ulong(*modifier); + } + }); + }, + ); + } + if let Some(vs) = sf.video_size { + f.write_property(SPA_FORMAT_VIDEO_size.0, PwPropFlag::none(), |f| { + f.write_choice(PW_CHOICE_Enum, 0, |f| { + f.write_rectangle(vs.width, vs.height); + f.write_rectangle(vs.width, vs.height); + }); + }); + } + }); + } + if let Some(bc) = &bc { + f.write_object(PW_OBJECT_ParamBuffers, SPA_PARAM_Buffers.0, |f| { + f.write_property(SPA_PARAM_BUFFERS_buffers.0, PwPropFlag::none(), |f| { + f.write_uint(bc.num_buffers as _); + }); + f.write_property(SPA_PARAM_BUFFERS_blocks.0, PwPropFlag::none(), |f| { + f.write_uint(bc.planes as _); + }); + // if let Some(size) = bc.size { + // f.write_property(SPA_PARAM_BUFFERS_size.0, PwPropFlag::none(), |f| { + // f.write_uint(size as _); + // }); + // } + // if let Some(stride) = bc.stride { + // f.write_property(SPA_PARAM_BUFFERS_stride.0, PwPropFlag::none(), |f| { + // f.write_uint(stride as _); + // }); + // } + // f.write_property(SPA_PARAM_BUFFERS_align.0, PwPropFlag::none(), |f| { + // f.write_uint(bc.align as _); + // }); + f.write_property(SPA_PARAM_BUFFERS_dataType.0, PwPropFlag::none(), |f| { + f.write_choice(PW_CHOICE_Flags, 0, |f| { + f.write_uint(1 << bc.data_type.0); + }); + }); + }); + } + for (key, size) in metas { + f.write_object(PW_OBJECT_ParamMeta, SPA_PARAM_Meta.0, |f| { + f.write_property(SPA_PARAM_META_type.0, PwPropFlag::none(), |f| { + f.write_id(key.0); + }); + f.write_property(SPA_PARAM_META_size.0, PwPropFlag::none(), |f| { + f.write_uint(size as u32); + }); + }); + } + f.write_struct(|f| { + // change mask + f.write_ulong( + SPA_PORT_CHANGE_MASK_FLAGS + // | SPA_PORT_CHANGE_MASK_PROPS + | SPA_PORT_CHANGE_MASK_PARAMS + | SPA_PORT_CHANGE_MASK_RATE, + ); + let mut flags = SPA_PORT_FLAG::none(); + if port.can_alloc_buffers.get() { + flags = SPA_PORT_FLAG_CAN_ALLOC_BUFFERS; + } + // flags + f.write_ulong(flags.0); + // rate num + f.write_int(0); + // rate denom + f.write_int(1); + // num props + f.write_int(0); + let mut num_params = 1; + if sf.is_some() { + num_params += 1; + } + if bc.is_some() { + num_params += 1; + } + // num params + f.write_uint(num_params); + if sf.is_some() { + f.write_id(SPA_PARAM_EnumFormat.0); + f.write_uint(SPA_PARAM_INFO_READ.0); + } + if bc.is_some() { + f.write_id(SPA_PARAM_Buffers.0); + f.write_uint(SPA_PARAM_INFO_READ.0); + } + f.write_id(SPA_PARAM_Meta.0); + f.write_uint(SPA_PARAM_INFO_READ.0); + }); + }); + }); + } + + fn handle_set_param(&self, _p: PwParser<'_>) -> Result<(), PwClientNodeError> { + Ok(()) + } + + fn handle_set_io(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> { + let s = p.read_struct()?; + let mut p2 = s.fields; + let id = PwIoType(p2.read_id()?); + let memid = p2.read_uint()?; + let offset = p2.read_uint()?; + let size = p2.read_uint()?; + log::debug!("set io {:?}", id); + if memid == !0 { + self.ios.remove(&id); + } else { + let map = match self.con.mem.map(memid, offset, size) { + Ok(m) => m, + Err(e) => { + log::error!("Could not map memory from the pool: {}", ErrorFmt(e)); + return Ok(()); + } + }; + self.ios.set(id, map); + } + Ok(()) + } + + fn handle_event(&self, _p: PwParser<'_>) -> Result<(), PwClientNodeError> { + Ok(()) + } + + fn handle_command(self: &Rc, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> { + let s1 = p.read_struct()?; + let mut p1 = s1.fields; + let obj = p1.read_object()?; + match SpaNodeCommand(obj.id) { + SPA_NODE_COMMAND_Start => { + if let Some(owner) = self.owner.get() { + owner.start(); + } + } + SPA_NODE_COMMAND_Pause => { + if let Some(owner) = self.owner.get() { + owner.pause(); + } + } + SPA_NODE_COMMAND_Suspend => { + if let Some(owner) = self.owner.get() { + owner.suspend(); + } + } + v => { + log::warn!("Unhandled node command {:?}", v); + } + } + Ok(()) + } + + fn handle_add_port(&self, _p: PwParser<'_>) -> Result<(), PwClientNodeError> { + Ok(()) + } + + fn handle_remove_port(&self, _p: PwParser<'_>) -> Result<(), PwClientNodeError> { + Ok(()) + } + + fn port_set_format( + &self, + port: &Rc, + obj: Option>, + ) -> Result<(), PwClientNodeError> { + let mut obj = match obj { + Some(obj) => obj, + _ => { + port.effective_format.take(); + return Ok(()); + } + }; + let mut format = PwClientNodePortFormat::default(); + if let Some(mt) = obj.get_param(SPA_FORMAT_mediaType.0)? { + format.media_type = Some(SpaMediaType(mt.pod.get_id()?)); + } + if let Some(mt) = obj.get_param(SPA_FORMAT_mediaSubtype.0)? { + format.media_sub_type = Some(SpaMediaSubtype(mt.pod.get_id()?)); + } + if let Some(mt) = obj.get_param(SPA_FORMAT_VIDEO_size.0)? { + format.video_size = Some(mt.pod.get_rectangle()?); + } + if let Some(mt) = obj.get_param(SPA_FORMAT_VIDEO_format.0)? { + if let Some(fmt) = pw_formats().get(&SpaVideoFormat(mt.pod.get_id()?)) { + format.format = Some(*fmt); + } + } + if let Some(mt) = obj.get_param(SPA_FORMAT_VIDEO_framerate.0)? { + format.framerate = Some(mt.pod.get_fraction()?); + } + port.effective_format.set(format); + Ok(()) + } + + fn handle_port_set_param(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> { + let s1 = p.read_struct()?; + let mut p1 = s1.fields; + let direction = SpaDirection(p1.read_uint()?); + let port_id = p1.read_uint()?; + let id = SpaParamType(p1.read_id()?); + let _flags = p1.read_int()?; + let obj = p1.read_object_opt()?; + let port = self.get_port(direction, port_id)?; + match id { + SPA_PARAM_Format => { + self.port_set_format(&port, obj)?; + if let Some(owner) = self.owner.get() { + owner.port_format_changed(&port); + } + } + _ => { + log::warn!( + "port_set_param: Ignoring unexpected port parameter {:?}", + id + ); + } + } + Ok(()) + } + + fn handle_port_use_buffers(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> { + let s1 = p.read_struct()?; + let mut p1 = s1.fields; + let direction = SpaDirection(p1.read_uint()?); + let port_id = p1.read_uint()?; + let _mix_id = p1.read_int()?; + let buffer_flags = SpaNodeBuffersFlags(p1.read_uint()?); + let n_buffers = p1.read_uint()?; + let port = self.get_port(direction, port_id)?; + + let mut res = vec![]; + + for _ in 0..n_buffers { + let mem_id = p1.read_uint()?; + let offset = p1.read_uint()?; + let size = p1.read_uint()?; + let n_metas = p1.read_uint()?; + + let mut meta_header = Default::default(); + let mut meta_video_crop = Default::default(); + let mut meta_busy = Default::default(); + let mut chunks = vec![]; + let mut slices = vec![]; + + let mem = self.con.mem.map(mem_id, offset, size)?; + + log::debug!(" mem_id={}, offset={}, size={}", mem_id, offset, size); + log::debug!(" n_metas={}", n_metas); + + let mut offset = 0; + + for _ in 0..n_metas { + let ty = SpaMetaType(p1.read_id()?); + let size = p1.read_uint()? as usize; + + match ty { + SPA_META_Header => { + let header = mem.typed_at::(offset); + meta_header = Some(header); + } + SPA_META_VideoCrop => { + let crop = mem.typed_at::(offset); + meta_video_crop = Some(crop); + } + SPA_META_VideoDamage => { + let _video_damage = mem.typed_at::(offset); + } + SPA_META_Bitmap => { + let _bitmap = mem.typed_at::(offset); + } + SPA_META_Cursor => { + let _cursor = mem.typed_at::(offset); + } + SPA_META_Control => {} + SPA_META_Busy => { + let busy = mem.typed_at::(offset); + meta_busy = Some(busy); + } + _ => {} + } + + offset += (size + 7) & !7; + } + + let n_datas = p1.read_uint()?; + + log::debug!(" offset = {}, n_datas={}", offset, n_datas); + + for _ in 0..n_datas { + let ty = SpaDataType(p1.read_id()?); + let data_id = p1.read_uint()?; + let _flags = SpaDataFlags(p1.read_uint()?); + let mapoffset = p1.read_uint()?; + let maxsize = p1.read_uint()?; + + chunks.push(mem.typed_at(offset)); + + if !buffer_flags.contains(SPA_NODE_BUFFERS_FLAG_ALLOC) { + if ty == SPA_DATA_MemPtr { + let offset = data_id as usize; + slices.push(mem.slice(offset..offset + maxsize as usize)); + } else if ty == SPA_DATA_MemFd { + let mem = self.con.mem.map(data_id, mapoffset, maxsize)?; + slices.push(mem.slice(0..maxsize as usize)); + } + } + } + + res.push(Rc::new(PwClientNodeBuffer { + meta_header, + meta_busy, + meta_video_crop, + chunks, + slices, + })); + } + + *port.buffers.borrow_mut() = res; + + if let Some(owner) = self.owner.get() { + owner.use_buffers(&port); + } + + Ok(()) + } + + fn handle_port_set_io(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> { + let s = p.read_struct()?; + let mut p2 = s.fields; + let direction = SpaDirection(p2.read_uint()?); + let port_id = p2.read_uint()?; + let mix_id = p2.read_uint()?; + let id = SpaIoType(p2.read_id()?); + let mem_id = p2.read_uint()?; + let offset = p2.read_uint()?; + let size = p2.read_uint()?; + let port = self.get_port(direction, port_id)?; + match id { + SPA_IO_Buffers => { + if mem_id == !0 { + port.io_buffers.remove(&mix_id); + } else { + port.io_buffers.set(mix_id, self.con.mem.map(mem_id, offset, size)?.typed()); + } + } + _ => {} + } + Ok(()) + } + + fn handle_transport(self: &Rc, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> { + let s = p.read_struct()?; + let mut p2 = s.fields; + let readfd = p2.read_fd()?; + let writefd = p2.read_fd()?; + let memid = p2.read_uint()?; + let offset = p2.read_uint()?; + let size = p2.read_uint()?; + let map = match self.con.mem.map(memid, offset, size) { + Ok(m) => m, + Err(e) => { + log::error!("Could not map memory from the pool: {}", ErrorFmt(e)); + return Ok(()); + } + }; + let typed = map.typed::(); + self.activation.set(Some(typed.clone())); + self.transport_in.set(Some( + self.con.eng.spawn(self.clone().transport_in(typed, readfd)), + )); + self.transport_out.set(Some(writefd)); + Ok(()) + } + + fn handle_set_activation( + self: &Rc, + mut p: PwParser<'_>, + ) -> Result<(), PwClientNodeError> { + let s = p.read_struct()?; + let mut p2 = s.fields; + let node = p2.read_uint()?; + let signalfd = p2.read_fd_opt()?; + if let Some(signalfd) = signalfd { + let memid = p2.read_uint()?; + let offset = p2.read_uint()?; + let size = p2.read_uint()?; + let map = match self.con.mem.map(memid, offset, size) { + Ok(m) => m, + Err(e) => { + log::error!("Could not map memory from the pool: {}", ErrorFmt(e)); + return Ok(()); + } + }; + let typed = map.typed::(); + self.activations.set( + node, + Rc::new(PwNodeActivation { + activation: typed, + fd: signalfd, + }), + ); + } else { + self.activations.remove(&node); + } + Ok(()) + } + + fn get_port( + &self, + direction: SpaDirection, + port_id: u32, + ) -> Result, PwClientNodeError> { + match self.ports.get(&(direction, port_id)) { + Some(p) => Ok(p), + _ => Err(PwClientNodeError::UnknownPort(direction, port_id)), + } + } + + fn handle_port_set_mix_info(&self, mut p: PwParser<'_>) -> Result<(), PwClientNodeError> { + let s1 = p.read_struct()?; + let mut p1 = s1.fields; + let direction = SpaDirection(p1.read_uint()?); + let port_id = p1.read_uint()?; + let mix_id = p1.read_int()?; + let peer_id = p1.read_int()?; + let dict = p1.read_dict_struct()?; + let _port = self.get_port(direction, port_id)?; + log::debug!( + "mix info: mix_id={}, peer_id={}, dict={:#?}", + mix_id, + peer_id, + dict + ); + Ok(()) + } + + async fn transport_in( + self: Rc, + _activation: Rc>, + fd: Rc, + ) { + loop { + // unsafe { + // log::info!("transport = {:#?}", activation.read()); + // } + if let Err(e) = self.con.ring.readable(&fd).await { + log::error!( + "Could not wait for transport to become readable: {}", + ErrorFmt(e) + ); + return; + } + // log::info!("transport in"); + // for port in self.ports.lock().values() { + // for io in port.io_buffers.lock().values() { + // unsafe { + // log::info!("status = {:?}", io.read().status); + // } + // } + // } + // unsafe { + // log::info!("state = {:#?}", activation.read().state[0]); + // } + let mut n = 0u64; + if let Err(e) = uapi::read(fd.raw(), &mut n) { + log::error!("Could not read from eventfd: {}", ErrorFmt(e)); + return; + } + if n > 1 { + log::warn!("Missed {} transport changes", n - 1); + } + } + } +} + +pw_object_base! { + PwClientNode, "client-node", PwClientNodeEvents; + + Transport => handle_transport, + SetParam => handle_set_param, + SetIo => handle_set_io, + Event => handle_event, + Command => handle_command, + AddPort => handle_add_port, + RemovePort => handle_remove_port, + PortSetParam => handle_port_set_param, + PortUseBuffers => handle_port_use_buffers, + PortSetIo => handle_port_set_io, + SetActivation => handle_set_activation, + PortSetMixInfo => handle_port_set_mix_info, +} + +impl PwObject for PwClientNode { + fn bound_id(&self, id: u32) { + if let Some(owner) = self.owner.get() { + owner.bound_id(id); + } + } + + fn break_loops(&self) { + self.owner.take(); + self.ports.clear(); + self.transport_in.take(); + self.transport_out.take(); + } +} + +#[derive(Debug, Error)] +pub enum PwClientNodeError { + #[error(transparent)] + PwParserError(#[from] PwParserError), + #[error(transparent)] + PwMemError(#[from] PwMemError), + #[error("Unknown port {0:?}@{1}")] + UnknownPort(SpaDirection, u32), +} diff --git a/src/pipewire/pw_ifs/pw_core.rs b/src/pipewire/pw_ifs/pw_core.rs new file mode 100644 index 00000000..fba71fa1 --- /dev/null +++ b/src/pipewire/pw_ifs/pw_core.rs @@ -0,0 +1,184 @@ +#![allow(non_upper_case_globals)] + +use { + crate::{ + pipewire::{ + pw_con::PwCon, + pw_mem::{PwMem, PwMemType}, + pw_object::{PwObject, PwObjectData}, + pw_parser::{PwParser, PwParserError}, + pw_pod::{SPA_DATA_DmaBuf, SPA_DATA_MemFd, SpaDataType}, + }, + utils::bitflags::BitflagsExt, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub struct PwCore { + pub data: PwObjectData, + pub con: Rc, +} + +pw_opcodes! { + PwCoreMethods; + + Hello = 1, + Sync = 2, + Pong = 3, + Error = 4, + GetRegistry = 5, + CreateObject = 6, + Destroy = 7, +} + +pw_opcodes! { + PwCoreEvents; + + Info = 0, + Done = 1, + Ping = 2, + Error = 3, + RemoveId = 4, + BoundId = 5, + AddMem = 6, + RemoveMem = 7, +} + +pub const PW_CORE_VERSION: i32 = 3; + +impl PwCore { + pub fn handle_info(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> { + let s1 = p1.read_struct()?; + let mut p2 = s1.fields; + let id = p2.read_int()?; + let cookie = p2.read_int()?; + let user_name = p2.read_string()?; + let host_name = p2.read_string()?; + let version_name = p2.read_string()?; + let name = p2.read_string()?; + let change_mask = p2.read_long()?; + let dict = p2.read_dict_struct()?; + log::info!("info: id={id}, cookie={cookie}, user_name={user_name}, host_name={host_name}, version_name={version_name}, name={name}, change_mask={change_mask}"); + log::info!("dict: {:#?}", dict); + Ok(()) + } + + pub fn handle_done(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> { + let s1 = p1.read_struct()?; + let mut p2 = s1.fields; + let id = p2.read_uint()?; + let seq = p2.read_uint()?; + if let Some(obj) = self.con.objects.get(&id) { + if obj.data().sync_id.get() <= seq { + obj.data().sync_id.set(seq); + obj.done(); + } + } + Ok(()) + } + + pub fn handle_ping(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> { + let s1 = p1.read_struct()?; + let mut p2 = s1.fields; + let id = p2.read_int()?; + let seq = p2.read_int()?; + self.con.send(self, PwCoreMethods::Pong, |f| { + f.write_struct(|f| { + f.write_int(id); + f.write_int(seq); + }); + }); + Ok(()) + } + + pub fn handle_error(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> { + let s1 = p1.read_struct()?; + let mut p2 = s1.fields; + let id = p2.read_int()?; + let seq = p2.read_int()?; + let res = p2.read_int()?; + let error = p2.read_string()?; + log::info!("error: id={id}, seq={seq}, res={res}, error={error}"); + Ok(()) + } + + pub fn handle_remove_id(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> { + let s1 = p1.read_struct()?; + let mut p2 = s1.fields; + let id = p2.read_uint()?; + self.con.objects.remove(&id); + self.con.ids.borrow_mut().release(id); + Ok(()) + } + + pub fn handle_bound_id(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> { + let s1 = p1.read_struct()?; + let mut p2 = s1.fields; + let id = p2.read_uint()?; + let bound_id = p2.read_uint()?; + if let Some(obj) = self.con.objects.get(&id) { + obj.data().bound_id.set(Some(bound_id)); + obj.bound_id(bound_id); + } + Ok(()) + } + + pub fn handle_add_mem(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> { + let s1 = p1.read_struct()?; + let mut p2 = s1.fields; + let id = p2.read_uint()?; + let ty = SpaDataType(p2.read_id()?); + let fd = p2.read_fd()?; + let flags = p2.read_int()?; + let read = flags.contains(1); + let write = flags.contains(2); + let ty = match ty { + SPA_DATA_MemFd => PwMemType::MemFd, + SPA_DATA_DmaBuf => PwMemType::DmaBuf, + _ => { + log::error!("Ignoring unknown mem type {:?}", ty); + return Ok(()); + } + }; + self.con.mem.mems.set( + id, + Rc::new(PwMem { + ty, + read, + write, + fd, + }), + ); + Ok(()) + } + + pub fn handle_remove_mem(&self, mut p1: PwParser<'_>) -> Result<(), PwCoreError> { + let s1 = p1.read_struct()?; + let mut p2 = s1.fields; + let id = p2.read_uint()?; + self.con.mem.mems.remove(&id); + Ok(()) + } +} + +pw_object_base! { + PwCore, "core", PwCoreEvents; + + Info => handle_info, + Done => handle_done, + Ping => handle_ping, + Error => handle_error, + RemoveId => handle_remove_id, + BoundId => handle_bound_id, + AddMem => handle_add_mem, + RemoveMem => handle_remove_mem, +} + +impl PwObject for PwCore {} + +#[derive(Debug, Error)] +pub enum PwCoreError { + #[error(transparent)] + PwParserError(#[from] PwParserError), +} diff --git a/src/pipewire/pw_ifs/pw_registry.rs b/src/pipewire/pw_ifs/pw_registry.rs new file mode 100644 index 00000000..379cf7e9 --- /dev/null +++ b/src/pipewire/pw_ifs/pw_registry.rs @@ -0,0 +1,48 @@ +use { + crate::pipewire::{ + pw_con::PwCon, + pw_object::{PwObject, PwObjectData}, + pw_parser::{PwParser, PwParserError}, + }, + std::rc::Rc, + thiserror::Error, +}; + +pub const PW_REGISTRY_VERSION: i32 = 3; + +pw_opcodes! { + PwRegistryEvents; + + Global = 0, + GlobalRemove = 1, +} + +pub struct PwRegistry { + pub data: PwObjectData, + pub con: Rc, +} + +impl PwRegistry { + fn handle_global(&self, _p: PwParser<'_>) -> Result<(), PwRegistryError> { + Ok(()) + } + + fn handle_global_remove(&self, _p: PwParser<'_>) -> Result<(), PwRegistryError> { + Ok(()) + } +} + +pw_object_base! { + PwRegistry, "registry", PwRegistryEvents; + + Global => handle_global, + GlobalRemove => handle_global_remove, +} + +impl PwObject for PwRegistry {} + +#[derive(Debug, Error)] +pub enum PwRegistryError { + #[error(transparent)] + PwParserError(#[from] PwParserError), +} diff --git a/src/pipewire/pw_mem.rs b/src/pipewire/pw_mem.rs new file mode 100644 index 00000000..be424daf --- /dev/null +++ b/src/pipewire/pw_mem.rs @@ -0,0 +1,152 @@ +use { + crate::utils::{ + copyhashmap::CopyHashMap, + mmap::{mmap, Mmapped}, + oserror::OsError, + page_size::page_size, + ptr_ext::{MutPtrExt, PtrExt}, + }, + std::{marker::PhantomData, mem, ops::Range, rc::Rc}, + thiserror::Error, + uapi::{c, OwnedFd, Pod}, +}; + +#[derive(Default)] +pub struct PwMemPool { + pub mems: CopyHashMap>, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum PwMemType { + MemFd, + DmaBuf, +} + +pub struct PwMem { + pub ty: PwMemType, + pub read: bool, + pub write: bool, + pub fd: Rc, +} + +pub struct PwMemMap { + pub mem: Rc, + pub range: Range, + pub map: Mmapped, +} + +pub struct PwMemTyped { + mem: Rc, + offset: usize, + _phantom: PhantomData, +} + +pub struct PwMemSlice { + mem: Rc, + range: Range, +} + +impl PwMemPool { + pub fn map(&self, memid: u32, offset: u32, size: u32) -> Result, PwMemError> { + match self.mems.get(&memid) { + Some(m) => m.map(offset, size), + _ => Err(PwMemError::MemidDoesNotExist(memid)), + } + } +} + +impl PwMem { + pub fn map(self: &Rc, offset: u32, size: u32) -> Result, PwMemError> { + let mask = page_size() - 1; + let offset = offset as usize; + let size = size as usize; + let start = offset & !mask; + let dist = offset - start; + let len = (size + dist + mask) & !mask; + let range = dist..dist + size; + let mut prot = 0; + if self.read { + prot |= c::PROT_READ; + } + if self.write { + prot |= c::PROT_WRITE; + } + let map = match mmap(len as _, prot, c::MAP_SHARED, self.fd.raw(), start as _) { + Ok(m) => m, + Err(e) => return Err(PwMemError::MmapFailed(e)), + }; + Ok(Rc::new(PwMemMap { + mem: self.clone(), + range, + map, + })) + } +} + +impl PwMemMap { + #[allow(dead_code)] + pub unsafe fn read(&self) -> &T { + self.check::(0); + (self.map.ptr.cast::().add(self.range.start) as *const T).deref() + } + + #[allow(dead_code)] + pub unsafe fn write(&self) -> &mut T { + self.check::(0); + (self.map.ptr.cast::().add(self.range.start) as *mut T).deref_mut() + } + + #[allow(dead_code)] + pub unsafe fn bytes_mut(&self) -> &mut [u8] { + std::slice::from_raw_parts_mut( + self.map.ptr.cast::().add(self.range.start) as _, + self.range.len(), + ) + } + + fn check(&self, offset: usize) { + assert!(offset <= self.range.len()); + assert!(mem::size_of::() <= self.range.len() - offset); + assert_eq!((mem::align_of::() - 1) & (self.range.start + offset), 0); + } + + pub fn typed(self: &Rc) -> Rc> { + self.typed_at(0) + } + + pub fn typed_at(self: &Rc, offset: usize) -> Rc> { + self.check::(offset); + Rc::new(PwMemTyped { + mem: self.clone(), + offset: self.range.start + offset, + _phantom: Default::default(), + }) + } + + pub fn slice(self: &Rc, range: Range) -> Rc { + assert!(range.start <= self.range.len()); + assert!(range.len() <= self.range.len() - range.start); + Rc::new(PwMemSlice { + mem: self.clone(), + range: self.range.start + range.start..self.range.start + range.end, + }) + } +} + +impl PwMemTyped { + pub unsafe fn read(&self) -> &T { + (self.mem.map.ptr.cast::().add(self.offset) as *const T).deref() + } + + pub unsafe fn write(&self) -> &mut T { + (self.mem.map.ptr.cast::().add(self.offset) as *mut T).deref_mut() + } +} + +#[derive(Debug, Error)] +pub enum PwMemError { + #[error("mmap failed")] + MmapFailed(#[source] OsError), + #[error("memid {0} does not exist")] + MemidDoesNotExist(u32), +} diff --git a/src/pipewire/pw_object.rs b/src/pipewire/pw_object.rs new file mode 100644 index 00000000..9b487aeb --- /dev/null +++ b/src/pipewire/pw_object.rs @@ -0,0 +1,52 @@ +use { + crate::{pipewire::pw_parser::PwParser, utils::numcell::NumCell}, + std::{cell::Cell, fmt::Debug, rc::Rc}, + thiserror::Error, +}; + +pub trait PwObjectBase { + fn data(&self) -> &PwObjectData; + fn interface(&self) -> &str; + fn handle_msg(self: Rc, opcode: u8, parser: PwParser<'_>) -> Result<(), PwObjectError>; + fn event_name(&self, opcode: u8) -> Option<&'static str>; +} + +pub trait PwObject: PwObjectBase { + fn bound_id(&self, id: u32) { + let _ = id; + } + + fn done(&self) {} + + fn break_loops(&self) {} +} + +pub struct PwObjectData { + pub id: u32, + pub bound_id: Cell>, + pub sync_id: NumCell, +} + +#[derive(Debug, Error)] +#[error("An error occurred in a `{interface}`")] +pub struct PwObjectError { + pub interface: &'static str, + #[source] + pub source: PwObjectErrorType, +} + +#[derive(Debug, Error)] +pub enum PwObjectErrorType { + #[error("Unknown event {0}")] + UnknownEvent(u8), + #[error("An error occurred in event `{method}`")] + EventError { + method: &'static str, + #[source] + source: Box, + }, +} + +pub trait PwOpcode: Debug { + fn id(&self) -> u8; +} diff --git a/src/pipewire/pw_parser.rs b/src/pipewire/pw_parser.rs new file mode 100644 index 00000000..78a71395 --- /dev/null +++ b/src/pipewire/pw_parser.rs @@ -0,0 +1,315 @@ +#![allow(non_upper_case_globals)] + +use { + crate::pipewire::pw_pod::{ + PW_CHOICE_None, PW_TYPE_Array, PW_TYPE_Bitmap, PW_TYPE_Bool, PW_TYPE_Bytes, PW_TYPE_Choice, + PW_TYPE_Double, PW_TYPE_Fd, PW_TYPE_Float, PW_TYPE_Fraction, PW_TYPE_Id, PW_TYPE_Int, + PW_TYPE_Long, PW_TYPE_None, PW_TYPE_Object, PW_TYPE_Pod, PW_TYPE_Pointer, + PW_TYPE_Rectangle, PW_TYPE_Sequence, PW_TYPE_String, PW_TYPE_Struct, PwChoiceType, + PwControlType, PwPod, PwPodArray, PwPodChoice, PwPodControl, PwPodFraction, PwPodObject, + PwPodObjectType, PwPodPointer, PwPodRectangle, PwPodSequence, PwPodStruct, PwPodType, + PwPointerType, PwProp, PwPropFlag, + }, + ahash::AHashMap, + bstr::{BStr, BString, ByteSlice}, + std::{ + fmt::Debug, + mem::{self, MaybeUninit}, + rc::Rc, + }, + thiserror::Error, + uapi::{OwnedFd, Pod}, +}; + +#[derive(Debug, Error)] +pub enum PwParserError { + #[error("Unexpected EOF")] + UnexpectedEof, + #[error("Message references an FD that is out of bounds")] + MissingFd, + #[error("Array element type has size of 0")] + ZeroSizedArrayElementType, + #[error("Unknown POD type: {0:?}")] + UnknownType(PwPodType), + #[error("Unexpected POD type: Expected {0:?}, got {1:?}")] + UnexpectedPodType(PwPodType, PwPodType), +} + +#[derive(Copy, Clone)] +pub struct PwParser<'a> { + data: &'a [u8], + fds: &'a [Rc], + pos: usize, +} + +impl<'a> PwParser<'a> { + pub fn new(data: &'a [u8], fds: &'a [Rc]) -> Self { + Self { data, fds, pos: 0 } + } + + pub fn reset(&mut self) { + self.pos = 0; + } + + fn read_raw(&mut self, offset: usize) -> Result { + if self.pos + offset + mem::size_of::() <= self.data.len() { + unsafe { + let mut res = MaybeUninit::uninit(); + let src = self.data[self.pos + offset..].as_ptr(); + std::ptr::copy_nonoverlapping(src, res.as_mut_ptr() as _, mem::size_of::()); + Ok(res.assume_init()) + } + } else { + Err(PwParserError::UnexpectedEof) + } + } + + pub fn len(&self) -> usize { + self.data.len() - self.pos + } + + pub fn pos(&self) -> usize { + self.pos + } + + fn read_array(&mut self, offset: usize, len: usize) -> Result, PwParserError> { + let child_len = self.read_raw::(offset)? as usize; + if child_len == 0 { + return Err(PwParserError::ZeroSizedArrayElementType); + } + let ty = PwPodType(self.read_raw(offset + 4)?); + Ok(PwPodArray { + ty, + child_len, + n_elements: (len - 8) / child_len, + elements: PwParser::new( + &self.data[self.pos + offset + 8..self.pos + offset + len], + self.fds, + ), + }) + } + + pub fn read_dict_struct(&mut self) -> Result, PwParserError> { + let s2 = self.read_struct()?; + let mut p3 = s2.fields; + let num_dict_entries = p3.read_int()?; + let mut de = AHashMap::new(); + for _ in 0..num_dict_entries { + de.insert(p3.read_string()?.to_owned(), p3.read_string()?.to_owned()); + } + Ok(de) + } + + pub fn read_struct(&mut self) -> Result, PwParserError> { + match self.read_pod()? { + PwPod::Struct(s) => Ok(s), + v => Err(PwParserError::UnexpectedPodType(PW_TYPE_Struct, v.ty())), + } + } + + pub fn read_uint(&mut self) -> Result { + self.read_int().map(|v| v as u32) + } + + pub fn read_int(&mut self) -> Result { + match self.read_value()? { + PwPod::Int(s) => Ok(s), + v => Err(PwParserError::UnexpectedPodType(PW_TYPE_Int, v.ty())), + } + } + + pub fn read_object_opt(&mut self) -> Result, PwParserError> { + match self.read_pod()? { + PwPod::Object(p) => Ok(Some(p)), + PwPod::None => Ok(None), + v => Err(PwParserError::UnexpectedPodType(PW_TYPE_Object, v.ty())), + } + } + + pub fn read_object(&mut self) -> Result { + match self.read_object_opt()? { + Some(p) => Ok(p), + _ => Err(PwParserError::UnexpectedPodType( + PW_TYPE_Object, + PW_TYPE_None, + )), + } + } + + pub fn read_id(&mut self) -> Result { + match self.read_value()? { + PwPod::Id(s) => Ok(s), + v => Err(PwParserError::UnexpectedPodType(PW_TYPE_Id, v.ty())), + } + } + + pub fn read_fd_opt(&mut self) -> Result>, PwParserError> { + match self.read_pod()? { + PwPod::Fd(idx) if idx == !0 => Ok(None), + PwPod::Fd(idx) => match self.fds.get(idx as usize) { + Some(fd) => Ok(Some(fd.clone())), + _ => Err(PwParserError::MissingFd), + }, + v => Err(PwParserError::UnexpectedPodType(PW_TYPE_Id, v.ty())), + } + } + + pub fn read_fd(&mut self) -> Result, PwParserError> { + match self.read_fd_opt()? { + Some(fd) => Ok(fd), + _ => Err(PwParserError::MissingFd), + } + } + + pub fn read_ulong(&mut self) -> Result { + self.read_long().map(|l| l as _) + } + + pub fn read_long(&mut self) -> Result { + match self.read_value()? { + PwPod::Long(s) => Ok(s), + v => Err(PwParserError::UnexpectedPodType(PW_TYPE_Long, v.ty())), + } + } + + pub fn read_string(&mut self) -> Result<&'a BStr, PwParserError> { + match self.read_value()? { + PwPod::String(s) => Ok(s), + v => Err(PwParserError::UnexpectedPodType(PW_TYPE_String, v.ty())), + } + } + + pub fn read_value(&mut self) -> Result, PwParserError> { + let mut v = self.read_pod(); + if let Ok(PwPod::Choice(v)) = &mut v { + if v.ty == PW_CHOICE_None && v.elements.n_elements > 0 { + return v + .elements + .elements + .read_pod_body_packed(v.elements.ty, v.elements.child_len); + } + } + v + } + + pub fn read_pod(&mut self) -> Result, PwParserError> { + let len = self.read_raw::(0)? as usize; + let ty = PwPodType(self.read_raw(4)?); + self.pos += 8; + self.read_pod_body(ty, len) + } + + pub fn read_pod_body_packed( + &mut self, + ty: PwPodType, + len: usize, + ) -> Result, PwParserError> { + self.read_pod_body2(ty, len, true) + } + + pub fn read_pod_body(&mut self, ty: PwPodType, len: usize) -> Result, PwParserError> { + self.read_pod_body2(ty, len, false) + } + + fn read_pod_body2( + &mut self, + ty: PwPodType, + len: usize, + packed: bool, + ) -> Result, PwParserError> { + let size = if packed { len } else { (len + 7) & !7 }; + if self.len() < size as usize { + return Err(PwParserError::UnexpectedEof); + } + let val = match ty { + PW_TYPE_None => PwPod::None, + PW_TYPE_Bool => PwPod::Bool(self.read_raw::(0)? != 0), + PW_TYPE_Id => PwPod::Id(self.read_raw(0)?), + PW_TYPE_Int => PwPod::Int(self.read_raw(0)?), + PW_TYPE_Long => PwPod::Long(self.read_raw(0)?), + PW_TYPE_Float => PwPod::Float(self.read_raw(0)?), + PW_TYPE_Double => PwPod::Double(self.read_raw(0)?), + PW_TYPE_String => { + let s = if len == 0 { + &[][..] + } else { + &self.data[self.pos..self.pos + len - 1] + }; + PwPod::String(s.as_bstr()) + } + PW_TYPE_Bytes => PwPod::Bytes(&self.data[self.pos..self.pos + len]), + PW_TYPE_Rectangle => PwPod::Rectangle(PwPodRectangle { + width: self.read_raw(0)?, + height: self.read_raw(4)?, + }), + PW_TYPE_Fraction => PwPod::Fraction(PwPodFraction { + num: self.read_raw(0)?, + denom: self.read_raw(4)?, + }), + PW_TYPE_Bitmap => PwPod::Bitmap(&self.data[self.pos..self.pos + len]), + PW_TYPE_Array => PwPod::Array(self.read_array(0, len)?), + PW_TYPE_Struct => PwPod::Struct(PwPodStruct { + fields: PwParser::new(&self.data[self.pos..self.pos + len], self.fds), + }), + PW_TYPE_Object => PwPod::Object(PwPodObject { + ty: PwPodObjectType(self.read_raw(0)?), + id: self.read_raw(4)?, + probs: PwParser::new(&self.data[self.pos + 8..self.pos + len], self.fds), + }), + PW_TYPE_Sequence => PwPod::Sequence(PwPodSequence { + unit: self.read_raw(0)?, + controls: PwParser::new(&self.data[self.pos + 8..self.pos + len], self.fds), + }), + PW_TYPE_Pointer => PwPod::Pointer(PwPodPointer { + ty: PwPointerType(self.read_raw(0)?), + value: self.read_raw(8)?, + }), + PW_TYPE_Fd => PwPod::Fd(self.read_raw(0)?), + PW_TYPE_Choice => PwPod::Choice(PwPodChoice { + ty: PwChoiceType(self.read_raw(0)?), + flags: self.read_raw(4)?, + elements: self.read_array(8, len - 8)?, + }), + PW_TYPE_Pod => { + let pos = self.pos; + let pod = self.read_pod()?; + self.pos = pos; + pod + } + _ => return Err(PwParserError::UnknownType(ty)), + }; + self.pos += size as usize; + Ok(val) + } + + pub fn read_prop(&mut self) -> Result, PwParserError> { + let key = self.read_raw(0)?; + let flags = PwPropFlag(self.read_raw(4)?); + self.pos += 8; + Ok(PwProp { + key, + flags, + pod: self.read_pod()?, + }) + } + + pub fn read_control(&mut self) -> Result, PwParserError> { + let offset = self.read_raw(0)?; + let ty = PwControlType(self.read_raw(4)?); + self.pos += 8; + Ok(PwPodControl { + offset, + ty, + value: self.read_pod()?, + }) + } + + pub fn skip(&mut self) -> Result<(), PwParserError> { + let size = self.read_raw::(0)? as usize; + if self.len() < size + 8 { + return Err(PwParserError::UnexpectedEof); + } + self.pos += size + 8; + Ok(()) + } +} diff --git a/src/pipewire/pw_pod.rs b/src/pipewire/pw_pod.rs new file mode 100644 index 00000000..babfc843 --- /dev/null +++ b/src/pipewire/pw_pod.rs @@ -0,0 +1,1451 @@ +#![allow(non_upper_case_globals, non_camel_case_types)] + +mod pw_debug; + +use { + crate::pipewire::pw_parser::{PwParser, PwParserError}, + bstr::BStr, + std::fmt::{Debug, Formatter}, + uapi::{c, Pod}, +}; + +macro_rules! ty { + ($name:ident; $($id:ident = $val:expr,)*) => { + #[derive(Copy, Clone, Eq, PartialEq, Hash)] + #[repr(transparent)] + pub struct $name(pub u32); + + $( + pub const $id: $name = $name($val); + )* + + impl $name { + pub fn name(self) -> Option<&'static str> { + let res = match self { + $( + $id => stringify!($id), + )* + _ => return None, + }; + Some(res) + } + } + + impl Debug for $name { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self.name() { + Some(n) => write!(f, "{}", n), + _ => write!(f, "{}({})", stringify!($name), self.0), + } + } + } + } +} + +ty! { + PwPodType; + + PW_TYPE_None = 0x01, + PW_TYPE_Bool = 0x02, + PW_TYPE_Id = 0x03, + PW_TYPE_Int = 0x04, + PW_TYPE_Long = 0x05, + PW_TYPE_Float = 0x06, + PW_TYPE_Double = 0x07, + PW_TYPE_String = 0x08, + PW_TYPE_Bytes = 0x09, + PW_TYPE_Rectangle = 0x0A, + PW_TYPE_Fraction = 0x0B, + PW_TYPE_Bitmap = 0x0C, + PW_TYPE_Array = 0x0D, + PW_TYPE_Struct = 0x0E, + PW_TYPE_Object = 0x0F, + PW_TYPE_Sequence = 0x10, + PW_TYPE_Pointer = 0x11, + PW_TYPE_Fd = 0x12, + PW_TYPE_Choice = 0x13, + PW_TYPE_Pod = 0x14, +} + +ty! { + PwPodObjectType; + + PW_COMMAND_Device = 0x30001, + PW_COMMAND_Node = 0x30002, + + PW_OBJECT_PropInfo = 0x40001, + PW_OBJECT_Props = 0x40002, + PW_OBJECT_Format = 0x40003, + PW_OBJECT_ParamBuffers = 0x40004, + PW_OBJECT_ParamMeta = 0x40005, + PW_OBJECT_ParamIO = 0x40006, + PW_OBJECT_ParamProfile = 0x40007, + PW_OBJECT_ParamPortConfig = 0x40008, + PW_OBJECT_ParamRoute = 0x40009, + PW_OBJECT_Profiler = 0x4000A, + PW_OBJECT_ParamLatency = 0x4000B, + PW_OBJECT_ParamProcessLatency = 0x4000C, +} + +ty! { + SpaParamType; + + SPA_PARAM_Invalid = 0, + SPA_PARAM_PropInfo = 1, + SPA_PARAM_Props = 2, + SPA_PARAM_EnumFormat = 3, + SPA_PARAM_Format = 4, + SPA_PARAM_Buffers = 5, + SPA_PARAM_Meta = 6, + SPA_PARAM_IO = 7, + SPA_PARAM_EnumProfile = 8, + SPA_PARAM_Profile = 9, + SPA_PARAM_EnumPortConfig = 10, + SPA_PARAM_PortConfig = 11, + SPA_PARAM_EnumRoute = 12, + SPA_PARAM_Route = 13, + SPA_PARAM_Control = 14, + SPA_PARAM_Latency = 15, + SPA_PARAM_ProcessLatency = 16, +} + +ty! { + SpaFormat; + + SPA_FORMAT_START = 0x00000, + + SPA_FORMAT_mediaType = 0x00001, + SPA_FORMAT_mediaSubtype = 0x00002, + + SPA_FORMAT_START_Audio = 0x10000, + SPA_FORMAT_AUDIO_format = 0x10001, + SPA_FORMAT_AUDIO_flags = 0x10002, + SPA_FORMAT_AUDIO_rate = 0x10003, + SPA_FORMAT_AUDIO_channels = 0x10004, + SPA_FORMAT_AUDIO_position = 0x10005, + SPA_FORMAT_AUDIO_iec958Codec = 0x10006, + SPA_FORMAT_AUDIO_bitorder = 0x10007, + SPA_FORMAT_AUDIO_interleave = 0x10008, + + SPA_FORMAT_START_Video = 0x20000, + SPA_FORMAT_VIDEO_format = 0x20001, + SPA_FORMAT_VIDEO_modifier = 0x20002, + SPA_FORMAT_VIDEO_size = 0x20003, + SPA_FORMAT_VIDEO_framerate = 0x20004, + SPA_FORMAT_VIDEO_maxFramerate = 0x20005, + SPA_FORMAT_VIDEO_views = 0x20006, + SPA_FORMAT_VIDEO_interlaceMode = 0x20007, + SPA_FORMAT_VIDEO_pixelAspectRatio = 0x20008, + SPA_FORMAT_VIDEO_multiviewMode = 0x20009, + SPA_FORMAT_VIDEO_multiviewFlags = 0x2000A, + SPA_FORMAT_VIDEO_chromaSite = 0x2000B, + SPA_FORMAT_VIDEO_colorRange = 0x2000C, + SPA_FORMAT_VIDEO_colorMatrix = 0x2000D, + SPA_FORMAT_VIDEO_transferFunction = 0x2000E, + SPA_FORMAT_VIDEO_colorPrimaries = 0x2000F, + SPA_FORMAT_VIDEO_profile = 0x20010, + SPA_FORMAT_VIDEO_level = 0x20011, + SPA_FORMAT_VIDEO_H264_streamFormat = 0x20012, + SPA_FORMAT_VIDEO_H264_alignment = 0x20013, + + SPA_FORMAT_START_Image = 0x30000, + SPA_FORMAT_START_Binary = 0x40000, + SPA_FORMAT_START_Stream = 0x50000, + SPA_FORMAT_START_Application = 0x60000, +} + +bitflags! { + SPA_PARAM_INFO: u32; + + SPA_PARAM_INFO_SERIAL = 1<<0, + SPA_PARAM_INFO_READ = 1<<1, + SPA_PARAM_INFO_WRITE = 1<<2, +} + +ty! { + PwControlType; + + PW_CONTROL_PropInfo = 1, + PW_CONTROL_Props = 2, + PW_CONTROL_Format = 3, +} + +ty! { + PwPointerType; + + PW_POINTER_Buffer = 0x10001, + PW_POINTER_Meta = 0x10002, + PW_POINTER_Dict = 0x10003, +} + +ty! { + PwChoiceType; + + PW_CHOICE_None = 0, + PW_CHOICE_Range = 1, + PW_CHOICE_Step = 2, + PW_CHOICE_Enum = 3, + PW_CHOICE_Flags = 4, +} + +ty! { + PwIoType; + + PW_IO_Buffers = 1, + PW_IO_Range = 2, + PW_IO_Clock = 3, + PW_IO_Latency = 4, + PW_IO_Control = 5, + PW_IO_Notify = 6, + PW_IO_Position = 7, + PW_IO_RateMatch = 8, + PW_IO_Memory = 9, +} + +bitflags! { + PwPropFlag: u32; + + PW_PROP_READONLY = 1 << 0, + PW_PROP_HARDWARE = 1 << 1, + PW_PROP_HINT_DICT = 1 << 2, + PW_PROP_MANDATORY = 1 << 3, + PW_PROP_DONT_FIXATE = 1 << 4, +} + +ty! { + SpaMediaType; + + SPA_MEDIA_TYPE_unknown = 0, + SPA_MEDIA_TYPE_audio = 1, + SPA_MEDIA_TYPE_video = 2, + SPA_MEDIA_TYPE_image = 3, + SPA_MEDIA_TYPE_binary = 4, + SPA_MEDIA_TYPE_stream = 5, + SPA_MEDIA_TYPE_application = 6, +} + +ty! { + SpaMediaSubtype; + + SPA_MEDIA_SUBTYPE_unknown = 0x00000, + SPA_MEDIA_SUBTYPE_raw = 0x00001, + SPA_MEDIA_SUBTYPE_dsp = 0x00002, + SPA_MEDIA_SUBTYPE_iec958 = 0x00003, + SPA_MEDIA_SUBTYPE_dsd = 0x00004, + + SPA_MEDIA_SUBTYPE_START_Audio = 0x10000, + SPA_MEDIA_SUBTYPE_mp3 = 0x10001, + SPA_MEDIA_SUBTYPE_aac = 0x10002, + SPA_MEDIA_SUBTYPE_vorbis = 0x10003, + SPA_MEDIA_SUBTYPE_wma = 0x10004, + SPA_MEDIA_SUBTYPE_ra = 0x10005, + SPA_MEDIA_SUBTYPE_sbc = 0x10006, + SPA_MEDIA_SUBTYPE_adpcm = 0x10007, + SPA_MEDIA_SUBTYPE_g723 = 0x10008, + SPA_MEDIA_SUBTYPE_g726 = 0x10009, + SPA_MEDIA_SUBTYPE_g729 = 0x1000A, + SPA_MEDIA_SUBTYPE_amr = 0x1000B, + SPA_MEDIA_SUBTYPE_gsm = 0x1000C, + + SPA_MEDIA_SUBTYPE_START_Video = 0x20000, + SPA_MEDIA_SUBTYPE_h264 = 0x20001, + SPA_MEDIA_SUBTYPE_mjpg = 0x20002, + SPA_MEDIA_SUBTYPE_dv = 0x20003, + SPA_MEDIA_SUBTYPE_mpegts = 0x20004, + SPA_MEDIA_SUBTYPE_h263 = 0x20005, + SPA_MEDIA_SUBTYPE_mpeg1 = 0x20006, + SPA_MEDIA_SUBTYPE_mpeg2 = 0x20007, + SPA_MEDIA_SUBTYPE_mpeg4 = 0x20008, + SPA_MEDIA_SUBTYPE_xvid = 0x20009, + SPA_MEDIA_SUBTYPE_vc1 = 0x2000A, + SPA_MEDIA_SUBTYPE_vp8 = 0x2000B, + SPA_MEDIA_SUBTYPE_vp9 = 0x2000C, + SPA_MEDIA_SUBTYPE_bayer = 0x2000D, + + SPA_MEDIA_SUBTYPE_START_Image = 0x30000, + SPA_MEDIA_SUBTYPE_jpeg = 0x30001, + + SPA_MEDIA_SUBTYPE_START_Binary = 0x40000, + + SPA_MEDIA_SUBTYPE_START_Stream = 0x50000, + SPA_MEDIA_SUBTYPE_midi = 0x50001, + + SPA_MEDIA_SUBTYPE_START_Application = 0x60000, + SPA_MEDIA_SUBTYPE_control = 0x60001, +} + +ty! { + SpaAudioFormat; + + SPA_AUDIO_FORMAT_UNKNOWN = 0x000, + SPA_AUDIO_FORMAT_ENCODED = 0x001, + + SPA_AUDIO_FORMAT_START_Interleaved = 0x100, + SPA_AUDIO_FORMAT_S8 = 0x101, + SPA_AUDIO_FORMAT_U8 = 0x102, + SPA_AUDIO_FORMAT_S16_LE = 0x103, + SPA_AUDIO_FORMAT_S16_BE = 0x104, + SPA_AUDIO_FORMAT_U16_LE = 0x105, + SPA_AUDIO_FORMAT_U16_BE = 0x106, + SPA_AUDIO_FORMAT_S24_32_LE = 0x107, + SPA_AUDIO_FORMAT_S24_32_BE = 0x108, + SPA_AUDIO_FORMAT_U24_32_LE = 0x109, + SPA_AUDIO_FORMAT_U24_32_BE = 0x10A, + SPA_AUDIO_FORMAT_S32_LE = 0x10B, + SPA_AUDIO_FORMAT_S32_BE = 0x10C, + SPA_AUDIO_FORMAT_U32_LE = 0x10D, + SPA_AUDIO_FORMAT_U32_BE = 0x10E, + SPA_AUDIO_FORMAT_S24_LE = 0x10F, + SPA_AUDIO_FORMAT_S24_BE = 0x110, + SPA_AUDIO_FORMAT_U24_LE = 0x111, + SPA_AUDIO_FORMAT_U24_BE = 0x112, + SPA_AUDIO_FORMAT_S20_LE = 0x113, + SPA_AUDIO_FORMAT_S20_BE = 0x114, + SPA_AUDIO_FORMAT_U20_LE = 0x115, + SPA_AUDIO_FORMAT_U20_BE = 0x116, + SPA_AUDIO_FORMAT_S18_LE = 0x117, + SPA_AUDIO_FORMAT_S18_BE = 0x118, + SPA_AUDIO_FORMAT_U18_LE = 0x119, + SPA_AUDIO_FORMAT_U18_BE = 0x11A, + SPA_AUDIO_FORMAT_F32_LE = 0x11B, + SPA_AUDIO_FORMAT_F32_BE = 0x11C, + SPA_AUDIO_FORMAT_F64_LE = 0x11D, + SPA_AUDIO_FORMAT_F64_BE = 0x11E, + + SPA_AUDIO_FORMAT_ULAW = 0x11F, + SPA_AUDIO_FORMAT_ALAW = 0x120, + + SPA_AUDIO_FORMAT_START_Planar = 0x200, + SPA_AUDIO_FORMAT_U8P = 0x201, + SPA_AUDIO_FORMAT_S16P = 0x202, + SPA_AUDIO_FORMAT_S24_32P = 0x203, + SPA_AUDIO_FORMAT_S32P = 0x204, + SPA_AUDIO_FORMAT_S24P = 0x205, + SPA_AUDIO_FORMAT_F32P = 0x206, + SPA_AUDIO_FORMAT_F64P = 0x207, + SPA_AUDIO_FORMAT_S8P = 0x208, + + SPA_AUDIO_FORMAT_START_Other = 0x400, +} + +ty! { + SpaVideoFormat; + + SPA_VIDEO_FORMAT_UNKNOWN = 000, + SPA_VIDEO_FORMAT_ENCODED = 001, + SPA_VIDEO_FORMAT_I420 = 002, + SPA_VIDEO_FORMAT_YV12 = 003, + SPA_VIDEO_FORMAT_YUY2 = 004, + SPA_VIDEO_FORMAT_UYVY = 005, + SPA_VIDEO_FORMAT_AYUV = 006, + SPA_VIDEO_FORMAT_RGBx = 007, + SPA_VIDEO_FORMAT_BGRx = 008, + SPA_VIDEO_FORMAT_xRGB = 009, + SPA_VIDEO_FORMAT_xBGR = 010, + SPA_VIDEO_FORMAT_RGBA = 011, + SPA_VIDEO_FORMAT_BGRA = 012, + SPA_VIDEO_FORMAT_ARGB = 013, + SPA_VIDEO_FORMAT_ABGR = 014, + SPA_VIDEO_FORMAT_RGB = 015, + SPA_VIDEO_FORMAT_BGR = 016, + SPA_VIDEO_FORMAT_Y41B = 017, + SPA_VIDEO_FORMAT_Y42B = 018, + SPA_VIDEO_FORMAT_YVYU = 019, + SPA_VIDEO_FORMAT_Y444 = 020, + SPA_VIDEO_FORMAT_v210 = 021, + SPA_VIDEO_FORMAT_v216 = 022, + SPA_VIDEO_FORMAT_NV12 = 023, + SPA_VIDEO_FORMAT_NV21 = 024, + SPA_VIDEO_FORMAT_GRAY8 = 025, + SPA_VIDEO_FORMAT_GRAY16_BE = 026, + SPA_VIDEO_FORMAT_GRAY16_LE = 027, + SPA_VIDEO_FORMAT_v308 = 028, + SPA_VIDEO_FORMAT_RGB16 = 029, + SPA_VIDEO_FORMAT_BGR16 = 030, + SPA_VIDEO_FORMAT_RGB15 = 031, + SPA_VIDEO_FORMAT_BGR15 = 032, + SPA_VIDEO_FORMAT_UYVP = 033, + SPA_VIDEO_FORMAT_A420 = 034, + SPA_VIDEO_FORMAT_RGB8P = 035, + SPA_VIDEO_FORMAT_YUV9 = 036, + SPA_VIDEO_FORMAT_YVU9 = 037, + SPA_VIDEO_FORMAT_IYU1 = 038, + SPA_VIDEO_FORMAT_ARGB64 = 039, + SPA_VIDEO_FORMAT_AYUV64 = 040, + SPA_VIDEO_FORMAT_r210 = 041, + SPA_VIDEO_FORMAT_I420_10BE = 042, + SPA_VIDEO_FORMAT_I420_10LE = 043, + SPA_VIDEO_FORMAT_I422_10BE = 044, + SPA_VIDEO_FORMAT_I422_10LE = 045, + SPA_VIDEO_FORMAT_Y444_10BE = 046, + SPA_VIDEO_FORMAT_Y444_10LE = 047, + SPA_VIDEO_FORMAT_GBR = 048, + SPA_VIDEO_FORMAT_GBR_10BE = 049, + SPA_VIDEO_FORMAT_GBR_10LE = 050, + SPA_VIDEO_FORMAT_NV16 = 051, + SPA_VIDEO_FORMAT_NV24 = 052, + SPA_VIDEO_FORMAT_NV12_64Z32 = 053, + SPA_VIDEO_FORMAT_A420_10BE = 054, + SPA_VIDEO_FORMAT_A420_10LE = 055, + SPA_VIDEO_FORMAT_A422_10BE = 056, + SPA_VIDEO_FORMAT_A422_10LE = 057, + SPA_VIDEO_FORMAT_A444_10BE = 058, + SPA_VIDEO_FORMAT_A444_10LE = 059, + SPA_VIDEO_FORMAT_NV61 = 060, + SPA_VIDEO_FORMAT_P010_10BE = 061, + SPA_VIDEO_FORMAT_P010_10LE = 062, + SPA_VIDEO_FORMAT_IYU2 = 063, + SPA_VIDEO_FORMAT_VYUY = 064, + SPA_VIDEO_FORMAT_GBRA = 065, + SPA_VIDEO_FORMAT_GBRA_10BE = 066, + SPA_VIDEO_FORMAT_GBRA_10LE = 067, + SPA_VIDEO_FORMAT_GBR_12BE = 068, + SPA_VIDEO_FORMAT_GBR_12LE = 069, + SPA_VIDEO_FORMAT_GBRA_12BE = 070, + SPA_VIDEO_FORMAT_GBRA_12LE = 071, + SPA_VIDEO_FORMAT_I420_12BE = 072, + SPA_VIDEO_FORMAT_I420_12LE = 073, + SPA_VIDEO_FORMAT_I422_12BE = 074, + SPA_VIDEO_FORMAT_I422_12LE = 075, + SPA_VIDEO_FORMAT_Y444_12BE = 076, + SPA_VIDEO_FORMAT_Y444_12LE = 077, + SPA_VIDEO_FORMAT_RGBA_F16 = 078, + SPA_VIDEO_FORMAT_RGBA_F32 = 079, + SPA_VIDEO_FORMAT_xRGB_210LE = 080, + SPA_VIDEO_FORMAT_xBGR_210LE = 081, + SPA_VIDEO_FORMAT_RGBx_102LE = 082, + SPA_VIDEO_FORMAT_BGRx_102LE = 083, + SPA_VIDEO_FORMAT_ARGB_210LE = 084, + SPA_VIDEO_FORMAT_ABGR_210LE = 085, + SPA_VIDEO_FORMAT_RGBA_102LE = 086, + SPA_VIDEO_FORMAT_BGRA_102LE = 087, +} + +ty! { + SpaVideoInterlaceMode; + + SPA_VIDEO_INTERLACE_MODE_PROGRESSIVE = 0, + SPA_VIDEO_INTERLACE_MODE_INTERLEAVED = 1, + SPA_VIDEO_INTERLACE_MODE_MIXED = 2, + SPA_VIDEO_INTERLACE_MODE_FIELDS = 3, +} + +ty! { + SpaVideoMultiviewMode; + + SPA_VIDEO_MULTIVIEW_MODE_NONE = !0, + SPA_VIDEO_MULTIVIEW_MODE_MONO = 0, + + SPA_VIDEO_MULTIVIEW_MODE_LEFT = 1, + SPA_VIDEO_MULTIVIEW_MODE_RIGHT = 2, + + SPA_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE = 3, + SPA_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE_QUINCUNX = 4, + SPA_VIDEO_MULTIVIEW_MODE_COLUMN_INTERLEAVED = 5, + SPA_VIDEO_MULTIVIEW_MODE_ROW_INTERLEAVED = 6, + SPA_VIDEO_MULTIVIEW_MODE_TOP_BOTTOM = 7, + SPA_VIDEO_MULTIVIEW_MODE_CHECKERBOARD = 8, + + SPA_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME = 32, + SPA_VIDEO_MULTIVIEW_MODE_MULTIVIEW_FRAME_BY_FRAME = 33, + SPA_VIDEO_MULTIVIEW_MODE_SEPARATED = 34, +} + +bitflags! { + SpaVideoMultiviewFlags: u32; + + SPA_VIDEO_MULTIVIEW_FLAGS_NONE = 0, + SPA_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST = 1 << 0, + SPA_VIDEO_MULTIVIEW_FLAGS_LEFT_FLIPPED = 1 << 1, + SPA_VIDEO_MULTIVIEW_FLAGS_LEFT_FLOPPED = 1 << 2, + SPA_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLIPPED = 1 << 3, + SPA_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLOPPED = 1 << 4, + SPA_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT = 1 << 14, + SPA_VIDEO_MULTIVIEW_FLAGS_MIXED_MONO = 1 << 15, +} + +bitflags! { + SpaVideoChromaSite: u32; + + SPA_VIDEO_CHROMA_SITE_UNKNOWN = 0, + SPA_VIDEO_CHROMA_SITE_NONE = 1 << 0, + SPA_VIDEO_CHROMA_SITE_H_COSITED = 1 << 1, + SPA_VIDEO_CHROMA_SITE_V_COSITED = 1 << 2, + SPA_VIDEO_CHROMA_SITE_ALT_LINE = 1 << 3, +} + +ty! { + SpaVideoColorRange; + + SPA_VIDEO_COLOR_RANGE_UNKNOWN = 0, + SPA_VIDEO_COLOR_RANGE_0_255 = 1, + SPA_VIDEO_COLOR_RANGE_16_235 = 2, +} + +ty! { + SpaVideoColorMatrix; + + SPA_VIDEO_COLOR_MATRIX_UNKNOWN = 0, + SPA_VIDEO_COLOR_MATRIX_RGB = 1, + SPA_VIDEO_COLOR_MATRIX_FCC = 2, + SPA_VIDEO_COLOR_MATRIX_BT709 = 3, + SPA_VIDEO_COLOR_MATRIX_BT601 = 4, + SPA_VIDEO_COLOR_MATRIX_SMPTE240M = 5, + SPA_VIDEO_COLOR_MATRIX_BT2020 = 6, +} + +ty! { + SpaVideoTransferFunction; + + SPA_VIDEO_TRANSFER_UNKNOWN = 0, + SPA_VIDEO_TRANSFER_GAMMA10 = 1, + SPA_VIDEO_TRANSFER_GAMMA18 = 2, + SPA_VIDEO_TRANSFER_GAMMA20 = 3, + SPA_VIDEO_TRANSFER_GAMMA22 = 4, + SPA_VIDEO_TRANSFER_BT709 = 5, + SPA_VIDEO_TRANSFER_SMPTE240M = 6, + SPA_VIDEO_TRANSFER_SRGB = 7, + SPA_VIDEO_TRANSFER_GAMMA28 = 8, + SPA_VIDEO_TRANSFER_LOG100 = 9, + SPA_VIDEO_TRANSFER_LOG316 = 10, + SPA_VIDEO_TRANSFER_BT2020_12 = 11, + SPA_VIDEO_TRANSFER_ADOBERGB = 12, +} + +ty! { + SpaVideoColorPrimaries; + + SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN = 0, + SPA_VIDEO_COLOR_PRIMARIES_BT709 = 1, + SPA_VIDEO_COLOR_PRIMARIES_BT470M = 2, + SPA_VIDEO_COLOR_PRIMARIES_BT470BG = 3, + SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M = 4, + SPA_VIDEO_COLOR_PRIMARIES_SMPTE240M = 5, + SPA_VIDEO_COLOR_PRIMARIES_FILM = 6, + SPA_VIDEO_COLOR_PRIMARIES_BT2020 = 7, + SPA_VIDEO_COLOR_PRIMARIES_ADOBERGB = 8, +} + +ty! { + SpaH264StreamFormat; + + SPA_H264_STREAM_FORMAT_UNKNOWN = 0, + SPA_H264_STREAM_FORMAT_AVC = 1, + SPA_H264_STREAM_FORMAT_AVC3 = 2, + SPA_H264_STREAM_FORMAT_BYTESTREAM = 3, +} + +ty! { + SpaH264Alignment; + + SPA_H264_ALIGNMENT_UNKNOWN = 0, + SPA_H264_ALIGNMENT_AU = 1, + SPA_H264_ALIGNMENT_NAL = 2, +} + +ty! { + SpaParamBuffers; + + SPA_PARAM_BUFFERS_START = 0, + SPA_PARAM_BUFFERS_buffers = 1, + SPA_PARAM_BUFFERS_blocks = 2, + SPA_PARAM_BUFFERS_size = 3, + SPA_PARAM_BUFFERS_stride = 4, + SPA_PARAM_BUFFERS_align = 5, + SPA_PARAM_BUFFERS_dataType = 6, +} + +ty! { + SpaDataType; + + SPA_DATA_Invalid = 0, + SPA_DATA_MemPtr = 1, + SPA_DATA_MemFd = 2, + SPA_DATA_DmaBuf = 3, + SPA_DATA_MemId = 4, +} + +impl Default for SpaDataType { + fn default() -> Self { + SPA_DATA_Invalid + } +} + +bitflags! { + SpaNodeBuffersFlags: u32; + + SPA_NODE_BUFFERS_FLAG_ALLOC = 1 << 0, +} + +bitflags! { + SpaDataFlags: u32; + + SPA_DATA_FLAG_READABLE = 1 << 0, + SPA_DATA_FLAG_WRITABLE = 1 << 1, + SPA_DATA_FLAG_DYNAMIC = 1 << 2, +} + +bitflags! { + SpaDataTypes: u32; + + SPA_DATA_MASK_Invalid = 1, + SPA_DATA_MASK_MemPtr = 2, + SPA_DATA_MASK_MemFd = 4, + SPA_DATA_MASK_DmaBuf = 8, + SPA_DATA_MASK_MemId = 16, +} + +ty! { + SpaParamMeta; + + SPA_PARAM_META_START = 0, + SPA_PARAM_META_type = 1, + SPA_PARAM_META_size = 2, +} + +ty! { + SpaParamIo; + + SPA_PARAM_IO_START = 0, + SPA_PARAM_IO_id = 1, + SPA_PARAM_IO_size = 2, +} + +ty! { + SpaIoType; + + SPA_IO_Invalid = 0, + SPA_IO_Buffers = 1, + SPA_IO_Range = 2, + SPA_IO_Clock = 3, + SPA_IO_Latency = 4, + SPA_IO_Control = 5, + SPA_IO_Notify = 6, + SPA_IO_Position = 7, + SPA_IO_RateMatch = 8, + SPA_IO_Memory = 9, +} + +ty! { + SpaParamProfile; + + SPA_PARAM_PROFILE_START = 0, + SPA_PARAM_PROFILE_index = 1, + SPA_PARAM_PROFILE_name = 2, + SPA_PARAM_PROFILE_description = 3, + SPA_PARAM_PROFILE_priority = 4, + SPA_PARAM_PROFILE_available = 5, + SPA_PARAM_PROFILE_info = 6, + SPA_PARAM_PROFILE_classes = 7, + SPA_PARAM_PROFILE_save = 8, +} + +ty! { + SpaParamAvailability; + + SPA_PARAM_AVAILABILITY_unknown = 0, + SPA_PARAM_AVAILABILITY_no = 1, + SPA_PARAM_AVAILABILITY_yes = 2, +} + +ty! { + SpaParamPortConfig; + + SPA_PARAM_PORT_CONFIG_START = 0, + SPA_PARAM_PORT_CONFIG_direction = 1, + SPA_PARAM_PORT_CONFIG_mode = 2, + SPA_PARAM_PORT_CONFIG_monitor = 3, + SPA_PARAM_PORT_CONFIG_control = 4, + SPA_PARAM_PORT_CONFIG_format = 5, +} + +ty! { + SpaDirection; + + SPA_DIRECTION_INPUT = 0, + SPA_DIRECTION_OUTPUT = 1, +} + +ty! { + SpaParamRoute; + + SPA_PARAM_ROUTE_START = 0, + SPA_PARAM_ROUTE_index = 1, + SPA_PARAM_ROUTE_direction = 2, + SPA_PARAM_ROUTE_device = 3, + SPA_PARAM_ROUTE_name = 4, + SPA_PARAM_ROUTE_description = 5, + SPA_PARAM_ROUTE_priority = 6, + SPA_PARAM_ROUTE_available = 7, + SPA_PARAM_ROUTE_info = 8, + SPA_PARAM_ROUTE_profiles = 9, + SPA_PARAM_ROUTE_props = 10, + SPA_PARAM_ROUTE_devices = 11, + SPA_PARAM_ROUTE_profile = 12, + SPA_PARAM_ROUTE_save = 13, +} + +ty! { + SpaProfiler; + + SPA_PROFILER_START = 0x0000000, + + SPA_PROFILER_START_Driver = 0x0010000, + SPA_PROFILER_info = 0x0010001, + SPA_PROFILER_clock = 0x0010002, + SPA_PROFILER_driverBlock = 0x0010003, + SPA_PROFILER_START_Follower = 0x0020000, + SPA_PROFILER_followerBlock = 0x0020001, + SPA_PROFILER_START_CUSTOM = 0x1000000, +} + +ty! { + SpaParamLatency; + + SPA_PARAM_LATENCY_START = 0, + SPA_PARAM_LATENCY_direction = 1, + SPA_PARAM_LATENCY_minQuantum = 2, + SPA_PARAM_LATENCY_maxQuantum = 3, + SPA_PARAM_LATENCY_minRate = 4, + SPA_PARAM_LATENCY_maxRate = 5, + SPA_PARAM_LATENCY_minNs = 6, + SPA_PARAM_LATENCY_maxNs = 7, +} + +ty! { + SpaParamProcessLatency; + + SPA_PARAM_PROCESS_LATENCY_START = 0, + SPA_PARAM_PROCESS_LATENCY_quantum = 1, + SPA_PARAM_PROCESS_LATENCY_rate = 2, + SPA_PARAM_PROCESS_LATENCY_ns = 3, +} + +ty! { + SpaParamPortConfigMode; + + SPA_PARAM_PORT_CONFIG_MODE_none = 0, + SPA_PARAM_PORT_CONFIG_MODE_passthrough = 1, + SPA_PARAM_PORT_CONFIG_MODE_convert = 2, + SPA_PARAM_PORT_CONFIG_MODE_dsp = 3, +} + +bitflags! { + SpaMetaHeaderFlags: u32; + + SPA_META_HEADER_FLAG_DISCONT = 1 << 0, + SPA_META_HEADER_FLAG_CORRUPTED = 1 << 1, + SPA_META_HEADER_FLAG_MARKER = 1 << 2, + SPA_META_HEADER_FLAG_HEADER = 1 << 3, + SPA_META_HEADER_FLAG_GAP = 1 << 4, + SPA_META_HEADER_FLAG_DELTA_UNIT = 1 << 5, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct spa_meta_header { + pub flags: SpaMetaHeaderFlags, + pub offset: u32, + pub pts: i64, + pub dts_offset: i64, + pub seq: u64, +} + +unsafe impl Pod for spa_meta_header {} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct spa_point { + pub x: i32, + pub y: i32, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct spa_region { + pub position: spa_point, + pub size: spa_rectangle, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct spa_meta_region { + pub region: spa_region, +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct spa_meta_bitmap { + pub format: SpaVideoFormat, + pub size: spa_rectangle, + pub stride: i32, + pub offset: u32, +} + +unsafe impl Pod for spa_meta_bitmap {} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct spa_meta_cursor { + pub id: u32, + pub flags: u32, + pub position: spa_point, + pub hotspot: spa_point, + pub bitmap_offset: u32, +} + +unsafe impl Pod for spa_meta_cursor {} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct spa_meta_control {} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct spa_meta_busy { + pub flags: u32, + pub count: u32, +} + +unsafe impl Pod for spa_meta_busy {} + +unsafe impl Pod for spa_meta_region {} + +ty! { + SpaMetaType; + + SPA_META_Invalid = 0, + SPA_META_Header = 1, + SPA_META_VideoCrop = 2, + SPA_META_VideoDamage = 3, + SPA_META_Bitmap = 4, + SPA_META_Cursor = 5, + SPA_META_Control = 6, + SPA_META_Busy = 7, +} + +ty! { + SpaPropInfo; + + SPA_PROP_INFO_START = 0, + SPA_PROP_INFO_id = 1, + SPA_PROP_INFO_name = 2, + SPA_PROP_INFO_type = 3, + SPA_PROP_INFO_labels = 4, + SPA_PROP_INFO_container = 5, + SPA_PROP_INFO_params = 6, + SPA_PROP_INFO_description = 7, +} + +ty! { + SpaProp; + + SPA_PROP_START = 0x0000000, + SPA_PROP_unknown = 0x0000001, + SPA_PROP_START_Device = 0x0000100, + SPA_PROP_device = 0x0000101, + SPA_PROP_deviceName = 0x0000102, + SPA_PROP_deviceFd = 0x0000103, + SPA_PROP_card = 0x0000104, + SPA_PROP_cardName = 0x0000105, + SPA_PROP_minLatency = 0x0000106, + SPA_PROP_maxLatency = 0x0000107, + SPA_PROP_periods = 0x0000108, + SPA_PROP_periodSize = 0x0000109, + SPA_PROP_periodEvent = 0x000010A, + SPA_PROP_live = 0x000010B, + SPA_PROP_rate = 0x000010C, + SPA_PROP_quality = 0x000010D, + SPA_PROP_bluetoothAudioCodec = 0x000010E, + SPA_PROP_START_Audio = 0x0010000, + SPA_PROP_waveType = 0x0010001, + SPA_PROP_frequency = 0x0010002, + SPA_PROP_volume = 0x0010003, + SPA_PROP_mute = 0x0010004, + SPA_PROP_patternType = 0x0010005, + SPA_PROP_ditherType = 0x0010006, + SPA_PROP_truncate = 0x0010007, + SPA_PROP_channelVolumes = 0x0010008, + SPA_PROP_volumeBase = 0x0010009, + SPA_PROP_volumeStep = 0x001000A, + SPA_PROP_channelMap = 0x001000B, + SPA_PROP_monitorMute = 0x001000C, + SPA_PROP_monitorVolumes = 0x001000D, + SPA_PROP_latencyOffsetNsec = 0x001000E, + SPA_PROP_softMute = 0x001000F, + SPA_PROP_softVolumes = 0x0010010, + SPA_PROP_iec958Codecs = 0x0010011, + SPA_PROP_START_Video = 0x0020000, + SPA_PROP_brightness = 0x0020001, + SPA_PROP_contrast = 0x0020002, + SPA_PROP_saturation = 0x0020003, + SPA_PROP_hue = 0x0020004, + SPA_PROP_gamma = 0x0020005, + SPA_PROP_exposure = 0x0020006, + SPA_PROP_gain = 0x0020007, + SPA_PROP_sharpness = 0x0020008, + SPA_PROP_START_Other = 0x0080000, + SPA_PROP_params = 0x0080001, + SPA_PROP_START_CUSTOM = 0x1000000, +} + +ty! { + SpaAudioChannel; + + SPA_AUDIO_CHANNEL_UNKNOWN = 0x00000, + SPA_AUDIO_CHANNEL_NA = 0x00001, + + SPA_AUDIO_CHANNEL_MONO = 0x00002, + + SPA_AUDIO_CHANNEL_FL = 0x00003, + SPA_AUDIO_CHANNEL_FR = 0x00004, + SPA_AUDIO_CHANNEL_FC = 0x00005, + SPA_AUDIO_CHANNEL_LFE = 0x00006, + SPA_AUDIO_CHANNEL_SL = 0x00007, + SPA_AUDIO_CHANNEL_SR = 0x00008, + SPA_AUDIO_CHANNEL_FLC = 0x00009, + SPA_AUDIO_CHANNEL_FRC = 0x0000A, + SPA_AUDIO_CHANNEL_RC = 0x0000B, + SPA_AUDIO_CHANNEL_RL = 0x0000C, + SPA_AUDIO_CHANNEL_RR = 0x0000D, + SPA_AUDIO_CHANNEL_TC = 0x0000E, + SPA_AUDIO_CHANNEL_TFL = 0x0000F, + SPA_AUDIO_CHANNEL_TFC = 0x00010, + SPA_AUDIO_CHANNEL_TFR = 0x00011, + SPA_AUDIO_CHANNEL_TRL = 0x00012, + SPA_AUDIO_CHANNEL_TRC = 0x00013, + SPA_AUDIO_CHANNEL_TRR = 0x00014, + SPA_AUDIO_CHANNEL_RLC = 0x00015, + SPA_AUDIO_CHANNEL_RRC = 0x00016, + SPA_AUDIO_CHANNEL_FLW = 0x00017, + SPA_AUDIO_CHANNEL_FRW = 0x00018, + SPA_AUDIO_CHANNEL_LFE2 = 0x00019, + SPA_AUDIO_CHANNEL_FLH = 0x0001A, + SPA_AUDIO_CHANNEL_FCH = 0x0001B, + SPA_AUDIO_CHANNEL_FRH = 0x0001C, + SPA_AUDIO_CHANNEL_TFLC = 0x0001D, + SPA_AUDIO_CHANNEL_TFRC = 0x0001E, + SPA_AUDIO_CHANNEL_TSL = 0x0001F, + SPA_AUDIO_CHANNEL_TSR = 0x00020, + SPA_AUDIO_CHANNEL_LLFE = 0x00021, + SPA_AUDIO_CHANNEL_RLFE = 0x00022, + SPA_AUDIO_CHANNEL_BC = 0x00023, + SPA_AUDIO_CHANNEL_BLC = 0x00024, + SPA_AUDIO_CHANNEL_BRC = 0x00025, + + SPA_AUDIO_CHANNEL_AUX0 = 0x01000, + SPA_AUDIO_CHANNEL_AUX1 = 0x01001, + SPA_AUDIO_CHANNEL_AUX2 = 0x01002, + SPA_AUDIO_CHANNEL_AUX3 = 0x01003, + SPA_AUDIO_CHANNEL_AUX4 = 0x01004, + SPA_AUDIO_CHANNEL_AUX5 = 0x01005, + SPA_AUDIO_CHANNEL_AUX6 = 0x01006, + SPA_AUDIO_CHANNEL_AUX7 = 0x01007, + SPA_AUDIO_CHANNEL_AUX8 = 0x01008, + SPA_AUDIO_CHANNEL_AUX9 = 0x01009, + SPA_AUDIO_CHANNEL_AUX10 = 0x0100A, + SPA_AUDIO_CHANNEL_AUX11 = 0x0100B, + SPA_AUDIO_CHANNEL_AUX12 = 0x0100C, + SPA_AUDIO_CHANNEL_AUX13 = 0x0100D, + SPA_AUDIO_CHANNEL_AUX14 = 0x0100E, + SPA_AUDIO_CHANNEL_AUX15 = 0x0100F, + SPA_AUDIO_CHANNEL_AUX16 = 0x01010, + SPA_AUDIO_CHANNEL_AUX17 = 0x01011, + SPA_AUDIO_CHANNEL_AUX18 = 0x01012, + SPA_AUDIO_CHANNEL_AUX19 = 0x01013, + SPA_AUDIO_CHANNEL_AUX20 = 0x01014, + SPA_AUDIO_CHANNEL_AUX21 = 0x01015, + SPA_AUDIO_CHANNEL_AUX22 = 0x01016, + SPA_AUDIO_CHANNEL_AUX23 = 0x01017, + SPA_AUDIO_CHANNEL_AUX24 = 0x01018, + SPA_AUDIO_CHANNEL_AUX25 = 0x01019, + SPA_AUDIO_CHANNEL_AUX26 = 0x0101A, + SPA_AUDIO_CHANNEL_AUX27 = 0x0101B, + SPA_AUDIO_CHANNEL_AUX28 = 0x0101C, + SPA_AUDIO_CHANNEL_AUX29 = 0x0101D, + SPA_AUDIO_CHANNEL_AUX30 = 0x0101E, + SPA_AUDIO_CHANNEL_AUX31 = 0x0101F, + SPA_AUDIO_CHANNEL_AUX32 = 0x01020, + SPA_AUDIO_CHANNEL_AUX33 = 0x01021, + SPA_AUDIO_CHANNEL_AUX34 = 0x01022, + SPA_AUDIO_CHANNEL_AUX35 = 0x01023, + SPA_AUDIO_CHANNEL_AUX36 = 0x01024, + SPA_AUDIO_CHANNEL_AUX37 = 0x01025, + SPA_AUDIO_CHANNEL_AUX38 = 0x01026, + SPA_AUDIO_CHANNEL_AUX39 = 0x01027, + SPA_AUDIO_CHANNEL_AUX40 = 0x01028, + SPA_AUDIO_CHANNEL_AUX41 = 0x01029, + SPA_AUDIO_CHANNEL_AUX42 = 0x0102A, + SPA_AUDIO_CHANNEL_AUX43 = 0x0102B, + SPA_AUDIO_CHANNEL_AUX44 = 0x0102C, + SPA_AUDIO_CHANNEL_AUX45 = 0x0102D, + SPA_AUDIO_CHANNEL_AUX46 = 0x0102E, + SPA_AUDIO_CHANNEL_AUX47 = 0x0102F, + SPA_AUDIO_CHANNEL_AUX48 = 0x01030, + SPA_AUDIO_CHANNEL_AUX49 = 0x01031, + SPA_AUDIO_CHANNEL_AUX50 = 0x01032, + SPA_AUDIO_CHANNEL_AUX51 = 0x01033, + SPA_AUDIO_CHANNEL_AUX52 = 0x01034, + SPA_AUDIO_CHANNEL_AUX53 = 0x01035, + SPA_AUDIO_CHANNEL_AUX54 = 0x01036, + SPA_AUDIO_CHANNEL_AUX55 = 0x01037, + SPA_AUDIO_CHANNEL_AUX56 = 0x01038, + SPA_AUDIO_CHANNEL_AUX57 = 0x01039, + SPA_AUDIO_CHANNEL_AUX58 = 0x0103A, + SPA_AUDIO_CHANNEL_AUX59 = 0x0103B, + SPA_AUDIO_CHANNEL_AUX60 = 0x0103C, + SPA_AUDIO_CHANNEL_AUX61 = 0x0103D, + SPA_AUDIO_CHANNEL_AUX62 = 0x0103E, + SPA_AUDIO_CHANNEL_AUX63 = 0x0103F, + + SPA_AUDIO_CHANNEL_LAST_Aux = 0x01fff, + + SPA_AUDIO_CHANNEL_START_Custom = 0x10000, +} + +ty! { + SpaAudioIec958Codec; + + SPA_AUDIO_IEC958_CODEC_UNKNOWN = 0, + + SPA_AUDIO_IEC958_CODEC_PCM = 1, + SPA_AUDIO_IEC958_CODEC_DTS = 2, + SPA_AUDIO_IEC958_CODEC_AC3 = 3, + SPA_AUDIO_IEC958_CODEC_MPEG = 4, + SPA_AUDIO_IEC958_CODEC_MPEG2_AAC = 5, + + SPA_AUDIO_IEC958_CODEC_EAC3 = 6, + + SPA_AUDIO_IEC958_CODEC_TRUEHD = 7, + SPA_AUDIO_IEC958_CODEC_DTSHD = 8, +} + +ty! { + SpaParamBitorder; + + SPA_PARAM_BITORDER_unknown = 0, + SPA_PARAM_BITORDER_msb = 1, + SPA_PARAM_BITORDER_lsb = 2, +} +ty! { + SpaNodeCommand; + + SPA_NODE_COMMAND_Suspend = 0, + SPA_NODE_COMMAND_Pause = 1, + SPA_NODE_COMMAND_Start = 2, + SPA_NODE_COMMAND_Enable = 3, + SPA_NODE_COMMAND_Disable = 4, + SPA_NODE_COMMAND_Flush = 5, + SPA_NODE_COMMAND_Drain = 6, + SPA_NODE_COMMAND_Marker = 7, + SPA_NODE_COMMAND_ParamBegin = 8, + SPA_NODE_COMMAND_ParamEnd = 9, + SPA_NODE_COMMAND_RequestProcess = 10, +} + +#[derive(Copy, Clone)] +pub enum PwPod<'a> { + None, + Bool(bool), + Id(u32), + Int(i32), + Long(i64), + Float(f32), + Double(f64), + String(&'a BStr), + Bytes(&'a [u8]), + Rectangle(PwPodRectangle), + Fraction(PwPodFraction), + Bitmap(&'a [u8]), + Array(PwPodArray<'a>), + Struct(PwPodStruct<'a>), + Object(PwPodObject<'a>), + Sequence(PwPodSequence<'a>), + Pointer(PwPodPointer), + Fd(u64), + Choice(PwPodChoice<'a>), +} + +#[derive(Copy, Clone, Debug)] +pub struct PwPodRectangle { + pub width: u32, + pub height: u32, +} + +#[derive(Copy, Clone, Debug)] +pub struct PwPodFraction { + pub num: u32, + pub denom: u32, +} + +#[derive(Copy, Clone)] +pub struct PwPodArray<'a> { + pub ty: PwPodType, + pub child_len: usize, + pub n_elements: usize, + pub elements: PwParser<'a>, +} + +#[derive(Copy, Clone)] +pub struct PwPodStruct<'a> { + pub fields: PwParser<'a>, +} + +#[derive(Copy, Clone)] +pub struct PwPodObject<'a> { + pub ty: PwPodObjectType, + pub id: u32, + pub probs: PwParser<'a>, +} + +impl<'a> PwPodObject<'a> { + pub fn get_param(&mut self, key: u32) -> Result>, PwParserError> { + let start = self.probs.pos(); + loop { + if self.probs.len() == 0 { + self.probs.reset(); + } else { + let prob = self.probs.read_prop()?; + if prob.key == key { + return Ok(Some(prob)); + } + } + if self.probs.pos() == start { + return Ok(None); + } + } + } +} + +#[derive(Copy, Clone)] +pub struct PwPodSequence<'a> { + pub unit: u32, + pub controls: PwParser<'a>, +} + +#[derive(Copy, Clone, Debug)] +pub struct PwPodControl<'a> { + pub offset: u32, + pub ty: PwControlType, + pub value: PwPod<'a>, +} + +#[derive(Copy, Clone, Debug)] +pub struct PwPodPointer { + pub ty: PwPointerType, + pub value: usize, +} + +#[derive(Copy, Clone, Debug)] +pub struct PwPodChoice<'a> { + pub ty: PwChoiceType, + pub flags: u32, + pub elements: PwPodArray<'a>, +} + +#[derive(Copy, Clone, Debug)] +pub struct PwProp<'a> { + pub key: u32, + pub flags: PwPropFlag, + pub pod: PwPod<'a>, +} + +impl<'a> PwPod<'a> { + pub fn ty(&self) -> PwPodType { + match self { + PwPod::None => PW_TYPE_None, + PwPod::Bool(_) => PW_TYPE_Bool, + PwPod::Id(_) => PW_TYPE_Id, + PwPod::Int(_) => PW_TYPE_Int, + PwPod::Long(_) => PW_TYPE_Long, + PwPod::Float(_) => PW_TYPE_Float, + PwPod::Double(_) => PW_TYPE_Double, + PwPod::String(_) => PW_TYPE_String, + PwPod::Bytes(_) => PW_TYPE_Bytes, + PwPod::Rectangle(_) => PW_TYPE_Rectangle, + PwPod::Fraction(_) => PW_TYPE_Fraction, + PwPod::Bitmap(_) => PW_TYPE_Bitmap, + PwPod::Array(_) => PW_TYPE_Array, + PwPod::Struct(_) => PW_TYPE_Struct, + PwPod::Object(_) => PW_TYPE_Object, + PwPod::Sequence(_) => PW_TYPE_Sequence, + PwPod::Pointer(_) => PW_TYPE_Pointer, + PwPod::Fd(_) => PW_TYPE_Fd, + PwPod::Choice(_) => PW_TYPE_Choice, + } + } + + pub fn get_fraction(&self) -> Result { + match self.get_value()? { + PwPod::Fraction(i) => Ok(i), + _ => Err(PwParserError::UnexpectedPodType( + PW_TYPE_Fraction, + self.ty(), + )), + } + } + + pub fn get_rectangle(&self) -> Result { + match self.get_value()? { + PwPod::Rectangle(i) => Ok(i), + _ => Err(PwParserError::UnexpectedPodType( + PW_TYPE_Rectangle, + self.ty(), + )), + } + } + + pub fn get_id(&self) -> Result { + match self.get_value()? { + PwPod::Id(i) => Ok(i), + _ => Err(PwParserError::UnexpectedPodType(PW_TYPE_Id, self.ty())), + } + } + + pub fn get_value(mut self) -> Result, PwParserError> { + if let PwPod::Choice(v) = &mut self { + if v.ty == PW_CHOICE_None && v.elements.n_elements > 0 { + return v + .elements + .elements + .read_pod_body_packed(v.elements.ty, v.elements.child_len); + } + } + Ok(self) + } +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct spa_fraction { + pub num: u32, + pub denom: u32, +} + +bitflags! { + SPA_IO_SEGMENT_VIDEO_FLAG: u32; + + SPA_IO_SEGMENT_VIDEO_FLAG_VALID = 1<<0, + SPA_IO_SEGMENT_VIDEO_FLAG_DROP_FRAME = 1<<1, + SPA_IO_SEGMENT_VIDEO_FLAG_PULL_DOWN = 1<<2, + SPA_IO_SEGMENT_VIDEO_FLAG_INTERLACED = 1<<3, +} + +#[repr(C)] +#[derive(Debug)] +pub struct spa_io_segment_video { + pub flags: SPA_IO_SEGMENT_VIDEO_FLAG, + pub offset: u32, + pub framerate: spa_fraction, + pub hours: u32, + pub minutes: u32, + pub seconds: u32, + pub frames: u32, + pub field_count: u32, + pub padding: [u32; 11], +} + +bitflags! { + SPA_IO_SEGMENT_BAR_FLAG: u32; + + SPA_IO_SEGMENT_BAR_FLAG_VALID = 1<<0, +} + +#[repr(C)] +#[derive(Debug)] +pub struct spa_io_segment_bar { + pub flags: SPA_IO_SEGMENT_BAR_FLAG, + pub offset: u32, + pub signature_num: f32, + pub signature_denom: f32, + pub bpm: f64, + pub beat: f64, + pub padding: [u32; 8], +} + +bitflags! { + SPA_IO_SEGMENT_FLAG: u32; + + SPA_IO_SEGMENT_FLAG_LOOPING = 1<<0, + SPA_IO_SEGMENT_FLAG_NO_POSITION = 1<<1, +} + +#[repr(C)] +#[derive(Debug)] +pub struct spa_io_segment { + pub version: u32, + pub flags: SPA_IO_SEGMENT_FLAG, + pub start: u64, + pub duration: u64, + pub rate: f64, + pub position: u64, + pub bar: spa_io_segment_bar, + pub video: spa_io_segment_video, +} + +bitflags! { + SPA_IO_CLOCK_FLAG: u32; + + SPA_IO_CLOCK_FLAG_FREEWHEEL = 1<<0, +} + +#[repr(C)] +#[derive(Debug)] +pub struct spa_io_clock { + pub flags: SPA_IO_CLOCK_FLAG, + pub id: u32, + pub name: [u8; 64], + pub nsec: u64, + pub rate: spa_fraction, + pub position: u64, + pub duration: u64, + pub delay: i64, + pub rate_diff: f64, + pub next_nsec: u64, + pub padding: [u32; 8], +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct spa_rectangle { + pub width: u32, + pub height: u32, +} + +bitflags! { + SPA_IO_VIDEO_SIZE: u32; + + SPA_IO_VIDEO_SIZE_VALID = 1<<0, +} + +#[repr(C)] +#[derive(Debug)] +pub struct spa_io_video_size { + pub flags: SPA_IO_VIDEO_SIZE, + pub stride: u32, + pub size: spa_rectangle, + pub framerate: spa_fraction, + pub padding: [u32; 4], +} + +pub const SPA_IO_POSITION_MAX_SEGMENTS: usize = 8; + +#[repr(C)] +#[derive(Debug)] +pub struct spa_io_position { + pub clock: spa_io_clock, + pub video: spa_io_video_size, + pub offset: i64, + pub state: u32, + pub n_segments: u32, + pub segments: [spa_io_segment; SPA_IO_POSITION_MAX_SEGMENTS], +} + +#[repr(C)] +#[derive(Debug)] +pub struct pw_node_activation_state { + pub status: c::c_int, + pub required: i32, + pub pending: i32, +} + +ty! { + PW_NODE_ACTIVATION; + + PW_NODE_ACTIVATION_NOT_TRIGGERED = 0, + PW_NODE_ACTIVATION_TRIGGERED = 1, + PW_NODE_ACTIVATION_AWAKE = 2, + PW_NODE_ACTIVATION_FINISHED = 3, +} + +ty! { + PW_NODE_ACTIVATION_COMMAND; + + PW_NODE_ACTIVATION_COMMAND_NONE = 0, + PW_NODE_ACTIVATION_COMMAND_START = 1, + PW_NODE_ACTIVATION_COMMAND_STOP = 2, +} + +#[repr(C)] +#[derive(Debug)] +pub struct pw_node_activation { + pub status: PW_NODE_ACTIVATION, + + pub flags: c::c_uint, + + pub state: [pw_node_activation_state; 2], + + pub signal_time: u64, + pub awake_time: u64, + pub finish_time: u64, + pub prev_signal_time: u64, + + pub reposition: spa_io_segment, + pub segment: spa_io_segment, + + pub segment_owner: [u32; 32], + pub position: spa_io_position, + + pub sync_timeout: u64, + pub sync_left: u64, + + pub cpu_load: [f32; 3], + pub xrun_count: u32, + pub xrun_time: u64, + pub xrun_delay: u64, + pub max_delay: u64, + + pub command: PW_NODE_ACTIVATION_COMMAND, + pub reposition_owner: u32, +} + +unsafe impl Pod for pw_node_activation {} + +bitflags! { + SPA_PORT_FLAG: u64; + + SPA_PORT_FLAG_REMOVABLE = 1<<0, + SPA_PORT_FLAG_OPTIONAL = 1<<1, + SPA_PORT_FLAG_CAN_ALLOC_BUFFERS = 1<<2, + SPA_PORT_FLAG_IN_PLACE = 1<<3, + SPA_PORT_FLAG_NO_REF = 1<<4, + SPA_PORT_FLAG_LIVE = 1<<5, + SPA_PORT_FLAG_PHYSICAL = 1<<6, + SPA_PORT_FLAG_TERMINAL = 1<<7, + SPA_PORT_FLAG_DYNAMIC_DATA = 1<<8, +} + +bitflags! { + SpaStatus: u32; + + SPA_STATUS_NEED_DATA = 1 << 0, + SPA_STATUS_HAVE_DATA = 1 << 1, + SPA_STATUS_STOPPED = 1 << 2, + SPA_STATUS_DRAINED = 1 << 3, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct spa_io_buffers { + pub status: SpaStatus, + pub buffer_id: u32, +} + +unsafe impl Pod for spa_io_buffers {} + +bitflags! { + SpaChunkFlags: u32; + + SPA_CHUNK_FLAG_CORRUPTED = 1, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct spa_chunk { + pub offset: u32, + pub size: u32, + pub stride: u32, + pub flags: SpaChunkFlags, +} + +unsafe impl Pod for spa_chunk {} diff --git a/src/pipewire/pw_pod/pw_debug.rs b/src/pipewire/pw_pod/pw_debug.rs new file mode 100644 index 00000000..24baf948 --- /dev/null +++ b/src/pipewire/pw_pod/pw_debug.rs @@ -0,0 +1,461 @@ +use { + crate::{ + pipewire::{ + pw_parser::PwParser, + pw_pod::{ + PW_COMMAND_Node, PW_OBJECT_Format, PW_OBJECT_ParamBuffers, PW_OBJECT_ParamIO, + PW_OBJECT_ParamLatency, PW_OBJECT_ParamMeta, PW_OBJECT_ParamPortConfig, + PW_OBJECT_ParamProcessLatency, PW_OBJECT_ParamProfile, PW_OBJECT_ParamRoute, + PW_OBJECT_Profiler, PW_OBJECT_PropInfo, PW_OBJECT_Props, PW_TYPE_Id, PwPod, + PwPodArray, PwPodObject, PwPodObjectType, PwPodSequence, PwPodStruct, PwPodType, + PwProp, SPA_FORMAT_AUDIO_bitorder, SPA_FORMAT_AUDIO_format, + SPA_FORMAT_AUDIO_iec958Codec, SPA_FORMAT_AUDIO_position, + SPA_FORMAT_VIDEO_H264_alignment, SPA_FORMAT_VIDEO_H264_streamFormat, + SPA_FORMAT_VIDEO_chromaSite, SPA_FORMAT_VIDEO_colorMatrix, + SPA_FORMAT_VIDEO_colorPrimaries, SPA_FORMAT_VIDEO_colorRange, + SPA_FORMAT_VIDEO_format, SPA_FORMAT_VIDEO_interlaceMode, + SPA_FORMAT_VIDEO_multiviewFlags, SPA_FORMAT_VIDEO_multiviewMode, + SPA_FORMAT_VIDEO_transferFunction, SPA_FORMAT_mediaSubtype, SPA_FORMAT_mediaType, + SPA_PARAM_BUFFERS_dataType, SPA_PARAM_IO_id, SPA_PARAM_META_type, + SPA_PARAM_PORT_CONFIG_direction, SPA_PARAM_PORT_CONFIG_mode, + SPA_PARAM_PROFILE_available, SPA_PARAM_ROUTE_available, SPA_PARAM_ROUTE_direction, + SPA_PROP_channelMap, SPA_PROP_iec958Codecs, SpaAudioChannel, SpaAudioFormat, + SpaAudioIec958Codec, SpaDataTypes, SpaDirection, SpaFormat, SpaH264Alignment, + SpaH264StreamFormat, SpaIoType, SpaMediaSubtype, SpaMediaType, SpaMetaType, + SpaNodeCommand, SpaParamAvailability, SpaParamBitorder, SpaParamBuffers, + SpaParamIo, SpaParamLatency, SpaParamMeta, SpaParamPortConfig, + SpaParamPortConfigMode, SpaParamProcessLatency, SpaParamProfile, SpaParamRoute, + SpaParamType, SpaProfiler, SpaProp, SpaPropInfo, SpaVideoChromaSite, + SpaVideoColorMatrix, SpaVideoColorPrimaries, SpaVideoColorRange, SpaVideoFormat, + SpaVideoInterlaceMode, SpaVideoMultiviewFlags, SpaVideoMultiviewMode, + SpaVideoTransferFunction, + }, + }, + utils::{debug_fn::debug_fn, errorfmt::ErrorFmt}, + }, + std::fmt::{Debug, DebugList, Formatter, Write}, +}; + +trait PwPodObjectDebugger: Sync { + fn debug_property(&self, fmt: &mut Formatter<'_>, value: PwProp<'_>) -> std::fmt::Result; + fn id_name(&self, id: u32) -> Option<&'static str>; +} + +struct PwPodObjectDebuggerSimple { + key_name: F, + debug_pod: G, + id_name: H, +} + +impl PwPodObjectDebugger for PwPodObjectDebuggerSimple +where + F: Fn(u32) -> Option<&'static str> + Sync, + G: Fn(u32, &mut Formatter<'_>, PwPod<'_>) -> std::fmt::Result + Sync, + H: Fn(u32) -> Option<&'static str> + Sync, +{ + fn debug_property(&self, fmt: &mut Formatter<'_>, value: PwProp<'_>) -> std::fmt::Result { + let mut s = fmt.debug_struct("PwProp"); + match (self.key_name)(value.key) { + Some(n) => s.field("key", &n), + _ => s.field("key", &value.key), + }; + s.field("flags", &value.flags) + .field( + "pod", + &debug_fn(|f| (self.debug_pod)(value.key, f, value.pod)), + ) + .finish() + } + + fn id_name(&self, id: u32) -> Option<&'static str> { + (self.id_name)(id) + } +} + +fn choice_debug(fmt: &mut Formatter<'_>, p: PwPod<'_>, ty: PwPodType, f: F) -> std::fmt::Result +where + F: Fn(&mut Formatter<'_>, PwPod<'_>) -> std::fmt::Result, +{ + match p { + PwPod::Choice(c) if c.elements.ty == ty => fmt + .debug_struct("choice") + .field("ty", &c.ty) + .field("flags", &c.flags) + .field( + "elements", + &debug_fn(|fmt| { + array_body_debug(fmt, c.elements, |l, p| { + match p.read_pod_body_packed(ty, c.elements.child_len) { + Ok(p) => { + l.entry(&debug_fn(|fmt| f(fmt, p))); + true + } + Err(e) => { + let e = ErrorFmt(e); + l.entry(&debug_fn(|fmt| { + write!(fmt, "Could not read choice element: {}", e) + })); + false + } + } + }) + }), + ) + .finish(), + _ if p.ty() == ty => f(fmt, p), + _ => p.fmt(fmt), + } +} + +fn id_debug(fmt: &mut Formatter<'_>, p: PwPod<'_>, f: F) -> std::fmt::Result +where + F: Fn(&mut Formatter<'_>, u32) -> std::fmt::Result, +{ + choice_debug(fmt, p, PW_TYPE_Id, |fmt, p| match p { + PwPod::Id(id) => f(fmt, id), + _ => p.fmt(fmt), + }) +} + +fn array_body_debug(fmt: &mut Formatter<'_>, mut a: PwPodArray<'_>, f: F) -> std::fmt::Result +where + F: Fn(&mut DebugList, &mut PwParser<'_>) -> bool, +{ + let mut l = fmt.debug_list(); + for _ in 0..a.n_elements { + if !f(&mut l, &mut a.elements) { + break; + } + } + l.finish() +} + +fn array_debug(fmt: &mut Formatter<'_>, p: PwPod<'_>, ty: PwPodType, f: F) -> std::fmt::Result +where + F: Fn(&mut DebugList, &mut PwParser<'_>) -> bool, +{ + match p { + PwPod::Array(a) if a.ty == ty => array_body_debug(fmt, a, f), + _ => p.fmt(fmt), + } +} + +fn array_id_debug(fmt: &mut Formatter<'_>, p: PwPod<'_>, f: F) -> std::fmt::Result +where + F: Fn(&mut DebugList, u32) -> T, +{ + array_debug(fmt, p, PW_TYPE_Id, |l, p| match p.read_id() { + Ok(a) => { + f(l, a); + true + } + Err(e) => { + let e = ErrorFmt(e); + l.entry(&debug_fn(|f| write!(f, "Could not read id: {}", e))); + false + } + }) +} + +fn object_id_name(id: u32) -> Option<&'static str> { + SpaParamType(id).name() +} + +fn command_id_name(id: u32) -> Option<&'static str> { + SpaNodeCommand(id).name() +} + +static PROP_INFO_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple { + key_name: |key| SpaPropInfo(key).name(), + debug_pod: |_, f: &mut Formatter<'_>, p: PwPod<'_>| p.fmt(f), + id_name: object_id_name, +}; + +static PROPS_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple { + key_name: |key| SpaProp(key).name(), + id_name: object_id_name, + debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaProp(key) { + SPA_PROP_channelMap => array_id_debug(f, p, |l, a| { + l.entry(&SpaAudioChannel(a)); + }), + SPA_PROP_iec958Codecs => array_id_debug(f, p, |l, a| { + l.entry(&SpaAudioIec958Codec(a)); + }), + _ => p.fmt(f), + }, +}; + +static FORMAT_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple { + key_name: |key| SpaFormat(key).name(), + id_name: object_id_name, + debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaFormat(key) { + SPA_FORMAT_mediaType => id_debug(f, p, |f, a| SpaMediaType(a).fmt(f)), + SPA_FORMAT_mediaSubtype => id_debug(f, p, |f, a| SpaMediaSubtype(a).fmt(f)), + SPA_FORMAT_AUDIO_format => id_debug(f, p, |f, a| SpaAudioFormat(a).fmt(f)), + SPA_FORMAT_AUDIO_position => array_id_debug(f, p, |l, a| { + l.entry(&SpaAudioChannel(a)); + }), + SPA_FORMAT_AUDIO_iec958Codec => id_debug(f, p, |f, a| SpaAudioIec958Codec(a).fmt(f)), + SPA_FORMAT_AUDIO_bitorder => id_debug(f, p, |f, a| SpaParamBitorder(a).fmt(f)), + SPA_FORMAT_VIDEO_format => id_debug(f, p, |f, a| SpaVideoFormat(a).fmt(f)), + SPA_FORMAT_VIDEO_interlaceMode => id_debug(f, p, |f, a| SpaVideoInterlaceMode(a).fmt(f)), + SPA_FORMAT_VIDEO_multiviewMode => id_debug(f, p, |f, a| SpaVideoMultiviewMode(a).fmt(f)), + SPA_FORMAT_VIDEO_multiviewFlags => id_debug(f, p, |f, a| SpaVideoMultiviewFlags(a).fmt(f)), + SPA_FORMAT_VIDEO_chromaSite => id_debug(f, p, |f, a| SpaVideoChromaSite(a).fmt(f)), + SPA_FORMAT_VIDEO_colorRange => id_debug(f, p, |f, a| SpaVideoColorRange(a).fmt(f)), + SPA_FORMAT_VIDEO_colorMatrix => id_debug(f, p, |f, a| SpaVideoColorMatrix(a).fmt(f)), + SPA_FORMAT_VIDEO_transferFunction => { + id_debug(f, p, |f, a| SpaVideoTransferFunction(a).fmt(f)) + } + SPA_FORMAT_VIDEO_colorPrimaries => id_debug(f, p, |f, a| SpaVideoColorPrimaries(a).fmt(f)), + SPA_FORMAT_VIDEO_H264_streamFormat => id_debug(f, p, |f, a| SpaH264StreamFormat(a).fmt(f)), + SPA_FORMAT_VIDEO_H264_alignment => id_debug(f, p, |f, a| SpaH264Alignment(a).fmt(f)), + _ => p.fmt(f), + }, +}; + +static PARAM_BUFFERS_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple { + key_name: |key| SpaParamBuffers(key).name(), + id_name: object_id_name, + debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaParamBuffers(key) { + SPA_PARAM_BUFFERS_dataType => match p { + PwPod::Int(v) => SpaDataTypes(v as _).fmt(f), + _ => p.fmt(f), + }, + _ => p.fmt(f), + }, +}; + +static PARAM_META_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple { + key_name: |key| SpaParamMeta(key).name(), + id_name: object_id_name, + debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaParamMeta(key) { + SPA_PARAM_META_type => id_debug(f, p, |f, b| SpaMetaType(b).fmt(f)), + _ => p.fmt(f), + }, +}; + +static PARAM_IO_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple { + key_name: |key| SpaParamIo(key).name(), + id_name: object_id_name, + debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaParamIo(key) { + SPA_PARAM_IO_id => id_debug(f, p, |f, b| SpaIoType(b).fmt(f)), + _ => p.fmt(f), + }, +}; + +static PARAM_PROFILE_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple { + key_name: |key| SpaParamProfile(key).name(), + id_name: object_id_name, + debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaParamProfile(key) { + SPA_PARAM_PROFILE_available => id_debug(f, p, |f, b| SpaParamAvailability(b).fmt(f)), + _ => p.fmt(f), + }, +}; + +static PARAM_PORT_CONFIG_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple { + key_name: |key| SpaParamPortConfig(key).name(), + id_name: object_id_name, + debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaParamPortConfig(key) { + SPA_PARAM_PORT_CONFIG_direction => id_debug(f, p, |f, b| SpaDirection(b).fmt(f)), + SPA_PARAM_PORT_CONFIG_mode => id_debug(f, p, |f, b| SpaParamPortConfigMode(b).fmt(f)), + _ => p.fmt(f), + }, +}; + +static PARAM_ROUTE_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple { + key_name: |key| SpaParamRoute(key).name(), + id_name: object_id_name, + debug_pod: |key, f: &mut Formatter<'_>, p: PwPod<'_>| match SpaParamRoute(key) { + SPA_PARAM_ROUTE_direction => id_debug(f, p, |f, b| SpaDirection(b).fmt(f)), + SPA_PARAM_ROUTE_available => id_debug(f, p, |f, b| SpaParamAvailability(b).fmt(f)), + _ => p.fmt(f), + }, +}; + +static PROFILER_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple { + key_name: |key| SpaProfiler(key).name(), + id_name: object_id_name, + debug_pod: |_, f: &mut Formatter<'_>, p: PwPod<'_>| p.fmt(f), +}; + +static PARAM_LATENCY_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple { + key_name: |key| SpaParamLatency(key).name(), + id_name: object_id_name, + debug_pod: |_, f: &mut Formatter<'_>, p: PwPod<'_>| p.fmt(f), +}; + +static PARAM_PROCESS_LATENCY_DEBUGGER: &'static dyn PwPodObjectDebugger = + &PwPodObjectDebuggerSimple { + key_name: |key| SpaParamProcessLatency(key).name(), + id_name: object_id_name, + debug_pod: |_, f: &mut Formatter<'_>, p: PwPod<'_>| p.fmt(f), + }; + +static COMMAND_NODE_DEBUGGER: &'static dyn PwPodObjectDebugger = &PwPodObjectDebuggerSimple { + key_name: |key| SpaNodeCommand(key).name(), + id_name: command_id_name, + debug_pod: |_, f: &mut Formatter<'_>, p: PwPod<'_>| p.fmt(f), +}; + +fn object_debugger(obj: PwPodObjectType) -> Option<&'static dyn PwPodObjectDebugger> { + let res: &dyn PwPodObjectDebugger = match obj { + PW_OBJECT_PropInfo => PROP_INFO_DEBUGGER, + PW_OBJECT_Props => PROPS_DEBUGGER, + PW_OBJECT_Format => FORMAT_DEBUGGER, + PW_OBJECT_ParamBuffers => PARAM_BUFFERS_DEBUGGER, + PW_OBJECT_ParamMeta => PARAM_META_DEBUGGER, + PW_OBJECT_ParamIO => PARAM_IO_DEBUGGER, + PW_OBJECT_ParamProfile => PARAM_PROFILE_DEBUGGER, + PW_OBJECT_ParamPortConfig => PARAM_PORT_CONFIG_DEBUGGER, + PW_OBJECT_ParamRoute => PARAM_ROUTE_DEBUGGER, + PW_OBJECT_Profiler => PROFILER_DEBUGGER, + PW_OBJECT_ParamLatency => PARAM_LATENCY_DEBUGGER, + PW_OBJECT_ParamProcessLatency => PARAM_PROCESS_LATENCY_DEBUGGER, + PW_COMMAND_Node => COMMAND_NODE_DEBUGGER, + _ => return None, + }; + Some(res) +} + +impl<'a> Debug for PwPodObject<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let debugger = object_debugger(self.ty); + let mut s = f.debug_struct("object"); + s.field("type", &self.ty); + let name; + let mut id: &dyn Debug = &self.id; + if let Some(d) = debugger { + if let Some(n) = d.id_name(self.id) { + name = n; + id = &name; + } + } + s.field("id", id); + s.field( + "props", + &debug_fn(|f| { + let mut l = f.debug_list(); + let mut parser = self.probs; + while parser.len() > 0 { + match parser.read_prop() { + Ok(p) => match debugger { + Some(d) => l.entry(&debug_fn(|fmt| d.debug_property(fmt, p))), + _ => l.entry(&p), + }, + Err(e) => { + let e = ErrorFmt(e); + l.entry(&debug_fn(|f| { + write!(f, "Could not read object property: {}", &e) + })); + break; + } + }; + } + l.finish() + }), + ); + s.finish() + } +} + +impl<'a> Debug for PwPodSequence<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut s = f.debug_struct("sequence"); + s.field("unit", &self.unit); + s.field( + "controls", + &debug_fn(|f| { + let mut l = f.debug_list(); + let mut parser = self.controls; + while parser.len() > 0 { + match parser.read_control() { + Ok(c) => l.entry(&c), + Err(e) => { + let e = ErrorFmt(e); + l.entry(&debug_fn(|f| { + write!(f, "Could not read control element: {}", &e) + })); + break; + } + }; + } + l.finish() + }), + ); + s.finish() + } +} + +impl<'a> Debug for PwPodStruct<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut parser = self.fields; + let mut s = f.debug_struct("struct"); + let mut field = String::new(); + for i in 0.. { + if parser.len() == 0 { + break; + } + field.clear(); + let _ = write!(&mut field, "\"{}\"", i); + match parser.read_pod() { + Ok(p) => s.field(&field, &p), + Err(e) => { + let e = ErrorFmt(e); + s.field( + &field, + &debug_fn(|f| write!(f, "Could not parse struct field: {}", &e)), + ); + break; + } + }; + } + s.finish() + } +} + +impl<'a> Debug for PwPodArray<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut list = f.debug_list(); + let mut parser = self.elements; + for _ in 0..self.n_elements { + match parser.read_pod_body_packed(self.ty, self.child_len) { + Ok(e) => list.entry(&e), + Err(e) => { + let e = ErrorFmt(e); + list.entry(&debug_fn(|f| { + write!(f, "Could not parse array element: {}", &e) + })); + break; + } + }; + } + list.finish() + } +} + +impl<'a> Debug for PwPod<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + PwPod::None => write!(f, "None"), + PwPod::Bool(b) => write!(f, "{}", b), + PwPod::Id(id) => write!(f, "id({})", id), + PwPod::Int(i) => write!(f, "int({})", i), + PwPod::Long(l) => write!(f, "long({})", l), + PwPod::Float(v) => write!(f, "float({})", v), + PwPod::Double(d) => write!(f, "double({})", d), + PwPod::String(s) => write!(f, "string({:?})", s), + PwPod::Bytes(b) => write!(f, "bytes(len = {})", b.len()), + PwPod::Rectangle(r) => write!(f, "rectangle({}x{})", r.width, r.height), + PwPod::Fraction(v) => write!(f, "fraction({}/{})", v.num, v.denom), + PwPod::Bitmap(b) => write!(f, "bitmap(len = {})", b.len()), + PwPod::Array(a) => a.fmt(f), + PwPod::Struct(s) => s.fmt(f), + PwPod::Object(o) => o.fmt(f), + PwPod::Sequence(s) => s.fmt(f), + PwPod::Pointer(p) => p.fmt(f), + PwPod::Fd(v) => write!(f, "fd({})", v), + PwPod::Choice(c) => c.fmt(f), + } + } +} diff --git a/src/utils.rs b/src/utils.rs index 6e2a3aac..7cf02f82 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -20,6 +20,7 @@ pub mod numcell; pub mod once; pub mod option_ext; pub mod oserror; +pub mod page_size; pub mod ptr_ext; pub mod queue; pub mod rc_eq; diff --git a/src/utils/page_size.rs b/src/utils/page_size.rs new file mode 100644 index 00000000..acf28436 --- /dev/null +++ b/src/utils/page_size.rs @@ -0,0 +1,7 @@ +use {once_cell::sync::Lazy, uapi::c}; + +static PAGE_SIZE: Lazy = Lazy::new(|| uapi::sysconf(c::_SC_PAGESIZE).unwrap_or(4096) as _); + +pub fn page_size() -> usize { + *PAGE_SIZE +}