简介:《Airplay 协议》是一份面向 iOS 与 Apple TV 互操作开发、协议逆向分析及智能硬件调试人员的英文协议说明文档,系统梳理苹果 AirPlay 协议族的实现机制与扩展细节。文档以非官方规范形式,围绕服务发现、照片与视频传输、音频流媒体、屏幕镜像、密码保护等模块展开,涉及 AirTunes 与 AirPlay 服务发布、RAOP、HTTP/RTSP 请求、RTP 流、音量与元数据控制、AirPort Express 认证、远程控制及时间同步等知识点,并附有历史沿革与 IETF RFC、草案、苹果协议等参考资源,便于读者建立从设备发现到媒体传输的完整协议视图。资源包共 1 个 doc 文件,约 511KB,内容按章节组织,适合作为协议查阅与实现对照的手册。目前已有 1432 人学习,可供从事投屏、多屏互动和苹果生态适配的开发者参考。
1. 手机点一下,电视就亮了:AirPlay 到底在忙什么
手机里存着一段视频,你在播放界面点了一下那个“屏幕套屏幕”的小图标,客厅的电视就亮起来开始放了。大多数人的第一反应是“蓝牙把文件传过去了”,或者“屏幕被实时录下来传过去了”——这两个猜测都不对,或者说只对了一小半。AirPlay 是一族协议的总称,它把整件事拆成四步:找到附近谁有能力接、和对方谈好用什么格式、把内容推过去、再把播放状态收回来。四步分别交给不同的协议完成,有现成的标准协议,也有自己加的扩展字段。搞懂这套分工,你才会明白为什么有时候投屏一秒就连上,有时候转半天圈连设备名字都刷不出来。
2. 设备是怎么互相认出来的:服务发现与一条自报家门的 TXT 记录
投屏列表弹出来的那三秒钟,屏幕上发生的事比想象中多。手机并没有一个“已知设备通讯录”挨个去拨号,它做的是在局域网里公开问一句“谁会放视频”,会放的那些设备自己举手应答。这套机制不需要账号、不需要提前配对、不需要在设置里填 IP,插上电、连上同一个网络就能被看见。整套发现流程建立在多播 DNS 之上,日常更常见的叫法是 Bonjour。
2.1 没有配对按钮,靠的是在局域网里“喊话”
多播 DNS 的思路很朴素:局域网里有一个约定好的多播地址和端口,任何设备都可以往那儿发问题,也可以监听那儿的问题。查询的域名以.local结尾,意思是“这个答案不需要去问外面的 DNS 服务器,本地就有人知道”。想找能放视频的设备,就查_airplay._tcp.local这类服务类型;想找能放音频的设备,就查_raop._tcp.local。
打个比方,这像是在一个没有物业的小区里找开锁师傅:不是挨家挨户敲门,而是站在院子里喊一嗓子“谁家会开锁”,会的人自己从窗户探头报名。喊话的人不需要知道院子里住了几户,也不需要维护一份名单,新搬来的人只要听得见就能应答。
这个设计带来两个直接后果。好处是零配置,设备换 IP、换网段、改名,都不影响发现;代价是它只在一个广播域内有效,跨路由器的两个网段之间默认互相听不见。很多“投屏搜不到设备”的现场,问题就出在这里:手机连的是 5G 频段的访客网络,电视插的是另一台路由器的 LAN 口,两边物理上在同一个屋子里,逻辑上是两个小区。
2.2 同一个设备上住着两套服务:RAOP 管声音,AirPlay 管画面
一个能接 AirPlay 的电视盒子,会同时广播两条服务记录。第一条叫 RAOP,全称是 Remote Audio Output Protocol,端口示例是 49152,专门负责音频流;第二条就叫 AirPlay 服务,端口示例是 7000,负责照片和视频内容。两条记录的服务名通常由设备的 MAC 地址加一个展示名拼成,形如“一串十六进制地址@客厅电视”,前半段保证唯一,后半段是给人看的。
为什么要分成两条而不是合并成一条?有历史原因——最早只有音频这一条路,后来才加上画面,为了不破坏已有的客户端,新能力就挂在新的服务类型下。也有现实好处:一个只带喇叭的音频设备可以只广播 RAOP,一个只做投屏的接收器可以只广播 AirPlay,双方都不必实现自己用不到的那部分。客户端查哪一条,就等于声明了“我要干什么”。
提示:看到
_raop._tcp不要以为那是另一个厂家的协议,它就是 AirPlay 家族里负责音频的那一支,服务名一直没改。
2.3 TXT 记录:贴在门口的一张能力清单
发现到设备只是知道了“有这么个东西、IP 是多少、端口是多少”,还不足以决定要不要连。真正让客户端下判断的,是随服务记录一起发出来的 TXT 记录——一串键值对,相当于贴在门口的营业说明。RAOP 那条记录里的常见字段如下:
| 字段 | 示例值 | 含义 |
|---|---|---|
| txtvers | 1 | 这条 TXT 记录的格式版本 |
| ch | 2 | 声道数,2 表示立体声 |
| cn | 0,1,2,3 | 支持的音频编码编号列表 |
| et | 0,3,5 | 支持的加密类型编号列表 |
| md | 0,1,2 | 支持的元数据类型编号列表 |
| pw | false | 这台设备是否需要密码 |
| sr | 44100 | 采样率,单位赫兹 |
| ss | 16 | 采样位深,单位比特 |
| tp | UDP | 传输层用 TCP 还是 UDP |
| vs | 130.14 | 服务端软件版本 |
| am | 型号标识 | 设备型号 |
| sf | 0x4 | 状态标志位 |
cn、et、md这三个字段的值都是编号,编号对应的含义是固定的:
| 编码编号 cn | 含义 | 加密编号 et | 含义 | 元数据编号 md | 含义 |
|---|---|---|---|---|---|
| 0 | PCM 未压缩 | 0 | 不加密 | 0 | 文本(曲名、艺术家) |
| 1 | ALAC 无损 | 1 | RSA(用于音频基站) | 1 | 封面图 |
| 2 | AAC 有损 | 3 | FairPlay | 2 | 播放进度 |
| 3 | AAC-ELD 低延迟 | 4 | MFiSAP(第三方设备) | ||
| 5 | FairPlay SAPv2.5 |
这套自描述的价值在于解耦。客户端不需要内置一张“某年某型号支持什么”的对照表,它只要读一遍对方自报的能力,就能决定用 ALAC 还是 AAC 发、要不要带封面图、要不要弹密码框。厂家出新设备、加新编码,只要在 TXT 里多写一个编号,老客户端不认识这个编号,会自动跳过,不会崩。
2.4 位域 features:一个十六进制数里塞了十几项能力
AirPlay 那条服务记录用的是另一套描述方式,字段少但信息密度更高,其中最关键的是features,一个十六进制数,每一位代表一项能力。以0x39f7为例,把它展开成二进制,从最低位往高位读:
| 位号 | 能力 | 位号 | 能力 |
|---|---|---|---|
| 0 | 视频 | 8 | 屏幕旋转 |
| 1 | 照片 | 9 | 音频 |
| 2 | 视频 FairPlay 保护 | 11 | 音频包冗余 |
| 3 | 视频音量控制 | 12 | 安全认证(SAPv2.5 + AES-GCM) |
| 4 | HTTP Live Streaming | 13 | 照片预缓存 |
| 5 | 幻灯片 | ||
| 7 | 屏幕镜像 |
光看表容易糊涂,写几行代码把它解出来最直观:
# 位号 -> 能力名称,只列文档里定义过的那几位 FEATURES = { 0: "视频", 1: "照片", 2: "视频FairPlay", 3: "视频音量控制", 4: "HTTP Live Streaming", 5: "幻灯片", 7: "屏幕镜像", 8: "屏幕旋转", 9: "音频", 11: "音频包冗余", 12: "安全认证", 13: "照片预缓存", } def decode_features(bitfield: int) -> list: # 右移 bit 位再和 1 相与,就是在问"第 bit 位是不是 1" return [name for bit, name in FEATURES.items() if (bitfield >> bit) & 1] print(decode_features(0x39f7))这段代码没什么技巧,核心只有一行:(bitfield >> bit) & 1。把一个数右移若干位,最低位就变成了原来那一位的值,再和 1 做与运算,得到 0 或 1。用它扫一遍所有已定义的位号,就得到这台设备自报的能力清单。
0x39f7解出来的结果是:视频、照片、HTTP Live Streaming、幻灯片、屏幕镜像、屏幕旋转、音频包冗余、安全认证、照片预缓存。注意第 3 位(视频音量控制)和第 9 位(音频)是 0。前者说明这台盒子不支持在播放视频时调节音量——这个能力后来才被加进来,早期实现里干脆留空;后者说明音频不走这条服务,而是走 RAOP 那条,跟前面说的两套服务分工正好对上。
注意:位域是“自报”,不是“承诺”。广播里写了支持某一项,客户端连上去之后仍可能发现行为不完全一致;广播里没写,也不代表设备物理上做不到,只代表这条服务不负责。把位域当成线索,不要当成合同。
3. 三种内容三条路:照片、视频、音频各走各的通道
发现阶段结束,双方握手建立会话,接下来就该搬内容了。有意思的是,AirPlay 并没有给所有内容设计同一条管道。一张照片、一段视频、一路音频、一屏实时画面,走的是四种差别很大的传输方式,因为它们对延迟、对同步、对能不能中途改主意的要求完全不同。
3.1 照片:一次 PUT 就是一张图
最简单的是照片。客户端把 JPEG 的二进制数据直接塞进一个 HTTP PUT 请求的正文里,发给接收端的/photo路径,服务端回一个 200,图就显示出来了。整个交互干净得像往服务器上传一个文件:
PUT /photo HTTP/1.1 X-Apple-AssetKey: F92F9B91-954E-4D63-BB9A-EEC771ADE6E8 X-Apple-Transition: Dissolve Content-Length: 462848 User-Agent: MediaControl/1.0 X-Apple-Session-ID: 1bd6ceeb-fffd-456c-a09c-996053a7a08c <这里是 JPEG 二进制数据>三个自定义头各管一件事。X-Apple-AssetKey是这张图的唯一标识,一串 UUID,后面做缓存要靠它认人;X-Apple-Transition指定用哪种转场效果把这张图推上来,示例里是溶解,不写就是硬切;Content-Length告诉服务端正文有多长,接收端据此知道读到哪儿算完。服务端返回 200 且正文为空,就表示图已经收下并显示。
转场名字不是随便编的,客户端可以先发一个GET /slideshow-features,把这个接收端支持的所有转场主题列出来。请求里带一个Accept-Language头,服务端就会用对应语言返回转场名称,返回体是一段 XML 属性列表,里面是一个themes数组,每项有key和name。这一步的意义在于让用户先挑效果,再开始放,而不是放起来之后才发现这台设备不支持某种花哨的切换。
3.2 幻灯片与预缓存:为什么要提前塞几张图进去
单张照片是一次请求,幻灯片就是一串请求加一份配置。客户端先GET /slideshow-features拿主题,再用PUT /slideshows/1把设置推过去,正文是一段 XML 属性列表,里面写三样东西:settings.slideDuration是每张停留的秒数,settings.theme是选中的转场主题,state是playing或stopped。服务端回一个空的字典表示收到,之后客户端就按节奏一张张 PUT 图片。要停的时候,发一个POST /stop。
真正有意思的是缓存机制。切图如果每次都从头传一张几兆的 JPEG,转场动画必然卡在半路等数据。协议里给出的解法是让客户端提前把相邻的几张图“预存”进去:在 PUT 请求里带上X-Apple-AssetAction: cacheOnly,服务端收到后只把图存进缓存,不显示。等真正要切到那张图时,客户端再发一次请求,动作换成displayCached,带上同一个X-Apple-AssetKey,服务端直接从缓存里取,不用再传一遍数据。
这个设计思路在工程上很常见:把“传输”和“呈现”拆成两个时刻。传输可以慢慢来、可以提前做,呈现必须准时。这也是为什么翻幻灯片时,第一张总有点迟疑,后面几张反而顺——缓存被填热了。
3.3 视频:请求加上事件回报
视频这一路走的是 HTTP 请求,客户端告诉接收端要放什么,接收端自己去取、自己缓冲、自己解码。这一点很关键:视频数据并不是由手机一路实时推过去的,手机更像一个遥控器加一份播放清单。好处是手机可以锁屏、可以切到别的应用,播放不中断;坏处是接收端必须能独立访问到那个媒体地址,如果视频存在手机本地、接收端又拿不到,就得换一条路。
伴随视频的还有事件。播放开始、暂停、播完、出错,这些状态变化需要回传给客户端,让手机上的进度条跟电视上的画面对得上。事件不是客户端轮询问出来的,而是接收端主动推过来的,推的通道就是后面第 4 章要讲的反向连接。
3.4 音频:先用 RTSP 谈条件,再用 RTP 送数据
音频是四路里最讲究的一路,因为它对时间最敏感。它用的控制协议是 RTSP,一个专门为流媒体设计的协议,长得像 HTTP,但语义是“会话式”的:先描述要放什么,再建立传输通道,然后播放、暂停、调整、结束。和 HTTP 的一次请求一次响应不同,RTSP 允许一个会话持续存在,双方在会话期间一直保持状态。
条件谈好之后,真正的音频数据走 RTP。RTP 的每个包里都带序号和时间戳,接收端靠序号发现丢包、靠时间戳把采样排回正确的位置。这也是为什么 TXT 记录里要自报sr(采样率)和ss(位深)——发送端必须先知道对方的时钟基准,才能把时间戳算对。传输层可以是 UDP 也可以是 TCP,前者快但可能丢,后者稳但延迟高,具体用哪个由tp字段和实际协商决定。
音频这条线上还挂着几样附属能力。音量控制让客户端能远程调接收端的输出电平,而不是去调自己的媒体音量;元数据(md字段里声明的文本、封面、进度)让接收端的屏幕上能显示曲名和专辑图,哪怕手机已经锁屏;远程控制让接收端的按键或遥控器能把“下一首”“暂停”这类指令送回手机。此外还有认证环节,用于音频基站这类设备,走的是 RSA 那一套。
注意:原始文档明确说明它不提供任何 RSA 密钥,也不解释受 DRM 保护的内容如何解码。也就是说,协议层描述的是“怎么把已解码的数据送过去”,不是“怎么把加密内容解开”。这两件事经常被混为一谈。
3.5 屏幕镜像:一边编码一边对表
屏幕镜像和前面三种都不一样。照片、视频、音频都是有明确终点的内容,镜像没有——它是一路持续的、实时的画面。手机必须把当前屏幕一帧帧编码成视频流推过去,接收端边收边解码边显示。
这件事对硬件有硬性要求:编码必须由专门的硬件完成,不能靠通用处理器软编,否则 CPU 会被吃满、手机发烫、帧率掉到个位数。文档里明确写了,屏幕镜像只在具备这种硬件编码能力的设备上可用,老设备直接被排除在外。这也解释了一个常见困惑:同一款应用,在新手机上能镜像,在老手机上那个按钮是灰的——不是软件不想支持,是硬件扛不住。
镜像这条路上还有一个容易被忽略的环节:时间同步。实时画面和声音要一起出来,接收端收到的一堆流包必须按统一的时间基准排好队再播放,早到的等一等,晚到的丢掉或者补帧。这一步借用了网络时间协议那套思路,让两端对同一时刻的理解尽量一致。没有它,画面和声音就会逐渐漂移,播几分钟之后口型对不上。
4. 为什么要有“反向连接”:一条被倒过来用的 HTTP
前面反复提到“事件回传”,但用普通 HTTP 很难做到这件事。HTTP 是请求-响应模型:客户端开口,服务端回答。服务端手里没有客户端的“电话号码”,它没法主动敲客户端的门说“我播完了”。这是一条硬约束,AirPlay 为了绕开它,做了一件挺聪明的事。
4.1 普通 HTTP 的死角:服务器没法主动说话
假设只有普通 HTTP,客户端想知道播放状态,有两条路。一条是轮询:每隔一秒发一次请求问“现在什么状态”,绝大部分回答都是“没变”,白白浪费请求和电量,而且从状态变化到被发现之间总有一个间隔。另一条是长轮询:发一个请求挂在那儿不返回,等服务端有话说再返回,然后立刻再发一个。这条路能用,但实现别扭,中间任何一个代理或超时都可能把连接掐断。
对一个要在客厅里连续放两小时电影的场景来说,这两种方案都不体面。协议的设计者选了第三条路:干脆开一条连接,方向反过来用。
4.2 PTTH:把方法名倒过来写
客户端在正常的 HTTP 会话之外,再向接收端的 7000 端口发起一条连接,请求长这样:
POST /reverse HTTP/1.1 Upgrade: PTTH/1.0 Connection: Upgrade X-Apple-Purpose: event Content-Length: 0 User-Agent: MediaControl/1.0 X-Apple-Session-ID: 1bd6ceeb-fffd-456c-a09c-996053a7a08c服务端如果同意,回一个101 Switching Protocols,连接就从普通 HTTP 升级成一条双向通道。注意Upgrade头里的PTTH——就是HTTP反过来拼的,一个藏在协议里的玩笑。升级之后,这条连接上不再是“客户端问、服务端答”,而是服务端随时可以发一个POST /event过来,客户端回一个 200 表示收到。
X-Apple-Purpose: event这个头是给接收端看的说明书:这条连接专门用来发事件,不要拿它干别的。X-Apple-Session-ID则是把这条反向连接和主连接绑定在一起的钥匙——一个客户端可能同时开着好几条会话,接收端必须知道这个事件该算到哪一次播放头上。
4.3 会话 ID:把两条连接缝成一条
正向连接和反向连接是两个独立的 TCP 连接,操作系统层面毫无关系。把它们变成“同一次会话”的,就是那个X-Apple-Session-ID,一个 UUID,在两条连接的所有请求里都带上。除此之外,每个 HTTP 请求还会带X-Apple-Device-ID,值是设备的 MAC 地址,用来标识“是谁在说话”。
这种设计的好处是解耦。接收端不需要维护一张“哪个 IP 对应哪个客户端”的表,它只认会话 ID;客户端也不需要保证两条连接从同一个源端口出去,只要 ID 一致就行。代价是 ID 变成了唯一的身份凭据,一旦泄漏,别人就能冒充你的会话收发事件。
4.4 事件报文长什么样
事件本身的格式是 XML 属性列表,和前面幻灯片设置用的是同一套。以照片会话结束为例,接收端发过来的是这样一段:
POST /event HTTP/1.1 Content-Type: text/x-apple-plist+xml Content-Length: 277 X-Apple-Session-ID: 1bd6ceeb-fffd-456c-a09c-996053a7a08c <?xml version="1.0" encoding="UTF-8"?> <plist version="1.0"> <dict> <key>category</key><string>photo</string> <key>sessionID</key><integer>38</integer> <key>state</key><string>stopped</string> </dict> </plist>三个键读起来很清楚:category说明这是哪一类事件,sessionID是会话编号,state是状态值。照片会话只有stopped一个状态,意思是“结束了,你可以安全断开”。幻灯片的状态多一些,有loading、playing、stopped三种,还会额外带一个lastAssetID,告诉客户端“我最后显示到第几张”,客户端据此知道下次该从哪儿接着放。
提示:事件的方向要记清楚。照片和幻灯片的事件是接收端发给客户端的(通知状态变化),而幻灯片播放状态的事件也可以是客户端发给接收端的(告知自己这边的情况)。同一个
/event路径,两个方向都用,靠会话 ID 区分。
5. 几个最容易搞混的地方:AirPlay 不等于投屏,也不等于蓝牙
概念解释到这儿,剩下的风险不是“不懂”,而是“懂了一半”。下面这几组词,日常对话里经常被当成同义词互换使用,但在协议层面它们指的东西差别很大,混着用会在排查问题时把你带偏。
5.1 AirPlay 和 AirTunes:改的是名字,留的是服务名
AirTunes 是这套协议最早的名字,那时候它只干一件事:把音频从电脑送到带音箱的网络基站。后来视频能力被加进来,名字改成了 AirPlay,但底下的服务发现记录一直没改名,还是_raop._tcp。所以在网络扫描结果里看到 RAOP,不要以为那是某个不相干的第三方协议——它就是 AirPlay 的音频分支,只是名字停留在了过去。
这个区别在排查时很有用。如果你在列表里能看到设备但放不出声音,先看 RAOP 那条服务有没有广播、TXT 里的cn里有没有你打算用的编码;如果你能看到设备但投不了画面,该看的是 AirPlay 那条服务里的features位域。
5.2 镜像和投放:一个是录屏直播,一个是把地址递过去
“投屏”这个词在日常里覆盖了两件事,协议层把它们分得很开。屏幕镜像(Screen Mirroring)是把整个屏幕实时编码成视频流推过去,接收端看到的是你手机上的一切,包括通知、误触、横竖屏切换。媒体投放是把内容的地址或数据交给接收端,让它自己取、自己播,手机只是个遥控器。
两者的成本差得很远。镜像必须持续编码,手机一定发热、一定耗电,画质受编码码率和网络抖动影响;投放的负担在接收端,手机可以锁屏走人。所以判断该用哪种,看的是“我要不要连操作界面一起给对方看”。只想看个片子,用投放;要演示 App 怎么点,才用镜像。照片的 PUT 上传其实属于第三类——既不是镜像也不是投放,而是“把数据推过去让对方显示”。
5.3 和蓝牙、DLNA 比,差在哪
| 维度 | AirPlay | 蓝牙音频 | DLNA |
|---|---|---|---|
| 传输介质 | 局域网(Wi-Fi 有线皆可) | 短距无线,点对点 | 局域网 |
| 发现方式 | 多播 DNS 自动发现 | 配对 | UPnP 广播发现 |
| 数据流向 | 推流,接收端可回传事件 | 推流,几乎无回传 | 通常是把地址给接收端,让它自己拉 |
| 时间同步 | 有专门的对时环节 | 依赖编解码器缓冲 | 较弱,多设备同步困难 |
| 典型短板 | 只在一个广播域内有效 | 带宽有限,压缩重 | 格式兼容性靠协商,体验参差 |
把这张表浓缩成一句话:蓝牙解决的是“一对一、离得近”,DLNA 解决的是“把地址给你、你自己想办法播”,AirPlay 解决的是“我知道你有什么能力、我按你的能力推、你还要把状态告诉我”。第三件事里的“回传”是它和另外两者最本质的分野。
5.4 密码保护和内容加密:门锁和保险柜
这两个词经常被合并成“加密”,其实是两层完全不同的东西。密码保护是准入控制,反映在服务发现里就是那个pw字段——只有接收端设置了密码,这个字段才会出现在 TXT 记录里,客户端看到它才会弹密码框。它管的是“你能不能进来”。
内容加密是另一回事,对应 TXT 里的et字段:0 表示不加密,1 是 RSA(音频基站那类设备用),3 和 5 是不同版本的 FairPlay 方案,4 是面向第三方设备的方案。它管的是“进来的数据能不能解”。原始文档在这里划了一条很清楚的线:它不提供任何 RSA 密钥,不解释受 FairPlay 保护的内容怎么解码,也不解释手机端用来保护音频和屏幕内容的那套认证。也就是说,协议文档描述的是管道形状,不是钥匙。
注意:很多人看到“支持 AirPlay”就以为能放任何内容,实际卡住的地方往往就在这一层。能不能连上、能不能解码,是两个独立的问题。
5.5 “支持 AirPlay”这句话的三种含金量
同一个说法,可能指三件不同的事。第一种是“能被发现”:设备在网络上广播了_airplay._tcp服务,客户端列表里能看到它的名字。第二种是“能用某项功能”:features位域里对应位是 1,客户端据此启用对应按钮。第三种是“通过认证”:这是商业层面的合规声明,和协议能不能跑通没有必然关系。
三种含义混在一起,就会出现“列表里有它、点了没反应”的现场。这时候该做的不是反复重启,而是去看那段 TXT 记录:位域里到底声明了什么,pw在不在,model和vs是什么。这些字段是排查的起点,比猜有用得多。
6. 什么时候该用它,什么时候绕开走
理解了这套分工,判断就变得很直接。适合的场景几乎都集中在一个前提下:设备和接收端在同一个局域网、同一个广播域内,且你需要的不只是“把画面弄过去”,还包括状态回传和多设备协同。不适合的场景,也几乎都能归结为这个前提被打破,或者你要解决的问题根本不在协议覆盖范围内。
| 场景 | 建议 | 原因 |
|---|---|---|
| 家里把手机视频放到客厅大屏 | 用 | 同一网段,发现和投放都很顺 |
| 会议室轮流演示,需要展示操作过程 | 用镜像 | 要连界面一起给对方看 |
| 只放一段本地音频到音箱 | 用 RAOP 那一路 | 负担小,元数据能显示在音箱屏上 |
| 手机连访客 Wi-Fi,电视在另一网段 | 换网络或加中继 | 多播发现跨不了广播域 |
| 需要把画面同步到十几个房间 | 另找方案 | 协议本身不解决大规模同步分发 |
| 只想在局域网里传一个文件 | 别用 | 一个 HTTP 请求就能解决,不必上这套会话机制 |
| 播放受保护的内容 | 先确认解码链路 | 协议层不提供密钥,也不解释 DRM 解码 |
拿一个具体例子走一遍。你在会议室,笔记本和投影盒子都连着同一个内网,要把一段演示视频投上去。先看服务发现:盒子的_airplay._tcp能不能被扫到、features里视频位是不是 1。能扫到、位是 1,就发 HTTP 请求把媒体地址递过去,同时开一条/reverse连接等着收事件。播放中如果发现进度条不动,先看反向连接有没有成功升级到 101,再看/event有没有正常 POST 过来——卡在这一步的话,问题在事件通道,不在视频本身。
很多时候我会用一个最土的办法验证:先不看界面,直接把那条服务记录打印出来。
# 在局域网内列出广播了 _airplay._tcp 的设备及其 TXT 记录 # 命令名各系统不同,能把 SRV 和 TXT 一起打出来即可 avahi-browse -rt _airplay._tcp输出里该关注四样东西:端口是不是你以为的那个、model和vs是不是和实物对得上、features解出来缺了哪几位、pw在不在。这四样一对,八成的“为什么连不上”现场就有方向了,剩下的才是抓包看请求。
我踩过的坑是反过来:一开始总盯着应用层的报错,反复重装、反复重启设备,折腾半天;后来才养成习惯,任何一次投屏出问题,先去把那段 TXT 记录完整读一遍,确认对方的自报能力和我的预期一致,再往下查。这个顺序换过来之后,很多玄学问题当场就变成了明确的一行字段差异。希望帮到你。
本文还有配套的精品资源,点击获取