一、蘋果自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的格式