ios swift 自定义压缩压缩视频(清晰,解决了旋转问题)

    公司最近有一个需求,需要压缩视频,体积和清晰度都有要求。在网上查了很多资料,最终使用了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自定义视频压缩 - 知乎

    大家可以在视频压缩出来后,参照文章的参数,进行调整,以便达到最佳效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值