如何绘制到平面/ ycbcr / 420f / yuv / NV12 /不是rgb的CVPixelBufferRef?
我从包含不是RGBA
(线性像素)的CVPixelBufferRef
的系统API收到CMSampleBufferRef
。 缓冲区包含平面像素(如420f
又名kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
又名yCbCr
又称YUV
)。
我想修改一些操作这个video数据,然后把它发送到VideoToolkit
编码为h264
(绘制一些文本,覆盖一个标志,旋转图像等),但我希望它是有效的即时的。 Buuuut的平面图像数据看起来很肮脏的工作 – 有色度平面和亮度平面,他们是不同的大小和…在这个字节级工作似乎很多工作。
我可能可以使用CGContextRef
,只是在像素上绘制,但从我可以收集它只支持RGBA像素。 任何关于如何尽可能less地复制数据的build议,尽可能less的代码行?
CGBitmapContextRef
只能绘制成类似32ARGB
东西,是正确的。 这意味着您将需要创buildARGB
(或RGBA
)缓冲区,然后find一种方法将YUV
像素快速传输到ARGB
表面。 这个配方包括使用CoreImage
,一个通过池自制的CVPixelBufferRef
,一个CGBitmapContextRef
引用你自制的像素缓冲区,然后重新创build一个类似于你的input缓冲区的CMSampleBufferRef
,但是引用你的输出像素。 换一种说法,
- 将传入像素获取到
CIImage
。 - 使用您正在创build的像素格式和输出尺寸创build一个
CVPixelBufferPool
。 您不想实时创build没有池的CVPixelBuffer
:如果您的制作者速度过快,则会耗尽内存; 你会碎片你的RAM,因为你不会重复使用缓冲区; 这是浪费周期。 - 用你将在缓冲区之间共享的默认构造函数创build一个
CIContext
。 它不包含外部状态,但文档说,在每一帧重新创build它是非常昂贵的。 - 在传入的帧上,创build一个新的像素缓冲区。 确保使用分配阈值,这样就不会出现失控的内存使用情况。
- locking像素缓冲区
- 创build一个引用像素缓冲区中的字节的位图上下文
- 使用CIContext将平面图像数据渲染到线性缓冲区中
- 在CGContext中执行您的特定于应用程序的绘图!
- 解锁像素缓冲区
- 获取原始采样缓冲区的时间信息
- 通过询问像素缓冲区的确切格式来创build
CMVideoFormatDescriptionRef
- 为像素缓冲区创build一个采样缓冲区。 完成!
下面是一个示例实现,我select了32ARGB作为图像格式来处理,因为这是CGBitmapContext
和CoreVideo
喜欢在iOS上使用的东西:
- (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); }