Как мне нарисовать на CVPixelBufferRef, который является плоским /ycbcr/420f/yuv/NV12/ не rgb?

Я получил CMSampleBufferRef из системного API, который содержит CVPixelBufferRefс которые не RGBA (линейные пиксели). Буфер содержит плоские пиксели (такие как 420f ака kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange ака yCbCr ака YUV).

Я хотел бы изменить некоторые видео манипуляции перед отправкой VideoToolkit быть закодированным в h264 (рисование текста, наложение логотипа, поворот изображения и т. д.), но я бы хотел, чтобы это было эффективно и в режиме реального времени. Buuuut плоские данные изображения выглядят очень грязно - работать с плоскостью цветности и плоскостью яркости, они имеют разные размеры и... Работа с этим на уровне байтов кажется большой работой.

Я мог бы использовать CGContextRef и просто рисовать прямо поверх пикселей, но из того, что я могу собрать, он поддерживает только пиксели RGBA. Любой совет о том, как я могу сделать это с минимальным копированием данных, при этом как можно меньше строк кода?

1 ответ

CGBitmapContextRef можно рисовать только во что-то вроде 32ARGB, правильный. Это означает, что вы захотите создать ARGB (или же RGBA), а затем найти способ очень быстро перенести YUV пикселей на это ARGB поверхность. Этот рецепт включает в себя использование CoreImageсамодельный CVPixelBufferRef через бассейн, CGBitmapContextRef ссылка на ваш домашний пиксельный буфер, а затем воссоздание CMSampleBufferRef напоминает ваш входной буфер, но ссылается на ваши выходные пиксели. Другими словами,

  1. Получить входящие пиксели в CIImage,
  2. Создать CVPixelBufferPool с форматом пикселя и размерами вывода, которые вы создаете. Вы не хотите создавать CVPixelBufferбез пула в режиме реального времени: вам не хватит памяти, если ваш продюсер слишком быстр; вы будете фрагментировать свою оперативную память, поскольку не будете повторно использовать буферы; и это пустая трата циклов.
  3. Создать CIContext с конструктором по умолчанию, которым вы будете делиться между буферами. Он не содержит внешнего состояния, но в документации сказано, что воссоздание его на каждом кадре очень дорого.
  4. На входящем кадре создайте новый буфер пикселей. Убедитесь, что вы используете пороговое значение, чтобы избежать чрезмерного использования ОЗУ.
  5. Блокировка пиксельного буфера
  6. Создайте растровый контекст, ссылающийся на байты в пиксельном буфере
  7. Используйте CIContext для рендеринга данных плоского изображения в линейный буфер
  8. Выполните свой специфичный для приложения рисунок в CGContext!
  9. Разблокировать пиксельный буфер
  10. Получить информацию о синхронизации исходного буфера семплов
  11. Создать CMVideoFormatDescriptionRef спрашивая буфер пикселей для его точного формата
  12. Создайте пример буфера для пиксельного буфера. Готово!

Вот пример реализации, где я выбрал 32ARGB в качестве формата изображения для работы, так как это то, что оба CGBitmapContext а также CoreVideo любит работать на 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);
}
Другие вопросы по тегам