feat(portal): BWE-driven resolution adaptation + duplicate frame skipping
WebRTC client bandwidth estimate now drives both encoder bitrate and resolution tier selection, replacing the previous static-target encoder. - webrtc.rs: enable str0m BWE (seeded at 5 Mbps), surface EgressBitrateEstimate + KeyframeRequest events, expose get_bwe_estimate() / set_need_keyframe() - state_portal.rs: wire bitrate/resolution channels between the WebRTC thread and the encode thread; tier ladder [1440p, 1080p, 720p] with downscale at 60% budget and upscale hysteresis (120% sustained 10s) - avhw.rs: SwEncImport::poll_resolution_commands() rebuilds the import filter graph on UpdateResolution; SwEncEncode::recreate_encoder() rebuilds sws/enc_video/yuv_frame atomically; hash_sampled_y_plane() skips duplicate frames; VBV x264opts cap IDR bursts; H.264 level 4.0 (muxer) / 4.2 (WebRTC) - state.rs: sync wlr-screencopy GOP to fps*2 max 20 for parity - fix: drain bitrate_rx + resolution_rx BEFORE the stride check in encode_cpu_frame() so the new (smaller-stride) frame produced after a resolution change does not hit the stale (larger) enc_width and crash the encode thread - WebRTC GOP widened to fps*2 max 20 (was fps/2 max 10)
This commit is contained in:
+32
-14
@@ -539,7 +539,10 @@ impl<S: CaptureSource> State<S> {
|
||||
// takes ownership of the fd, and the original fd is owned by map_frame.
|
||||
let fd_dup = unsafe { libc::dup(obj.fd) };
|
||||
if fd_dup < 0 {
|
||||
tracing::error!("failed to dup dma-buf fd: {}", std::io::Error::last_os_error());
|
||||
tracing::error!(
|
||||
"failed to dup dma-buf fd: {}",
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
// wayland-client does not auto-destroy params on Drop.
|
||||
params.destroy();
|
||||
self.errored = true;
|
||||
@@ -641,7 +644,11 @@ impl<S: CaptureSource> State<S> {
|
||||
if last.elapsed() >= std::time::Duration::from_secs(10) {
|
||||
let delta = self.stats_frames;
|
||||
let fps = delta as f64 / last.elapsed().as_secs_f64();
|
||||
tracing::info!(frames = self.stats_frames, fps = format!("{fps:.1}"), "encoding stats");
|
||||
tracing::info!(
|
||||
frames = self.stats_frames,
|
||||
fps = format!("{fps:.1}"),
|
||||
"encoding stats"
|
||||
);
|
||||
self.stats_last_time = Some(std::time::Instant::now());
|
||||
self.stats_frames = 0;
|
||||
}
|
||||
@@ -672,7 +679,9 @@ impl<S: CaptureSource> State<S> {
|
||||
}
|
||||
|
||||
pub fn poll_webrtc(&mut self) -> Result<()> {
|
||||
let Some(ref mut wrtc) = self.webrtc else { return Ok(()) };
|
||||
let Some(ref mut wrtc) = self.webrtc else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
wrtc.handle_signaling()?;
|
||||
wrtc.poll_and_feed()?;
|
||||
@@ -697,7 +706,8 @@ impl<S: CaptureSource> State<S> {
|
||||
continue;
|
||||
}
|
||||
count += 1;
|
||||
if let Err(e) = wrtc.write_h264_frame(&data, self.webrtc_frames_sent, self.args.fps) {
|
||||
if let Err(e) = wrtc.write_h264_frame(&data, self.webrtc_frames_sent, self.args.fps)
|
||||
{
|
||||
tracing::debug!("WebRTC write frame error: {e}");
|
||||
}
|
||||
self.stats.record_send(0.0, None);
|
||||
@@ -709,10 +719,8 @@ impl<S: CaptureSource> State<S> {
|
||||
}
|
||||
|
||||
if self.args.stats && self.stats.should_snapshot() {
|
||||
self.stats.set_queue_depths(
|
||||
0,
|
||||
self.webrtc_rx.as_ref().map(|r| r.len()).unwrap_or(0),
|
||||
);
|
||||
self.stats
|
||||
.set_queue_depths(0, self.webrtc_rx.as_ref().map(|r| r.len()).unwrap_or(0));
|
||||
let snap = self.stats.snapshot_and_reset();
|
||||
tracing::info!("stats: {snap}");
|
||||
}
|
||||
@@ -752,9 +760,12 @@ impl<S: CaptureSource> State<S> {
|
||||
.unwrap_or_else(|| 2 * (width as u64) * (height as u64) * (fps as u64) / 100);
|
||||
|
||||
let enc = if let Some(ref tx) = self.webrtc_tx {
|
||||
let (enc_w, enc_h) =
|
||||
transpose_if_transform_transposed(output_info.transform, width as i32, height as i32);
|
||||
let actual_gop_size = self.args.gop_size.unwrap_or((fps / 2).max(10));
|
||||
let (enc_w, enc_h) = transpose_if_transform_transposed(
|
||||
output_info.transform,
|
||||
width as i32,
|
||||
height as i32,
|
||||
);
|
||||
let actual_gop_size = self.args.gop_size.unwrap_or((fps * 2).max(20));
|
||||
match SwEncState::new_webrtc(
|
||||
&drm_path,
|
||||
width,
|
||||
@@ -765,7 +776,10 @@ impl<S: CaptureSource> State<S> {
|
||||
bitrate,
|
||||
actual_gop_size,
|
||||
tx.clone(),
|
||||
self.webrtc_paused.as_ref().expect("webrtc_paused must exist when webrtc_tx exists").clone(),
|
||||
self.webrtc_paused
|
||||
.as_ref()
|
||||
.expect("webrtc_paused must exist when webrtc_tx exists")
|
||||
.clone(),
|
||||
) {
|
||||
Ok(enc) => StreamingEncoder::WebRtc(enc),
|
||||
Err(e) => {
|
||||
@@ -775,7 +789,11 @@ impl<S: CaptureSource> State<S> {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let output_path = self.args.output.as_deref().expect("output required for MP4 mode");
|
||||
let output_path = self
|
||||
.args
|
||||
.output
|
||||
.as_deref()
|
||||
.expect("output required for MP4 mode");
|
||||
match crate::avhw::create_encoder(
|
||||
&drm_path,
|
||||
Path::new(output_path),
|
||||
@@ -1035,7 +1053,7 @@ impl<S: CaptureSource> Dispatch<WlRegistry, GlobalListContents> for State<S> {
|
||||
qhandle: &QueueHandle<State<S>>,
|
||||
) {
|
||||
use wayland_client::protocol::wl_registry::Event as RegistryEvent;
|
||||
|
||||
|
||||
match event {
|
||||
RegistryEvent::Global {
|
||||
name,
|
||||
|
||||
Reference in New Issue
Block a user