WWDC Quick Look 💓 By SwiftGGTeam
Support Cinematic mode videos in your app

Support Cinematic mode videos in your app

观看原视频

Highlight

iOS 17/macOS Sonoma 引入 Cinematic API(前缀 CN),让第三方应用可以播放和编辑 iPhone 13/14 拍摄的电影效果模式视频,支持实时调整光圈、无损修改焦点决策、保存/加载编辑状态。

核心内容

电影效果模式的两层文件结构

iPhone 拍摄的电影效果模式视频包含两个文件。一个是渲染后的普通 QuickTime 电影,带有景深效果,可以直接分享播放。另一个是特殊的电影效果资产,包含所有原始数据和编辑所需信息。

电影效果资产有三个轨道。视频轨道是原始拍摄画面,最高支持 4K@30fps。视差轨道记录双摄像头的像素偏移,近处物体偏移大,远处物体偏移小,这个数据驱动景深渲染。元数据轨道包含渲染属性(焦点视差和光圈 f 值)和电影脚本(自动检测到的人脸、头部、躯干,以及焦点决策记录)。

焦点决策的层级机制

拍摄时 Cinematic 引擎自动检测场景中的对象并决定焦点。这些自动决策叫 base decision。后期编辑时,用户可以插入两种用户决策:weak decision 只持续到下一个 base 或 user decision;strong decision 会覆盖后续所有 base decision,直到检测对象离开画面或用户再次手动切换焦点。

这种层级设计让用户可以只修改片段中的部分焦点,其余部分保持自动决策,实现精细的叙事控制。

实时渲染的数据流

播放或编辑时,Cinematic 引擎读取当前帧的渲染属性,结合视差图计算浅景深效果。焦点切换时引擎会提前开始 rack focus,像专业跟焦员一样预判关键帧,产生平滑的过渡效果。

详细内容

获取电影效果资产

08:16

从 Photos 库获取电影效果资产:

// 使用 Photos picker 筛选电影效果视频
let picker = PHPickerViewController(configuration: config)

// 获取资产标识后请求原始版本
let options = PHVideoRequestOptions()
options.version = .original
options.isNetworkAccessAllowed = true // 资产可能在 iCloud

PHImageManager.default().requestAVAsset(
    forVideo: phAsset,
    options: options
) { avAsset, _, _ in
    // avAsset 包含电影效果资产的所有轨道
}

关键点:

  • version = .original 必须设置,否则只能拿到渲染后的普通视频
  • isNetworkAccessAllowed = true 处理 iCloud 上的资产
  • AirDrop 传输时选择 “All Photos Data” 才能保留完整电影效果数据

设置渲染会话

10:08

// 1. 从资产获取渲染属性
let assetInfo = try await CNScript.loadFromAsset(asset)
let renderingAttributes = assetInfo.renderingAttributes

// 2. 创建渲染会话
let session = try CNRenderingSession(
    attributes: renderingAttributes,
    commandQueue: metalCommandQueue
)

// 3. 设置渲染质量
session.quality = .export // 或 .preview

关键点:

  • CNRenderingSession 需要 Metal command queue,渲染在 GPU 上执行
  • .preview 适合实时播放,.export 适合最终输出
  • CNScript.loadFromAsset 异步加载资产的脚本数据

构建自定义视频合成器

10:53

// 将资产轨道添加到 composition
let composition = AVMutableComposition()
let cinematicComposition = try await composition.loadCinematicCompositionInfo(
    from: assetInfo
)

// 获取电影脚本
let script = assetInfo.script

// 创建渲染指令
let instruction = CinematicVideoCompositionInstruction(
    renderingSession: session,
    composition: cinematicComposition,
    script: script,
    aperture: fNumber // 从 UI 获取的光圈值
)

// 构建视频合成
let videoComposition = AVMutableVideoComposition(
    propertiesOf: composition,
    instruction: instruction
)
videoComposition.customVideoCompositorClass = CinematicCompositor.self

// 播放
let playerItem = AVPlayerItem(asset: composition)
playerItem.videoComposition = videoComposition
let player = AVPlayer(playerItem: playerItem)

关键点:

  • loadCinematicCompositionInfo 一步完成多轨道添加
  • CinematicVideoCompositionInstruction 封装渲染参数
  • 自定义 compositor 在 startRequest 中调用 Cinematic 渲染器

自定义合成器中的渲染流程

12:10

class CinematicCompositor: NSObject, AVVideoCompositing {
    func startRequest(_ request: AVAsynchronousVideoCompositionRequest) {
        // 获取源缓冲区
        let videoBuffer = request.sourceFrame(
            byTrackID: cinematicComposition.videoTrackID
        )
        let disparityBuffer = request.sourceFrame(
            byTrackID: cinematicComposition.disparityTrackID
        )
        let metadataBuffer = request.sourceFrame(
            byTrackID: cinematicComposition.metadataTrackID
        )
        
        // 创建输出缓冲区
        let outputBuffer = ...
        
        // 从元数据获取帧渲染属性
        var frameAttributes = session.frameAttributes(
            from: metadataBuffer
        )
        
        // 应用用户调整:修改光圈
        if let aperture = instruction.aperture {
            frameAttributes.aperture = aperture
        }
        
        // 应用用户调整:修改焦点(从更新后的脚本获取)
        let scriptFrame = script.frame(at: request.compositionTime)
        frameAttributes.focusDisparity = scriptFrame.focusDisparity
        
        // 编码渲染
        let commandBuffer = commandQueue.makeCommandBuffer()!
        session.encodeRender(
            frameAttributes: frameAttributes,
            frame: videoBuffer,
            disparity: disparityBuffer,
            output: outputBuffer,
            commandBuffer: commandBuffer
        )
        
        commandBuffer.addCompletedHandler { _ in
            request.finish(withComposedVideoFrame: outputBuffer)
        }
        commandBuffer.commit()
    }
}

关键点:

  • metadataBuffer 提取 CNRenderingFrameAttributes,这是渲染的核心输入
  • 光圈和焦点视差可以在渲染前修改,实现实时调整
  • 焦点视差从更新后的 CNScript 帧获取,包含平滑过渡计算
  • 渲染完全在 GPU 上完成,通过 Metal command buffer 调度

绘制检测框

18:09

// 获取当前时间的脚本帧
let scriptFrame = script.frame(at: compositionTime)

// 遍历所有检测对象绘制矩形
for detection in scriptFrame.detections {
    let rect = detection.rect
    // 用白色绘制检测框
    drawRect(rect, color: .white)
}

// 焦点检测用黄色高亮
if let focusDetection = scriptFrame.focusDetection {
    let rect = focusDetection.rect
    drawRect(rect, color: .yellow)
}

关键点:

  • scriptFrame.detections 包含当前帧所有人脸、头部、躯干检测
  • focusDetection 是当前焦点所在的检测对象
  • 检测框可以按需筛选,比如只显示人脸

修改焦点决策

19:40

// 用户点击画面时判断点击位置是否在检测对象内
for detection in scriptFrame.detections {
    if detection.rect.contains(tapPoint) {
        // 根据点击方式决定决策强度
        let strength: CNDecisionStrength = isDoubleTap ? .strong : .weak
        
        let decision = CNDecision(
            time: compositionTime,
            detectionID: detection.detectionID,
            strength: strength
        )
        
        // 添加到脚本
        script.addUserDecision(decision)
        break
    }
}

关键点:

  • CNDecisionStrength.weak 只持续到下一个自动或用户决策
  • CNDecisionStrength.strong 覆盖后续所有自动决策,直到检测对象消失
  • 添加决策后,后续帧的 focusDisparity 会自动跟随新焦点
  • 引擎会提前开始 rack focus,产生平滑过渡

保存和加载编辑

22:19

// 保存编辑
let scriptChanges = script.changes
let data = scriptChanges.dataRepresentation()
try data.write(to: editFileURL)

// 加载编辑
let loadedData = try Data(contentsOf: editFileURL)
let loadedChanges = try CNScript.Changes(dataRepresentation: loadedData)
let newScript = try CNScript(asset: asset, changes: loadedChanges)

关键点:

  • 编辑以独立数据文件存储,原始资产不被修改
  • dataRepresentation() 生成紧凑的二进制格式
  • 加载时可以同时传入原始脚本和修改,自动合并
  • 随时可以丢弃 changes 回退到原始状态

核心启发

  1. 电影效果视频编辑器

    • 做什么:做一个简化版 iMovie,专门编辑电影效果视频的焦点和光圈
    • 为什么值得做:Cinematic API 提供了完整的无损编辑能力,单指/双指点击切换焦点的交互直观自然
    • 怎么开始:用 PHPickerViewController 筛选电影效果视频,搭建 CNRenderingSession + 自定义 AVVideoCompositing,实现点击切换焦点的交互
  2. 自动叙事生成工具

    • 做什么:根据视频内容自动生成多种焦点叙事版本(悬疑版、喜剧版、温情版)
    • 为什么值得做:Cinematic 脚本中的检测数据暴露了场景中的所有人脸和对象,可以基于规则引擎自动编排焦点切换时机
    • 怎么开始:解析 CNScript 的检测时间线,根据人脸朝向、距离、进入/离开画面的时机,用算法生成不同的决策序列
  3. 景深视频社交平台

    • 做什么:用户上传电影效果视频后,观众可以在播放时点击画面切换焦点,发现隐藏细节
    • 为什么值得做:strong/weak decision 机制让观众可以临时切换焦点而不破坏作者的叙事,增加互动性
    • 怎么开始:后端存储原始电影效果资产和用户的编辑 changes,前端用 CNRenderingSession 实时渲染,点击时插入 weak decision
  4. 视频封面智能生成

    • 做什么:从电影效果视频中自动提取焦点清晰、景深最美的帧作为封面
    • 为什么值得做:可以通过调整光圈到最大值(f/1.4)预览每帧的散景效果,选择视觉冲击力最强的画面
    • 怎么开始:遍历视频关键帧,用 CNRenderingSession 以不同光圈值渲染,计算散景区域的对比度/色彩丰富度,评分选最优

关联 Session

评论

GitHub Issues · utterances