AirPlay 镜像协议-上(发现)
可以用来运行和调试的源码
目标
当点击iPhone上的屏幕镜像设备后,屏幕镜像设备是如何收到镜像图像的
在iPhone上点击屏幕镜像之后,iPhone发起了13次请求,每次请求都是一次rtsp协议,rtsp协议可以看做是一种特别的http协议,只不过http协议请求和返回的第一行常规的HTTP/1.1修改成了RTSP/1.0
这13次请求分别如下
1. /info
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;
上述数据中,r_node["txtAirPlay"]比较特别,它是文章AirPlay 镜像协议-上(发现)中的字典信息,而且格式比较特别,举例如下
假如有配对信息a->b,和cd->ef, 那么数据AirPlayServer_mdns.bytesProperties内容为
{0x3, 'a', '=', 'b', 0x5, 'c', 'd', '=', 'e', 'f'}
,可以看到配对信息用等号=相连,配对信息前面有一字节的信息表明该组信息的长度,所以限制了配对信息长度不可以大于255,
在返回给iPhone的RTSP信息中,HEADER部分增加信息表明返回的是二进制形式的plist文件
response.AddHeader("Content-Type", "application/x-apple-binary-plist");
2. /pair-setup
该请求,iPhone没有携带重要的信息,镜像设备发送一个ed25519的public key到iPhone,发送内容作为RTSP的Body部分,该ed25519秘钥对可以在使用的时候才生成
3&4. /pair-verify
这两次请求是非常关键的,首先iPhone发送了自己的加密信息中的公钥部分,也包含签名需要的信息,然后镜像设备进行了签名,并把签名结果返回给iPhone,如果iPhone验证了签名成功,则把再次签名的结果发送给镜像设备来验证,如果镜像设备验证成功,说明双方都得到了对方身份已确认,稍微详细点的信息可以看散列与加密算法的几处实际应用场景中的场景4:AirPlay协议
5&6. /fp-setup
两次请求,body部分都带有数据,分别调用fairplay函数的setup和handshake,返回这两个函数的返回值即可
7. SETUP
iPhone请求第二次,iPhone发给镜像设备key,该key经过步骤5和6初始化之后的fairplay解码成一个aes加密算的aeskey,未来传输的视频编码会用aeskey可以来加密,镜像设备准备好事件反馈端口和时间对齐端口发送给iPhone
8. GET /info RTSP/1.0
iPhone再次请求和1一样的数据,镜像设备和1返回一样的内容
9. GET_PARAMETER
iPHone查询镜像设备的一些信息,目前在body播放只有如下内容"volume\r\n"
, 镜像设备可以返回给iPhone的body部分为"volume:0.0\r\n"
10. RECORD
iPhone发送Record命令,镜像设备返回的RTSP Header中增加一条记录,body为空
response.AddHeader("Audio-Latency", request.GetHeader("2205"));
11. SETUP
iPhone请求第二次setup,镜像设备准备好新的tcp服务器,准备接收数据,端口任意,并返回给iPhone
12. SET_PARAMETER
iPhone发送音量或者进度条信息,可以不用处理,返回RTSP 200
13. /feedback
iPhone会不间断发送/feedback,里面包含时间信息,可以不用处理,返回RTSP 200
这时,在步骤11中准备的新tcp服务器,可以开始收到经过步骤7中的提供的秘钥进行aes加密的视频数据了。
音频数据依然会通过roap.tcp.local来传输,所以现在收到的数据不包含音频数据。