iOS11 ReplayKit2 问题总结
一、苹果自6月30日发布iOS11系统之后,其中的Airplay的协议发生变更,导致市场上的苹果直播助手(录屏)大部分变得不可用,因此在iOS11之后需要寻找新的技术方案来录屏
1)采用系统提供的ReplayKit2 包含的System Screen Record的框架
2) 采用libUSB的方案,这个方案利用的苹果的USB协议,github上面已经存在一个库,据说比较难编译,国外的直播平台Mobcrush,体验了一下效果非常好。
二、采用苹果的提供的方案才是正途,不然以后每次升级去破解Airplay的协议太折腾,也不经济。下面总结遇到的一些难题
1)ReplayKit2 本身存在bug,在不断更新的beta版本中,一直存在框架回调视频帧时序错位、声音消失,无法正常启动框架必须重启
2)ReplayKit2 开发Xcode调试很难,每次启动调试,Xcode调试器默认挂起的是主App,如果你需要调试一个一启动就发生的问题,很可能进程直接结束了,调试器什么信息都没有,吐槽Xcode
苹果这么大的市值,到底拿出多少钱用于研发测试,大概以大众为测试,这种态度一定会没落!!!
3)ReplayKit2 直到正式版本中存在的问题,内存不能超过50MB,如果一超过,系统马上干掉你
4)ReplayKit2 与主App之间没有进程通信机制(重大缺陷),直播平台一般主播都有自己的账号,直播权限,弹幕,礼物,苹果只考虑推流么??并且推的流还只能是竖屏,需要hack解决
三、一些经验
1)内存不能超过50MB
在系统回调给你的YUV数据(NV12格式)中,这个回调在多个线程,之前为了避免时序的问题,将回调统一调度到一个串行队列中:
if([_txLivePush isPublishing]){ __weak typeof(self) wSelf = self; CFRetain(videoSample); dispatch_async(_encodeQueue, ^{ [wSelf NV12ToI420AndRotate:videoSample]; // [_txLivePush sendVideoSampleBuffer:videoSample]; CFRelease(videoSample); });
这里带来一个问题,大屏手机在按home键的过程中,upload进程的线程调度受到影响,导致视频数据在队列中积压,内存峰值一旦超过50MB,系统立马把你杀掉
所以在视频的数据流中,一定要注意缓冲区的长度,申请内存一般不要超过3MB,采用同步的方案更可控一些
2)隐私模式
隐私模式就是将系统给的数据替换成一张YUV图片,通常涉及给到的是PNG、JPG
这里需要将JPG->UIImage->pix Data -> YUV I420
下面是一些介绍:
PG->UIImage->pix Data
+ (unsigned char *)pixelARGBBytesFromImageRef:(CGImageRef)imageRef { NSUInteger iWidth = CGImageGetWidth(imageRef); NSUInteger iHeight = CGImageGetHeight(imageRef); NSUInteger iBytesPerPixel = 4; NSUInteger iBytesPerRow = iBytesPerPixel * iWidth; NSUInteger iBitsPerComponent = 8; unsigned char *imageBytes = malloc(iWidth * iHeight * iBytesPerPixel); CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(imageBytes, iWidth, iHeight, iBitsPerComponent, iBytesPerRow, colorspace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); CGRect rect = CGRectMake(0 , 0 , iWidth , iHeight); CGContextDrawImage(context , rect ,imageRef); CGColorSpaceRelease(colorspace); CGContextRelease(context); CGImageRelease(imageRef); return imageBytes; }
上面的方法将UIImage转成ARGB的格式,因为libyuv中有一个ARGBToI420的方法。上面的方法中注意选项
kCGImageAlphaNone, /* For example, RGB. */ kCGImageAlphaPremultipliedLast, /* For example, premultiplied RGBA */ kCGImageAlphaPremultipliedFirst, /* For example, premultiplied ARGB */ kCGImageAlphaLast, /* For example, non-premultiplied RGBA */ kCGImageAlphaFirst, /* For example, non-premultiplied ARGB */ kCGImageAlphaNoneSkipLast, /* For example, RBGX. */ kCGImageAlphaNoneSkipFirst, /* For example, XRGB. */ kCGImageAlphaOnly
控制颜色通道的顺序,数据的大小端
转好的数据,再转成I420
//ToI420 CVReturn rc = CVPixelBufferCreate(NULL, imgSize.width, imgSize.height, kCVPixelFormatType_420YpCbCr8PlanarFullRange, NULL, &_pausePixBuffer); rc = CVPixelBufferLockBaseAddress(_pausePixBuffer, 0); uint8_t *y_copyBaseAddress = (uint8_t*)CVPixelBufferGetBaseAddressOfPlane(_pausePixBuffer, 0); uint8_t *u_copyBaseAddress = (uint8_t*)CVPixelBufferGetBaseAddressOfPlane(_pausePixBuffer, 1); uint8_t *v_copyBaseAddress = (uint8_t*)CVPixelBufferGetBaseAddressOfPlane(_pausePixBuffer, 2); size_t dYLineSize = (size_t)CVPixelBufferGetBytesPerRowOfPlane(_pausePixBuffer, 0); size_t dULineSize = (size_t)CVPixelBufferGetBytesPerRowOfPlane(_pausePixBuffer, 1); size_t dVLineSize = (size_t)CVPixelBufferGetBytesPerRowOfPlane(_pausePixBuffer, 2); tx_ARGBToI420(argbData, imgSize.width*4, y_copyBaseAddress, (int)dYLineSize, u_copyBaseAddress, (int)dULineSize, v_copyBaseAddress, (int)dVLineSize, (int)imgSize.width, (int)imgSize.height); free(argbData);
kCVPixelFormatType_420YpCbCr8PlanarFullRange 代表I420的格式