fix: convert PTS from from frame号单位转换

This commit is contained in:
dailz
2026-04-06 16:23:59 +08:00
parent 1e8d00126b
commit 27aa8d2c65

View File

@@ -29,7 +29,7 @@ use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_frame_v1::{
use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1; use wayland_protocols_wlr::screencopy::v1::client::zwlr_screencopy_manager_v1::ZwlrScreencopyManagerV1;
use ffmpeg_next as ff; use ffmpeg_next as ff;
use ffmpeg_next::ffi as ffi; use ffmpeg_next::ffi;
use crate::args::Args; use crate::args::Args;
use crate::avhw::{AvHwDevCtx, EncState}; use crate::avhw::{AvHwDevCtx, EncState};
@@ -153,6 +153,7 @@ pub struct State<S: CaptureSource> {
pub stage: EncConstructionStage<S>, pub stage: EncConstructionStage<S>,
pub in_flight_surface: InFlightSurface<S>, pub in_flight_surface: InFlightSurface<S>,
pub starting_timestamp: Option<i64>, pub starting_timestamp: Option<i64>,
pub first_frame: bool,
pub args: Args, pub args: Args,
pub errored: bool, pub errored: bool,
pub gm: GlobalList, pub gm: GlobalList,
@@ -168,7 +169,8 @@ pub struct State<S: CaptureSource> {
/// Scan /dev/dri for the first available DRM render node (renderD*). /// Scan /dev/dri for the first available DRM render node (renderD*).
fn find_drm_render_node() -> Option<PathBuf> { fn find_drm_render_node() -> Option<PathBuf> {
std::fs::read_dir("/dev/dri").ok()? std::fs::read_dir("/dev/dri")
.ok()?
.filter_map(|e| e.ok()) .filter_map(|e| e.ok())
.filter(|e| { .filter(|e| {
e.file_name() e.file_name()
@@ -214,6 +216,7 @@ impl<S: CaptureSource> State<S> {
}, },
in_flight_surface: InFlightSurface::None, in_flight_surface: InFlightSurface::None,
starting_timestamp: None, starting_timestamp: None,
first_frame: true,
fps_limit: FpsLimit::new(fps), fps_limit: FpsLimit::new(fps),
args, args,
errored: false, errored: false,
@@ -268,9 +271,7 @@ impl<S: CaptureSource> State<S> {
let mut surface = ff::frame::Video::empty(); let mut surface = ff::frame::Video::empty();
// SAFETY: frames_rgb_ctx is a valid AVHWFramesContext pointer; surface // SAFETY: frames_rgb_ctx is a valid AVHWFramesContext pointer; surface
// is a freshly allocated empty Video frame. // is a freshly allocated empty Video frame.
let ret = unsafe { let ret = unsafe { ffi::av_hwframe_get_buffer(frames_rgb_ctx, surface.as_mut_ptr(), 0) };
ffi::av_hwframe_get_buffer(frames_rgb_ctx, surface.as_mut_ptr(), 0)
};
if ret < 0 { if ret < 0 {
tracing::error!("av_hwframe_get_buffer failed: error {}", ret); tracing::error!("av_hwframe_get_buffer failed: error {}", ret);
self.errored = true; self.errored = true;
@@ -281,8 +282,7 @@ impl<S: CaptureSource> State<S> {
// SAFETY: Setting format to DRM_PRIME and calling av_hwframe_map creates // SAFETY: Setting format to DRM_PRIME and calling av_hwframe_map creates
// a mapped view of the GPU surface with DMA-BUF file descriptors. // a mapped view of the GPU surface with DMA-BUF file descriptors.
unsafe { unsafe {
(*map_frame.as_mut_ptr()).format = (*map_frame.as_mut_ptr()).format = ffi::AVPixelFormat::AV_PIX_FMT_DRM_PRIME as i32;
ffi::AVPixelFormat::AV_PIX_FMT_DRM_PRIME as i32;
} }
let ret = unsafe { ffi::av_hwframe_map(map_frame.as_mut_ptr(), surface.as_ptr(), 0) }; let ret = unsafe { ffi::av_hwframe_map(map_frame.as_mut_ptr(), surface.as_ptr(), 0) };
if ret < 0 { if ret < 0 {
@@ -294,8 +294,7 @@ impl<S: CaptureSource> State<S> {
// SAFETY: After av_hwframe_map with DRM_PRIME format, data[0] points to // SAFETY: After av_hwframe_map with DRM_PRIME format, data[0] points to
// a valid AVDRMFrameDescriptor. // a valid AVDRMFrameDescriptor.
let desc: ff::ffi::AVDRMFrameDescriptor = unsafe { let desc: ff::ffi::AVDRMFrameDescriptor = unsafe {
let desc_ptr = let desc_ptr = (*map_frame.as_ptr()).data[0] as *const ff::ffi::AVDRMFrameDescriptor;
(*map_frame.as_ptr()).data[0] as *const ff::ffi::AVDRMFrameDescriptor;
std::ptr::read(desc_ptr) std::ptr::read(desc_ptr)
}; };
@@ -354,23 +353,23 @@ impl<S: CaptureSource> State<S> {
where where
S::Frame: Default, S::Frame: Default,
{ {
let (mut surface, _drm_map, frame, buffer) = match mem::replace( let (mut surface, _drm_map, frame, buffer) =
&mut self.in_flight_surface, match mem::replace(&mut self.in_flight_surface, InFlightSurface::None) {
InFlightSurface::None, InFlightSurface::CopyQueued {
) { surface,
InFlightSurface::CopyQueued { drm_map,
surface, frame,
drm_map, buffer,
frame, } => (surface, drm_map, frame, buffer),
buffer, other => {
} => (surface, drm_map, frame, buffer), tracing::warn!("on_copy_complete: unexpected state");
other => { self.in_flight_surface = other;
tracing::warn!("on_copy_complete: unexpected state"); return;
self.in_flight_surface = other; }
return; };
} let fps = self.args.fps as i64;
}; // PTS in frame-number units (encoder time_base = 1/fps)
let pts = (tv_sec as i64) * 1_000_000 + (tv_usec as i64); let pts = (tv_sec as i64) * fps + (tv_usec as i64) * fps / 1_000_000;
surface.set_pts(Some(pts)); surface.set_pts(Some(pts));
drop(buffer); drop(buffer);
let cap = match &mut self.stage { let cap = match &mut self.stage {
@@ -385,7 +384,14 @@ impl<S: CaptureSource> State<S> {
EncConstructionStage::Streaming { enc, .. } => enc, EncConstructionStage::Streaming { enc, .. } => enc,
_ => unreachable!("already checked Streaming above"), _ => unreachable!("already checked Streaming above"),
}; };
let should_encode = self.fps_limit.on_new_frame(S::Frame::default(), Instant::now()).is_some(); let should_encode = if self.first_frame {
self.first_frame = false;
true
} else {
self.fps_limit
.on_new_frame(S::Frame::default(), Instant::now())
.is_some()
};
if should_encode { if should_encode {
if let Err(e) = enc.encode_frame(&surface) { if let Err(e) = enc.encode_frame(&surface) {
tracing::error!("encode_frame failed: {}", e); tracing::error!("encode_frame failed: {}", e);
@@ -441,7 +447,10 @@ impl<S: CaptureSource> State<S> {
}; };
tracing::info!( tracing::info!(
"Encoder initialized: {}x{} format={} bitrate={}", "Encoder initialized: {}x{} format={} bitrate={}",
width, height, format, bitrate width,
height,
format,
bitrate
); );
self.stage = EncConstructionStage::Streaming { self.stage = EncConstructionStage::Streaming {
output_info, output_info,
@@ -458,16 +467,21 @@ impl<S: CaptureSource> State<S> {
EncConstructionStage::ProbingOutputs { outputs, .. } => { EncConstructionStage::ProbingOutputs { outputs, .. } => {
let output_count = outputs.len(); let output_count = outputs.len();
let idx = if let Some(ref name) = self.args.output_name { let idx = if let Some(ref name) = self.args.output_name {
let pos = outputs.iter().position(|o| o.name.as_deref() == Some(name.as_str())); let pos = outputs
.iter()
.position(|o| o.name.as_deref() == Some(name.as_str()));
match pos { match pos {
Some(i) => Some(i), Some(i) => Some(i),
None => { None => {
let all_probed = outputs.iter().all(|o| o.done_count >= 2); let all_probed = outputs.iter().all(|o| o.done_count >= 2);
if all_probed { if all_probed {
let available: Vec<&str> = outputs.iter() let available: Vec<&str> =
.filter_map(|o| o.name.as_deref()) outputs.iter().filter_map(|o| o.name.as_deref()).collect();
.collect(); tracing::error!(
tracing::error!("Output '{}' not found. Available outputs: {:?}", name, available); "Output '{}' not found. Available outputs: {:?}",
name,
available
);
self.errored = true; self.errored = true;
} }
None None
@@ -589,7 +603,10 @@ impl<S: CaptureSource> State<S> {
tracing::info!("Selected output: {}", output_info.name); tracing::info!("Selected output: {}", output_info.name);
if self.args.output_name.is_none() && output_count > 1 { if self.args.output_name.is_none() && output_count > 1 {
tracing::warn!("Multiple outputs found, using '{}'. Use --output-name to select.", output_info.name); tracing::warn!(
"Multiple outputs found, using '{}'. Use --output-name to select.",
output_info.name
);
} }
self.stage = EncConstructionStage::EverythingButFmt { self.stage = EncConstructionStage::EverythingButFmt {
output_info, output_info,
@@ -620,62 +637,81 @@ impl<S: CaptureSource> Dispatch<WlRegistry, GlobalListContents> for State<S> {
use wayland_client::protocol::wl_registry::Event as RegistryEvent; use wayland_client::protocol::wl_registry::Event as RegistryEvent;
match event { match event {
RegistryEvent::Global { name, interface, version } => { RegistryEvent::Global {
match interface.as_str() { name,
"zwlr_screencopy_manager_v1" => { interface,
let v = version.min(3); version,
tracing::debug!("Binding zwlr_screencopy_manager_v1 v{v} (name={name})"); } => match interface.as_str() {
let mgr: ZwlrScreencopyManagerV1 = registry.bind(name, v, qhandle, ()); "zwlr_screencopy_manager_v1" => {
if let EncConstructionStage::ProbingOutputs { screencopy_manager, .. } = &mut state.stage { let v = version.min(3);
*screencopy_manager = Some(mgr); tracing::debug!("Binding zwlr_screencopy_manager_v1 v{v} (name={name})");
} let mgr: ZwlrScreencopyManagerV1 = registry.bind(name, v, qhandle, ());
if let EncConstructionStage::ProbingOutputs {
screencopy_manager, ..
} = &mut state.stage
{
*screencopy_manager = Some(mgr);
} }
"zwp_linux_dmabuf_v1" => {
let v = version.min(4);
tracing::debug!("Binding zwp_linux_dmabuf_v1 v{v} (name={name})");
let proxy: ZwpLinuxDmabufV1 = registry.bind(name, v, qhandle, ());
if let EncConstructionStage::ProbingOutputs { dmabuf, dmabuf_feedback, .. } = &mut state.stage {
*dmabuf = Some(proxy.clone());
if v >= 4 {
let feedback = proxy.get_default_feedback(qhandle, ());
*dmabuf_feedback = Some(feedback);
}
}
}
"wl_output" => {
let v = version.min(4);
tracing::debug!("Binding wl_output v{v} (name={name})");
let output: WlOutput = registry.bind(name, v, qhandle, ());
if let EncConstructionStage::ProbingOutputs {
outputs, bound_outputs, output_names, xdg_output_manager, ..
} = &mut state.stage {
outputs.push(PartialOutputInfo::default());
bound_outputs.push(output.clone());
output_names.push(name);
if let Some(xdg_mgr) = xdg_output_manager {
let output_id = OutputId(name);
xdg_mgr.get_xdg_output(&output, qhandle, output_id);
}
}
}
"zxdg_output_manager_v1" => {
let v = version.min(3);
tracing::debug!("Binding zxdg_output_manager_v1 v{v} (name={name})");
let xdg_mgr: ZxdgOutputManagerV1 = registry.bind(name, v, qhandle, ());
if let EncConstructionStage::ProbingOutputs {
bound_outputs, xdg_output_manager, output_names, ..
} = &mut state.stage {
for (i, output) in bound_outputs.iter().enumerate() {
let oname = output_names.get(i).copied().unwrap_or(0);
let output_id = OutputId(oname);
xdg_mgr.get_xdg_output(output, qhandle, output_id);
}
*xdg_output_manager = Some(xdg_mgr);
}
}
_ => {}
} }
} "zwp_linux_dmabuf_v1" => {
let v = version.min(4);
tracing::debug!("Binding zwp_linux_dmabuf_v1 v{v} (name={name})");
let proxy: ZwpLinuxDmabufV1 = registry.bind(name, v, qhandle, ());
if let EncConstructionStage::ProbingOutputs {
dmabuf,
dmabuf_feedback,
..
} = &mut state.stage
{
*dmabuf = Some(proxy.clone());
if v >= 4 {
let feedback = proxy.get_default_feedback(qhandle, ());
*dmabuf_feedback = Some(feedback);
}
}
}
"wl_output" => {
let v = version.min(4);
tracing::debug!("Binding wl_output v{v} (name={name})");
let output: WlOutput = registry.bind(name, v, qhandle, OutputId(name));
if let EncConstructionStage::ProbingOutputs {
outputs,
bound_outputs,
output_names,
xdg_output_manager,
..
} = &mut state.stage
{
outputs.push(PartialOutputInfo::default());
bound_outputs.push(output.clone());
output_names.push(name);
if let Some(xdg_mgr) = xdg_output_manager {
let output_id = OutputId(name);
xdg_mgr.get_xdg_output(&output, qhandle, output_id);
}
}
}
"zxdg_output_manager_v1" => {
let v = version.min(3);
tracing::debug!("Binding zxdg_output_manager_v1 v{v} (name={name})");
let xdg_mgr: ZxdgOutputManagerV1 = registry.bind(name, v, qhandle, ());
if let EncConstructionStage::ProbingOutputs {
bound_outputs,
xdg_output_manager,
output_names,
..
} = &mut state.stage
{
for (i, output) in bound_outputs.iter().enumerate() {
let oname = output_names.get(i).copied().unwrap_or(0);
let output_id = OutputId(oname);
xdg_mgr.get_xdg_output(output, qhandle, output_id);
}
*xdg_output_manager = Some(xdg_mgr);
}
}
_ => {}
},
RegistryEvent::GlobalRemove { name } => { RegistryEvent::GlobalRemove { name } => {
tracing::debug!("Global removed: name={name}"); tracing::debug!("Global removed: name={name}");
} }
@@ -688,12 +724,12 @@ impl<S: CaptureSource> Dispatch<WlRegistry, GlobalListContents> for State<S> {
// Dispatch<WlOutput, ()> // Dispatch<WlOutput, ()>
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
impl<S: CaptureSource> Dispatch<WlOutput, ()> for State<S> { impl<S: CaptureSource> Dispatch<WlOutput, OutputId> for State<S> {
fn event( fn event(
state: &mut Self, state: &mut Self,
_proxy: &WlOutput, _proxy: &WlOutput,
event: wayland_client::protocol::wl_output::Event, event: wayland_client::protocol::wl_output::Event,
_data: &(), data: &OutputId,
_conn: &wayland_client::Connection, _conn: &wayland_client::Connection,
_qhandle: &QueueHandle<State<S>>, _qhandle: &QueueHandle<State<S>>,
) { ) {
@@ -701,15 +737,25 @@ impl<S: CaptureSource> Dispatch<WlOutput, ()> for State<S> {
use wayland_client::protocol::wl_output::Mode as WlMode; use wayland_client::protocol::wl_output::Mode as WlMode;
use wayland_client::protocol::wl_output::Transform as WlTransform; use wayland_client::protocol::wl_output::Transform as WlTransform;
let idx = match &mut state.stage { let OutputId(target_name) = data;
EncConstructionStage::ProbingOutputs { outputs, .. } => { let idx = match &state.stage {
outputs.len().saturating_sub(1) EncConstructionStage::ProbingOutputs { output_names, .. } => {
output_names.iter().position(|&n| n == *target_name)
} }
_ => return, _ => None,
};
let idx = match idx {
Some(i) => i,
None => return,
}; };
match event { match event {
OutputEvent::Geometry { transform, physical_width, physical_height, .. } => { OutputEvent::Geometry {
transform,
physical_width,
physical_height,
..
} => {
let t = match transform { let t = match transform {
wayland_client::WEnum::Value(WlTransform::Normal) => Transform::Normal, wayland_client::WEnum::Value(WlTransform::Normal) => Transform::Normal,
wayland_client::WEnum::Value(WlTransform::_90) => Transform::Normal90, wayland_client::WEnum::Value(WlTransform::_90) => Transform::Normal90,
@@ -728,7 +774,12 @@ impl<S: CaptureSource> Dispatch<WlOutput, ()> for State<S> {
} }
} }
} }
OutputEvent::Mode { width, height, flags, .. } => { OutputEvent::Mode {
width,
height,
flags,
..
} => {
let is_current = matches!(flags, wayland_client::WEnum::Value(WlMode::Current)); let is_current = matches!(flags, wayland_client::WEnum::Value(WlMode::Current));
if is_current { if is_current {
if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage {
@@ -843,20 +894,28 @@ impl<S: CaptureSource> Dispatch<ZwpLinuxDmabufFeedbackV1, ()> for State<S> {
DmabufFeedbackEvent::MainDevice { device } => { DmabufFeedbackEvent::MainDevice { device } => {
if device.len() >= 8 { if device.len() >= 8 {
let dev_bytes: [u8; 8] = device[..8].try_into().unwrap_or([0u8; 8]); let dev_bytes: [u8; 8] = device[..8].try_into().unwrap_or([0u8; 8]);
let dev_t = u64::from_ne_bytes(dev_bytes); let dev = u64::from_ne_bytes(dev_bytes);
let minor = (dev_t as u32) & 0xFFFFF; let minor = ((dev & 0xFF) | ((dev >> 12) & 0xFFFFFF00)) as u32;
let path = PathBuf::from(format!("/dev/dri/renderD{}", minor)); let path = PathBuf::from(format!("/dev/dri/renderD{}", minor));
if path.exists() { if path.exists() {
tracing::info!("Compositor DRM device: {} (dev_t: {})", path.display(), dev_t); tracing::info!(
"Compositor DRM device: {} (dev_t: {})",
path.display(),
dev
);
state.drm_device_from_compositor = Some(path); state.drm_device_from_compositor = Some(path);
} else { } else {
tracing::warn!( tracing::warn!(
"Compositor reported DRM device {} (dev_t: {}) but path does not exist", "Compositor reported DRM device {} (dev_t: {}) but path does not exist",
path.display(), dev_t path.display(),
dev
); );
} }
} else { } else {
tracing::warn!("main_device event with unexpected data length: {}", device.len()); tracing::warn!(
"main_device event with unexpected data length: {}",
device.len()
);
} }
} }
DmabufFeedbackEvent::FormatTable { .. } => {} DmabufFeedbackEvent::FormatTable { .. } => {}
@@ -910,12 +969,18 @@ impl Dispatch<ZwlrScreencopyFrameV1, ()> for State<CapWlrScreencopy> {
) { ) {
match event { match event {
ScreencopyFrameEvent::Buffer { .. } => { ScreencopyFrameEvent::Buffer { .. } => {
tracing::warn!("Received SHM Buffer event — only DMA-BUF capture is supported. Ignoring."); tracing::warn!(
"Received SHM Buffer event — only DMA-BUF capture is supported. Ignoring."
);
proxy.destroy(); proxy.destroy();
state.errored = true; state.errored = true;
return; return;
} }
ScreencopyFrameEvent::LinuxDmabuf { format, width, height } => { ScreencopyFrameEvent::LinuxDmabuf {
format,
width,
height,
} => {
tracing::debug!("Screencopy LinuxDmabuf: format={format}, {width}x{height}"); tracing::debug!("Screencopy LinuxDmabuf: format={format}, {width}x{height}");
if matches!(state.stage, EncConstructionStage::EverythingButFmt { .. }) { if matches!(state.stage, EncConstructionStage::EverythingButFmt { .. }) {
state.negotiate_format(format, width, height); state.negotiate_format(format, width, height);
@@ -928,7 +993,11 @@ impl Dispatch<ZwlrScreencopyFrameV1, ()> for State<CapWlrScreencopy> {
} }
state.on_frame_allocd((), format, width, height); state.on_frame_allocd((), format, width, height);
} }
ScreencopyFrameEvent::Ready { tv_sec_hi, tv_sec_lo, tv_nsec } => { ScreencopyFrameEvent::Ready {
tv_sec_hi,
tv_sec_lo,
tv_nsec,
} => {
let tv_sec = (tv_sec_hi as u64) << 32 | tv_sec_lo as u64; let tv_sec = (tv_sec_hi as u64) << 32 | tv_sec_lo as u64;
let tv_usec = tv_nsec / 1000; let tv_usec = tv_nsec / 1000;
tracing::trace!("Screencopy ready: tv_sec={tv_sec}, tv_usec={tv_usec}"); tracing::trace!("Screencopy ready: tv_sec={tv_sec}, tv_usec={tv_usec}");