3

I am working with Video, I create video from array of Uimage. Its works fine with low quantity of images, but with big quantity (ex: 100+ images) it crash because of memory Issue.

I have tested when video is creating, memory is growing big but after the process completed memory is still size, not release.

Any help will be appreciated thanks

here is my code:

    func buildVideoFromImageArray(completed:@escaping (String)->Void) {

        selectedPhotosArray = arrimageVideo

        imageArrayToVideoURL = NSURL(fileURLWithPath: NSHomeDirectory() + "/Documents/video1.MP4")
        removeFileAtURLIfExists(url: imageArrayToVideoURL)
        guard let videoWriter = try? AVAssetWriter(outputURL: imageArrayToVideoURL as URL, fileType: AVFileType.mp4) else {
            fatalError("AVAssetWriter error")
        }

        let outputSettings = [AVVideoCodecKey : AVVideoCodecH264, AVVideoWidthKey : NSNumber(value: Float(outputSize.width)), AVVideoHeightKey : NSNumber(value: Float(outputSize.height))] as [String : Any]
        guard videoWriter.canApply(outputSettings: outputSettings, forMediaType: AVMediaType.video) else {
            fatalError("Negative : Can't apply the Output settings...")
        }
        let videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: outputSettings)

        let sourcePixelBufferAttributesDictionary = [kCVPixelBufferPixelFormatTypeKey as String : NSNumber(value: kCVPixelFormatType_32ARGB), kCVPixelBufferWidthKey as String: NSNumber(value: Float(outputSize.width)), kCVPixelBufferHeightKey as String: NSNumber(value: Float(outputSize.height))]
        let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriterInput, sourcePixelBufferAttributes: sourcePixelBufferAttributesDictionary)
        if videoWriter.canAdd(videoWriterInput) {
            videoWriter.add(videoWriterInput)
        }

        if videoWriter.startWriting() {



            videoWriter.startSession(atSourceTime: kCMTimeZero)
            assert(pixelBufferAdaptor.pixelBufferPool != nil)
            let media_queue = DispatchQueue(label: "mediaInputQueue")


            videoWriterInput.requestMediaDataWhenReady(on: media_queue, using: { () -> Void in
                let fps: Int32 = 1000

                var frameCount: Int64 = 0
                var appendSucceeded = true
                var lastTimeVl :Int64 = 0
                var framePerSecond: Int64 = Int64(0)



                for nextDicData in self.selectedPhotosArray{

                    if (videoWriterInput.isReadyForMoreMediaData) {

                        if let nextImage = nextDicData["img"] as? UIImage
                        {
                            var frameDuration = CMTimeMake(Int64(0), fps)
                            if let timeVl = nextDicData["time"] as? Float{
                                   framePerSecond = Int64(timeVl * 1000)
                                print("TIME FRAME : \(timeVl)")

                            }else{
                                 framePerSecond = Int64(0.1 * 1000)
                            }

                            frameDuration =  CMTimeMake(framePerSecond ,fps)
                            let lastFrameTime = CMTimeMake(Int64(lastTimeVl), fps)
                            let presentationTime =  lastFrameTime


                            if !self.appendPixelBufferForImage(image: nextImage, pixelBufferAdaptor: pixelBufferAdaptor, presentationTime: presentationTime){
                                      print("AVAssetWriterInputPixelBufferAdapter failed to append pixel buffer")
                                break
                            }





                            if let index = self.selectedPhotosArray.firstIndex(where: { NSDictionary(dictionary: $0).isEqual(to: nextDicData)}){

                                self.selectedPhotosArray.remove(at: index)

                            }


                        }

                    }else{
                        //not ready
                           print("write is Not Ready: \(lastTimeVl)")
                    }
                    if !appendSucceeded {
                        break
                    }
                    frameCount += 1
                   lastTimeVl += framePerSecond

                  usleep(100000)
                }

                videoWriterInput.markAsFinished()

                videoWriter.endSession(atSourceTime: CMTimeMake(lastTimeVl, fps))
                videoWriter.finishWriting { () -> Void in
                    print("-----video1 url = \(self.imageArrayToVideoURL)")
                    self.urlString = "\(self.imageArrayToVideoURL)"
                    self.asset = AVAsset(url: self.imageArrayToVideoURL as URL)


                    DispatchQueue.main.async {
                        //code that caused error goes here
                        completed(self.urlString)
                         self.setupPlayerVideoWithUrl(strUrl: self.urlString)
                    }

                }
            })
        }


    }

//create pixel Buffer

func appendPixelBufferForImage(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")
                }

                pixelBufferPointer.deallocate()
            }
        }

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

        let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer)
        let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
        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
        )

        context!.clear(CGRect(x: 0, y: 0, width: CGFloat(self.outputSize.width), height: CGFloat(self.outputSize.height)))
        context?.draw(image.cgImage!, in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))

        CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
    }
Anand
  • 1,820
  • 2
  • 18
  • 25
Anna
  • 181
  • 3
  • 8
  • Where do all those images come from? And where does `arrimageVideo` come from? Suppose that each image takes 5 MB of memory. And you keep them all in `arrimageVideo`. So how much memory does your app take? – El Tomato Apr 06 '19 at 03:44
  • Hi EL! all images come from UIGraphicsGetCurrentContext, and size of image is 1920*1080 , arrimageVideo include all of them, memory take 1Gb – Anna Apr 06 '19 at 03:52
  • Save all images in your sandbox folder and read each of them one by one. – El Tomato Apr 06 '19 at 04:11
  • memory grow up to 1+Gb and app crash when creating video. What's is sandbox folder , is it include : document, Temp, cache thanks. sorry for my experience – Anna Apr 06 '19 at 04:19
  • Hey @Anna, did you find a solution to this? I'm currently stuck at the same point. – JanMensch Jan 29 '20 at 12:28
  • @ElTomato good suggestion, you should have left some code as an answer – Lance Samaria Feb 10 '20 at 17:24
  • hey, @ElTomato any workaround for the same, since the last couple of days facing the same issue. – Darshan Mar 11 '21 at 07:11
  • @user1374 Use an array of strings with file paths, instead of an array of image objects. – El Tomato Mar 12 '21 at 00:24

1 Answers1

0

I have same issue in my application, and after spending number of hours on it. finally i got solution to release memory during draw image with CVPixelBuffer.

Please refer code below.

func pixelBufferFromImage(image: UIImage) -> CVPixelBuffer {
        
        let ciimage = CIImage(image: image)
        let tmpcontext = CIContext(options: nil)
        let cgimage =  tmpcontext.createCGImage(ciimage!, from: ciimage!.extent)
        
        let cfnumPointer = UnsafeMutablePointer<UnsafeRawPointer>.allocate(capacity: 1)
        let cfnum = CFNumberCreate(kCFAllocatorDefault, .intType, cfnumPointer)
        let keys: [CFString] = [kCVPixelBufferCGImageCompatibilityKey, kCVPixelBufferCGBitmapContextCompatibilityKey, kCVPixelBufferBytesPerRowAlignmentKey]
        let values: [CFTypeRef] = [kCFBooleanTrue, kCFBooleanTrue, cfnum!]
        let keysPointer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 1)
        let valuesPointer =  UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 1)
        keysPointer.initialize(to: keys)
        valuesPointer.initialize(to: values)
        let options:[String: Any] = [kCVPixelBufferCGImageCompatibilityKey as String: true, kCVPixelBufferCGBitmapContextCompatibilityKey as String: true]
       
        let width = Int(image.size.width)
        let height = Int(image.size.width)
     
        var pxbuffer: CVPixelBuffer?
        // if pxbuffer = nil, you will get status = -6661
        var status = CVPixelBufferCreate(kCFAllocatorDefault, width, height,
                                         kCVPixelFormatType_32BGRA, options as CFDictionary, &pxbuffer)
        assert(status == kCVReturnSuccess && pxbuffer != nil, "newPixelBuffer failed")

        status = CVPixelBufferLockBaseAddress(pxbuffer!, CVPixelBufferLockFlags(rawValue: 0));
        
        let bufferAddress = CVPixelBufferGetBaseAddress(pxbuffer!);

        let rgbColorSpace = CGColorSpaceCreateDeviceRGB();
        let bytesperrow = CVPixelBufferGetBytesPerRow(pxbuffer!)
        let context = CGContext(data: bufferAddress,
                                width: width,
                                height: height,
                                bitsPerComponent: 8,
                                bytesPerRow: bytesperrow,
                                space: rgbColorSpace,
                                bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue);
        context?.concatenate(CGAffineTransform(rotationAngle: 0))            
        context?.draw(cgimage!, in: CGRect(x:0, y:0, width:CGFloat(width), height:CGFloat(height)));
        status = CVPixelBufferUnlockBaseAddress(pxbuffer!, CVPixelBufferLockFlags(rawValue: 0));
        return pxbuffer!;
        
    }
Zarna Parikh
  • 181
  • 4