You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用AVFoundation AVAssetWriter录制Metal帧到QuickTime文件时全黑

解决Mac上Metal MTKView录制视频全黑的问题

嘿,我刚踩过和你一模一样的坑!在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:52:38