iPhone设备发现屏幕镜像设备依靠的是mdns协议,这是一个用于局域网发现设备的协议,仿照dns协议,镜像设备启动后,会注册自己到路由器的组播地址224.0.0.251,当iPhone设备发起搜索协议的时候,会发送搜索的信息到224.0.0.251,这时路由器会转发信息到所有曾经注册到224.0.0.251的镜像设备
参考:https://www.jianshu.com/p/f35ff6e36777
._airplay._tcp.local airPlayPort 7000
._raop._tcp.local airTunesPort 49152
字典信息
- "deviceid" -> "58:55:CA:1A:E2:88"
- "features" -> "0x5A7FFEE6""
- "flags" -> "0x4"
- "model" -> "AppleTV3,1"
- "pk" -> "b07727d6f6cd6e08b58ede525ec3cdeaa252ad9f683feb212ef8a205246554e7"
- "pi" -> "2e388006-13ba-4041-9a67-25dd4a43d536"
- "rhd" -> "5.6.0.0"
- "pw" -> "false"
- "srcvers" -> "220.68"
- "vv" -> "2"
iPhone收到上面的信息之后,还会再次查询txt信息,依然把上面的字典信息返回,这时iPhone上面会看到新的镜像设备
My AirPlay Device
参考:https://www.jianshu.com/p/ae7eb3fba1e9
iPhone没有发送什么信息过来,只有一个请求,这是屏幕镜像设备需要准备比较多的数据,形成一个字典,字典里面可能包含信息对,也可能包含字典,还可以包含字典,并把数据保存为plist二进制形式发送给手机,例如根字典数据如下
字典数据参考
- NSDictionary r_node = new NSDictionary();
- r_node["txtAirPlay"] = new NSData(AirPlayServer_mdns.bytesProperties);
- r_node["features"] =new NSNumber((UInt64)0x1E << 32 | 0x5A7FFFF7);
- r_node["audioFormats"] = audio_formats_node;
- r_node["pi"] = new NSString("2e388006-13ba-4041-9a67-25dd4a43d536");
- r_node["vv"] = new NSNumber(2);
- r_node["statusFlags"] = new NSNumber(68);
- r_node["keepAliveLowPower"] = new NSNumber(1);
- r_node["sourceVersion"] = new NSString("220.68");
- r_node["pk"] = new NSData(HexStringToBytes("b07727d6f6cd6e08b58ede525ec3cdeaa252ad9f683feb212ef8a205246554e7"));
- r_node["keepAliveSendStatsAsBody"] = new NSNumber(1);
- r_node["deviceID"] = new NSString("58:55:CA:1A:E2:88");
- r_node["name"] = new NSString("My AirPlay Device");
- r_node["model"] = new NSString("AppleTV2,1");
- r_node["macAddress"] = new NSString("58:55:CA:1A:E2:88");
-
- NSArray audio_formats_node = new NSArray();
-
- NSDictionary audio_format_0_node = new NSDictionary();
- audio_format_0_node["type"] = new NSNumber(100);
- ...
- audio_formats_node.Add(audio_format_0_node);
-
- NSDictionary audio_format_1_node = new NSDictionary();
- audio_format_1_node["type"] = new NSNumber(101);
- ...
- audio_formats_node.Add(audio_format_1_node);
-
-
-
- NSArray audio_latencies_node = new NSArray();
- NSDictionary audio_latencies_0_node = new NSDictionary();
- audio_latencies_0_node["outputLatencyMicros"] = new NSNumber(0);
- ...
- audio_latencies_node.Add(audio_latencies_0_node);
-
- NSDictionary audio_latencies_1_node = new NSDictionary();
- audio_latencies_1_node["outputLatencyMicros"] = new NSNumber(0);
- ...
- audio_latencies_node.Add(audio_latencies_1_node);
- r_node["audioLatencies"] = audio_latencies_1_node;
-
-
- NSArray displays_node = new NSArray();
- NSDictionary displays_0_node = new NSDictionary();
- displays_0_node["uuid"] = new NSString("e0ff8a27-6738-3d56-8a16-cc53aacee925");
- displays_0_node["widthPhysical"] = new NSNumber(0);
- displays_0_node["heightPhysical"] = new NSNumber(0);
- ...
- displays_node.Add(displays_0_node);
-
- r_node["displays"] = displays_node;
- GET /info RTSP/1.0
- X-Apple-ProtocolVersion: 1
- Content-Type: application/x-apple-binary-plist
- CSeq: 0
- DACP-ID: DBA1F21D1459CFDD
- Active-Remote: 1345566021
- User-Agent: AirPlay/665.13.1
- content-length: 70
- RTSP/1.0 200 OK
- CSeq: 0
- content-length: 689
该请求,iPhone没有携带重要的信息,镜像设备发送一个ed25519的public key到iPhone,发送内容作为RTSP的Body部分,该ed25519秘钥对可以在使用的时候才生成
- POST /pair-setup RTSP/1.0
- Content-Type: application/octet-stream
- CSeq: 1
- DACP-ID: DBA1F21D1459CFDD
- Active-Remote: 1345566021
- User-Agent: AirPlay/665.13.1
- content-length: 32
- RTSP/1.0 200 OK
- CSeq: 1
- content-length: 32
这两次请求是非常关键的,首先iPhone发送了自己的加密信息中的公钥部分,也包含签名需要的信息,然后镜像设备进行了签名,并把签名结果返回给iPhone,如果iPhone验证了签名成功,则把再次签名的结果发送给镜像设备来验证,如果镜像设备验证成功,说明双方都得到了对方身份已确认,稍微详细点的信息可以看散列与加密算法的几处实际应用场景中的
场景4:AirPlay协议
- POST /pair-verify RTSP/1.0
- X-Apple-PD: 1
- X-Apple-AbsoluteTime: 721822232
- Content-Type: application/octet-stream
- CSeq: 2
- DACP-ID: DBA1F21D1459CFDD
- Active-Remote: 1345566021
- User-Agent: AirPlay/665.13.1
- content-length: 68
- RTSP/1.0 200 OK
- CSeq: 2
- content-length: 96
- POST /pair-verify RTSP/1.0
- X-Apple-PD: 1
- X-Apple-AbsoluteTime: 721822233
- Content-Type: application/octet-stream
- CSeq: 3
- DACP-ID: DBA1F21D1459CFDD
- Active-Remote: 1345566021
- User-Agent: AirPlay/665.13.1
- content-length: 68
两次请求,body部分都带有数据,分别调用fairplay函数的setup和handshake,返回这两个函数的返回值即可
- POST /fp-setup RTSP/1.0
- X-Apple-ET: 32
- Content-Type: application/octet-stream
- CSeq: 4
- DACP-ID: DBA1F21D1459CFDD
- Active-Remote: 1345566021
- User-Agent: AirPlay/665.13.1
- content-length: 16
- RTSP/1.0 200 OK
- CSeq: 5
- content-length: 32
iPhone请求第二次,iPhone发给镜像设备key,该key经过步骤5和6初始化之后的fairplay解码成一个aes加密算的aeskey,未来传输的视频编码会用aeskey可以来加密,镜像设备准备好事件反馈端口和时间对齐端口发送给iPhone
- SETUP rtsp://172.16.0.105/14027482186540797578 RTSP/1.0
- Content-Type: application/x-apple-binary-plist
- CSeq: 6
- DACP-ID: DBA1F21D1459CFDD
- Active-Remote: 1345566021
- User-Agent: AirPlay/665.13.1
- content-length: 656
- RTSP/1.0 200 OK
- CSeq: 6
- content-length: 0
iPHone查询镜像设备的一些信息,目前在body播放只有如下内容
"volume\r\n"
, 镜像设备可以返回给iPhone的body部分为"volume:0.0\r\n"
- GET_PARAMETER rtsp://172.16.0.105/14027482186540797578 RTSP/1.0
- Content-Type: text/parameters
- CSeq: 8
- DACP-ID: DBA1F21D1459CFDD
- Active-Remote: 1345566021
- User-Agent: AirPlay/665.13.1
- content-length: 8
- RTSP/1.0 200 OK
- CSeq: 8
- content-length: 18
iPhone发送Record命令,镜像设备返回的RTSP Header中增加一条记录,body为空
response.AddHeader("Audio-Latency", request.GetHeader("2205"));
- RECORD rtsp://172.16.0.105/14027482186540797578 RTSP/1.0
- CSeq: 9
- DACP-ID: DBA1F21D1459CFDD
- Active-Remote: 1345566021
- User-Agent: AirPlay/665.13.1
- content-length: 0
- RTSP/1.0 200 OK
- CSeq: 9
- Audio-Latency: 11025
- Audio-Jack-Status: connected; type=analog
- content-length: 0
- SETUP rtsp://172.16.0.105/14027482186540797578 RTSP/1.0
- Content-Type: application/x-apple-binary-plist
- CSeq: 10
- DACP-ID: DBA1F21D1459CFDD
- Active-Remote: 1345566021
- User-Agent: AirPlay/665.13.1
- content-length: 204
- RTSP/1.0 200 OK
- CSeq: 6
- content-length: 0
iPhone发送音量或者进度条信息,可以不用处理,返回RTSP 200
iPhone会不间断发送/feedback,里面包含时间信息,可以不用处理,返回RTSP 200
这时,在步骤11中准备的新tcp服务器,可以开始收到经过步骤7中的提供的秘钥进行aes加密的视频数据了。
音频数据依然会通过roap.tcp.local来传输,所以现在收到的数据不包含音频数据。
- RTSP/1.0 200 OK
- CSeq: 6
- content-length: 0
音频数据依然会通过roap.tcp.local来传输