用 AVAssetWriter 制作分段 MPEG-4 内容
Author fragmented MPEG-4 content with AVAssetWriter
2020年6月25日
一句话判断
如果你在做视频流媒体相关的工作,fMP4(fragmented MPEG-4)是 HLS 和 DASH 的基础容器格式——这场 Session 讲了如何用 AVAssetWriter 高效生成 fMP4 内容。
这场 Session 讲了什么
分段 MPEG-4(fMP4)是现代流媒体的核心格式。与传统 MP4 把所有元数据(moov atom)放在文件末尾不同,fMP4 把文件分成多个片段(fragment),每个片段都包含独立的元数据和媒体数据,因此可以在写入过程中就开始播放。
Session 详细讲解了 fMP4 的结构:初始化片段(init segment,包含编解码信息和轨道定义)和媒体片段(media segment,包含实际的音视频数据)。然后介绍了如何使用 AVAssetWriter 配合 AVAssetWriterInput 来生成 fMP4 内容。
重点内容包括:如何配置 AVAssetWriter 的分段参数(片段时长、片段边界条件)、如何在写入过程中处理音视频同步、以及如何生成适合 HLS 的 fMP4 文件序列。
值得深挖的点
fMP4 与传统 MP4 的核心区别。 传统 MP4 的 moov atom 在文件末尾,必须等整个文件写完才能播放。fMP4 把元数据分散到每个片段中,每个片段可以独立解码。这种结构上的变化是流媒体”边下边播”的技术基础。
片段边界的精确控制。 在 HLS 等协议中,片段边界通常需要对齐到关键帧。AVAssetWriter 允许你通过 AVAssetWriterInput.append() 的时机来控制片段边界,配合视频编码器的关键帧间隔设置,可以精确控制每个片段的时长和起点。
代码片段
import AVFoundation
// 配置 AVAssetWriter 生成 fMP4
let outputURL = URL(fileURLWithPath: "output_fragmented.mp4")
let writer = try AVAssetWriter(outputURL: outputURL, fileType: .mp4)
// 关键:设置分段参数
writer.preferredOutputSegmentInterval = CMTime(seconds: 4, preferredTimescale: 600)
// 每 4 秒生成一个片段
// 配置视频输入
let videoSettings: [String: Any] = [
AVVideoCodecKey: AVVideoCodecType.hevc,
AVVideoWidthKey: 1920,
AVVideoHeightKey: 1080
]
let videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
writer.add(videoInput)
// 配合 AVAssetWriterInputMetadataAdaptor 添加元数据
let metadataInput = AVAssetWriterInput(
mediaType: .metadata,
outputSettings: nil
)
let metadataAdaptor = AVAssetWriterInputMetadataAdaptor(
assetWriterInput: metadataInput
)
writer.add(metadataInput)
// 在写入片段时追加定时元数据
let metadataItem = AVMutableMetadataItem()
metadataItem.key = "segment_info" as NSString
metadataItem.value = "fragment_001" as NSString
metadataAdaptor.append(
AVTimedMetadataGroup(
items: [metadataItem],
timeRange: CMTimeRange(start: .zero, duration: CMTime(seconds: 4, preferredTimescale: 600))
)
)
// 处理片段写入回调
// iOS 14 新增的 segment callback
writer.delegate = self
// AVAssetWriterDelegate 方法
func assetWriter(_ writer: AVAssetWriter,
didOutputSegment segment: AVAssetSegment,
segmentType: AVAssetSegmentType) {
switch segmentType {
case .initialization:
print("初始化片段已生成")
case .separable:
print("媒体片段已生成,可以独立播放")
@unknown default:
break
}
}
最佳实践
- 片段时长选择 2-6 秒。 太短会导致元数据开销过大,太长会增加首屏延迟。4 秒是 HLS 的常见选择。
- 确保片段边界对齐关键帧。 每个片段的第一帧应该是 IDR 帧,否则该片段无法独立解码。
- 初始化片段要尽早生成。 在 HLS 中,客户端需要先加载 init segment 才能播放后续的 media segment,确保 init segment 足够小且快速可用。
- 使用 delegate 方法监控片段生成。 通过 AVAssetWriterDelegate 的回调可以实时获知片段的生成进度,方便构建上传管线。
还有什么值得关注
- fMP4 是 HLS 从 MPEG-TS 迁移到新容器格式的方向,苹果推荐新项目使用 fMP4
- AVAssetWriter 的分段功能同时支持视频点播和直播场景
- 配合 AVAssetExportSession 也可以生成 fMP4,但控制粒度不如 AVAssetWriter
- Session 中提到了 fMP4 在低延迟直播场景中的优势