公司最近有一个需求,需要压缩视频,体积和清晰度都有要求。在网上查了很多资料,终极使用了assetReader和assetWriter,但是网上找到的资料,直接拷贝到项目中,都或多或少有些题目,或少了一些参数,所以我把自己这些天查到的,并更改好,能顺利压缩的代码写出来,记录下,也方便各人检察。但这些代码只是适用于我自己的项目,如果各人使用有题目的话,可以留言大概自己网上去探求办理方法。
之前压缩视频,我使用的是苹果提供的一个基于AVFoundation框架的类,AVAssetExportSession,用它来进行视频压缩和转码,使用也是比较方便的,只需设置输出之类和格式等参数。如下图所示,设置比较简单:
- func saveVideo(from videoURL: URL,
- to videoFinalPath: URL,
- handler: ((URL?) -> Void)? = nil) {
-
- let asset = AVAsset(url: videoURL)
- if (deleteIfExists(path: videoFinalPath)) {
-
- guard let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPreset960x540) else {
- handler?(nil)
- return
- }
- exporter.outputURL = videoFinalPath
- exporter.outputFileType = .mp4
- exporter.exportAsynchronously { () -> Void in
- switch exporter.status {
- case .failed:
- let err = exporter.error
- print("failed import video: \(exporter.error)")
- handler?(nil)
- case .cancelled:
- print("cancelled import video: \(exporter.error)")
- handler?(nil)
- case .completed:
- print("completed import video save")
- handler?(videoFinalPath)
- default:
- handler?(nil)
- break
- }
- }
- }
- }
复制代码 此中AVAssetExportPreset960x540就是压缩的质量参数。之前我选择的是AVAssetExportPresetMediumQuality,压缩的体积照旧不错的。压缩的视频是我自己拍摄的41秒的视频,70多MB,能压缩到10多MB,但是视频的清晰度就大打扣头。厥后我选择了AVAssetExportPreset640x480的压缩参数,压缩出来为18.3MB,但是视频照旧不太清晰,但是比之前AVAssetExportPresetMediumQuality的要清晰一点。选 AVAssetExportPreset960x540,压缩出来为27MB,视频这时是比较清晰的,但是体积太大了。参考微信的压缩视频功能,微信中发送视频,默认的压缩出来是13MB多,而且很清晰。所以,上面的参数都达不到这个结果,需要重新找方法去办理。
之后,我使用了assetReader和assetWriter,这个是可以自界说压缩参数并实现更精细的控制,但使用起来相对复杂一些。在使用的时候需要设置好音视频的参数,如果设置不对,大概压缩失败,直接报错闪退。我反复设置了参数后,终于可以压缩,视频的体积和清晰度都能到达想要的结果,但是这时我发现,压缩出来的视频永远是相当于home键在右侧的方向。比如,我拿手机拍一个视频,手机是竖着的,home键朝下,那么压缩出来的视频,便是会逆时针转90度。查了参数,才知道可以通过AVAssetTrack中的preferredTransform检察视频的方向。默认0度就是手机横着,Home键在右边拍摄的视频。如果手机是竖着,Home键在下方,拍摄的视频,那么他的角度是90度。代码如下:
- //获取视频的角度
- private func degressFromVideoFileWithURL(videoTrack: AVAssetTrack)->Int {
- var degress = 0
- let t: CGAffineTransform = videoTrack.preferredTransform
- if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0){
- // Portrait
- degress = 90
- }else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0){
- // PortraitUpsideDown
- degress = 270
- }else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0){
- // LandscapeRight
- degress = 0
- }else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0){
- // LandscapeLeft
- degress = 180
- }
- return degress
- }
复制代码 然后,我也找到相识决方法。如果视频的角度不为0,我们在压缩时,直接把视频手动旋转到之前的角度就行了。各人切记,不要再用AVAssetExportSession去旋转方向,网上很多如许的文章,我也试了,转向是成功了,但是之前压缩好的体积,经过AVAssetExportSession再一压缩又变大了……
现在把已经测试过能顺利运行的代码贴出来,供各人参考:
- func exportVideo(from videoURL: URL,
- to videoFinalPath: URL,
- completeHandler: @escaping (URL) -> ()) {
- // 创建音视频输入asset
- let asset = AVAsset(url: videoURL)
- // 原视频大小,用于测试压缩效果
- let oldfileItem = try? FileManager.default.attributesOfItem(atPath: videoURL.path)
- print(oldfileItem?[FileAttributeKey.size]) //打印之前视频体积
- // 创建音视频Reader和Writer
- guard let reader = try? AVAssetReader(asset: asset),
- let writer = try? AVAssetWriter.init(outputURL: videoFinalPath, fileType: AVFileType.mp4) else { return}
-
- //视频输出配置
- let configVideoOutput: [String : Any] = [kCVPixelBufferPixelFormatTypeKey: NSNumber(value: kCVPixelFormatType_422YpCbCr8)] as! [String: Any]
- //音频输出配置
- let configAudioOutput: [String : Any] = [AVFormatIDKey: NSNumber(value: kAudioFormatLinearPCM)] as! [String: Any]
-
- let compressionProperties: [String: Any] = [AVVideoAverageBitRateKey: 1600 * 1024, //码率
- AVVideoExpectedSourceFrameRateKey: 25, //帧率
- AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel]
- let videoCodeec: String = AVVideoCodecType.h264.rawValue //视频编码
- var videoSettings: [String: Any] = [AVVideoCodecKey: videoCodeec, //视频编码
- AVVideoWidthKey: 960, //视频宽(必须填写正确,否则压缩后有问题)
- AVVideoHeightKey: 540, //视频高(必须填写正确,否则压缩后有问题)
- AVVideoCompressionPropertiesKey: compressionProperties,
- AVVideoScalingModeKey: AVVideoScalingModeResizeAspectFill] //设置视频缩放方式
-
- let stereoChannelLayout: AudioChannelLayout = AudioChannelLayout(mChannelLayoutTag: kAudioChannelLayoutTag_Stereo,
- mChannelBitmap: AudioChannelBitmap.init(rawValue: 0),
- mNumberChannelDescriptions: 0,
- mChannelDescriptions: AudioChannelDescription())
- let channelLayoutAsData: NSData = NSData(bytes: (stereoChannelLayout as? UnsafeRawPointer), length: MemoryLayout.size(ofValue: AudioChannelLayout.self))
- let audioSettings: [String: Any] = [AVFormatIDKey : kAudioFormatMPEG4AAC,
- AVEncoderBitRateKey : 96000, // 码率
- AVSampleRateKey : 44100, // 采样率
- AVChannelLayoutKey : channelLayoutAsData,
- AVNumberOfChannelsKey : 2]
- // video part
- guard let videoTrack: AVAssetTrack = (asset.tracks(withMediaType: .video)).first else {return}
-
- //获取原视频的角度
- let degree = self.degressFromVideoFileWithURL(videoTrack: videoTrack)
- //获取原视频的宽高,如果是手机拍摄,一般是宽大,高小,如果是手机自带录屏,那么是高大,宽小
- let naturalSize = videoTrack.naturalSize
- if naturalSize.width < naturalSize.height {
- videoSettings[AVVideoWidthKey] = 540
- videoSettings[AVVideoHeightKey] = 960
- }
-
-
- let videoOutput: AVAssetReaderTrackOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: configVideoOutput)
- let videoInput: AVAssetWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
- //视频写入的旋转(这句很重要)
- if let transform = self.getAffineTransform(degree: degree, videoTrack: videoTrack) {
- videoInput.transform = transform
- }
-
- if reader.canAdd(videoOutput) {
- reader.add(videoOutput)
- }
- if writer.canAdd(videoInput) {
- writer.add(videoInput)
- }
- // audio part
- guard let audioTrack: AVAssetTrack = (asset.tracks(withMediaType: .audio)).first else {return}
- let audioOutput: AVAssetReaderTrackOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: configAudioOutput)
- let audioInput: AVAssetWriterInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings)
- if reader.canAdd(audioOutput) {
- reader.add(audioOutput)
- }
- if writer.canAdd(audioInput) {
- writer.add(audioInput)
- }
- // 开始读写
- reader.startReading()
- writer.startWriting()
- writer.startSession(atSourceTime: .zero)
-
- let group = DispatchGroup()
-
- group.enter()
- videoInput.requestMediaDataWhenReady(on: DispatchQueue(label: "videoOutQueue"), using: {
- var completedOrFailed = false
- while (videoInput.isReadyForMoreMediaData) && !completedOrFailed {
- let sampleBuffer: CMSampleBuffer? = videoOutput.copyNextSampleBuffer()
- if sampleBuffer != nil {//}&& reader.status == .reading {
- let result = videoInput.append(sampleBuffer!)
- // if (!result) {
- // reader.cancelReading()
- // break
- // }
- } else {
- completedOrFailed = true
- videoInput.markAsFinished()
- group.leave()
- break
- }
- }
- })
-
- group.enter()
- audioInput.requestMediaDataWhenReady(on: DispatchQueue(label: "audioOutQueue"), using: {
- var completedOrFailed = false
- while (audioInput.isReadyForMoreMediaData) && !completedOrFailed {
- let sampleBuffer: CMSampleBuffer? = audioOutput.copyNextSampleBuffer()
- if sampleBuffer != nil {//}&& reader.status == .reading {
- let result = audioInput.append(sampleBuffer!)
- // if (!result) {
- // reader.cancelReading()
- // break
- // }
- } else {
- completedOrFailed = true
- audioInput.markAsFinished()
- group.leave()
- break
- }
- }
- })
- group.notify(queue: DispatchQueue.main) {
- if reader.status == .reading {
- reader.cancelReading()
- }
- switch writer.status {
- case .writing:
- writer.finishWriting(completionHandler: {
- DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3, execute: {
- let newfileSize = try? FileManager.default.attributesOfItem(atPath: videoFinalPath.path)
- print(newfileSize?[FileAttributeKey.size]) //打印压缩后视频的体积
- completeHandler(videoFinalPath)
- })
- })
- case .cancelled:
- print("$$$ compress cancelled")
- case .failed:
- print("$$$ compress failed",writer.error)
- case .completed:
- print("$$$ compress completed")
- case .unknown:
- print("$$$ compress unknown")
- }
- }
- }
复制代码- private func getAffineTransform(degree: Int, videoTrack: AVAssetTrack)-> CGAffineTransform? {
- var translateToCenter: CGAffineTransform?
- var mixedTransform: CGAffineTransform?
- if degree == 90 { //视频旋转90度,home按键在左"
- translateToCenter = CGAffineTransform(translationX: videoTrack.naturalSize.height, y: 0.0)
- mixedTransform = translateToCenter!.rotated(by: Double.pi / 2)
- } else if degree == 180 { //视频旋转180度,home按键在上"
- translateToCenter = CGAffineTransform(translationX: videoTrack.naturalSize.width, y: videoTrack.naturalSize.height)
- mixedTransform = translateToCenter!.rotated(by: Double.pi)
- } else if degree == 270 { //视频旋转270度,home按键在右"
- translateToCenter = CGAffineTransform(translationX: 0.0, y: videoTrack.naturalSize.width)
- mixedTransform = translateToCenter!.rotated(by: Double.pi / 2 * 3)
- }
- return mixedTransform
- }
复制代码 以上便是所有代码,渴望对各人有帮助!另外关于压缩视频的参数,有篇文章写得非常详细,链接为:手把手教你iOS自界说视频压缩 - 知乎
各人可以在视频压缩出来后,参照文章的参数,进行调整,以便到达最佳结果。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |