使用AVFoundation AVAssetWriter录制Metal帧到QuickTime文件时全黑
嘿,我刚踩过和你一模一样的坑!在iOS上用那个MetalVideoRecorder类录MTKView画面完全正常,但换到Mac(NVIDIA GPU)上,生成的QuickTime文件全是黑帧——尺寸对、像素格式也是bgra8Unorm,就是看不到内容。折腾了好一阵终于找到原因和解决办法,分享给你:
问题到底出在哪?
Mac上的NVIDIA GPU(其实部分其他Mac GPU也有这个特性)的帧缓冲区是私有的,和iOS的GPU架构不一样。你之前把framebufferOnly设为false虽然消了报错,但本质上还是没解决纹理数据的同步问题——私有纹理的内容不会自动暴露给读取操作,所以你读出来的全是空数据(也就是黑帧)。
正确的解决步骤
核心思路是:通过BlitCommandEncoder把drawable的私有纹理同步复制到一个可读取的纹理上,再用这个复制后的纹理来写入视频。
1. 提前创建可读取的目标纹理
在MetalVideoRecorder初始化的时候,根据你的MTKView尺寸和像素格式,创建一个支持读取的纹理:
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor( pixelFormat: .bgra8Unorm, width: Int(view.bounds.width), height: Int(view.bounds.height), mipmapped: false ) textureDescriptor.storageMode = .shared // 关键:设置为shared或managed,确保可以被读取 textureDescriptor.usage = [.shaderRead, .shaderWrite, .blitSource, .blitDestination] let targetTexture = device.makeTexture(descriptor: textureDescriptor)!
2. 写入帧前先做Blit复制
不要直接用drawable的纹理调用writeFrame,先执行复制操作确保拿到有效像素:
// 注意:尽量不要在主线程做这个,避免卡顿 let commandBuffer = device.makeCommandBuffer()! let blitEncoder = commandBuffer.makeBlitCommandEncoder()! // 把drawable的纹理内容复制到我们的目标纹理 blitEncoder.copy( from: drawable.texture, sourceSlice: 0, sourceLevel: 0, sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0), sourceSize: MTLSize(width: drawable.texture.width, height: drawable.texture.height, depth: 1), to: targetTexture, destinationSlice: 0, destinationLevel: 0, destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0) ) blitEncoder.endEncoding() commandBuffer.commit() commandBuffer.waitUntilCompleted() // 必须等复制完成再写入 // 现在用复制好的targetTexture来写帧 videoRecorder.writeFrame(texture: targetTexture)
3. 还原framebufferOnly设置
既然现在不需要直接访问drawable的私有纹理了,你可以把MTKView的framebufferOnly改回默认的true,这样性能会更好——毕竟这个属性设为false会带来额外的性能开销。
额外优化建议
如果你的录制场景对性能要求高,别用waitUntilCompleted()阻塞线程,改用命令缓冲区的addCompletedHandler来异步执行写入操作:
commandBuffer.addCompletedHandler { [weak self] _ in guard let self = self else { return } self.videoRecorder.writeFrame(texture: self.targetTexture) } commandBuffer.commit()
这样修改后,你再录制的QuickTime文件就能正常显示MTKView的画面了,我在NVIDIA GPU的Mac上亲测有效!
内容的提问来源于stack exchange,提问作者bsabiston

