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 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<S: CaptureSource> {
pub stage: EncConstructionStage<S>,
pub in_flight_surface: InFlightSurface<S>,
pub starting_timestamp: Option<i64>,
pub first_frame: bool,
pub args: Args,
pub errored: bool,
pub gm: GlobalList,
@@ -168,7 +169,8 @@ pub struct State<S: CaptureSource> {
/// Scan /dev/dri for the first available DRM render node (renderD*).
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(|e| {
e.file_name()
@@ -214,6 +216,7 @@ impl<S: CaptureSource> State<S> {
},
in_flight_surface: InFlightSurface::None,
starting_timestamp: None,
first_frame: true,
fps_limit: FpsLimit::new(fps),
args,
errored: false,
@@ -268,9 +271,7 @@ impl<S: CaptureSource> State<S> {
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<S: CaptureSource> State<S> {
// 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<S: CaptureSource> State<S> {
// 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<S: CaptureSource> State<S> {
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<S: CaptureSource> State<S> {
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<S: CaptureSource> State<S> {
};
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<S: CaptureSource> State<S> {
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<S: CaptureSource> State<S> {
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<S: CaptureSource> Dispatch<WlRegistry, GlobalListContents> for State<S> {
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<S: CaptureSource> Dispatch<WlRegistry, GlobalListContents> for State<S> {
// Dispatch<WlOutput, ()>
// ---------------------------------------------------------------------------
impl<S: CaptureSource> Dispatch<WlOutput, ()> for State<S> {
impl<S: CaptureSource> Dispatch<WlOutput, OutputId> for State<S> {
fn event(
state: &mut Self,
_proxy: &WlOutput,
event: wayland_client::protocol::wl_output::Event,
_data: &(),
data: &OutputId,
_conn: &wayland_client::Connection,
_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::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<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));
if is_current {
if let EncConstructionStage::ProbingOutputs { outputs, .. } = &mut state.stage {
@@ -843,20 +894,28 @@ impl<S: CaptureSource> Dispatch<ZwpLinuxDmabufFeedbackV1, ()> for State<S> {
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<ZwlrScreencopyFrameV1, ()> for State<CapWlrScreencopy> {
) {
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<ZwlrScreencopyFrameV1, ()> for State<CapWlrScreencopy> {
}
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}");