微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 AVAssetWriter 将图像转换为视频期间的 iOS 快速内存问题

如何解决使用 AVAssetWriter 将图像转换为视频期间的 iOS 快速内存问题

我已经为此做了很多搜索并做了很多实验,但我没有得到任何合适的解决方案。

我尝试将 UIImages 转换为视频。我有 250 多个图像阵列,我尝试将这些图像转换为 60FPS 的视频。

我将渲染代码放在 autoreleasepool 方法中并添加了一些其他代码添加了 autoreleasepool 但没有效果

代码

import AVFoundation
import UIKit
import Photos
import AVKit

var tempurl = ""

struct RenderSettings {
    
    var width: CGFloat = UIScreen.main.bounds.width * UIScreen.main.scale
    var height: CGFloat = UIScreen.main.bounds.width * UIScreen.main.scale
    var fps: Int32 = 60   //frames per second
    var avCodecKey = AVVideoCodecType.h264
    var videoFilename = "ImagetoVideo"
    var videoFilenameExt = "mp4"
    
    var size: CGSize {
        return CGSize(width: width,height: height)
    }
    
    var outputURL: URL {
        
        let fileManager = FileManager.default
        if let tmpDirURL = try? fileManager.url(for: .cachesDirectory,in: .userDomainMask,appropriateFor: nil,create: true) {
            return tmpDirURL.appendingPathComponent(videoFilename).appendingPathExtension(videoFilenameExt) as URL
        }
        fatalError("URLForDirectory() Failed")
        
    }
}

class VideoWriter {
    
    let renderSettings: RenderSettings
    
    var videoWriter: AVAssetWriter!
    var videoWriterInput: AVAssetWriterInput!
    var pixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor!
    
    var isReadyForData: Bool {
        return videoWriterInput?.isReadyForMoreMediaData ?? false
    }
    
    class func pixelBufferFromImage(image: UIImage,pixelBufferPool: CVPixelBufferPool,size: CGSize) -> CVPixelBuffer {
        
        autoreleasepool {
            
            var pixelBufferOut: CVPixelBuffer?
            
            let status = CVPixelBufferPoolCreatePixelBuffer(kcfAllocatorDefault,pixelBufferPool,&pixelBufferOut)
            
            if status != kCVReturnSuccess {
                fatalError("CVPixelBufferPoolCreatePixelBuffer() Failed")
            }
            
            let pixelBuffer = pixelBufferOut!
            
            CVPixelBufferLockBaseAddress(pixelBuffer,[])
            
            let data = CVPixelBufferGetBaseAddress(pixelBuffer)
            let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
            let context = CGContext(data: data,width: Int(size.width),height: Int(size.height),bitsPerComponent: 8,bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer),space: rgbColorSpace,bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)
            
            context!.clear(CGRect(x: 0,y: 0,width: size.width,height: size.height))
            
            let horizontalRatio = size.width / image.size.width
            let verticalRatio = size.height / image.size.height
            
            let aspectRatio = min(horizontalRatio,verticalRatio) // ScaleAspectFit
            
            let newSize = CGSize(width: image.size.width * aspectRatio,height: image.size.height * aspectRatio)
            
            let x = newSize.width < size.width ? (size.width - newSize.width) / 2 : 0
            let y = newSize.height < size.height ? (size.height - newSize.height) / 2 : 0
            
            context!.concatenate(CGAffineTransform.identity)
            context!.draw(image.cgImage!,in: CGRect(x: x,y: y,width: newSize.width,height: newSize.height))
            
            CVPixelBufferUnlockBaseAddress(pixelBuffer,[])
            
            return pixelBuffer
        }
    }
    
    init(renderSettings: RenderSettings) {
        self.renderSettings = renderSettings
    }
    
    func start() {
        
        let avOutputSettings: [String: AnyObject] = [
            AVVideoCodecKey: renderSettings.avCodecKey as AnyObject,AVVideoWidthKey: NSNumber(value: Float(renderSettings.width)),AVVideoHeightKey: NSNumber(value: Float(renderSettings.height))
        ]
        
        func createPixelBufferAdaptor() {
            let sourcePixelBufferAttributesDictionary = [
                kCVPixelBufferPixelFormatTypeKey as String: NSNumber(value: kCVPixelFormatType_32ARGB),kCVPixelBufferWidthKey as String: NSNumber(value: Float(renderSettings.width)),kCVPixelBufferHeightKey as String: NSNumber(value: Float(renderSettings.height))
            ]
            pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriterInput,sourcePixelBufferAttributes: sourcePixelBufferAttributesDictionary)
        }
        
        func createAssetWriter(outputURL: URL) -> AVAssetWriter {
            guard let assetWriter = try? AVAssetWriter(outputURL: outputURL,fileType: AVFileType.mp4) else {
                fatalError("AVAssetWriter() Failed")
            }
            
            guard assetWriter.canApply(outputSettings: avOutputSettings,forMediaType: AVMediaType.video) else {
                fatalError("canApplyOutputSettings() Failed")
            }
            
            return assetWriter
        }
        
        videoWriter = createAssetWriter(outputURL: renderSettings.outputURL)
        videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video,outputSettings: avOutputSettings)
        
        if videoWriter.canAdd(videoWriterInput) {
            videoWriter.add(videoWriterInput)
        }
        else {
            fatalError("canAddinput() returned false")
        }
        
        
        createPixelBufferAdaptor()
        
        if videoWriter.startWriting() == false {
            fatalError("startWriting() Failed")
        }
        
        videoWriter.startSession(atSourceTime: CMTime.zero)
        
        precondition(pixelBufferAdaptor.pixelBufferPool != nil,"nil pixelBufferPool")
    }
    
    
    func render(appendPixelBuffers: @escaping (VideoWriter)->Bool,completion: @escaping ()->Void) {

        autoreleasepool {
            precondition(videoWriter != nil,"Call start() to initialze the writer")

            let queue = dispatchQueue(label: "mediaInputQueue")
            videoWriterInput.requestMediaDataWhenReady(on: queue) {
                let isFinished = appendPixelBuffers(self)
                if isFinished {
                    self.videoWriterInput.markAsFinished()
                    self.videoWriter.finishWriting() {
                        dispatchQueue.main.async {
                            completion()
                        }
                    }
                }
            }
        }
        
    }
    
    func addImage(image: UIImage,withPresentationTime presentationTime: CMTime) -> Bool {
        
        autoreleasepool {
            precondition(pixelBufferAdaptor != nil,"Call start() to initialze the writer")
            
            let pixelBuffer = VideoWriter.pixelBufferFromImage(image: image,pixelBufferPool: pixelBufferAdaptor.pixelBufferPool!,size: renderSettings.size)
            return pixelBufferAdaptor.append(pixelBuffer,withPresentationTime: presentationTime)
        }
            
    }
    
}

class ImageAnimator {
    
    static let kTimescale: Int32 = 600
    
    let settings: RenderSettings
    let videoWriter: VideoWriter
    var images: [UIImage]!
    
    var frameNum = 0
    
    class func removeFileAtURL(fileURL: URL) {
        do {
            try FileManager.default.removeItem(atPath: fileURL.path)
        }
        catch _ as NSError {
            //
        }
    }
    
    init(renderSettings: RenderSettings,imagearr: [UIImage]) {
        settings = renderSettings
        videoWriter = VideoWriter(renderSettings: settings)
        images = imagearr
    }
    
    func render(completion: @escaping ()->Void) {
        
        // The VideoWriter will fail if a file exists at the URL,so clear it out first.
        ImageAnimator.removeFileAtURL(fileURL: settings.outputURL)
        
        videoWriter.start()
        videoWriter.render(appendPixelBuffers: appendPixelBuffers) {
            let s: String = self.settings.outputURL.path
            tempurl = s
            completion()
        }
        
    }
    
    func appendPixelBuffers(writer: VideoWriter) -> Bool {
        
        let frameDuration = CMTimeMake(value: Int64(ImageAnimator.kTimescale / settings.fps),timescale: ImageAnimator.kTimescale)
        
        
        while !images.isEmpty {
            
            if writer.isReadyForData == false {
                return false
            }
            
            let image = images.removeFirst()
            let presentationTime = CMTimeMultiply(frameDuration,multiplier: Int32(frameNum))
            let success = videoWriter.addImage(image: image,withPresentationTime: presentationTime)
            if success == false {
                fatalError("addImage() Failed")
            }
            
            frameNum=frameNum+1
        }
        
        return true
    }
    
}

内存使用:

enter image description here

使用此代码获取图像:

   @objc public class Recorder: NSObject {
    
    public var view : UIView?
    
    var displayLink : CAdisplayLink?
    var referenceDate : NSDate?
    var imageArray = [UIImage]()
    
    public func start() {
        
        self.imageArray.removeAll()
        
        if (view == nil) {
            NSException(name: NSExceptionName(rawValue: "No view set"),reason: "You must set a view before calling start.",userInfo: nil).raise()
        }else {
            displayLink = CAdisplayLink(target: self,selector: #selector(self.handledisplayLink(displayLink:)))
            displayLink!.add(to: RunLoop.main,forMode: RunLoop.Mode.common)
            referenceDate = NSDate()
        }
        
    }
    
    @objc func handledisplayLink(displayLink : CAdisplayLink) {
        if (view != nil) {
            createImageFromView(captureView: view!)
        }
    }

    
    func createImageFromView(captureView : UIView) {
        
        UIGraphicsBeginImageContextWithOptions(captureView.bounds.size,false,0)
        captureView.drawHierarchy(in: captureView.bounds,afterScreenUpdates: false)
        
        let image = UIGraphicsGetimageFromCurrentimageContext();
        
        if let img = image {
            self.imageArray.append(img)
        }

        UIGraphicsEndImageContext();
    }
    
    public func stop(completion: @escaping (_ saveURL: String) -> Void) {
        
        displayLink?.invalidate()
        
        let seconds = referenceDate?.timeIntervalSinceNow
        if (seconds != nil) {
            
            print("Image Count : \(self.imageArray.count)")            
            dispatchQueue.main.async {
                let settings = RenderSettings()
                let imageAnimator = ImageAnimator(renderSettings: settings,imagearr: self.imageArray)
                imageAnimator.render() {
                    let u: String = tempurl
                    completion(u)
                    //self.saveVideoInPhotos()
                }
            }
            
        }
        
    }    
    
}

提前致谢

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。