Все черные рамки при попытке записать металлические рамки в файл Quicktime с помощью AVFoundation AVAssetWriter - PullRequest
0 голосов
/ 01 июня 2018

Я использую этот класс Swift (первоначально показанный в ответе на этот вопрос: Capture Metal MTKView как фильм в режиме реального времени? ), чтобы попытаться записать кадры моего приложения Metal в файл фильма.

class MetalVideoRecorder {
    var isRecording = false
    var recordingStartTime = TimeInterval(0)

    private var assetWriter: AVAssetWriter
    private var assetWriterVideoInput: AVAssetWriterInput
    private var assetWriterPixelBufferInput: AVAssetWriterInputPixelBufferAdaptor

    init?(outputURL url: URL, size: CGSize) {
        do {
            assetWriter = try AVAssetWriter(outputURL: url, fileType: AVFileTypeAppleM4V)
        } catch {
            return nil
        }

        let outputSettings: [String: Any] = [ AVVideoCodecKey : AVVideoCodecH264,
            AVVideoWidthKey : size.width,
            AVVideoHeightKey : size.height ]

        assetWriterVideoInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: outputSettings)
        assetWriterVideoInput.expectsMediaDataInRealTime = true

        let sourcePixelBufferAttributes: [String: Any] = [
            kCVPixelBufferPixelFormatTypeKey as String : kCVPixelFormatType_32BGRA,
            kCVPixelBufferWidthKey as String : size.width,
            kCVPixelBufferHeightKey as String : size.height ]

        assetWriterPixelBufferInput = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: assetWriterVideoInput,
                                                                           sourcePixelBufferAttributes: sourcePixelBufferAttributes)

        assetWriter.add(assetWriterVideoInput)
    }

    func startRecording() {
        assetWriter.startWriting()
        assetWriter.startSession(atSourceTime: kCMTimeZero)

        recordingStartTime = CACurrentMediaTime()
        isRecording = true
    }

    func endRecording(_ completionHandler: @escaping () -> ()) {
        isRecording = false

        assetWriterVideoInput.markAsFinished()
        assetWriter.finishWriting(completionHandler: completionHandler)
    }

    func writeFrame(forTexture texture: MTLTexture) {
        if !isRecording {
            return
        }

        while !assetWriterVideoInput.isReadyForMoreMediaData {}

        guard let pixelBufferPool = assetWriterPixelBufferInput.pixelBufferPool else {
            print("Pixel buffer asset writer input did not have a pixel buffer pool available; cannot retrieve frame")
            return
        }

        var maybePixelBuffer: CVPixelBuffer? = nil
        let status  = CVPixelBufferPoolCreatePixelBuffer(nil, pixelBufferPool, &maybePixelBuffer)
        if status != kCVReturnSuccess {
            print("Could not get pixel buffer from asset writer input; dropping frame...")
            return
        }

        guard let pixelBuffer = maybePixelBuffer else { return }

        CVPixelBufferLockBaseAddress(pixelBuffer, [])
        let pixelBufferBytes = CVPixelBufferGetBaseAddress(pixelBuffer)!

        // Use the bytes per row value from the pixel buffer since its stride may be rounded up to be 16-byte aligned
        let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer)
        let region = MTLRegionMake2D(0, 0, texture.width, texture.height)

        texture.getBytes(pixelBufferBytes, bytesPerRow: bytesPerRow, from: region, mipmapLevel: 0)

        let frameTime = CACurrentMediaTime() - recordingStartTime
        let presentationTime = CMTimeMakeWithSeconds(frameTime, 240)
        assetWriterPixelBufferInput.append(pixelBuffer, withPresentationTime: presentationTime)

        CVPixelBufferUnlockBaseAddress(pixelBuffer, [])
    }
}

Я не вижу никаких ошибок, но кадры в результирующем файле Quicktime все черные.Кадры имеют правильный размер, и мой формат пикселей правильный (bgra8Unorm).Кто-нибудь знает, почему он может не работать?

Я вызываю функцию writeFrame перед тем, как представить и зафиксировать текущую отрисовку, например:

        if let drawable = view.currentDrawable {

            if BigVideoWriter != nil && BigVideoWriter!.isRecording {
                commandBuffer.addCompletedHandler { commandBuffer in
                    BigVideoWriter?.writeFrame(forTexture: drawable.texture)
                }
            }

            commandBuffer.present(drawable)
            commandBuffer.commit()      
        }

Сначала я получил ошибку, чтомой слой MetalKitView был 'framebufferOnly'.Поэтому я установил значение false перед попыткой записи.Это избавило от ошибки, но все рамки черные.Я также попытался установить его в false в самом начале программы, но я получаю те же результаты.

Я также попытался использовать «addCompletedHandler» вместо «addScheduledHandler», но это дает мне ошибку »[CAMetalLayerDrawableне нужно вызывать текстуру] после того, как вы уже представили этот чертеж. Вместо этого получите nextDrawable. ".

Спасибо за любые предложения!


РЕДАКТИРОВАТЬ.Idogy.Тестирование показало, что оригинальная версия работала на iOS, но не на Mac.Он сказал, что, поскольку у меня есть графический процессор NVIDIA, кадровые буферы являются частными.Поэтому мне пришлось добавить blitCommandEncoder с вызовом синхронизации на текстуру, после чего он начал работать.Как это:

   if let drawable = view.currentDrawable {

        if BigVideoWriter != nil && BigVideoWriter!.isRecording {
 #if ISMAC
            if let blitCommandEncoder = commandBuffer.makeBlitCommandEncoder() {
                blitCommandEncoder.synchronize(resource: drawable.texture)
                blitCommandEncoder.endEncoding()
            }
 #endif
            commandBuffer.addCompletedHandler { commandBuffer in
                BigVideoWriter?.writeFrame(forTexture: drawable.texture)
            }
        }

        commandBuffer.present(drawable)
        commandBuffer.commit()    
    }

1 Ответ

0 голосов
/ 01 июня 2018

Я полагаю, что вы пишете свои кадры слишком рано - вызывая writeFrame из цикла рендеринга, вы по сути захватываете рисование в то время, когда оно еще пусто (GPU просто еще не рендерил его).

Помните, что перед вызовом commmandBuffer.commit() графический процессор даже не начал рендеринга вашего кадра.Вам нужно подождать, пока графический процессор завершит рендеринг, прежде чем пытаться получить получившийся кадр.Последовательность немного запутана, потому что вы также вызываете present() перед вызовом commit(), но это не фактический порядок операций во время выполнения.Этот present вызов просто говорит Metal запланировать вызов, чтобы представить ваш кадр на экране , как только графический процессор завершит рендеринг .

Вы должны вызвать writeFrame из обработчика завершения (используя commandBuffer.addCompletedHandler()).Это должно позаботиться об этом.

ОБНОВЛЕНИЕ: Хотя ответ выше верен, он только частичный.Так как OP использовал дискретный графический процессор с частной VRAM, процессор не мог видеть целевые пиксели рендеринга.Решением этой проблемы является добавление MTLBlitCommandEncoder и использование метода synchronize(), чтобы гарантировать, что визуализированные пиксели копируются обратно в ОЗУ из VRAM графического процессора.

...