/****************************************************
AirPlay2协议整合文档
****************************************************/
+-------------------------------------------+
| 文档目录
+-------------------------------------------+
|
| 1-AirPlay2简介
| | 1.1-服务注册与设备发现
| | 1.2-密钥协商与设备连接
| | 1.3-音频工作原理
| | 1.4-镜像工作原理
| | 1.5-苹果数据包承载分析
| 2-AirPlay2工作主流程
| | 2.1-mDNS与DNS_SD
| | 2.2-RTSP请求与响应流程
| | 2.3-ED数字签名ed25519
| | 2.4-DF密钥协商curve25519
| | 2.5-AES分组密码-CTR模式
| | 2.6-镜像线程与h264解密还原
| | 2.7-音频线程与aac_decoder
| 3-BUG
| 4-可能的新方法
| 5-可参考的博客及资料
|
| more
|
+-------------------------------------------+
1-AirPlay2简介
AirPlay2协议为苹果私有协议,其适用于IOS设备与MAC设备,协议中包含的模块有:服务注册、服务发现、HTTP(RTSP)、RTP、数据流解密。
下面对各个模块做相应的介绍,如果要了解具体工作,可直接移步至第2章(AirPlay2工作主流程)。
1.1-服务注册与设备发现
Bonjour协议是苹果公司实现的一种零配置网络协议,在AirPlay2中也包含该协议,Bonjour可以完成在局域网内的设备进行对应服务的发现,在发现所需要的服务以后通过查其PTR,SRV,TXT记录得到服务端的ip和port,用于后续建立通信。
1.2-密钥协商与设备连接
密钥协商过程完成客户端(如iPhone)与服务端(Android设备)的加解密准备工作,此时的密钥还不是完成的密钥,后续的步骤中,会进行相关的操作来生成真正用于加解密的密钥。这个步骤被嵌入到 RTSP连接的第1个 POST /pair-verify请求中。跟密钥相关的另一个请求是第1个 SETUP。具体见第2章讲解。
设备连接(如镜像请求)主要位于RTSP连接的第2个 SETUP请求中。该步骤要求Android端打开数据监听端口、时间同步端口,并把端口response给客户端。
1.3-音频工作原理
RAOP(远程音频控制协议)。在音频连接请求中,其type代号为96。AirPlay2发送的音频包为经过AES加密的aac数据,对于Java中的AudioTrack而言,需要转成pcm格式使用。详情可见第2章(音频线程与aac_decoder)
1.4-镜像工作原理
Mirror。在镜像连接请求中,其type代号为110。AirPlay2发送的镜像视频包为经过AES加密(CTR模式)的h264数据。详情可见第2章(镜像线程与h264解密还原)
1.5-苹果数据包承载分析
以AirPlay2镜像连接为例,Android端设备不断执行select、recv操作。在每次可读循环中,都会先recv一个128字节的数据包,该数据包可参见PPT(音视频数据解析),其含有负载数据大小,负载类型(sps_pps参数集?视频流?心跳包?),NTP时间戳,视频高度与宽度值的地址值。具体的后续操作都要依据负载类型来进行执行。
2-AirPlay2工作主流程
工作主流程为:
(服务端准备工作)
->(客户端寻找服务端)
->(客户端发起请求)
->*(RTSP请求与响应)*
->(连接建立,开始投屏)
->(客户端发送数据、服务端接收数据)
->(退出投屏,连接关闭)
2.1-mDNS与DNS_SD
AirPlay2采用的组播地址为224.0.0.251,端口为5353。服务端利用mDNSResponder这个第三方库(来自Apple)即可将raop,airplay两个服务注册完成,注册后的这两个服务名字结构可参考PPT(设备广播与发现)。另一个需要准备的任务是建立一个http监听线程,用于监听raop服务所属端口。至此,Android服务端准备工作完成。
当iPhone打开服务发现时,会通过组播查询相应的两个服务。并查询PTR,SRV,TXT记录(由Android服务端一次性发送给iPhone)。iPhone可解析得到Android服务端名字,ip,端口。至此,服务发现完成。
这一模块的代码比较标准,因为第三方库是苹果自身提供的,因此暂时没有做修改的必要与打算。
2.2-RTSP请求与响应流程
这一步是工作主流程的重要步骤,也是建立连接及区分服务端后续任务的关键。
在Android服务端中,由于在准备工作中有一个监听来自raop服务所属端口的http请求的线程(称其为http监听线程),因此第一个RTSP请求就是先发送到这个端口中,在被http监听线程监听到fd改变后,该线程会进行accept等动作,建立socket连接。连接建立后,RTSP请求正式一个一个地进入。
依次到来的请求顺序为:
GET /info
-> POST /pair-setup
*-> POST /pair-verify*
-> POST /pair-verify
-> POST /fp-setup
-> POST /fp-setup
-> SETUP
-> GET /info
-> GET_PARAMETER
-> RECORD
-> SET_PARAMETER
*-> SETUP*
-> SET_PARAMETER
-> ...
注意:这个...代表的后续行为是依据后续iPhone用户的不同操作来产生的。例如后续需要播放声音了,就会产生type值为96的SETUP请求。(但是在iPhone一开始连接上Android服务端时,先是这几个请求动作。)
其中各个请求的任务已经在PPT中有所描述,其中较为复杂的是两个加*的请求。具体可见PPT(POST /pair-verify 工作详解)与(SETUP(第二个) 工作详解)。
2.3-ED数字签名ed25519
ed25519是AirPlay2中所用到的数字签名函数,主要在*POST/pair-verify*被使用。其具体可参考PPT(信息交互与能力协商(数字签名))。
2.4-DF密钥协商curve25519
curve25519是AirPlay2中所用到的Diffie-Hellman密钥交换(密钥协商)函数,这是一个椭圆曲线函数,可用于客户端与服务端协商产生一个在后续加解密中需要使用的一个密钥雏形。具体的aeskey会在第一个SETUP中到达,而用于解密h264数据的密钥会在第二个SETUP之后完成,具体可参考PPT(RTSP请求到达顺序)。
2.5-AES分组密码-CTR模式
在镜像传输过程中,h264流被AES分组密码(CTR模式)所加密。该AES分组密码是由多个RTSP请求,逐步求得的。根据对称密码的特点,服务端可以利用这个密码解密数据。具体可见PPT(音视频数据解析(视频流))。
2.6-镜像线程与h264解密还原
在第二个SETUP中,会有一个搜索type值的过程,若type值为110,则为镜像请求;若为96,则为音频请求。若得到镜像请求,则开启镜像端口,并把端口response给客户端(端口会被构造、添加到一个plist的node)。随后,监听相应端口。在该代码中,会监听一个用于TCP连接的data socket,以及一个用于UDP的time socket。在建立TCP连接后,将持续轮询执行select fd,recv data,用以得到 视频数据包。在每个可读的轮询中,先recv一个128字节的数据包,该包中包含后续要recv的视频数据包的相关信息。详情可见PPT(音视频数据分析)。
2.7-音频线程与aac_decoder
在第二个SETUP中,会有一个搜索type值的过程,若type值为110,则为镜像请求;若为96,则为音频请求。若得到音频请求,则开始raop端口的监听,后续操作与2.6步骤差不多,此处不再赘述。音频数据后续利用aac_decoder解码,得到pcm数据后,送入AudioTrack进行播放。
3-BUG
因技术有限,目前仍存在如下BUG:
1、延迟显示问题(由于抓到的流能进行正常的重新播放,故基本不存在流数据破损导致画面延时的可能,猜测是MediaCodec解码具有缓存引起的,可能需要进行秒开优化或者丢帧处理)
2、音视频同步问题(由于现在并没有集成到播放器那个维度,故收到的音频数据与视频数据是各自独立播放的,在C层代码中有为音视频同步做准备的代码,但目前看来,不起效果。可能是因为没有正确组织代码逻辑引起的,预言:将分离的音频和视频送入IJK播放器可能可以进行同步)
3、关闭服务时的异常(在部分机器上,关闭服务时会导致APP闪退,问题是线程及部分socket连接未正确关闭,由于之前主要关心第1个问题,故尚未处理线程与socket连接的关闭问题。)
4-可能的新办法
见PPT最后,集成IJK播放器,实现秒开。
充分利用播放器的能力,实现音视频同步。
5-可参考的博客及资料
+-----------------------------------------------------------------+
- https://nto.github.io/AirPlay.html
-- AirPlay非官方的文档,非AirPlay2,有一定参考意义;
- https://www.cnblogs.com/seven-sky/p/4729962.html
-- 介绍mDNSResponder,用于mDNS;
- https://blog.csdn.net/feng19870412/column/info/36310
-- AirPlay协议开发;
- https://blog.csdn.net/rambomatrix/article/details/80961060
-- AirPlay Android接收端学习一 协议
- https://blog.csdn.net/yueqian_scut/article/details/52694411
-- 局域网设备发现之Bonjour协议
- https://blog.csdn.net/wirelessdisplay/article/details/78228872
-- ios airplay mirroring镜像
- https://blog.csdn.net/bxjie/article/details/39581565
-- 关于airplay协议实现镜像功能研究
+-----------------------------------------------------------------+
more-更多关于该工程有关的代码分析,可参考 STRUCT.txt
/****************************************************
AirPlay2工程重要结构体介绍
+
第三方库介绍
****************************************************/
+---------------------------------------------+
| 请同时打开 raop_server.vsdx(图中有字母标识)
+---------------------------------------------+
|
| A. raop_server_s
| B. raop_s
| C. dnssd_s
| D. httpd_s
| E. httpd_callbacks_s
| F. http_connection_s
| G. raop_conn_s
| H. raop_rtp_s
| I. raop_rtp_mirror_s
| J. pairing_session_s
| K. raop_buffer_s
| L. mirror_buffer_s
|
+---------------------------------------------+
--- A. raop_server_s
- 主结构体,内部含 raop_s 和 dnssd_s
--- B. raop_s
- 关于Airplay2功能的总结构体(dnssd除外)
--- C. dnssd_s
- 关于服务注册与服务发现的结构体
- 利用第三方库mDNSResponder(Apple开源)
--- D. httpd_s
- 用于监听服务端口,处理到达的请求(RTSP),根据不同的请求执行后续操作
- 关键在于其内部的thread(httpd_thread)
--- E. httpd_callbacks_s
- 回调函数结构体
- 关键在于conn_request(处理不同请求)
--- F. http_connection_s
- 已完成的连接(客户端与服务端)
- 其中的user_data实际为raop_conn_t的指针
--- G. raop_conn_s
- 用于在有连接建立之后使用,可用来处理镜像(视频)或者音频
--- H. raop_rtp_s
- 用于处理音频
- 包含音量、进度等音频相关变量
--- I. raop_rtp_mirror_s
- 用于处理镜像
- 关键在于产生的子线程
- 分别用于接收数据、解密数据、转发数据至Java层
--- J. pairing_session_s
- 这是一个过渡结构体,用于保存各种密钥与签名相关数据,最后将密钥保存至K\L两个结构体中后
--- K. raop_buffer_s
- 用于处理音频的结构体,将aac解密后,再转成pcm,送入音频播放器
--- L. mirror_buffer_s
- 用于处理镜像的结构体,将h264解密后,送入视频解码器解码
+---------------------------------------------+
| 第三方库介绍
+---------------------------------------------+
|
| fdk-aac:音频转码
| mDNSResponder:服务注册与服务发现
| plist:解析请求中的字段
| playfair:苹果私有加密的核心
| crypto:密码技术相关
| curve25519:椭圆曲线函数,用于密钥协商
| ed25519:数字签名算法
|
+---------------------------------------------+