如何在Swift中压缩视频?

12
TLDR: 跳到更新部分。我正在寻找一种压缩或降低视频输出质量的方法,最好是在创建之后而不是直接创建之后,但如果这是唯一的方式,那就这样吧。
另外,如果您知道任何可以完成此操作的好的可可豆荚,那将是很好的。
更新3: 我正在寻找一个函数,它可以输出经过压缩的 URL,并且我应该能够控制压缩质量...
更新2: 在尝试使当前状态下的函数工作后,它没有起作用,产生了 nil。 我认为这是由于以下原因:
                let outputURL = urlToCompress
            assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: AVFileType.mov)

我正在尝试在Swift中压缩视频。到目前为止,所有的解决方案都是用于创建期间的。我想知道是否有一种方法可以在创建后进行压缩?仅使用视频URL?

如果不行,那么我该如何编写一个压缩函数,以压缩视频并返回已压缩的URL?

我一直在使用以下代码:

func compressVideo(videoURL: URL) -> URL {
        let data = NSData(contentsOf: videoURL as URL)!
        print("File size before compression: \(Double(data.length / 1048576)) mb")
        let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + NSUUID().uuidString + ".mov")
        compressVideoHelperMethod(inputURL: videoURL , outputURL: compressedURL) { (exportSession) in

        }
        return compressedURL
    }

    func compressVideoHelperMethod(inputURL: URL, outputURL: URL, handler:@escaping (_ exportSession: AVAssetExportSession?)-> Void) {
        let urlAsset = AVURLAsset(url: inputURL, options: nil)
        guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality) else {
            handler(nil)

            return
        }

        exportSession.outputURL = outputURL
        exportSession.outputFileType = AVFileType.mov
        exportSession.shouldOptimizeForNetworkUse = true
        exportSession.exportAsynchronously { () -> Void in
            handler(exportSession)
        }
    }

更新:

我找到了下面的代码。虽然我还没有测试过,但我不知道如何使其选择压缩质量:

var assetWriter:AVAssetWriter?
var assetReader:AVAssetReader?
let bitrate:NSNumber = NSNumber(value:250000)

func compressFile(urlToCompress: URL, outputURL: URL, completion:@escaping (URL)->Void){
    //video file to make the asset

    var audioFinished = false
    var videoFinished = false

    let asset = AVAsset(url: urlToCompress);

    let duration = asset.duration
    let durationTime = CMTimeGetSeconds(duration)

    print("Video Actual Duration -- \(durationTime)")

    //create asset reader
    do{
        assetReader = try AVAssetReader(asset: asset)
    } catch{
        assetReader = nil
    }

    guard let reader = assetReader else{
        fatalError("Could not initalize asset reader probably failed its try catch")
    }

    let videoTrack = asset.tracks(withMediaType: AVMediaType.video).first!
    let audioTrack = asset.tracks(withMediaType: AVMediaType.audio).first!

    let videoReaderSettings: [String:Any] =  [(kCVPixelBufferPixelFormatTypeKey as String?)!:kCVPixelFormatType_32ARGB ]

    // ADJUST BIT RATE OF VIDEO HERE

    if #available(iOS 11.0, *) {
        let videoSettings:[String:Any] = [
            AVVideoCompressionPropertiesKey: [AVVideoAverageBitRateKey:self.bitrate],
            AVVideoCodecKey: AVVideoCodecType.h264,
            AVVideoHeightKey: videoTrack.naturalSize.height,
            AVVideoWidthKey: videoTrack.naturalSize.width
        ]
    } else {
        // Fallback on earlier versions
    }


    let assetReaderVideoOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: videoReaderSettings)
    let assetReaderAudioOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil)


    if reader.canAdd(assetReaderVideoOutput){
        reader.add(assetReaderVideoOutput)
    }else{
        fatalError("Couldn't add video output reader")
    }

    if reader.canAdd(assetReaderAudioOutput){
        reader.add(assetReaderAudioOutput)
    }else{
        fatalError("Couldn't add audio output reader")
    }

    let audioInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: nil)
    let videoInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoReaderSettings)
    videoInput.transform = videoTrack.preferredTransform
    //we need to add samples to the video input

    let videoInputQueue = DispatchQueue(label: "videoQueue")
    let audioInputQueue = DispatchQueue(label: "audioQueue")

    do{
        assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: AVFileType.mov)
    }catch{
        assetWriter = nil
    }
    guard let writer = assetWriter else{
        fatalError("assetWriter was nil")
    }

    writer.shouldOptimizeForNetworkUse = true
    writer.add(videoInput)
    writer.add(audioInput)


    writer.startWriting()
    reader.startReading()
    writer.startSession(atSourceTime: CMTime.zero)


    let closeWriter:()->Void = {
        if (audioFinished && videoFinished){
            self.assetWriter?.finishWriting(completionHandler: {
                print("------ Finish Video Compressing")                    
                completion((self.assetWriter?.outputURL)!)
            })

            self.assetReader?.cancelReading()
        }
    }


    audioInput.requestMediaDataWhenReady(on: audioInputQueue) {
        while(audioInput.isReadyForMoreMediaData){
            let sample = assetReaderAudioOutput.copyNextSampleBuffer()
            if (sample != nil){
                audioInput.append(sample!)
            }else{
                audioInput.markAsFinished()
                DispatchQueue.main.async {
                    audioFinished = true
                    closeWriter()
                }
                break;
            }
        }
    }

    videoInput.requestMediaDataWhenReady(on: videoInputQueue) {
        //request data here
        while(videoInput.isReadyForMoreMediaData){
            let sample = assetReaderVideoOutput.copyNextSampleBuffer()
            if (sample != nil){
                let timeStamp = CMSampleBufferGetPresentationTimeStamp(sample!)
                let timeSecond = CMTimeGetSeconds(timeStamp)
                let per = timeSecond / durationTime
                print("Duration --- \(per)")
                videoInput.append(sample!)
            }else{
                videoInput.markAsFinished()
                DispatchQueue.main.async {
                    videoFinished = true
                    closeWriter()
                }
                break;
            }
        }
    }
}

我该如何更改以便能够设置质量?我希望压缩比约为0.6。

我现在正在尝试以下代码,问题是它一直打印错误(似乎不起作用):

    func convertVideoToLowQuailty(withInputURL inputURL: URL?, outputURL: URL?, handler: @escaping (AVAssetExportSession?) -> Void) {
    do {
        if let outputURL = outputURL {
            try FileManager.default.removeItem(at: outputURL)
        }
    } catch {
    }
    var asset: AVURLAsset? = nil
    if let inputURL = inputURL {
        asset = AVURLAsset(url: inputURL, options: nil)
    }
    var exportSession: AVAssetExportSession? = nil
    if let asset = asset {
        exportSession = AVAssetExportSession(asset: asset, presetName:AVAssetExportPresetMediumQuality)
    }
    exportSession?.outputURL = outputURL
    exportSession?.outputFileType = .mov
    exportSession?.exportAsynchronously(completionHandler: {
        handler(exportSession)
    })
}

func compressVideo(videoURL: URL) -> URL {
    var outputURL = URL(fileURLWithPath: "/Users/alexramirezblonski/Desktop/output.mov")
    convertVideoToLowQuailty(withInputURL: videoURL, outputURL: outputURL, handler: { exportSession in
        print("fdshljfhdlasjkfdhsfsdljk")
        if exportSession?.status == .completed {
            print("completed\n", exportSession!.outputURL!)
            outputURL = exportSession!.outputURL!
        } else {
            print("error\n")
            outputURL = exportSession!.outputURL!//this needs to be fixed and may cause errors
        }
    })
    return outputURL
}

1
也许这篇文章会有所帮助 - https://blog.testfairy.com/fine-tuned-video-compression-in-ios-swift-4-no-dependencies/ - Nina
@Nina,我一直在尝试让它工作,但似乎不起作用。我实现了类和使用方法,但似乎无法通过//压缩部分。 - user11182243
我对这个压缩代码唯一的问题是:1. 它很慢,2. 它会水平翻转视频 @Nina - user11182243
试试这个 https://dev59.com/spXfa4cB1Zd3GeqPbzmD#42548218 - Nikunj Kumbhani
你尝试过这个回答吗?问题是一样的: https://dev59.com/XVkS5IYBdhLWcg3wCCbf#40470706 - NickCoder
显示剩余3条评论
2个回答

5

我已经查看了你的代码。实际上,你正在以中等质量压缩视频,这将与你拥有的原始视频大致相同。因此,你需要在导出会话初始化中更改 presetName 如下:

exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetLowQuality)

您可以使用AVAssetExportPresetMediumQuality,因此可以按照您的预期进行压缩。

下面是可用于压缩视频的格式列表:

1. 从iOS 11.0开始可用

  • AVAssetExportPresetHEVCHighestQuality
  • AVAssetExportPresetHEVC1920x1080
  • AVAssetExportPresetHEVC3840x2160

2. 从iOS 4.0开始可用

  • AVAssetExportPresetLowQuality
  • AVAssetExportPresetMediumQuality
  • AVAssetExportPresetHighestQuality
  • AVAssetExportPreset640x480
  • AVAssetExportPreset960x540
  • AVAssetExportPreset1280x720
  • AVAssetExportPreset1920x1080
  • AVAssetExportPreset3840x2160
  • AVAssetExportPresetAppleM4A

您可以根据自己的需求使用上述所有格式来压缩您的视频。希望这能对您有所帮助。


看下面的例子,它没有录制视频,但是运行良好:https://ufile.io/zbchdu27 - Mayur Karmur
它们被压缩到可以忽略不计的程度。请帮我解决。 - Kudos

1
如果您想使用AVAssetWriter获得更多可定制的压缩过滤器,请考虑使用我编写的。您可以使用一般质量设置或更详细的过滤器(如比特率、fps、缩放等)压缩视频。
FYVideoCompressor().compressVideo(yourVideoPath, quality: .lowQuality) { result in
    switch result {
    case .success(let compressedVideoURL):
    case .failure(let error):
    }
 }

或者使用更多的自定义配置:
let config = FYVideoCompressor.CompressionConfig(videoBitrate: 1000_000,
                                                videomaxKeyFrameInterval: 10,
                                                fps: 24,
                                                audioSampleRate: 44100,
                                                audioBitrate: 128_000,
                                                fileType: .mp4,
                                                scale: CGSize(width: 640, height: 480))
FYVideoCompressor().compressVideo(yourVideoPath, config: config) { result in
    switch result {
    case .success(let compressedVideoURL):
    case .failure(let error):
    }
}

更多:现在支持批量压缩。

这是目前为止最方便的解决方案,并附带一些额外的功能。但是就目前而言,它无法用于批量视频压缩(循环内部),更多信息请查看此处。在问题修复之前,我采用了上面接受的解决方案。如果您感兴趣,可以阅读更详细的本地实现此处 - peetadelic
现在支持批量视频压缩(循环内),请尝试@peetadelic。 - manman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接