Airplay研究 (转)

    之前有使用DLNA实现投屏功能 ,但或许此功能对用户体验来讲有限,达不到用户的满意度,所以开始研镜像功能,但镜像的实现实际上只有两种解决方案 miracast以及airplay。前者 并不能实时使用,需要引导用户对电视端与手机端进行一定的设置开启后方可使用,与airplay的体验相差甚远,so 打算入坑airplay。查找多方资料,做以处总结,方便后继接着调研,开发。以下内容大多为我转载,都会标注来源处 ~

多屏互动技术研究(三)之Airplay研究(原创)
来源:https://blog.csdn.net/u011897062/article/details/79446001

Airplay技术研究

1. Airplay简介

AirPlay 是苹果开发的一种无线技术,可以通过WiFi将iPhone 、iPad、iPod touch 等iOS 设备上的包括图片、音频、视频及镜像通过无线的方式传输到支持AirPlay 设备(IOS8后,AirPlay可使用P2P直连,绕过了WIFI,具体有待深入)。
AirPlay支持如下几种使用场景:
• 从iOS设备上传输并显示照片、幻灯片;
• 从iOS设备或者Itunes软件中传输并播放音频;
• 从iOS设备或者Itunes软件中传输并播放视频;
• 对iOS设备或者OS X Mountain Lion进行屏幕镜像。由于此功能需要硬件的硬解码支持,所以只能在iPad 2、iPhone 4S、带Sandy Bridge CPU的Mac电脑(或更新的设备)上支持。
最初这套协议名字叫AirTunes,只支持音频流播放。 后来苹果开发Apple TV时,对此协议进行了扩充和改进,加入了视频支持,并改名叫做AIRPLAY。

1.1 Airplay协议构成

AirPlay并不是完全重新开始写的一个协议, 是好几个现有的协议的组合, 其中有的协议是完全标准的, 有一部分协议进行了一些修改,有的则是完全私有的。
• Multicast DNS (aka Bonjour) 用于发布服务, 启动后, 在iOS的控制中心菜单中就能看到对应的设备;
• HTTP / RTSP / RTP 用于流媒体服务, 传输音视频数据, 进行播放控制等;
• NTP 时间同步;
• FairPlay DRM加密 完全私有的加密协议。

1.2 Airplay难点分析

1.2.1 Airplay 协议文档缺失

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

1.2.2 Airplay 敏感数据加密的破解

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

1.3 Airplay 握手建连

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

主要的交互过程如下:
4次POST请求, 分别对应的路径为 /pair-setup, /pair-verify和两次/fp-setup这是开头于FairPlay相关的核心加密部分, 任何一次POST的回复内容不对都会导致握手失败。SETUP请求 不同版本的iOS系统中, SETUP方法的次数可能不同。

在SETUP方法中能得到比较多的信息, 比如后面视频AES解密需要用到的ekey和eiv. 接收端需要将自己的监听的接收端口airVideoPort回复给客户端.这些信息需要对这次请求的content使用Apple的Binary plist格式进行解码才能提取, 回复内容也要编码为Binary plist格式. 后续完成握手后, 客户端会通过 airVideoPort 端口建立 TCP连接, 然后将屏幕画面通过H.264编码后再通过AES加密处理后, 向连接持续发送。

一次GET请求, 路径为/info, 这里接收端需要回复一个Binary plist格式的数据, 将用户配置的画面分辨率, 最大帧率等信息传递到客户端。

可能有多次GET_PARAMETER和SET_PARAMETER请求用于调整音量大小。

一次RECORD请求, 表明握手完成, airplay镜像开始。

在镜像过程中会每秒都收到POST请求,路径为/feedback, 相当于心跳, 用于保持TCP的长连接。

1.4 Airplay Screen Mirroring 视频数据传输

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

2. 实现机制

实现Airplay主要分为以下几个步骤:
1. 设备发现,也就是实现音视频服务发现功能;
2. 设备连接及信令处理;
3. 音视频数据包解密及解码处理。

2.1 设备发现

实现AIRPLAY协议的软件不需要再做任何配置就能发现同一网络中的相关设备,这主要得益于Bonjour(基于M-DNS协议实现)。Bonjour:苹果为基于组播域名服务(multicast DNS)的开放性Zeroconf标准所起的名字。Zeroconf (零设置网络标准):全称为Zero configuration networking,中文名则为零配置网络服务标准,是一种用于自动生成可用IP地址的网络技术,不需要额外的手动配置和专属的配置服务器。具体例子为:用户拥有一台apple tv和一台iPhone5s,那之只要都连入到同一个无线局域网内,iphone4s就会自动找出apple tv,那么在播放音乐或者视频时候,用户只要点击推送,就可以讲音乐和视频推送到apple tv上播放。
关于mDNS部分,苹果已经开源了一个工程:mDNSResponder,可以进行参考,另外JAVA下也有一个开源实现jmdns,而在Android平台中,自Android4.1开始官方实现了一套Nsd API(网络服务发现),也实现了此功能。
服务发布后的查询响应包如下:


服务发布后的查询响应包

图1 服务发布后的查询响应包
在这个查询包中有下面几个关键字段,Name、Service、Port,AirPlay服务名称格式如下:AS-XX._airplay._tcp.local, RAOP服务名称格式如下:000B828B571E@AS-XX._raop._tcp.local. Service字段也就是.号分割的第一项,第二项说明了服务的类别:_airplay是视频服务,_raop是音频服务,第三项说明数据传输的协议,可以通过tcp或者udp传输。Port声明了RTSP及HTTP信令交互的端口,也就是接收端以这两个端口分别创建ServerSocket,Client可以通过这两个端口与服务端建立起来连接。所以在发布airplay及raop服务之前必须准备好这两个ServerSocket, 当iphone/ipad发现服务并发起连接时,信令便通过这两个端口来与接收端进行交互。在镜像流程中,信令会随机选择一个通道进行信令交互。
下面是mDNS中txt相关字段的抓包及各个字段的含义,在最新的IOS系统中增加了比较多的字段,有些字段的含义暂时还不明确,但是这个并不影响服务被发现,只要声明了比较关键的字段服务就会被发现。
raop中比较关键的字段有ch, cn,et, sr,ss, tp等,各字段的具体含义及取值在下面的表格中已写明。
airplay中比较关键的字段有devicesid,features等,各个字段具体含义及取值在下面的表格中。


mDNS查询包

图2 mDNS查询包

The name is formed using the MAC address of the device and the name of the remote speaker which will be shown by the clients.
The following fields appear in the TXT record:

name value description
txtvers 1 TXT record version 1
ch 2 audio channels: stereo
cn 0,1,2,3 audio codecs
et 0,3,5 supported encryption types
md 0,1,2 supported metadata types
pw false does the speaker require a password?
sr 44100 audio sample rate: 44100 Hz
ss 16 audio sample size: 16-bit
tp UDP supported transport: TCP or UDP
vs 220.68 server version 220.68
am AppleTV2,1 device model
fp 0xA7FFFF7 supported features

Audio codecs

cn description
0 PCM
1 Apple Lossless (ALAC)
2 AAC
3 AAC ELD (Enhanced Low Delay)

Encryption Types

et description
0 no encryption
1 RSA (AirPort Express)
3 FairPlay
4 MFiSAP (3rd-party devices)
5 FairPlay SAPv2.5

Metadata Types

md description
0 text
1 artwork
2 progress

The following fields are available in the TXT record:

name value description
model AppleTV2,1 device model
deviceid 58:55:CA:1A:E2:88 MAC address of the device
features 0x39f7 bitfield of supported features
pw 1 server is password protected

The pw field appears only if the AirPlay server is password protected. Otherwise it is not included in the TXT record.
The features bitfield allows the following features to be defined:

bit name description
0 Video video supported
1 Photo photo supported
2 VideoFairPlay video protected with FairPlay DRM
3 VideoVolumeControl volume control supported for videos
4 VideoHTTPLiveStreams http live streaming supported
5 Slideshow slideshow supported
7 Screen mirroring supported
8 ScreenRotate screen rotation supported
9 Audio audio supported
11 AudioRedundant audio packet redundancy supported
12 FPSAPv2pt5_AES_GCM FairPlay secure auth supported
13 PhotoCaching photo preloading supported

2.2 设备连接及信令处理

当iPhone手机端发现接收端以后就会通过TCP连接上来,然后会通过RTSP协议进行交互,不过经过苹果的数次升级和变更,现在虽然表面上还是基于RTSP协议,不过已经和标准的RTSP协议区别很大了,如果想学习标准的RTSP协议,有个非常好的开源项目live555值得参考。接下来重点来了,我们的接收端需要处理来自手机端发来的各种请求命令,并且做出正确的响应。

2.2.1 Mirror基本流程

流程简图如下:
1.| <————–fp-setup————> |
| |
2.| <————–fp-setup————> |
| |
| |
3.| <————–SETUP—————> |
| |
4.| <————–GET /info———–> |
| |
5.| <————–GET_PARAMMETER——> |
| | 6 | <————–SET_PARAMMETER——> |
| |
7 | <————–POST /feedback——> | (every 2 seconds)
streaming data to port 7100 128bytes headers | H264 DATA | 128bytes headers | H264 DATA | …
伪代码大致如下:

if (!strcmp(method,"POST") && !strcmp(uri,"/pair-setup")) {
res = request_handle_pairsetup(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method,"POST") && !strcmp(uri,"/pair-verify")) {
res = request_handle_pairverify(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "POST") && !strcmp(uri,"/fp-setup")) {
res = request_handle_fpsetup(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "POST") && !strcmp(uri, "/auth-setup")) {
res = request_handle_authsetup(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "GET") && !strcmp(uri, "/info")) {
res = request_handle_info(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "GET") && !strcmp(uri, "/stream.xml")) {
res = request_handle_streamxml(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "OPTIONS")) {
res = request_handle_options(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "ANNOUNCE")) {
res = request_handle_announce(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "SETUP")) {
res = request_handle_setup(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "SET_PARAMETER")) {
res = request_handle_setparameter(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "FLUSH")) {
res = request_handle_flush(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "TEARDOWN")) {
res = request_handle_teardown(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "RECORD")) {
res = request_handle_record(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method, "GET_PARAMETER")) {
res = request_handle_getparameter(conn, request, res, &responseData, &responseDataLen);
} else if (!strcmp(method,"POST") && !strcmp(uri,"/feedback")) {
res = request_handle_feedback(conn, request, res, &responseData, &responseDataLen);
}

其中有些命令是为了兼容老版本协议而存在的。

2.2.2 Mirror交互流程

下面以一次完整的镜像流程抓包分析相关的交互流程。
大致交互流程如下:
一. POST /pair-setup POST /pair-verify等命令用于配对验证,其中会用到ed25519,SHA-512,AES等算法。

PS. 目前这一步相关的加密流程比较复杂,还有找到破解的方法,但是在发布Airplay服务时候可以设置特定的features跳过这一步的验证流程。

二. POST /fp-setup命令是FairPlay相关,FairPlay是苹果公司开发的一种DRM(数字版权管理)技术,苹果的视频和音频传输都在这种技术的保护之下被AES加密后传输,这个FairPlay技术也是整个AirPlay中最难的部分,真的很难。

三. 第一个SETUP命令,手机端会传输大量的参数给到接收端,其中最重要的两个参数是ekey和eiv,它们经过一些变换之后要用于AES解密的。要重点说的是参数是用plist格式保存的,与标准的RTSP协议完全不同。

四. GET /info命令 手机端会通过此命令获取接收端的一些参数,比如接收端能接受的音频格式,能播放的视频长和宽,分辨率等。

五. GET_PARAMETER和SET_PARAMETER命令用于调整音量大小,比较简单。

六. RECORD命令好像没什么具体要做的,因为重要的事情都在SETUP命令里面做了。

七. 第二个SETUP命令,手机端通过这个命令通知接收端,准备建立屏幕镜像传输通道,命令中的type参数等于110,代表是屏幕镜像。接收端会在应答报文中将自己的接收端口告诉手机端,也就是dataPort这个参数,默认一般是7100。手机端会通过TCP连接上这个端口,然后将屏幕镜像通过h264编码以后再通过AES加密处理后通过这个端口源源不断的传给接收端,而具体的承载协议是苹果自定义的。

八. POST /feedback命令 每秒钟手机端都会向接收端发一次这个命令,我理解为定时保活的意思。

每次推送音频及视频的时候会有一个二次SETUP的交互,音频和视频的交互内容稍有不同,主要是端口、数据类型及数据格式等的交互。

Client相关信令的相关内容主要通过plist这种格式,这是苹果专门的一种xml形式的内容交换格式,目前java端可以通过dd-plist.jar完成数据解析。

完整的Mirror交互抓包如下:

POST /pair-setup RTSP/1.0
Content-Length: 32
Content-Type: application/octet-stream
CSeq: 0
DACP-ID: E28CCF9054EDE3B9
Active-Remote: 3016615115
User-Agent: AirPlay/320.20

1t<..*….i1”m,.. g.|h.Y…t..M.RTSP/1.0 200 OK
Date: Thu, 19 Oct 2017 02:16:35 GMT
CSeq: 0
Content-Type: application/octet-stream
Server: AirTunes/220.68
Content-Length: 32

8.5…p.*…..A.%.Yn…..w
…$.

POST /pair-verify RTSP/1.0
X-Apple-PD: 1
X-Apple-AbsoluteTime: 530072180
Content-Length: 68
Content-Type: application/octet-stream
CSeq: 1
DACP-ID: E28CCF9054EDE3B9
Active-Remote: 3016615115
User-Agent: AirPlay/320.20

…………..k……)oWL..t.%s..(.b1t<..*….i1”m,.. g.|h.Y…t..M.RTSP/1.0 200 OK
Date: Thu, 19 Oct 2017 02:16:36 GMT
CSeq: 1
Content-Type: application/octet-stream
Server: AirTunes/220.68
Content-Length: 96

.^{ …O. j..Xa+..y….+.^.!…p…nF.!.g*.W….. .U…L…b[bz.tf………sm..”…..,A.=….g..

POST /pair-verify RTSP/1.0
X-Apple-PD: 1
X-Apple-AbsoluteTime: 530072180
Content-Length: 68
Content-Type: application/octet-stream
CSeq: 2
DACP-ID: E28CCF9054EDE3B9
Active-Remote: 3016615115
User-Agent: AirPlay/320.20

……H.u6.Fq`.(V?..+6.4.7…^..&…..K.L………….T1Wv7I.*ke.m..RTSP/1.0 200 OK
Date: Thu, 19 Oct 2017 02:16:36 GMT
CSeq: 2
Content-Type: application/octet-stream
Server: AirTunes/220.68
Content-Length: 0

POST /fp-setup RTSP/1.0
X-Apple-ET: 32
Content-Length: 16
Content-Type: application/octet-stream
CSeq: 3
DACP-ID: E28CCF9054EDE3B9
Active-Remote: 3016615115
User-Agent: AirPlay/320.20

FPLY…………RTSP/1.0 200 OK
Date: Thu, 19 Oct 2017 02:16:36 GMT
CSeq: 3
Content-Type: application/octet-stream
X-Apple-ET: 32
Server: AirTunes/220.68
Content-Length: 142

FPLY………..2.W..RO…z.d.{.D$…~.
.z..]..’0.Y….:.M…….M…{V…….C….e.N.9.[..d..]..>.j.~.V.+..@Bu.ZD.Y.rV…Q8…’r..W.P.*.Fh.

POST /fp-setup RTSP/1.0
X-Apple-ET: 32
Content-Length: 164
Content-Type: application/octet-stream
CSeq: 4
DACP-ID: E28CCF9054EDE3B9
Active-Remote: 3016615115
User-Agent: AirPlay/320.20

FPLY………….p..gO.;……%jXB…..*…..T….x….#.E.^.]|…..s….
j……….q..pT….y.Gq<..r.
.+..ZW.._..Z……H…>…k’..k..4…M;r..XU.vc…z,…..O.RTSP/1.0 200 OK
Date: Thu, 19 Oct 2017 02:16:36 GMT
CSeq: 4
Content-Type: application/octet-stream
X-Apple-ET: 32
Server: AirTunes/220.68
Content-Length: 32

FPLY……….XU.vc…z,…..O.

SETUP rtsp://192.168.123.47/6108990559123033009 RTSP/1.0
Content-Length: 425
Content-Type: application/x-apple-binary-plist
CSeq: 5
DACP-ID: E28CCF9054EDE3B9
Active-Remote: 3016615115
User-Agent: AirPlay/320.20

bplist00……….
..
………RetTname]sourceVersionZtimingPortXdeviceIDUmodelZmacAddress^osBuildVersion[sessionUUIDTekeySeiv. ..GrandStream-6plusV320.20..&..D8:BB:2C:1F:28:94YiPhone7,1_..D8:BB:2C:1F:28:92U14G60_.$54C77E8B-F4BE-4BB1-A9D7-D246D6672D8BO.HFPLY…….<….?z.(…K`…..}…..’……W…B…6j..w.{^..d…,…!.O..Ds*..i#6…D.T.!…..”.’.5.@.I.O.Z.i.u.z.~……………….H………………………….[

二进制的plist转换成xml格式如下:

<?xml version="1.0" encoding="UTF-8"?>
                                                <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
                                                <plist version="1.0">
                                                <dict>
                                                   <key>et</key>
                                                   <integer>32</integer>
                                                   <key>name</key>
                                                   <string>GrandStream-6plus</string>
                                                   <key>sourceVersion</key>
                                                   <string>320.20</string>
                                                   <key>timingPort</key>
                                                   <integer>51149</integer>
                                                   <key>deviceID</key>
                                                   <string>D8:BB:2C:1F:28:94</string>
                                                   <key>model</key>
                                                   <string>iPhone7,1</string>
                                                   <key>macAddress</key>
                                                   <string>D8:BB:2C:1F:28:92</string>
                                                   <key>osBuildVersion</key>
                                                   <string>14G60</string>
                                                   <key>sessionUUID</key>
                                                   <string>0DBDA1BF-5083-46E7-9F25-37143854A861</string>
                                                   <key>ekey</key>
                                                   <data>
                                                      RlBMWQECAQAAAAA8AAAAAF5xvfwQNcVLHM2KurVtQCMAAAAQC8/GGZ7i56Hu5wMkHYQ0Bh/SImp90UX8SzQltjNHayrNtwmX
                                                   </data>
                                                   <key>eiv</key>
                                                   <data>
                                                      SdqQ7TXGEvz+R80Xi4IoaA==
                                                   </data>
                                                </dict>
                                                </plist>

关键字段已通过红色字体标出,et,加密类型;timingPort, NTP同步端口, ekey/eiv主要用AES解密,用于后期视频帧的解密处理。

RTSP/1.0 200 OK
Date: Thu, 19 Oct 2017 02:16:37 GMT
CSeq: 5
Server: AirTunes/220.68
Content-Length: 284

3. 关键代码实现

目前能跑通的流程为发现流程、连接流程,相关代码实现如下,音视频数据解密及解码流程还没有仔细去研究。

3.1 服务发现

raop服务发布,相关txt设置如下:

protected ServiceInfo onCreateServiceInfo(byte[] hwAddr, String name, int port) {
                String identifier = getStringHardwareAdress(hwAddr);
                Map<String, String> txt = new HashMap<String, String>();
                txt.put("tp", "UDP"); /*传输方式*/
                txt.put("sm", "false");
                txt.put("sv", "false");/*不清楚*/
                txt.put("ek", "0");
                txt.put("et", "0,1"); /*supported encryption types, 0, no encryption; 1,RSA (AirPort Express);3, FairPlay;4,MFiSAP (3rd-party devices); 5,FairPlay SAPv2.5*/
                txt.put("md", "0,1,2"); /*metadata: text, artwork, progress*/
                txt.put("cn", "0,1");/*audio codecs 0   PCM,1   Apple Lossless (ALAC), 2        AAC, 3  AAC ELD (Enhanced Low Delay) */
                txt.put("sr", "44100");/*audio sample rate: 44100 Hz*/
                txt.put("ch", "2"); /*audio channels: stereo*/
                txt.put("ss", "16"); /*audio sample size: 16-bit*/
                txt.put("pw", "false");         /*does the speaker require a password?*/
                txt.put("vn", "3");  
                txt.put("txtvers", "1");  /*TXT record version 1*/
                txt.put("vs", AirPlay.SRCVERS);  /*server version 130.14*/
                txt.put("am",AirPlay.MODEL);  /*device model*/
                //txt.put("pk", AirTunes.PKSTR);  还不知道这个字段的用处
                //txt.put("da", "true");还不知道怎么用
                //txt.put("ft", AirPlay.FEATURES);  support function
                txt.put("w", "1");  //unknown
                //txt.put("vn", "65537");  //unknown
                //txt.put("sf", "0x4");  //unknown

                return ServiceInfo
        .create(AirTunes.AIR_TUNES_SERVICE_TYPE,
                        identifier + "@" + name ,
                port, 0, 0, txt);
        }

airplay服务发布:

protected ServiceInfo onCreateServiceInfo(byte[] hwAddr, String name, int port) {
                String addr = getStringHardwareAdress(hwAddr);
                Map<String, String> txt = new HashMap<String, String>();
                txt.put("deviceid", addr);
                txt.put("features", String.format("0x%04x", AirPlay.FEATURES));
                txt.put("model", AirPlay.MODEL);
                txt.put("srcvers", AirPlay.SRCVERS);
                txt.put("vv", "1");
                //txt.put("rhd", "1.9.7");
                txt.put("flags", "0x44");
                txt.put("pi", AirPlay.PISTR);
                txt.put("pk", AirPlay.PKSTR);
                return ServiceInfo.create(AirPlay.TYPE, name, port, 0, 0, txt);
        }

3.2 RTSP信令解析

信令相关的代码太多,只截取SETUP命令的处理

if(contentType.equals("application/x-apple-binary-plist")){
    try {
        NSDictionary dict = (NSDictionary) BinaryPropertyListParser.parse(packet.getContent());
        Log.d(TAG,"[SETUP], dict  is "+dict.toXMLPropertyList());
        //process ekey && eiv
        NSData ekeyData = (NSData)dict.get("ekey");
        NSData eivData = (NSData)dict.get("eiv");
        if(ekeyData != null && eivData != null){
        response.append("Audio-Jack-Status", "connected; type=analog");
        String ekey =  (ekeyData).getBase64EncodedData();
        String eiv = (eivData).getBase64EncodedData();
        mAudioSession = AudioSession.parse(ekey, eiv, mKey);
        NSNumber timingportObj = (NSNumber)dict.get("timingPort");
        Log.d(TAG,"[SETUP], timingportObj  is "+timingportObj.toString());
        int  timingPort =timingportObj.intValue();
        // Address
        Pattern p = Pattern.compile("SETUP\\s(rtsp://.*?)\\s");
        Matcher m = p.matcher(packet.getRawPacket());
        if ( m.find() && mAudioSession != null) {
            String uri = m.group(1);
            Log.d(TAG, "[SETUP] uri is "+uri+", timingPort is "+timingPort);
            InetAddress iaddr = InetAddress.getByName(Uri.parse(uri).getHost());
            // Creaet Audio Server
            mAudioServer = AudioServer.create(mAudioSession, iaddr, 0, timingPort);
            }
         if(mAudioServer != null ){
             NSDictionary reponsedict = new NSDictionary();
             Log.d(TAG,"local event Port is "+mAudioServer.getServerPort()+
             ",  time port is "+mAudioServer.getTimingPort());
             reponsedict.put("eventPort",  AirPlay.PORT);
             reponsedict.put("timingPort", mAudioServer.getTimingPort());
             Log.d(TAG,"[SETUP]response content is"+reponsedict.toXMLPropertyList());
             byte[] contents = BinaryPropertyListWriter.writeToArray(reponsedict);
             response.append("Content-Length", contents.length+"");
             response.setContent(contents, 0, contents.length);
          }
     }else {
           //second SETUP
            NSDictionary reponsedict = new NSDictionary();
            NSDictionary mirrorDic = new NSDictionary();
            mirrorDic.put("dataPort",  7100);
            mirrorDic.put("type", 110);
            reponsedict.put("streams", NSArray.wrap(new NSObject[]{mirrorDic}));
            Log.d(TAG,"[SETUP]response content is "+reponsedict.toXMLPropertyList());
            byte[] contents = BinaryPropertyListWriter.writeToArray(reponsedict);
            //byte[] contents = reponsedict.toXMLPropertyList().getBytes();
            response.append("Content-Length", contents.length+"");
            response.finalizeHeader();
            response.setContent(contents, 0, contents.length);
          }

   } catch (IOException e) {
                        // TODO Auto-generated catch block
                           e.printStackTrace();
                         } catch (PropertyListFormatException e) {
                             // TODO Auto-generated catch block
                             e.printStackTrace();
                         } catch (Exception e){
                           e.printStackTrace();
       }
}

4. 总结与展望

目前实现了设备发现、设备连接功能。但是相关音频及视频解码流程还没有跑通,涉及的内容比较多,能力及精力有限,还没能进一步去研究相关实现及解密解码流程。在前面的步骤中虽然实现了发现和连接,但是有很多字段的含义及用途不清楚,网上能找到的一份协议并非官方并且是比较老的版本,12年以前的,很多流程都已经变更了,参考意义有限。这些字段对后期音视频解码是否影响暂不清楚。后期如果需要进步研究的话,首先需要理清楚前面协议的字段与后面音视频流参数之间的关系,其次,需要搞清楚后期发送的数据包相关头部字段的信息及数据加密类型。最后,最关键的是能够找到相关的解密方法及解码器。如果这些问题解决掉了,最终能够实现镜像功能。

5. 参考文档

[1] Unofficial AirPlay Protocol Specification——http://blog.csdn.net/cyshuxin/article/details/39694111
[2] AirPlay协议浅析——http://phonegap.me/post/42.html
[3] 关于airplay协议实现镜像功能研究——http://blog.csdn.net/bxjie/article/details/39581565
[4] ios 9.3 POST /pair-setup,POST /pair-verify ——https://github.com/juhovh/shairplay/issues/61




尝试过的 github代码参照于:https://github.com/jamesdlow/open-airplay

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

推荐阅读更多精彩内容

  • 手游直播是直播行业中非常重要的一个垂直领域. 手游直播与其他移动直播相比主要是画面的来源不同, 手游直播其实是一种...
    金山视频云阅读 12,477评论 13 18
  • RFC 2326RTSP Spec中文版(1-11)RTSP Spec中文版(12-16)RTSP Spec中文版...
    SniperPan阅读 5,487评论 3 10
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • RTSP SDP RTP/RTCP 介绍应用层 RTSP、SDP; 传输层 RTP、TCP、UDP; 网络层 IP...
    Atom_Woo阅读 3,801评论 0 7
  • 一个想法 对历史有些了解的同学,应该都知道《海国图志》这本书,当时中国闭关锁国,社会、科技和经济发展严重滞后于其他...
    _qisen阅读 2,585评论 8 11