# AirplayDemo **Repository Path**: moleo/airplay-demo ## Basic Information - **Project Name**: AirplayDemo - **Description**: Android Airplay Server - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2025-01-15 - **Last Updated**: 2025-08-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: airplay, Kotlin ## README # 简介 基于`Android`的`Airplay`屏幕镜像服务器 # 支持的投放设备 支持iPhone和Mac设备屏幕投放和音乐播放 支持iOS系统:iOS9~iOS13、iOS14~iOS18(已验) 支持MacOS系统:10.14(低版本未测试) # 涉及到的协议和算法 如果了解了相关协议和算法会更好的理解代码 1. 协议:RTSP,RTCP、RTP、DNS,DNS-SD,mDNS,NTP 2. 加解密算法:AES(cbc&ctr) 3. 签名算法:curve25519,ed25519 4. 音视频基础:H264编码,AAC编码 # 第三方开源模块 - MDNS:third_party/mDNSResponder Apple开源mDNS功能,用于设备发现 - fdk-aac音频解码:third_party/fdk-aac 用于AAC音频解码 # 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命令 每秒钟手机端都会向接收端发一次这个命令,我理解为定时保活的意思。 # GET /info命令相关参数在以下方法中配置 搜素该raop_handler_info方法,配置对应参数 plist_t displays_node = plist_new_array(); plist_t displays_0_node = plist_new_dict(); plist_t displays_0_uuid_node = plist_new_string("e0ff8a27-6738-3d56-8a16-cc53aacee925"); plist_t displays_0_width_physical_node = plist_new_bool(0); plist_t displays_0_height_physical_node = plist_new_bool(0); plist_t displays_0_width_node = plist_new_uint(1920); plist_t displays_0_height_node = plist_new_uint(1080); plist_t displays_0_width_pixels_node = plist_new_uint(1920); plist_t displays_0_height_pixels_node = plist_new_uint(1080); plist_t displays_0_rotation_node = plist_new_bool(0); plist_t displays_0_refresh_rate_node = plist_new_uint(60); plist_t displays_0_overscanned_node = plist_new_bool(1); plist_t displays_0_features = plist_new_uint(14); plist_t displays_0_max_fps = plist_new_uint(30);