CVPixelBuffer的创建数据填充以及数据读取

CVPixelBuffer 在音视频编解码以及图像处理过程中应用广泛,有时需要读取内部数据,很少的时候需要自行创建并填充数据,下面简单叙述。

创建

创建时调用的方法主要是这个:

CVReturn CVPixelBufferCreate(CFAllocatorRef allocator,
size_t width,
size_t height,
OSType pixelFormatType,
CFDictionaryRef pixelBufferAttributes,
CVPixelBufferRef _Nullable *pixelBufferOut);

提供必须的参数即可,

XX pixelFormatType 常用的这几个:


/* NV12 */ kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange = '420v',
/* Bi-Planar Component Y'CbCr 8-bit 4:2:0, video-range (luma=[16,235] chroma=[16,240]).
baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct */ kCVPixelFormatType_420YpCbCr8BiPlanarFullRange = '420f',
/* Bi-Planar Component Y'CbCr 8-bit 4:2:0, full-range (luma=[0,255] chroma=[1,255]).
baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct */ /* YUV420P */ kCVPixelFormatType_420YpCbCr8Planar = 'y420',
/* Planar Component Y'CbCr 8-bit 4:2:0.
baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrPlanar struct */

因为我想要创建NV12格式的buffer,所以没有使用那个直接提供数据的创建函数,后续提供。如果数据格式爲420P的话,直接指定数据地址也可以。

XX pixelBufferAttributes

这个参数是optinal的,提供所有额外的信息。Core Video根据提供的参数来创建合适的数据,我看到网上的代码往往是这样提供的:

NSDictionary *pixelAttributes = @{(id)kCVPixelBufferIOSurfacePropertiesKey : @{}};

说明如下:

Provide a value for this key if you want Core Video to use the
IOSurface framework to allocate the pixel buffer.
(See IOSurface.)
Provide an empty dictionary to use default IOSurface options.

数据填充

以 NV12 格式的数据填充举例说明。

在访问buffer内部裸数据的地址時(读或写都一样),需要先将其锁上,用完了再放开,如下:

CVPixelBufferLockBaseAddress(pixelBuffer, 0);

// To touch the address of pixel...

CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);

Y通道(Luminance)与 UV通道(Chrominance)分开填充数据,而且需要注意后者是UV交错排列的。在填充数据時还需要考虑到数据对齐的问题,当视频帧的宽高并不是某个对齐基数的倍数時(比如16),内部具体如何分配内存是不确定的,保险的做法就是逐行数据填充。这里我放上填充Chrominance通道数据的例子:

size_t bytesPerRowChrominance = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);

long chrominanceWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
long chrominanceHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1); // Chrominance
uint8_t *uvDestPlane = (uint8_t *) CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
memset(uvDestPlane, 0x80, chrominanceHeight * bytesPerRowChrominance); for (int row = 0; row < chrominanceHeight; ++row) {
memcpy(uvDestPlane + row * bytesPerRowChrominance,
uvDataPtr + row * _outVideoWidth,
_outVideoWidth);
} free(uvDataPtr);

在逐行copy数据的时候,pixel内部地址每个循环步进 current_row * bytesPerRowChrominance 的大小,这是pixelbuffer内部的内存排列。然后我的数据来源内存排列是紧密排列不考虑内存多少位对齐的问题的,所以每次的步进是 current_row * _outVideoWidth 也就是真正的视频帧的宽度。每次copy的大小也应该是真正的宽度。对于这个通道来说,宽度和高度都是亮度通道的一半,每个元素有UV两个信息,所以这个通道每一行占用空间和亮度通道应该是一样的。也就是每一行copy数据的大小是这样算出来的:_outVideoWidth / 2 * 2.

数据读取

数据读取和数据填充正好是相反的操作,操作流程相似,先获取pixelBuffer的一些具体信息,判断信息无误后继续读取数据。

unsigned long planes = CVPixelBufferGetPlaneCount(pixelRef); 若通道数目错误显然逻辑已经错误,无需继续。同样是先锁住BaseAddress,然后获取其bytesPerRowChrominance等信息,然后按行读取数据即可。切记,仍然需要按行读取数据。

补充:从 Image 创建 PixelBuffer

直接附上可运行的代码:


size_t height;
size_t width; NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
[NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,
nil];
CVPixelBufferRef pxbuffer = NULL; CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, width,
height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options,
&pxbuffer); NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL); CVPixelBufferLockBaseAddress(pxbuffer, 0);
void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);
NSParameterAssert(pxdata != NULL); CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(pxdata, width,
height, 8, 4 * width, rgbColorSpace,
kCGImageAlphaNoneSkipFirst);
NSParameterAssert(context);
CGContextConcatCTM(context, CGAffineTransformMakeRotation(0));
CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image),
CGImageGetHeight(image)), image);
CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context); CVPixelBufferUnlockBaseAddress(pxbuffer, 0);

创建格式爲 kCVPixelFormatType_32ARGB 的 pixelBuffer,创建一个CGContextRef 对象,并将其内部地址设置爲pixelBuffer的内部地址。使用 CGContextDrawImage() 函数将原始图片的数据绘制到我们创建的context上面,完成。


参考资料:

大神的文章,很详细:读写CVPixelBufferRef

Create CVPixelBuffer from YUV with IOSurface backed

How to convert from YUV to CIImage for iOS

How do I export UIImage array as a movie?

CVPixelBuffer的创建 数据填充 以及数据读取的更多相关文章

  1. Code First开发系列之管理数据库创建,填充种子数据以及LINQ操作详解

    返回<8天掌握EF的Code First开发>总目录 本篇目录 管理数据库创建 管理数据库连接 管理数据库初始化 填充种子数据 LINQ to Entities详解 什么是LINQ to ...

  2. 8天掌握EF的Code First开发系列之3 管理数据库创建,填充种子数据以及LINQ操作详解

    本文出自8天掌握EF的Code First开发系列,经过自己的实践整理出来. 本篇目录 管理数据库创建 管理数据库连接 管理数据库初始化 填充种子数据 LINQ to Entities详解 什么是LI ...

  3. Entity Framework应用:使用Code First模式管理数据库创建和填充种子数据

    一.管理数据库连接 1.使用配置文件管理连接之约定 在数据库上下文类中,如果我们只继承了无参数的DbContext,并且在配置文件中创建了和数据库上下文类同名的连接字符串,那么EF会使用该连接字符串自 ...

  4. laravel模型建立和数据迁移和数据填充(数据填充没有成功)未完

    开始创建我们的第一个 Article 模型及其对应迁移文件了,我们在项目根目录运行如下 Artisan 命令一步到位: php artisan make:model Article -m -m 是 - ...

  5. Jquery table元素操作-创建|数据填充|重置|隐藏行

    1.Jquery创建表格 /** * 创建表格 * @param label 标题 json格式,数据结构见附录1 * @param data 数据 json格式,数据结构见附录1 * @param ...

  6. Laravel --- artisan创建表以及填充表数据流程总结

    1.创建建表文件 php artisan make:migration create_comments_table 打开database/migrations/xxx_create_comments_ ...

  7. (转载)EF 使用code first模式创建数据库和 填充种子数据

    第一篇:来自 .net 开发菜鸟 博主的文章:https://www.cnblogs.com/dotnet261010/p/8035213.html 第二篇:来自 JustYong 博主的文章:htt ...

  8. 整理关于Java进行word文档的数据动态数据填充

    首先我们看下,别人整理的关于Java生成doc 的 资料. java生成word的几种方案 1. Jacob是Java-COM Bridge的缩写,它在Java与微软的COM组件之间构建一座桥梁.使用 ...

  9. 在VC下采用ADO实现BLOB(Binary)数据的存储,读取,修改,删除。

    在VC下采用ADO实现BLOB(Binary)数据的存储,读取,修改,删除. 作者:邵盛松 2009-09-05 前言 1关于的BLOB(Binary)数据的存储和读取功能主要参考了MSDN上的一篇& ...

随机推荐

  1. 201521123089 《Java程序设计》第3周学习总结

    1. 本周学习总结 初学面向对象,会学习到很多碎片化的概念与知识.尝试学会使用思维导图将这些碎片化的概念.知识组织起来.请使用纸笔或者下面的工具画出本周学习到的知识点.截图或者拍照上传. 2. 书面作 ...

  2. 201521123018 《Java程序设计》第2周学习总结

    1. 本章学习总结 * String类 * Java数组Array的使用 2. 书面作业 Q1.(1) 使用Eclipse关联jdk源代码,并查看String对象的源代码(截图)? (2)分析Stri ...

  3. 如何将ubuntu文件夹中文名改为英文

    其实我已经忍了很久. ubuntu在中文界面下面,自动创建了"桌面","文档",图片 .公共的 .下载. 音乐. 视频等中文目录. 在命令行下操作的时候,要么切 ...

  4. 201521123017 《Java程序设计》第14周学习总结

    1. 本周学习总结 2. 书面作业 Q1.MySQL数据库基本操作 建立数据库,将自己的姓名.学号作为一条记录插入.(截图,需出现自己的学号.姓名) 在自己建立的数据库上执行常见SQL语句(截图) - ...

  5. 201521123103 《Java学习笔记》 第九周学习总结

    一.本周学习总结 1.以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. 二.书面作业 本次PTA作业题集异常 1.常用异常 题目5-1 1.1 截图你的提交结果(出现学号) 1.2 自己以前编写 ...

  6. Linux系统下用户与组的管理

    Linux系统下用户与组的管理 一.用户及组基本概述 Linux 系统上,用户管理是基于用户名和密码的方式进行资源的分配. 1.uid(用户身份标识) (1)root用户 uid为0 (2)普通用户: ...

  7. Nim函数调用的几种形

    Nim函数调用的几种形式 Nim 转载条件:如果你需要转载本文,你需要做到完整转载本文所有的内容,不得删改文内的作者名字与链接.否则拒绝转载. 关于nim的例行介绍: Nim 是一门静态编译型的系统级 ...

  8. Java是如何解析xml文件的(DOM)

    Java解析xml文件 在Java程序中读取xml文件的过程也称为"解析xml文件": 解析的目的: 获取 节点名和节点值 获取 属性名.属性值. 四中解析方式: DOM SAX ...

  9. org.springframework.core.io包内的源码分析

    前些日子看<深入理解javaweb开发>时,看到第一章java的io流,发觉自己对io流真的不是很熟悉.然后看了下JDK1.7中io包的一点点代码,又看了org.springframewo ...

  10. Redisson分布式锁的简单使用

    一:前言 我在实际环境中遇到了这样一种问题,分布式生成id的问题!因为业务逻辑的问题,我有个生成id的方法,是根据业务标识+id当做唯一的值! 而uuid是递增生成的,从1开始一直递增,那么在同一台机 ...