Create a more responsive media app
Media & Web 进阶 20m

构建更流畅的媒体应用

Create a more responsive media app

2022年6月6日

在 Apple 官方观看视频

一句话判断

如果你的媒体应用在加载缩略图或编辑视频时偶尔会卡顿,这场 Session 提供了 AVFoundation 新增的一整套 async API 来彻底解决这个问题。

这场 Session 讲了什么

AVFoundation 团队的 Jeremy 介绍了 2022 年新增的异步 API,目标是让媒体应用在加载图片、创建合成、检查资源属性等操作时不再阻塞主线程。

内容分为三部分:AVAssetImageGenerator 的新 async 方法、AVMutableComposition 和相关类的 async 版本、以及 async asset inspection 的更新(正式废弃了旧的 async key-value loading)。

Session 还介绍了 AVAssetResourceLoader 的自定义数据加载优化,允许开发者为本地和缓存媒体提供自定义加载逻辑。

值得深挖的点

图片生成的 async 化。 传统的 copyCGImage 是同步方法,在主线程调用会导致 UI 冻结。新增的 image(at:) async 方法利用 async/await 释放调用线程。对于批量生成图片,新的 images(for:times) 方法接受 CMTime 数组并返回 AsyncSequence,不再需要先映射到 NSValue

宽容差策略。 压缩视频中,I 帧可以独立解码,其他帧依赖邻近帧。默认情况下 image generator 使用最近的 I 帧生成图片。将容差设为零获取精确帧虽然听起来好,但意味着需要加载更多依赖帧。宽容差策略让 image generator 有更多帧可选,减少数据加载量。

Composition 的 async 方法。 AVMutableCompositioninsertTimeRange 原本是同步的——如果 track 信息未加载,会同步加载。新增的 async 版本会自动异步加载所需的 track 属性。AVVideoCompositionAVMutableVideoComposition 的构造器和验证方法也新增了 async 对应版本。

正式废弃 async key-value loading。 旧的基于字符串 key 的异步加载方式(loadValuesAsynchronously)被正式废弃。拼写错误的 key 不会被编译器捕获,会导致属性同步加载而引入性能问题。新的 async load(_:) 使用类型安全的 key,编译期就能发现错误。

代码片段

import AVFoundation

// 新的 async 图片生成方法
let generator = AVAssetImageGenerator(asset: asset)
generator.requestedTimeToleranceBefore = .positiveInfinity  // 宽容差策略
generator.requestedTimeToleranceAfter = .positiveInfinity

// 单张图片
let (image, actualTime) = try await generator.image(at: CMTime(seconds: 5, preferredTimescale: 600))

// 批量图片 - 使用 AsyncSequence
let times = [0.0, 5.0, 10.0, 15.0].map { CMTime(seconds: $0, preferredTimescale: 600) }
for await result in generator.images(for: times) {
    switch result {
    case .success(let imageResult):
        let image = imageResult.image  // CGImage
        let requestedTime = imageResult.requestedTime
    case .failure(let error):
        print("生成失败: \(error)")
    }
}

// 新的 async composition 插入方法
let composition = AVMutableComposition()
guard let compositionTrack = composition.addMutableTrack(
    withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid
) else { return }

// 自动异步加载 track 属性
try await compositionTrack.insertTimeRange(
    timeRange,
    of: videoTrack,
    at: .zero
)

// 新的 async 属性检查(推荐方式)
let (duration, tracks) = try await asset.load(.duration, .tracks)

最佳实践

  • 使用新的 async API 替代所有同步的媒体数据加载操作,特别是在主线程上
  • 图片生成时使用宽容差策略,减少不必要的帧数据加载
  • 将代码从旧的 loadValuesAsynchronously 迁移到新的 async load(_:),获得编译期类型安全
  • 只有当属性数据已在内存中(如 AVMutableComposition 的属性)时才使用同步访问
  • 对于自定义数据加载场景,考虑使用 AVAssetResourceLoader 提供自定义加载逻辑
  • 参考 WWDC 2021 的 Swift 并发相关 Session 了解 async/await 的最佳实践

还有什么值得关注

  • AVAssetResourceLoader 的自定义数据加载允许你为本地和缓存媒体提供更优化的加载策略
  • 旧的同步属性在 Swift 中被标记为废弃,但 Objective-C 中仍可用,迁移可以分阶段进行
  • AVMetadataItem 等子类也支持新的 async load API
  • AsyncSequence 的错误处理需要特别注意,每个元素都可能独立失败
WWDC 2022