Я использую этот класс 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()
}