WWDC Quick Look 💓 By SwiftGGTeam
What's new in AVFoundation

What's new in AVFoundation

观看原视频

Highlight

AVFoundation 在 iOS 15 / macOS 12 中引入了基于 Swift async/await 的异步属性加载 API,一行代码替代过去的三步回调流程;同时支持在自定义视频合成中注入 timed metadata,并新增了 .itt 和 .scc 字幕文件创作能力。

核心内容

你有一个视频文件,想知道它的时长、轨道信息。在旧版 AVFoundation 中,这需要一个繁琐的三步流程:调用异步加载方法、在回调中检查状态、再读取同步属性。代码冗长,还容易出错——很多人跳过加载步骤直接读属性,导致主线程阻塞。

iOS 15 和 macOS 12 彻底改变了这个体验。

详细内容

异步属性加载:从三步到一行

02:16

旧 API 需要这样写:

// 旧方式:三步流程
asset.loadValuesAsynchronously(forKeys: ["duration", "tracks"]) {
    var error: NSError?
    guard asset.statusOfValue(forKey: "duration", error: &error) == .loaded else { return }
    guard asset.statusOfValue(forKey: "tracks", error: &error) == .loaded else { return }
    let duration = asset.duration
    let audioTracks = asset.tracks(withMediaType: .audio)
}

07:16

新 API 用 Swift async/await 简化为一行:

func inspectAsset() async throws {
    let asset = AVAsset(url: movieURL)
    let duration = try await asset.load(.duration)
    myFunction(thatUses: duration)
}

02:16

关键点:

  • asset.load(.duration) 是异步的,不会阻塞主线程
  • .duration 是编译期类型安全的属性标识符,返回类型是 CMTime
  • try await 统一处理成功和失败两种情况
  • 如果加载失败,会抛出错误

还可以同时加载多个属性:

func inspectAsset() async throws {
    let asset = AVAsset(url: movieURL)
    let (duration, tracks) = try await asset.load(.duration, .tracks)
    myFunction(thatUses: duration, and: tracks)
}

04:02

关键点:

  • 同时加载多个属性更高效,AVAsset 可以批量处理 I/O
  • 返回结果是元组,顺序与传入的属性标识符一致
  • durationCMTimetracks[AVAssetTrack]

检查属性加载状态

04:52

新 API 提供了 status(of:) 方法,可以在不等待的情况下检查属性状态:

switch asset.status(of: .duration) {
case .notYetLoaded:
    // 初始状态,尚未请求加载
    break
case .loading:
    // 正在加载中
    break
case .loaded(let duration):
    // 加载成功,直接使用值
    print(duration)
case .failed(let error):
    // 加载失败
    print(error)
}

04:52

关键点:

  • .notYetLoaded 是初始状态,AVAsset 按需加载
  • .loading 表示正在异步处理
  • .loaded 携带关联值,可以直接使用
  • .failed 携带错误信息

异步过滤方法

06:32

除了加载属性,AVAsset 和 AVAssetTrack 还新增了异步过滤方法:

let asset: AVAsset
let track = try await asset.loadTrack(withTrackID: 1)
let audioTracks = try await asset.loadTracks(withMediaType: .audio)
let legibleTracks = try await asset.loadTracks(withMediaCharacteristic: .legible)
let metadata = try await asset.loadMetadata(for: .quickTimeMetadata)
let chapterGroups = try await asset.loadChapterMetadataGroups(bestMatchingPreferredLanguages: ["en-US"])
let audibleGroup = try await asset.loadMediaSelectionGroup(for: .audible)

let track: AVAssetTrack
let segment = try await track.loadSegment(forTrackTime: .zero)
let sampleTime = try await track.loadSamplePresentationTime(forTrackTime: .zero)
let associatedTracks = try await track.loadAssociatedTracks(ofType: .audioFallback)

06:32

关键点:

  • loadTrack(withTrackID:) 按 ID 获取特定轨道
  • loadTracks(withMediaType:) 按媒体类型过滤(音频/视频/字幕)
  • loadTracks(withMediaCharacteristic:) 按特征过滤(可听/可视/可读)
  • loadMetadata(for:) 加载特定格式的元数据
  • loadMediaSelectionGroup(for:) 加载媒体选择组(多语言音轨等)

旧 API 迁移指南

08:09

Apple 计划在未来的版本中废弃旧的同步 API。迁移路径很清晰:

旧 API新 API
loadValuesAsynchronously + statusOfValue + 同步属性try await asset.load(.property)
loadValuesAsynchronously + statusOfValue + 同步过滤方法try await asset.loadTracks(...)
statusOfValue(forKey:) + 同步属性switch asset.status(of: .property)

如果需要在同步上下文中获取已加载的值,可以检查状态后断言:

guard case .loaded(let tracks) = asset.status(of: .tracks) else { return }
// tracks 已加载,可以同步使用

09:49

关键点:

  • 再次调用 load(.tracks) 不会重复工作,会返回缓存值
  • load 只能在 async 上下文中调用
  • 同步上下文中用 status(of:) 检查并解包
  • 属性加载后也可能变为 .failed 状态

自定义视频合成中的 Timed Metadata

11:49

新 API 允许在自定义视频合成中传递 timed metadata。比如你有 GPS 数据,想在合成每一帧时根据位置信息调整画面。

设置步骤:

// 告诉 video composition 哪些 metadata track 相关
videoComposition.sourceSampleDataTrackIDs = [4, 5]

// 每个 instruction 指定需要哪些 track
instruction1.requiredSourceSampleDataTrackIDs = [4]
instruction2.requiredSourceSampleDataTrackIDs = [4, 5]

11:49

在合成回调中读取 metadata:

func startRequest(_ request: AVAsynchronousVideoCompositionRequest) {
    for trackID in request.sourceSampleDataTrackIDs {
        let metadata: AVTimedMetadataGroup? = request.sourceTimedMetadata(byTrackID: trackID)
        // 或者用 sourceSampleBuffer(byTrackID:) 获取原始 CMSampleBuffer
    }
    // 用 metadata 合成视频帧
    request.finish(withComposedVideoFrame: composedFrame)
}

12:44

关键点:

  • sourceSampleDataTrackIDs 必须在 composition 级别和 instruction 级别都设置
  • sourceTimedMetadata(byTrackID:) 返回解析后的 AVTimedMetadataGroup
  • sourceSampleBuffer(byTrackID:) 返回原始 CMSampleBuffer
  • 两个设置步骤缺一不可,否则收不到 metadata

字幕文件创作

13:57

macOS 12 新增了对两种字幕文件格式的创作支持:

  • iTunes Timed Text(.itt):字幕文件
  • Scenarist Closed Captions(.scc):闭字幕文件

核心 API:

// AVCaption 表示单条字幕
let caption = AVCaption(text: "Hello", timeRange: timeRange)

// 用 AVCaptionConversionValidator 验证兼容性
let validator = AVCaptionConversionValidator()

// AVCaptionRenderer 预览字幕效果
let renderer = AVCaptionRenderer()
renderer.render(caption, to: cgContext)

14:30

关键点:

  • AVCaption 包含文本、位置、样式等属性
  • AVAssetWriterInputCaptionAdaptor 将字幕写入文件
  • AVAssetReaderOutputCaptionAdaptor 从文件读取字幕
  • AVCaptionConversionValidator 检查字幕与目标格式的兼容性
  • .scc 格式有严格的字符预算限制,validator 会建议调整

核心启发

  1. 把现有 AVAsset 属性读取代码迁移到 async/await。旧的三步回调代码既冗长又容易出错,新 API 一行搞定。入口 API:try await asset.load(.duration)

  2. 在视频编辑应用中加入 GPS 驱动的动态合成。把 GPS 数据写成 timed metadata track,在自定义 compositor 中读取并调整画面。入口 API:videoComposition.sourceSampleDataTrackIDs + request.sourceTimedMetadata(byTrackID:)

  3. 为视频平台添加字幕文件生成能力。用 AVCaption 和 AVAssetWriterInputCaptionAdaptor 批量生成 .itt 或 .scc 文件。入口 API:AVCaption + AVCaptionConversionValidator

  4. 在同步代码中安全地获取已加载属性。用 status(of:) 检查状态后解包,避免在同步上下文中调用 async 方法。入口 API:asset.status(of: .property)

  5. 批量加载多个属性提升性能。一次调用 load(.duration, .tracks, .metadata) 比三次单独调用更高效。入口 API:try await asset.load(.duration, .tracks)

关联 Session

评论

GitHub Issues · utterances