乐于分享
好东西不私藏

【音视频】iOS Metal 纹理转换 CVPixelBuffer

【音视频】iOS Metal 纹理转换 CVPixelBuffer

这个公众号会路线图式的遍历分享音视频技术音视频基础 → 音视频工具 → 音视频工程示例 → 音视频工业实战。欢迎关注!

欢迎关注!音视频技术讨论和交流欢迎加群 ↓↓↓

这个系列文章我们来介绍一位海外工程师如何探索安卓音视频基础技术,对于想要开始学习音视频技术的朋友,这些文章是份不错的入门资料,本篇介绍音视频iOS 客户端高像素(12MP / 48MP)拍摄

学习和提升音视频开发技术,推荐你加入我们的知识星球:【关键帧的音视频开发圈】,加入后你就能:

1)下载 30+ 个开箱即用的「音视频及渲染 Demo 源代码」

2)下载包含 500+ 知识条目的完整版「音视频知识图谱」

3)下载包含 200+ 题目的完整版「音视频面试题集锦」

4)技术和职业发展咨询 100% 得到回答

5)获得简历优化建议和大厂内推

现在加入,送你一张 20 元优惠券

扫码领取优惠券 👇👇👇


将 GPU 端的纹理数据转回 CVPixelBuffer 的核心在于:不要通过 CPU 拷贝数据。我们应该利用 IOSurface 的共享内存特性,让 Metal 直接渲染到 CVPixelBuffer 绑定的纹理上。


1、核心思路:Render-to-Texture

最有效的方法不是“转换”,而是“直接绘制”

  1. 从 CVPixelBufferPool 或 CVPixelBufferCreate 创建一个带有 IOSurface 属性的 Buffer。
  2. 使用 CVMetalTextureCache 将该 Buffer 映射为一个可写的 MTLTexture
  3. 将此纹理作为 Metal 渲染管线的 Render Target (colorAttachment) 或 Compute Shader 的 Destination Texture

2、 初始化目标 CVPixelBuffer

确保 Buffer 包含 kCVPixelBufferMetalCompatibilityKey,这是 GPU 写入的前提。

- (CVPixelBufferRef)createMatchingPixelBufferWithWidth:(size_t)width height:(size_t)height format:(OSType)format {    CVPixelBufferRef pixelBuffer = NULL;NSDictionary *attributes = @{        (id)kCVPixelBufferIOSurfacePropertiesKey: @{}, // 启用 IOSurface 共享内存        (id)kCVPixelBufferMetalCompatibilityKey: @YES,  // 允许 Metal 直接访问        (id)kCVPixelBufferOpenGLCompatibilityKey: @YES // 可选:兼容 OpenGL    };    CVPixelBufferCreate(kCFAllocatorDefault,                        width,                        height,                        format, // 例如 kCVPixelFormatType_32BGRA                        (__bridge CFDictionaryRef)attributes,                        &pixelBuffer);return pixelBuffer;}

3、将纹理渲染/复制回 Buffer (Objective-C 实现)

这里展示如何将一个已有的 sourceTexture(Metal 处理后的结果)同步到 CVPixelBuffer 中。

- (void)copyMetalTexture:(id<MTLTexture>)sourceTexture toPixelBuffer:(CVPixelBufferRef)pixelBuffer {    size_t width = CVPixelBufferGetWidth(pixelBuffer);    size_t height = CVPixelBufferGetHeight(pixelBuffer);// 1. 将 CVPixelBuffer 映射为目标 Metal 纹理    CVMetalTextureRef outputTextureRef = NULL;    CVReturn status = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault,                                                               _textureCache,                                                               pixelBuffer,NULL,MTLPixelFormatBGRA8Unorm// 须与 Buffer 格式匹配                                                               width,                                                               height,0,                                                               &outputTextureRef);if (status != kCVReturnSuccess || outputTextureRef == NULL) {NSLog(@"Failed to map CVPixelBuffer to Metal texture");return;    }id<MTLTexture> destTexture = CVMetalTextureGetTexture(outputTextureRef);// 2. 使用 Blit Command Encoder 进行高速 GPU 拷贝id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];    [blitEncoder copyFromTexture:sourceTexture                     sourceSlice:0                     sourceLevel:0                    sourceOrigin:MTLOriginMake(000)                      sourceSize:MTLSizeMake(width, height, 1)                       toTexture:destTexture                destinationSlice:0                destinationLevel:0               destinationOrigin:MTLOriginMake(000)];    [blitEncoder endEncoding];// 3. 关键:确保 GPU 完成写入后再释放    [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {// 此时 pixelBuffer 中的数据已由 GPU 更新完毕// 可以在此处通知编码器(如 AVAssetWriter)开始读取    }];    [commandBuffer commit];// 清理CFRelease(outputTextureRef);    CVMetalTextureCacheFlush(_textureCache, 0);}

4、特殊情况:YUV (NV12) 回写

如果你需要将 Metal 渲染的 RGB 结果转回 YUV 格式的 CVPixelBuffer(通常为了更高效的 H.264 编码),建议在 Metal 中使用 Compute Shader 进行色空间转换。

  • 步骤
  1. 映射 CVPixelBuffer 的 Plane 0 (Y) 和 Plane 1 (UV) 为两个不同的纹理。
  2. 在 Compute Shader 中读取 RGB 纹理,计算 YUV 分量。
  3. 将结果分别通过 write(color, gid) 写入对应的 Y 和 UV 纹理。

5、开发建议与避坑 (Best Practices)

5.1、避免 getBytes 同步调用

不要使用 [MTLTexture getBytes:...] 将数据拉回到 CPU 内存再创建 Buffer。这会造成巨大的流水线阻塞(Pipeline Stall),导致帧率骤降。

5.2、缓冲区同步

当 commandBuffer 提交后,GPU 写入是异步的。如果你立即将 pixelBuffer 发送给 AVAssetWriter,可能会得到一帧不完整或全黑的画面。

  • 对策:使用 [commandBuffer waitUntilCompleted] 或在 addCompletedHandler 回调中执行后续逻辑。

5.3、纹理用法 (Usage)

用于映射 CVPixelBuffer 的纹理,其 MTLTextureDescriptor 必须包含以下标志(如果手动创建):

descriptor.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite | MTLTextureUsageRenderTarget;

音视频技术交流和讨论,欢迎加入我们的微信群
扫码加入
谢谢看完全文,也点一下『赞』和『在看』吧 ↓