如何在一个平面/ycbcr/420f/yuv/NV12而非rgb的CVPixelBufferRef上进行绘制?

4

我从系统API接收到了一个包含非线性像素的CMSampleBufferRef,其中包含平面像素(如420f,也称为kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,又称yCbCrYUV)。

在将视频数据发送到VideoToolkit进行h264编码之前,我想对此进行一些操作(例如绘制一些文本、添加水印、旋转图像等),但同时希望操作高效且实时。然而,平面图像数据看起来非常混乱 - 它们有色度平面和亮度平面,它们的大小不同... 在字节级别处理这个问题似乎需要花费很多力气。

我可能可以使用一个CGContextRef并直接在像素上绘制,但据我所知它只支持RGBA像素。您有什么建议可以尽可能少地复制数据,并且代码行数尽量少?

1个回答

5
CGBitmapContextRef只能绘制类似于32ARGB的图像,这意味着您需要创建ARGB(或RGBA)缓冲区,然后找到一种快速将YUV像素转移到此ARGB表面的方法。该过程包括使用CoreImage,通过池自制CVPixelBufferRef,引用您自制像素缓冲区的CGBitmapContextRef,然后重新创建一个类似于输入缓冲区但引用输出像素的CMSampleBufferRef。换句话说,
  1. 将传入的像素提取到CIImage中。
  2. 使用您正在创建的像素格式和输出尺寸创建CVPixelBufferPool。在实时情况下,不要创建没有池的CVPixelBuffer:如果生产者太快,则会耗尽内存;如果您不重复使用缓冲区,则会分段RAM;这是浪费周期。
  3. 使用默认构造函数创建CIContext,并在缓冲区之间共享。它不包含外部状态,但文档表示,在每个帧上重新创建它非常昂贵。
  4. 在传入的帧上,创建一个新的像素缓冲区。确保使用分配阈值,以便您不会出现失控的RAM使用情况。
  5. 锁定像素缓冲区
  6. 创建引用像素缓冲区中字节的位图上下文
  7. 使用CIContext将平面图像数据渲染到线性缓冲区中
  8. 在CGContext中执行应用程序特定的绘制!
  9. 解锁像素缓冲区
  10. 获取原始样本缓冲区的时间信息
  11. 通过询问像素缓冲区获取其精确格式来创建CMVideoFormatDescriptionRef
  12. 为像素缓冲区创建样本缓冲区。完成!
以下是一个示例实现,在其中我选择了32ARGB作为要处理的图像格式,因为这是iOS上CGBitmapContextCoreVideo都能很好地处理的格式:
{
    CGPixelBufferPoolRef *_pool;
    CGSize _poolBufferDimensions;
}
- (void)_processSampleBuffer:(CMSampleBufferRef)inputBuffer
{
    // 1. Input data
    CVPixelBufferRef inputPixels = CMSampleBufferGetImageBuffer(inputBuffer);
    CIImage *inputImage = [CIImage imageWithCVPixelBuffer:inputPixels];

    // 2. Create a new pool if the old pool doesn't have the right format.
    CGSize bufferDimensions = {CVPixelBufferGetWidth(inputPixels), CVPixelBufferGetHeight(inputPixels)};
    if(!_pool || !CGSizeEqualToSize(bufferDimensions, _poolBufferDimensions)) {
        if(_pool) {
            CFRelease(_pool);
        }
        OSStatus ok0 = CVPixelBufferPoolCreate(NULL,
            NULL, // pool attrs
            (__bridge CFDictionaryRef)(@{
                (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32ARGB),
                (id)kCVPixelBufferWidthKey: @(bufferDimensions.width),
                (id)kCVPixelBufferHeightKey: @(bufferDimensions.height),
            }), // buffer attrs
            &_pool
        );
        _poolBufferDimensions = bufferDimensions;
        assert(ok0 == noErr);
    }

    // 4. Create pixel buffer
    CVPixelBufferRef outputPixels;
    OSStatus ok1 = CVPixelBufferPoolCreatePixelBufferWithAuxAttributes(NULL,
        _pool,
        (__bridge CFDictionaryRef)@{
            // Opt to fail buffer creation in case of slow buffer consumption
            // rather than to exhaust all memory.
            (__bridge id)kCVPixelBufferPoolAllocationThresholdKey: @20
        }, // aux attributes
        &outputPixels
    );
    if(ok1 == kCVReturnWouldExceedAllocationThreshold) {
        // Dropping frame because consumer is too slow
        return;
    }
    assert(ok1 == noErr);

    // 5, 6. Graphics context to draw in
    CGColorSpaceRef deviceColors = CGColorSpaceCreateDeviceRGB();
    OSStatus ok2 = CVPixelBufferLockBaseAddress(outputPixels, 0);
    assert(ok2 == noErr);
    CGContextRef cg = CGBitmapContextCreate(
        CVPixelBufferGetBaseAddress(outputPixels), // bytes
        CVPixelBufferGetWidth(inputPixels), CVPixelBufferGetHeight(inputPixels), // dimensions
        8, // bits per component
        CVPixelBufferGetBytesPerRow(outputPixels), // bytes per row
        deviceColors, // color space
        kCGImageAlphaPremultipliedFirst // bitmap info
    );
    CFRelease(deviceColors);
    assert(cg != NULL);

    // 7
    [_imageContext render:inputImage toCVPixelBuffer:outputPixels];

    // 8. DRAW
    CGContextSetRGBFillColor(cg, 0.5, 0, 0, 1);
    CGContextSetTextDrawingMode(cg, kCGTextFill);
    NSAttributedString *text = [[NSAttributedString alloc] initWithString:@"Hello world" attributes:NULL];
    CTLineRef line = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)text);
    CTLineDraw(line, cg);
    CFRelease(line);

    // 9. Unlock and stop drawing
    CFRelease(cg);
    CVPixelBufferUnlockBaseAddress(outputPixels, 0);

    // 10. Timings
    CMSampleTimingInfo timingInfo;
    OSStatus ok4 = CMSampleBufferGetSampleTimingInfo(inputBuffer, 0, &timingInfo);
    assert(ok4 == noErr);

    // 11. VIdeo format
    CMVideoFormatDescriptionRef videoFormat;
    OSStatus ok5 = CMVideoFormatDescriptionCreateForImageBuffer(NULL, outputPixels, &videoFormat);
    assert(ok5 == noErr);

    // 12. Output sample buffer
    CMSampleBufferRef outputBuffer;
    OSStatus ok3 = CMSampleBufferCreateForImageBuffer(NULL, // allocator
        outputPixels, // image buffer 
        YES, // data ready
        NULL, // make ready callback
        NULL, // make ready refcon
        videoFormat,
        &timingInfo, // timing info
        &outputBuffer // out
    );
    assert(ok3 == noErr);

    [_consumer consumeSampleBuffer:outputBuffer];
    CFRelease(outputPixels);
    CFRelease(videoFormat);
    CFRelease(outputBuffer);
}

请提供一个Swift 4的例子。 - user924
它真的很快吗?因为我尝试了下一步,它会占用大量CPU。https://dev59.com/_Knka4cB1Zd3GeqPOnmk - user924
_pool,_poolBufferDimensions它们是哪种类型的? - user924
@user924 不好意思,这对我来说太多工作了 :( 它应该基本上是逐行翻译;如果您这样做,请发布!此外,我在代码块顶部添加了两个实例变量的类型。 - nevyn
我不再需要快速解决方案了,我决定在objective-c++类(.mm)中使用opencv(c++),并直接使用指针处理pixelbuffer(无需复制、转换任何内容)。 - user924
你们有没有广播扩展的解决方案?我在示例处理程序上使用了这段代码来压缩CMSampleBuffer,但它在几秒钟内就崩溃了。 - Mehmet Baykar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接