Как мне нарисовать на 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
напоминает ваш входной буфер, но ссылается на ваши выходные пиксели. Другими словами,
- Получить входящие пиксели в
CIImage
, - Создать
CVPixelBufferPool
с форматом пикселя и размерами вывода, которые вы создаете. Вы не хотите создаватьCVPixelBuffer
без пула в режиме реального времени: вам не хватит памяти, если ваш продюсер слишком быстр; вы будете фрагментировать свою оперативную память, поскольку не будете повторно использовать буферы; и это пустая трата циклов. - Создать
CIContext
с конструктором по умолчанию, которым вы будете делиться между буферами. Он не содержит внешнего состояния, но в документации сказано, что воссоздание его на каждом кадре очень дорого. - На входящем кадре создайте новый буфер пикселей. Убедитесь, что вы используете пороговое значение, чтобы избежать чрезмерного использования ОЗУ.
- Блокировка пиксельного буфера
- Создайте растровый контекст, ссылающийся на байты в пиксельном буфере
- Используйте CIContext для рендеринга данных плоского изображения в линейный буфер
- Выполните свой специфичный для приложения рисунок в CGContext!
- Разблокировать пиксельный буфер
- Получить информацию о синхронизации исходного буфера семплов
- Создать
CMVideoFormatDescriptionRef
спрашивая буфер пикселей для его точного формата - Создайте пример буфера для пиксельного буфера. Готово!
Вот пример реализации, где я выбрал 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);
}