问题与范围
在 Swift / AVFoundation 的视频预览或离线导出链路中,处理器常常只暴露“给我一帧、我还你一帧”的接口。但当一帧进入处理前,至少有三个问题不能留给下游猜测:它是什么像素格式、它携带的是 SDR/HLG/PQ 哪一种动态范围,以及处理之后原来的色彩信息是否仍可被认为保留。
没有这些边界时,错误可能表现为某台设备上处理失败,也可能是预览看似正常、导出后才暴露偏色。本文讨论怎样把这类前置条件写成媒体处理合同;不讨论本次真机画质、性能、不同解码器组合或最终导出验收。
先给结论
- 处理器应显式声明可接受的像素格式与动态范围,而不是在内部静默猜测输入。
- 在真正启动处理前拒绝不匹配的帧,能把格式故障固定在有意义的边界。
- HDR 转 SDR 是有意改变色彩语义的转换;输出 metadata 与附件必须随之更新。
unknown动态范围不是 SDR,宁可失败也不要把不确定性伪装成确定结果。
能处理什么,不应该靠内推断
Kakapos 的 FrameProcessor 异步处理 MediaFrame。一个处理器还可以提供 FrameProcessorCapabilities,其中的 acceptedPixelFormats、supportedDynamicRanges 和 preservesColorInformation 分别表达输入像素格式、输入动态范围与输出是否保留色彩信息。
这不是增加一组配置而已。像素格式决定处理器面对的是 BGRA、YUV 或其他 buffer 表示;动态范围决定输入是标准动态范围还是 HLG、PQ 等 HDR 语义;而色彩保留标记让下游知道结果还能否被当作未改变色彩语义的帧。空集合表示该维度不限制准入,不是“未知”。
拒绝输入,通常比隐式转换更诚实
processFrame(using:frame:completion:) 会在调用处理器前检查能力。若处理器声明了能力、帧 metadata 也提供了 format、但该 format 不被接受,它回调 .failure(.unsupportedFormat(format)) 并直接返回 nil,不会启动处理器。
这个取舍很关键。隐式把 YUV 转成 BGRA 有时确实方便,但转换会改变资源开销,也可能改变色彩或动态范围语义。如果转换不是一个明确的处理步骤,调用方就不知道它是否发生过。能力不匹配时尽早拒绝,调用方才能有意识地选择更换处理器、插入显式转换,或调整上游输出。
可取消处理器是另一条独立合同:只有处理实际启动后,processFrame 才会返回底层的取消操作。格式不匹配的前置失败没有已经启动的任务可取消。
下面是说明这一顺序的简化伪代码,不是 Kakapos 的原样 API:
if let format = frame.metadata.format,
!processor.capabilities.accepts(format) {
completion(.failure(.unsupportedFormat(format)))
return nil
}
return processor.processCancellable(frame, completion: completion)
HDR 转 SDR 不能靠猜
StandardDynamicRangeFrameProcessor 给出了更具体的例子。源帧本来是 SDR 时,它复用原 pixel buffer,但重新发布当前实际 format。源帧为 HLG 或 PQ 时,它创建 BGRA 输出 buffer,用 Core Image 做 tone map,写入 BT.709/sRGB attachment,并移除旧的 YCbCr matrix。输出的动态范围成为 standard,同时能力标记 preservesColorInformation 为 false。
这不是实现细节:tone mapping 是一次有意的语义变化,不能让下游继续认为原始色彩信息完整保留。相反,当输入动态范围是 unknown 时,该处理器返回失败,而不是猜成 SDR。未知不等于标准动态范围;把不确定输入默认化,常常只会把问题推迟到预览、导出或存储之后。
测试验证的是合同,而不是所有观感
当前 Kakapos 集成测试验证了 BGRA8 + standard 可被声明为接受、YUV 不满足同一声明;验证了从 pixel buffer 读取 BT.2020/HLG 附件,以及无法识别的十位 transfer 不会被猜成 SDR。HDR tone-map 测试进一步检查:输出为 BGRA、metadata 为 standard、transfer attachment 为 sRGB、旧 YCbCr matrix 被移除,并且结果标记为不保留色彩信息。
这些 XCTest 能证明格式与 metadata 变换的确定性合同;它们不能替代真实设备上的视觉比较、性能测试、不同解码器组合或端到端导出验收。把这层限制写清楚,才能避免把“测试通过”说成“所有画面都已验证”。
Harbeth 与 Kakapos 如何分层承接
Kakapos 负责媒体生命周期和处理编排,因此应在帧进入处理器前守住能力准入。Harbeth 是独立的逐帧 GPU 图像处理引擎:它读取 pixel buffer 的格式、plane 和颜色模型,并在生成兼容输出 pixel buffer 时复制参考 buffer 的附件。这说明底层处理同样需要显式对待输入与输出合同,但不代表两者在当前源码中被直接绑定,也不把 Harbeth 变成相机或视频编辑 SDK。
小结与讨论
处理器无法安全理解一帧时,尽早失败不是能力不足,而是让系统保持诚实。把格式和动态范围声明为能力、把转换写成显式步骤、把语义变化发布给下游,再用测试固定这些边界,能让预览和导出问题更早被定位。
当你的处理器遇到不支持的像素格式时,会选择直接拒绝、显式转换,还是交给下游兜底?欢迎结合 frame metadata 与测试策略讨论。