diff --git a/src/avhw.rs b/src/avhw.rs index 1fb1f0e..db8e456 100644 --- a/src/avhw.rs +++ b/src/avhw.rs @@ -163,8 +163,12 @@ impl EncState { fps: u32, transform: Transform, ) -> Result { + 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::::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) } diff --git a/src/state.rs b/src/state.rs index 4bc9e17..4d10de8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -225,7 +225,7 @@ impl State { pub fn new(gm: GlobalList, args: Args, qhandle: QueueHandle>) -> 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 State { 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 handler. + fn bind_initial_globals(&mut self) { + use wayland_client::globals::Global; + + let globals: Vec = 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); + } + } + } + _ => {} + } } }