docs(fps_limit): 中文注释帧率限制器

This commit is contained in:
dailz
2026-06-22 17:26:03 +08:00
parent f9e59756c3
commit de93d31c89
+60
View File
@@ -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}; 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<T> { pub struct FpsLimit<T> {
/// 缓存最近一次被丢弃/待输出的帧。`Option<T>` 类比 Go 中可空指针 `*T`
/// `Some(frame)` 表示有缓存,`None` 表示空。`flush` 会取出此字段。
on_deck: Option<T>, on_deck: Option<T>,
/// 最近一次「放行」(输出给下游)的时间戳;`None` 表示尚未放过任何帧,
/// 此时下一帧必放行(首帧直通语义)。
last_output_time: Option<Instant>, last_output_time: Option<Instant>,
/// 最小放行间隔 = `1 / fps` 秒。两次输出之间的时间差必须 ≥ 该值。
/// 类比 Go`time.Duration(float64(1) / float64(fps) * float64(time.Second))`。
min_interval: Duration, min_interval: Duration,
} }
impl<T> FpsLimit<T> { impl<T> FpsLimit<T> {
/// 构造一个目标帧率为 `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 { pub fn new(fps: u32) -> Self {
Self { Self {
on_deck: None, on_deck: None,
last_output_time: None, last_output_time: None,
// 见上文 `Duration::from_secs_f64` 的 Go 类比。
min_interval: Duration::from_secs_f64(1.0 / fps as f64), min_interval: Duration::from_secs_f64(1.0 / fps as f64),
} }
} }
// 下面的英文 `///` 块为既有文档(保持原样),中文说明见函数体内 `//` 注释。
/// Feed a new frame. Returns: /// Feed a new frame. Returns:
/// - Some(()) if enough time elapsed since the last output — proceed to encode current frame /// - 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 /// - None if too close to the last output — drop current frame
///
/// 参数 `&mut self` 相当于 Go 方法接收者 `l *FpsLimit[T]`(可变借用 → 持有可写引用);
/// 返回值 `Option<T>` 相当于 Go 中可空返回值:`Some` 表示放行该帧,`None` 表示丢弃。
pub fn on_new_frame(&mut self, frame: T, timestamp: Instant) -> Option<T> { pub fn on_new_frame(&mut self, frame: T, timestamp: Instant) -> Option<T> {
// 判断本帧是否「就绪」(可放行)。Rust 的 `match` 强制穷尽,类比 Go 的 `switch`
// 但编译器会在漏掉分支时报错,比 Go 更严格。
let ready = match self.last_output_time { let ready = match self.last_output_time {
// 首帧:从未输出过,直接放行。
None => true, None => true,
// 非首帧:`timestamp.duration_since(last)` 计算时间差,
// 类比 Go `timestamp.Sub(last)`;返回 `Duration`,与 `>=` 比较的是 `min_interval`。
Some(last) => timestamp.duration_since(last) >= self.min_interval, Some(last) => timestamp.duration_since(last) >= self.min_interval,
}; };
if ready { if ready {
// 放行路径:先更新最近输出时间,再把本帧记到 `on_deck`(保留引用用于 flush)。
self.last_output_time = Some(timestamp); self.last_output_time = Some(timestamp);
self.on_deck = Some(frame); self.on_deck = Some(frame);
// `Option::take`:移出内部值并把原位置置为 `None`。这里返回刚写入的 `frame`
// 即把本帧交给调用方编码输出。
self.on_deck.take() self.on_deck.take()
} else { } else {
// 丢弃路径:仍把本帧缓存到 `on_deck`(覆盖上一帧的丢弃值),以便 flush 时
// 取到「最后一帧」用于收尾。`Option::replace` 返回旧值(这里用 `let _ =` 丢弃)。
let _ = self.on_deck.replace(frame); let _ = self.on_deck.replace(frame);
None None
} }
} }
/// 取出并清空缓存的「最后一帧」。常用于流尾 flush,确保下游收到最后一帧。
/// 连续第二次调用必返回 `None`,因为 `take` 后 `on_deck` 已为 `None`。
pub fn flush(&mut self) -> Option<T> { pub fn flush(&mut self) -> Option<T> {
self.on_deck.take() self.on_deck.take()
} }
@@ -46,6 +96,7 @@ mod tests {
#[test] #[test]
fn first_frame_passes_immediately() { fn first_frame_passes_immediately() {
let mut limiter: FpsLimit<u32> = FpsLimit::new(30); let mut limiter: FpsLimit<u32> = FpsLimit::new(30);
// `Instant::now()` 取单调时钟当前时间,类比 Go `time.Now()`。
let now = Instant::now(); let now = Instant::now();
let result = limiter.on_new_frame(1u32, now); let result = limiter.on_new_frame(1u32, now);
assert_eq!(result, Some(1)); assert_eq!(result, Some(1));
@@ -56,6 +107,8 @@ mod tests {
let mut limiter: FpsLimit<u32> = FpsLimit::new(30); let mut limiter: FpsLimit<u32> = FpsLimit::new(30);
let now = Instant::now(); let now = Instant::now();
limiter.on_new_frame(1, 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)); let result = limiter.on_new_frame(2, now + Duration::from_millis(1));
assert!(result.is_none()); assert!(result.is_none());
} }
@@ -65,6 +118,7 @@ mod tests {
let mut limiter: FpsLimit<u32> = FpsLimit::new(30); let mut limiter: FpsLimit<u32> = FpsLimit::new(30);
let now = Instant::now(); let now = Instant::now();
limiter.on_new_frame(1, now); limiter.on_new_frame(1, now);
// 34ms > 33.33ms30fps 的 min_interval),应放行。
let result = limiter.on_new_frame(2, now + Duration::from_millis(34)); let result = limiter.on_new_frame(2, now + Duration::from_millis(34));
assert_eq!(result, Some(2)); assert_eq!(result, Some(2));
} }
@@ -75,8 +129,11 @@ mod tests {
let base = Instant::now(); let base = Instant::now();
let mut outputs = Vec::new(); let mut outputs = Vec::new();
// 模拟 60fps 输入(每 16ms 一帧),目标 30fps(每 33ms 一帧),
// 期望 10 帧输入至少产生 3 帧输出。
for i in 0..10u32 { for i in 0..10u32 {
let t = base + Duration::from_millis(i as u64 * 16); 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) { if let Some(f) = limiter.on_new_frame(i, t) {
outputs.push(f); outputs.push(f);
} }
@@ -96,8 +153,11 @@ mod tests {
let mut limiter: FpsLimit<u32> = FpsLimit::new(30); let mut limiter: FpsLimit<u32> = FpsLimit::new(30);
let now = Instant::now(); let now = Instant::now();
limiter.on_new_frame(1, now); limiter.on_new_frame(1, now);
// 第二帧被丢弃,但仍缓存到 `on_deck`。
limiter.on_new_frame(2, now + Duration::from_millis(1)); limiter.on_new_frame(2, now + Duration::from_millis(1));
// flush 取出被丢弃的最后一帧(=2)。
assert_eq!(limiter.flush(), Some(2)); assert_eq!(limiter.flush(), Some(2));
// 第二次 flush 应返回 `None``take` 已清空)。
assert_eq!(limiter.flush(), None); assert_eq!(limiter.flush(), None);
} }
} }