ios – swift creating video the output is darker video than images

My app takes a snapshot of the view and saves it as an image so the user can generate a video using that image and some audio I followed the tutorials

https://img.ly/blog/how-to-make-videos-from-still-images-with-avfoundation-and-swift/ http://twocentstudios.com/2017/02/20/creating-a- movie-with-an-image-and-sound-on-ios/ https://www.hackingwithswift.com/quick-start/swiftui/how-to-convert-a-swiftui-view-to-an-image

The video is successfully generated with audio and photo, but the output video is always darker than the produced photo from the snapshot.

saved image and video from the photos app

Photo

video

here are some features

snapshot

extension View {
    func snapshot() -> UIImage {
        let controller = UIHostingController(rootView: self.ignoresSafeArea(.all))
        let view = controller.view

    let targetSize = controller.view.intrinsicContentSize 
        view?.bounds = CGRect(origin: .zero, size: targetSize)
        view?.backgroundColor = .clear
    
        let renderer = UIGraphicsImageRenderer(size: targetSize)

        return renderer.image { _ in
            view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
        }
        
    }
    
}

Create video using image

  func writeSingleImageToMovie(image: UIImage, movieLength: TimeInterval, outputFileURL: URL, completion: @escaping (Error?) -> ())
     {
        print("writeSingleImageToMovie is called")
   
     
     
        do {
            let imageSize = image.size
            let videoWriter = try AVAssetWriter(outputURL: outputFileURL, fileType: AVFileType.mp4)
            let videoSettings: [String: Any] = [AVVideoCodecKey: AVVideoCodecType.h264,
                                                AVVideoWidthKey: imageSize.width, //was imageSize.width
                                                AVVideoHeightKey: imageSize.height] //was imageSize.height
            
          
            let videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings)
            let adaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriterInput, sourcePixelBufferAttributes: nil)
            
            if !videoWriter.canAdd(videoWriterInput) { throw NSError() }
            videoWriterInput.expectsMediaDataInRealTime = true
            videoWriter.add(videoWriterInput)
            
            videoWriter.startWriting()
            let timeScale: Int32 = 4 // 600 recommended in CMTime for movies.
            let halfMovieLength = Float64(movieLength/2.0) // videoWriter assumes frame lengths are equal.
            let startFrameTime = CMTimeMake(value: 0, timescale: timeScale)
         
            
            let endFrameTime = CMTimeMakeWithSeconds(Double(60), preferredTimescale: timeScale) 
                                                  
            videoWriter.startSession(atSourceTime: startFrameTime)
          
         
            
        guard let cgImage = image.cgImage else { throw NSError() }
             
     
            let buffer: CVPixelBuffer = try CGImage.pixelBuffer(fromImage: cgImage, size: imageSize)

            while !adaptor.assetWriterInput.isReadyForMoreMediaData { usleep(10) }
            adaptor.append(buffer, withPresentationTime: startFrameTime)
            while !adaptor.assetWriterInput.isReadyForMoreMediaData { usleep(10) }
            adaptor.append(buffer, withPresentationTime: endFrameTime)
            
            videoWriterInput.markAsFinished()
            videoWriter.finishWriting
            {
                completion(videoWriter.error)
            }
            
        } catch {
            print("CATCH Error in writeSingleImageToMovie")
            completion(error)
        }
}

Here is the function to create CVPixelBuffer, I tried to create buffer using CIImage also but got the same result

extension CGImage {
 
    static func pixelBuffer(fromImage image: CGImage, size: CGSize) throws -> CVPixelBuffer {
        print("pixelBuffer from CGImage")
        let options: CFDictionary = [kCVPixelBufferCGImageCompatibilityKey as String: true, kCVPixelBufferCGBitmapContextCompatibilityKey as String: true] as CFDictionary
        var pxbuffer: CVPixelBuffer? = nil
        let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(size.width), Int(size.height), kCVPixelFormatType_32ARGB, options, &pxbuffer) 
        guard let buffer = pxbuffer, status == kCVReturnSuccess else { throw NSError() }
        
        CVPixelBufferLockBaseAddress(buffer, [])
        guard let pxdata = CVPixelBufferGetBaseAddress(buffer)
        else { throw NSError() }
        let bytesPerRow = CVPixelBufferGetBytesPerRow(buffer)
        
        let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
         
            guard let context = CGContext(data: pxdata, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue) else { print("error in `CG context")
                throw NSError() }
        context.concatenate(CGAffineTransform(rotationAngle: 0))
        context.draw(image, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
        
        CVPixelBufferUnlockBaseAddress(buffer, [])
        
        return buffer
        }
}

I got stuck on this problem, I can’t seem to find a solution.. any tips would be appreciated.

William

Leave a Reply

Your email address will not be published. Required fields are marked *