perf(agent): 关键帧周期从 4s/10s 统一到 1s,缩短丢帧后的兜底恢复窗口
背景 协议 v2 与 vendor nvenc 补丁之后,KeyFrameReq 已成为**真正生效**的恢复主路径 (此前 NVENC 缺 FORCEIDR 标志,请求被静默忽略)。因此周期 IDR 的角色变了: 从「唯一恢复手段」降级为「兜底手段」,只用于防「请求连续丢失」这种极端情况。 既然是兜底,就没必要配得那么长。 问题:三处周期不一致,且都过长 - agent/src/capture.rs NVENC:gop_len = 240 帧 @60fps = 4 秒 - agent/src/capture.rs 软编: intra_period = 300 帧 @60fps = 5 秒 - agent-core/src/lib.rs Linux: intra_period = 300 帧 @30fps = **10 秒** 同一产品在三种编码后端上的恢复等待时间差 2.5 倍,且 Linux 端最差。 改动 - 新增 agent/src/capture.rs::INTRA_PERIOD_FRAMES = 60(Windows 两个编码器共用) - agent-core 的 EncoderSettings::default() 改为按 max_fps 推导(1 秒对应帧数), 避免以后调帧率时周期又漂移 代价 IDR 是整帧内编码,实测一次约 95-120KB,而 P 帧约 3-10KB。1 秒一次 IDR 约 增加 0.8Mbps 开销,在 5Mbps 码率下可接受。 验证(dump_stream 观察 150 帧) [ 0] seq=0 IDR ← 首帧 [ 60] seq=60 IDR ← 间隔精确 60 帧 [120] seq=120 IDR @60fps 即每 1 秒一个自动 IDR,与 gop_len=60 配置一致。
This commit is contained in:
parent
8410a8de2a
commit
6e83bda8b8
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Option<Vec<u8>>>, keyreq: Arc<AtomicBool>, 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) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user