录制结束,不代表视频文件已经交付:AVFoundation 录制完成状态的工程化判断

5 阅读5分钟

问题与范围

实现相机录制时,最容易被低估的不是如何开始,而是何时才算真正完成。一个看似自然的实现往往直接在 finishWriting 回调里返回输出 URL:

// 简化示例:这还不足以代表媒体文件可交付。
writer.finishWriting {
    completion(.success(outputURL))
}

但回调被调用,只能说明写入器的结束流程到达了这里。它本身不能证明当前录制状态允许结束、写入器最终成功、磁盘上确实有文件,或本次会话曾写入任何媒体帧。本文讨论录制层的完成合同,不讨论真机长录制、编码兼容性、播放器可播放性或上传结果。

先给结论

  • 完成回调应同时满足状态条件和产物条件,不能只表示结束 API 被调用。
  • AVAssetWriter 的最终状态为 completed 后,仍应确认输出路径存在实际文件。
  • 零帧、取消、写入器失败和输出缺失应走可区分的失败路径,而不是返回空成功。
  • 成功结果应携带 URL、时间范围、时长等可检查描述,减少上层对录制内部状态的猜测。

结束方法首先是状态机的一部分

录制对象在取消、完成、失败、录制中和暂停中,不能共享同一种结束行为。一个可靠的完成入口应先收紧可接受的新帧,再判断当前状态:已取消返回取消结果;已完成且已有产物时复用该产物;已失败时保留终端错误;只有 recordingpaused 进入真正的 finishing 流程。其他状态不应假设存在可交付媒体。

这层判断避免了两个常见问题:失败后重复驱动 writer 会掩盖原始错误;已完成对象再次结束则可能造成重复回调或重复构造结果。换句话说,finish 不是任意时刻都可以无条件成功的命令,而是受生命周期约束的状态转换。

finishWriting 之后还缺一层产物校验

写入器回调是重要信号,却不是完整的交付证据。更合适的顺序是:检查 writer 的最终状态、确认它为 completed、检查输出 URL 是否对应实际文件、生成产物描述、再把状态更新为 finished。

// 简化示例:只在状态和产物都成立时构造成功结果。
writer.finishWriting {
    guard writer.status == .completed,
          FileManager.default.fileExists(atPath: outputURL.path) else {
        return completion(.failure(.outputMissing))
    }

    completion(.success(makeRecordedClip(at: outputURL)))
}

第三步很容易被省略。即使 writer 显示完成,调用方仍需要一个真实存在的文件才能预览、保存或交给下一段媒体工作流。相反,如果 writer 没有完成,应该回传其终端错误;如果文件缺失,则要报告产物合同被破坏,而不是交付一个未经验证的 URL。

零帧录制应失败,而不是产生空成功

“点击开始后立即结束”是正常边界情况:会话可能已经建立,却没有任何音视频帧进入 writer。控制流程存在不等于媒体产物存在。把它视为成功,会让 UI、上传和诊断层误以为有可交付视频。

因此,成功结果不宜只是布尔值或裸 URL;它至少应描述输出 URL、录制时间范围、时长和媒体段信息。上层可以用这个结果继续预览、保存或安排后续处理,而不必反向猜测录制层做到了什么。它仍不承诺所有播放器都能播放、任意编码环境都兼容,或文件已经上传成功;这些属于完成合同之外的后续验证。

用测试守住调用方真正关心的合同

当前 Kakapos 的 RecorderSink 将这一逻辑写成可回归的行为:正常结束测试会检查返回的 RecordedClip 包含输出 URL、起止时间和时长,检查 fileExists、asset 与实际文件存在;无媒体帧时调用 finishRecording 必须失败,状态进入 failed,且不能遗留输出文件。

这类断言比“completion 被调用了”更贴近交付语义。它把取消、writer 失败、零帧和输出缺失保留为可区分的路径,使 UI 可以提示重新录制,日志可以保留底层错误,后续工作流也不会误把空结果当成文件。

Harbeth 与 Kakapos 如何分层承接

Kakapos 负责相机、录制和媒体生命周期,因此应负责定义并验证录制产物合同。Harbeth 可作为可替换的逐帧 GPU 处理后端,处理图像、纹理、pixel buffer 或 sample buffer;但逐帧处理成功并不能证明媒体文件已落盘。保持这条边界,才能替换帧处理实现而不改变录制完成的可靠性要求。

适用边界

本文结论来自当前源码和 XCTest 的合同覆盖。它没有替代真机摄像头权限、长时间录制、不同编码配置、磁盘空间压力或端到端播放与上传验证;这些都应作为产品交付前的独立测试层。

小结与讨论

录制成功不是“结束回调到了”,而是状态合法、writer 完成、文件存在和产物描述都已成立。把这几个条件固定为同一个结果合同,能让上层少做猜测,也让失败路径更容易诊断。

你会在录制完成后额外检查哪些条件?欢迎分享遇到过的“回调成功但文件不可用”场景。

源码与参考实现