-
-
Notifications
You must be signed in to change notification settings - Fork 104
fix/feat: SystemTime instead of timestamp #178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,10 +5,10 @@ use std::{ | |
| mpsc::{self, sync_channel, SyncSender}, | ||
| }, | ||
| thread::JoinHandle, | ||
| time::Duration, | ||
| time::{Duration, SystemTime}, | ||
| }; | ||
|
|
||
| use pipewire as pw; | ||
| use pipewire::{self as pw, spa::sys::spa_meta_type, sys::pw_buffer}; | ||
| use pw::{ | ||
| context::Context, | ||
| main_loop::MainLoop, | ||
|
|
@@ -31,7 +31,7 @@ use pw::{ | |
|
|
||
| use crate::{ | ||
| capturer::Options, | ||
| frame::{BGRxFrame, Frame, RGBFrame, RGBxFrame, XBGRFrame}, | ||
| frame::{BGRxFrame, Frame, RGBFrame, RGBxFrame, VideoFrame, XBGRFrame}, | ||
| }; | ||
|
|
||
| use self::{error::LinCapError, portal::ScreenCastPortal}; | ||
|
|
@@ -91,38 +91,68 @@ fn state_changed_callback( | |
| } | ||
| } | ||
|
|
||
| unsafe fn get_timestamp(buffer: *mut spa_buffer) -> i64 { | ||
| unsafe fn find_meta_in_buffer<T: Copy>(buffer: *mut spa_buffer, type_: spa_meta_type) -> Option<T> { | ||
| let n_metas = (*buffer).n_metas; | ||
| if n_metas > 0 { | ||
| let mut meta_ptr = (*buffer).metas; | ||
| let metas_end = (*buffer).metas.wrapping_add(n_metas as usize); | ||
| while meta_ptr != metas_end { | ||
| if (*meta_ptr).type_ == SPA_META_Header { | ||
| let meta_header: &mut spa_meta_header = | ||
| &mut *((*meta_ptr).data as *mut spa_meta_header); | ||
| return meta_header.pts; | ||
| } | ||
| meta_ptr = meta_ptr.wrapping_add(1); | ||
| let mut meta_ptr = (*buffer).metas; | ||
| let metas_end = (*buffer).metas.wrapping_add(n_metas as usize); | ||
|
|
||
| while meta_ptr != metas_end { | ||
| if (*meta_ptr).type_ == type_ { | ||
| let target: T = *((*meta_ptr).data as *mut T); | ||
|
|
||
| return Some(target); | ||
| } | ||
| 0 | ||
|
|
||
| meta_ptr = meta_ptr.wrapping_add(1); | ||
| } | ||
|
|
||
| None | ||
| } | ||
|
|
||
| unsafe fn get_timestamp_and_sequence(buffer: *mut spa_buffer) -> (i64, u64) { | ||
| let meta_header: Option<spa_meta_header> = find_meta_in_buffer(buffer, SPA_META_Header); | ||
|
|
||
| if let Some(meta_header) = meta_header { | ||
| (meta_header.pts, meta_header.seq) | ||
| } else { | ||
| 0 | ||
| (0, 0) | ||
| } | ||
| } | ||
|
|
||
| unsafe fn find_most_recent_buffer(stream: &StreamRef) -> *mut pw_buffer { | ||
| let mut buffer: *mut pw_buffer = std::ptr::null_mut(); | ||
|
|
||
| loop { | ||
| let tmp = stream.dequeue_raw_buffer(); | ||
| if tmp.is_null() { | ||
| break; | ||
| } | ||
|
|
||
| if !buffer.is_null() { | ||
| stream.queue_raw_buffer(buffer); | ||
| } | ||
|
|
||
| buffer = tmp; | ||
| } | ||
|
|
||
| buffer | ||
| } | ||
|
|
||
| fn process_callback(stream: &StreamRef, user_data: &mut ListenerUserData) { | ||
| let buffer = unsafe { stream.dequeue_raw_buffer() }; | ||
| if !buffer.is_null() { | ||
| let pw_buffer = unsafe { find_most_recent_buffer(stream) }; | ||
| if !pw_buffer.is_null() { | ||
| 'outside: { | ||
|
Comment on lines
+142
to
144
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Null pw_buffer re-queued when no buffers available. When find_most_recent_buffer returns null, queue_raw_buffer(pw_buffer) still runs, passing null into FFI. Apply this guard: - unsafe { stream.queue_raw_buffer(pw_buffer) };
+ if !pw_buffer.is_null() {
+ unsafe { stream.queue_raw_buffer(pw_buffer) };
+ }Also applies to: 207-207 🤖 Prompt for AI Agents |
||
| let buffer = unsafe { (*buffer).buffer }; | ||
| let buffer = unsafe { (*pw_buffer).buffer }; | ||
| if buffer.is_null() { | ||
| break 'outside; | ||
| } | ||
| let timestamp = unsafe { get_timestamp(buffer) }; | ||
|
|
||
| let (timestamp, sequence) = unsafe { get_timestamp_and_sequence(buffer) }; | ||
| let timestamp = SystemTime::UNIX_EPOCH + Duration::from_nanos(timestamp as u64); | ||
|
|
||
| let n_datas = unsafe { (*buffer).n_datas }; | ||
| if n_datas < 1 { | ||
| return; | ||
| break 'outside; | ||
| } | ||
| let frame_size = user_data.format.size(); | ||
| let frame_data: Vec<u8> = unsafe { | ||
|
|
@@ -134,30 +164,38 @@ fn process_callback(stream: &StreamRef, user_data: &mut ListenerUserData) { | |
| }; | ||
|
|
||
| if let Err(e) = match user_data.format.format() { | ||
| VideoFormat::RGBx => user_data.tx.send(Frame::RGBx(RGBxFrame { | ||
| display_time: timestamp as u64, | ||
| VideoFormat::RGBx => user_data.tx.send(Frame::Video(VideoFrame::RGBx(RGBxFrame { | ||
| display_time: timestamp, | ||
| processed_time: SystemTime::now(), | ||
| sequence, | ||
| width: frame_size.width as i32, | ||
| height: frame_size.height as i32, | ||
| data: frame_data, | ||
| })), | ||
| VideoFormat::RGB => user_data.tx.send(Frame::RGB(RGBFrame { | ||
| display_time: timestamp as u64, | ||
| }))), | ||
| VideoFormat::RGB => user_data.tx.send(Frame::Video(VideoFrame::RGB(RGBFrame { | ||
| display_time: timestamp, | ||
| processed_time: SystemTime::now(), | ||
| sequence, | ||
| width: frame_size.width as i32, | ||
| height: frame_size.height as i32, | ||
| data: frame_data, | ||
| })), | ||
| VideoFormat::xBGR => user_data.tx.send(Frame::XBGR(XBGRFrame { | ||
| display_time: timestamp as u64, | ||
| }))), | ||
| VideoFormat::xBGR => user_data.tx.send(Frame::Video(VideoFrame::XBGR(XBGRFrame { | ||
| display_time: timestamp, | ||
| processed_time: SystemTime::now(), | ||
| sequence, | ||
| width: frame_size.width as i32, | ||
| height: frame_size.height as i32, | ||
| data: frame_data, | ||
| })), | ||
| VideoFormat::BGRx => user_data.tx.send(Frame::BGRx(BGRxFrame { | ||
| display_time: timestamp as u64, | ||
| }))), | ||
| VideoFormat::BGRx => user_data.tx.send(Frame::Video(VideoFrame::BGRx(BGRxFrame { | ||
| display_time: timestamp, | ||
| processed_time: SystemTime::now(), | ||
| sequence, | ||
| width: frame_size.width as i32, | ||
| height: frame_size.height as i32, | ||
| data: frame_data, | ||
| })), | ||
| }))), | ||
| _ => panic!("Unsupported frame format received"), | ||
|
Comment on lines
166
to
199
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainMinor: compute processed_time once per frame; also handle RGBA or remove it from negotiation.
Apply: - if let Err(e) = match user_data.format.format() {
- VideoFormat::RGBx => user_data.tx.send(Frame::Video(VideoFrame::RGBx(RGBxFrame {
- display_time: timestamp,
- processed_time: SystemTime::now(),
+ let processed = SystemTime::now();
+ if let Err(e) = match user_data.format.format() {
+ VideoFormat::RGBx => user_data.tx.send(Frame::Video(VideoFrame::RGBx(RGBxFrame {
+ display_time: timestamp,
+ processed_time: processed,
sequence,
width: frame_size.width as i32,
height: frame_size.height as i32,
data: frame_data,
}))),
- VideoFormat::RGB => user_data.tx.send(Frame::Video(VideoFrame::RGB(RGBFrame {
- display_time: timestamp,
- processed_time: SystemTime::now(),
+ VideoFormat::RGB => user_data.tx.send(Frame::Video(VideoFrame::RGB(RGBFrame {
+ display_time: timestamp,
+ processed_time: processed,
sequence,
width: frame_size.width as i32,
height: frame_size.height as i32,
data: frame_data,
}))),
- VideoFormat::xBGR => user_data.tx.send(Frame::Video(VideoFrame::XBGR(XBGRFrame {
- display_time: timestamp,
- processed_time: SystemTime::now(),
+ VideoFormat::xBGR => user_data.tx.send(Frame::Video(VideoFrame::XBGR(XBGRFrame {
+ display_time: timestamp,
+ processed_time: processed,
sequence,
width: frame_size.width as i32,
height: frame_size.height as i32,
data: frame_data,
}))),
- VideoFormat::BGRx => user_data.tx.send(Frame::Video(VideoFrame::BGRx(BGRxFrame {
- display_time: timestamp,
- processed_time: SystemTime::now(),
+ VideoFormat::BGRx => user_data.tx.send(Frame::Video(VideoFrame::BGRx(BGRxFrame {
+ display_time: timestamp,
+ processed_time: processed,
sequence,
width: frame_size.width as i32,
height: frame_size.height as i32,
data: frame_data,
}))),
_ => panic!("Unsupported frame format received"),
} {Additionally, either add a VideoFormat::RGBA branch or remove RGBA from the negotiated formats to prevent panic. Example (remove RGBA): - pw::spa::param::video::VideoFormat::RGBA,I'll verify the claims in this review comment by examining the codebase. Length of output: 57 🏁 Script executed: #!/bin/bash
# Find the negotiation code and context around RGBA advertising
rg -n -B5 -A5 "pw::spa::param::video::VideoFormat" src/capturer/engine/linux/mod.rs | head -60Length of output: 524 🏁 Script executed: #!/bin/bash
# Get context around the match statement to confirm RGBA is not handled
cat src/capturer/engine/linux/mod.rs | sed -n '150,210p'Length of output: 2538 CRITICAL: RGBA negotiation/handling mismatch causes panic at runtime. The review comment is verified as accurate. RGBA is advertised in format negotiation (line 256) but not handled in the frame format match statement (lines 166–195), which will panic with "Unsupported frame format received" if RGBA is selected. The suggested fixes are correct:
Apply the provided diff to resolve both issues. 🤖 Prompt for AI Agents |
||
| } { | ||
| eprintln!("{e}"); | ||
|
|
@@ -166,8 +204,7 @@ fn process_callback(stream: &StreamRef, user_data: &mut ListenerUserData) { | |
| } else { | ||
| eprintln!("Out of buffers"); | ||
| } | ||
|
|
||
| unsafe { stream.queue_raw_buffer(buffer) }; | ||
| unsafe { stream.queue_raw_buffer(pw_buffer) }; | ||
| } | ||
|
|
||
| // TODO: Format negotiation | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UB risk: unchecked meta->data deref and missing size/alignment validation.
find_meta_in_buffer dereferences meta->data without verifying it's non-null or large enough for T, and assumes alignment. This can read invalid memory.
Apply this safer pattern:
📝 Committable suggestion
🤖 Prompt for AI Agents