go2rtc 中的 Home Accessory Protocol(HAP)实现剖析:从 HomeKit 配对到 AAC-ELD 音视频流

发布时间:2026/9/14 6:40:37
go2rtc 中的 Home Accessory Protocol(HAP)实现剖析:从 HomeKit 配对到 AAC-ELD 音视频流
go2rtc 中的 Home Accessory ProtocolHAP实现剖析从 HomeKit 配对到 AAC-ELD 音视频流【免费下载链接】go2rtcUltimate camera streaming application项目地址: https://gitcode.com/GitHub_Trending/go/go2rtcgo2rtc 通过 pkg/hap 包实现了 Apple 家庭自动化配件协议Home Accessory Protocol简称 HAP为智能家居摄像头场景提供了完整的 Client控制器与 Server配件双端能力。本文以该文档为骨架结合仓库源码、配置文件与测试用例系统讲解 HAP 的配件模型Accessory / Service / Characteristic、配对认证Pair Setup / Pair Verify、TLV8 与加密连接的数据面以及 HomeKit 摄像头特有的 AAC-ELD 音频与 OPUS RTP 打包细节帮助你理解 go2rtc 是如何把一个 RTSP 摄像头变成 iPhone 可识别的 HomeKit 摄像头又是如何把 Aqara、Eve、Eufy 等 HomeKit 摄像头接入自家体系的。一、HAP 协议的角色模型Device / Client / Server文档开篇即定义了 HAP 中三个核心角色的职责边界这是理解整个包结构的前提。1.1 DeviceHomeKit 终端配件Device 是 HomeKit 生态里的末端设备典型如开关switch、摄像头camera等。它有如下标识特征mDNS 名称形如MyCamera._hap._tcp.local.通过多播 DNS 向局域网广播自身DeviceID形如0E:AA:CE:2B:35:71的类 MAC 地址全局唯一配件描述层次一个 Device 由一个或多个 Accessories 描述每个 Accessory 拥有AIDAccessory ID和多个 Services每个 Service 拥有IIDInstance ID、Type类型 UUID和多个 Characters每个 Character 拥有IID、Type、Format数据类型和Value当前值。这一配件 → 服务 → 特征的三级树状模型正是 HAP 数据模型的核心也是 HomeKit 客户端如 iPhone 的家庭 App读取设备能力清单/accessories接口的返回结构。仓库中 pkg/hap/accessory.go 用 Go 结构体原样映射了这三级模型AccessoryAID注释标明每个桥接器最多 150 个唯一配件与Services列表ServiceType、IID、Primary、Characters、Linked等字段Character见下文 1.3 节。值得说明的是 IID 的生成规则Accessory.InitIID()按AID 服务序号 服务类型 特征类型拼接十六进制串来推导出每个 Service 和 Character 的实例 IDANSSS000与ANSSSCCC的注释即此意使客户端无需解析类型 UUID 即可稳定寻址。1.2 ClientHomeKit 客户端Client 指 iPhone、iPad、MacBook 或第三方开源库实现的控制器。文档明确列出了其能力与身份信息ClientID静态随机 UUIDClientPublic / ClientPrivate静态随机的 32 字节密钥对ed25519 签名密钥对可以使用 PIN 码与 Device 配对配对过程中交换双方的 ID 与公钥ClientID/ClientPublic 与 ServerID/ServerPublic可以使用 ClientPrivate 向 Device 认证Pair Verify即每次建立连接时的双向认证持有到设备的长连接persistent Secure connection可以读取设备的 Accessories 清单可以读写设备的 Characters可以订阅设备 Characters 的变化事件Event。上述每一条能力在 pkg/hap/client.go 中都有对应方法GetAccessories()/GetFirstAccessory()读配件清单、GetCharacters()/GetCharacter()/PutCharacters()读写特征、OnEvent回调配合eventsReader()协程订阅变更事件、GetImage()抓取快照资源。而 URL 形式的连接串也体现了客户端所需的全部凭据// pkg/hap/client.go 中的 URL 组装 func (c *Client) URL() string { return fmt.Sprintf( homekit://%s?device_id%sdevice_public%16xclient_id%sclient_private%32x, c.DeviceAddress, c.DeviceID, c.DevicePublic, c.ClientID, c.ClientPrivate, ) }1.3 Characteristic特征与 Character 的补充约定文档以一句脚注提醒读者PS. Character Characteristic即 HAP 术语中的 Characteristic特征在 go2rtc 源码中统一缩写为Character。同时给出三个重要约定只读特征PR与可写特征PW通过Perms权限数组区分Value 在 PW可写特征中应省略在 PR只读特征中可以为空——这与 pkg/hap/character.go 的注释Value should be omit for PW, Value may be empty for PR完全一致Character 除了IID/Type/Format/Value外还有Perms、Desc等字段源码中还预留了MaxLen、Unit、MinValue、MaxValue、MinStep、ValidVal等被注释掉的约束字段可见其目标是完整覆盖 HAP 特征描述能力。特征的值写入与事件通知紧密耦合Character.Set()先写值再通知监听者NotifyListeners而GenerateEvent()会生成带原始 HTTP 头的EVENT/1.0帧这正是订阅变更事件的数据面格式见第四节。Character.Write()还会按Format做类型归一化例如bool格式会把float64非零值转为truetlv8格式会自动做 base64 编码。二、配对与认证Pair Setup 与 Pair Verify 的完整流程HAP 的安全模型是本文档最核心的实战内容之一。客户端与服务端围绕配对Pair Setup→ 验证Pair Verify→ 加密连接三个阶段展开仓库在 pkg/hap/server.go 与 pkg/hap/client_pairing.go 中实现了对称的六步状态机State M1~M6。2.1 配对前准备PIN 码与密钥Server配件侧ServerID与 DeviceID 相同用于客户端认证ServerPublic/ServerPrivate是静态随机的 32 字节密钥对pkg/hap/server.go 中Server结构体由Pin、DeviceID、DevicePrivate与可选回调GetClientPublic组成Client控制器侧若未指定ClientID由GenerateUUID()生成ClientPrivate由GenerateKey()ed25519生成见 pkg/hap/client_pairing.go。PIN 码有严格的合法性约束SanitizePin()要求去掉短横线后恰好 8 位数字并拒绝 HAP 规范 4.2.1.2 节列出的不安全组合如00000000、12345678等 12 个弱 PIN格式化输出为123-45-678形式见 pkg/hap/helpers.go。2.2 Pair Setup基于 SRP 的六步握手配对过程使用Stanford Secure Remote PasswordSRP/ PAKE口令认证密钥交换群参数为rfc5054.3072哈希为 SHA-512用户名固定为Pair-Setup密码即 PIN 码。go2rtc 在keyDerivativeFuncRFC2945中实现了 RFC 2945 风格的密钥派生函数。六步状态机如下步骤方向内容M1Client → Server发送配对方法MethodPair0MFi 证书设备用MethodPairMFi1与状态M2Server → Client返回 SRP 公钥session.GetB()与 16 字节盐saltM3Client → Server返回客户端 SRP 公钥session.GetA()与客户端证明ComputeAuthenticator()M4Server → Client校验客户端证明后返回服务端证明M5Client → Server用 HKDF-SHA512 派生加密密钥经 ChaCha20-Poly1305 加密发送 ClientID、ClientPublic 与 ed25519 签名M6Server → Client加密返回 DeviceID、ServerPublic 与签名客户端校验签名并比对 DeviceID实现细节上pkg/hap/server.goSRP 会话盐长设为 16 字节pake.SaltLength 16M5/M6 消息使用 HKDF-SHA512 派生两个不同的签名盐Pair-Setup-Controller-Sign-Salt/Info与Pair-Setup-Accessory-Sign-Salt/Info分别验证对方身份消息体用 TLV8 编码其中状态字段为 TLV6、标识符为 TLV1、公钥为 TLV3、签名为 TLV10、加密数据为 TLV5客户端侧在 pkg/hap/client_pairing.go 中对称实现最终把设备公钥存为DevicePublic。2.3 Pair Verify连接时的双向认证每次建立安全连接前都需要 Pair Verify它基于Curve25519 临时密钥协商 ed25519 静态签名M1Client 发送临时会话公钥M2Server 用 HKDF 派生加密密钥加密返回 DeviceID 与签名签名内容为服务器会话公钥 DeviceID 客户端会话公钥客户端用预存的DevicePublic验签M3Client 加密发送 ClientID 与签名内容为客户端会话公钥 ClientID 服务器会话公钥Server 通过GetClientPublic(id)回调取回该客户端公钥验签M4Server 返回成功状态双方持有共享会话密钥。值得注意的工程细节Server.GetClientPublic可以为nil此时客户端校验被禁用适用于开发或非严格场景PairVerify中所有消息PV-Msg02、PV-Msg03均以 ChaCha20-Poly1305 加密传输。2.4 配对管理与错误码客户端还支持完整的管理操作PairingsAdd添加配对可指定 User/Admin 权限、ListPairings列出配对、DeletePairing删除配对配合Unpair()便捷函数一次性完成连接 列出 删除。服务端在配对出错时返回 TLV7错误码pkg/hap/client_pairing.go 完整罗列了 1~7 号错误语义验证失败、重试等待、配对数达上限、认证尝试次数超限、配对方法不可用、服务忙等。三、配件与服务从 AccessoryInformation 到 CameraRTPStreamManagement文档指出 Device 由 Accessories/Services/Characters 三级描述go2rtc 在 pkg/hap/accessory.go 中预置了标准服务工厂并在 pkg/hap/camera 中实现了摄像头专用服务。3.1 标准服务工厂ServiceAccessoryInformation(manuf, model, name, serial, firmware)构造AccessoryInformation服务Type3E包含 Identify14、Manufacturer20、Model21、Name23、Serial Number30、Firmware Revision52六个特征ServiceHAPProtocolInformation()构造HAPProtocolInformation服务TypeA2携带协议版本1.1.0。3.2 摄像头配件pkg/hap/camera/accessory.go 的NewAccessory()组装了一个完整摄像头配件AccessoryInformation上述标准服务CameraRTPStreamManagementType110HAP 摄像头核心服务包含 7 个特征——StreamingStatusTLV8、SupportedVideoStreamConfigurationTLV8、SupportedAudioStreamConfigurationTLV8、SupportedRTPConfigurationTLV8、Activeuint8、SelectedStreamConfigurationTLV8初始为空、SetupEndpointsTLV8初始为空MicrophoneType112提供 Mute 特征11Abool 类型可订阅可读写。其中能力声明Supported* 特征通过 TLV8 编码视频能力pkg/hap/camera/ch114_supported_video.go只支持 H.264VideoCodecTypeH2640主档次 Main Profile1级别 3.1/4.0分辨率档位依次为1920×108030、1280×72030注释特别标注important for iPhones、320×24015Apple Watch音频能力只支持OPUS1 声道、16kHz 采样率、可变码率同时声明舒适噪声不支持RTP 能力SRTP 加密套件为AES_CM_128_HMAC_SHA1_80。3.3 流管理与端点协商pkg/hap/camera/stream.go 完整实现了 HAP 摄像头推流协商流程GetFreeStream()遍历配件服务读取每个CameraRTPStreamManagement服务的 StreamingStatus找到一个StreamingStatusAvailable的空闲流注释说明HomeKit 摄像头通常只允许同时两路客户端推流因此设备上往往有两个相同的流管理服务ExchangeEndpoints()通过SetupEndpoints特征交换双方的 SRTP 端点——本地生成 master key/salt 与 RTP 端口远端回复地址、端口与 SSRCNewStream()组装SelectedStreamConfiguration视频 PT99、音频 PT110、默认视频码率 4096 kbpsbitrate/1024转 kbps、MaxMTU1378、RTCP 间隔 0.5s/5s然后通过SetStreamConfig()写入SelectedStreamConfiguration特征启动会话Close()发送SessionCommandEnd结束会话。四、传输层TLV8、EVENT 事件与 ChaCha20-Poly1305 加密连接4.1 TLV8 编解码HAP 配对消息与能力配置均以 TLV8Type-Length-Value8 字节对齐的变体编码。仓库提供了独立子包 pkg/hap/tlv8基于反射实现了 Go 结构体与 TLV8 的互转Marshal/MarshalReader/MarshalBase64编码为字节流、Reader 或 base64 字符串特征值以 base64 字符串形式存储Unmarshal/UnmarshalReader反向解码字段通过tlv8:1之类的 tag 指定 TLV 类型号文档注释特别提示了 TLV 分隔符separator在规范中最易混淆取值可能是0x00、0xFF甚至0x05。4.2 EVENT/1.0 事件帧特征变更订阅通过长连接推送帧协议为自定义的EVENT/1.0。实现位于 pkg/hap/client_http.goReadResponse()先Peek(9)字节若是EVENT/1.0则改写为HTTP/1.1前缀以便用标准http.ReadResponse解析再恢复res.Proto ProtoEventWriteEvent()/eventWriter反向把标准响应写回EVENT/1.0帧pkg/hap/character.go 的GenerateEvent()直接把 JSON 载荷{characteristics:[{aid:1,iid:...,value:...}]}封装成带原始 HTTP 头的 EVENT 帧。4.3 加密长连接认证完成后普通 TCP 连接会被包装为 pkg/hap/conn.go 的Conn注释明确这是类似 tls.Client 的 net.Conn 包装由共享会话密钥经 HKDF-SHA512Control-SaltControl-Read/Write-Encryption-Key派生出读写分离的两把密钥客户端与服务端交换使用分帧每包先写 2 字节明文长度VerifySize随后是密文与 16 字节 ChaCha20-Poly1305 认证标签Overhead8 字节计数器 nonce 随收发各自递增单包最大0x4001024字节超长载荷自动分片提供MarshalJSON()把连接映射为 go2rtc 统一的core.ConnectionFormatNamehomekit、Protocolhap便于接入统一的 API 与调试面板。4.4 HTTP 端点路径方法说明/pair-setupPOST配对MimeTLV8/pair-verifyPOST认证MimeTLV8/pairingsPOST配对增删查MimeTLV8/accessoriesGET读取配件清单application/hapjson/characteristicsGET/PUT读/写特征值/resourcePOST获取图片等资源五、AAC-ELD 音频非标参数与 ffmpeg 编码要求5.1 为什么是 AAC-ELD文档明确指出HomeKit 摄像头音频使用的是非常不标准的 AAC-ELD 编解码器参数非标且违反部分规范。internal/homekit/README.md进一步补充了两个关键事实HomeKit 音频无法在 VLC 及绝大多数播放器中直接播放用于 MSE、WebRTC 等场景时必须转码如ffmpeg:...#audioaac#audioopus。5.2 编码前提必须启用 libfdk_aacAAC-ELD 需要ffmpeg 以--enable-libfdk-aac编译才能支持转码命令为-acodec libfdk_aac -aprofile aac_eld若 ffmpeg 未启用该库将无法生成 HomeKit 需要的 AAC-ELD 流这是实践中最常见的坑之一。5.3 采样率 / RTP 时间戳 / 帧长对照表文档给出了一张关键参数表决定 RTP 打包时的constantDuration每包音频时长毫秒与 MPEG-4 Audio Object TypeSampleRateRTPTimeconstantDurationobjectType8000608000/1000*6048039 (AAC ELD)160003016000/1000*3048039 (AAC ELD)240002024000/1000*2048039 (AAC ELD)160006016000/1000*6096023 (AAC LD)240004024000/1000*4096023 (AAC LD)解读表中RTPTime是每个 RTP 包承载的采样数每秒采样率 ÷ 每秒包数每一行都满足SampleRate / RTPTime × 1000 constantDuration毫秒即每个包时长恒为 480ms 或 960ms 的整数毫秒objectType39 对应 AAC-ELDEnhanced Low Delay23 对应 AAC-LDLow Delay与 pkg/aac/aac.go 中TypeAACLD23、TypeAACELD39的常量定义一一对应选型时要同时满足constantDuration匹配 HomeKit 请求的包时长480 或 960ms且 objectType 为 39/23 之一。结合 pkg/aac/README.md 的兼容性矩阵可知AAC-LD 在 22050/24000/32000 采样率下 ffmpeg 可编AAC-ELD 在 22050/24000/32000 采样率下 ffmpeg 与 VLC 均可支持而在 8000/16000 等低采样率下 QuickTime 支持但 ffmpeg/VLC 不支持这正是 HomeKit 音频需要特殊处理与转码的根源。六、与 go2rtc 生态的衔接HomeKit 客户端与服务端实战配置pkg/hap之上internal/homekit模块把 HAP 协议封装进 go2rtc 的流处理体系支持客户端与服务端两种模式。6.1 客户端模式接入 HomeKit 摄像头关键事实来自 internal/homekit/README.md使用 HomeKit 摄像头无需 Apple 设备它只是又一种取流协议HomeKit 设备只能配对一个生态系统已配对 iPhoneApple Home就无法再配 Home Assistant 或 go2rtc反之亦然可通过从 Home Assistant 导入配对来同时使用设备与 go2rtc 需处于同一局域网且 mDNS 互通可在 go2rtc Web 界面的 HomeKit 页面完成配对若看不到设备或设备无配对按钮说明已被其他生态占用需先解绑/重置。推荐配置HomeKit 音频转码为标准 AAC供 WebRTC/MSE/MP4/RTSP 使用streams: aqara_g3: - hass:Camera-Hub-G3-AB12 - ffmpeg:aqara_g3#audioaac#audioopus任意播放器可播的 RTSP 地址普通音频rtsp://192.168.1.123:8554/aqara_g3?videoaudioaac6.2 服务端模式把任意 H264 摄像头导出为 HomeKit 摄像头从 v1.7.0 起支持服务端模式两种用途导出任意 H.264 摄像头到 Apple HomeKit透明代理任意 HomeKit 摄像头Aqara、Eve、Eufy 等回 Apple Home——Apple 设备与摄像头之间视频直传而 RTSP/WebRTC/MP4 等取流则经 go2rtc 中转。注意HomeKit 摄像头只支持 H.264 视频与 OPUS 音频。最小配置streams: dahua1: rtsp://admin:password192.168.1.123/cam/realmonitor?channel1subtype0 homekit: dahua1: # 与 streams 中相同的流 ID默认 PIN - 19550224完整配置streams: dahua1: - rtsp://admin:password192.168.1.123/cam/realmonitor?channel1subtype0 - ffmpeg:dahua1#videoh264#hardware # 若摄像头不支持 H264必须转码HomeKit 必需 - ffmpeg:dahua1#audioopus # HomeKit 仅支持 OPUS 音频 homekit: dahua1: # 与 streams 中相同的流 ID pin: 12345678 # 自定义 PIN默认: 19550224 name: Dahua camera # 自定义摄像头名默认: 由流 ID 生成 device_id: dahua1 # 自定义设备 ID默认: 由流 ID 生成 device_private: dahua1 # 自定义密钥默认: 由流 ID 生成代理 HomeKit 摄像头streams: aqara1: - homekit://... - ffmpeg:aqara1#audioaac#audioopus # 可选音频转码 homekit: aqara1: # 与 streams 中相同的流 ID6.3 音频/视频的 RTP 细节补充OPUS 打包pkg/opus/homekit.goApple 不遵循 RFC 7587而是使用 RFC 3550 的时间戳规则支持 20msLAN与 60msLTE 场景两种包时长其中 60ms 模式下会把 3 个 20ms 帧合并为单包帧数标记0b1000_0011、TOC 置0b11输出采样率统一为 16000视频 RTPH.264 以 99 为载荷类型、MaxMTU1378分片配合srtp.Sessionpkg/srtp承载加解密。七、总结与延伸阅读pkg/hap是 go2rtc 中协议深度最高的模块之一从 TLV8、SRP 配对、ed25519 签名、Curve25519 密钥协商、ChaCha20-Poly1305 加密帧到 EVENT 长连接事件推送、H.264/OPUS RTP 打包与 AAC-ELD 转码覆盖了 HAP 协议栈的完整链路。你可以基于以下文件继续深入pkg/hap/server.goServer 侧配对/认证状态机pkg/hap/client.go 与 pkg/hap/client_pairing.goClient 侧拨号、配对与管理pkg/hap/accessory.go 与 pkg/hap/character.go配件/服务/特征模型pkg/hap/camera摄像头配件与服务协商pkg/hap/tlv8TLV8 编解码pkg/opus/homekit.go 与 pkg/aacHomeKit 音频处理internal/homekit/README.md模块级集成说明与配置示例。配套的有用资源文档原文整理Apple HomeKitADK 的 crypto.md 与 HAPPairingPairSetup.c配对流程权威参考、Extracting HomeKit Pairing Keys配对密钥提取、HAP in AirPlay2 receiver另一实现参照、HomeKit Secure Video Unofficial Specification 与 HAP-Specification-Non-Commercial-Version.pdfHAP 非商用规范全文。【免费下载链接】go2rtcUltimate camera streaming application项目地址: https://gitcode.com/GitHub_Trending/go/go2rtc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考