iOS 手游直播方案比较及Airplay录屏详解

手游直播是直播行业中非常重要的一个垂直领域. 手游直播与其他移动直播相比主要是画面的来源不同, 手游直播其实是一种录屏技术. 游戏玩家在玩游戏的同时将画面内容实时地分享展示给其他观众, 在配上玩家自己的语音, 能够给观众带来比较有趣的观看体验. 手游直播与PC端游戏直播相比主要是设备的计算能力不同。
PC游戏直播有OBS等强大的第三方直播软件, 加上PC强劲有富余的计算能力, 使得PC游戏直播的门槛相对较低。
手游直播中由于Apple对个人隐私和安全性的重视, iOS手游直播相对于Android手游直播的难度又大了很多。

一. 现有iOS录屏方案分析

为了解决iOS手游直播中视频数据采集的难题, 主要存在以下三种方案:

  1. 通过私有API获取系统的IOSurface
    这种方法效率比较高, 但是从iOS9 开始, 这个私有API的漏洞就被Apple堵上了, 新系统中无法使用;

  2. Airplay Screen Mirroring;

Airplay Screen Mirroring

Airplay 是 Apple 提供的一种远程播放技术, 可以将iPhone、iPod touch、iPad及Mac上的音频,照片,ppt, 视频和系统界面镜像等内容传送到同一局域网中支持Airplay的设备(如:音箱、Apple TV)中播放. 其中Airplay Screen Mirroring 就是用于屏幕投影的功能,有iOS系统自身将屏幕内容进行采集压缩,通过网络投屏的其他设备上. 但 Airplay 是Apple的私有协议组, 并且为了安全考虑, 传输过程中音视频数据都是用Apple私有的Fairplay加密的, 因此要想获得这些数据, 必须破解Airplay的协议和并且破解Fairplay加密方式。
另外出于个人隐私保护和影视版权保护的考虑,Apple不允许录屏功能的软件上架 AppStore, 即使成功上架, 不久也会被强制下架。

  1. ReplayKit
ReplayKit

Apple 注意到了广大用户对手游直播的呼声, 从iOS 9开始提供了ReplayKit, 给了用户主动对外分享屏幕内容的能力. 对与Replaykit, Apple在不断的增强, iOS9的时候还只能把特定App的画面录制成MP4的片段, 到iOS10 能够获取特定App的原始图像和声音, 到iOS11, 能够从系统级启动录制, 获得所有APP包括桌面的画面. 但是Replaykit是以APP扩展的方式存在的, 真正接受到画面, 进行压缩发送的部分, 并不是一个完整的应用程序, 而是一个在后台运行的扩展. Airplay则可以作为一个在后台运行的App, 相对来说, 完整App的稳定性和可控程度在现阶段可能要优于App扩展。

ReplayKit 的方案大家可以参考 KSYReplayKit 或者Apple 官方的文档。

二. KSYAirStreamer SDK

KSYAirStreamer SDK基于Airplay Screen Mirroring的录屏SDK,使用Airplay方案录屏, 其实就要求直播APP将自己伪装成一个Airplay的接收设备, 实现Airplay的协议,解密出Fairplay加密的数据, 再将数据转发出去。

KSYAirStreamer

KSYAirStreamer_iOS 就提供了这样一个模拟接收设备的SDK, 开始录屏时iOS系统与SDK建立连接, SDK收到画面后, 编码发送到直播服务器. 其中编码和推流功能使用金山云直播SDK实现。

正常的Airplay镜像, 是当与手机在同一个局域网中有一台Apple TV时, 通过手机的控制中心(在屏幕下方边缘处上拨时出现), 可以看到候选的Airplay 设备, 选中对应的设备,即可开始镜像, 此时系统顶部的状态栏会变成蓝色, 并有AirPlay的logo。

使用KSYAirStreamer 进行录屏直播时, 则省去了用户手动选择设备的过程, 因为我们已经知道设备的名称, 可以直接主动的选择对应的设备. 这一步主要是通过调用私有API, 直接对控制中心进行操作实现的. 也是无法上AppStore的理由之一。

2.1 KSYAirStreamKit

KSYAirStreamKitKSYAirTunesServer和金山云直播SDK中的音频采集和推流等组件 组装在一起共同实现了iOS录屏直播的功能。
需要录屏时, 构造一个KSYAirStreamKit的实例, 并通过kit 设置关于录屏和推流的一些配置,最后调用startService / stopService 方法, 即可启 / 停录屏。

    _kit = [[KSYAirStreamKit alloc] init];
    _kit.airCfg = [[KSYAirTunesConfig alloc] init];
    _kit.videoBitrate = 800; //kbps
    _kit.streamUrl = @"rtmp://xxx.xxx/live/stream";
    [_kit startService];
    ....
    [_kit stopService];

2.2 Airplay镜像配置参数

启动镜像前, 需要根据实际情况, 设置airplay镜像的参数, 这一步和直播时设置摄像头采集的参数是类似的. 通过KSYAirTunesConfig类可以配置的参数内容如下:

参数名 类型 说明 默认值
airplayName NSString* Airplay接收设备的名称 ksyair
videoSize int 接收设备的尺寸, 指定的是MAX(宽,高) 960
framerate int 希望接收到iOS发送端的视频帧率 30
airTunesPort short 设置airtunes 服务的监听端口, 0 表示系统自动分配 0
airVideoPort short 设置视频数据的接收端口(带重试) 7100
macAddr NSData * 设备的mac地址, 默认随机生成,(长度为6字节) 随机

其中airplayName设定的字符串就是, 在控制中心中能够看到的设备的名称. 当同一个局域网需要有多个设备同时使用录屏SDK时, 建议这个名称加上设备相关的后缀, 避免冲突。

videoSize 和正常的分辨率设置不太一样, 只有一个数字. 竖屏时高度为videoSize, 宽度根据屏幕比例计算得到,横屏时则宽度为videoSize, 高度又系统自动计算. 实际测试发现, iOS 10以上的系统中,部分设备可能不支持低分辨率. 比如iPhone 6s等设备已经不支持720(416 * 720,或720 * 416)以下分辨率输出, 请按实际情况做分辨率调整. 设置这些分辨率容易导致系统异常, 可能需要重启手机才能恢复。

2.3 推流参数设置

KSYAirStreamKit 中通过streamerBase实例来实现推流, 对应参数的配置请参考wiki中的描述。

2.4 声音采集

KSYAirStreamKit 中通过 KSYAUAudioCapture来实现的音频采集. 正常的Airplay镜像时, 其实是音频和画面同步被投影到AppleTV等设备上的. 但是当我们录屏时, 接收端也在同一个设备上, 这时候保持原有逻辑音频就会出现回环了. 因此实际上, 录屏时, 只有画面部分走Airplay协议发送, 而音频还是通过外放(Speaker)播放, 这样系统或者游戏等App的声音, 能和主播的声音一起, 通过 KSYAUAudioCapture一起采集进来, 观众听到的就是比较完整的场景声音了。

2.5 状态变化和消息通知

整个录屏推流过程中, 有两个状态变化需要关注. 一个是来自数据源的KSYAirTunesServer的状态变化, 另一个是推流状态的变化。

2.5.1 Airplay镜像的状态变化

Airplay镜像 其实也是iOS系统和我们SDK通过网络进行连接交互的一个过程, 在连接阶段可能出错, 在连接成功后也可能受到网络变化等因素的影响断开连接. KSYAirTunesServer 通过 KSYAirDelegate代理协议来提供状态变化的通知, KSYAirStreamKit也转发了此代理协议.
didStartMirroring 和 didStopMirroring 方法负责通知Airplay镜像的启动和停止。
mirroringErrorDidOcccur 负责通知当前遇到的错误。

错误类型 说明 错误码值
KSYAirErrorCodePortConflict 端口冲突, 请检查airTunesPort/airVideoPort的配置 0
KSYAirErrorCodeNetworkDisconnection 网络未连接 1
KSYAirErrorCodeAirPlaySelectTimeout 连接AirPlay超时 2
KSYAirErrorCodeConnectBreak 连接断开(网络切换) 3
KSYAirErrorCodeOther 其他未知错误 4

2.5.2 推流状态变化通知

推流的状态变化和直播SDK中的内容一致, 请参考wiki
KSYAirStreamKit 中已经实现了基本的重连逻辑

三. Airplay 协议浅析

KSYAirStreamer SDK中的核心类KSYAirTunesServer实现了Airplay Screen Mirroring接收端的协议。

3.1 Airplay 录屏SDK 难点分析

3.1.1 Airplay 协议文档缺失

AirPlay是Apple的私有协议族, 没有公开的官方文档作为参考. 目前能够参考的是github上nto大神整理的一份非官方的协议spec文档, 但是完全照着文档的描述是无法兼容新的iOS系统(iOS9/10)的, 新系统对协议有改变,而这份文档的最后更新日期是2012年, 这些变更内容都没有包含。

3.1.2 Airplay 敏感数据加密的破解

有人会说Airplay毕竟是通过网络交互的协议, 当年 nto能够整理出一份spec, 现在也可以通过抓包, 分析的方式得到新的交互过程, 缺少了文档不过是增加一些破解协议的难度而已. 但是以保护用户隐私著称的Apple, 不会明文传输音视频数据的. Airplay协议中有着比较完善的DRM保护, 传输的音视频都是用AES算法加密过的, 而AES的key 则是在握手的过程中通过Fairplay协议保护的, 这一部分是Apple重点保护的内容, 目前没有人能真正破解。

3.1.3 单机Airplay 录屏中音频采集的坑

使用Airplay的方案做录屏, 其实是接收端和客户端在同一设备上的,我们可以称之为单机Airplay. 正常的投屏过程中, 客户端会把音频投到接收设备上. 单机Airplay的话, 投屏的音频, 按逻辑还得继续投屏, 这时候就出现回环了. 因此录屏是必须通过一定的手段避免通过airplay传递音频, 强制保证音频还是通过外放(Speaker)播放. 再加上直播时还需要采集主播的声音. 这些与音频相关的操作都和iOS的AudioSession相关, 非常容易出现不稳定的情况。

3.2 Airplay 协议可行性分析

Airplay的可行性主要是通过市面上已有的一些商业方案来保证的, 我们能看到reflector 2, 360 Mirroring 等收费的商业软件, 还有小米电视等机顶盒设备都有一些成功案例. 因此一定有人攻克以上的难点。

3.3 Airplay 协议构成

AirPlay并不是完全重新开始写的一个协议, 是好几个现有的协议的组合, 其中有的协议是完全标准的, 有一部分协议进行了一些修改,有的则是完全私有的。

  • Multicast DNS (aka Bonjour) 用于发布服务, 启动后, 在iOS的控制中心菜单中就能看到对应的设备;
  • HTTP / RTSP / RTP 用于流媒体服务, 传输音视频数据, 进行播放控制等;
  • NTP 时间同步;
  • FairPlay DRM加密 完全私有的加密协议。

Airplay 在iOS上有很多种应用场景, 可以单独传输音频, 可以传输照片, 幻灯片, 可以直接传输本地播放的视频, 也可以直接投影屏幕内容. 不同应用场景协议交互的步骤也不同. 录屏应用主要是需要实现其中投影屏幕内容的部分交互协议。

3.4 Airplay Screen Mirroring

Airplay 录屏SDK内部的主要流程如下:

airplay_flow

启动录屏时, 通过iOS上的 NSNetService API 实现Airplay的服务的发布, 服务发布的txtRecod里的字段比较关键, 其中有些字段决定了后续交互的内容, 比如加密方式等。

整个投影过程中有两个主要的网络连接, 接收端需要监听两个端口(airTunesPort,airVideoPort)。

其中, 通过airTunesPort建立的TCP连接主要完成的是建连握手部分, 通过airVideoPort建立的TCP连接用于传输 H.264编码的视频数据, 视频数据是被AES加密过的。

3.5 Airplay 握手建连

建连握手部分是经过修改的RTSP协议, 与HTTP协议比较类似, 都是客户端(iOS系统)发起请求, 接收端SDK针对请求内容进行回应的方式. 每个请求包括:方法+路径+header+content等内容。

主要的交互过程如下:

  1. 4次POST请求, 分别对应的路径为 /pair-setup, /pair-verify和两次/fp-setup 这是开头于FairPlay相关的核心加密部分, 任何一次POST的回复内容不对都会导致握手失败。
  2. SETUP请求 不同版本的iOS系统中, SETUP方法的次数可能不同.
    在SETUP方法中能得到比较多的信息, 比如后面视频AES解密需要用到的ekey和eiv. 接收端需要将自己的监听的接收端口airVideoPort回复给客户端.这些信息需要对这次请求的content使用Apple的Binary plist格式进行解码才能提取, 回复内容也要编码为Binary plist格式. 后续完成握手后, 客户端会通过 airVideoPort 端口建立 TCP连接, 然后将屏幕画面通过H.264编码后再通过AES加密处理后, 向连接持续发送。
  3. 一次GET请求, 路径为/info, 这里接收端需要回复一个Binary plist格式的数据, 将用户配置的画面分辨率, 最大帧率等信息传递到客户端。
  4. 可能有多次GET_PARAMETER和SET_PARAMETER请求用于调整音量大小。
  5. 一次RECORD请求, 表明握手完成, airplay镜像开始。
  6. 在镜像过程中会每秒都收到POST请求,路径为/feedback, 相当于心跳, 用于保持TCP的长连接。

3.6 Airplay Screen Mirroring 视频数据传输

视频数据传输是客户端通过TCP 单方向的往接收端灌加密后的数据. 数据内容分为头部和载荷. 头部包含了载荷的类型, 长度, 时间戳等信息. 载荷部分有两种, 一种是H.264的参数集, sps, pps等; 另一种就是AES加密后的H.264裸流, 没有填充到封装格式中, 用SETUP请求中得到的key和iv解密后,即可送到视频解码器中解码。

4. 总结

以上主要汇总了一下当前iOS平台上录屏的一些方案. 介绍了 基于Airplay Screen Mirroring的录屏SDK - KSYAirStreamer 的基本结构和使用方法。最后介绍了KSYAirStreamer中实现的Airplay协议的一些细节。希望能够帮到有录屏需求的同学.。
KSYAirStreamer 的demo大家可以到 github 查看代码和体验功能。

转载请注明:
作者金山视频云,首发简书 Jianshu.com


也欢迎大家使用我们的直播SDK:
iOS融合版(推流 + 播放):https://github.com/ksvc/KSYLive_iOS
有关音视频的更多精彩内容,请参考https://github.com/ksvc
金山云SDK相关的QQ交流群:

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