diff --git a/agent-core/src/lib.rs b/agent-core/src/lib.rs index c6c6f67..c623a91 100644 --- a/agent-core/src/lib.rs +++ b/agent-core/src/lib.rs @@ -79,11 +79,14 @@ pub struct EncoderSettings { impl Default for EncoderSettings { fn default() -> Self { - // 对标 UU:屏幕内容实时档;关键帧周期 300 帧 ≈ 10s + // 关键帧周期:以**时间**为准而非固定帧数,避免帧率变化后恢复窗口漂移。 + // 1 秒 @30fps = 30 帧。主恢复路径是 KeyFrameReq(协议 v2 后已确认生效), + // 自动 IDR 只是兜底;此前 300 帧 @30fps 意味着兜底窗口长达 10 秒。 + let max_fps = 30.0f32; Self { bitrate_bps: 5_000_000, - max_fps: 30.0, - intra_period: 300, + max_fps, + intra_period: (max_fps as u32).max(1), } } } diff --git a/agent/src/capture.rs b/agent/src/capture.rs index a01c6f7..b760b50 100644 --- a/agent/src/capture.rs +++ b/agent/src/capture.rs @@ -39,6 +39,17 @@ use windows::Win32::Graphics::Dxgi::{ use std::time::{Duration, Instant}; +/// 周期 IDR 的帧间隔(兜底恢复点)。 +/// +/// 两个编码器共用同一常量,避免 NVENC 与软编的恢复窗口不一致——此前分别是 +/// 240 帧@60fps=4s 与 300 帧@60fps=5s,而 Linux 端又是 300 帧@30fps=10s。 +/// +/// 取值理由:**主恢复路径是 KeyFrameReq,不是自动 IDR**(协议 v2 后已确认请求 +/// 真正生效,见 vendor/nvenc 的 FORCEIDR 补丁)。自动 IDR 只用于兜底 +/// 「请求连续丢失」这种极端情况。60 帧 @60fps = 1 秒,既足够兜底, +/// 又不会因过密的整帧内编码抬高码率。 +pub const INTRA_PERIOD_FRAMES: u32 = 60; + pub struct Duplication { pub device: ID3D11Device, context: ID3D11DeviceContext, @@ -201,8 +212,15 @@ impl NvencEncoder { // CBR 桌面实时档:码率恒定,延迟可预测(对标 UU 3.2Mbps@60fps) config.preset_cfg.rc_params.rate_control_mode = NVencParamsRcMode::CBR; config.preset_cfg.rc_params.average_bit_rate = bitrate_bps; - config.preset_cfg.gop_len = 240; // 周期 IDR(60fps 下 4s):网络异常后的自愈恢复点, - // 配合 KeyFrameReq 即时恢复,避免长时间冻结 + // 周期 IDR 是**兜底恢复点**,不是主恢复路径——主路径是 KeyFrameReq + // (协议 v2 后已确认真正生效,见 vendor/nvenc 的 FORCEIDR 补丁)。 + // + // 取值权衡:GOP 越短,丢帧后自动恢复越快,但每次 IDR 都是整帧内编码, + // 码率开销显著(1080p 桌面 IDR 通常数倍于 P 帧)。 + // 60 帧 @60fps = 1s:既覆盖「KeyFrameReq 连续丢失」这种极端情况, + // 又把兜底窗口压到 1 秒以内。原先 240 帧 = 4s,在请求已可靠的前提下 + // 只会让受害时长无谓地长 4 倍。 + config.preset_cfg.gop_len = INTRA_PERIOD_FRAMES; config.preset_cfg.frame_interval_p = 1; // 无 B 帧 let init = InitParams { @@ -312,7 +330,9 @@ pub fn run(tx: Sender>>, keyreq: Arc, congestion: Arc .max_frame_rate(FrameRate::from_hz(60.0)) .rate_control_mode(RateControlMode::Bitrate) .usage_type(UsageType::ScreenContentRealTime) - .intra_frame_period(IntraFramePeriod::from_num_frames(300)) + .intra_frame_period(IntraFramePeriod::from_num_frames( + INTRA_PERIOD_FRAMES, + )) .num_threads(4), ) { Ok(e) => {