深入Mira Screenshare源码:Windows.Graphics.Capture截屏引擎与D3D11 GPU转NV12像素格式原理
2026/9/19 7:03:16 网站建设 项目流程

深入Mira Screenshare源码:Windows.Graphics.Capture截屏引擎与D3D11 GPU转NV12像素格式原理

【免费下载链接】sharerA screen-sharing / remote collaboration software written in Rust项目地址: https://gitcode.com/gh_mirrors/sha/sharer

Mira Screenshare 是一款基于 Rust 的开源屏幕共享 / 远程协作软件,可实现 4K 60FPS、约 110ms 端到端低延迟的屏幕串流。本文带你深入它的源码,剖析Windows.Graphics.Capture 截屏引擎如何高效抓帧,以及D3D11 GPU 如何把 BGRA 屏幕帧转换为 NV12 像素格式——这正是它能在 CPU 上"零拷贝"喂给 x264 编码器的关键。

整个数据流可以概括为一条 GPU 管线:

Windows.Graphics.Capture (截屏帧池) │ BGRA8 D3D11 纹理 ▼ D3D11 GPU 着色器转换 (Y pass + UV pass) │ NV12 (亮度平面 + 色度平面) ▼ x264 硬件级编码 → WebRTC 串流出站

截屏引擎:Windows.Graphics.Capture 帧池是如何工作的

在 Windows 上,Mira 使用系统原生的Windows.Graphics.CaptureAPI(要求 Win10 1803+)。其核心实现在 src/capture/wgc/wgc_capture.rs。

关键设计点有四个 🖥️:

  1. 自由线程帧池:通过Direct3D11CaptureFramePool::CreateFreeThreaded创建一个容量为 3 的帧池,像素格式指定为BGRA8_UNORMFreeThreaded意味着系统可以在任意线程投递帧,避免截屏被绑定到 UI 线程 src/capture/wgc/wgc_capture.rs#L44-L50。

  2. 事件驱动 + 无锁通道:系统每次屏幕刷新(画面变化)都会触发FrameArrived事件,回调里用TryGetNextFrame取出帧,再通过容量为 1 的mpsc通道try_send给主循环——一旦处理不过来就自然丢帧,保证"永远抓最新画面" src/capture/wgc/wgc_capture.rs#L95-L107。

  3. 帧率节流:主循环用tokio::time::intervalconfig.max_fps打节拍,配合select!同时监听帧到达与停止信号 src/capture/wgc/wgc_capture.rs#L112-L140。

  4. WinRT 与 D3D11 双世界桥接:WGC 的帧池只认 WinRT 的IDirect3DDevice,而着色器管线用的是ID3D11Device。Mira 在 src/capture/wgc/d3d.rs 中用CreateDirect3D11DeviceFromDXGIDevice把同一个底层 DXGIDevice 包装出两种接口,一套设备通吃两条管线;若硬件驱动不支持(DXGI_ERROR_UNSUPPORTED),还会自动回退到 WARP 软件光栅化器,保证任何机器都能跑 src/capture/wgc/d3d.rs#L36-L54。

D3D11 GPU 转 NV12:一次拷贝 + 两趟着色器

拿到帧后,WGC 给的是 BGRA 纹理,而 x264 最擅长吃的却是NV12(Y 平面满分辨率 + U/V 交错半分辨率)。如果放 CPU 上做矩阵变换,4K 帧动辄要十几毫秒;Mira 的做法是全程在 GPU 上完成,核心类YuvConverter位于 src/capture/yuv_convert/yuv_converter.rs。

整个转换只有三步 ⚡️:

第一步:CopyResource 拉入自己的纹理

把系统截屏纹理通过CopyResource拷到一块 BGRA 后端纹理上。这一步是纯 GPU 显存拷贝,CPU 完全不参与 src/capture/yuv_convert/yuv_converter.rs#L100-L107。

第二步:两趟全屏着色器,分别渲染 Y 与 UV

着色器源码见 PixelShaderY.hlsl 与 PixelShaderUV.hlsl,采用BT.709 HDTV 色彩矩阵

  • Y pass:亮度着色器对每个像素做Y = RGB · [0.182585, 0.614230, 0.062007] + 0.0625,输出到一块R8_UNORM(满分辨率)渲染目标 PixelShaderY.hlsl#L38-L43;
  • UV pass:色度着色器做 3×2 矩阵乘法得到(U, V),输出到R8G8_UNORM半分辨率(W/2 × H/2)渲染目标 PixelShaderUV.hlsl#L38-L43。

注意 UV 平面分辨率减半——这正是 NV12 的 4:2:0 色度抽样要求。几何部分简单到"令人发笑":仅 6 个顶点拼成两个三角形铺满整个屏幕(全屏四边形),顶点着色器是恒等映射 dx_math.rs#L27-L76。

更妙的是纹理格式的选择:R8(1 字节/像素)正好装 Y,R8G8(2 字节/像素)正好装交错的 UV——渲染目标本身就是标准的 NV12 内存布局,无需任何打包转换。

第三步:staging 纹理 + Map 回读 CPU

GPU 渲染结果无法直接给 CPU 读,Mira 为 Y、UV 各建了一块D3D11_USAGE_STAGING可读纹理:先CopyResource把渲染纹理拷进 staging 纹理,再Map(D3D11_MAP_READ)把指针拿到用户态,最终组装成YUVFrame(含luminance_bytes/chrominance_bytes双平面与各自 stride)交给编码器 src/capture/yuv_convert/yuv_converter.rs#L181-L243,结构体定义见 src/capture/frame.rs。

💡 一次回读 = 一帧 Y 平面(W×H 字节)+ 一帧 UV 平面(W×H/2 字节)。对 4K 而言约 11.25MB,远小于 BGRA 的 24.5MB——色彩空间转换顺带把数据量砍了 54%。

为什么是 NV12?——为 x264 而生

编码器侧通过encoder::encode(FrameData::NV12(...))直接把两平面指针交给 FFmpeg/x264 硬件编码通路 src/capture/wgc/wgc_capture.rs#L121-L128。NV12 是 x264、NVENC 等硬件编码器原生的输入格式,选它意味着:从截屏到编码之间没有任何 CPU 像素格式转换,整条链路只剩 GPU 拷贝和 GPU 矩阵变换。编码质量、速度等参数可在configs/目录的预设配置中调整,例如 configs/config.libx264.toml 与 configs/config.nvenc.toml。

小结:这套设计的三个精髓

设计收益
CreateFreeThreaded帧池 +try_send丢帧截屏永不阻塞,画面永远最新
GPU 双 pass 着色器直出 NV12 布局色彩转换零 CPU 开销,数据量减半
硬件驱动失败自动回退 WARP任何机器(含远程桌面)都能降级运行

配合max_fps节流与PerformanceProfiler逐阶段计时(见 src/performance_profiler.rs),这套"截屏引擎 + GPU 转格式"的组合拳,正是 Mira Screenshare 能做到 4K 60FPS 低延迟串流的核心。

想动手读源码?克隆仓库后即可按上文路径逐文件对照:

git clone https://gitcode.com/gh_mirrors/sha/sharer

【免费下载链接】sharerA screen-sharing / remote collaboration software written in Rust项目地址: https://gitcode.com/gh_mirrors/sha/sharer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询