diff --git a/src/fps_limit.rs b/src/fps_limit.rs index 1fca42d..3610b53 100644 --- a/src/fps_limit.rs +++ b/src/fps_limit.rs @@ -1,39 +1,89 @@ +//! 帧率限制器(FPS Limiter)。 +//! +//! 基于时间间隔的下采样策略:当输入帧率高于目标时,按时间窗口丢弃多余帧, +//! 保证输出帧率不超过配置上限。本实现是「非阻塞丢帧」策略——调用方收到 +//! `None` 时应主动丢弃该帧,而不是 `thread::sleep` 阻塞等待(这与 Go 中 +//! 用 `time.Now()` + `time.Since(last)` + `time.Sleep(d)` 的阻塞式限速器不同)。 +//! +//! - 时间点:`std::time::Instant`(单调时钟,类比 Go `time.Time` / `time.Now()`) +//! - 时间差:`std::time::Duration`(类比 Go `time.Duration`) +//! +//! Go 等价伪码: +//! ```text +//! type Limiter struct { last time.Time; minInterval time.Duration } +//! if time.Since(l.last) >= l.minInterval { /* 放行 */ } else { /* 丢帧 */ } +//! ``` + use std::time::{Duration, Instant}; +/// 帧率限制器。泛型参数 `T` 代表「帧」的载荷类型(如 AVFrame 包裹、纹理 ID、序号等), +/// 类比 Go 1.18+ 的 `type FpsLimit[T any] struct{ ... }`。 +/// +/// 字段全部私有,外部只能通过 [`new`](Self::new) / [`on_new_frame`](Self::on_new_frame) +/// / [`flush`](Self::flush) 三个方法操作,确保不变量(如「首帧必过」)不被绕过。 pub struct FpsLimit { + /// 缓存最近一次被丢弃/待输出的帧。`Option` 类比 Go 中可空指针 `*T`: + /// `Some(frame)` 表示有缓存,`None` 表示空。`flush` 会取出此字段。 on_deck: Option, + /// 最近一次「放行」(输出给下游)的时间戳;`None` 表示尚未放过任何帧, + /// 此时下一帧必放行(首帧直通语义)。 last_output_time: Option, + /// 最小放行间隔 = `1 / fps` 秒。两次输出之间的时间差必须 ≥ 该值。 + /// 类比 Go:`time.Duration(float64(1) / float64(fps) * float64(time.Second))`。 min_interval: Duration, } impl FpsLimit { + /// 构造一个目标帧率为 `fps`(帧/秒)的限速器。 + /// + /// - `fps as f64`:把 `u32` 提升为 `f64` 才能做浮点除法,类比 Go 的 `float64(fps)`; + /// Rust 不允许 `u32 / f64` 隐式转换,必须显式 cast。 + /// - `Duration::from_secs_f64(1.0 / fps as f64)`:用浮点秒构造 `Duration`, + /// 例如 `fps=30` → `min_interval ≈ 33.33ms`。 pub fn new(fps: u32) -> Self { Self { on_deck: None, last_output_time: None, + // 见上文 `Duration::from_secs_f64` 的 Go 类比。 min_interval: Duration::from_secs_f64(1.0 / fps as f64), } } + // 下面的英文 `///` 块为既有文档(保持原样),中文说明见函数体内 `//` 注释。 /// Feed a new frame. Returns: /// - Some(()) if enough time elapsed since the last output — proceed to encode current frame /// - None if too close to the last output — drop current frame + /// + /// 参数 `&mut self` 相当于 Go 方法接收者 `l *FpsLimit[T]`(可变借用 → 持有可写引用); + /// 返回值 `Option` 相当于 Go 中可空返回值:`Some` 表示放行该帧,`None` 表示丢弃。 pub fn on_new_frame(&mut self, frame: T, timestamp: Instant) -> Option { + // 判断本帧是否「就绪」(可放行)。Rust 的 `match` 强制穷尽,类比 Go 的 `switch`, + // 但编译器会在漏掉分支时报错,比 Go 更严格。 let ready = match self.last_output_time { + // 首帧:从未输出过,直接放行。 None => true, + // 非首帧:`timestamp.duration_since(last)` 计算时间差, + // 类比 Go `timestamp.Sub(last)`;返回 `Duration`,与 `>=` 比较的是 `min_interval`。 Some(last) => timestamp.duration_since(last) >= self.min_interval, }; if ready { + // 放行路径:先更新最近输出时间,再把本帧记到 `on_deck`(保留引用用于 flush)。 self.last_output_time = Some(timestamp); self.on_deck = Some(frame); + // `Option::take`:移出内部值并把原位置置为 `None`。这里返回刚写入的 `frame`, + // 即把本帧交给调用方编码输出。 self.on_deck.take() } else { + // 丢弃路径:仍把本帧缓存到 `on_deck`(覆盖上一帧的丢弃值),以便 flush 时 + // 取到「最后一帧」用于收尾。`Option::replace` 返回旧值(这里用 `let _ =` 丢弃)。 let _ = self.on_deck.replace(frame); None } } + /// 取出并清空缓存的「最后一帧」。常用于流尾 flush,确保下游收到最后一帧。 + /// 连续第二次调用必返回 `None`,因为 `take` 后 `on_deck` 已为 `None`。 pub fn flush(&mut self) -> Option { self.on_deck.take() } @@ -46,6 +96,7 @@ mod tests { #[test] fn first_frame_passes_immediately() { let mut limiter: FpsLimit = FpsLimit::new(30); + // `Instant::now()` 取单调时钟当前时间,类比 Go `time.Now()`。 let now = Instant::now(); let result = limiter.on_new_frame(1u32, now); assert_eq!(result, Some(1)); @@ -56,6 +107,8 @@ mod tests { let mut limiter: FpsLimit = FpsLimit::new(30); let now = Instant::now(); limiter.on_new_frame(1, now); + // `now + Duration::from_millis(1)`:`Instant + Duration` 通过 `Add` trait 重载, + // 类比 Go `now.Add(1 * time.Millisecond)`。1ms 远小于 33ms,应被丢弃。 let result = limiter.on_new_frame(2, now + Duration::from_millis(1)); assert!(result.is_none()); } @@ -65,6 +118,7 @@ mod tests { let mut limiter: FpsLimit = FpsLimit::new(30); let now = Instant::now(); limiter.on_new_frame(1, now); + // 34ms > 33.33ms(30fps 的 min_interval),应放行。 let result = limiter.on_new_frame(2, now + Duration::from_millis(34)); assert_eq!(result, Some(2)); } @@ -75,8 +129,11 @@ mod tests { let base = Instant::now(); let mut outputs = Vec::new(); + // 模拟 60fps 输入(每 16ms 一帧),目标 30fps(每 33ms 一帧), + // 期望 10 帧输入至少产生 3 帧输出。 for i in 0..10u32 { let t = base + Duration::from_millis(i as u64 * 16); + // `if let Some(f) = ...`:模式匹配解构 `Option`,类比 Go 的 `if v, ok := ...; ok {}`。 if let Some(f) = limiter.on_new_frame(i, t) { outputs.push(f); } @@ -96,8 +153,11 @@ mod tests { let mut limiter: FpsLimit = FpsLimit::new(30); let now = Instant::now(); limiter.on_new_frame(1, now); + // 第二帧被丢弃,但仍缓存到 `on_deck`。 limiter.on_new_frame(2, now + Duration::from_millis(1)); + // flush 取出被丢弃的最后一帧(=2)。 assert_eq!(limiter.flush(), Some(2)); + // 第二次 flush 应返回 `None`(`take` 已清空)。 assert_eq!(limiter.flush(), None); } }