利用AVMutableVideoComposition和 AVAssetExportSession配合可以简单的实现修改asset的帧率:
let videoAsset = AVURLAsset(url: videoURL)
guard let videoTrack = videoAsset.tracks(withMediaType: .video).first else { return }
let timeRange = CMTimeRange(start: CMTime.zero, duration: videoAsset.duration)
//生成videoComposition方法1
let videoComposition = AVMutableVideoComposition()
let instruction = AVMutableVideoCompositionInstruction()
let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
instruction.layerInstructions = [layerInstruction]
instruction.timeRange = timeRange
videoComposition.instructions = [instruction]
//关键就是这一句,frameDuration指的是每一帧的持续时间,所以就等于帧率的倒数,如果要将视频帧率设置为24,用videoComposition.frameDuration = CMTime(value: 1, timescale: 24) 即可
videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
videoComposition.renderSize = CGSize(width: 540, height: 960)
let outputURLString = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)[0] + "/output.mp4"
let outputURL = URL(fileURLWithPath: outputURLString)
try? FileManager.default.removeItem(atPath: outputURLString)
let exportSession = AVAssetExportSession(asset: videoAsset, presetName: AVAssetExportPreset1280x720)
exportSession?.outputFileType = AVFileType.mp4
exportSession?.outputURL = outputURL
exportSession?.shouldOptimizeForNetworkUse = true
exportSession?.videoComposition = videoComposition
exportSession?.exportAsynchronously(completionHandler: { [weak exportSession] in
if exportSession?.status == .completed {
// saveBlock(outputURL)
debugPrint("export success")
} else {
debugPrint("export failed: \(String(describing: exportSession?.error))")
}
})
比较坑的是:AVVideoComposition提供了一个直接用asset的URL来初始化的方法
/*
@method videoCompositionWithPropertiesOfAsset:
@abstract
Returns a new instance of AVMutableVideoComposition with values and instructions suitable for presenting the video tracks of the specified asset according to its temporal and geometric properties and those of its tracks.
@param asset An instance of AVAsset. For best performance, ensure that the duration and tracks properties of the asset are already loaded before invoking this method.
@result An instance of AVMutableVideoComposition.
@discussion
The returned AVMutableVideoComposition will have instructions that respect the spatial properties and timeRanges of the specified asset's video tracks. The client can set sourceTrackIDForFrameTiming to kCMPersistentTrackID_Invalid and frameDuration to an appropriate value in order to specify the maximum output frame rate independent of the source track timing.
It will also have the following values for its properties:
- If the asset has exactly one video track, the original timing of the source video track will be used. If the asset has more than one video track, and the nominal frame rate of any of video tracks is known, the reciprocal of the greatest known nominalFrameRate will be used as the value of frameDuration. Otherwise, a default framerate of 30fps is used.
- If the specified asset is an instance of AVComposition, the renderSize will be set to the naturalSize of the AVComposition; otherwise the renderSize will be set to a value that encompasses all of the asset's video tracks.
- A renderScale of 1.0.
- A nil animationTool.
If the specified asset has no video tracks, this method will return an AVMutableVideoComposition instance with an empty collection of instructions.
*/
public /*not inherited*/ init(propertiesOf asset: AVAsset)`
理论上这两句跟上面那几句代码的效果是完全一样的,但实际上并不可行。。。。。。。。。。。。。。。。
//生成videoComposition方法2
let videoComposition = AVMutableVideoComposition(propertiesOf: resultComposition)
videoComposition.frameDuration = CMTime(value: 1, timescale: 20)
测试发现如果用这个初始化AVMutableVideoComposition的话,不管怎么修改其属性,都不会生效。。。。。。
用AVVideoComposition 的mutableCopy 方法将AVVideoComposition变成AVMutableVideoComposition也不行。
断点调试的话能看出来 ,方式1方式2生成的videoComposition的属性是完全一致的,但方式2的就是不管用。。。
很奇葩很无语。。。
如果有知道具体原因的,望不吝赐教~~