【问题标题】:Clear a CVPixelBufferRef using the GPU使用 GPU 清除 CVPixelBufferRef
【发布时间】:2015-01-13 23:08:43
【问题描述】:

我正在使用AVComposition 编写一些视频处理代码。仅提供必要的背景细节,我从我无法控制的苹果 API 收到CVPixelBuffer。此 CVPixel 缓冲区包含先前渲染的视频帧,因为它们显然是由我无法控制的 Apple API 回收的。所以我的目标是将 CVPixelBufferRef 中的所有像素设置为 [0, 0, 0, 0] (在 RGBA 颜色空间中)。我可以通过这个函数在 CPU 上做到这一点:

- (void)processPixelBuffer: (CVImageBufferRef)pixelBuffer
{
    CVPixelBufferLockBaseAddress( pixelBuffer, 0 );

    int bufferWidth = CVPixelBufferGetWidth(pixelBuffer);
    int bufferHeight = CVPixelBufferGetHeight(pixelBuffer);
    unsigned char *pixel = (unsigned char *)CVPixelBufferGetBaseAddress(pixelBuffer);

    for( int row = 0; row < bufferHeight; row++ ) {
        for( int column = 0; column < bufferWidth; column++ ) {
            pixel[0] = 0;
            pixel[1] = 0;
            pixel[2] = 0;
            pixel[3] = 0;
            pixel += 4;
        }
    }
    CVPixelBufferUnlockBaseAddress( pixelBuffer, 0 );
}

有什么方法可以让我使用 GPU 完成同样的事情吗?此外,是否可以通过 CoreImage 做到这一点?因为我不知道 openGL,而且设置起来似乎很复杂。

【问题讨论】:

    标签: ios avfoundation core-video core-media


    【解决方案1】:

    假设您的像素缓冲区附加到an image buffer backed by an IO surface,您可以使用CVOpenGLESTextureCacheCreateTextureFromImage 获取CVOpenGLESTextureCache,从而为您提供CVOpenGLESTextureRef。这将能够提供纹理目标和名称,以便您可以在 OpenGL 中绑定事物。

    在 OpenGL 中,您可以use a framebuffer object to render to a texture。完成后,您可以使用glClear 清除此内容。调用glFinish创建一个与GL管道的同步点,当你下次从CPU检查它时,你的内存应该被清除。

    【讨论】:

    • 能够通过苹果的一些示例代码实现这一点。但它涉及的更多,因为我实际上必须为 openGL 做一堆设置,坦率地说我不明白。
    • 这可能是一个更好的问题,与使用 memset 将像素缓冲区归零相比,这实际上会提供任何性能改进吗?
    • 我的直觉是,你最好的希望是并行化它——为缓冲区 B 发出 glClear,然后在缓冲区 A 上执行 CPU 工作,然后仅在 CPU 想要滚动到时调用 glFinish在缓冲区 B 上工作。但如果你还不是多线程的,那么通过 GCD 做同样的事情可能同样有效。 GPU 不仅天生就能够执行更高效的memset — 你还会受到内存带宽的限制。
    【解决方案2】:

    您可以使用Accelerate vImageBufferFill 来做到这一点(虽然不是 GPU)。这在 Swift 4 中用黑色填充 BGRA:

    let width: Int = 3
    let height: Int = 3
    
    var pixelBuffer: CVPixelBuffer?
    var imageBuffer: vImage_Buffer
    var date: Date
    let iterations: Int = 10000
    
    let pixelBufferAttributes: [CFString: Any] = [kCVPixelBufferCGImageCompatibilityKey: true, kCVPixelBufferCGBitmapContextCompatibilityKey: true]
    if CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA, pixelBufferAttributes as CFDictionary, &pixelBuffer) != kCVReturnSuccess || pixelBuffer == nil { fatalError("Cannot create pixel buffer.") }
    if CVPixelBufferLockBaseAddress(pixelBuffer!, []) != kCVReturnSuccess { fatalError("Cannot lock pixel buffer base address.") }
    imageBuffer = vImage_Buffer(data: CVPixelBufferGetBaseAddress(pixelBuffer!), height: UInt(height), width: UInt(width), rowBytes: CVPixelBufferGetBytesPerRow(pixelBuffer!))
    vImageBufferFill_ARGB8888(&imageBuffer, [0, 0, 0, 0xFF], UInt32(kvImageNoFlags))
    if CVPixelBufferUnlockBaseAddress(pixelBuffer!, []) != kCVReturnSuccess { fatalError("Cannot unlock pixel buffer base address.") }
    

    在 Playground 中测试普通 memset 以在 10000 次迭代中用零填充小缓冲区要快得多。在具有真实数据的发布版本中,结果可能不会有太大差异。

    • 加速: 1629.0
    • memset: 314.0

    【讨论】:

      【解决方案3】:

      我有同样的问题(强制-[AVVideoCompositionRenderContext newPixelBuffer] 提供的回收缓冲区为黑色)。

      如果您使用 Core Image 来完成繁重的工作会怎样?像这样的:

      EAGLContext *eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
      CIContext *ciContext = [CIContext contextWithEAGLContext:eaglContext];
      
      CIImage *image = [CIImage imageWithColor:[CIColor colorWithRed:0 green:0 blue:0]];
      CVPixelBufferRef destination = [request.renderContext newPixelBuffer];
      [ciContext render:image toCVPixelBuffer:destination];
      [request finishWithComposedVideoFrame:destination];
      CVBufferRelease(destination);
      

      您必须测试性能才能知道这是否比memset 更好,但对于我来说,这比尝试查看 OpenGL API 来设置一些位更好。

      【讨论】:

        【解决方案4】:

        this page 看来,您可以通过以下方式直接访问CVImageBufferRef 作为 OpenGL 纹理:

        glBindTexture( CVOpenGLTextureGetTarget( image ), CVOpenGLTextureGetName( image ) );
        

        一旦将其作为纹理,您就可以将其用作 FBO 的绘制缓冲区,并在其上调用 glClear(GL_COLOR_BUFFER_BIT);

        【讨论】:

        • 似乎没有运气:glBindTexture(CVOpenGLESTextureGetTarget(dstPixels),CVOpenGLESTextureGetName(dstPixels));glClear(GL_COLOR_BUFFER_BIT);glFinish();
        • 您仍然需要将其附加到帧缓冲区,然后才能清除它。但是,如果您使用的是 iOS 而不是 MacOS,那么您可能需要以下 Tommy 的解决方案。
        • 我想得越多,我就越认为简单地使用 memset 将像素缓冲区归零可能与使用 glClear 一样有效。对此有什么想法吗?
        猜你喜欢
        • 2014-06-13
        • 1970-01-01
        • 2019-08-24
        • 2022-10-12
        • 2016-09-21
        • 2017-02-07
        • 1970-01-01
        • 2020-08-31
        • 2019-12-21
        相关资源
        最近更新 更多