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是编译期类型安全的属性标识符,返回类型是CMTimetry 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
- 返回结果是元组,顺序与传入的属性标识符一致
duration是CMTime,tracks是[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:)返回解析后的AVTimedMetadataGroupsourceSampleBuffer(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 会建议调整
核心启发
-
把现有 AVAsset 属性读取代码迁移到 async/await。旧的三步回调代码既冗长又容易出错,新 API 一行搞定。入口 API:
try await asset.load(.duration)。 -
在视频编辑应用中加入 GPS 驱动的动态合成。把 GPS 数据写成 timed metadata track,在自定义 compositor 中读取并调整画面。入口 API:
videoComposition.sourceSampleDataTrackIDs+request.sourceTimedMetadata(byTrackID:)。 -
为视频平台添加字幕文件生成能力。用 AVCaption 和 AVAssetWriterInputCaptionAdaptor 批量生成 .itt 或 .scc 文件。入口 API:
AVCaption+AVCaptionConversionValidator。 -
在同步代码中安全地获取已加载属性。用
status(of:)检查状态后解包,避免在同步上下文中调用 async 方法。入口 API:asset.status(of: .property)。 -
批量加载多个属性提升性能。一次调用
load(.duration, .tracks, .metadata)比三次单独调用更高效。入口 API:try await asset.load(.duration, .tracks)。
关联 Session
- Evaluate videos with the Advanced Video Quality Tool — AVQT 视频质量评估工具,支持 SDR/HDR
- Explore low-latency video encoding with VideoToolbox — VideoToolbox 低延迟 H.264 硬件编码
- Edit and play back HDR video with AVFoundation — AVFoundation HDR 视频编辑和播放
- Explore Core Image kernel improvements — Core Image Metal kernel 新写法
评论
GitHub Issues · utterances