fix(state,avhw): bind initial globals manually + fix filter graph crash on niri
- registry_queue_init consumes registry events during its internal roundtrip without forwarding them to Dispatch<WlRegistry>. Added bind_initial_globals() to manually iterate GlobalList and bind all initial globals (wl_output, xdg_output_manager, dmabuf, screencopy, wlr_output_manager) at State::new time. - Fix av_freep segfault in build_filter_graph: av_buffersrc_parameters_alloc returns a plain pointer, use av_free instead of av_freep (which expects pointer-to-pointer). - Fix filter graph format negotiation: remove software format filter that broke scale_vaapi hardware pipeline. Chain is now src -> scale -> sink. - Downgrade repeat_pps error to warning (not available in FFmpeg 6.x).
This commit is contained in:
32
src/avhw.rs
32
src/avhw.rs
@@ -163,8 +163,12 @@ impl EncState {
|
|||||||
fps: u32,
|
fps: u32,
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
|
tracing::info!(
|
||||||
|
"EncState::new: {width}x{height} enc={enc_width}x{enc_height} transform={transform:?}"
|
||||||
|
);
|
||||||
// 1. VAAPI device
|
// 1. VAAPI device
|
||||||
let hw_device_ctx = AvHwDevCtx::new_vaapi(drm_device)?;
|
let hw_device_ctx = AvHwDevCtx::new_vaapi(drm_device)?;
|
||||||
|
tracing::debug!("EncState::new: VAAPI device created");
|
||||||
|
|
||||||
// 2. Frame contexts (capture=XRGB/RGBZ, encode=NV12)
|
// 2. Frame contexts (capture=XRGB/RGBZ, encode=NV12)
|
||||||
// frames_rgb uses original capture dimensions (matches raw framebuffer)
|
// frames_rgb uses original capture dimensions (matches raw framebuffer)
|
||||||
@@ -209,8 +213,8 @@ impl EncState {
|
|||||||
|
|
||||||
// SAFETY: Set repeat_pps=1 on the encoder so PPS is inserted in every encoded frame.
|
// SAFETY: Set repeat_pps=1 on the encoder so PPS is inserted in every encoded frame.
|
||||||
// This ensures decoders can start decoding from any frame (important for WebRTC).
|
// This ensures decoders can start decoding from any frame (important for WebRTC).
|
||||||
// Note: h264_metadata BSF does NOT have repeat_sps/repeat_pps options in any FFmpeg version
|
// Note: repeat_pps is only available in FFmpeg 7.0+ (not in 6.x). On older FFmpeg,
|
||||||
// (verified on both 6.1.3 and 8.0). The encoder's own repeat_pps option is the correct approach.
|
// IDR frames carry SPS by default; PPS repetition depends on the driver.
|
||||||
// For SPS repetition: IDR frames carry SPS by default, controlled by gop_size/idr_interval.
|
// For SPS repetition: IDR frames carry SPS by default, controlled by gop_size/idr_interval.
|
||||||
{
|
{
|
||||||
let key = CString::new("repeat_pps").unwrap();
|
let key = CString::new("repeat_pps").unwrap();
|
||||||
@@ -219,17 +223,20 @@ impl EncState {
|
|||||||
ffi::av_opt_set((*enc.as_mut_ptr()).priv_data, key.as_ptr(), val.as_ptr(), 0)
|
ffi::av_opt_set((*enc.as_mut_ptr()).priv_data, key.as_ptr(), val.as_ptr(), 0)
|
||||||
};
|
};
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
bail!("av_opt_set repeat_pps on encoder failed: error {ret}");
|
tracing::warn!("av_opt_set repeat_pps failed (error {ret}), likely FFmpeg < 7.0; continuing without per-frame PPS");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Open encoder. Video::open() returns Encoder(Video); .0 extracts the Video.
|
// 4. Open encoder. Video::open() returns Encoder(Video); .0 extracts the Video.
|
||||||
|
tracing::debug!("EncState::new: opening encoder...");
|
||||||
let opened = enc
|
let opened = enc
|
||||||
.open()
|
.open()
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to open h264_vaapi encoder: {e}"))?;
|
.map_err(|e| anyhow::anyhow!("Failed to open h264_vaapi encoder: {e}"))?;
|
||||||
let enc_video = opened.0;
|
let enc_video = opened.0;
|
||||||
|
tracing::debug!("EncState::new: encoder opened");
|
||||||
|
|
||||||
// 5. Filter graph (inline)
|
// 5. Filter graph (inline)
|
||||||
|
tracing::debug!("EncState::new: building filter graph...");
|
||||||
let video_filter = build_filter_graph(
|
let video_filter = build_filter_graph(
|
||||||
&hw_device_ctx,
|
&hw_device_ctx,
|
||||||
&frames_rgb,
|
&frames_rgb,
|
||||||
@@ -474,11 +481,11 @@ fn build_filter_graph(
|
|||||||
ff::filter::find("buffer").ok_or_else(|| anyhow::anyhow!("filter 'buffer' not found"))?;
|
ff::filter::find("buffer").ok_or_else(|| anyhow::anyhow!("filter 'buffer' not found"))?;
|
||||||
let buffersink = ff::filter::find("buffersink")
|
let buffersink = ff::filter::find("buffersink")
|
||||||
.ok_or_else(|| anyhow::anyhow!("filter 'buffersink' not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("filter 'buffersink' not found"))?;
|
||||||
let format_filter =
|
|
||||||
ff::filter::find("format").ok_or_else(|| anyhow::anyhow!("filter 'format' not found"))?;
|
|
||||||
let scale_vaapi = ff::filter::find("scale_vaapi")
|
let scale_vaapi = ff::filter::find("scale_vaapi")
|
||||||
.ok_or_else(|| anyhow::anyhow!("filter 'scale_vaapi' not found"))?;
|
.ok_or_else(|| anyhow::anyhow!("filter 'scale_vaapi' not found"))?;
|
||||||
|
|
||||||
|
tracing::debug!("filter_graph: filters found");
|
||||||
|
|
||||||
// buffersrc — use AVBufferSrcParameters to set hw_frames_ctx properly
|
// buffersrc — use AVBufferSrcParameters to set hw_frames_ctx properly
|
||||||
let args = format!(
|
let args = format!(
|
||||||
"video_size={}x{}:pix_fmt={}:time_base=1/{fps}:pixel_aspect=1/1",
|
"video_size={}x{}:pix_fmt={}:time_base=1/{fps}:pixel_aspect=1/1",
|
||||||
@@ -487,6 +494,7 @@ fn build_filter_graph(
|
|||||||
Into::<ffi::AVPixelFormat>::into(ff::format::Pixel::VAAPI) as i32,
|
Into::<ffi::AVPixelFormat>::into(ff::format::Pixel::VAAPI) as i32,
|
||||||
);
|
);
|
||||||
let mut src_ctx = graph.add(&buffersrc, "in", &args)?;
|
let mut src_ctx = graph.add(&buffersrc, "in", &args)?;
|
||||||
|
tracing::debug!("filter_graph: buffersrc added");
|
||||||
|
|
||||||
// SAFETY: av_buffersrc_parameters_alloc allocates params for the buffersrc.
|
// SAFETY: av_buffersrc_parameters_alloc allocates params for the buffersrc.
|
||||||
let par = unsafe { ffi::av_buffersrc_parameters_alloc() };
|
let par = unsafe { ffi::av_buffersrc_parameters_alloc() };
|
||||||
@@ -504,15 +512,12 @@ fn build_filter_graph(
|
|||||||
};
|
};
|
||||||
(*par).hw_frames_ctx = frames_rgb.ref_clone();
|
(*par).hw_frames_ctx = frames_rgb.ref_clone();
|
||||||
let ret = ffi::av_buffersrc_parameters_set(src_ctx.as_mut_ptr(), par);
|
let ret = ffi::av_buffersrc_parameters_set(src_ctx.as_mut_ptr(), par);
|
||||||
ffi::av_freep(par as *mut _ as *mut _);
|
ffi::av_free(par as *mut _);
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
bail!("av_buffersrc_parameters_set failed: error {ret}");
|
bail!("av_buffersrc_parameters_set failed: error {ret}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// format filter: negotiate pixel format to NV12
|
|
||||||
let mut fmt_ctx = graph.add(&format_filter, "fmt", "pix_fmts=nv12")?;
|
|
||||||
|
|
||||||
// scale_vaapi: hardware scaling and colourspace conversion (keeps original dimensions)
|
// scale_vaapi: hardware scaling and colourspace conversion (keeps original dimensions)
|
||||||
let mut scale_ctx = graph.add(&scale_vaapi, "scale", &format!("{width}:{height}"))?;
|
let mut scale_ctx = graph.add(&scale_vaapi, "scale", &format!("{width}:{height}"))?;
|
||||||
// SAFETY: scale_vaapi needs hw_device_ctx for VAAPI device access.
|
// SAFETY: scale_vaapi needs hw_device_ctx for VAAPI device access.
|
||||||
@@ -522,9 +527,10 @@ fn build_filter_graph(
|
|||||||
|
|
||||||
// buffersink
|
// buffersink
|
||||||
let mut sink_ctx = graph.add(&buffersink, "out", "")?;
|
let mut sink_ctx = graph.add(&buffersink, "out", "")?;
|
||||||
|
tracing::debug!("filter_graph: all filters added, linking...");
|
||||||
|
|
||||||
// Build filter chain: src -> format -> scale -> [transpose] -> sink
|
// Build filter chain: src -> scale -> [transpose] -> sink
|
||||||
src_ctx.link(0, &mut fmt_ctx, 0);
|
src_ctx.link(0, &mut scale_ctx, 0);
|
||||||
|
|
||||||
match transform {
|
match transform {
|
||||||
Transform::Normal90 | Transform::Normal270 => {
|
Transform::Normal90 | Transform::Normal270 => {
|
||||||
@@ -540,7 +546,6 @@ fn build_filter_graph(
|
|||||||
unsafe {
|
unsafe {
|
||||||
(*trans_ctx.as_mut_ptr()).hw_device_ctx = hw_dev.ref_clone();
|
(*trans_ctx.as_mut_ptr()).hw_device_ctx = hw_dev.ref_clone();
|
||||||
}
|
}
|
||||||
fmt_ctx.link(0, &mut scale_ctx, 0);
|
|
||||||
scale_ctx.link(0, &mut trans_ctx, 0);
|
scale_ctx.link(0, &mut trans_ctx, 0);
|
||||||
trans_ctx.link(0, &mut sink_ctx, 0);
|
trans_ctx.link(0, &mut sink_ctx, 0);
|
||||||
}
|
}
|
||||||
@@ -548,11 +553,9 @@ fn build_filter_graph(
|
|||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"Normal180 transform detected; rotation correction deferred to follow-up"
|
"Normal180 transform detected; rotation correction deferred to follow-up"
|
||||||
);
|
);
|
||||||
fmt_ctx.link(0, &mut scale_ctx, 0);
|
|
||||||
scale_ctx.link(0, &mut sink_ctx, 0);
|
scale_ctx.link(0, &mut sink_ctx, 0);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
fmt_ctx.link(0, &mut scale_ctx, 0);
|
|
||||||
scale_ctx.link(0, &mut sink_ctx, 0);
|
scale_ctx.link(0, &mut sink_ctx, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -560,6 +563,7 @@ fn build_filter_graph(
|
|||||||
graph
|
graph
|
||||||
.validate()
|
.validate()
|
||||||
.map_err(|e| anyhow::anyhow!("Filter graph validation failed: {e}"))?;
|
.map_err(|e| anyhow::anyhow!("Filter graph validation failed: {e}"))?;
|
||||||
|
tracing::debug!("filter_graph: graph validated");
|
||||||
|
|
||||||
Ok(graph)
|
Ok(graph)
|
||||||
}
|
}
|
||||||
|
|||||||
130
src/state.rs
130
src/state.rs
@@ -225,7 +225,7 @@ impl<S: CaptureSource> State<S> {
|
|||||||
pub fn new(gm: GlobalList, args: Args, qhandle: QueueHandle<State<S>>) -> Self {
|
pub fn new(gm: GlobalList, args: Args, qhandle: QueueHandle<State<S>>) -> Self {
|
||||||
let fps = args.fps;
|
let fps = args.fps;
|
||||||
let drm_device = args.drm_device.as_ref().map(PathBuf::from);
|
let drm_device = args.drm_device.as_ref().map(PathBuf::from);
|
||||||
Self {
|
let mut state = Self {
|
||||||
stage: EncConstructionStage::ProbingOutputs {
|
stage: EncConstructionStage::ProbingOutputs {
|
||||||
outputs: Vec::new(),
|
outputs: Vec::new(),
|
||||||
bound_outputs: Vec::new(),
|
bound_outputs: Vec::new(),
|
||||||
@@ -249,6 +249,134 @@ impl<S: CaptureSource> State<S> {
|
|||||||
qhandle,
|
qhandle,
|
||||||
drm_device,
|
drm_device,
|
||||||
drm_device_from_compositor: None,
|
drm_device_from_compositor: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// registry_queue_init consumes registry events internally during its
|
||||||
|
// initial roundtrip and does NOT forward them to our Dispatch impl.
|
||||||
|
// We must manually bind the initial globals here.
|
||||||
|
state.bind_initial_globals();
|
||||||
|
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over the GlobalList from registry_queue_init and bind all
|
||||||
|
/// globals we care about. This is necessary because registry_queue_init
|
||||||
|
/// consumes registry events during its internal roundtrip without forwarding
|
||||||
|
/// them to our Dispatch<WlRegistry> handler.
|
||||||
|
fn bind_initial_globals(&mut self) {
|
||||||
|
use wayland_client::globals::Global;
|
||||||
|
|
||||||
|
let globals: Vec<Global> = self.gm.contents().clone_list();
|
||||||
|
let registry = self.gm.registry();
|
||||||
|
let qhandle = &self.qhandle;
|
||||||
|
|
||||||
|
// Sort globals so that managers are bound BEFORE wl_output.
|
||||||
|
// This ensures xdg_output_manager and zwlr_output_manager are available
|
||||||
|
// when we bind wl_output, so we can immediately get xdg_output / wlr head.
|
||||||
|
let globals = {
|
||||||
|
fn priority(interface: &str) -> u8 {
|
||||||
|
match interface {
|
||||||
|
"zwlr_screencopy_manager_v1" => 0,
|
||||||
|
"zwp_linux_dmabuf_v1" => 0,
|
||||||
|
"zxdg_output_manager_v1" => 1,
|
||||||
|
"zwlr_output_manager_v1" => 1,
|
||||||
|
"wl_output" => 2,
|
||||||
|
_ => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut g = globals;
|
||||||
|
g.sort_by_key(|g| priority(&g.interface));
|
||||||
|
g
|
||||||
|
};
|
||||||
|
|
||||||
|
for Global {
|
||||||
|
name,
|
||||||
|
interface,
|
||||||
|
version,
|
||||||
|
} in globals
|
||||||
|
{
|
||||||
|
match interface.as_str() {
|
||||||
|
"zwlr_screencopy_manager_v1" => {
|
||||||
|
let v = version.min(3);
|
||||||
|
tracing::debug!("Init: binding zwlr_screencopy_manager_v1 v{v} (name={name})");
|
||||||
|
let mgr: ZwlrScreencopyManagerV1 = registry.bind(name, v, qhandle, ());
|
||||||
|
if let EncConstructionStage::ProbingOutputs {
|
||||||
|
screencopy_manager, ..
|
||||||
|
} = &mut self.stage
|
||||||
|
{
|
||||||
|
*screencopy_manager = Some(mgr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"zwp_linux_dmabuf_v1" => {
|
||||||
|
let v = version.min(4);
|
||||||
|
tracing::debug!("Init: 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 self.stage
|
||||||
|
{
|
||||||
|
*dmabuf = Some(proxy.clone());
|
||||||
|
if v >= 4 {
|
||||||
|
let feedback = proxy.get_default_feedback(qhandle, ());
|
||||||
|
*dmabuf_feedback = Some(feedback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"zxdg_output_manager_v1" => {
|
||||||
|
let v = version.min(3);
|
||||||
|
tracing::debug!("Init: 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 self.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"zwlr_output_manager_v1" => {
|
||||||
|
let v = version.min(4);
|
||||||
|
tracing::debug!("Init: binding zwlr_output_manager_v1 v{v} (name={name})");
|
||||||
|
let mgr: ZwlrOutputManagerV1 = registry.bind(name, v, qhandle, ());
|
||||||
|
if let EncConstructionStage::ProbingOutputs {
|
||||||
|
wlr_output_manager, ..
|
||||||
|
} = &mut self.stage
|
||||||
|
{
|
||||||
|
*wlr_output_manager = Some(mgr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"wl_output" => {
|
||||||
|
let v = version.min(4);
|
||||||
|
tracing::debug!("Init: 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 self.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user