Я делаю приложение для 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 хранит их в памяти и никогда не освобождает. Есть ли способ очистить это из памяти?
Функция, которая вызывает бумеранг и использование функции 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)
}
}