From 27aa8d2c651d49894f6d43657ff75f2413f06c6c Mon Sep 17 00:00:00 2001 From: dailz Date: Mon, 6 Apr 2026 16:23:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20convert=20PTS=20from=20from=20frame?= =?UTF-8?q?=E5=8F=B7=E5=8D=95=E4=BD=8D=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/state.rs | 277 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 173 insertions(+), 104 deletions(-) diff --git a/src/state.rs b/src/state.rs index 9211559..dca2c89 100644 --- a/src/state.rs +++ b/src/state.rs @@ -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 ffmpeg_next as ff; -use ffmpeg_next::ffi as ffi; +use ffmpeg_next::ffi; use crate::args::Args; use crate::avhw::{AvHwDevCtx, EncState}; @@ -153,6 +153,7 @@ pub struct State { pub stage: EncConstructionStage, pub in_flight_surface: InFlightSurface, pub starting_timestamp: Option, + pub first_frame: bool, pub args: Args, pub errored: bool, pub gm: GlobalList, @@ -168,7 +169,8 @@ pub struct State { /// Scan /dev/dri for the first available DRM render node (renderD*). fn find_drm_render_node() -> Option { - std::fs::read_dir("/dev/dri").ok()? + std::fs::read_dir("/dev/dri") + .ok()? .filter_map(|e| e.ok()) .filter(|e| { e.file_name() @@ -214,6 +216,7 @@ impl State { }, in_flight_surface: InFlightSurface::None, starting_timestamp: None, + first_frame: true, fps_limit: FpsLimit::new(fps), args, errored: false, @@ -268,9 +271,7 @@ impl State { let mut surface = ff::frame::Video::empty(); // SAFETY: frames_rgb_ctx is a valid AVHWFramesContext pointer; surface // is a freshly allocated empty Video frame. - let ret = unsafe { - ffi::av_hwframe_get_buffer(frames_rgb_ctx, surface.as_mut_ptr(), 0) - }; + let ret = unsafe { ffi::av_hwframe_get_buffer(frames_rgb_ctx, surface.as_mut_ptr(), 0) }; if ret < 0 { tracing::error!("av_hwframe_get_buffer failed: error {}", ret); self.errored = true; @@ -281,8 +282,7 @@ impl State { // SAFETY: Setting format to DRM_PRIME and calling av_hwframe_map creates // a mapped view of the GPU surface with DMA-BUF file descriptors. unsafe { - (*map_frame.as_mut_ptr()).format = - ffi::AVPixelFormat::AV_PIX_FMT_DRM_PRIME as i32; + (*map_frame.as_mut_ptr()).format = 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) }; if ret < 0 { @@ -294,8 +294,7 @@ impl State { // SAFETY: After av_hwframe_map with DRM_PRIME format, data[0] points to // a valid AVDRMFrameDescriptor. let desc: ff::ffi::AVDRMFrameDescriptor = unsafe { - let desc_ptr = - (*map_frame.as_ptr()).data[0] as *const ff::ffi::AVDRMFrameDescriptor; + let desc_ptr = (*map_frame.as_ptr()).data[0] as *const ff::ffi::AVDRMFrameDescriptor; std::ptr::read(desc_ptr) }; @@ -354,23 +353,23 @@ impl State { where S::Frame: Default, { - let (mut surface, _drm_map, frame, buffer) = match mem::replace( - &mut self.in_flight_surface, - InFlightSurface::None, - ) { - InFlightSurface::CopyQueued { - surface, - drm_map, - frame, - buffer, - } => (surface, drm_map, frame, buffer), - other => { - tracing::warn!("on_copy_complete: unexpected state"); - self.in_flight_surface = other; - return; - } - }; - let pts = (tv_sec as i64) * 1_000_000 + (tv_usec as i64); + let (mut surface, _drm_map, frame, buffer) = + match mem::replace(&mut self.in_flight_surface, InFlightSurface::None) { + InFlightSurface::CopyQueued { + surface, + drm_map, + frame, + buffer, + } => (surface, drm_map, frame, buffer), + other => { + tracing::warn!("on_copy_complete: unexpected state"); + 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) * fps + (tv_usec as i64) * fps / 1_000_000; surface.set_pts(Some(pts)); drop(buffer); let cap = match &mut self.stage { @@ -385,7 +384,14 @@ impl State { EncConstructionStage::Streaming { enc, .. } => enc, _ => 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 let Err(e) = enc.encode_frame(&surface) { tracing::error!("encode_frame failed: {}", e); @@ -441,7 +447,10 @@ impl State { }; tracing::info!( "Encoder initialized: {}x{} format={} bitrate={}", - width, height, format, bitrate + width, + height, + format, + bitrate ); self.stage = EncConstructionStage::Streaming { output_info, @@ -458,16 +467,21 @@ impl State { EncConstructionStage::ProbingOutputs { outputs, .. } => { let output_count = outputs.len(); 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 { Some(i) => Some(i), None => { let all_probed = outputs.iter().all(|o| o.done_count >= 2); if all_probed { - let available: Vec<&str> = outputs.iter() - .filter_map(|o| o.name.as_deref()) - .collect(); - tracing::error!("Output '{}' not found. Available outputs: {:?}", name, available); + let available: Vec<&str> = + outputs.iter().filter_map(|o| o.name.as_deref()).collect(); + tracing::error!( + "Output '{}' not found. Available outputs: {:?}", + name, + available + ); self.errored = true; } None @@ -589,7 +603,10 @@ impl State { tracing::info!("Selected output: {}", output_info.name); 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 { output_info, @@ -620,62 +637,81 @@ impl Dispatch for State { use wayland_client::protocol::wl_registry::Event as RegistryEvent; match event { - RegistryEvent::Global { name, interface, version } => { - match interface.as_str() { - "zwlr_screencopy_manager_v1" => { - let v = version.min(3); - 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); - } + RegistryEvent::Global { + name, + interface, + version, + } => match interface.as_str() { + "zwlr_screencopy_manager_v1" => { + let v = version.min(3); + 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 } => { tracing::debug!("Global removed: name={name}"); } @@ -688,12 +724,12 @@ impl Dispatch for State { // Dispatch // --------------------------------------------------------------------------- -impl Dispatch for State { +impl Dispatch for State { fn event( state: &mut Self, _proxy: &WlOutput, event: wayland_client::protocol::wl_output::Event, - _data: &(), + data: &OutputId, _conn: &wayland_client::Connection, _qhandle: &QueueHandle>, ) { @@ -701,15 +737,25 @@ impl Dispatch for State { use wayland_client::protocol::wl_output::Mode as WlMode; use wayland_client::protocol::wl_output::Transform as WlTransform; - let idx = match &mut state.stage { - EncConstructionStage::ProbingOutputs { outputs, .. } => { - outputs.len().saturating_sub(1) + let OutputId(target_name) = data; + let idx = match &state.stage { + 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 { - OutputEvent::Geometry { transform, physical_width, physical_height, .. } => { + OutputEvent::Geometry { + transform, + physical_width, + physical_height, + .. + } => { let t = match transform { wayland_client::WEnum::Value(WlTransform::Normal) => Transform::Normal, wayland_client::WEnum::Value(WlTransform::_90) => Transform::Normal90, @@ -728,7 +774,12 @@ impl Dispatch for State { } } } - OutputEvent::Mode { width, height, flags, .. } => { + OutputEvent::Mode { + width, + height, + flags, + .. + } => { let is_current = matches!(flags, wayland_client::WEnum::Value(WlMode::Current)); if is_current { if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage { @@ -843,20 +894,28 @@ impl Dispatch for State { DmabufFeedbackEvent::MainDevice { device } => { if device.len() >= 8 { let dev_bytes: [u8; 8] = device[..8].try_into().unwrap_or([0u8; 8]); - let dev_t = u64::from_ne_bytes(dev_bytes); - let minor = (dev_t as u32) & 0xFFFFF; + let dev = u64::from_ne_bytes(dev_bytes); + let minor = ((dev & 0xFF) | ((dev >> 12) & 0xFFFFFF00)) as u32; let path = PathBuf::from(format!("/dev/dri/renderD{}", minor)); 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); } else { tracing::warn!( "Compositor reported DRM device {} (dev_t: {}) but path does not exist", - path.display(), dev_t + path.display(), + dev ); } } 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 { .. } => {} @@ -910,12 +969,18 @@ impl Dispatch for State { ) { match event { 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(); state.errored = true; return; } - ScreencopyFrameEvent::LinuxDmabuf { format, width, height } => { + ScreencopyFrameEvent::LinuxDmabuf { + format, + width, + height, + } => { tracing::debug!("Screencopy LinuxDmabuf: format={format}, {width}x{height}"); if matches!(state.stage, EncConstructionStage::EverythingButFmt { .. }) { state.negotiate_format(format, width, height); @@ -928,7 +993,11 @@ impl Dispatch for State { } 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_usec = tv_nsec / 1000; tracing::trace!("Screencopy ready: tv_sec={tv_sec}, tv_usec={tv_usec}");