(二十一)深入了解AVFoundation-编辑:导出视频与格式转换的全流程
博客专栏链接:AVFoundation架构与实践
博客专栏源码链接:AVFoundation-建议转存大量实战源码
一. 引言
在前面的系列博客中,我们主要聚焦于音视频的播放与合成技术,完成了视频的剪辑、水印叠加等功能,但并未深入讲解如何将编辑好的内容导出成新的视频文件。
本篇博客将详细介绍 AVFoundation 中视频导出的实现,重点讲解:
- 参数化导出配置:如何灵活设置导出质量、格式等参数,满足不同需求;
- 导出进度监听与取消:利用定时器轮询实现进度反馈,并支持用户主动取消导出;
- 错误处理:针对导出失败的常见情况给出解决思路;
- 保存到相册:导出完成后如何将视频保存至系统相册,及权限相关处理。
通过这篇文章,你将掌握一个可复用的导出组件设计思路,实战中可以直接使用并根据需求灵活调整。
二. 导出功能详解
在前面的博客中,我们使用了比较简陋的导出方式,参数固定且缺少对导出进度、错误和取消的处理。实际开发中,这些功能至关重要,能极大提升用户体验和程序稳定性。
本节将围绕我们封装的 PHCompositionExporter 类,详细介绍如何实现:
- 参数化导出配置
- 导出进度监听
- 取消导出
- 错误处理
- 保存到相册
2.1 参数化导出配置
AVFoundation 中,AVAssetExportSession 允许我们通过不同的预设(preset)控制导出质量、分辨率等,同时也支持多种输出格式。
在 PHCompositionExporter 里,beginExport 方法支持传入三个参数:
func beginExport(presetName: String = AVAssetExportPresetHighestQuality,outputFileType: AVFileType = .mp4,saveToPhotoLibrary: Bool = true)
presetName:导出预设名称,常用的有
- AVAssetExportPresetLowQuality
- AVAssetExportPresetMediumQuality
- AVAssetExportPresetHighestQuality(默认)
- 还有专门针对HEVC编码的预设,如 AVAssetExportPresetHEVCHighestQuality
outputFileType:输出文件格式,常见有 .mp4, .mov, .m4v 等,需与预设兼容,否则导出会失败。
saveToPhotoLibrary:导出完成后是否自动保存到相册,默认为 true。
通过参数化配置,开发者可以根据具体场景灵活调整导出效果和格式,满足从快速预览到高质量成片的不同需求。
2.2 导出进度监听
AVAssetExportSession 自身不支持通过 KVO 监听导出进度,因此我们通过一个定时器每 0.1 秒轮询 exportSession.progress。
在类中我们通过 progressTimer 实现:
self.progressTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ inguard let self = self, let session = self.exportSession else { return }self.onProgress?(session.progress)
}
然后在外部通过设置 onProgress 闭包,就能实时拿到进度数据,比如更新进度条。
2.3 取消导出
用户可能在导出过程中主动取消操作,我们需要提供接口取消导出,并正确处理回调。
PHCompositionExporter 提供了 cancelExport() 方法,内部调用 exportSession.cancelExport(),并且停止进度定时器:
func cancelExport() {exportSession?.cancelExport()progressTimer?.invalidate()progressTimer = nil
}
导出取消后,exportSession.status 会变成 .cancelled,我们在回调中判断并返回对应错误。
2.4 错误处理
导出失败的情况较多,常见包括:
- 磁盘空间不足
- 导出格式和预设不兼容
- 权限问题(写入沙盒或相册失败)
- 导出过程异常中断
在导出完成的回调中,我们会判断 exportSession.status,如果是 .failed,则将错误信息通过 onCompletion 回调返回。
case .failed:let error = exportSession.error ?? NSError(domain: "Export", code: -6, userInfo: [NSLocalizedDescriptionKey: "Export failed with unknown error."])self.onCompletion?(.failure(error))
这样调用方就可以据此做出对应的 UI 提示或重试逻辑。
2.5 保存到相册
导出视频后,通常需要保存到系统相册方便用户查看。
PHCompositionExporter 提供了 saveToPhotoLibrary 方法,利用 Photos 框架实现保存:
PHPhotoLibrary.shared().performChanges({PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL)
}, completionHandler: { success, error in// 回调保存结果
})
注意,iOS 14+ 需要用户授权访问相册,若用户拒绝则保存失败。
三. Demo:导出功能完整示例
接下来我们以实际代码演示如何结合前面封装好的导出组件,实现完整的导出流程,包括显示导出进度视图、响应取消和完成操作。
/// 执行导出
private func export() {let timeLine = timeLineViewController.timeLinelet compositionBuilder = PHCompositionBuilderFactory.buildComositionBuilder(timeLine: timeLine)guard let composition = compositionBuilder.buildComposition() else {print("Failed to build composition for export")return}// 初始化导出助手exportHelper = PHCompositionExporter(composition: composition)// 显示导出进度视图showExportView()// 绑定导出进度和完成回调exportHelper?.onProgress = { [weak self] progress in// 更新导出视图进度guard let self = self else { return }self.exportView?.updateState(progress: progress, result: nil)}exportHelper?.onCompletion = { [weak self] result in// 导出完成,更新视图状态guard let self = self else { return }switch result {case .success(let url):print("导出成功,文件路径:\(url)")DispatchQueue.main.async {// 在主线程更新 UIself.exportView?.updateState(progress: 1.0, result: true)}// 可根据需求处理导出成功后的逻辑,比如保存、分享case .failure(let error):print("导出失败:\(error.localizedDescription)")DispatchQueue.main.async {// 在主线程更新 UIself.exportView?.updateState(progress: 0.0, result: false)}}}// 开始导出exportHelper?.beginExport()print("开始导出视频")
}/// 显示导出视图
/// Show the export progress view, keep a weak reference, and handle user actions.
private func showExportView() {// 若已有导出视图,先移除if let existView = exportView {existView.removeFromSuperview()}// 初始化导出视图let exportView = PHExportView()// 设置视图样式及布局exportView.layer.masksToBounds = trueexportView.layer.cornerRadius = 20.0self.view.addSubview(exportView)exportView.snp.makeConstraints { make inmake.width.height.equalTo(250.0)make.center.equalToSuperview()}// 弱引用保存self.exportView = exportView// 绑定取消按钮回调exportView.onCancel = { [weak self] in// 取消导出self?.exportHelper?.cancelExport()// 移除导出视图并清理资源self?.exportView?.removeFromSuperview()self?.exportView = nilself?.exportHelper = nil}// 绑定完成按钮回调exportView.onComplete = { [weak self] in// 移除导出视图并清理资源self?.exportView?.removeFromSuperview()self?.exportView = nilself?.exportHelper = nil}
}
四. 结语
本篇博客我们详细介绍了 AVFoundation 中视频导出的全流程实现,从参数化配置、进度监听、取消导出,到错误处理与保存相册,都做了深入讲解和实战示范。
通过封装 PHCompositionExporter 和自定义的导出视图,我们实现了一个易用、灵活且用户体验友好的导出模块,方便在实际项目中直接集成。
接下来,你可以在此基础上继续扩展更多功能,比如导出格式支持更多编码参数,或者结合后台任务实现更稳定的长时间导出。
希望这篇文章对你的开发有所帮助,欢迎留言交流!