Flutter TextTrue ios 视频渲染 YUV420 转换 BGRA

image
image.png

网易云信播放器 Flutter 封装

事情是这样的 我们公司的业务是有 视频播放这一块业务 而且 是基于网易云信的 视频服务的 做的开发 。公司的App开发框架是使用 Flutter , 那么问题来了 Flutter 怎么 实现视频播放嘞 , 官方给出的解决方案 是 ### video_player 这个库的 实现 是 原生端做视频解码 然后通过 Texture 的方式 映射到 Flutter 中 但是解码器 IOS 使用的是 官方的 AVPlayer(苹果官方提供的播放器 啥都好 就是不支持流媒体播放 ) Android 解码器则是 exoplayer 很好很nice
但是

网易云信的视频 是加密的 只有自己的 播放器sdk 在能解码播放 android 和 ios 都支持流媒体 so 只能自己封装

Android 使用 SurfaceTexture 衔接 视频流 正常 但是 ios emmm 网易云信 播放器 返回 的 编码格式 是 NELP_YUV420 就是 YUV420 直接映射到 Flutter 黑屏 但是有声音

//获取 视频回调 数据
 [_player  registerGetVideoRawDataCB:NELP_YUV420 and:^(NELPVideoRawData *frame) {
 Videodata=frame;
 }];

因为Skia 引擎底层只支持了 BGRA 格式的视频数据 所以 和黑屏了

首先我们吧 YUV420 转换成 CVPixelBufferRef 方法如下
该方法依赖 libyuv 请自行导入

+ (CVPixelBufferRef)i420FrameToPixelBuffer:(NSData *)i420Frame width:( int )frameWidth height:( int )frameHeight

{

        

 int width = frameWidth;

 int height = frameHeight;

    

*if (i420Frame == nil) {

 return NULL;

 }

    

 CVPixelBufferRef pixelBuffer =  NULL ;

 NSDictionary *pixelBufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys:

 [NSDictionary dictionary], ( id )kCVPixelBufferIOSurfacePropertiesKey,

 nil ];

    

 CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault,

 width,

 height,

 kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,

 ( __bridge  CFDictionaryRef)pixelBufferAttributes,

 &pixelBuffer);

    

  if  (result != kCVReturnSuccess) {

 NSLog(@"Failed to create pixel buffer: %d", result);

 return NULL ;

 }

    

 result = CVPixelBufferLockBaseAddress(pixelBuffer, 0);

    

  if  (result != kCVReturnSuccess) {

 CFRelease(pixelBuffer);

 NSLog(@"Failed to lock base address: %d", result);

 return  NULL ;

 }

        

 uint8 *dstY = (uint8 *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);

  int  dstStrideY = (**int**)CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);

 uint8* dstUV = (uint8*)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);

  int  dstStrideUV = ( int )CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);

    

 UInt8 *_planeData[3];

 NSUInteger _stride[3];

    

 CFDataRef dataref = ( __bridge  CFDataRef)i420Frame;

 uint8 * _data = (UInt8 *) CFDataGetBytePtr(dataref);

    

 _planeData[NEI420FramePlaneY] = _data;

 _planeData[NEI420FramePlaneU] = _planeData[NEI420FramePlaneY] + width * height;

 _planeData[NEI420FramePlaneV] = _planeData[NEI420FramePlaneU] + width * height / 4;

    

 _stride[NEI420FramePlaneY] = width;

 _stride[NEI420FramePlaneU] = width >> 1;

 _stride[NEI420FramePlaneV] = width >> 1;

    

#ifndef KLSMediaCaptureDemoCondense

    

 int  ret = libyuv::I420ToNV12(_planeData[NEI420FramePlaneY], ( int )_stride[NEI420FramePlaneY],

 _planeData[NEI420FramePlaneU], ( int )_stride[NEI420FramePlaneU],

 _planeData[NEI420FramePlaneV], ( int )_stride[NEI420FramePlaneV],

 dstY, dstStrideY,

 dstUV, dstStrideUV,

 width, height);

    

#endif

 CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);

#ifndef KLSMediaCaptureDemoCondense

    

 if (ret) {

 NSLog(@"Error converting I420 VideoFrame to NV12: %d", result);

 CFRelease(pixelBuffer);

  return NULL ;

 }

#endif

    

  return  pixelBuffer;

}



然后是 pixelBuffer To SampleBuffer


+ (CMSampleBufferRef)pixelBufferToSampleBuffer:(CVPixelBufferRef)pixelBuffer

{

 CMSampleBufferRef sampleBuffer;

 CMTime frameTime = CMTimeMakeWithSeconds([[NSDate  date] timeIntervalSince1970], 1000000000);

 CMSampleTimingInfo timing = {frameTime, frameTime, kCMTimeInvalid};

 CMVideoFormatDescriptionRef videoInfo =  NULL ;

 CMVideoFormatDescriptionCreateForImageBuffer( NULL , pixelBuffer, &videoInfo);

    

 OSStatus status = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixelBuffer, true ,  NULL , NULL , videoInfo, &timing, &sampleBuffer);

  if (status != noErr) {

 NSLog(@"Failed to create sample buffer with error %d.", ( int )status);

 }

    

 CVPixelBufferRelease(pixelBuffer);

  if (videoInfo)

 CFRelease(videoInfo);

    

  return  sampleBuffer;

}


最后吧 SmapleBuffer 转换 BGRA

  

  

_//转化_

-(CVPixelBufferRef)convertVideoSmapleBufferToBGRAData:(CMSampleBufferRef)videoSample{

    

 _//CVPixelBufferRef是CVImageBufferRef的别名,两者操作几乎一致。_

 _//获取CMSampleBuffer的图像地址_

 CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(videoSample);

 _//VideoToolbox解码后的图像数据并不能直接给CPU访问,需先用CVPixelBufferLockBaseAddress()锁定地址才能从主存访问,否则调用CVPixelBufferGetBaseAddressOfPlane等函数则返回NULL或无效值。值得注意的是,CVPixelBufferLockBaseAddress自身的调用并不消耗多少性能,一般情况,锁定之后,往CVPixelBuffer拷贝内存才是相对耗时的操作,比如计算内存偏移。_

 CVPixelBufferLockBaseAddress(pixelBuffer, 0);

 _//图像宽度(像素)_

 size_t pixelWidth = CVPixelBufferGetWidth(pixelBuffer);

 _//图像高度(像素)_

 size_t pixelHeight = CVPixelBufferGetHeight(pixelBuffer);

 _//获取CVImageBufferRef中的y数据_

 uint8_t *y_frame = ( unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);

 _//获取CMVImageBufferRef中的uv数据_

 uint8_t *uv_frame =( unsigned char  *) CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);

        

 _// 创建一个空的32BGRA格式的CVPixelBufferRef_

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

 CVPixelBufferRef pixelBuffer1 = NULL ;

 CVReturn result = CVPixelBufferCreate(kCFAllocatorDefault,

 pixelWidth,pixelHeight,kCVPixelFormatType_32BGRA,

 ( __bridge  CFDictionaryRef)pixelAttributes,&pixelBuffer1);

  if  (result != kCVReturnSuccess) {

 NSLog(@"Unable to create cvpixelbuffer %d", result);

  return NULL ;

 }

 CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);

    

 result = CVPixelBufferLockBaseAddress(pixelBuffer1, 0);

 if (result != kCVReturnSuccess) {

 CFRelease(pixelBuffer1);

 NSLog(@"Failed to lock base address: %d", result);

  return NULL ;

 }

    

 _// 得到新创建的CVPixelBufferRef中 rgb数据的首地址_

 uint8_t *rgb_data = (uint8*)CVPixelBufferGetBaseAddress(pixelBuffer1);

    

 _// 使用libyuv为rgb_data写入数据,将NV12转换为BGRA_

  int  ret = NV12ToARGB(y_frame, pixelWidth, uv_frame, pixelWidth, rgb_data, pixelWidth * 4, pixelWidth, pixelHeight);

  if  (ret) {

 NSLog(@"Error converting NV12 VideoFrame to BGRA: %d", result);

 CFRelease(pixelBuffer1);

 return NULL ;

 }

 CVPixelBufferUnlockBaseAddress(pixelBuffer1, 0);

    

 return  pixelBuffer1;

}

方法如何使用


if(Videodata){

 int width,height;

 width=Videodata->width;

 height=Videodata->height;

 int len = Videodata->width * Videodata->height * 3 / 2;

 NSData * data = [[NSData alloc] initWithBytes:Videodata->UsrData length:len];

 CVPixelBufferRef originalPixelBuffer  = [NEYUVConverter i420FrameToPixelBuffer:data width:Videodata->width height:Videodata->height];

 CMSampleBufferRef sampleBuffer = [NEYUVConverter pixelBufferToSampleBuffer:originalPixelBuffer];

 CVPixelBufferRef finalPiexelBuffer;

 finalPiexelBuffer = [ self  convertVideoSmapleBufferToBGRAData:sampleBuffer];

 CVPixelBufferRelease(originalPixelBuffer);

 return finalPiexelBuffer;

 }


转载:姜姜和张张
原文地址
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容

  • 1.你的公众号目标用户是谁?有什么诱饵有可能激发他帮你传播?有什么内容会激发他共鸣自发传播? 注意:前后的区别在于...
    Jeffreyshang阅读 249评论 1 0
  • 去年看完《逃避虽可耻但有用》,恰逢分手后低谷丧期,今年看《今生是第一次》,又正巧碰上工作颓期。所以累积起来的情绪...
    羊乐喜阅读 227评论 0 0
  • 有时候觉得人生真的不能少了朋友,在伤心难过的时候,感谢有那么多的朋友关心我.不论是身边的朋友还是网上的朋友,...
    紫依晓韵阅读 479评论 0 12
  • 病人 文/ 思 风摩挲着,他们是秋天散落的 几片枯叶,在轮椅里,在医院花坛边 在重与轻的阴影里 尊严,蜷缩成透明的...
    思_zs912阅读 201评论 0 0