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;

 }


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

你可能感兴趣的:(Flutter TextTrue ios 视频渲染 YUV420 转换 BGRA)