简单了解iOSCVPixelBuffer(下)
1、前言
在「简单了解 iOS CVPixelBuffer (中)」中,我们了解了颜色空间 RGB 和 YUV 的区别以及相关的背景知识,最后对 CVPixelBuffer 中的 kCVPixelFormatType 相关类型进行了解读。我们已经对 CVPixelBuffer 有了初步的了解,在这篇文章中,我们将继续聊聊 CVPixelBuffer 在使用过程中的一些格式转换;
RGB和YUV格式转换
在很多场景下,我们需要将不同的颜色空间进行转换,以此来解决对应的工程性问题。 以下是转换公式: 1.1 YUV -> RGBR = Y + 1.13983 * V G = Y - 0.39465 * U - 0.58060 * V B = Y + 2.03211 * U1.2 RGB -> YUVY = 0.299 * R + 0.587 * G + 0.114 * B U = -0.14713 * R - 0.28886 * G + 0.436 * B V = 0.615 * R - 0.51499 * G - 0.10001 * B2、iOS中常见格式转换
在iOS中 RGB 和 YUV 互相转换的方法会使用到 libyuv开源库 ,打开此链接需要梯子,目前国内也有,需要的自取 libyuv开源库·国内仓库
iOS在 CVPixelBuffer 转换的上会很复杂,对buffer操作之前需要执行加锁方法 CVPixelBufferLockBaseAddress 进行保护,在处理完后,执行解锁buffer方法 CVPixelBufferUnlockBaseAddress 。
以下的方法是我在项目中以及平常的开发中所整理,仅供参考; 2.1 NV12 to I420
核心 NV12ToI420 方法是使用了 libyuv开源库 ; /// NV12 to I420 + (CVPixelBufferRef)I420PixelBufferWithNV12:(CVImageBufferRef)cvpixelBufferRef { CVPixelBufferLockBaseAddress(cvpixelBufferRef, 0); //图像宽度(像素) size_t pixelWidth = CVPixelBufferGetWidth(cvpixelBufferRef); //图像高度(像素) size_t pixelHeight = CVPixelBufferGetHeight(cvpixelBufferRef); //获取CVPixelBufferRef中的y数据 const uint8* y_frame = (uint8*)CVPixelBufferGetBaseAddressOfPlane(cvpixelBufferRef,0); //获取CMVImageBufferRef中的uv数据 const uint8* uv_frame = (uint8*)CVPixelBufferGetBaseAddressOfPlane(cvpixelBufferRef,1); //y stride size_t plane1_stride = CVPixelBufferGetBytesPerRowOfPlane (cvpixelBufferRef, 0); //uv stride size_t plane2_stride = CVPixelBufferGetBytesPerRowOfPlane (cvpixelBufferRef, 1); //yuv_size(内存空间) size_t frame_size = pixelWidth*pixelHeight*3/2; //开辟frame_size大小的内存空间用于存放转换好的i420数据 uint8* buffer = (unsigned char *)malloc(frame_size); //buffer为这段内存的首地址,plane1_size代表这一帧中y数据的长度 uint8* dst_u = buffer + pixelWidth*pixelHeight; //dst_u为u数据的首地,plane1_size/4为u数据的长度 uint8* dst_v = dst_u + pixelWidth*pixelHeight/4; //libyuv转换 int ret = NV12ToI420(y_frame, (int)plane1_stride, uv_frame, (int)plane2_stride, buffer, (int)pixelWidth, dst_u, (int)pixelWidth/2, dst_v, (int)pixelWidth/2, (int)pixelWidth, (int)pixelHeight ); if (ret) { return NULL; } NSDictionary *pixelAttributes = @{(id)kCVPixelBufferIOSurfacePropertiesKey : @{}}; CVPixelBufferRef pixelBuffer = NULL; CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault, pixelWidth, pixelHeight, kCVPixelFormatType_420YpCbCr8Planar, (__bridge CFDictionaryRef)(pixelAttributes), &pixelBuffer); CVPixelBufferLockBaseAddress(pixelBuffer, 0); size_t d = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); size_t ud = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1); size_t vd = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 2); unsigned char* dsty = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); unsigned char* dstu = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); unsigned char* dstv = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 2); unsigned char* srcy = buffer; for (unsigned int rIdx = 0; rIdx < pixelHeight; ++rIdx, srcy += pixelWidth, dsty += d) { memcpy(dsty, srcy, pixelWidth); } unsigned char* srcu = buffer + pixelHeight*pixelWidth; for (unsigned int rIdx = 0; rIdx < pixelHeight/2; ++rIdx, srcu += pixelWidth/2, dstu += ud) { memcpy(dstu, srcu, pixelWidth/2); } unsigned char* srcv = buffer + pixelHeight*pixelWidth*5/4; for (unsigned int rIdx = 0; rIdx < pixelHeight/2; ++rIdx, srcv += pixelWidth/2, dstv += vd) { memcpy(dstv, srcv, pixelWidth/2); } CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); if (result != kCVReturnSuccess) { NSLog(@"Unable to create cvpixelbuffer %d", result); } free(buffer); // CVPixelBufferRelease(cvpixelBufferRef); return pixelBuffer; }
C++音视频学习资料免费获取方法:关注音视频开发T哥 ,点击「链接」即可免费获取2023年最新 C++音视频开发进阶独家免费学习大礼包! 2.2 NV12 to BGRA
核心 NV12ToARGB 方法同样使用了 libyuv开源库 /// NV12 to BGRA + (CVPixelBufferRef)RGBAPixelBufferWithNV12:(CVImageBufferRef)pixelBufferNV12{ CVPixelBufferLockBaseAddress(pixelBufferNV12, 0); //图像宽度(像素) size_t pixelWidth = CVPixelBufferGetWidth(pixelBufferNV12); //图像高度(像素) size_t pixelHeight = CVPixelBufferGetHeight(pixelBufferNV12); //y_stride size_t src_stride_y = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferNV12, 0); //uv_stride size_t src_stride_uv = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferNV12,1); //获取CVImageBufferRef中的y数据 uint8_t *src_y = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBufferNV12, 0); //获取CMVImageBufferRef中的uv数据 uint8_t *src_uv =(unsigned char *) CVPixelBufferGetBaseAddressOfPlane(pixelBufferNV12, 1); // 创建一个空的32BGRA格式的CVPixelBufferRef NSDictionary *pixelAttributes = @{(id)kCVPixelBufferIOSurfacePropertiesKey : @{}}; CVPixelBufferRef pixelBufferRGBA = NULL; CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault, pixelWidth,pixelHeight,kCVPixelFormatType_32BGRA, (__bridge CFDictionaryRef)pixelAttributes,&pixelBufferRGBA);//kCVPixelFormatType_32BGRA if (result != kCVReturnSuccess) { NSLog(@"Unable to create cvpixelbuffer %d", result); return NULL; } result = CVPixelBufferLockBaseAddress(pixelBufferRGBA, 0); if (result != kCVReturnSuccess) { CFRelease(pixelBufferRGBA); NSLog(@"Failed to lock base address: %d", result); return NULL; } // 得到新创建的CVPixelBufferRef中 rgb数据的首地址 uint8_t *rgb_data = (uint8*)CVPixelBufferGetBaseAddress(pixelBufferRGBA); // 使用libyuv为rgb_data写入数据,将NV12转换为BGRA size_t bgraStride = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferRGBA,0); int ret = NV12ToARGB(src_y, (int)src_stride_y, src_uv, (int)src_stride_uv, rgb_data,(int)bgraStride, (int)pixelWidth, (int)pixelHeight); if (ret) { NSLog(@"Error converting NV12 VideoFrame to BGRA: %d", result); CFRelease(pixelBufferRGBA); return NULL; } CVPixelBufferUnlockBaseAddress(pixelBufferRGBA, 0); CVPixelBufferUnlockBaseAddress(pixelBufferNV12, 0); return pixelBufferRGBA; }2.3 CVPixelBufferRef to UIImage
以下方法可以将视频帧转成单张图片(比较适用于间隔时间长的截图,高频的使用这个方法很可能会引起内存的问题) /// buffer to image + (UIImage *)convert:(CVPixelBufferRef)pixelBuffer { CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer]; CIContext *temporaryContext = [CIContext contextWithOptions:nil]; CGImageRef videoImage = [temporaryContext createCGImage:ciImage fromRect:CGRectMake(0, 0, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer))]; UIImage *uiImage = [UIImage imageWithCGImage:videoImage]; CGImageRelease(videoImage); return uiImage; }2.4 CGImageRef to CVPixelBufferRef
以下方法会通过单张图片转成一个PixelBuffer (适用于将某一帧图片转成Buffer添加字幕或者美颜贴纸等等) /// image to buffer + (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image { NSDictionary *options = @{ (NSString*)kCVPixelBufferCGImageCompatibilityKey : @YES, (NSString*)kCVPixelBufferCGBitmapContextCompatibilityKey : @YES, (NSString*)kCVPixelBufferIOSurfacePropertiesKey: [NSDictionary dictionary] }; CVPixelBufferRef pxbuffer = NULL; CGFloat frameWidth = CGImageGetWidth(image); CGFloat frameHeight = CGImageGetHeight(image); CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, frameWidth, frameHeight, kCVPixelFormatType_32BGRA, (__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, frameWidth, frameHeight, 8, CVPixelBufferGetBytesPerRow(pxbuffer), rgbColorSpace, (CGBitmapInfo)kCGImageAlphaNoneSkipFirst); NSParameterAssert(context); CGContextConcatCTM(context, CGAffineTransformIdentity); CGContextDrawImage(context, CGRectMake(0, 0, frameWidth, frameHeight), image); CGColorSpaceRelease(rgbColorSpace); CGContextRelease(context); CVPixelBufferUnlockBaseAddress(pxbuffer, 0); return pxbuffer; }2.5 Buffer Data to UIImage
以下方法会通过内存数据转成图片 (根据内存的地址去取出存储的buffer并生成图片,其实这里的内存的地址指向的就是Buffer) // NV12 to image + (UIImage *)YUVtoUIImage:(int)w h:(int)h buffer:(unsigned char *)buffer { //YUV(NV12)-->CIImage--->UIImage Conversion NSDictionary *pixelAttributes = @{(NSString*)kCVPixelBufferIOSurfacePropertiesKey:@{}}; CVPixelBufferRef pixelBuffer = NULL; CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault, w, h, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange, (__bridge CFDictionaryRef)(pixelAttributes), &pixelBuffer); CVPixelBufferLockBaseAddress(pixelBuffer,0); void *yDestPlane = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); // Here y_ch0 is Y-Plane of YUV(NV12) data. unsigned char *y_ch0 = buffer; unsigned char *y_ch1 = buffer + w * h; memcpy(yDestPlane, y_ch0, w * h); void *uvDestPlane = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); // Here y_ch1 is UV-Plane of YUV(NV12) data. memcpy(uvDestPlane, y_ch1, w * h * 0.5); CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); if (result != kCVReturnSuccess) { NSLog(@"Unable to create cvpixelbuffer %d", result); } // CIImage Conversion if (@available(iOS 13.0, *)) { CIImage *coreImage = [CIImage imageWithCVPixelBuffer:pixelBuffer]; CIContext *temporaryContext = [CIContext contextWithOptions:nil]; CGImageRef videoImage = [temporaryContext createCGImage:coreImage fromRect:CGRectMake(0, 0, w, h)]; UIImage *finalImage = [[UIImage alloc] initWithCGImage:videoImage]; CVPixelBufferRelease(pixelBuffer); CGImageRelease(videoImage); return finalImage; } return nil; };2.6 Buffer To NSData+ (NSData *)dataFrompixelBuffer:(CVPixelBufferRef)pixelBuffer { CVPixelBufferLockBaseAddress(pixelBuffer, 0); size_t pixelWidth = CVPixelBufferGetWidth(pixelBuffer); size_t pixelHeight = CVPixelBufferGetHeight(pixelBuffer); size_t y_size = pixelWidth * pixelHeight; size_t uv_size = y_size / 2; uint8_t *yuv_frame = (uint8_t *)malloc(uv_size + y_size); uint8_t *y_frame = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0); memcpy(yuv_frame, y_frame, y_size); uint8_t *uv_frame = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1); memcpy(yuv_frame + y_size, uv_frame, uv_size); CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); NSData *data = [NSData dataWithBytesNoCopy:yuv_frame length:y_size + uv_size]; return data; }
iOS中格式转换涉及到 C、OC、C++ 的一些方法,所以很多方法看起来会非常的冗余,需要一定的基础和持续的学习。还有很多是通过OpenGL来绘制图像的方法,更是难得看懂,所以初学者做好笔记,保持耐心,把常用的方法梳理起来,最后封装到工具类中。等到机缘巧合的时候再来深入了解。 3、参考文献
一文读懂 YUV 的采样与格式
原文链接:简单了解 iOS CVPixelBuffer (下) - 掘金
我们只是一个平凡的人,我们只属于自己的那片天鲁迅先生说过父母存在的意义不是给予舒适和富裕的生活,而是当我们想到父母时,内心会充满力量和温暖,从而有克服困难的能力和勇气。看到鲁迅的这段文字,不由自主的开始反思自己,特别是对于小
声波驱动的无线水下摄像机面世科技日日报记者张梦然科学家估计,超过95的地球海洋从未被观测到过,而为水下摄像机长时间供电成本太高,阻碍了对海底的广泛探索。美国麻省理工学院(MIT)研究人员开发出一种声波驱动的无
为什么元宇宙这么火?普通人如何参与?元宇宙(Metaverse)这个词起源于NealStephenson在1992年出版的小说雪崩,Metaverse这个词,Metaverse本身就是由Meta(意即超越元)和ver
Cr(III)光催化剂催化吲哚与烯基重氮化物的(32)环加成反应近日,美国乔治亚大学(UniversityofGeorgia)EricM。Ferreira课题组发展了一种新颖的Cr(III)催化剂,在光催化下成功实现了吲哚和烯基重氮试剂的(32
新研究对木星北极的怪异气旋展开了更深入的挖掘2017年,NASA的朱诺号航天器在木星的北极捕捉到了九个气旋的图像。现在,四年多过去了,这九个气旋仍保持着朱诺号第一次发现它们时的模式。为了了解木星上这些奇异的气旋是如何保持稳定
真无线耳机也玩圈铁?重力星球P9圈铁蓝牙耳机不仅潮,更重音质云瑞将军图文版权声明图文均属原创,版权所有,未经许可或授权,禁止转载或引用。人生,就是在不断感受,不断体验,不断修行!感谢在这一路上给予我帮助的老师和朋友们。第一次遇到这么潮的耳机
河南大学,生日快乐嵩岳苍苍,河水泱泱中原文化悠且长济济多士,风雨一堂继往开来扬辉光四郊多垒,国仇难忘民主是式,科学允张猗欤吾校永无疆今天,是河南大学建校110周年百十年回望虽经霜历雪,坎坷荣光依旧不
王旭十年如一日公益路上演绎精彩人生王旭是马鞍山曼迪新大药房连锁有限公司董事长,同时他也是我市的一名公益先锋,十年来,他在经营企业的同时,积极参加各项公益事业,用实际行动回报社会,追求生命价值,演绎精彩公益人生。19
刘翔人生大起大落,二婚娶初恋,他终于让失意的人生重回赛道2004年8月27日,在雅典奥运会赛场中,五星红旗伴着国歌缓缓升起,这一次的奖项意义非凡。刘翔以12秒91的成绩打破了奥运会纪录,夺得了金牌,成为了中国田径项目上的第一个男子奥运冠
10款热门大公司增额寿对比,哪款最值得买?最近增额终身寿险火了,有钱人都在抢,很多粉丝来问,肝了几天,找了市面上热门10款,做了真实的收益对比测评。那么在此之前,我先科普下增额终身寿险,为什么有钱人都在抢?1。现金价值复利
开平赤坎华侨古镇项目(一期)计划2023年元旦试运营位于广东开平市的赤坎华侨古镇(一期)经过全新改造升级,计划2023年元旦试运营。赤坎古镇华侨文化展示旅游项目,即赤坎华侨古镇,已列入省和江门市重点项目。其中(一期)计划投资66亿元
海外躺赢时代落幕,国产制造齐发芯片再突破,屏幕面板追赶超一流全球知名拆机机构TechInsights在不久前发声称在对台积电三星的4nm工艺剖析后,认为这两家晶圆代工厂为了竞争,便放任其客户打着4nm的口号售卖产品,实则仍是5nm技术!今年
PC散热系统下放拯救者Y70在极致纤薄中实现猛兽性能拯救者y70芯片领域有一个不变的真理,即在相同工艺下,芯片要实现更强的性能,就需要有更高的热功耗(TDP)设计。虽然芯片有较强的耐高温性能,但无论是PC还是手机,过高的温度都会造成
央行系统重要性金融机构附加监管取得积极成效中新网9月22日电据中国人民银行微信公众号22日消息,央行宏观审慎管理局发布题为完善中国特色宏观审慎政策框架筑牢系统性金融风险防线的文章,其中提到,系统重要性金融机构附加监管取得积
自动驾驶新力量创新是驱动发展的第一动力。秦创原创新驱动平台启动建设以来,依托西咸新区秦创原总窗口,秦汉新城聚焦生命科学智能制造数字文化等领域,一大批产业项目和科创企业在这里聚集发展。秦创原上话发
乡村游会不会一地鸡毛?乡村游会不会一地鸡毛?重阳节单位组织退休人员活动,去县内某个村子游玩。这个村子比较有名,盛产橘子。政府因势利导,发展旅游。环境确实搞得不错,河水清清,芳草萋萋,粉墙黛瓦,花红柳绿。
中沥公司党建赋能维保先行助力公司高质量差异化发展大众网海报新闻记者纪超越通讯员牟国平滨州报道近年来,中海沥青股份有限公司(下称中沥公司)维修作业区党支部以党的建设与生产经营融合深化工程为契机,以四新技术为依托,以五小成果为载体,
门头沟民宿中的美丽乡村体验游主题线路探古寻幽,古村落体验乡村民俗京白梨线路节点炭厂村(体验红色文化)灵溪风景区(打卡岭角村一户一品创意立体画灵溪水利研学体验)妙峰山居妙峰山风景区(品高山玫瑰咖啡赏名山奇景)军庄镇孟悟村东山村(采摘京白梨)返程沿
长源电力湖北松滋抽水蓄能电站项目获得核准长源电力9月20日公告,公司收到湖北省发展和改革委员会下发的湖北省发展和改革委员会关于湖北松滋抽水蓄能电站项目核准的批复,公司湖北松滋抽水蓄能电站项目获得核准。项目总装机120万千
陈玘刘诗雯离队回家,女乒1号单打锁定,封闭比赛娱乐项目曝光9月30日,成都团体世乒赛即将拉开大幕,作为主办方,中国乒协和中国国乒也是全力冲刺各项准备。我们先来谈谈国乒人员这边的变动,因为接下来的比赛将实行闭环全封闭,所以国乒这边也是出现了
黄金周去哪里?成为了最头疼的一件事国庆黄金周临近,这可是一年一度的,可以和春节相媲美的小长假。也许很多人早就已经安排好了自己的小长假,毕竟这是一个可以好好放松的时间,但是对于很多人来说,这也成为了一件令人头疼的事情
世界旅游日重庆涪陵推出一站式旅游攻略央广网重庆9月27日消息(记者白刁尹)9月27日是世界旅游日,临近国庆,重庆涪陵推出一站式旅游攻略从武陵山大裂谷到816工程景区到点易园再到涪州画舫游,饱览涪陵优美自然景观和历史人