# wl-screenrec 源码分析 ## 1. 项目概述 wl-screenrec 是一个高性能 Wayland 屏幕录制器,核心特性:原始视频数据不经过 CPU,全程在 GPU 上完成捕获、格式转换和编码。 **技术栈**:Rust + FFmpeg(硬件加速)+ Wayland 协议(wayland-client crate)+ mio 事件循环 **9 个源文件**: | 文件 | 行数 | 职责 | |------|------|------| | `main.rs` | ~2430 | 状态机、事件循环、编码管线编排 | | `avhw.rs` | ~444 | FFmpeg 硬件设备/帧上下文(VAAPI/Vulkan) | | `audio.rs` | ~405 | 音频捕获→解码→重采样→编码(独立线程) | | `cap_ext_image_copy.rs` | ~237 | ext-image-copy-capture 协议后端 | | `filter.rs` | ~194 | FFmpeg 视频滤镜图(crop+scale+transpose) | | `transform.rs` | ~215 | 坐标系变换(处理旋转/翻转) | | `cap_wlr_screencopy.rs` | ~165 | wlr-screencopy 协议后端 | | `fps_limit.rs` | ~130 | 帧率限制器(VRR 感知) | | `fifo.rs` | ~60 | FFmpeg AVAudioFifo 安全封装 | --- ## 2. 架构概览 ### 2.1 模块依赖 ``` ┌──────────────────────────────┐ │ main.rs │ └──┬──┬──┬──┬──┬──┬──┬─────────┘ │ │ │ │ │ │ │ ┌───────────┘ │ │ │ │ │ └──────────┐ ▼ ▼ │ ▼ │ ▼ ▼ ┌──────────┐ ┌────────┐│┌───────┐ ┌────────┐ ┌──────────┐ │ avhw.rs │ │filter.rs│││audio.rs│ │fps_limit│ │transform │ └──────────┘ └───┬────┘│└───┬───┘ └────────┘ └──────────┘ │ │ │ ┌───────┤ │ ▼ ▼ ▼ │ ┌──────┐ ┌────────────┐┌─────┴┐│fifo.rs│ │cap_wlr_ ││cap_ │└──────┘ │screencopy ││ext_* │ └────────────┘└──────┘ ``` **依赖层次**:`fifo/fps_limit/transform`(叶节点)→ `avhw/audio` → `filter` → `cap_*` → `main.rs`(核心)。main.rs 与 cap_*.rs 之间存在**双向依赖**:main 定义 `CaptureSource` trait 和 `State`,cap_* 为 `State` 实现 `Dispatch`。 ### 2.2 State\ 全局状态 核心状态结构体持有以下关键字段: - **in_flight_surface**:帧在途状态,跟踪当前帧的捕获生命周期 - **dma**:DMA-BUF 协议对象,用于 GPU 缓冲区共享 - **enc**:编码器构造状态机(`EncConstructionStage`),管理从探测到就绪的全过程 - **starting_timestamp**:首帧时间戳(纳秒),用于音视频同步 - **args**:命令行参数 - **errored**:致命错误标志 - **gm**:Wayland 全局对象列表 - **xdg_output_manager**:输出几何信息管理器 泛型 `S: CaptureSource` 使同一个 State 支持两种截屏后端,无需运行时动态分发。 ### 2.3 CaptureSource Trait 定义了屏幕捕获后端的统一接口契约: - **关联类型 Frame**:每种后端有自己的帧类型 - **new()**:从 Wayland 全局对象和输出创建后端实例 - **alloc_frame()**:分配捕获帧,返回 `Option` 统一同步/异步两种模式 - **queue_copy()**:提交 DMA-BUF 缓冲区给合成器,请求捕获 - **on_done_with_frame()**:帧使用完毕后的回收回调 | 实现者 | 协议 | 文件 | |--------|------|------| | `CapWlrScreencopy` | zwlr-screencopy-unstable-v1 | `cap_wlr_screencopy.rs` | | `CapExtImageCopy` | ext-image-copy-capture-v1 | `cap_ext_image_copy.rs` | 关键差异:wlr-screencopy 的 `alloc_frame()` 返回 `None`(异步),ext-image-copy 直接返回 `Some(frame)`(同步)。 ### 2.4 事件循环 采用 mio polling + Wayland 事件队列 + Unix 信号三合一架构: - **Token(0)** = 信号(SIGINT/SIGTERM/SIGHUP 退出,SIGUSR1 触发 history flush) - **Token(1)** = Wayland 连接 fd → `queue.dispatch_pending(&mut state)` - 超时由 FPS 报告周期驱动 - 退出时仅 `Complete` 状态才 flush 编码器 后端自动选择:探测全局列表,优先 ext-image-copy-capture(跨桌面标准),否则回退 wlr-screencopy。 --- ## 3. Wayland 协议交互层 ### 3.1 两个后端的事件流对比 **wlr-screencopy**: ``` capture_output() [异步] → LinuxDmabuf { format, w, h } × N → 收集格式(仅 LINEAR) → BufferDone → negotiate_format() + on_frame_allocd() queue_copy(WlBuffer) → Ready { timestamp } → on_copy_complete() ``` **ext-image-copy-capture**: ``` [会话初始化] → BufferSize / DmabufDevice / DmabufFormat × N → 收集约束 → Done → negotiate_format() [每帧] create_frame() [同步,直接返回 Some] queue_copy(WlBuffer) → PresentationTime { timestamp } → 暂存 → Ready → on_copy_complete() ``` 核心差异:wlr 每帧触发格式协商(帧级别),ext 在会话建立时完成(会话级别)。ext 提供真实 modifier 列表,wlr 硬编码 `DrmModifier::LINEAR`。 ### 3.2 DMA-BUF 缓冲区创建 零拷贝路径: ``` AV HW Surface → av_hwframe_map → DRM PRIME 描述符 (DMA-BUF fd) → zwp_linux_dmabuf.create_params → add(planes) → create_immed → WlBuffer → cap.queue_copy(WlBuffer) → 合成器直接写入 GPU Surface(零拷贝) ``` ### 3.3 格式协商 格式优先级:`XRGB8888` > `XBGR8888` > `XRGB2101010`。VAAPI 模式仅接受 LINEAR modifier,Vulkan 模式接受任意 modifier。 DRM 设备发现:两条路径(wlr 的 `MainDevice` / ext 的 `DmabufDevice`),核心逻辑相同:`dev_t` → `DrmNode` → Render 节点路径。回退 `/dev/dri/renderD128`。 ### 3.4 Dispatch 泛型分发模式 三种模式: - **A. 完全泛型**:`Dispatch for State` — 共享协议,通常空实现 - **B. 带状态回调的泛型**:`Dispatch for State` — 需要 `'static`,含实质逻辑 - **C. 后端专用**:`Dispatch for State` — 非泛型,含后端特有逻辑 输出探测通过 `WlOutput` + `ZxdgOutputV1` 协作完成,`PartialOutputInfo` 增量收集直到所有字段填充。每个输出收到两次 `Done` 事件,忽略第一次。 --- ## 4. GPU 编码管道 ### 4.1 零拷贝数据流 ``` GPU 帧池 ─alloc()→ HW Surface ↓ av_hwframe_map → DMA-BUF fd ↓ zwp_linux_dmabuf → WlBuffer (fd 共享) ↓ 合成器直接写入 GPU Surface ↓ buffersrc → GPU 滤镜 (crop/scale/transpose) ↓ buffersink → 编码器 (send_frame) ↓ receive_packet → Muxer → 文件 ``` **整条链路中原始帧数据始终在 GPU 内存,不经过 CPU。** ### 4.2 硬件设备上下文 两种硬件加速路径: - **VAAPI**:一步创建,直接从 DRM 设备创建 VAAPI 硬件设备上下文 - **Vulkan**:两步创建,先创建 DRM 上下文,再派生 Vulkan 上下文,中间 DRM 上下文立即释放 帧上下文两种用途: - **Capture**:Vulkan flags = `SAMPLED | TRANSFER_DST`,tiling = `Drm(modifiers)` - **Enc**:Vulkan flags = `VIDEO_ENCODE_SRC_KHR | TRANSFER_DST`,tiling = `Optimal` ### 4.3 Vulkan 自引用 Pin 模式 `AvHwDevCtxVulkanBuffers` 包含自引用 C 指针链(drm_info → image_fmt_list_info → 内部数组),通过 `Pin>` 解决。`PhantomPinned` 标记 `!Unpin`,`'static` 是对 FFmpeg C API 的"善意谎言"。 ### 4.4 FFmpeg 滤镜图 ``` buffersrc (HW) → crop → scale → [transpose] → [hwdownload] → buffersink ``` - `hw_frames_ctx` 绑定是零拷贝的关键 - crop 使用 `exact=1` workaround - scale/transpose 按硬件类型选择:`scale_vaapi`/`scale_vulkan`,`transpose_vaapi`/`transpose_vulkan` - `hwdownload` 仅在软件编码路径添加 ### 4.5 编码器选择 | Codec | VAAPI | Vulkan | |-------|-------|--------| | H.264 | `h264_vaapi` | `h264_vulkan` | | HEVC | `hevc_vaapi` | `hevc_vulkan` | | VP8/VP9 | `vp8/vp9_vaapi` | 不支持 | | AV1 | `av1_vaapi` | `av1_vulkan` | 选择优先级:`--ffmpeg-encoder` 显式指定 > 硬件编码器(尝试 `low_power=1` 后回退)> 通用编码器。 ### 4.6 EncodePixelFormat 三路派发 `Vaapi(Pixel) / Vulkan(Pixel) / Sw(Pixel)` 在编码器格式设置、硬件上下文绑定、滤镜图构建、帧上下文创建四处做三路匹配。 `--no-hw` 路径:捕获仍用 GPU(DMA-BUF),编码前 `hwdownload` 到 CPU,软件编码器(x264 自动 `ultrafast`)。 --- ## 5. 状态机与帧生命周期 ### 5.1 EncConstructionStage 状态机 ``` ┌──────────────────┐ 应用启动 │ ProbingOutputs │ │ └────────┬─────────┘ ▼ │ 所有输出探测完毕 ┌───────────────┐ ▼ │ ProbingOutputs├──→ ┌──────────────────┐ └───────────────┘ │EverythingButFmt │ └────────┬─────────┘ │ negotiate_format() ▼ ┌───────────┐ 输出断开 ┌─────→│ Complete │──────────┐ │ └─────┬─────┘ │ │ │ ▼ │ 格式变化 │ ┌──────────────┐ │ on_new_ │ │OutputWentAway│ │ capture_ │ └──────┬───────┘ │ format() │ │ 同名输出重连 └────────────┘ │ ←───────────────────────┘ Intermediate 瞬态存在于所有转换箭头处(mem::replace) ``` 关键转换点: - **ProbingOutputs → EverythingButFormat**:所有输出探测完毕 - **EverythingButFormat → Complete**:`negotiate_format()` 创建 EncState - **Complete → OutputWentAway**:`on_copy_fail()` 检测到输出断开,**保留 enc 丢弃 cap** - **OutputWentAway → Complete**:同名输出重新出现时创建新 cap 复用旧 enc `Intermediate` 瞬态通过 `mem::replace` + `take_enc()` 实现安全所有权转移。`take_enc()` 只允许从 `Complete`/`OutputWentAway` 提取编码器。 ### 5.2 InFlightSurface 帧生命周期 ``` ┌──────┐ queue_alloc_frame() ┌─────────────┐ │ None │ ───────────────────→ │ AllocQueued │ └──────┘ └──────┬───────┘ ↑ │ on_frame_allocd() │ ▼ │ ┌───────────┐ │ │ Allocd │ │ └─────┬─────┘ │ │ queue_frame_capture() │ ▼ │ ┌──────────────┐ └────── on_copy_complete ─│ CopyQueued │ / on_copy_fail └──────────────┘ ``` 帧级串行化:同一时间只有一帧在途,通过 `assert!` 强制执行。`CopyQueued` 持有 GPU surface、DRM 映射、Wayland 帧和 buffer 四个资源的所有权,拷贝完成后全部释放并启动下一帧。 ### 5.3 HistoryState 双模式 - **RecordingHistory(Duration, VecDeque\)**:环形缓冲,以关键帧为边界裁剪,确保回放可解码 - **Recording(i64)**:正常写入,PTS 减去偏移量保证起始对齐 SIGUSR1 触发 `RecordingHistory → Recording` 转换:先转换状态,再将历史包通过正常录制路径写出。 ### 5.4 错误恢复 `on_copy_fail()` 三个分支按优先级判断: 1. `output_went_away == true` → 保留编码器,进入 `OutputWentAway` 等待重连 2. `format_change == true` → 预期失败,重置标志后重新分配帧 3. 其他 → 未知错误,记录日志后重试 ### 5.5 动态格式切换 捕获格式变化时重建 `frames_rgb`、`video_filter`、`enc_video`、`frames_yuv`,但保留 `octx`、`hw_device_ctx`、`audio`、`history_state`。 --- ## 6. 音频管道与辅助模块 ### 6.1 音频管道 独立线程运行,三阶段构造: 1. **IncompleteAudioState**:完成编码器选择、设备打开、解码/编码器创建 2. **AudioHandle**:主线程句柄,含 `Receiver` + `AtomicBool` 控制标志 3. **AudioState**:音频线程内部状态,move 到独立线程 数据流:`音频设备 → 解码 → audio_filter(aformat) → AudioFifo(可选) → 编码 → mpsc → 主线程` 同步机制:`started` 原子标志在视频首帧时间戳获得后才置 true,确保音视频起点对齐。 AudioFifo:解决变长帧编码器需要固定 `frame_size` 的问题。条件判断:编码器不支持 `VARIABLE_FRAME_SIZE` 时创建。 ### 6.2 帧率限制器 VRR 感知设计:引入一帧缓冲延迟判定,避免在 VRR 场景下丢弃"更长时间显示"的帧。 ``` on_new_frame(frame, ts): 第1帧 → 直接通过 第2帧 → 存入 on_deck 缓冲 第N帧 → 比较缓冲帧与新帧时间戳: 新帧太近 → 丢弃缓冲帧 间隔足够 → 输出缓冲帧,新帧存入 on_deck ``` ### 6.3 坐标变换 处理 Wayland 输出变换(旋转/翻转)对坐标系的影响: - **transform_basis()**:8 种变换的基矩阵映射 - **screen_to_frame()**:矩形从屏幕空间到帧空间 - **transpose_if_transform_transposed()**:90° 旋转时交换宽高 - **fit_inside_bounds()**:ROI 越界时安全裁剪 ### 6.4 主线程事件循环集成 主循环中音频包在视频帧处理间隙通过 `try_recv` 非阻塞收取,无需额外事件源。 退出时 `EncState::flush()` 依次:刷出 FPS 限制器缓冲帧 → flush 音频线程 → 刷视频过滤器 → 发送编码器 EOF → 写容器 trailer。 --- ## 7. 可移植设计模式 从代码库中提取的 10 个可复用设计模式,按复杂度从低到高排列。 ### 7.1 策略 Trait + 泛型状态(CaptureSource) **问题**:多后端系统如何在避免运行时动态分发(`dyn Trait`)开销的同时保持类型安全和可扩展性? **方案**:定义 `CaptureSource` trait 带关联类型 `Frame`,将整个状态 `State` 泛型参数化。`State` 和 `State` 编译为两个独立单态化类型,后端选择在启动时确定。`alloc_frame()` 返回 `Option` 统一了同步和异步两种帧分配模式。 **移植要点**: - 适用后端数量有限(2-5 个)且进程生命周期内不变的场景;需运行时热切换则改用 trait object - `State` 中 `Sized` 约束必须,因为 `S` 作为字段存储;编译膨胀需注意大型 State 的泛型实例化 - main.rs 定义 trait 而 cap_*.rs 实现它,形成双向依赖,大型项目应将 trait 提取到独立模块 ### 7.2 多态枚举状态机(EncConstructionStage) **问题**:Rust 中如何以零开销实现状态机,同时保证状态转换的类型安全? **方案**:`EncConstructionStage` 有 5 个枚举变体(`ProbingOutputs`、`EverythingButFormat`、`Complete`、`OutputWentAway`、`Intermediate`),每个携带该状态所需的数据载荷。`Intermediate` 瞬态 + `mem::replace` 组合解决了部分借用限制:match 解构 `&mut self.enc` 同时给 `self.enc` 赋新值。`take_enc()` 通过消费 `self` 确保只有含编码器的状态才能被提取。 **移植要点**: - 3-7 个状态是 enum 状态机甜蜜点;优势是编译期穷尽检查,添加新状态时所有 match 报编译错误 - `Intermediate` 瞬态必须存在,否则 `mem::replace` 无法满足类型系统要求 - `Complete` 和 `OutputWentAway` 都持有 `EncState` 但后者丢弃 `cap`,体现"保留昂贵资源、丢弃可重建资源" ### 7.3 类型安全帧生命周期(InFlightSurface) **问题**:异步 DMA-BUF 传输涉及多个阶段,如何防止在错误阶段执行操作? **方案**:`InFlightSurface` 是 4 状态枚举 `None → AllocQueued → Allocd(S::Frame) → CopyQueued{...} → None`。每个状态携带该阶段特有的资源(`CopyQueued` 持有 GPU surface、DRM 映射、Wayland 帧和 buffer)。状态转换通过 `assert!(matches!(...))` 运行时守护,`take()` 方法(`mem::replace`)提供安全取出并自动重置为 `None`。同一时间只有一帧在途。 **移植要点**: - 适用任何"请求→资源就绪→提交操作→操作完成"的异步 I/O 管道 - 运行时 assert 而非编译期 typestate 是合理权衡:回调驱动的异步场景中编译期类型状态过于复杂 - RAII 确保 `CopyQueued → None` 路径释放所有资源(DRM 映射、Wayland buffer、帧对象) ### 7.4 Pin\ 自引用结构(Vulkan Buffers) **问题**:C 库中的链式结构体(Vulkan pNext 链)内部指针指向同结构其他字段,Rust 中移动会使指针失效,如何安全构建? **方案**:`AvHwDevCtxVulkanBuffers` 通过 `PhantomPinned` 标记 `!Unpin`,`Box::pin` 在堆上分配并固定,`get_unchecked_mut` 设置自引用指针。Vulkan 结构体的生命周期标记为 `'static` 作为对 C API 的"善意谎言",实际受 `Pin>` 控制。unsafe 代码集中在 `new()` 中,使用方完全安全。`chain_ptr()` 根据有无 DRM modifier 返回不同链头。 **移植要点**: - 通用模式,适用于 Vulkan、FFmpeg 硬件加速、内核 IOCTL 等涉及自引用 C 结构的场景 - `'static` 不是真正静态生命周期,而是向 C API 表达"指针在使用期间有效";确保持有者比 C API 使用时间更长 - 优于 `ouroboros` crate:手写 `Pin>` 逻辑清晰可控,生成的代码可调试 ### 7.5 独立线程管道 + 原子标志(音频线程) **问题**:音频需要持续低延迟处理,视频帧率不固定且受 VRR 影响,如何设计无锁跨线程协作? **方案**:音频处理完全隔离在独立线程。`mpsc::channel` 传递已编码 `Packet`,主线程在视频帧处理间隙通过 `try_recv()` 非阻塞收取。`Arc` + `SeqCst` 实现两个控制信号:`started`(视频首帧时间戳获得后置 true,音视频起点对齐)、`flush_flag`(退出通知)。音频线程主循环为 pull 模型,生命周期由输入设备驱动。 **移植要点**: - `AtomicBool` 适用于简单布尔信号,比 `Mutex` 高效且不死锁;不适用于需要等待/通知的场景 - 主循环间隙调用 `try_recv` 是经典的"顺便收取"模式,避免为音频注册额外事件源 - 适用任何生产者-消费者跨线程场景:传感器采集、网络 I/O 卸载、日志异步写入 ### 7.6 VRR 感知帧率控制(FpsLimit) **问题**:VRR 显示器上帧时间戳极不规则,简单"距上帧太近就丢弃"会产生错误决策,如何在不确定的时间戳流中做出正确帧选择? **方案**:`FpsLimit` 引入一帧延迟:第一帧直接通过,第二帧存入 `on_deck` 缓冲,从第三帧起用新帧时间戳判断旧帧是否保留。新帧太近则丢弃缓冲帧,间隔足够则输出缓冲帧。目标时间计算中使用 `max` 防止回退,正确处理帧跳跃后恢复。零项目内依赖,可直接复制使用。 **移植要点**: - 泛型 `T` 无约束,只做保留/丢弃决策,调用者完全控制帧生命周期 - 结束时必须调用 `flush()` 取出缓冲中的最后一帧,否则丢帧 - 一帧延迟对录屏/编码场景可接受,实时交互场景(如游戏输入)需评估 ### 7.7 泛型 Dispatch 三层分发(Wayland 协议) **问题**:多后端 Wayland 客户端如何组织 Dispatch 实现,使共享协议代码只写一次、后端专用代码各自独立? **方案**:三层分发模式。**A. 完全泛型**:`impl Dispatch for State`,共享协议,通常空实现。**B. 带状态回调泛型**:`impl Dispatch for State`,需 `'static` 约束,含实质状态更新逻辑。**C. 后端专用**:`impl Dispatch for State`,非泛型,在各自后端文件中。Rust trait 系统根据代理类型 × 状态泛型参数 × UserData 自动路由。 **移植要点**: - 适用于所有 wayland-client 项目;共享 Dispatch 放 main.rs,专用 Dispatch 放各自后端文件 - 需关联信息时(如 xdg-output 关联 WlOutput)用 `TypedObjectId` 作为 UserData - `'static` 约束源自事件循环要求,状态类型必须满足因为回调可能在任意时刻触发 ### 7.8 三阶段安全构造(IncompleteAudioState) **问题**:对象需分多阶段初始化且后续阶段依赖前阶段资源,如何在类型系统中安全表达? **方案**:三个不同类型表示三个阶段。`IncompleteAudioState` 持有输入设备、解码器、编码器(完成 FFmpeg 流创建)。`finish(self)` 消费不完整状态,创建过滤器、FIFO、通道,组装 `AudioState` 并启动线程,返回 `AudioHandle`(主线程句柄,含 `Receiver` + `AtomicBool`)。`AudioState` 通过 move 语义进入线程。`finish(self)` 而非 `finish(&mut self)` 保证不完整状态被消费后不再存在。 **移植要点**: - typestate pattern 变体,用不同类型(非泛型参数)编码状态,优势是不需要泛型 - 每阶段恰好分配该阶段所需资源;第一阶段打开设备(可能失败)不浪费线程资源 - 适用 FFmpeg 管线构建、数据库连接池、GPU 资源分配等"先收集信息、再一次性创建"的场景 ### 7.9 显示器热插拔自动恢复(OutputWentAway) **问题**:长时间录屏中显示器断连/重连,如何保持编码上下文不丢失并自动恢复录制? **方案**:`OutputWentAway` 状态机变体实现完整断连恢复。`wl_registry` 的 `GlobalRemove` 设置 `output_went_away` 标志(延迟到 `on_copy_fail` 时再切换,避免事件处理中途转状态)。转换时通过 `Intermediate` 取出 `enc`(保留编码器),丢弃 `cap`(协议对象已失效),记录等待的输出名称并重新探测。重连时按名称匹配创建新 `CaptureSource`,复用旧编码器继续录制。 **移植要点**: - 核心策略:保留昂贵资源(编码器、文件句柄)、丢弃可重建资源(协议对象、设备句柄) - 名称匹配(如 "DP-1")而非序号或指针,因为重连后 Wayland 对象 ID 会变化;稳定标识符是热插拔场景关键 - 适用 USB 摄像头、音频设备、网络连接等可热插拔设备的应用 ### 7.10 零拷贝 GPU 管道(DMA-BUF → HW Frame → Filter → Encoder) **问题**:传统录屏将 GPU 帧下载到 CPU 再上传回 GPU 编码,如何实现全程不离开 GPU 内存的零拷贝管线? **方案**:GPU 帧池分配硬件表面,`av_hwframe_map` 映射为 DRM PRIME 描述符获取 DMA-BUF fd,注册为 `WlBuffer` 后合成器直接写入 GPU 表面。滤镜图 `buffersrc → crop → scale → [transpose] → buffersink` 全部在 GPU 执行,`hw_frames_ctx` 绑定确保 FFmpeg 识别 GPU 帧。编码器(VAAPI/Vulkan)直接消费 GPU 帧。`EncodePixelFormat` 三路枚举在编码器选择、滤镜构建、帧上下文创建处统一派发,仅 `Sw` 路径添加 `hwdownload`。 **移植要点**: - DMA-BUF 桥接是 Linux 特有的;Windows/macOS 需用 D3D11 共享句柄或 IOSurface - 捕获帧上下文用 `Drm(modifiers)` 匹配合成器,编码帧上下文用 `Optimal` 获最佳性能,滤镜图做格式转换 - 零拷贝路径失败应降级到 CPU 路径或重试,而非直接崩溃 ### 模式总结与关联 | # | 模式 | 核心机制 | 复杂度 | |--|------|---------|--------| | 1 | 策略 Trait + 泛型状态 | `trait + State` 单态化 | 中 | | 2 | 多态枚举状态机 | `enum + mem::replace + Intermediate` | 中高 | | 3 | 类型安全帧生命周期 | 4 状态 enum + assert 守护 | 低中 | | 4 | Pin\ 自引用结构 | `PhantomPinned + Box::pin + unsafe` | 高 | | 5 | 独立线程管道 + 原子标志 | `mpsc::channel + AtomicBool` | 低 | | 6 | VRR 感知帧率控制 | 一帧缓冲延迟决策 | 低 | | 7 | 泛型 Dispatch 三层分发 | `impl Dispatch for State` | 中 | | 8 | 三阶段安全构造 | 不同类型 × 消费 self | 低中 | | 9 | 显示器热插拔恢复 | 标志延迟 + 资源分类 + 名称匹配 | 中 | | 10 | 零拷贝 GPU 管道 | DMA-BUF + HW Frame + GPU Filter | 高 | 模式围绕"GPU 加速屏幕录制"协同工作:模式 1(策略 Trait)是架构骨架,模式 2(状态机)是运行时驱动核心,模式 10(零拷贝管道)是性能关键路径。模式 1 被模式 2/3/7/9 使用,模式 5(音频线程)使用模式 8(三阶段构造),模式 10 使用模式 4(Pin\)并被模式 6(帧率控制)调节。