2014-09-01 6 views
8

Ho implementato i suggerimenti precedenti con Swift (How to use CVPixelBufferPool in conjunction with AVAssetWriterInputPixelBufferAdaptor in iPhone?), ma sono bloccato con una "kCVReturnInvalidArgument" (valore di errore: -6661) quando si utilizza CVPixelBufferPoolCreatePixelBuffer come guida.CVPixelBufferPool Errore (kCVReturnInvalidArgument/-6661)

Fondamentalmente sto provando a creare un film da immagini, ma poiché il pool di buffer non è stato creato correttamente, non posso aggiungere buffer di pixel - ecco il mio codice per farlo.

Qualsiasi suggerimento è molto apprezzato!

import Foundation 
import Photos 
import OpenGLES 
import AVFoundation 
import CoreMedia 

class MovieGenerator { 

    var _videoWriter:AVAssetWriter 
    var _videoWriterInput: AVAssetWriterInput 
    var _adapter: AVAssetWriterInputPixelBufferAdaptor 
    var _buffer = UnsafeMutablePointer<Unmanaged<CVPixelBuffer>?>.alloc(1) 


    init(frameSize size: CGSize, outputURL url: NSURL) { 

    // delete file if exists 
    let sharedManager = NSFileManager.defaultManager() as NSFileManager 
    if(sharedManager.fileExistsAtPath(url.path!)) { 
     sharedManager.removeItemAtPath(url.path, error: nil) 
    } 

    // video writer 
    _videoWriter = AVAssetWriter(URL: url, fileType: AVFileTypeQuickTimeMovie, error: nil) 

    // writer input 
    var videoSettings = [AVVideoCodecKey:AVVideoCodecH264, AVVideoWidthKey:size.width, AVVideoHeightKey:size.height] 
    _videoWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings) 
    _videoWriterInput.expectsMediaDataInRealTime = true 
    _videoWriter.addInput(_videoWriterInput) 

    // pixel buffer adapter 
    var adapterAttributes = [kCVPixelBufferPixelFormatTypeKey:kCVPixelFormatType_32BGRA, kCVPixelBufferWidthKey: size.width, 
     kCVPixelBufferHeightKey: size.height, 
     kCVPixelFormatOpenGLESCompatibility: kCFBooleanTrue] 

    _adapter = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: _videoWriterInput, sourcePixelBufferAttributes: adapterAttributes) 
    var poolCreateResult:CVReturn = CVPixelBufferPoolCreatePixelBuffer(nil, _adapter.pixelBufferPool, _buffer) 
    println("pool creation:\(poolCreateResult)") 

    _videoWriter.startWriting() 
    _videoWriter.startSessionAtSourceTime(kCMTimeZero) 

} 

func addImage(image:UIImage, frameNum:Int, fps:Int)->Bool { 


    self.createPixelBufferFromCGImage(image.CGImage, pixelBufferPtr: _buffer) 

    var presentTime:CMTime = CMTimeMake(Int64(frameNum), Int32(fps)) 
    var result:Bool = _adapter.appendPixelBuffer(_buffer.memory?.takeUnretainedValue(), withPresentationTime: presentTime) 

    return result 
} 

func finalizeMovie(timeStamp: CMTime) { 
    _videoWriterInput.markAsFinished() 
    _videoWriter.endSessionAtSourceTime(timeStamp) 
    _videoWriter.finishWritingWithCompletionHandler({println("video writer finished with status: \(self._videoWriter.status)")}) 
} 

func createPixelBufferFromCGImage(image: CGImage, pixelBufferPtr: UnsafeMutablePointer<Unmanaged<CVPixelBuffer>?>) { 

    let width:UInt = CGImageGetWidth(image) 
    let height:UInt = CGImageGetHeight(image) 

    let imageData:CFData = CGDataProviderCopyData(CGImageGetDataProvider(image)) 
    let options:CFDictionary = [kCVPixelBufferCGImageCompatibilityKey:NSNumber.numberWithBool(true), kCVPixelBufferCGBitmapContextCompatibilityKey:NSNumber.numberWithBool(true)] 

    var status:CVReturn = CVPixelBufferCreate(kCFAllocatorDefault, width, height, OSType(kCVPixelFormatType_32BGRA), options, pixelBufferPtr) 
    assert(status != 0,"CVPixelBufferCreate: \(status)") 

    var lockStatus:CVReturn = CVPixelBufferLockBaseAddress(pixelBufferPtr.memory?.takeUnretainedValue(), 0) 
    println("CVPixelBufferLockBaseAddress: \(lockStatus)") 

    var pxData:UnsafeMutablePointer<(Void)> = CVPixelBufferGetBaseAddress(pixelBufferPtr.memory?.takeUnretainedValue()) 
    let bitmapinfo = CGBitmapInfo.fromRaw(CGImageAlphaInfo.NoneSkipFirst.toRaw()) 
    let rgbColorSpace:CGColorSpace = CGColorSpaceCreateDeviceRGB() 

    var context:CGContextRef = CGBitmapContextCreate(pxData, width, height, 8, 4*CGImageGetWidth(image), rgbColorSpace, bitmapinfo!) 

    CGContextDrawImage(context, CGRectMake(0, 0, CGFloat(width), CGFloat(height)), image) 

    CVPixelBufferUnlockBaseAddress(pixelBufferPtr.memory?.takeUnretainedValue(), 0) 


} 



} 
+0

Ho risolto (o, più precisamente, risolto il problema) implementando la mia funzione 'createPixelBufferFromCGImage' in obj-c e chiamando 'CVPixelBufferPoolCreatePixelBuffer' da lì. La mia ipotesi è che i nostri puntatori Swift 'CVPixelBuffer' siano in qualche modo incompatibili con' CVPixelBufferPoolCreatePixelBuffer', creando così l'errore "argomento non valido". Se avrò una versione di Swift funzionante, posterò una risposta. – acj

risposta

6

Non riesco esattamente a rispondere alla tua domanda, frustrante, ma sto lavorando su codice che fa essenzialmente la stessa cosa. E, il mio capita di andare oltre l'errore che hai ottenuto; arriva fino al punto in cui sta tentando di aggiungere le immagini al film e poi fallisce semplicemente non ottenendo mai un risultato positivo da appendPixelBuffer() - e non sono sicuro di come capire perché. Sto postando questo nella speranza che ti aiuti ad andare oltre, però.

(Il mio codice è adattato da AVFoundation + AssetWriter: Generate Movie With Images and Audio, e ho usato il tuo post per navigare som e degli imbrogli puntatore di interoperabilità ...)

func writeAnimationToMovie(path: String, size: CGSize, animation: Animation) -> Bool { 
    var error: NSError? 
    let writer = AVAssetWriter(URL: NSURL(fileURLWithPath: path), fileType: AVFileTypeQuickTimeMovie, error: &error) 

    let videoSettings = [AVVideoCodecKey: AVVideoCodecH264, AVVideoWidthKey: size.width, AVVideoHeightKey: size.height] 

    let input = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings) 
    let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: input, sourcePixelBufferAttributes: nil) 
    input.expectsMediaDataInRealTime = true 
    writer.addInput(input) 

    writer.startWriting() 
    writer.startSessionAtSourceTime(kCMTimeZero) 

    var buffer: CVPixelBufferRef 

    var frameCount = 0 
    for frame in animation.frames { 
     let rect = CGRectMake(0, 0, size.width, size.height) 
     let rectPtr = UnsafeMutablePointer<CGRect>.alloc(1) 
     rectPtr.memory = rect 
     buffer = pixelBufferFromCGImage(frame.image.CGImageForProposedRect(rectPtr, context: nil, hints: nil).takeUnretainedValue(), size) 
     var appendOk = false 
     var j = 0 
     while (!appendOk && j < 30) { 
      if pixelBufferAdaptor.assetWriterInput.readyForMoreMediaData { 
       let frameTime = CMTimeMake(Int64(frameCount), 10) 
       appendOk = pixelBufferAdaptor.appendPixelBuffer(buffer, withPresentationTime: frameTime) 
       // appendOk will always be false 
       NSThread.sleepForTimeInterval(0.05) 
      } else { 
       NSThread.sleepForTimeInterval(0.1) 
      } 
      j++ 
     } 
     if (!appendOk) { 
      println("Doh, frame \(frame) at offset \(frameCount) failed to append") 
     } 
    } 

    input.markAsFinished() 
    writer.finishWritingWithCompletionHandler({ 
     if writer.status == AVAssetWriterStatus.Failed { 
      println("oh noes, an error: \(writer.error.description)") 
     } else { 
      println("hrmmm, there should be a movie?") 
     } 
    }) 

    return true; 
} 

Dove pixelBufferFromCGImage è definito in questo modo:

func pixelBufferFromCGImage(image: CGImageRef, size: CGSize) -> CVPixelBufferRef { 
    let options = [ 
     kCVPixelBufferCGImageCompatibilityKey: true, 
     kCVPixelBufferCGBitmapContextCompatibilityKey: true] 
    var pixBufferPointer = UnsafeMutablePointer<Unmanaged<CVPixelBuffer>?>.alloc(1) 

    let status = CVPixelBufferCreate(
     nil, 
     UInt(size.width), UInt(size.height), 
     OSType(kCVPixelFormatType_32ARGB), 
     options, 
     pixBufferPointer) 

    CVPixelBufferLockBaseAddress(pixBufferPointer.memory?.takeUnretainedValue(), 0) 

    let rgbColorSpace = CGColorSpaceCreateDeviceRGB() 
    let bitmapinfo = CGBitmapInfo.fromRaw(CGImageAlphaInfo.NoneSkipFirst.toRaw()) 

    var pixBufferData:UnsafeMutablePointer<(Void)> = CVPixelBufferGetBaseAddress(pixBufferPointer.memory?.takeUnretainedValue()) 

    let context = CGBitmapContextCreate(
     pixBufferData, 
     UInt(size.width), UInt(size.height), 
     8, UInt(4 * size.width), 
     rgbColorSpace, bitmapinfo!) 

    CGContextConcatCTM(context, CGAffineTransformMakeRotation(0)) 
    CGContextDrawImage(
     context, 
     CGRectMake(0, 0, CGFloat(CGImageGetWidth(image)), CGFloat(CGImageGetHeight(image))), 
     image) 

    CVPixelBufferUnlockBaseAddress(pixBufferPointer.memory?.takeUnretainedValue(), 0) 
    return pixBufferPointer.memory!.takeUnretainedValue() 
} 
+1

Ah, stupido. In realtà non avevo chiamato 'markAsFinished' sull'input e' finishWritingWithCompletionHandler() 'sul writer! –

+0

Hai mai funzionato?Sto lavorando alla stessa cosa (porting di codice Objective C funzionante a Swift). – tremolo

4

per la docs per pixelBufferPool:

Questo prope rty è NULL prima della prima chiamata a startSessionAtTime: sull'oggetto AVAssetWriter associato.

Spostare la chiamata al CVPixelBufferPoolCreatePixelBuffer alla fine del init dovrebbe risolvere il problema immediato.

Poche altre osservazioni:

  • Tu hai il tuo AVAssetWriterInputPixelBufferAdaptor configurato per BGRA, ma in createPixelBufferFromCGImage si sta utilizzando RGB. I tuoi video finali sembreranno strani se i formati dei pixel non sono corrispondenti.
  • Non è necessario chiamare CVPixelBufferCreate nel metodo createPixelBufferFromCGImage. Ciò annulla lo scopo dell'utilizzo del pool di buffer.
  • Se si esegue questo in un ciclo stretto, il consumo di memoria diventerà un problema. Utilizzare autoreleasepool e fare attenzione con takeUnretainedValue vs takeRetainedValue aiuterà.

Ho postato reference implementations per Swift 1.2, 2.0 e 3.0 che utilizzano pool di buffer.

+0

cosa succede se si impostano invece gli attributi per AVAssetWriterInputPixelBufferAdaptor su zero? altre risposte SO sembrano farlo. mente spiegando perché hai impostato in modo esplicito gli attributi? grazie per aver condiviso il codice! – Crashalot

+0

anche nel codice 2.0 perché imposti frameCount = Int64 (0) all'interno del blocco di requestMediaDataWhenReadyOnQueue? Questo non significa che ogni volta che viene richiamato il blocco che frameCount viene reimpostato su 0? Ad esempio, se si scrivono 100 immagini e quindi readyForMoreMediaData diventa false, quando si ricomincia dall'immagine 101, frameCount verrà reimpostato su 0 anziché essere 101? – Crashalot

+0

@Crashalot Probabilmente si può usare 'nil' per gli attributi. Ho fatto alcuni esperimenti con spazi colore e dimensioni diverse e ho appena lasciato il codice in posizione. // Per i set di foto che ho usato nei miei test, 'readyForMoreMediaData' è sempre stato vero e non ha attivato lo scenario che stai descrivendo. Hai ragione che potrebbe presentare un problema, però, e per motivi di robustezza probabilmente dovresti mantenere quello stato ('frameCount', ecc.) Al di fuori del blocco. – acj