Быстрое управление памятью при съемке - PullRequest
0 голосов
/ 28 октября 2019

Я делаю приложение для iOS, которое делает фотографии, делает видео с бумерангом и делает гифки. Для бумеранга я делаю 16 снимков и помещаю их в массив, а затем я снова добавляю их в обратном порядке к мужскому эффекту бумеранга, который составляет в общей сложности 31 изображение, потому что я не повторяю последнее. Я преобразовываю этот массив в видео, используя следующий код:

func writeImagesAsMovie(_ allImages: [UIImage], videoPath: String, videoSize: CGSize, videoFPS: Int32, completion: @escaping (Bool) -> ()) -> Bool{

    guard let assetWriter = try? AVAssetWriter(outputURL: URL(string: videoPath)!, fileType: AVFileType.mp4) else {
        fatalError("AVVideoCodecType.h264 error")
    }
    let outputSettings = [AVVideoCodecKey : AVVideoCodecType.h264, AVVideoWidthKey : NSNumber(value: Float(videoSize.width)), AVVideoHeightKey : NSNumber(value: Float(videoSize.height))] as [String : Any]
    guard assetWriter.canApply(outputSettings: outputSettings, forMediaType: AVMediaType.video) else {
        fatalError("Negative : Can't apply the Output settings...")
    }
    let writerInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: outputSettings)


    let sourcePixelBufferAttributesDictionary = [kCVPixelBufferPixelFormatTypeKey as String : NSNumber(value: kCVPixelFormatType_32ARGB), kCVPixelBufferWidthKey as String: NSNumber(value: Float(videoSize.width)), kCVPixelBufferHeightKey as String: NSNumber(value: Float(videoSize.height))]
    let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: writerInput, sourcePixelBufferAttributes: sourcePixelBufferAttributesDictionary)
    if assetWriter.canAdd(writerInput) {
        assetWriter.add(writerInput)
    }
    // Start writing session
    if assetWriter.startWriting() {
        assetWriter.startSession(atSourceTime: CMTime.zero)                       
        // -- Create queue for <requestMediaDataWhenReadyOnQueue>
        assert(pixelBufferAdaptor.pixelBufferPool != nil)
        let mediaQueue = DispatchQueue(label: "mediaInputQueue", attributes: [])

        // -- Set video parameters
        let frameDuration = CMTimeMake(value: 1, timescale: videoFPS)
        var frameCount = 0

        // -- Add images to video
        let numImages = allImages.count
        writerInput.requestMediaDataWhenReady(on: mediaQueue, using: { [unowned self]() -> Void in
            // Append unadded images to video but only while input ready
            while (writerInput.isReadyForMoreMediaData && frameCount < numImages) {
                let lastFrameTime = CMTimeMake(value: Int64(frameCount), timescale: videoFPS)
                let presentationTime = frameCount == 0 ? lastFrameTime : CMTimeAdd(lastFrameTime, frameDuration)

                if !self.appendPixelBufferForImageAtURL(allImages[frameCount], pixelBufferAdaptor: pixelBufferAdaptor, presentationTime: presentationTime) {
                    print("Error converting images to video: AVAssetWriterInputPixelBufferAdapter failed to append pixel buffer")
                    return
                }

                frameCount += 1
            }

            // No more images to add? End video.
            if (frameCount >= numImages) {
                writerInput.markAsFinished()
                assetWriter.finishWriting {
                    if (assetWriter.error != nil) {
                        print("Error converting images to video: \(String(describing: assetWriter.error))")
                    } else {
                        print("Converted images to movie @ \(videoPath)")
                        completion(true)
                    }
                }
            }
        })
    }

    return true
}


func createAssetWriter(_ path: String, size: CGSize) -> AVAssetWriter? {
    // Convert <path> to NSURL object
    let pathURL = URL(fileURLWithPath: path)

    // Return new asset writer or nil
    do {
        // Create asset writer
        let newWriter = try AVAssetWriter(outputURL: pathURL, fileType: AVFileType.mp4)

        // Define settings for video input
        let videoSettings: [String : AnyObject] = [
            AVVideoCodecKey  : AVVideoCodecType.h264 as AnyObject,
            AVVideoWidthKey  : size.width as AnyObject,
            AVVideoHeightKey : size.height as AnyObject,
        ]

        // Add video input to writer
        let assetWriterVideoInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings)
        newWriter.add(assetWriterVideoInput)

        // Return writer
        print("Created asset writer for \(size.width)x\(size.height) video")
        return newWriter
    } catch {
        print("Error creating asset writer: \(error)")
        return nil
    }
}


func appendPixelBufferForImageAtURL(_ image: UIImage, pixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor, presentationTime: CMTime) -> Bool {
    var appendSucceeded = false

    autoreleasepool {
        if  let pixelBufferPool = pixelBufferAdaptor.pixelBufferPool {
            let pixelBufferPointer = UnsafeMutablePointer<CVPixelBuffer?>.allocate(capacity:1)
            let status: CVReturn = CVPixelBufferPoolCreatePixelBuffer(
                kCFAllocatorDefault,
                pixelBufferPool,
                pixelBufferPointer
            )

            if let pixelBuffer = pixelBufferPointer.pointee , status == 0 {
                fillPixelBufferFromImage(image, pixelBuffer: pixelBuffer)
                appendSucceeded = pixelBufferAdaptor.append(pixelBuffer, withPresentationTime: presentationTime)                    
                pixelBufferPointer.deinitialize(count: 1)
            } else {
                NSLog("Error: Failed to allocate pixel buffer from pool")
            }
            //capacity: 1
            pixelBufferPointer.deallocate()
        }
    }

    return appendSucceeded
}


func fillPixelBufferFromImage(_ image: UIImage, pixelBuffer: CVPixelBuffer) {
    CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))

    let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer)
    let rgbColorSpace = CGColorSpaceCreateDeviceRGB()

    // Create CGBitmapContext
    let context = CGContext(
        data: pixelData,
        width: Int(image.size.width),
        height: Int(image.size.height),
        bitsPerComponent: 8,
        bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer),
        space: rgbColorSpace,
        bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue
        )!

    // Draw image into context
    let drawCGRect = CGRect(x:0, y:0, width:image.size.width, height:image.size.height)
    var drawRect = NSCoder.string(for: drawCGRect);
    let ciImage = CIImage(image: image)
    let cgImage = convertCIImageToCGImage(inputImage: ciImage!)
    context.draw(cgImage!, in: CGRect(x: 0.0,y: 0.0,width: image.size.width,height: image.size.height))

    CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: CVOptionFlags(0)))
}

func convertCIImageToCGImage(inputImage: CIImage) -> CGImage! {
    let context = CIContext(options: nil)
    if context != nil {
        return context.createCGImage(inputImage, from: inputImage.extent)
    }
    return nil
}

Моя проблема с управлением памятью, мое приложение вылетает после 3 или 4 бумерангов. Я не знаю, что еще делать, я перепробовал множество советов по управлению памятью. Я использую autoreleasepool, и слабую и непризнанную личность, когда могу. Также нет нигде предупреждения об утечке памяти. Я замечаю, что после того, как я делаю бумеранг, всегда есть дополнительные 100 мб, которые никогда не будут выпущены. Как приложение начинается с 70 МБ, когда делает бумеранг, оно достигает 800 МБ, после завершения оно достигает 200 МБ и остается там. Если я сделаю еще один бумеранг, он уйдет сейчас в 900 мб, после того, как будет сделано, он уйдет в 300 мб, и теперь это новый минимум. Так будет до тех пор, пока не останется свободной памяти и приложение не будет работать во время создания нового бумеранга.

Я не думаю, что проблема в моем преобразовании в видео для бумеранга, потому что это постоянное накопление памятитакже происходит с обычными картинками и гифками, но в меньшем количестве, поэтому приложение никогда не накапливается достаточно, чтобы аварийно завершить работу.

Я также удаляю все после того, как использую. Я опустошил массив изображений бумеранга и установил все UIImages на ноль, где-то видел, что это может помочь, но это ничего не изменило. Я не знаю, хранятся ли сделанные снимки где-то в системе, которые мне нужно удалить.

Вот код для фотографирования:

@objc func takePhoto(_ sender: Any?) {
    TapToStart.alpha = 0
    contador.alpha = 1
    if deviceOrientation == .portrait {
        lookHereView.alpha = 1
    }

    reescolherMoldura.isEnabled = false
    var videoOrientation = AVCaptureVideoOrientation.portrait
    if moldura.larguraFoto > moldura.alturaFoto{
        videoOrientation = AVCaptureVideoOrientation.landscapeRight
    }
    stillImageOutput!.connection(with: .video)?.videoOrientation = videoOrientation       
    let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
    let gesture = previewView.gestureRecognizers
    previewView.removeGestureRecognizer(gesture![0])
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 
        self.contador.text = "2"
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 
            self.contador.text = "1"
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 
                self.contador.alpha = 0
                self.stillImageOutput!.capturePhoto(with: settings, delegate: self)
            }
        }
    }
}

func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
    guard let imageData = photo.fileDataRepresentation()
        else { return }
    if tipoDeFoto == "normal" {
        tirarFotoNormal(imageData: imageData)
    }
    else if tipoDeFoto == "gif" {
        tirarGif(imageData: imageData)
    }
    else if tipoDeFoto == "boomerang" {
        tirarBoomerang(imageData: imageData)
    }
}

Я не знаюНе знаю, что еще попробовать, любая помощь приветствуется.

Редактировать

Тщательная проверка инструментов Я понял, что приложение хранит все фотографии, которые я делаюв памяти. Я удаляю все ссылки на изображения, как только я закончу с ними. Поэтому я предполагаю, что AVCapturePhotoOutput хранит их в памяти и никогда не освобождает. Есть ли способ очистить это из памяти?

Image of all the picture in memory

Функция, которая вызывает бумеранг и использование функции writeImagesAsMovie:

func tirarBoomerang(imageData: Data){
    UIGraphicsBeginImageContextWithOptions(previewView.frame.size, false, 0.0)
    previewView.superview!.layer.render(in: UIGraphicsGetCurrentContext()!)
    let foto = UIImage(data: imageData)!        
    let cropedPhoto = cropToBounds(image: foto, width: Double(moldura.larguraFoto), height: Double(moldura.alturaFoto))
    let resultImage =  blendImages(cropedPhoto, molduraImagem!)
    fotosBoomerangArray.append(resultImage!)        
    numFotosLabel.text = "\(fotosBoomerangArray.count)/16"
    if fotosBoomerangArray.count == 16 {
        for (index, fotoBoomerang) in fotosBoomerangArray.enumerated().reversed() {
            if index == 15{
                continue
            }
            autoreleasepool {
                fotosBoomerangArray.append(fotoBoomerang)
            }
        }

        SVProgressHUD.show(withStatus: "Aguarde")
        SVProgressHUD.setDefaultStyle(.dark)
        let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let fileURL = documentsURL.appendingPathComponent("\(public_id!.description).mp4")

        let sizeVideo = CGSize(width: moldura.larguraMoldura, height: moldura.alturaMoldura)

        imageToVideo.writeImagesAsMovie(fotosBoomerangArray, videoPath: fileURL.absoluteString, videoSize: sizeVideo, videoFPS: 30) { [weak self](success) in
            DispatchQueue.main.async {
                SVProgressHUD.dismiss()
                self!.irParaPreview()
            }
        }

    }
    else {
        fotoNumero += 1
        let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
        self.stillImageOutput!.capturePhoto(with: settings, delegate: self)
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...