相关资料
Unofficial AirPlay Protocol Specification
AirReceiver 使用Java写的运行在PC端的AirPlay接收端
DroidAirPlay 基于AirReceiver代码转换成安卓项目的实现代码
AndroidAirPlayReceiver基于DroidAirPlay改造后能直接使用的安卓工程
https://www.ietf.org/rfc/rfc2326.txt
相关技术
服务发现:
mDns
ssdp
流程
转自知乎大佬 刘同学被占用了
https://zhuanlan.zhihu.com/p/34324606
Airplay Mirroring客户端的同屏交互过程,分为三个主要步骤:
1, 设备广播与发现
2, 信息交互与能力协商
3, 音视频数据接收与解扰
设备广播与发现:
Airplay设备间的广播与发现通过Bonjour协议进行。Bonjour也被称为ZeroConf, mDNS等,可以用来在局域网内进行数据记录广播与发现。该协议比较成熟,网上可以找到诸多介绍。对于实现的Airplay(包括Mirroring)接收端而言,首先需要注册两类服务,即airtunes和airplay。 Airtunes服务主要用来处理广播视音频接收能力协商,是最为重要的服务内容,对应Bonjour记录名称为’_raop._tcp’,注册服务端口不限,一般为了避免冲突,建议采用较高的端口数;Airplay服务主要用来兼容传统的streaming等服务,对应记录名称为’_airplay._tcp’,注册端口一般为7000。
具体的服务广播内容,可以进行局域网抓包,找到对应记录内容。
当接收端通过Bonjour广播器服务能力后,发送端(如iPhone等各类iOS设备)就可以发现该接收端。
信息交互与能力协商:
当发送端发现接收端后,可以开始信息交互与能力协商过程。该部分协议协议格式类似rtsp协议格式。主要分为两个阶段,设备匹配与和能力协商。
当发送端链接服务端后,设备匹配过程即开始。通信双方会进行fairplay加密协议进行信息交换,当完成信息交换后,客户端后续必须使用这部分信息来处理加密过的密钥,才能获得进一步视音频解密密钥。在iOS9之后,在fairplay过程之前,增加一个设备匹配过程,即pair-setup、pair-verify过程,其主要算法是较为标准的非对称公钥交换算法。
当两端成功匹配后,开始进行能力协商与信息交换,这些信息包括,设备名称、代号,音视频接收相关端口配置,视频接收能力以及加密密钥等,相关信息使用binary plist格式进行封装。
可以参考https://github.com/espes/Slave-in-the-Magic-Mirror找到相关协议交互的一些细节。
音视频数据接收与解密
双方协商成功后,发送端开始向接收端发送视音频数据,mirroring数据是通过TCP进行发送,为h.264 ES流格式。音频是通过RTP协议进行发送,根据内容的不同音频编码为ALAC或者AAC-ELD。
音视频流都是通过AES进行了加密处理,密钥需要通过上面一步的进过信息交互后的fairplay模组对setup过程中接收到的加密密钥进行解密,获得的AES解密需要的IV和KEY,然后经过AES解扰,即可以获得最终的视音频清流。
其他需要注意的地方:
Airplay没过Session传送过来的视频h264码流,只有开头一个关键帧. 因此这种情况并不适合直播这种需要固定GOP的场景. 还需要做进一步的转码的工作,或者直接在压缩域进行处理,获得合理的GOP结构。
广播发现
协议交互
/info
request:
response:
<?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>pk</key> <data> XYMxJlYMsZoUGTcneJbw/UN7poAeshCsTDnZAHLXDag= </data> <key>name</key> <string>Apple TV</string> <key>vv</key> <integer>2</integer> <key>statusFlags</key> <integer>68</integer> <key>keepAliveLowPower</key> <integer>1</integer> <key>keepAliveSendStatsAsBody</key> <integer>1</integer> <key>pi</key> <string>b08f5a79-db29-4384-b456-a4784d9e6055</string> <key>sourceVersion</key> <string>220.68</string> <key>deviceID</key> <string>00:50:56:C0:00:01</string> <key>macAddress</key> <string>00:50:56:C0:00:01</string> <key>model</key> <string>AppleTV3,2</string> <key>audioFormats</key> <array> <dict> <key>audioInputFormats</key> <integer>67108860</integer> <key>audioOutputFormats</key> <integer>67108860</integer> <key>type</key> <integer>100</integer> </dict> <dict> <key>audioInputFormats</key> <integer>67108860</integer> <key>audioOutputFormats</key> <integer>67108860</integer> <key>type</key> <integer>101</integer> </dict> </array> <key>audioLatencies</key> <array> <dict> <key>audioType</key> <string>default</string> <key>inputLatencyMicros</key> <false/> <key>outputLatencyMicros</key> <false/> <key>type</key> <integer>100</integer> </dict> <dict> <key>audioType</key> <string>default</string> <key>inputLatencyMicros</key> <false/> <key>outputLatencyMicros</key> <false/> <key>type</key> <integer>101</integer> </dict> </array> <key>features</key> <integer>130367356918</integer> <key>displays</key> <array> <dict> <key>height</key> <integer>1080</integer> <key>width</key> <integer>1920</integer> <key>rotation</key> <false/> <key>widthPhysical</key> <false/> <key>heightPhysical</key> <false/> <key>widthPixels</key> <integer>1920</integer> <key>heightPixels</key> <integer>1080</integer> <key>refreshRate</key> <real>0.016667</real> <key>maxFPS</key> <integer>60</integer> <key>features</key> <integer>14</integer> <key>overscanned</key> <false/> <key>uuid</key> <string>e5f7a68d-7b0f-4305-984b-974f677a150b</string> </dict> </array> </dict> </plist>
SETUP
response:
<?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>timingPort</key> <integer>6000</integer> <key>eventPort</key> <integer>47010</integer> <key>streams</key> <array> <dict> <key>type</key> <integer>96</integer> <key>controlPort</key> <integer>6001</integer> <key>dataPort</key> <integer>6003</integer> </dict> </array> </dict> </plist> <?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>eiv</key> <data> aAW66U8aj8bSvSFEJYVr1w== </data> <key>timingProtocol</key> <string>NTP</string> <key>sessionUUID</key> <string>F2F647DB-1E87-4AA6-B2AF-AA6CE0BF9D3F</string> <key>osName</key> <string>iPhone OS</string> <key>osBuildVersion</key> <string>17F80</string> <key>sourceVersion</key> <string>420.45</string> <key>timingPort</key> <integer>53648</integer> <key>isScreenMirroringSession</key> <true/> <key>osVersion</key> <string>13.5.1</string> <key>ekey</key> <data> RlBMWQECAQAAAAA8AAAAACbSNQTFG57dB11iWsNtMj8AAAAQvJ5sT/YIec4lRLGGRXsi HZ0UBENC+6P6Vco8NOvFLRsXN1Qi </data> <key>deviceID</key> <string>F8:95:EA:78:14:F0</string> <key>model</key> <string>iPhone10,3</string> <key>name</key> <string>姝︽眽鐨勬祴璇曟満iPhoneX 2</string> <key>macAddress</key> <string>F8:95:EA:83:87:59</string> </dict> </plist> <?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>timingPort</key> <integer>0</integer> <key>eventPort</key> <integer>46001</integer> <key>streams</key> <array> <dict> <key>type</key> <integer>110</integer> <key>dataPort</key> <integer>46000</integer> </dict> </array> </dict> </plist>