探索 AVFoundation 中的 HLS 变体
Explore HLS variants in AVFoundation
2021年6月11日
一句话判断
如果你只用 AVPlayer 播放 HLS 却从没碰过 AVAssetVariant 和 AVVariantPreferences,你大概率没有充分利用 Apple 为自适应流媒体准备的精细化控制能力。
这场 Session 讲了什么
Session 详细介绍了 AVFoundation 框架中与 HLS variant(变体流)相关的 API,包括如何获取和解析 master playlist 中的 variant 信息、如何自定义 variant 选择策略、以及如何监控播放过程中的 variant 切换事件。
HLS 的自适应码率(ABR)逻辑在 AVFoundation 中是自动的——播放器根据网络状况自动选择合适的分辨率和码率。但”自动”不等于”黑盒”。iOS 15 新增了 AVVariantPreferences API,让你可以在播放器自动选择的基础上施加额外约束:比如在蜂窝网络下限制最高 720p,或者强制选择包含特定音频语言的 variant。Session 还展示了 AVAssetVariant 的属性结构——你可以读取每个 variant 的码率、分辨率、codec、HDR 信息,甚至视频范围(Video Range)标签。
值得深挖的点
AVVariantAttributes 的查询能力
AVAssetVariant 包含一组 AVVariantAttributes,描述了这个 variant 的完整能力参数。以前开发者只能拿到码率和分辨率,现在可以读取 codec 类型(H.264、HEVC、VP9)、颜色空间(BT.709、BT.2020)、传输特性(SDR、HLG、PQ)、音频通道数和语言标签。这意味着你可以在播放前就做精确的 variant 筛选——比如只允许 HEVC 的 variant 被选中,因为你知道用户设备支持硬件解码。
自定义 variant 选择策略的实现方式
AVVariantPreferences 通过 preferredForwardBufferDuration 和 preferredPeakBitRate 等属性控制播放器的行为。但更强大的用法是配合 AVPlayerItemVariant 的 KVO 监听,在 variant 切换发生时做自定义处理。比如检测到用户从 WiFi 切到蜂窝网络后,立刻降低 preferredPeakBitRate,而不是等播放器自动降级(自动降级通常会有几秒的缓冲期)。
代码片段
// 读取 HLS asset 的所有 variant 信息
func analyzeVariants(for url: URL) async {
let asset = AVURLAsset(url: url)
await asset.loadValues(forKeys: ["variants"])
guard let variants = asset.variants else { return }
for (index, variant) in variants.enumerated() {
let videoAttributes = variant.videoAttributes ?? []
for attr in videoAttributes {
print("Variant \(index): \(attr.pixelWidth)x\(attr.pixelHeight)")
print(" 码率: \(attr.bitRate ?? 0) bps")
print(" Codec: \(attr.codecString ?? "unknown")")
}
let audioAttributes = variant.audioAttributes ?? []
for attr in audioAttributes {
print(" 音频语言: \(attr.languageCode ?? "und")")
print(" 通道数: \(attr.channelCount)")
}
}
}
// 使用 AVVariantPreferences 自定义选择策略
func configurePlayer(with url: URL) -> AVPlayer {
let asset = AVURLAsset(url: url)
let item = AVPlayerItem(asset: asset)
// 设置偏好:蜂窝网络下限制最高码率
let preferences = AVVariantPreferences()
preferences.preferredPeakBitRate = 2_500_000 // 2.5 Mbps
// 设置最大前向缓冲时长
preferences.preferredForwardBufferDuration = 10.0
item.preferredVariantPreferences = preferences
// 监听当前播放的 variant 变化
item.addObserver(self, forKeyPath: "currentItem.variantAttributes",
options: .new, context: nil)
return AVPlayer(playerItem: item)
}
// 监听 variant 切换事件
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?) {
guard keyPath == "currentItem.variantAttributes" else { return }
if let playerItem = object as? AVPlayerItem,
let attributes = playerItem.variantAttributes {
let width = attributes.videoAttributes?.first?.pixelWidth ?? 0
let height = attributes.videoAttributes?.first?.pixelHeight ?? 0
print("当前播放分辨率: \(width)x\(height)")
// 记录分辨率变化用于 QoE 分析
AnalyticsService.logResolutionChange(to: "\(width)x\(height)")
}
}
最佳实践
- 在播放前分析 variant 列表,如果发现没有任何 1080p 以上的 variant,提前在 UI 上标注”高清不可用”,而不是等用户投诉画质差。
- 蜂窝网络场景下主动设置
preferredPeakBitRate,不要完全依赖播放器的自动 ABR——自动逻辑的响应速度通常比手动设置慢 3-5 秒。 - 将 variant 切换事件上报到你的 QoE(Quality of Experience)监控平台,分辨率频繁波动说明网络质量不稳定,可以用来指导 CDN 调度策略。
还有什么值得关注
AVAssetVariant现在可以识别 Dolby Atmos 和 Dolby Vision 标记,你可以根据设备能力决定是否允许这些 variant 被选中。- iOS 15 的 AVPlayer 在 variant 切换时增加了对
VIDEO-RANGE标签的处理,SDR 和 HDR 之间的自动切换更加平滑。 AVVariantPreferences可以和 Network Framework 的NWPathMonitor配合,在网络类型变化时动态调整偏好。