博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ReplayKit2 直播
阅读量:6705 次
发布时间:2019-06-25

本文共 6288 字,大约阅读时间需要 20 分钟。

1. iOS 游戏直播方案简介

  • iOS 9 之前可以通过私有框架 CoreSurface.framework 来实现录制屏幕。因为用了私有框架,只能通过企业包的形式安装到用户设备中,这种方法的优点是效率很高,但是无法获取游戏声音,只能通过麦克风录制外放的声音。

  • iOS 9 以后,苹果去掉了 CoreSurface, 因此,上面的方法彻底失效。iOS 9 发布以后的很长一段时间,都没有办法录屏直播。 后来大家另辟蹊径,通过破解 AirPlay 投屏协议的方式实现,在手机上虚拟一个 AirPlay Server 来接受屏幕镜像, 然后解码后再直播。目前 大部分直播平台都是直接接入第三方 SDK,如乐播, xindawn。 这种方案的缺点是 每次 iOS 系统升级,对应的 Airplay Mirroring协议会更新,破解成本高,技术门槛比较高。

  • iOS 10 中苹果提供 ReplayKit 了,可以在游戏中实现录屏直播,但是需要游戏厂商支持通用性很低。所以基本上各大厂商基本上还是采用 Airplay的模式。

  • iOS 11 苹果增强为ReplayKit2 提供了更通用的桌面级录屏方案,本文接下来会着重介绍这种方案。

2. ReplayKit2 概述

录屏功能是 iOS 10 新推出的特性,苹果在 iOS 9 的 ReplayKit 保存录屏视频的基础上,增加了视频流实时直播功能,官方介绍见 Go Live with ReplayKit。iOS 11 增强为 ReplayKit2,进一步提升了 Replaykit 的易用性和通用性,并且可以对整个手机实现屏幕录制,而非某些做了支持ReplayKit功能的App,因此录屏推流建议直接使用iOS11的ReplayKit2屏幕录制方式。系统录屏采用的是扩展方式,扩展程序有单独的进程,iOS 系统为了保证系统流畅,给扩展程序的资源相对较少,扩展程序内存占用过大也会被 Kill 掉。

3. 部分关键功能实现

# 系统回调处理- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {    KSYRKStreamerKit* kit = [KSYRKStreamerKit sharedInstance];    switch (sampleBufferType) {    case RPSampleBufferTypeVideo: {        CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);        pixelBuffer = [kit resizeAndRotatePixelBuffer:pixelBuffer];        CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);        if (sampleBuffer) {            [kit.streamerBase processVideoPixelBuffer:pixelBuffer timeInfo:pts];            _tempVideoPts = pts;            _tempVideoPixelBuffer = pixelBuffer;            _tempVideoTimeStamp = [[NSDate date] timeIntervalSince1970];        }    }    break;    case RPSampleBufferTypeAudioApp:        [kit mixAudio:sampleBuffer to:kit.appTrack];    break;    case RPSampleBufferTypeAudioMic:        [kit mixAudio:sampleBuffer to:kit.micTrack];        kit.streamerBase.bWithAudio = YES;        self.hasMicVoice = YES;        break;        default:    break;    }}复制代码

4. 部分已知问题及解决方案

4.1 屏幕帧方向

系统回调回来的视频帧都是竖屏的全尺寸图像,我们需要对其进行处理

- (CVPixelBufferRef)resizeAndRotatePixelBuffer:(CVPixelBufferRef)sourcePixelBuffer {    CIImage *outputImage;    if (self.privacyMode) {        outputImage = self.privacyImage;    } else {    //11.1以上支持自动旋转        #ifdef __IPHONE_11_1        if (UIDevice.currentDevice.systemVersion.floatValue > 11.1) {            CGImagePropertyOrientation oritation = ((__bridge NSNumber*)CMGetAttachment(sampleBuffer, (__bridge CFStringRef)RPVideoSampleOrientationKey , NULL)).unsignedIntValue;            if (oritation != self.lastOritation) {                self.lastOritation = oritation;                if (oritation == kCGImagePropertyOrientationLeft) {                    [kit setVideoOrientation:UIDeviceOrientationLandscapeLeft];                } else if (oritation == kCGImagePropertyOrientationRight) {                    [kit setVideoOrientation:UIDeviceOrientationLandscapeRight];                }            }        }        #endif        CIImage *sourceImage = [CIImage imageWithCVPixelBuffer:sourcePixelBuffer];        sourceImage = [sourceImage imageByApplyingOrientation:cvMobileRotate];        CGFloat outputWidth  = self.videoSize.width;        CGFloat outputHeight = self.videoSize.height;        CGFloat inputWidth = sourceImage.extent.size.width;        CGFloat inputHeight = sourceImage.extent.size.height;        //    float scale = MIN(outputWidth/inputWidth, outputHeight/inputHeight);        CGAffineTransform tranfrom = CGAffineTransformMakeScale(outputWidth/inputWidth, outputHeight/inputHeight);        outputImage = [sourceImage imageByApplyingTransform:tranfrom];    }    if (!outputPixelBuffer) {        //推流        NSDictionary* pixelBufferOptions = @{            (NSString*) kCVPixelBufferWidthKey : @(self.videoSize.width),            (NSString*) kCVPixelBufferHeightKey : @(self.videoSize.height),            (NSString*) kCVPixelBufferOpenGLESCompatibilityKey : @YES,            (NSString*) kCVPixelBufferIOSurfacePropertiesKey : @{}        };        CVReturn ret = CVPixelBufferCreate(kCFAllocatorDefault, self.videoSize.width, self.videoSize.height, kCVPixelFormatType_32BGRA, (__bridge CFDictionaryRef)pixelBufferOptions, &outputPixelBuffer);        if (ret!= noErr) {            NSLog(@"创建streamer buffer失败");            outputPixelBuffer = nil;            return outputPixelBuffer;        }    }    if (cicontext) {        [cicontext render:outputImage toCVPixelBuffer:outputPixelBuffer bounds:outputImage.extent colorSpace:CGColorSpaceCreateDeviceRGB()];    }    return outputPixelBuffer;}复制代码

4.2 隐私模式的实现

在直播过程中比如要切换到QQ,或者输入密码等操作,不方便给观众看到,就需要用到隐私模式,用一张或多张图片来代替屏幕截屏。

# UIImage 图片转成 CIImage 然后可以调整大小和方向,直接通过 CIContext 渲染到 CVPixelBufferRef 中UIImage *privacyImage = [UIImage imageNamed:privacyImageName];CIImage *sourceImage = [[CIImage alloc] initWithImage:privacyImage];复制代码

4.3 某些情况下视频帧不回调

缓存上一个视频帧,根据推流的fps适当的补帧

4.4 弹幕和礼物信息的显示

弹幕和礼物信息的显示有两种方案:

1、是在主 App 中,建立 Socket 连接,收到消息后,创建本地通知,显示礼物和弹幕,大部分直播应用采用这种方式比较多,对原来弹幕系统的改造比较小。

2、类似企鹅电竞的做法,通过Apns 远程推送通知的方式,实现弹幕礼物通知。

4.5 后台保活

采用第一种弹幕就涉及到主App的后台保活问题:

常用的几种后台保活方式:VOIP,后台定位,播放空白声音。

考虑到耗电和上线审核的问题,我们目前采用的是利用background task 播放空白声音。

# 创建 background taskself.taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{    [[UIApplication sharedApplication] endBackgroundTask:weakSelf.taskIdentifier];    weakSelf.taskIdentifier = UIBackgroundTaskInvalid;}];# 播放背景音乐self.taskTimer = [NSTimer scheduledTimerWithTimeInterval:20.0f repeats:YES block:^(NSTimer * _Nonnull timer) {if ([[UIApplication sharedApplication] backgroundTimeRemaining] < 61.f) {    //创建播放器    AVAudioSession *session = [AVAudioSession sharedInstance];    [session setActive:YES error:nil];    [session setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];    AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:weakSelf.musicUrl error:nil];    [audioPlayer prepareToPlay];    [audioPlayer play];    [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];}}];复制代码

4.6 部分游戏无声音

腾讯系的游戏,例如王者荣耀,刺激战场,在 先开游戏,再开直播的情况下,会出现主播无法听到游戏声音的问题。

原因是我们利用 AVAudioPlayer 后台保活,我们设置了 AVAudioSession 的 Option 为 AVAudioSessionCategoryOptionMixWithOthers 保证能和其他应用共用扬声器,但是这属性会导致 已经在播放的非Mix的声音被停止。

解决的方案只能是告知用户,先打开直播,再进入游戏,这样后播放的声音才不会有问题。

4.7 锁屏后录屏断开

这个问题没有很好的解决办法,只能在断开以后创建通知告知用户。

5. 参考

转载地址:http://bddlo.baihongyu.com/

你可能感兴趣的文章
Javascript调用Webservice的多种方法 .
查看>>
让 linux 交互式命令行程序支持方向键等功能
查看>>
Linux 启动、关闭、重启网络服务
查看>>
[转载]定制CentOS 6.3 自动安装盘
查看>>
js生成动态的飘过效果
查看>>
Java进阶05 多线程
查看>>
SQLSERVER性能监控级别步骤
查看>>
Java使用ScriptEngine(javax.script)
查看>>
Nhibernate中 Many-To-One 中lazy="proxy" 延迟不起作用的原因
查看>>
C# COM Object for Use In JavaScript / HTML, Including Event Handling
查看>>
svn权限设置
查看>>
MVC验证11-对复杂类型使用jQuery异步验证
查看>>
C++static关键字用法
查看>>
excel在msdn上的说明文档
查看>>
指尖下的js ——多触式web前端开发之一:对于Touch的处理(转)
查看>>
visual studio 2013使用技巧
查看>>
Sublime Text 相关
查看>>
深入理解css优先级
查看>>
Android MediaPlayer状态机
查看>>
Material Design Animation
查看>>