fix: convert PTS from from frame号单位转换
This commit is contained in:
277
src/state.rs
277
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<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}");
|
||||
|
||||
Reference in New Issue
Block a user