1use std::ffi::{c_char, c_int, c_void, CStr, CString};
15use std::mem;
16use std::ptr;
17use std::sync::atomic::{AtomicI32, AtomicPtr, AtomicU64, Ordering};
18use std::sync::{Arc, Mutex};
19use std::time::{Duration, Instant};
20
21use libloading::Library;
22
23use super::ring::{RingFormat, V4L2_PIX_FMT_NV12, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_MJPEG};
24
25const SPA_TYPE_ID: u32 = 3;
26const SPA_TYPE_INT: u32 = 4;
27const SPA_TYPE_RECTANGLE: u32 = 10;
28const SPA_TYPE_FRACTION: u32 = 11;
29const SPA_TYPE_OBJECT: u32 = 15;
30const SPA_TYPE_CHOICE: u32 = 19;
31const SPA_TYPE_OBJECT_FORMAT: u32 = 0x40003;
32const SPA_TYPE_OBJECT_PARAM_BUFFERS: u32 = 0x40004;
33const SPA_TYPE_OBJECT_PARAM_META: u32 = 0x40005;
34const SPA_PARAM_ENUM_FORMAT: u32 = 3;
35const SPA_PARAM_FORMAT: u32 = 4;
36const SPA_PARAM_BUFFERS: u32 = 5;
37const SPA_PARAM_META: u32 = 6;
38const SPA_FORMAT_MEDIA_TYPE: u32 = 1;
39const SPA_FORMAT_MEDIA_SUBTYPE: u32 = 2;
40const SPA_FORMAT_VIDEO_FORMAT: u32 = 0x20001;
41const SPA_FORMAT_VIDEO_SIZE: u32 = 0x20003;
42const SPA_FORMAT_VIDEO_FRAMERATE: u32 = 0x20004;
43const SPA_MEDIA_TYPE_VIDEO: u32 = 2;
44const SPA_MEDIA_SUBTYPE_RAW: u32 = 1;
45const SPA_MEDIA_SUBTYPE_MJPG: u32 = 0x20002;
46const SPA_VIDEO_FORMAT_I420: u32 = 2;
47const SPA_VIDEO_FORMAT_YUY2: u32 = 4;
48const SPA_VIDEO_FORMAT_NV12: u32 = 23;
49const SPA_PARAM_BUFFERS_BUFFERS: u32 = 1;
50const SPA_PARAM_BUFFERS_BLOCKS: u32 = 2;
51const SPA_PARAM_BUFFERS_SIZE: u32 = 3;
52const SPA_PARAM_BUFFERS_STRIDE: u32 = 4;
53const SPA_PARAM_BUFFERS_ALIGN: u32 = 5;
54const SPA_PARAM_BUFFERS_DATATYPE: u32 = 6;
55const SPA_PARAM_META_TYPE: u32 = 1;
56const SPA_PARAM_META_SIZE: u32 = 2;
57const SPA_META_HEADER: u32 = 1;
58const SPA_META_HEADER_SIZE: u32 = 32;
59const SPA_DATA_MEMPTR: u32 = 1;
60const SPA_DATA_MEMFD: u32 = 2;
61const SPA_CHOICE_RANGE: u32 = 1;
62const SPA_CHOICE_FLAGS: u32 = 4;
63const PW_DIRECTION_OUTPUT: c_int = 1;
64const PW_ID_ANY: u32 = 0xFFFF_FFFF;
65const PW_STREAM_FLAG_MAP_BUFFERS: u32 = 4;
66const PW_STREAM_FLAG_DRIVER: u32 = 8;
67const PW_STREAM_STATE_ERROR: c_int = -1;
68const PW_STREAM_STATE_PAUSED: c_int = 2;
69const PW_STREAM_STATE_STREAMING: c_int = 3;
70const PW_VERSION_STREAM_EVENTS: u32 = 2;
71
72#[repr(C)]
73struct PwStreamEvents {
74 version: u32,
75 destroy: Option<unsafe extern "C" fn(*mut c_void)>,
76 state_changed: Option<unsafe extern "C" fn(*mut c_void, c_int, c_int, *const c_char)>,
77 control_info: Option<unsafe extern "C" fn(*mut c_void, u32, *const c_void)>,
78 io_changed: Option<unsafe extern "C" fn(*mut c_void, u32, *mut c_void, u32)>,
79 param_changed: Option<unsafe extern "C" fn(*mut c_void, u32, *const c_void)>,
80 add_buffer: Option<unsafe extern "C" fn(*mut c_void, *mut PwBuffer)>,
81 remove_buffer: Option<unsafe extern "C" fn(*mut c_void, *mut PwBuffer)>,
82 process: Option<unsafe extern "C" fn(*mut c_void)>,
83 drained: Option<unsafe extern "C" fn(*mut c_void)>,
84 command: Option<unsafe extern "C" fn(*mut c_void, *const c_void)>,
85 trigger_done: Option<unsafe extern "C" fn(*mut c_void)>,
86}
87
88#[repr(C)]
89struct PwBuffer {
90 buffer: *mut SpaBuffer,
91 user_data: *mut c_void,
92 size: u64,
93 requested: u64,
94 time: u64,
95}
96
97#[repr(C)]
98struct SpaBuffer {
99 n_metas: u32,
100 n_datas: u32,
101 metas: *mut SpaMeta,
102 datas: *mut SpaData,
103}
104
105#[repr(C)]
106struct SpaMeta {
107 type_: u32,
108 size: u32,
109 data: *mut c_void,
110}
111
112#[repr(C)]
113struct SpaData {
114 type_: u32,
115 flags: u32,
116 fd: i64,
117 mapoffset: u32,
118 maxsize: u32,
119 data: *mut c_void,
120 chunk: *mut SpaChunk,
121}
122
123#[repr(C)]
124struct SpaChunk {
125 offset: u32,
126 size: u32,
127 stride: i32,
128 flags: i32,
129}
130
131#[repr(C)]
132struct SpaMetaHeader {
133 flags: u32,
134 offset: u32,
135 pts: i64,
136 dts_offset: i64,
137 seq: u64,
138}
139
140type PwInit = unsafe extern "C" fn(*mut c_int, *mut *mut *mut c_char);
141type PwThreadLoopNew = unsafe extern "C" fn(*const c_char, *const c_void) -> *mut c_void;
142type PwThreadLoopGetLoop = unsafe extern "C" fn(*mut c_void) -> *mut c_void;
143type PwThreadLoopInt = unsafe extern "C" fn(*mut c_void) -> c_int;
144type PwThreadLoopVoid = unsafe extern "C" fn(*mut c_void);
145type PwContextNew = unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> *mut c_void;
146type PwContextConnect = unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> *mut c_void;
147type PwContextDestroy = unsafe extern "C" fn(*mut c_void);
148type PwCoreDisconnect = unsafe extern "C" fn(*mut c_void) -> c_int;
149type PwPropertiesNew = unsafe extern "C" fn(*const c_char, ...) -> *mut c_void;
150type PwPropertiesSet = unsafe extern "C" fn(*mut c_void, *const c_char, *const c_char) -> c_int;
151type PwStreamNew = unsafe extern "C" fn(*mut c_void, *const c_char, *mut c_void) -> *mut c_void;
152type PwStreamAddListener = unsafe extern "C" fn(*mut c_void, *mut c_void, *const PwStreamEvents, *mut c_void);
153type PwStreamConnect = unsafe extern "C" fn(*mut c_void, c_int, u32, u32, *mut *const c_void, u32) -> c_int;
154type PwStreamUpdateParams = unsafe extern "C" fn(*mut c_void, *mut *const c_void, u32) -> c_int;
155type PwStreamDequeueBuffer = unsafe extern "C" fn(*mut c_void) -> *mut PwBuffer;
156type PwStreamQueueBuffer = unsafe extern "C" fn(*mut c_void, *mut PwBuffer) -> c_int;
157type PwStreamTriggerProcess = unsafe extern "C" fn(*mut c_void) -> c_int;
158type PwStreamVoid = unsafe extern "C" fn(*mut c_void);
159type PwStreamInt = unsafe extern "C" fn(*mut c_void) -> c_int;
160
161#[derive(Clone, Copy)]
163struct Api {
164 thread_loop_new: PwThreadLoopNew,
165 thread_loop_get_loop: PwThreadLoopGetLoop,
166 thread_loop_start: PwThreadLoopInt,
167 thread_loop_stop: PwThreadLoopVoid,
168 thread_loop_lock: PwThreadLoopVoid,
169 thread_loop_unlock: PwThreadLoopVoid,
170 thread_loop_destroy: PwThreadLoopVoid,
171 context_new: PwContextNew,
172 context_connect: PwContextConnect,
173 context_destroy: PwContextDestroy,
174 core_disconnect: PwCoreDisconnect,
175 properties_new: PwPropertiesNew,
176 properties_set: PwPropertiesSet,
177 stream_new: PwStreamNew,
178 stream_add_listener: PwStreamAddListener,
179 stream_connect: PwStreamConnect,
180 stream_update_params: PwStreamUpdateParams,
181 stream_dequeue_buffer: PwStreamDequeueBuffer,
182 stream_queue_buffer: PwStreamQueueBuffer,
183 stream_trigger_process: PwStreamTriggerProcess,
184 stream_disconnect: PwStreamInt,
185 stream_destroy: PwStreamVoid,
186}
187
188fn api() -> Result<&'static Api, String> {
191 static API: std::sync::OnceLock<Result<Api, String>> = std::sync::OnceLock::new();
192 API.get_or_init(|| unsafe {
193 let lib = Library::new("libpipewire-0.3.so.0").map_err(|e| format!("libpipewire-0.3 not available: {}", e))?;
194 macro_rules! sym {
195 ($name:literal, $t:ty) => {
196 *lib.get::<$t>(concat!($name, "\0").as_bytes()).map_err(|e| format!("{}: {}", $name, e))?
197 };
198 }
199 let init: PwInit = sym!("pw_init", PwInit);
200 let api = Api {
201 thread_loop_new: sym!("pw_thread_loop_new", PwThreadLoopNew),
202 thread_loop_get_loop: sym!("pw_thread_loop_get_loop", PwThreadLoopGetLoop),
203 thread_loop_start: sym!("pw_thread_loop_start", PwThreadLoopInt),
204 thread_loop_stop: sym!("pw_thread_loop_stop", PwThreadLoopVoid),
205 thread_loop_lock: sym!("pw_thread_loop_lock", PwThreadLoopVoid),
206 thread_loop_unlock: sym!("pw_thread_loop_unlock", PwThreadLoopVoid),
207 thread_loop_destroy: sym!("pw_thread_loop_destroy", PwThreadLoopVoid),
208 context_new: sym!("pw_context_new", PwContextNew),
209 context_connect: sym!("pw_context_connect", PwContextConnect),
210 context_destroy: sym!("pw_context_destroy", PwContextDestroy),
211 core_disconnect: sym!("pw_core_disconnect", PwCoreDisconnect),
212 properties_new: sym!("pw_properties_new", PwPropertiesNew),
213 properties_set: sym!("pw_properties_set", PwPropertiesSet),
214 stream_new: sym!("pw_stream_new", PwStreamNew),
215 stream_add_listener: sym!("pw_stream_add_listener", PwStreamAddListener),
216 stream_connect: sym!("pw_stream_connect", PwStreamConnect),
217 stream_update_params: sym!("pw_stream_update_params", PwStreamUpdateParams),
218 stream_dequeue_buffer: sym!("pw_stream_dequeue_buffer", PwStreamDequeueBuffer),
219 stream_queue_buffer: sym!("pw_stream_queue_buffer", PwStreamQueueBuffer),
220 stream_trigger_process: sym!("pw_stream_trigger_process", PwStreamTriggerProcess),
221 stream_disconnect: sym!("pw_stream_disconnect", PwStreamInt),
222 stream_destroy: sym!("pw_stream_destroy", PwStreamVoid),
223 };
224 init(ptr::null_mut(), ptr::null_mut());
225 mem::forget(lib);
226 Ok(api)
227 })
228 .as_ref()
229 .map_err(|e| e.clone())
230}
231
232fn push_u32(v: &mut Vec<u8>, x: u32) {
235 v.extend_from_slice(&x.to_ne_bytes());
236}
237
238fn pad8(v: &mut Vec<u8>) {
239 while v.len() % 8 != 0 {
240 v.push(0);
241 }
242}
243
244fn pod_prim(v: &mut Vec<u8>, ty: u32, payload: &[u8]) {
245 push_u32(v, payload.len() as u32);
246 push_u32(v, ty);
247 v.extend_from_slice(payload);
248 pad8(v);
249}
250
251fn pod_id(v: &mut Vec<u8>, x: u32) {
252 pod_prim(v, SPA_TYPE_ID, &x.to_ne_bytes());
253}
254
255fn pod_int(v: &mut Vec<u8>, x: i32) {
256 pod_prim(v, SPA_TYPE_INT, &x.to_ne_bytes());
257}
258
259fn pod_rect(v: &mut Vec<u8>, w: u32, h: u32) {
260 let mut p = Vec::new();
261 push_u32(&mut p, w);
262 push_u32(&mut p, h);
263 pod_prim(v, SPA_TYPE_RECTANGLE, &p);
264}
265
266fn pod_frac(v: &mut Vec<u8>, num: u32, den: u32) {
267 let mut p = Vec::new();
268 push_u32(&mut p, num);
269 push_u32(&mut p, den);
270 pod_prim(v, SPA_TYPE_FRACTION, &p);
271}
272
273fn pod_choice_int(v: &mut Vec<u8>, choice: u32, values: &[i32]) {
274 let mut body = Vec::new();
275 push_u32(&mut body, choice);
276 push_u32(&mut body, 0);
277 push_u32(&mut body, 4);
278 push_u32(&mut body, SPA_TYPE_INT);
279 for x in values {
280 body.extend_from_slice(&x.to_ne_bytes());
281 }
282 pod_prim(v, SPA_TYPE_CHOICE, &body);
283}
284
285fn prop(v: &mut Vec<u8>, key: u32, value: impl FnOnce(&mut Vec<u8>)) {
286 push_u32(v, key);
287 push_u32(v, 0);
288 value(v);
289}
290
291fn object(ty: u32, id: u32, props: impl FnOnce(&mut Vec<u8>)) -> Vec<u8> {
292 let mut body = Vec::new();
293 push_u32(&mut body, ty);
294 push_u32(&mut body, id);
295 props(&mut body);
296 let mut v = Vec::new();
297 pod_prim(&mut v, SPA_TYPE_OBJECT, &body);
298 v
299}
300
301fn spa_video_format(fourcc: u32) -> Option<u32> {
302 match fourcc {
303 V4L2_PIX_FMT_YUV420 => Some(SPA_VIDEO_FORMAT_I420),
304 V4L2_PIX_FMT_NV12 => Some(SPA_VIDEO_FORMAT_NV12),
305 V4L2_PIX_FMT_YUYV => Some(SPA_VIDEO_FORMAT_YUY2),
306 _ => None,
307 }
308}
309
310fn format_pod(fmt: &RingFormat, spa_format: Option<u32>) -> Vec<u8> {
313 object(SPA_TYPE_OBJECT_FORMAT, SPA_PARAM_ENUM_FORMAT, |p| {
314 prop(p, SPA_FORMAT_MEDIA_TYPE, |v| pod_id(v, SPA_MEDIA_TYPE_VIDEO));
315 match spa_format {
316 Some(raw) => {
317 prop(p, SPA_FORMAT_MEDIA_SUBTYPE, |v| pod_id(v, SPA_MEDIA_SUBTYPE_RAW));
318 prop(p, SPA_FORMAT_VIDEO_FORMAT, |v| pod_id(v, raw));
319 }
320 None => prop(p, SPA_FORMAT_MEDIA_SUBTYPE, |v| pod_id(v, SPA_MEDIA_SUBTYPE_MJPG)),
321 }
322 prop(p, SPA_FORMAT_VIDEO_SIZE, |v| pod_rect(v, fmt.width, fmt.height));
323 prop(p, SPA_FORMAT_VIDEO_FRAMERATE, |v| pod_frac(v, fmt.fps_num, fmt.fps_den));
324 })
325}
326
327fn buffers_pod(fmt: &RingFormat) -> Vec<u8> {
330 object(SPA_TYPE_OBJECT_PARAM_BUFFERS, SPA_PARAM_BUFFERS, |p| {
331 prop(p, SPA_PARAM_BUFFERS_BUFFERS, |v| pod_choice_int(v, SPA_CHOICE_RANGE, &[4, 2, 8]));
332 prop(p, SPA_PARAM_BUFFERS_BLOCKS, |v| pod_int(v, 1));
333 prop(p, SPA_PARAM_BUFFERS_SIZE, |v| pod_int(v, fmt.sizeimage as i32));
334 prop(p, SPA_PARAM_BUFFERS_STRIDE, |v| pod_int(v, fmt.bytesperline as i32));
335 prop(p, SPA_PARAM_BUFFERS_ALIGN, |v| pod_int(v, 16));
336 prop(p, SPA_PARAM_BUFFERS_DATATYPE, |v| {
337 pod_choice_int(v, SPA_CHOICE_FLAGS, &[((1 << SPA_DATA_MEMFD) | (1 << SPA_DATA_MEMPTR)) as i32])
338 });
339 })
340}
341
342fn meta_pod() -> Vec<u8> {
343 object(SPA_TYPE_OBJECT_PARAM_META, SPA_PARAM_META, |p| {
344 prop(p, SPA_PARAM_META_TYPE, |v| pod_id(v, SPA_META_HEADER));
345 prop(p, SPA_PARAM_META_SIZE, |v| pod_int(v, SPA_META_HEADER_SIZE as i32));
346 })
347}
348
349struct Shared {
353 api: Api,
354 stream: AtomicPtr<c_void>,
355 frame: Mutex<Vec<u8>>,
356 frame_seq: AtomicU64,
357 frame_ts_ns: AtomicU64,
358 state: AtomicI32,
359 stride: i32,
360 buffers_pod: Vec<u8>,
361 meta_pod: Vec<u8>,
362}
363
364unsafe extern "C" fn on_state_changed(data: *mut c_void, _old: c_int, new: c_int, error: *const c_char) {
365 unsafe {
366 let shared = &*(data as *const Shared);
367 shared.state.store(new, Ordering::Release);
368 if new == PW_STREAM_STATE_ERROR {
369 let msg = if error.is_null() { String::new() } else { CStr::from_ptr(error).to_string_lossy().into_owned() };
370 eprintln!("[webcam] PipeWire stream error: {}", msg);
371 }
372 }
373}
374
375unsafe extern "C" fn on_param_changed(data: *mut c_void, id: u32, param: *const c_void) {
376 if id != SPA_PARAM_FORMAT || param.is_null() {
377 return;
378 }
379 unsafe {
380 let shared = &*(data as *const Shared);
381 let stream = shared.stream.load(Ordering::Acquire);
382 if stream.is_null() {
383 return;
384 }
385 let mut params: [*const c_void; 2] = [shared.buffers_pod.as_ptr() as *const c_void, shared.meta_pod.as_ptr() as *const c_void];
386 (shared.api.stream_update_params)(stream, params.as_mut_ptr(), 2);
387 }
388}
389
390unsafe extern "C" fn on_process(data: *mut c_void) {
391 unsafe {
392 let shared = &*(data as *const Shared);
393 let stream = shared.stream.load(Ordering::Acquire);
394 if stream.is_null() {
395 return;
396 }
397 let frame = shared.frame.lock().unwrap_or_else(|e| e.into_inner());
398 if frame.is_empty() {
399 return;
400 }
401 let b = (shared.api.stream_dequeue_buffer)(stream);
402 if b.is_null() {
403 return;
404 }
405 let spa_buf = (*b).buffer;
406 if !spa_buf.is_null() && (*spa_buf).n_datas >= 1 {
407 let d = &mut *(*spa_buf).datas;
408 let mut written = 0usize;
409 if !d.data.is_null() && !d.chunk.is_null() {
410 written = frame.len().min(d.maxsize as usize);
411 ptr::copy_nonoverlapping(frame.as_ptr(), d.data as *mut u8, written);
412 (*d.chunk).offset = 0;
413 (*d.chunk).size = written as u32;
414 (*d.chunk).stride = shared.stride;
415 (*d.chunk).flags = 0;
416 }
417 for i in 0..(*spa_buf).n_metas as usize {
418 let m = &*(*spa_buf).metas.add(i);
419 if m.type_ == SPA_META_HEADER && m.size >= SPA_META_HEADER_SIZE && !m.data.is_null() {
420 let h = &mut *(m.data as *mut SpaMetaHeader);
421 h.flags = 0;
422 h.offset = 0;
423 h.pts = shared.frame_ts_ns.load(Ordering::Relaxed) as i64;
424 h.dts_offset = 0;
425 h.seq = shared.frame_seq.load(Ordering::Relaxed);
426 }
427 }
428 (*b).size = written as u64;
429 }
430 (shared.api.stream_queue_buffer)(stream, b);
431 }
432}
433
434pub struct PipeWireSink {
435 api: Api,
436 thread_loop: *mut c_void,
437 context: *mut c_void,
438 core: *mut c_void,
439 stream: *mut c_void,
440 _hook: Box<[u64; 8]>,
441 _events: Box<PwStreamEvents>,
442 _format_pod: Vec<u8>,
443 shared: Arc<Shared>,
444 failed: bool,
445}
446
447unsafe impl Send for PipeWireSink {}
448
449impl PipeWireSink {
450 pub fn connect(node_name: &str, description: &str, fmt: &RingFormat) -> Result<Self, String> {
453 let api = *api()?;
454 let node_name = CString::new(node_name).map_err(|_| "node.name must not contain NUL bytes".to_string())?;
456 let description =
457 CString::new(description).map_err(|_| "node.description must not contain NUL bytes".to_string())?;
458 let spa_format = if fmt.fourcc == V4L2_PIX_FMT_MJPEG {
459 None
460 } else {
461 Some(spa_video_format(fmt.fourcc).ok_or_else(|| "pixel format has no PipeWire equivalent".to_string())?)
462 };
463 unsafe {
464 let thread_loop = (api.thread_loop_new)(c"pixelflux-webcam-pw".as_ptr(), ptr::null());
465 if thread_loop.is_null() {
466 return Err("pw_thread_loop_new failed".into());
467 }
468 let mut sink = PipeWireSink {
469 api,
470 thread_loop,
471 context: ptr::null_mut(),
472 core: ptr::null_mut(),
473 stream: ptr::null_mut(),
474 _hook: Box::new([0u64; 8]),
475 _events: Box::new(PwStreamEvents {
476 version: PW_VERSION_STREAM_EVENTS,
477 destroy: None,
478 state_changed: Some(on_state_changed),
479 control_info: None,
480 io_changed: None,
481 param_changed: Some(on_param_changed),
482 add_buffer: None,
483 remove_buffer: None,
484 process: Some(on_process),
485 drained: None,
486 command: None,
487 trigger_done: None,
488 }),
489 _format_pod: format_pod(fmt, spa_format),
490 shared: Arc::new(Shared {
491 api,
492 stream: AtomicPtr::new(ptr::null_mut()),
493 frame: Mutex::new(Vec::new()),
494 frame_seq: AtomicU64::new(0),
495 frame_ts_ns: AtomicU64::new(0),
496 state: AtomicI32::new(0),
497 stride: fmt.bytesperline as i32,
498 buffers_pod: buffers_pod(fmt),
499 meta_pod: meta_pod(),
500 }),
501 failed: false,
502 };
503 let pw_loop = (api.thread_loop_get_loop)(thread_loop);
504 sink.context = (api.context_new)(pw_loop, ptr::null_mut(), 0);
505 if sink.context.is_null() {
506 return Err("pw_context_new failed".into());
507 }
508 if (api.thread_loop_start)(thread_loop) < 0 {
509 return Err("pw_thread_loop_start failed".into());
510 }
511 (api.thread_loop_lock)(thread_loop);
512 let result = (|| -> Result<(), String> {
513 sink.core = (api.context_connect)(sink.context, ptr::null_mut(), 0);
514 if sink.core.is_null() {
515 return Err("no PipeWire daemon reachable".into());
516 }
517 let props = (api.properties_new)(ptr::null::<c_char>());
518 if props.is_null() {
519 return Err("pw_properties_new failed".into());
520 }
521 for (k, v) in [
522 (c"media.class", c"Video/Source" as &CStr),
523 (c"media.role", c"Camera"),
524 (c"node.name", node_name.as_c_str()),
525 (c"node.description", description.as_c_str()),
526 (c"node.virtual", c"true"),
527 ] {
528 (api.properties_set)(props, k.as_ptr(), v.as_ptr());
529 }
530 sink.stream = (api.stream_new)(sink.core, description.as_ptr(), props);
531 if sink.stream.is_null() {
532 return Err("pw_stream_new failed".into());
533 }
534 sink.shared.stream.store(sink.stream, Ordering::Release);
535 let user_data = Arc::as_ptr(&sink.shared) as *mut c_void;
536 (api.stream_add_listener)(sink.stream, sink._hook.as_mut_ptr() as *mut c_void, &*sink._events, user_data);
537 let mut params: [*const c_void; 1] = [sink._format_pod.as_ptr() as *const c_void];
538 let rc = (api.stream_connect)(
539 sink.stream,
540 PW_DIRECTION_OUTPUT,
541 PW_ID_ANY,
542 PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_DRIVER,
543 params.as_mut_ptr(),
544 1,
545 );
546 if rc < 0 {
547 return Err(format!("pw_stream_connect failed ({})", rc));
548 }
549 Ok(())
550 })();
551 (api.thread_loop_unlock)(thread_loop);
552 result?;
553 let deadline = Instant::now() + Duration::from_secs(3);
554 loop {
555 let st = sink.shared.state.load(Ordering::Acquire);
556 if st == PW_STREAM_STATE_ERROR {
557 return Err("PipeWire stream entered the error state".into());
558 }
559 if st == PW_STREAM_STATE_PAUSED || st == PW_STREAM_STATE_STREAMING || Instant::now() >= deadline {
560 break;
561 }
562 std::thread::sleep(Duration::from_millis(10));
563 }
564 Ok(sink)
565 }
566 }
567
568 pub fn is_failed(&self) -> bool {
569 self.failed || self.shared.state.load(Ordering::Acquire) == PW_STREAM_STATE_ERROR
570 }
571
572 pub fn is_streaming(&self) -> bool {
575 self.shared.state.load(Ordering::Acquire) == PW_STREAM_STATE_STREAMING
576 }
577
578 pub fn publish(&mut self, frame: &[u8], ts_ns: u64) {
581 if self.is_failed() || !self.is_streaming() {
582 return;
583 }
584 {
585 let mut slot = self.shared.frame.lock().unwrap_or_else(|e| e.into_inner());
586 slot.clear();
587 slot.extend_from_slice(frame);
588 }
589 self.shared.frame_seq.fetch_add(1, Ordering::Relaxed);
590 self.shared.frame_ts_ns.store(ts_ns, Ordering::Relaxed);
591 unsafe {
592 (self.api.thread_loop_lock)(self.thread_loop);
593 (self.api.stream_trigger_process)(self.stream);
594 (self.api.thread_loop_unlock)(self.thread_loop);
595 }
596 }
597}
598
599impl Drop for PipeWireSink {
600 fn drop(&mut self) {
601 unsafe {
602 (self.api.thread_loop_lock)(self.thread_loop);
603 self.shared.stream.store(ptr::null_mut(), Ordering::Release);
604 if !self.stream.is_null() {
605 (self.api.stream_disconnect)(self.stream);
606 (self.api.stream_destroy)(self.stream);
607 }
608 if !self.core.is_null() {
609 (self.api.core_disconnect)(self.core);
610 }
611 (self.api.thread_loop_unlock)(self.thread_loop);
612 (self.api.thread_loop_stop)(self.thread_loop);
613 if !self.context.is_null() {
614 (self.api.context_destroy)(self.context);
615 }
616 (self.api.thread_loop_destroy)(self.thread_loop);
617 }
618 }
619}
620
621#[cfg(test)]
622mod tests {
623 use super::*;
624
625 fn u32_at(v: &[u8], off: usize) -> u32 {
626 u32::from_ne_bytes(v[off..off + 4].try_into().unwrap())
627 }
628
629 fn parse_object(pod: &[u8]) -> (u32, u32, Vec<(u32, u32, Vec<u8>)>) {
631 assert_eq!(pod.len() % 8, 0);
632 assert_eq!(u32_at(pod, 0) as usize, pod.len() - 8);
633 assert_eq!(u32_at(pod, 4), SPA_TYPE_OBJECT);
634 let mut props = Vec::new();
635 let mut off = 16;
636 while off < pod.len() {
637 let key = u32_at(pod, off);
638 let size = u32_at(pod, off + 8) as usize;
639 let ty = u32_at(pod, off + 12);
640 props.push((key, ty, pod[off + 16..off + 16 + size].to_vec()));
641 off += 16 + size.div_ceil(8) * 8;
642 }
643 (u32_at(pod, 8), u32_at(pod, 12), props)
644 }
645
646 #[test]
647 fn format_pod_is_a_well_formed_object() {
648 let fmt = RingFormat::raw(V4L2_PIX_FMT_YUV420, 1280, 720, 30, 1).unwrap();
649 let (ty, id, props) = parse_object(&format_pod(&fmt, Some(SPA_VIDEO_FORMAT_I420)));
650 assert_eq!((ty, id), (SPA_TYPE_OBJECT_FORMAT, SPA_PARAM_ENUM_FORMAT));
651 let get = |k: u32| props.iter().find(|p| p.0 == k).cloned().unwrap();
652 assert_eq!(get(SPA_FORMAT_MEDIA_TYPE).1, SPA_TYPE_ID);
653 assert_eq!(u32_at(&get(SPA_FORMAT_MEDIA_TYPE).2, 0), SPA_MEDIA_TYPE_VIDEO);
654 assert_eq!(u32_at(&get(SPA_FORMAT_MEDIA_SUBTYPE).2, 0), SPA_MEDIA_SUBTYPE_RAW);
655 assert_eq!(u32_at(&get(SPA_FORMAT_VIDEO_FORMAT).2, 0), SPA_VIDEO_FORMAT_I420);
656 let size = get(SPA_FORMAT_VIDEO_SIZE);
657 assert_eq!(size.1, SPA_TYPE_RECTANGLE);
658 assert_eq!((u32_at(&size.2, 0), u32_at(&size.2, 4)), (1280, 720));
659 let rate = get(SPA_FORMAT_VIDEO_FRAMERATE);
660 assert_eq!(rate.1, SPA_TYPE_FRACTION);
661 assert_eq!((u32_at(&rate.2, 0), u32_at(&rate.2, 4)), (30, 1));
662 }
663
664 #[test]
665 fn mjpg_format_pod_names_no_pixel_format() {
666 let fmt = RingFormat::compressed(V4L2_PIX_FMT_MJPEG, 640, 480, 30, 1).unwrap();
667 let (ty, id, props) = parse_object(&format_pod(&fmt, None));
668 assert_eq!((ty, id), (SPA_TYPE_OBJECT_FORMAT, SPA_PARAM_ENUM_FORMAT));
669 let get = |k: u32| props.iter().find(|p| p.0 == k).cloned();
670 assert_eq!(u32_at(&get(SPA_FORMAT_MEDIA_SUBTYPE).unwrap().2, 0), SPA_MEDIA_SUBTYPE_MJPG);
671 assert!(get(SPA_FORMAT_VIDEO_FORMAT).is_none());
672 let size = get(SPA_FORMAT_VIDEO_SIZE).unwrap();
673 assert_eq!((u32_at(&size.2, 0), u32_at(&size.2, 4)), (640, 480));
674 let (_, _, bprops) = parse_object(&buffers_pod(&fmt));
675 let bget = |k: u32| bprops.iter().find(|p| p.0 == k).cloned().unwrap();
676 assert_eq!(u32_at(&bget(SPA_PARAM_BUFFERS_SIZE).2, 0), 640 * 480 * 2);
677 assert_eq!(u32_at(&bget(SPA_PARAM_BUFFERS_STRIDE).2, 0), 0);
678 }
679
680 #[test]
681 fn buffers_pod_carries_frame_geometry() {
682 let fmt = RingFormat::raw(V4L2_PIX_FMT_YUYV, 640, 480, 30, 1).unwrap();
683 let (ty, id, props) = parse_object(&buffers_pod(&fmt));
684 assert_eq!((ty, id), (SPA_TYPE_OBJECT_PARAM_BUFFERS, SPA_PARAM_BUFFERS));
685 let get = |k: u32| props.iter().find(|p| p.0 == k).cloned().unwrap();
686 assert_eq!((get(SPA_PARAM_BUFFERS_SIZE).1, u32_at(&get(SPA_PARAM_BUFFERS_SIZE).2, 0)), (SPA_TYPE_INT, 640 * 480 * 2));
687 assert_eq!(u32_at(&get(SPA_PARAM_BUFFERS_STRIDE).2, 0), 1280);
688 assert_eq!(u32_at(&get(SPA_PARAM_BUFFERS_BLOCKS).2, 0), 1);
689 let buffers = get(SPA_PARAM_BUFFERS_BUFFERS);
690 assert_eq!(buffers.1, SPA_TYPE_CHOICE);
691 assert_eq!(u32_at(&buffers.2, 0), SPA_CHOICE_RANGE);
692 assert_eq!((u32_at(&buffers.2, 8), u32_at(&buffers.2, 12)), (4, SPA_TYPE_INT));
693 assert_eq!(&buffers.2[16..28], &[4u8, 0, 0, 0, 2, 0, 0, 0, 8, 0, 0, 0]);
694 let dt = get(SPA_PARAM_BUFFERS_DATATYPE);
695 assert_eq!(u32_at(&dt.2, 0), SPA_CHOICE_FLAGS);
696 assert_eq!(u32_at(&dt.2, 16), (1 << SPA_DATA_MEMFD) | (1 << SPA_DATA_MEMPTR));
697 let (mty, mid, mprops) = parse_object(&meta_pod());
698 assert_eq!((mty, mid), (SPA_TYPE_OBJECT_PARAM_META, SPA_PARAM_META));
699 assert_eq!(u32_at(&mprops[0].2, 0), SPA_META_HEADER);
700 assert_eq!(u32_at(&mprops[1].2, 0), SPA_META_HEADER_SIZE);
701 }
702
703 #[test]
704 fn struct_layouts_match_libpipewire() {
705 assert_eq!(mem::size_of::<PwStreamEvents>(), 96);
706 assert_eq!(mem::size_of::<PwBuffer>(), 40);
707 assert_eq!(mem::size_of::<SpaBuffer>(), 24);
708 assert_eq!(mem::size_of::<SpaData>(), 40);
709 assert_eq!(mem::size_of::<SpaChunk>(), 16);
710 assert_eq!(mem::size_of::<SpaMetaHeader>(), 32);
711 assert_eq!(mem::offset_of!(PwStreamEvents, process), 64);
712 assert_eq!(mem::offset_of!(SpaData, data), 24);
713 assert_eq!(mem::offset_of!(SpaData, chunk), 32);
714 }
715}