赞
踩
一个可以播放的媒体组合我们已经创建好了,不过如果不能把这个组合的结果导出那么组合的价值就不那么突出,所以我们还要学习如何将组合的媒体进行导出。
有了上篇博客做基础,导出功能就显得容易许多,所以我们在这篇博客中稍微介绍一下在视频编辑以及视频播放中时间和时间范围相关的知识。
通常来讲我们在日常开发中都是通过浮点数来表示时间,或者使用NSTimerInterval。实际上,AV Foundation在AVAudioPlayer和AVAudioRecorder中处理时间问题时本身也会使用这个类型。虽然很多通用的开发环境使用双精度表示时间已经足够满足要求了,不过其天然的不精确性导致双精度类型无法应用于更多的高级时基媒体的开发中。比如,一个单一舍入错误就会导致丢帧或音频丢失。相反,苹果公司使用Core Media框架定义的CMTime数据类型作为时间格式。结构如下:
- /**
- @typedef CMTime
- @abstract Rational time value represented as int64/int32.
- */
- @available(iOS 4.0, *)
- public struct CMTime {
- public init()
- public init(value: CMTimeValue, timescale: CMTimeScale, flags: CMTimeFlags, epoch: CMTimeEpoch)
- /**< The value of the CMTime. value/timescale = seconds */
- public var value: CMTimeValue
- /**< The timescale of the CMTime. value/timescale = seconds. */
- public var timescale: CMTimeScale
- /**< The flags, eg. kCMTimeFlags_Valid, kCMTimeFlags_PositiveInfinity, etc. */
- public var flags: CMTimeFlags
- /**< Differentiates between equal timestamps that are actually different because
- of looping, multi-item sequencing, etc.
- Will be used during comparison: greater epochs happen after lesser ones.
- Additions/subtraction is only possible within a single epoch,
- however, since epoch length may be unknown/variable */
- public var epoch: CMTimeEpoch
- }
上面结构中与时间最相关的三个属性分别是:value、timescale、flags。
value-CMTimeValue:64位有符号整型变量。
timescale-CMTimeScale:32位有符号整型变量。
分别是CMTime元素分数形式的分子和分母。
flags-CMTimeFlags:是一个位掩码,用于表示时间的指定状态,比如判断数据是否有效、不确定或是否出现舍入值等。
CMTime实例可标记特定的时间点或用于表示持续时间。
创建CMTime
有多种方法可以创建CMTime实例,不过最常见的方法是使用CMTimeMake函数,指定一个64位的value参数和一个32位的timescale参数。
下面的三种创建方式都表示一个3秒的CMTime。
- let time1 = CMTimeMake(value: 3, timescale: 1)
- let time2 = CMTimeMake(value: 6, timescale: 2)
- let time3 = CMTime(value: 9, timescale: 3)
显示CMTime
使用CMTimeShow函数来显示这些时间。
- CMTimeShow(time1)
- CMTimeShow(time2)
- CMTimeShow(time3)
Core Media框架还为时间范围提供了一个数据类型,称为CMTimeRange,它在有关资源编辑的API中扮演着重要角色。CMTimeRange由两个CMTime值组成,第一个值定义时间范围的起点,第二个值定义时间范围的持续时间。
- public struct CMTimeRange {
- public init()
- public init(start: CMTime, duration: CMTime)
- /**< The start time of the time range. */
- public var start: CMTime
- /**< The duration of the time range. */
- public var duration: CMTime
- }
创建CMTimeRange的一个方法是使用CMTimeRangeMake函数,它的第一个参数是定义时间范围起点的CMTime值,第二个参数是表示范围持续时长的CMTime值。比如,如果我们要创建一个时间范围,从时间轴5秒位置开始,持续时长5秒,则创建方法如下所示:
- let fiveSecondsTime = CMTimeMake(value: 5, timescale: 1)
- let timeRange = CMTimeRange(start: fiveSecondsTime, duration: fiveSecondsTime)
- CMTimeRangeShow(timeRange)
另一种创建时间范围的方法是使用CMTimeRangeFromTimetoTime函数。该函数通过表示范围起点和终点的CMTime值来创建一个CMTimeRange。比如,上面的示例还可以这样实现:
- let fiveSecondsTime = CMTimeMake(value: 5, timescale: 1)
- let tenSecondsTime = CMTimeMake(value: 10, timescale: 1)
- let timeRange = CMTimeRangeFromTimeToTime(start: fiveSecondsTime, end: tenSecondsTime)
- CMTimeRangeShow(timeRange)
CMTimeRange还提供了大量的函数来处理时间范围相关的运算和比较。
如果希望创建一个两个时间交叉的时间范围,或者希望得到两个时间范围的总和,可以尝试下面给出的实现方法。
- let range1 = CMTimeRange(start: CMTime.zero, duration: CMTimeMake(value: 5, timescale: 1))
- let range2 = CMTimeRange(start: CMTimeMake(value: 2, timescale: 1), duration: CMTimeMake(value: 5, timescale: 1))
- let intersectionRange = CMTimeRangeGetIntersection(range1, otherRange: range2)
- CMTimeRangeShow(intersectionRange)
-
- let unionRange = CMTimeRangeGetUnion(range1, otherRange: range2)
- CMTimeRangeShow(unionRange)
PHComposition协议中声明了两个协议方法,之前播放组合我们在PHBaseComposition中实现了其中一个生成视频的可播放版本,接下来我们来实现另外一个方法生成媒体资源的可导出版本。
- //MARK: PHComposition - 生成 AVAssetExportSession
- func makeAssetExportSession() -> AVAssetExportSession? {
- var assetExportSession:AVAssetExportSession? = nil
- if let compostion = compostion {
- let prset = AVAssetExportPresetHighestQuality
- assetExportSession = AVAssetExportSession(asset: compostion.copy() as! AVAsset, presetName: prset)
- }
- return assetExportSession
- }
使用AVMutableComposition的副本,创建一个新的AVAssetExportSession实例。
创建一个PHCompositionExporter类专门负责媒体资源的导出。
- private var composition:PHComposition!
- /// 视频导出类
- private var exportSession:AVAssetExportSession?
- /// 是否正在导出
- private var exporting:Bool = false
- /// 延迟任务
- private var afterTask:PHAfterTask?
- /// 进度
- private var progress:Double = 0.0
-
- init(composition: PHComposition!) {
- self.composition = composition
- }
-
另外声明一个开始导出的方法
- /// 开始导出
- public func beginExport() {
- self.exportSession = self.composition.makeAssetExportSession()
- guard let exportSession = exportSession else { return }
- exportSession.outputURL = exportURL()
- exportSession.outputFileType = .mp4
- exportSession.exportAsynchronously {[weak self] in
- guard let self = self else { return }
- guard let exportSession = self.exportSession else { return }
- let status = exportSession.status
- if status == .completed {
- //成功导出,存储到相册
- writeExportedVideoToPhotoLibrary()
-
- }
- }
- exporting = true
- monitorExportProgress()
- }
-
- /// 获取视频导出地址
- /// - Returns: 视频导出地址
- private func exportURL() -> URL {
- var filePath:String? = nil
- let count:UInt = 0
- repeat {
- let temp = NSTemporaryDirectory()
- let number = String(format: "-%li", count)
- let fileName = String(format: "Masterpice-%@mp4", number)
- filePath = String(format: "%@/%@", temp,fileName)
- } while (FileManager.default.fileExists(atPath: filePath!))
- return URL(filePath: filePath!)
- }
monitorExportProgress方法实现
- /// 监听视频导出进度
- @objc private func monitorExportProgress() {
- guard let exportSession = exportSession else { return }
- let status = exportSession.status
- if status == .exporting {
- self.progress = Double(exportSession.progress)
- self.afterTask = PHAfterTask(after: 0.1, target: self, selector: #selector(monitorExportProgress))
- } else {
- exporting = false
- }
- }
保存到相册需要使用到Photos框架中的知识,将视频保存到相册需要提前申请相册的权限,接下来我们看一下保存到相册的代码实现。
- /// 保存到相册
- private func writeExportedVideoToPhotoLibrary() {
- let url = self.exportSession?.outputURL
- let photoLibrary = PHPhotoLibrary.shared()
- DispatchQueue.main.async {[weak self] in
- guard let self = self else { return }
- var assetId = ""
- var assetCollectionId = ""
- do {
- try photoLibrary.performChangesAndWait {
- assetId = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url!)?.placeholderForCreatedAsset!.localIdentifier ?? ""
- }
- var createAssetCollection = self.fetchAssetCollection(title: "Masterpice")
- if createAssetCollection == nil {
- try photoLibrary.performChangesAndWait {
- assetCollectionId = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: "Masterpice").placeholderForCreatedAssetCollection.localIdentifier
- }
- //获取刚刚创建的相册
- createAssetCollection = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [assetCollectionId], options: nil).firstObject
- }
- //保存
- try photoLibrary.performChangesAndWait {
- let asset = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: nil).firstObject
- let collectionChangeRequest = PHAssetCollectionChangeRequest(for: createAssetCollection!)
- collectionChangeRequest?.addAssets([asset!] as NSFastEnumeration)
- }
- } catch {
- print("writeExportedVideoToPhotoLibrary error")
- }
- }
- }
-
- /// 获取相册
- /// - Parameter title: 相册名
- /// - Returns: 相册
- private func fetchAssetCollection(title:String) -> PHAssetCollection? {
- let fetchOptions = PHFetchOptions()
- fetchOptions.predicate = NSPredicate(format: "title = %@", title)
- let fetchResult = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
- if fetchResult.count > 0 {
- return fetchResult.firstObject
- }
- return nil
- }
以上代码将导出的视频保存到了一个自定义名为Masterpice的相册中,具体细节在这里就不过多介绍。
学习AV Foundation的编辑功能,其中最重要的一点就是熟练掌握CMTime和CMTimeRange的知识,需要对相关知识多加练习。现在我们应用已经具备了创建组合和导出组合的功能,下面的博客我们将继续讨论如何先剪切编辑,处理音频等诸多高级功能。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。