Files
wl-webrtc/analysis.md
dailz 6d49222de8 feat: Phase 1 MVP with audit fixes — Wayland screen capture + VAAPI encoding
Phase 1 MVP implementation of wl-webrtc: Wayland screen capture tool
with hardware-accelerated VAAPI H.264 encoding and WebTransport output.

Includes all 9 runtime bug fixes from code audit (fix-audit-issues plan):

CRITICAL:
- C2: h264_metadata BSF with repeat_sps/repeat_pps in encode pipeline
- C4: FpsLimit wired as timing gate in on_copy_complete

HIGH:
- C3+A2: DRM device discovery via dmabuf feedback MainDevice event,
  unified resolve_drm_path() helper (CLI > compositor > auto > fallback)
- H2: Separate physical_size (mm) from mode_size (pixels) in wl_output
- H1+A3: Multi-output warning + named-output-not-found error

MEDIUM:
- M5: tv_sec u32->u64 to avoid Y2106 timestamp truncation
- M4: Guard against SHM Buffer event (DMA-BUF only)

Key components:
- src/avhw.rs: FFmpeg VAAPI encoder + filter graph + BSF pipeline
- src/state.rs: Wayland event loop + output negotiation + screencopy
- src/cap_wlr_screencopy.rs: wlr-screencopy capture source
- src/fps_limit.rs: Frame rate limiting with configurable target
- src/transform.rs: Frame format conversion utilities
2026-04-05 23:35:00 +08:00

26 KiB
Raw Blame History

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/audiofiltercap_*main.rs核心。main.rs 与 cap_.rs 之间存在双向依赖main 定义 CaptureSource trait 和 State<S>cap_State<Cap*> 实现 Dispatch

2.2 State<S> 全局状态

核心状态结构体持有以下关键字段:

  • in_flight_surface:帧在途状态,跟踪当前帧的捕获生命周期
  • dmaDMA-BUF 协议对象,用于 GPU 缓冲区共享
  • enc:编码器构造状态机(EncConstructionStage),管理从探测到就绪的全过程
  • starting_timestamp:首帧时间戳(纳秒),用于音视频同步
  • args:命令行参数
  • errored:致命错误标志
  • gmWayland 全局对象列表
  • xdg_output_manager:输出几何信息管理器

泛型 S: CaptureSource 使同一个 State 支持两种截屏后端,无需运行时动态分发。

2.3 CaptureSource Trait

定义了屏幕捕获后端的统一接口契约:

  • 关联类型 Frame:每种后端有自己的帧类型
  • new():从 Wayland 全局对象和输出创建后端实例
  • alloc_frame():分配捕获帧,返回 Option<Frame> 统一同步/异步两种模式
  • 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 modifierVulkan 模式接受任意 modifier。

DRM 设备发现两条路径wlr 的 MainDevice / ext 的 DmabufDevice),核心逻辑相同:dev_tDrmNode → Render 节点路径。回退 /dev/dri/renderD128

3.4 Dispatch 泛型分发模式

三种模式:

  • A. 完全泛型Dispatch<ZwpLinuxDmabufV1, ()> for State<S> — 共享协议,通常空实现
  • B. 带状态回调的泛型Dispatch<WlOutput, ()> for State<S> — 需要 'static,含实质逻辑
  • C. 后端专用Dispatch<ZwlrScreencopyFrameV1, ()> for State<CapWlrScreencopy> — 非泛型,含后端特有逻辑

输出探测通过 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 上下文立即释放

帧上下文两种用途:

  • CaptureVulkan flags = SAMPLED | TRANSFER_DSTtiling = Drm(modifiers)
  • EncVulkan flags = VIDEO_ENCODE_SRC_KHR | TRANSFER_DSTtiling = Optimal

4.3 Vulkan 自引用 Pin 模式

AvHwDevCtxVulkanBuffers 包含自引用 C 指针链drm_info → image_fmt_list_info → 内部数组),通过 Pin<Box<Self>> 解决。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_vulkantranspose_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 路径:捕获仍用 GPUDMA-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 → Completenegotiate_format() 创建 EncState
  • Complete → OutputWentAwayon_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<Packet>):环形缓冲,以关键帧为边界裁剪,确保回放可解码
  • 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_rgbvideo_filterenc_videoframes_yuv,但保留 octxhw_device_ctxaudiohistory_state


6. 音频管道与辅助模块

6.1 音频管道

独立线程运行,三阶段构造:

  1. IncompleteAudioState:完成编码器选择、设备打开、解码/编码器创建
  2. AudioHandle:主线程句柄,含 Receiver<Packet> + 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<S: CaptureSource> 泛型参数化。State<CapWlrScreencopy>State<CapExtImageCopy> 编译为两个独立单态化类型,后端选择在启动时确定。alloc_frame() 返回 Option<Self::Frame> 统一了同步和异步两种帧分配模式。 移植要点

  • 适用后端数量有限2-5 个)且进程生命周期内不变的场景;需运行时热切换则改用 trait object
  • State<S>Sized 约束必须,因为 S 作为字段存储;编译膨胀需注意大型 State 的泛型实例化
  • main.rs 定义 trait 而 cap_*.rs 实现它,形成双向依赖,大型项目应将 trait 提取到独立模块

7.2 多态枚举状态机EncConstructionStage

问题Rust 中如何以零开销实现状态机,同时保证状态转换的类型安全? 方案EncConstructionStage<S> 有 5 个枚举变体(ProbingOutputsEverythingButFormatCompleteOutputWentAwayIntermediate),每个携带该状态所需的数据载荷。Intermediate 瞬态 + mem::replace 组合解决了部分借用限制match 解构 &mut self.enc 同时给 self.enc 赋新值。take_enc() 通过消费 self 确保只有含编码器的状态才能被提取。 移植要点

  • 3-7 个状态是 enum 状态机甜蜜点;优势是编译期穷尽检查,添加新状态时所有 match 报编译错误
  • Intermediate 瞬态必须存在,否则 mem::replace 无法满足类型系统要求
  • CompleteOutputWentAway 都持有 EncState 但后者丢弃 cap,体现"保留昂贵资源、丢弃可重建资源"

7.3 类型安全帧生命周期InFlightSurface

问题:异步 DMA-BUF 传输涉及多个阶段,如何防止在错误阶段执行操作? 方案InFlightSurface<S> 是 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<Box> 自引用结构Vulkan Buffers

问题C 库中的链式结构体Vulkan pNext 链内部指针指向同结构其他字段Rust 中移动会使指针失效,如何安全构建? 方案AvHwDevCtxVulkanBuffers 通过 PhantomPinned 标记 !UnpinBox::pin 在堆上分配并固定,get_unchecked_mut 设置自引用指针。Vulkan 结构体的生命周期标记为 'static 作为对 C API 的"善意谎言",实际受 Pin<Box<>> 控制。unsafe 代码集中在 new() 中,使用方完全安全。chain_ptr() 根据有无 DRM modifier 返回不同链头。 移植要点

  • 通用模式,适用于 Vulkan、FFmpeg 硬件加速、内核 IOCTL 等涉及自引用 C 结构的场景
  • 'static 不是真正静态生命周期,而是向 C API 表达"指针在使用期间有效";确保持有者比 C API 使用时间更长
  • 优于 ouroboros crate手写 Pin<Box<>> 逻辑清晰可控,生成的代码可调试

7.5 独立线程管道 + 原子标志(音频线程)

问题:音频需要持续低延迟处理,视频帧率不固定且受 VRR 影响,如何设计无锁跨线程协作? 方案:音频处理完全隔离在独立线程。mpsc::channel 传递已编码 Packet,主线程在视频帧处理间隙通过 try_recv() 非阻塞收取。Arc<AtomicBool> + SeqCst 实现两个控制信号:started(视频首帧时间戳获得后置 true音视频起点对齐flush_flag(退出通知)。音频线程主循环为 pull 模型,生命周期由输入设备驱动。 移植要点

  • AtomicBool 适用于简单布尔信号,比 Mutex<bool> 高效且不死锁;不适用于需要等待/通知的场景
  • 主循环间隙调用 try_recv 是经典的"顺便收取"模式,避免为音频注册额外事件源
  • 适用任何生产者-消费者跨线程场景:传感器采集、网络 I/O 卸载、日志异步写入

7.6 VRR 感知帧率控制FpsLimit

问题VRR 显示器上帧时间戳极不规则,简单"距上帧太近就丢弃"会产生错误决策,如何在不确定的时间戳流中做出正确帧选择? 方案FpsLimit<T> 引入一帧延迟:第一帧直接通过,第二帧存入 on_deck 缓冲,从第三帧起用新帧时间戳判断旧帧是否保留。新帧太近则丢弃缓冲帧,间隔足够则输出缓冲帧。目标时间计算中使用 max 防止回退,正确处理帧跳跃后恢复。零项目内依赖,可直接复制使用。 移植要点

  • 泛型 T 无约束,只做保留/丢弃决策,调用者完全控制帧生命周期
  • 结束时必须调用 flush() 取出缓冲中的最后一帧,否则丢帧
  • 一帧延迟对录屏/编码场景可接受,实时交互场景(如游戏输入)需评估

7.7 泛型 Dispatch 三层分发Wayland 协议)

问题:多后端 Wayland 客户端如何组织 Dispatch 实现,使共享协议代码只写一次、后端专用代码各自独立? 方案:三层分发模式。A. 完全泛型impl<S: CaptureSource> Dispatch<ZwpLinuxDmabufV1, ()> for State<S>,共享协议,通常空实现。B. 带状态回调泛型impl<S: CaptureSource + 'static> Dispatch<WlOutput, ()> for State<S>,需 'static 约束,含实质状态更新逻辑。C. 后端专用impl Dispatch<ZwlrScreencopyFrameV1, ()> for State<CapWlrScreencopy>非泛型在各自后端文件中。Rust trait 系统根据代理类型 × 状态泛型参数 × UserData 自动路由。 移植要点

  • 适用于所有 wayland-client 项目;共享 Dispatch 放 main.rs专用 Dispatch 放各自后端文件
  • 需关联信息时(如 xdg-output 关联 WlOutputTypedObjectId<T> 作为 UserData
  • 'static 约束源自事件循环要求,状态类型必须满足因为回调可能在任意时刻触发

7.8 三阶段安全构造IncompleteAudioState

问题:对象需分多阶段初始化且后续阶段依赖前阶段资源,如何在类型系统中安全表达? 方案:三个不同类型表示三个阶段。IncompleteAudioState 持有输入设备、解码器、编码器(完成 FFmpeg 流创建)。finish(self) 消费不完整状态创建过滤器、FIFO、通道组装 AudioState 并启动线程,返回 AudioHandle(主线程句柄,含 Receiver<Packet> + AtomicBool)。AudioState 通过 move 语义进入线程。finish(self) 而非 finish(&mut self) 保证不完整状态被消费后不再存在。 移植要点

  • typestate pattern 变体,用不同类型(非泛型参数)编码状态,优势是不需要泛型
  • 每阶段恰好分配该阶段所需资源;第一阶段打开设备(可能失败)不浪费线程资源
  • 适用 FFmpeg 管线构建、数据库连接池、GPU 资源分配等"先收集信息、再一次性创建"的场景

7.9 显示器热插拔自动恢复OutputWentAway

问题:长时间录屏中显示器断连/重连,如何保持编码上下文不丢失并自动恢复录制? 方案OutputWentAway 状态机变体实现完整断连恢复。wl_registryGlobalRemove 设置 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<S> 单态化
2 多态枚举状态机 enum + mem::replace + Intermediate 中高
3 类型安全帧生命周期 4 状态 enum + assert 守护 低中
4 Pin<Box> 自引用结构 PhantomPinned + Box::pin + unsafe
5 独立线程管道 + 原子标志 mpsc::channel + AtomicBool
6 VRR 感知帧率控制 一帧缓冲延迟决策
7 泛型 Dispatch 三层分发 impl<S: Trait> Dispatch for State<S>
8 三阶段安全构造 不同类型 × 消费 self 低中
9 显示器热插拔恢复 标志延迟 + 资源分类 + 名称匹配
10 零拷贝 GPU 管道 DMA-BUF + HW Frame + GPU Filter

模式围绕"GPU 加速屏幕录制"协同工作:模式 1策略 Trait是架构骨架模式 2状态机是运行时驱动核心模式 10零拷贝管道是性能关键路径。模式 1 被模式 2/3/7/9 使用,模式 5音频线程使用模式 8三阶段构造模式 10 使用模式 4Pin<Box>)并被模式 6帧率控制调节。