立聪堂德州十三局店 发表于 2024-8-7 20:03:34

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?) //打印之前视频体积
    // 创建音视频Reader和Writer
    guard let reader = try? AVAssetReader(asset: asset),
          let writer = try? AVAssetWriter.init(outputURL: videoFinalPath, fileType: AVFileType.mp4) else { return}
   
    //视频输出配置
    let configVideoOutput: = as!
    //音频输出配置
    let configAudioOutput: = as!
   
    let compressionProperties: = [AVVideoAverageBitRateKey: 1600 * 1024,//码率
                                       AVVideoExpectedSourceFrameRateKey: 25,         //帧率
                                                AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel]
    let videoCodeec: String = AVVideoCodecType.h264.rawValue //视频编码
    var videoSettings: = [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: = [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 = 540
      videoSettings = 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?) //打印压缩后视频的体积
                  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企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: ios swift 自界说压缩压缩视频(清晰,办理了旋转题目)