如何绘制到平面/ ycbcr / 420f / yuv / NV12 /不是rgb的CVPixelBufferRef?

时间:2017-10-02 11:44:21

标签: ios core-image core-video replaykit core-media

我收到了来自系统API的autofill,其中包含CMSampleBufferRef不是CVPixelBufferRef(线性像素)的RGBA。缓冲区包含平面像素(例如420f又名kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange又名yCbCr又名YUV)。

我想修改对此视频数据进行一些操作,然后再将其发送到VideoToolkit以编码为h264(绘制一些文字,覆盖徽标,旋转图像等),但我希望它能够高效,实时。 Buuuut平面图像数据看起来很混乱 - 有色度平面和亮度平面,它们的大小不同......在字节级别上使用它似乎很多工作。

我可以使用CGContextRef并且只是在像素顶部绘制,但是从我可以收集它只支持RGBA像素。有关如何使用尽可能少的数据复制执行此操作的任何建议,但尽可能少的代码行?

1 个答案:

答案 0 :(得分:3)

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. 通过询问像素缓冲区的确切格式
  12. 来创建CMVideoFormatDescriptionRef
  13. 为像素缓冲区创建一个示例缓冲区。完成!
  14. 这是一个示例实现,我选择32ARGB作为图像格式,因为CGBitmapContextCoreVideo喜欢在iOS上使用的东西:

    {
        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);
    }