Skip to content

DECODE VIDEO

未竟 edited this page Oct 5, 2026 · 1 revision

Moonward 视频背景的解码与渲染

Moonward(代码命名空间 Starward)的视频背景使用系统 MediaPlayer(Windows Media Foundation 管线)播放:开启帧服务器模式逐帧取出画面,经 Win2D 合成后显示在普通图片控件中。系统缺少的 VP9 与 Vorbis 解码能力由进程内注册的 MFT 补齐,其中 VP9 使用 Starward.Codec 包中的 libvpx 1.16.1(纯软件解码);官方扩展与 GPU 都无法解码的 VP9 高 Profile / RGB 视频,会在后台一次性转码为 H.264,之后改由系统解码器播放。

本文描述的实现以 rebase/develop 分支提交 fb2b3dcd(2026-10-05)为准。

基础概念

解封装与解码

播放一个视频文件分为前后两步:

步骤 处理对象 关心的问题
解封装(demux) 容器格式:MP4、MKV、WebM 等 有几条视频轨和音轨;时间戳与关键帧索引;跳转(seek)到某一时刻对应文件中的哪个位置
解码(decode) 编码格式:H.264、HEVC(H.265)、VP9、Vorbis 等 如何把压缩数据还原为图像帧或音频采样;使用 CPU(软件解码)还是 GPU(硬件解码)

在 Media Foundation 中,解封装由媒体源(media source)完成,解码由 MFT 完成。只补充一个解码 MFT,替换的仅是解码这一步,容器仍须由系统媒体源解析——系统无法解封装的格式,单有解码器也无法播放。Moonward 支持的三种视频容器(.mp4、.mkv、.webm)都由系统完成解封装,软件只补充解码器。

Media Foundation 与 MFT

  • MediaPlayer、MediaSource 等播放 API 底层都基于 Media Foundation(MF)。MF 中的解码器、编码器和效果器都以 MFT(Media Foundation Transform,实现 IMFTransform 接口的 COM 对象)的形式存在。
  • new MediaPlayer() 只创建播放器对象。设置 Source 后,MF 异步打开文件、解析容器、识别各轨道的编码(如 WebM 中的 V_VP9、A_VORBIS),再查找能处理这些编码的 MFT 构建播放管线。
  • MFTRegisterLocal 把 MFT 注册到当前进程:不写注册表、不需要管理员权限,其他进程不可见。MFTEnumEx 排序结果时,本进程注册的 MFT 排在最前,先于按 merit 排序的系统 MFT。

由此产生一个作用于整个进程的特性:libvpx 一旦注册,本进程之后打开的所有 VP9 视频都会优先使用它,即使系统已安装可以硬件解码的官方扩展。项目实测一个 Profile 0 背景因此由约 2% 单核占用升至约 22%。所以软件只在确有需要时注册 libvpx,并在视频背景释放时注销(规则见「WebM 解码器选择」)。

颜色编码与色度抽样

视频通常以 YUV 而不是 RGB 存储颜色:Y 为亮度,U、V(也写作 Cb、Cr)为色度。人眼对色度细节的敏感度低于亮度,因此色度平面可以降低分辨率存储,即色度抽样。以 1920×1080 视频为例:

抽样格式 亮度分辨率 每个色度平面的分辨率 色度数据量(相对 4:4:4)
4:4:4 1920×1080 1920×1080 100%
4:2:2 1920×1080 960×1080(水平减半) 50%
4:2:0 1920×1080 960×540(水平、垂直都减半) 25%

4:2:0 是网络视频最常用的格式,文件更小,硬件解码支持也最广。RGB 存储没有色度抽样,相当于 4:4:4。ffprobe 输出中,yuv420p 表示 8-bit YUV 4:2:0、平面存储(planar,Y、U、V 分别存放);gbrp 表示 8-bit 平面 RGB(按 G、B、R 三个平面存放)。

VP9 Profile

VP9 用 Profile 规定码流可以使用的位深与色度抽样:

Profile 位深 色度抽样 允许 RGB
0 8-bit 4:2:0 否
1 8-bit 4:2:2、4:4:0、4:4:4 是
2 10/12-bit 4:2:0 否
3 10/12-bit 4:2:2、4:4:0、4:4:4 是
  • Profile 0 是网页视频与绝大多数背景视频使用的基础 Profile。Profile 1、2、3 在本文和代码中统称「高 Profile」。
  • RGB 在码流中以帧头 color_space 字段等于 7(CS_RGB)表示,只允许出现在 Profile 1 和 3 中。
  • Direct3D 11/12 视频解码定义的 VP9 解码配置只有两种:8-bit(即 Profile 0)和 10-bit Profile 2,对应 D3D12_VIDEO_DECODE_PROFILE_VP9 与 D3D12_VIDEO_DECODE_PROFILE_VP9_10BIT_PROFILE2。Profile 1/3 没有对应的硬件解码配置,凡是经由 D3D11 / DXVA 做硬件解码的方案(包括 FFmpeg 的 d3d11va、dxva2)对它们都只能软解。
  • 微软商店的 VP9 Video Extensions 可以解码 Profile 0,通常走 GPU 硬件解码;无法解码 Profile 1/3 与 RGB。它对 Profile 2 的支持是否取决于 GPU 的 10-bit VP9 解码能力(待核实:未找到官方说明)。Moonward 不区分这种情况,Profile 2 一律按高 Profile 处理。

VP8 是 VP9 的前代编码(2010 年开源,VP9 于 2013 年发布),只支持 8-bit 4:2:0。Starward.Codec 中的 libvpx MFT 只提供 VP9 解码,代码按「VP8 由官方 VP9 Video Extensions 解码」处理。

官方背景视频的实际规格

2026 年 9 月对本机缓存的 9 个米哈游官方 WebM 背景做 ffprobe 检查,结果如下:

  • 分辨率均为 1920×1080,帧率 30 或 60 fps,r_frame_rate 与 avg_frame_rate 一致(恒定帧率)。
  • 8 个为 VP9 Profile 0、yuv420p,其中 4 个带 Opus 音轨。
  • 1 个为 VP9 Profile 1、gbrp(RGB 4:4:4),时长 16 秒,无音轨。

据项目代码注释,部分官方背景由 After Effects 直接导出为 VP9 Profile 1 + RGB 4:4:4,消费级 GPU 与官方 VP9 扩展都无法解码。此外,绝区零百科较新的好感壁纸为 HEVC 编码的 MP4。

用 ffprobe 检查视频规格

ffprobe -v error -show_entries stream=codec_type,codec_name,profile,level,pix_fmt,color_space,width,height,r_frame_rate,avg_frame_rate,bit_rate -show_entries format=duration,bit_rate -of compact "<视频文件路径>"
字段 含义
codec_name、profile 编码与 Profile,如 vp9、Profile 1
pix_fmt 像素格式,如 yuv420p、gbrp
color_space gbr 表示 RGB
r_frame_rate 能精确表示全部时间戳的最小帧率
avg_frame_rate 按时间戳统计的平均帧率;与 r_frame_rate 相同通常表示恒定帧率,适合循环播放
level VP9 帧头没有 level 字段,ffprobe 对 VP9 常显示 -99(未知),不影响播放
流级 bit_rate WebM 轨道头不记录码率,显示 N/A;整体平均码率见 format 级 bit_rate

播放流程

AppBackground.StartMediaPlayerAsync 启动视频背景,步骤如下:

  1. 选择播放文件:该 WebM 已有转码产物时,改播产物(见「高 Profile / RGB 视频的转码」)。
  2. HEVC 预检:.mp4、.mkv、.mov 的视频轨为 HEVC 而系统没有 HEVC 解码器时,不创建播放器,改显示默认背景图并提示安装「来自设备制造商的 HEVC 视频扩展」。安装后主窗口再次激活时自动重试。
  3. 准备 WebM 解码器:按「WebM 解码器选择」的规则决定是否注册 libvpx;高 Profile / RGB 视频首次播放时先等待转码。
  4. 注册 Vorbis 解码器:所有视频背景都注册,以覆盖带 Vorbis 音轨的 WebM / MKV。
  5. 创建媒体源:文件不超过 32 MiB 时读入 InMemoryRandomAccessStream 循环播放;超过或读入失败时使用文件 URI。
  6. 创建播放器:外包一层 MediaPlaybackItem 以监听视频轨打开失败;MediaPlayer 开启循环播放与帧服务器模式(IsVideoFrameServerEnabled = true),关闭系统媒体传输控件(SMTC)与命令管理器。只有该游戏启用了自定义背景时才使用设置中的音量,其余情况静音。

同一时刻只认最后发起的那次启动:新的启动或背景切换会取消仍在进行中的旧启动(例如正在等待转码的那次),避免切换后旧视频又被拉起。

WebM 解码器选择

视频 已安装官方 VP9 扩展 未安装官方 VP9 扩展
VP9 Profile 0(非 RGB) 不注册 libvpx,由官方扩展解码 官方背景:不播放视频,改显示该背景的静态图并提示安装扩展。自定义背景:注册 libvpx 软解,并在本次运行中提示一次「安装扩展可降低 CPU 占用」
VP9 高 Profile 或 RGB 注册 libvpx;首次播放时等待转码 同左
VP8 不注册 libvpx,由官方扩展解码 官方背景:同 Profile 0。自定义背景:交给系统尝试,解码失败时提示安装扩展

判断方式(均在 src/Starward/Features/Codec/VP9Helper.cs):

  • 是否安装官方扩展(IsVP9DecoderInstalled):在注册表 HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\Repository\Packages 下查找以 Microsoft.VP9VideoExtensions 开头的包。
  • 是否为高 Profile / RGB(IsVP9HighProfileOrRGB):只读文件前 1 MiB,确认 EBML 头与 DocType 为 webm 后,查找 SimpleBlock / Block 元素,跳过块头,按 VP9 码流规范解析第一个可解析帧头中的 profile 与 color_space。不做完整解封装与解码,开销很小。
  • 是否为 VP8(IsVP8VideoFile):在前 1 MiB 中查找 CodecID V_VP8。

补充说明:

  • 「官方背景改显示静态图」只在该背景同时提供静态图时生效。这一状态与用户手动暂停视频相同并会被记住;安装扩展后,需要手动点击播放才会重新检查并播放视频。
  • libvpx 的注册作用于整个进程(见「Media Foundation 与 MFT」),在视频背景资源释放时(DisposeVideoResource,即切换到其他背景时)注销。

高 Profile / RGB 视频的转码

libvpx 只能软件解码,据代码注释,1080p60 的高 Profile 视频持续软解会占用大半个 CPU 核心。VideoTranscodeService 在后台把这类 WebM 一次性转码为 H.264 MP4,之后的播放由系统 H.264 解码器完成,有 GPU 支持时为硬件解码。

实现

  • 解码:用 MFCreateSourceReaderFromURL 创建 SourceReader。据代码注释,SourceReader 会使用本进程以 MFTRegisterLocal 注册的 libvpx,MediaTranscoder 不会,因此未使用后者。
  • 编码:SinkWriter 使用系统内置的 H.264 编码器(允许使用硬件 MFT),输出 H.264 High Profile、逐行扫描,帧率与源一致。目标码率为「宽 × 高 × 帧率 × 0.13」,限制在 4–30 Mbps 之间,1080p60 约 16 Mbps。
  • 整个过程只使用系统组件与已注册的 libvpx,不引入第三方编解码二进制。

规则与原因

规则 原因
只转 VP9 高 Profile / RGB 的 WebM,VP8 与 Profile 0 跳过 Profile 0 可由官方扩展硬件解码,转码只多占磁盘
源文件带音轨时不转码,继续软解 当前只转视频轨,转码会丢失声音
游戏运行时不转码,下次播放时再尝试 避免与游戏争抢 CPU 与编码器
同一时刻只转一个文件;单个文件超过 5 分钟视为失败 限制资源占用
首次播放最多等待 10 秒(含排队);超时则先用 libvpx 软解播放,转码在后台继续,下次播放直接用产物 官方背景(约 5 秒 1080p60)硬件编码约 3 秒、多核软件编码 2.5–7.6 秒,只有单线程软件编码或较长的自定义视频会超时
转码服务自身不注册 libvpx,借用播放端已有的注册 本进程注册的 MFT 优先级最高:若由转码服务在后台注册,会截走随后打开的 Profile 0 播放器;若由它注销,又可能拆掉背景正在使用的注册
向 SourceReader 请求 RGB32 输出,而不是 NV12 libvpx MFT 解码 Profile 1 时只输出 RGB32;请求 NV12 时协商显示成功,但样本仍是 RGB32,编码结果整片错色
帧率优先用另一个 SourceReader 按样本时间戳测量 WebM 的媒体类型上没有 MF_MT_FRAME_RATE;编码器按声明的帧率取舍样本,声明偏低会被抽帧(早期版本回落到 30 fps,导致 60 fps 背景丢掉一半帧)
临时文件写到 <数据目录>\cache\transcode,文件名以 .mp4 结尾 写在 bg 中会被背景图库列出;MFCreateSinkWriterFromURL 按扩展名选择封装器,使用 .tmp 会返回 MF_E_NOT_FOUND

<数据目录> 指用户所选目录下的 data 文件夹。

产物管理

  • 产物与原片同放在 <数据目录>\bg,命名为「原文件名 + .mp4」,例如 <MD5>_<编号>.webm.mp4。
  • 播放端改用产物后,后台校验产物完整,再删除 bg 中的原片;校验不通过则保留原片。设置中记录的仍是原片文件名。
  • 官方背景文件名为「内容 MD5_编号.webm」,不同区服的同一视频只有编号不同,可共用同一个产物。
  • 产物的修改时间早于原片(原片被同名替换)时视为失效,重新转码。

帧服务器渲染

默认模式下,MediaPlayer 把视频交给 MediaPlayerElement 自行呈现,应用拿不到帧内容。开启帧服务器模式后,MediaPlayer 不再呈现视频,而是在新帧可用时触发 VideoFrameAvailable,由应用调用 CopyFrameToVideoSurface 把帧复制到 IDirect3DSurface,再自行处理和显示。Moonward 用这一模式完成三件事:把官方背景的主题叠加图(theme overlay)合成到视频上;窗口隐藏时冻结画面;抓取当前帧(如生成抽卡分享图)。

每帧的处理:

  1. VideoFrameAvailable 在非 UI 线程触发。若上一帧仍在处理(信号量被占用)则丢弃本帧,否则切换到 UI 线程。
  2. 首帧按 NaturalVideoWidth / NaturalVideoHeight 创建 Win2D 的 CanvasRenderTarget(接收解码帧)与 CanvasImageSource(用于显示),并把后者设为背景 ImageEx 控件的 Source。
  3. 调用 CopyFrameToVideoSurface 把帧复制到 CanvasRenderTarget,再在 CanvasImageSource 的绘制会话中画出视频帧,并以 HighQualityCubic 插值叠加主题图。

窗口状态的处理:

  • 隐藏到托盘:不释放播放器。先把当前帧冻结为 CPU 端的 WriteableBitmap 顶替显示,再暂停。原因是 CanvasImageSource 属于 SurfaceImageSource,窗口隐藏后合成器会丢弃其像素,恢复时直接显示它会先闪一下空白。
  • 恢复显示:继续播放,等下一帧画完再把 Source 切回 CanvasImageSource。
  • 锁屏:暂停。
  • 释放:先退订 VideoFrameAvailable 再 Dispose。否则已在排队的帧回调会对已释放的播放器取帧,把定格画面清成空白。

其他使用视频解码的位置:

  • 好感壁纸悬停预览:静音播放,使用普通模式的 MediaPlayerElement 呈现;WebM 解码器的注册规则与背景相同。
  • 视频缩略图:未安装官方扩展、或高 Profile / RGB 的 WebM,用帧服务器模式取第一帧保存为 JPEG;其他视频使用系统缩略图(StorageFile.GetThumbnailAsync)。

解码失败的检测与提示

  • 系统缺少某种视频编码的解码器时,MediaPlayer 不触发 MediaFailed,只有对应的 VideoTrack 触发 OpenFailed(实测错误码 0xC00D5212,找不到解码器)。此时音频照常播放、进度照常推进,但没有任何画面。软件因此监听 OpenFailed:触发后停止播放、显示默认背景图并提示。
  • 不使用 VideoTrack.SupportInfo.DecoderStatus 预判能否解码:它只反映该编码是否注册了解码器(VP9 Profile 1 一帧都解不出时也报告 FullySupported),也无法确认是否计入本进程注册的 libvpx,未装扩展时可能把能播放的 WebM 误判为不能播放。
  • HEVC 缺解码器时同样不会触发 MediaFailed,所以在播放前预检(见「播放流程」第 2 步)。HEVC 解码器是否存在通过 MFTEnumEx 枚举视频解码 MFT 判断,商店扩展与显卡驱动注册的硬件 MFT 都计入;查询结果为「没有」时,最多每 5 秒重新查询一次,以便安装扩展后生效。

界面提示:

  • 需要安装 VP9 视频扩展(带下载按钮):官方背景为 VP8 或 Profile 0 且未安装扩展时显示;不会自动消失,关闭前不重复弹出。
  • 建议安装 VP9 视频扩展以降低 CPU 占用:自定义背景为 Profile 0、未安装扩展而改用 libvpx 软解时显示,每次运行最多一次。
  • 视频解码失败,请安装 VP9 视频扩展:未安装扩展时播放 VP8 且收到 MediaFailed 时显示。
  • HEVC 视频扩展提示:HEVC 视频缺少解码器时显示。
  • 视频解码失败:其他解码失败。

解码路径汇总

视频 实际使用的解码器 解码方式
H.264 MP4(含转码产物) 系统 H.264 解码器 有 GPU 支持时为硬件
HEVC,系统有 HEVC 解码器 HEVC 视频扩展或显卡驱动注册的 MFT 通常为硬件
HEVC,系统无 HEVC 解码器 不播放,显示默认背景图 —
VP9 Profile 0,已安装官方扩展 官方扩展 通常为硬件
VP9 Profile 0,未安装官方扩展 官方背景:不播放,显示静态图;自定义背景:libvpx 软件
VP9 高 Profile / RGB,已转码 系统 H.264 解码器 有 GPU 支持时为硬件
VP9 高 Profile / RGB,未转码(首播等待超时、带音轨、转码失败、游戏运行中) libvpx 软件
VP8,已安装官方扩展 官方扩展 取决于扩展与 GPU
VP8,未安装官方扩展 官方背景:不播放,显示静态图;自定义背景:交给系统尝试,失败时提示安装扩展 —

备选方案与取舍

方案 说明 代价与限制
基于 libvpx 自建播放 libvpx 是 Google 的 VP8/VP9 参考实现,只提供编解码 需自行实现解封装、音画同步、循环与渲染;只有软件解码
FFmpeg / FFmpegInteropX FFmpegInteropX 的 FFmpegMediaSource 在内部用 FFmpeg 完成解封装与解码,支持 D3D11 硬件解码,不可用时回退到 FFmpeg 软件解码。FFmpeg 自带独立实现的 VP9 解码器(vp9),也可调用 libvpx(libvpx-vp9) 需随应用分发 FFmpeg DLL 并遵守其 LGPL/GPL 许可;VP9 Profile 1/3 同样没有 D3D 硬件解码配置,仍是软解
把 FFmpeg 解码器封装为 MFT 继续使用 MediaPlayer,只替换解码这一步 仍需分发 FFmpeg;容器解析仍依赖 MF 媒体源
libmpv 完整的播放引擎,自带解码、渲染与帧同步 无法作为 MFT 嵌入 MF 管线;在 WinUI 3 中显示需要自行做渲染互操作,并放弃 MediaPlayer

当前方案保留 MediaPlayer,以本地 MFT 补齐 VP9 与 Vorbis,再把需要长期软解的高 Profile / RGB 视频一次性转码为 H.264。常见格式由此走系统或官方扩展的硬件解码,也不引入 FFmpeg 依赖与许可负担;代价是转码产物占用磁盘、首次播放最多等待 10 秒,且带音轨的高 Profile 视频仍需持续软解。

限制与注意事项

  • libvpx 注册期间,本进程打开的所有 VP9 视频(包括 Profile 0)都使用 libvpx 软解,即使已安装官方扩展。新增调用 VP9Helper.RegisterVP9Decoder 的位置时,需要同时考虑何时注销。
  • 带音轨的高 Profile / RGB WebM 不会被转码,只能持续软解。
  • 官方背景因未安装扩展而改显示静态图后,安装扩展不会自动恢复视频,需要用户手动点击播放。
  • 缺少解码器时 MediaFailed 不一定触发,判断「视频能否播放」需要同时监听 VideoTrack.OpenFailed。

参考资料

Clone this wiki locally