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,
|
||||
transform: Transform,
|
||||
) -> Result<Self> {
|
||||
tracing::info!(
|
||||
"EncState::new: {width}x{height} enc={enc_width}x{enc_height} transform={transform:?}"
|
||||
);
|
||||
// 1. VAAPI 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)
|
||||
// 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.
|
||||
// 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
|
||||
// (verified on both 6.1.3 and 8.0). The encoder's own repeat_pps option is the correct approach.
|
||||
// Note: repeat_pps is only available in FFmpeg 7.0+ (not in 6.x). On older FFmpeg,
|
||||
// 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.
|
||||
{
|
||||
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)
|
||||
};
|
||||
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.
|
||||
tracing::debug!("EncState::new: opening encoder...");
|
||||
let opened = enc
|
||||
.open()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to open h264_vaapi encoder: {e}"))?;
|
||||
let enc_video = opened.0;
|
||||
tracing::debug!("EncState::new: encoder opened");
|
||||
|
||||
// 5. Filter graph (inline)
|
||||
tracing::debug!("EncState::new: building filter graph...");
|
||||
let video_filter = build_filter_graph(
|
||||
&hw_device_ctx,
|
||||
&frames_rgb,
|
||||
@@ -474,11 +481,11 @@ fn build_filter_graph(
|
||||
ff::filter::find("buffer").ok_or_else(|| anyhow::anyhow!("filter 'buffer' not found"))?;
|
||||
let buffersink = ff::filter::find("buffersink")
|
||||
.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")
|
||||
.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
|
||||
let args = format!(
|
||||
"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,
|
||||
);
|
||||
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.
|
||||
let par = unsafe { ffi::av_buffersrc_parameters_alloc() };
|
||||
@@ -504,15 +512,12 @@ fn build_filter_graph(
|
||||
};
|
||||
(*par).hw_frames_ctx = frames_rgb.ref_clone();
|
||||
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 {
|
||||
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)
|
||||
let mut scale_ctx = graph.add(&scale_vaapi, "scale", &format!("{width}:{height}"))?;
|
||||
// SAFETY: scale_vaapi needs hw_device_ctx for VAAPI device access.
|
||||
@@ -522,9 +527,10 @@ fn build_filter_graph(
|
||||
|
||||
// buffersink
|
||||
let mut sink_ctx = graph.add(&buffersink, "out", "")?;
|
||||
tracing::debug!("filter_graph: all filters added, linking...");
|
||||
|
||||
// Build filter chain: src -> format -> scale -> [transpose] -> sink
|
||||
src_ctx.link(0, &mut fmt_ctx, 0);
|
||||
// Build filter chain: src -> scale -> [transpose] -> sink
|
||||
src_ctx.link(0, &mut scale_ctx, 0);
|
||||
|
||||
match transform {
|
||||
Transform::Normal90 | Transform::Normal270 => {
|
||||
@@ -540,7 +546,6 @@ fn build_filter_graph(
|
||||
unsafe {
|
||||
(*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);
|
||||
trans_ctx.link(0, &mut sink_ctx, 0);
|
||||
}
|
||||
@@ -548,11 +553,9 @@ fn build_filter_graph(
|
||||
tracing::warn!(
|
||||
"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);
|
||||
}
|
||||
_ => {
|
||||
fmt_ctx.link(0, &mut scale_ctx, 0);
|
||||
scale_ctx.link(0, &mut sink_ctx, 0);
|
||||
}
|
||||
}
|
||||
@@ -560,6 +563,7 @@ fn build_filter_graph(
|
||||
graph
|
||||
.validate()
|
||||
.map_err(|e| anyhow::anyhow!("Filter graph validation failed: {e}"))?;
|
||||
tracing::debug!("filter_graph: graph validated");
|
||||
|
||||
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 {
|
||||
let fps = args.fps;
|
||||
let drm_device = args.drm_device.as_ref().map(PathBuf::from);
|
||||
Self {
|
||||
let mut state = Self {
|
||||
stage: EncConstructionStage::ProbingOutputs {
|
||||
outputs: Vec::new(),
|
||||
bound_outputs: Vec::new(),
|
||||
@@ -249,6 +249,134 @@ impl<S: CaptureSource> State<S> {
|
||||
qhandle,
|
||||
drm_device,
|
||||
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