1use std::ffi::{c_char, c_int, c_void};
16use std::mem;
17use std::ptr;
18
19use libloading::Library;
20
21pub const SPA_TYPE_ID: u32 = 3;
22pub const SPA_TYPE_INT: u32 = 4;
23pub const SPA_TYPE_LONG: u32 = 5;
24pub const SPA_TYPE_RECTANGLE: u32 = 10;
25pub const SPA_TYPE_FRACTION: u32 = 11;
26pub const SPA_TYPE_OBJECT: u32 = 15;
27pub const SPA_TYPE_CHOICE: u32 = 19;
28pub const SPA_TYPE_OBJECT_FORMAT: u32 = 0x40003;
29pub const SPA_TYPE_OBJECT_PARAM_BUFFERS: u32 = 0x40004;
30pub const SPA_TYPE_OBJECT_PARAM_META: u32 = 0x40005;
31pub const SPA_PARAM_ENUM_FORMAT: u32 = 3;
32pub const SPA_PARAM_FORMAT: u32 = 4;
33pub const SPA_PARAM_BUFFERS: u32 = 5;
34pub const SPA_PARAM_META: u32 = 6;
35pub const SPA_FORMAT_MEDIA_TYPE: u32 = 1;
36pub const SPA_FORMAT_MEDIA_SUBTYPE: u32 = 2;
37pub const SPA_FORMAT_VIDEO_FORMAT: u32 = 0x20001;
38pub const SPA_FORMAT_VIDEO_MODIFIER: u32 = 0x20002;
39pub const SPA_FORMAT_VIDEO_SIZE: u32 = 0x20003;
40pub const SPA_FORMAT_VIDEO_FRAMERATE: u32 = 0x20004;
41pub const SPA_FORMAT_VIDEO_MAX_FRAMERATE: u32 = 0x20005;
42pub const SPA_MEDIA_TYPE_VIDEO: u32 = 2;
43pub const SPA_MEDIA_SUBTYPE_RAW: u32 = 1;
44pub const SPA_MEDIA_SUBTYPE_MJPG: u32 = 0x20002;
45pub const SPA_VIDEO_FORMAT_I420: u32 = 2;
46pub const SPA_VIDEO_FORMAT_YUY2: u32 = 4;
47pub const SPA_VIDEO_FORMAT_RGBX: u32 = 7;
48pub const SPA_VIDEO_FORMAT_BGRX: u32 = 8;
49pub const SPA_VIDEO_FORMAT_RGBA: u32 = 11;
50pub const SPA_VIDEO_FORMAT_BGRA: u32 = 12;
51pub const SPA_VIDEO_FORMAT_ARGB: u32 = 13;
52pub const SPA_VIDEO_FORMAT_ABGR: u32 = 14;
53pub const SPA_VIDEO_FORMAT_NV12: u32 = 23;
54pub const SPA_PARAM_BUFFERS_BUFFERS: u32 = 1;
55pub const SPA_PARAM_BUFFERS_BLOCKS: u32 = 2;
56pub const SPA_PARAM_BUFFERS_SIZE: u32 = 3;
57pub const SPA_PARAM_BUFFERS_STRIDE: u32 = 4;
58pub const SPA_PARAM_BUFFERS_ALIGN: u32 = 5;
59pub const SPA_PARAM_BUFFERS_DATATYPE: u32 = 6;
60pub const SPA_PARAM_META_TYPE: u32 = 1;
61pub const SPA_PARAM_META_SIZE: u32 = 2;
62pub const SPA_META_HEADER: u32 = 1;
63pub const SPA_META_VIDEO_CROP: u32 = 2;
64pub const SPA_META_VIDEO_DAMAGE: u32 = 3;
65pub const SPA_META_CURSOR: u32 = 5;
66pub const SPA_META_HEADER_SIZE: u32 = 32;
67pub const SPA_META_REGION_SIZE: u32 = 16;
68pub const SPA_DATA_MEMPTR: u32 = 1;
69pub const SPA_DATA_MEMFD: u32 = 2;
70pub const SPA_DATA_DMABUF: u32 = 3;
71pub const SPA_CHUNK_FLAG_CORRUPTED: i32 = 1;
72pub const SPA_CHOICE_NONE: u32 = 0;
73pub const SPA_CHOICE_RANGE: u32 = 1;
74pub const SPA_CHOICE_ENUM: u32 = 3;
75pub const SPA_CHOICE_FLAGS: u32 = 4;
76pub const SPA_POD_PROP_FLAG_MANDATORY: u32 = 1 << 3;
77pub const SPA_POD_PROP_FLAG_DONT_FIXATE: u32 = 1 << 4;
78pub const PW_DIRECTION_INPUT: c_int = 0;
79pub const PW_DIRECTION_OUTPUT: c_int = 1;
80pub const PW_ID_ANY: u32 = 0xFFFF_FFFF;
81pub const PW_STREAM_FLAG_AUTOCONNECT: u32 = 1;
82pub const PW_STREAM_FLAG_MAP_BUFFERS: u32 = 4;
83pub const PW_STREAM_FLAG_DRIVER: u32 = 8;
84pub const PW_STREAM_STATE_ERROR: c_int = -1;
85pub const PW_STREAM_STATE_UNCONNECTED: c_int = 0;
86pub const PW_STREAM_STATE_PAUSED: c_int = 2;
87pub const PW_STREAM_STATE_STREAMING: c_int = 3;
88pub const PW_VERSION_STREAM_EVENTS: u32 = 2;
89
90#[repr(C)]
91pub struct PwStreamEvents {
92 pub version: u32,
93 pub destroy: Option<unsafe extern "C" fn(*mut c_void)>,
94 pub state_changed: Option<unsafe extern "C" fn(*mut c_void, c_int, c_int, *const c_char)>,
95 pub control_info: Option<unsafe extern "C" fn(*mut c_void, u32, *const c_void)>,
96 pub io_changed: Option<unsafe extern "C" fn(*mut c_void, u32, *mut c_void, u32)>,
97 pub param_changed: Option<unsafe extern "C" fn(*mut c_void, u32, *const c_void)>,
98 pub add_buffer: Option<unsafe extern "C" fn(*mut c_void, *mut PwBuffer)>,
99 pub remove_buffer: Option<unsafe extern "C" fn(*mut c_void, *mut PwBuffer)>,
100 pub process: Option<unsafe extern "C" fn(*mut c_void)>,
101 pub drained: Option<unsafe extern "C" fn(*mut c_void)>,
102 pub command: Option<unsafe extern "C" fn(*mut c_void, *const c_void)>,
103 pub trigger_done: Option<unsafe extern "C" fn(*mut c_void)>,
104}
105
106impl PwStreamEvents {
107 pub const fn empty() -> Self {
109 Self {
110 version: PW_VERSION_STREAM_EVENTS,
111 destroy: None,
112 state_changed: None,
113 control_info: None,
114 io_changed: None,
115 param_changed: None,
116 add_buffer: None,
117 remove_buffer: None,
118 process: None,
119 drained: None,
120 command: None,
121 trigger_done: None,
122 }
123 }
124}
125
126#[repr(C)]
127pub struct PwBuffer {
128 pub buffer: *mut SpaBuffer,
129 pub user_data: *mut c_void,
130 pub size: u64,
131 pub requested: u64,
132 pub time: u64,
133}
134
135#[repr(C)]
136pub struct SpaBuffer {
137 pub n_metas: u32,
138 pub n_datas: u32,
139 pub metas: *mut SpaMeta,
140 pub datas: *mut SpaData,
141}
142
143#[repr(C)]
144pub struct SpaMeta {
145 pub type_: u32,
146 pub size: u32,
147 pub data: *mut c_void,
148}
149
150#[repr(C)]
151pub struct SpaData {
152 pub type_: u32,
153 pub flags: u32,
154 pub fd: i64,
155 pub mapoffset: u32,
156 pub maxsize: u32,
157 pub data: *mut c_void,
158 pub chunk: *mut SpaChunk,
159}
160
161#[repr(C)]
162pub struct SpaChunk {
163 pub offset: u32,
164 pub size: u32,
165 pub stride: i32,
166 pub flags: i32,
167}
168
169#[repr(C)]
170pub struct SpaMetaHeader {
171 pub flags: u32,
172 pub offset: u32,
173 pub pts: i64,
174 pub dts_offset: i64,
175 pub seq: u64,
176}
177
178#[repr(C)]
180#[derive(Clone, Copy)]
181pub struct SpaMetaRegion {
182 pub x: i32,
183 pub y: i32,
184 pub width: u32,
185 pub height: u32,
186}
187
188#[repr(C)]
189pub struct SpaMetaCursor {
190 pub id: u32,
191 pub flags: u32,
192 pub x: i32,
193 pub y: i32,
194 pub hot_x: i32,
195 pub hot_y: i32,
196 pub bitmap_offset: u32,
197}
198
199#[repr(C)]
200pub struct SpaMetaBitmap {
201 pub format: u32,
202 pub width: u32,
203 pub height: u32,
204 pub stride: i32,
205 pub offset: u32,
206}
207
208pub const fn cursor_meta_size(w: u32, h: u32) -> u32 {
210 (mem::size_of::<SpaMetaCursor>() + mem::size_of::<SpaMetaBitmap>()) as u32 + w * h * 4
211}
212
213type PwInit = unsafe extern "C" fn(*mut c_int, *mut *mut *mut c_char);
214type PwThreadLoopNew = unsafe extern "C" fn(*const c_char, *const c_void) -> *mut c_void;
215type PwThreadLoopGetLoop = unsafe extern "C" fn(*mut c_void) -> *mut c_void;
216type PwThreadLoopInt = unsafe extern "C" fn(*mut c_void) -> c_int;
217type PwThreadLoopVoid = unsafe extern "C" fn(*mut c_void);
218type PwContextNew = unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> *mut c_void;
219type PwContextConnect = unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> *mut c_void;
220type PwContextConnectFd =
221 unsafe extern "C" fn(*mut c_void, c_int, *mut c_void, usize) -> *mut c_void;
222type PwContextDestroy = unsafe extern "C" fn(*mut c_void);
223type PwCoreDisconnect = unsafe extern "C" fn(*mut c_void) -> c_int;
224type PwPropertiesNew = unsafe extern "C" fn(*const c_char, ...) -> *mut c_void;
225type PwPropertiesSet = unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char) -> c_int;
226type PwStreamNew = unsafe extern "C" fn(*mut c_void, *const c_char, *mut c_void) -> *mut c_void;
227type PwStreamAddListener =
228 unsafe extern "C" fn(*mut c_void, *mut c_void, *const PwStreamEvents, *mut c_void);
229type PwStreamConnect =
230 unsafe extern "C" fn(*mut c_void, c_int, u32, u32, *mut *const c_void, u32) -> c_int;
231type PwStreamUpdateParams = unsafe extern "C" fn(*mut c_void, *mut *const c_void, u32) -> c_int;
232type PwStreamDequeueBuffer = unsafe extern "C" fn(*mut c_void) -> *mut PwBuffer;
233type PwStreamQueueBuffer = unsafe extern "C" fn(*mut c_void, *mut PwBuffer) -> c_int;
234type PwStreamTriggerProcess = unsafe extern "C" fn(*mut c_void) -> c_int;
235type PwStreamSetActive = unsafe extern "C" fn(*mut c_void, bool) -> c_int;
236type PwStreamVoid = unsafe extern "C" fn(*mut c_void);
237type PwStreamInt = unsafe extern "C" fn(*mut c_void) -> c_int;
238
239#[derive(Clone, Copy)]
241pub struct Api {
242 pub thread_loop_new: PwThreadLoopNew,
243 pub thread_loop_get_loop: PwThreadLoopGetLoop,
244 pub thread_loop_start: PwThreadLoopInt,
245 pub thread_loop_stop: PwThreadLoopVoid,
246 pub thread_loop_lock: PwThreadLoopVoid,
247 pub thread_loop_unlock: PwThreadLoopVoid,
248 pub thread_loop_destroy: PwThreadLoopVoid,
249 pub context_new: PwContextNew,
250 pub context_connect: PwContextConnect,
251 pub context_connect_fd: PwContextConnectFd,
252 pub context_destroy: PwContextDestroy,
253 pub core_disconnect: PwCoreDisconnect,
254 pub properties_new: PwPropertiesNew,
255 pub properties_set: PwPropertiesSet,
256 pub stream_new: PwStreamNew,
257 pub stream_add_listener: PwStreamAddListener,
258 pub stream_connect: PwStreamConnect,
259 pub stream_update_params: PwStreamUpdateParams,
260 pub stream_dequeue_buffer: PwStreamDequeueBuffer,
261 pub stream_queue_buffer: PwStreamQueueBuffer,
262 pub stream_trigger_process: PwStreamTriggerProcess,
263 pub stream_set_active: PwStreamSetActive,
264 pub stream_disconnect: PwStreamInt,
265 pub stream_destroy: PwStreamVoid,
266}
267
268pub fn api() -> Result<&'static Api, String> {
271 static API: std::sync::OnceLock<Result<Api, String>> = std::sync::OnceLock::new();
272 API.get_or_init(|| unsafe {
273 let lib = Library::new("libpipewire-0.3.so.0")
274 .map_err(|e| format!("libpipewire-0.3 not available: {}", e))?;
275 macro_rules! sym {
276 ($name:literal, $t:ty) => {
277 *lib.get::<$t>(concat!($name, "\0").as_bytes())
278 .map_err(|e| format!("{}: {}", $name, e))?
279 };
280 }
281 let init: PwInit = sym!("pw_init", PwInit);
282 let api = Api {
283 thread_loop_new: sym!("pw_thread_loop_new", PwThreadLoopNew),
284 thread_loop_get_loop: sym!("pw_thread_loop_get_loop", PwThreadLoopGetLoop),
285 thread_loop_start: sym!("pw_thread_loop_start", PwThreadLoopInt),
286 thread_loop_stop: sym!("pw_thread_loop_stop", PwThreadLoopVoid),
287 thread_loop_lock: sym!("pw_thread_loop_lock", PwThreadLoopVoid),
288 thread_loop_unlock: sym!("pw_thread_loop_unlock", PwThreadLoopVoid),
289 thread_loop_destroy: sym!("pw_thread_loop_destroy", PwThreadLoopVoid),
290 context_new: sym!("pw_context_new", PwContextNew),
291 context_connect: sym!("pw_context_connect", PwContextConnect),
292 context_connect_fd: sym!("pw_context_connect_fd", PwContextConnectFd),
293 context_destroy: sym!("pw_context_destroy", PwContextDestroy),
294 core_disconnect: sym!("pw_core_disconnect", PwCoreDisconnect),
295 properties_new: sym!("pw_properties_new", PwPropertiesNew),
296 properties_set: sym!("pw_properties_set", PwPropertiesSet),
297 stream_new: sym!("pw_stream_new", PwStreamNew),
298 stream_add_listener: sym!("pw_stream_add_listener", PwStreamAddListener),
299 stream_connect: sym!("pw_stream_connect", PwStreamConnect),
300 stream_update_params: sym!("pw_stream_update_params", PwStreamUpdateParams),
301 stream_dequeue_buffer: sym!("pw_stream_dequeue_buffer", PwStreamDequeueBuffer),
302 stream_queue_buffer: sym!("pw_stream_queue_buffer", PwStreamQueueBuffer),
303 stream_trigger_process: sym!("pw_stream_trigger_process", PwStreamTriggerProcess),
304 stream_set_active: sym!("pw_stream_set_active", PwStreamSetActive),
305 stream_disconnect: sym!("pw_stream_disconnect", PwStreamInt),
306 stream_destroy: sym!("pw_stream_destroy", PwStreamVoid),
307 };
308 init(ptr::null_mut(), ptr::null_mut());
309 mem::forget(lib);
310 Ok(api)
311 })
312 .as_ref()
313 .map_err(|e| e.clone())
314}
315
316pub fn push_u32(v: &mut Vec<u8>, x: u32) {
319 v.extend_from_slice(&x.to_ne_bytes());
320}
321
322fn pad8(v: &mut Vec<u8>) {
323 while !v.len().is_multiple_of(8) {
324 v.push(0);
325 }
326}
327
328pub fn pod_prim(v: &mut Vec<u8>, ty: u32, payload: &[u8]) {
330 push_u32(v, payload.len() as u32);
331 push_u32(v, ty);
332 v.extend_from_slice(payload);
333 pad8(v);
334}
335
336pub fn pod_id(v: &mut Vec<u8>, x: u32) {
337 pod_prim(v, SPA_TYPE_ID, &x.to_ne_bytes());
338}
339
340pub fn pod_int(v: &mut Vec<u8>, x: i32) {
341 pod_prim(v, SPA_TYPE_INT, &x.to_ne_bytes());
342}
343
344pub fn pod_rect(v: &mut Vec<u8>, w: u32, h: u32) {
345 let mut p = Vec::new();
346 push_u32(&mut p, w);
347 push_u32(&mut p, h);
348 pod_prim(v, SPA_TYPE_RECTANGLE, &p);
349}
350
351pub fn pod_frac(v: &mut Vec<u8>, num: u32, den: u32) {
352 let mut p = Vec::new();
353 push_u32(&mut p, num);
354 push_u32(&mut p, den);
355 pod_prim(v, SPA_TYPE_FRACTION, &p);
356}
357
358fn pod_choice(v: &mut Vec<u8>, choice: u32, child_ty: u32, child_size: u32, values: &[u8]) {
361 let mut body = Vec::new();
362 push_u32(&mut body, choice);
363 push_u32(&mut body, 0);
364 push_u32(&mut body, child_size);
365 push_u32(&mut body, child_ty);
366 body.extend_from_slice(values);
367 pod_prim(v, SPA_TYPE_CHOICE, &body);
368}
369
370pub fn pod_choice_int(v: &mut Vec<u8>, choice: u32, values: &[i32]) {
371 let bytes: Vec<u8> = values.iter().flat_map(|x| x.to_ne_bytes()).collect();
372 pod_choice(v, choice, SPA_TYPE_INT, 4, &bytes);
373}
374
375pub fn pod_choice_id(v: &mut Vec<u8>, choice: u32, values: &[u32]) {
376 let bytes: Vec<u8> = values.iter().flat_map(|x| x.to_ne_bytes()).collect();
377 pod_choice(v, choice, SPA_TYPE_ID, 4, &bytes);
378}
379
380pub fn pod_choice_long(v: &mut Vec<u8>, choice: u32, values: &[i64]) {
381 let bytes: Vec<u8> = values.iter().flat_map(|x| x.to_ne_bytes()).collect();
382 pod_choice(v, choice, SPA_TYPE_LONG, 8, &bytes);
383}
384
385pub fn pod_choice_rect(v: &mut Vec<u8>, choice: u32, values: &[(u32, u32)]) {
387 let bytes: Vec<u8> = values
388 .iter()
389 .flat_map(|(w, h)| [w.to_ne_bytes(), h.to_ne_bytes()].concat())
390 .collect();
391 pod_choice(v, choice, SPA_TYPE_RECTANGLE, 8, &bytes);
392}
393
394pub fn pod_choice_frac(v: &mut Vec<u8>, choice: u32, values: &[(u32, u32)]) {
396 let bytes: Vec<u8> = values
397 .iter()
398 .flat_map(|(n, d)| [n.to_ne_bytes(), d.to_ne_bytes()].concat())
399 .collect();
400 pod_choice(v, choice, SPA_TYPE_FRACTION, 8, &bytes);
401}
402
403pub fn prop_flags(v: &mut Vec<u8>, key: u32, flags: u32, value: impl FnOnce(&mut Vec<u8>)) {
405 push_u32(v, key);
406 push_u32(v, flags);
407 value(v);
408}
409
410pub fn prop(v: &mut Vec<u8>, key: u32, value: impl FnOnce(&mut Vec<u8>)) {
411 prop_flags(v, key, 0, value);
412}
413
414pub fn object(ty: u32, id: u32, props: impl FnOnce(&mut Vec<u8>)) -> Vec<u8> {
416 let mut body = Vec::new();
417 push_u32(&mut body, ty);
418 push_u32(&mut body, id);
419 props(&mut body);
420 let mut v = Vec::new();
421 pod_prim(&mut v, SPA_TYPE_OBJECT, &body);
422 v
423}
424
425#[derive(Clone, Copy, Debug)]
429pub struct PodProp<'a> {
430 pub key: u32,
431 pub flags: u32,
432 pub ty: u32,
433 pub payload: &'a [u8],
434}
435
436fn u32_at(v: &[u8], off: usize) -> Option<u32> {
437 v.get(off..off + 4)
438 .map(|b| u32::from_ne_bytes(b.try_into().unwrap()))
439}
440
441pub unsafe fn pod_bytes<'a>(pod: *const c_void) -> &'a [u8] {
446 let size = unsafe { *(pod as *const u32) } as usize;
447 unsafe { std::slice::from_raw_parts(pod as *const u8, size + 8) }
448}
449
450pub fn object_props(pod: &[u8]) -> Option<(u32, u32, Vec<PodProp<'_>>)> {
452 let size = u32_at(pod, 0)? as usize;
453 if u32_at(pod, 4)? != SPA_TYPE_OBJECT || pod.len() < size + 8 {
454 return None;
455 }
456 let body = &pod[8..8 + size];
457 let ty = u32_at(body, 0)?;
458 let id = u32_at(body, 4)?;
459 let mut props = Vec::new();
460 let mut off = 8;
461 while off + 16 <= body.len() {
462 let key = u32_at(body, off)?;
463 let flags = u32_at(body, off + 4)?;
464 let vsize = u32_at(body, off + 8)? as usize;
465 let vty = u32_at(body, off + 12)?;
466 let payload = body.get(off + 16..off + 16 + vsize)?;
467 props.push(PodProp {
468 key,
469 flags,
470 ty: vty,
471 payload,
472 });
473 off += 16 + vsize.div_ceil(8) * 8;
474 }
475 Some((ty, id, props))
476}
477
478pub fn pod_value(ty: u32, payload: &[u8]) -> Option<(u32, &[u8])> {
481 if ty != SPA_TYPE_CHOICE {
482 return Some((ty, payload));
483 }
484 let child_size = u32_at(payload, 8)? as usize;
485 let child_ty = u32_at(payload, 12)?;
486 Some((child_ty, payload.get(16..16 + child_size)?))
487}
488
489impl PodProp<'_> {
490 pub fn as_u32(&self) -> Option<u32> {
491 let (ty, b) = pod_value(self.ty, self.payload)?;
492 (ty == SPA_TYPE_ID || ty == SPA_TYPE_INT)
493 .then(|| u32_at(b, 0))
494 .flatten()
495 }
496
497 pub fn as_i64(&self) -> Option<i64> {
498 let (ty, b) = pod_value(self.ty, self.payload)?;
499 (ty == SPA_TYPE_LONG)
500 .then(|| {
501 b.get(..8)
502 .map(|x| i64::from_ne_bytes(x.try_into().unwrap()))
503 })
504 .flatten()
505 }
506
507 pub fn as_pair(&self) -> Option<(u32, u32)> {
509 let (ty, b) = pod_value(self.ty, self.payload)?;
510 (ty == SPA_TYPE_RECTANGLE || ty == SPA_TYPE_FRACTION)
511 .then(|| Some((u32_at(b, 0)?, u32_at(b, 4)?)))
512 .flatten()
513 }
514}
515
516pub fn find_prop<'a>(props: &'a [PodProp<'a>], key: u32) -> Option<&'a PodProp<'a>> {
518 props.iter().find(|p| p.key == key)
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 fn struct_layouts_match_libpipewire() {
527 assert_eq!(mem::size_of::<PwStreamEvents>(), 96);
528 assert_eq!(mem::size_of::<PwBuffer>(), 40);
529 assert_eq!(mem::size_of::<SpaBuffer>(), 24);
530 assert_eq!(mem::size_of::<SpaData>(), 40);
531 assert_eq!(mem::size_of::<SpaChunk>(), 16);
532 assert_eq!(mem::size_of::<SpaMetaHeader>(), 32);
533 assert_eq!(
534 mem::size_of::<SpaMetaRegion>(),
535 SPA_META_REGION_SIZE as usize
536 );
537 assert_eq!(mem::size_of::<SpaMetaCursor>(), 28);
538 assert_eq!(mem::size_of::<SpaMetaBitmap>(), 20);
539 assert_eq!(cursor_meta_size(64, 64), 28 + 20 + 64 * 64 * 4);
540 assert_eq!(mem::offset_of!(PwStreamEvents, process), 64);
541 assert_eq!(mem::offset_of!(SpaData, data), 24);
542 assert_eq!(mem::offset_of!(SpaData, chunk), 32);
543 }
544
545 #[test]
548 fn built_pods_read_back() {
549 let pod = object(SPA_TYPE_OBJECT_FORMAT, SPA_PARAM_ENUM_FORMAT, |p| {
550 prop(p, SPA_FORMAT_MEDIA_TYPE, |v| {
551 pod_id(v, SPA_MEDIA_TYPE_VIDEO)
552 });
553 prop(p, SPA_FORMAT_VIDEO_FORMAT, |v| {
554 pod_choice_id(
555 v,
556 SPA_CHOICE_ENUM,
557 &[
558 SPA_VIDEO_FORMAT_BGRX,
559 SPA_VIDEO_FORMAT_BGRX,
560 SPA_VIDEO_FORMAT_BGRA,
561 ],
562 )
563 });
564 prop_flags(
565 p,
566 SPA_FORMAT_VIDEO_MODIFIER,
567 SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE,
568 |v| {
569 pod_choice_long(
570 v,
571 SPA_CHOICE_ENUM,
572 &[0x0300_0000_0000_0001, 0x0300_0000_0000_0001, 0],
573 )
574 },
575 );
576 prop(p, SPA_FORMAT_VIDEO_SIZE, |v| {
577 pod_choice_rect(v, SPA_CHOICE_RANGE, &[(1280, 720), (1, 1), (16384, 16384)])
578 });
579 prop(p, SPA_FORMAT_VIDEO_FRAMERATE, |v| pod_frac(v, 0, 1));
580 prop(p, SPA_FORMAT_VIDEO_MAX_FRAMERATE, |v| {
581 pod_choice_frac(v, SPA_CHOICE_RANGE, &[(60, 1), (1, 1), (60, 1)])
582 });
583 prop(p, SPA_FORMAT_VIDEO_MAX_FRAMERATE + 1, |v| pod_int(v, 4));
584 });
585 assert_eq!(pod.len() % 8, 0);
586 let (ty, id, props) = object_props(&pod).unwrap();
587 assert_eq!((ty, id), (SPA_TYPE_OBJECT_FORMAT, SPA_PARAM_ENUM_FORMAT));
588 assert_eq!(
589 find_prop(&props, SPA_FORMAT_MEDIA_TYPE).unwrap().as_u32(),
590 Some(SPA_MEDIA_TYPE_VIDEO)
591 );
592 assert_eq!(
593 find_prop(&props, SPA_FORMAT_VIDEO_FORMAT).unwrap().as_u32(),
594 Some(SPA_VIDEO_FORMAT_BGRX)
595 );
596 let modifier = find_prop(&props, SPA_FORMAT_VIDEO_MODIFIER).unwrap();
597 assert_eq!(
598 modifier.flags,
599 SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE
600 );
601 assert_eq!(modifier.as_i64(), Some(0x0300_0000_0000_0001));
602 assert_eq!(
603 find_prop(&props, SPA_FORMAT_VIDEO_SIZE).unwrap().as_pair(),
604 Some((1280, 720))
605 );
606 assert_eq!(
607 find_prop(&props, SPA_FORMAT_VIDEO_FRAMERATE)
608 .unwrap()
609 .as_pair(),
610 Some((0, 1))
611 );
612 assert_eq!(
613 find_prop(&props, SPA_FORMAT_VIDEO_MAX_FRAMERATE)
614 .unwrap()
615 .as_pair(),
616 Some((60, 1))
617 );
618 assert_eq!(
619 find_prop(&props, SPA_FORMAT_VIDEO_MAX_FRAMERATE + 1)
620 .unwrap()
621 .as_u32(),
622 Some(4)
623 );
624 assert!(find_prop(&props, SPA_FORMAT_MEDIA_SUBTYPE).is_none());
625 let bytes = unsafe { pod_bytes(pod.as_ptr() as *const c_void) };
626 assert_eq!(bytes.len(), pod.len());
627 }
628}