4

我收到了CMSampleBufferRef来自系统 API 的 a,其中包含CVPixelBufferRef不是RGBA(线性像素)的 s。缓冲区包含平面像素(例如420faka kCVPixelFormatType_420YpCbCr8BiPlanarVideoRangeaka yCbCraka YUV)。

我想修改此视频数据,然后再将其发送VideoToolkit以进行编码h264(绘制一些文本、覆盖徽标、旋转图像等),但我希望它高效且真实-时间。Buuuut 平面图像数据看起来非常混乱——有色度平面和亮度平面,它们的大小不同,而且......在字节级别上处理这个似乎需要做很多工作。

我可能可以使用 aCGContextRef并直接在像素上绘制,但据我所知,它只支持 RGBA 像素。关于如何通过尽可能少的数据复制和尽可能少的代码行来做到这一点的任何建议?

4

1 回答 1

5

CGBitmapContextRef只能画成类似的东西32ARGB,正确的。这意味着您将需要创建ARGB(或RGBA)缓冲区,然后找到一种方法将YUV像素快速传输到该ARGB表面上。这个秘籍包括使用CoreImage一个自制的CVPixelBufferRef通过池,一个CGBitmapContextRef引用你自制的像素缓冲区,然后重新创建一个CMSampleBufferRef类似于你的输入缓冲区,但引用你的输出像素。换句话说,

  1. 将输入像素提取到CIImage.
  2. 使用您正在创建CVPixelBufferPool的像素格式和输出尺寸创建一个。你不希望CVPixelBuffer实时创建没有池的 s:如果你的生产者太快,你会耗尽内存;由于不会重用缓冲区,因此您将分散 RAM;这是对周期的浪费。
  3. CIContext使用您将在缓冲区之间共享的默认构造函数创建一个。它不包含外部状态,但文档说在每一帧上重新创建它是非常昂贵的。
  4. 在传入帧上,创建一个新的像素缓冲区。确保使用分配阈值,以免 RAM 使用失控。
  5. 锁定像素缓冲区
  6. 创建一个引用像素缓冲区中字节的位图上下文
  7. 使用 CIContext 将平面图像数据渲染到线性缓冲区中
  8. 在 CGContext 中执行您的应用程序特定的绘图!
  9. 解锁像素缓冲区
  10. 获取原始样本缓冲区的时序信息
  11. CMVideoFormatDescriptionRef通过询问像素缓冲区的确切格式来创建
  12. 为像素缓冲区创建一个样本缓冲区。完毕!

这是一个示例实现,我选择了 32ARGB 作为要使用的图像格式,因为这是在 iOS 上都喜欢使用的CGBitmapContext东西CoreVideo

{
    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);
}
于 2017-10-02T11:44:20.987 回答