家里有两只虎斑猫,aki 和 kuro。上一篇把客厅的 Tapo C210 接进 Frigate,做出了录影回看、猫咪日记和每日精华影片。但 C210 挂在墙上拍客厅全景,喂食区刚好在它的死角。

我想找一台同时有摄影机和自动喂食功能的装置,补上这个视角。看起来很简单:一台宠物喂食器摄影机,能把影像串流接进 Frigate 就好。

结果我研究了十个品牌,才搞清楚一件事:这个产品品类里没有任何一台开放影像串流。不是某几台不支援 ,是整个品类结构上就没有。

每一台都锁云端,这不是巧合#

最先看的是米家智慧宠物喂食器 2 可视版。2026 年 3 月众筹上市,500 万画素、150° 超广角、940nm 红外夜视、5L 粮仓、还有秤重功能。而且 的原始码里,xiaomi 来源的装置过滤条件明确包含 .feeder.——看起来完美。

但读完 go2rtc 的 xiaomi 模组原始码才发现问题:它的影像传输只有两条路,小米自家的云端中继(CS2)或 ThroughTek Kalay(TUTK)P2P,全都要经过云端。不是「金钥交换要连网、影像走区网」,是连影像本身都走云端。挡掉外网,串流直接断。

对照我已经在用的 C210:Tapo 的 RTSP 是纯区网的,路由器断了外网,录影侦测全部照常。小米的摄影机在架构上跟它不是同一种东西。

IMPORTANT

后来在 go2rtc 社群翻到过一个重要的观察:go2rtc 的 xiaomi 来源支援清单里有 50 几台摄影机的回报,零台喂食器。作者自己也说过:「If a camera isn't on the list, don't ask if it works. No one knows until you try it.」

台湾官方只卖无镜头的标准版;可视版是中国机(人民币 449 ≈ NT$2,000),要淘宝或代购,没有保固。加上小米的资安纪录——2020 年有人在 Google Nest Hub 上看到别人家里的小米摄影机画面、2021 年 ThroughTek Kalay 被揭露 CVSS 9.6 的漏洞(影响约 8,300 万台装置)——我决定不买。

PETKIT + LocalKit:去云端化的代价#

接着找到了 YumShare Dual Gen2(P592)。它本身不支援本地串流,但 GitHub 上有一个叫 LocalKit 的第三方专案,宣称能把 PETKIT 装置完全去云端化,包含摄影机串流。

看了部署文件之后放弃了。它需要的东西:

  1. DNS 改写——把 PETKIT 的云端网域导向本地伺服器,让装置以为连到了原厂
  2. 凭证信任修改——装置有 cert pinning,要进去改凭证信任链
  3. 本地 MQTT broker——跑一个容器模拟 PETKIT 的云端 broker
  4. 装置上跑 go2rtc——每台有镜头的装置自己跑一份 go2rtc
  5. 韧体锁版本 895——安装脚本只认特定韧体版本,不能让 PETKIT 自动更新

而且这是一个人在维护的专案。Tapo 的 RTSP 是原厂支援的功能,PETKIT 的本地串流是踩在原厂疏忽上的临时立足点——原厂一推 OTA 就可能整套报废。

P592 台湾售价大约 NT$5,000。花这个钱买一台要锁韧体、搞 DNS 改写的喂食器,感觉方向就不对。

结构性原因:为什么没有厂商愿意开放#

研究完前几台之后,我开始理解为什么这整个品类都不开放影像串流。

摄影机品牌(Tapo、Reolink、EZVIZ、Imou)靠硬体赚钱,开放 RTSP 是增加卖点。喂食器品牌靠的是 App 黏着度和云端订阅——云端储存、AI 宠物辨识、远端观看,全部绑在自家 App 里。开放串流等于把使用者推给第三方 NVR,订阅就没人买了。

NOTE

连自家有 RTSP 传统的摄影机厂商,跨到喂食器品类也收掉了。Imou(大华消费线)的 PF1 喂食器摄影机,我找了三个来源都没有 RTSP 的证据:iSpy 的 Imou 装置表没有 PF1、HA 的 imou_life 整合零个相关 issue、Frigate 社群零回报。而且台湾没有通路。

买之前怎么知道:拆 APK 看影像 SDK#

既然卖场页面不会写「本机支援 RTSP」,要怎么在买之前判断一台喂食器的影像能不能接出来?

方法其实很简单:下载它的 App 安装档,解压缩看里面用了哪家影像 SDK。

本质上是一个 ZIP 档,lib/ 资料夹里的原生函式库(.so 档)会透露它用了哪家的影像传输 SDK。不需要反编译,不需要安装,只要看档名:

看到这些函式库代表用了能接进 Frigate 吗
libtuya_*.so涂鸦(Tuya)go2rtc 有 tuya://,但信令走云端
libmiot*.so、libmiss*.so米家go2rtc 有 xiaomi://,影像强制走云端
libIOTCAPIs.so、libAVAPIs.soThroughTek Kalay(TUTK)go2rtc 没有 tutk,要自己逆
libagora-*.so声网(Agora)商用云端 RTC,完全没有本地路径

光「看到 TUTK 函式库」还不够。关键是 App 有没有呼叫 SDK 里的区网直连功能——有些 App 虽然包了 TUTK SDK,但只走云端中继,根本不尝试区网。这个要再深一层看 dex 位元组码里的呼叫,不过对判断「完全没机会」vs「可能有机会」已经足够。

这就是零成本的买前筛选#

我用这个方法扫了十个台湾买得到(或可代购)的品牌:

展開
品牌App 套件名影像 SDKApp 有呼叫区网直连判断
FurSparkcom.ddpai.cpp_overseasTUTK Kalay有(12 处)✅ 有机会
Dogness 多尼斯—TUTK Kalay有(17 处)✅ 有机会
CATLINK—TUTK Kalay有(15 处)台湾只卖无镜头款
小米米家—TUTK + CS2有影像强制走云端
PETLIBRO—TUTK没有只走云端中继
Petwant—TUTK没有只走云端中继
PETKIT—Agora RTC—App 侧无本地路径
Petoneer—Agora RTC—同上
Hama Home(德国)—Tuya—App 底层 100% Tuya
maoxga 喵享家—Agora RTC—镜头确定接不进

有几个意外发现:

  • PETKIT 用的是 Agora,不是 TUTK。我一开始以为它跟 PETLIBRO 一样走 Kalay,结果完全不是——这也解释了为什么 LocalKit 非得进机器不可:App 端根本没有区网路径可以利用。
  • 德国 Hama 的自有 App 底层 100% 是 Tuya。外面看不出来,拆开一目了然。「查不出来它是不是涂鸦」这件事是真的,但「没办法查」不是。
  • maoxga(喵享家)的 App 里意外发现了 Matter SDK,代表喂食那半可能支援 Matter。但摄影机部分是 Agora,确定没有本地路径。

WARNING

App 里呼叫了区网直连函式,不代表韧体那边也支援。App 有呼叫只证明「开发者写了这段程式码」,装置端有没有对应的处理,要机器在手才知道。但反过来说,App 完全没有区网直连的呼叫,就可以直接排除。

最后选了 FurSpark#

筛完剩两台有机会:Dogness F10 和 FurSpark Big Eye。

FurSpark Big EyeDogness F10PETKIT P592
解析度2K1080P1080P
视角广角(官方不公布数值)165°140°
粮仓3.7L3.6L双料仓
储存内建 64GB、48h 回放——
特殊功能机械眼睑实体遮蔽镜头干电池备援猫脸辨识
影像 SDKTUTK(有区网直连)TUTK(有区网直连)Agora(无本地路径)
接进 Frigate需逆向需逆向需 LocalKit 改装
台湾最低价NT$3,990NT$4,200(momo 促销)~NT$5,000

FurSpark 的硬体最好、价格最低,而且 App 的套件名 com.ddpai.cpp_overseas 透露了一件有趣的事:开发商是深圳盯盯拍(DDPai),一家做行车记录器的公司。「美国 Furspark」是行销包装。

这反而是好消息——DDPai 的行车记录器有人公开拆解过,有一条本地 HTTP 介面的前例。这代表除了 TUTK P2P 之外,可能还有第二条路。

IMPORTANT

我没办法保证这台一定能接进 Frigate。所有资讯都指向「有机会」,但没有人做过。最后决定买的理由很简单:就算整合失败,它照样是一台能定时喂、有 App 看的喂食器。不碰韧体、原厂功能全部保留,最差的情况就是多一台普通喂食器。

影像接入:一个私有的 TCP 埠#

2026 年 9 月 13 日,喂食器到手。装好、喂完猫、连上 Wi-Fi,开始找影像。

没有 RTSP,但 daemon 活着#

用 nmap 扫装置开放的埠,554 上跑着 Hisilicon RTSP Streaming Media Server/1.0.0。但试了十几条路径,全部回 400 或 500——媒体源没有挂上去。RTSP daemon 是活着的,只是没人喂它内容。

NOTE

第一次探测的时候犯了一个低级错误:送出的 RTSP 请求用了相对 URI 而不是绝对 URI(rtsp://host:554/path),所以被回 400。用正确格式重送之后,OPTIONS 回 200、CSeq 正确回应,daemon 是有在解析的。但 DESCRIBE 某些路径回 500(路径认得、取不到媒体),其余回 400(路径不认得)。没有任何路径会自己吐出 SDP。

没有 ONVIF。装置回报的型号是 DDPaiQ3,SoC 是 Rockchip RV1126——hostname 直接留着原厂的 RV1126_RV1109 没改。

私有 TCP 6200#

转头去看其他埠。TCP 6200 有东西:连上去之后开始吐二进位资料。每个封包前面有 8 bytes 的标头,用十六进位看:

0a 0b 0c 00 [4-byte 大端长度] → 关键帧(I-frame) 0a 0b 0c 01 [4-byte 大端长度] → 一般帧(P-frame)

0a 0b 0c 是固定魔术位元组,第四个 byte 区分帧类型,接着四个 byte 是这个 NAL 单元的长度。把标头剥掉,剩下的直接丢给 ffmpeg,零解码错误。

用 ffprobe 量出的规格:

项目值
解析度1664 × 2224(直式)
帧率25 fps
编码H.264 High profile, level 5.0
码率~2530 kbps(≈ 15 GB/天)
音讯无(这条路径上没有音轨)
关键帧间隔4 秒

直式影像。官方页面有一段文字提到「expanding the upper and lower visual range from the previous 16:9 aspect ratio」——上一代是 16:9,新款的感光元件旋转 90° 安装,往上下扩展视野。从行车记录器的料改的。

WARNING

这条串流的规格会变。 我用历史录影的 ffprobe 验出来:上线第一天是 576×768 / level 3.1,隔天就跳到 1664×2224 / level 5.0。推测是 App 上的「视频品质」被改过(目前设在「超清」)。Frigate 的 detect 解析度必须跟着改,否则会降采样或过度采样,影响侦测品质。

架构:装置 → go2rtc → Frigate#

H.264(私有标头)RTSPWebRTC喂食器TCP 6200go2rtc(ffmpeg: 来源)Frigate(侦测+录影)浏览器

go2rtc 用 exec:ffmpeg 来源连接装置的 TCP 6200,ffmpeg 负责接收原始 H.264 位元组流并转成 RTSP。Frigate 从 go2rtc 拉 RTSP,同时负责侦测和录影。浏览器透过 WebRTC 直接从 go2rtc 拿画面。

有几个坑:

  • 只能开一条连线。 同时开第二条 TCP 6200 连线,会造成 camera_fps 飙升到 100 以上、NAL 错误,整个串流坏掉。手机 App 连线时也可能有同样的竞争。
  • go2rtc 必须绑全介面(:8554)。 ffmpeg producer 内部会把媒体 publish 到 rtsp://127.0.0.1:${port},如果 loopback 没监听就会 self-loop 失败。要靠防火墙规则保护——只允许 Frigate 容器的网段拉 RTSP,其余拒绝。
  • 浮水印不需要遮。 画面左下角有「Forspark Big Eye」和逐秒时间戳,但缩到 Frigate 的 motion 侦测解析度之后只剩约 1 个像素的面积,远小于 contour_area 门槛,自动被滤掉。

开画面要等几秒#

装置的关键帧间隔固定 4 秒,新连线要等下一个关键帧才能开始解码。平均等 2 秒、最坏 4 秒。C210 大约 0.1 到 1.7 秒。App 没有调整关键帧间隔的选项。

音讯那边的 ffmpeg 探测输入格式也会拖慢开画面(go2rtc 是依序启动 producer 的),加上 -f aac -probesize 32 -analyzeduration 0 之后从 ~3.2 秒降到 0.2 秒。两段加起来,开画面大约 3 秒。

喂食控制:云端 MQTT 桥接到 Home Assistant#

影像走本地,喂食控制走云端。两条路完全分开。

装置的即时状态和控制指令走的是厂商自己的 broker。我从 App 里逆出了连线方式和指令格式,写了一支 Python daemon(furspark_ha.py),让它一边连厂商的 MQTT 读状态,一边用 MQTT discovery 推到本地的 Home Assistant。

IMPORTANT

喂食控制的具体指令格式和云端端点是这台装置的私有协定,涉及其他使用者的装置安全,不在这里公开。下面只讲「做出了什么」,不讲「怎么发指令」。

22 个 entity,全挂在同一台装置底下#

类型项目
sensor余粮等级、喂食计划、今日计划出粮份数、下次投喂时间、干燥剂剩余天数、储存空间剩余与使用率、视讯品质、韧体版本
binary_sensor线上状态、故障
switch摄影机开关、指示灯、童锁、麦克风、喂食提醒、喂食语音
number喇叭音量、按键每次份数、喂食份量
button立即喂食、喂 1 份
text + button计划编辑器(格式 07:30x40, 18:00x2)、套用计划、计划启用

有几个容易搞错的地方:

  • 余粮是等级不是百分比。 装置回传的值是 0(充足)、1(偏少)、2(耗尽),不是 0–100。我一开始当成百分比用,结果「装满的显示 0%」。
  • 1 份 ≈ 1 克。 实测量的,说明书写「随饲料密度变动、不可调」。两只猫早上吃 40 克,就设 40 份。份数上限实测到 99 都能接受。
  • 只有常驻模式(--daemon)才会推控制项。 一次性模式只推 sensor——因为开关和按钮需要有人监听指令才有意义。

daemon 装成 systemd 服务,开机自启。每 10 分钟轮询一次状态。HA 里切开关会即时生效——实测过指示灯和摄影机开关,指令送出到装置状态改变不到一秒。喂食按钮没实测(会真的出粮),留给手动按。

即时音讯与双向对讲#

TCP 6200 那条串流没有音轨。但装置的录影档里有 AAC 音讯,代表麦克风是好的——声音只是不在 6200 这条路径上。

TUTK P2P 桥接#

音讯走的是 ThroughTek Kalay(TUTK)的 P2P 协定。go2rtc 的 41 种来源协定里没有 tutk,所以要自己建桥。

做法是从 App 的 APK 里提取 arm64 版的 TUTK SDK 函式库(libIOTCAPIs.so、libAVAPIs.so 等),用 在 x86 主机上跑起来,再用 Python 的 ctypes 呼叫 SDK 函式建立 P2P session、收音讯。

这是整个专案里最难的部分。TUTK SDK 是为 Android(bionic libc)编译的,要在 Linux(glibc)上跑,有好几个 ABI 不相容的地方需要用 shim 拦截:

展開
问题症状解法
pthread_mutex_t 大小不同bionic 4 bytes、glibc arm64 48 bytes,TUTK FIFO 内嵌 bionic 大小的 mutex → segfaultshim 拦截 pthread_mutex_*,用物件位址做 key、旁边维护真正的 glibc mutex
struct addrinfo 栏位顺序不同bionic 的 ai_canonname 和 ai_addr 对调 → 野指标shim 拦截 getaddrinfo,翻译成 bionic 的布局
init_array 里有 null 项目bionic linker 会跳过,glibc 直接呼叫位址 0 → segfault扫描每个函式库,全 null 的 init_array 归零 DT_INIT_ARRAYSZ
__errno 符号名不同bionic 用 __errno,glibc 用 __errno_locationshim 转接
_ctype_ 是指标不是阵列bionic 的 _ctype_ 是 const char*,给阵列会被当野指标shim 提供正确的指标版本

桥接的架构是一个按需 TCP server。go2rtc 连进来(有人在看画面)时才连 TUTK、开启音讯接收、输出 AAC-ADTS;没人看的时候释放 session,避免占用装置的 P2P 连线数。

AAC-LC16kHz monoAAC-ADTSTCP 6202H.264 + Opus喂食器TUTK P2P音讯桥接(qemu-user + ctypes)go2rtc(合轨)浏览器(WebRTC)

稳态资源消耗:CPU ~5%(一颗核)、RAM 67MB、音讯流量 ~65kbps。之前担心「24/7 跑 qemu 桥接很吃资源」是错的——那是建置时的瞬间尖峰,稳态极轻。

踩过的几个坑#

认证会过期。 连线需要的密码是从云端 API 算出来的,几天之内就会失效。失效时 DTLS 握手静静失败,错误码是「逾时」而不是「密码错误」——完全看不出是帐密问题。现在用 crontab 每 6 小时重算。

音讯断了会拖垮影像。 go2rtc 的多来源合轨是「全有全无」,音讯来源一连不上,整个串流就起不来,连影像一起黑掉。修法是让桥接「只要有 client 就永远吐得出音轨」——真音讯断超过 600ms 就补静音帧,TUTK 连线失败用指数退避重试。

WebRTC 需要 Opus。 音讯只给 AAC 的话,WebRTC 带不了(不支援 AAC),浏览器会退回 MSE,外网就会卡。加上 Opus 转码之后,WebRTC 能带音讯了,外网流畅。

双向对讲#

送话的协定也逆出来了。装置接受 AAC-LC 16kHz mono ADTS 格式的音讯(跟收音端一致)。辨识正确编码的方法很暴力:四种候选格式各配不同声数的音档(1/2/3/4 声),一次送完,让人在喂食器旁边数声音——只听到第三组的声数,就是那个格式。

NOTE

送话途中发现一个 bug 藏了六天:Python 的 struct.pack 用有号 int32 塞毫秒时间戳,值超过 2^31 直接丢 struct.error——送话在送出第一帧前就挂了,一帧都没送出去。前面四轮盲扫不同编码格式,测的全是一个根本没执行的路径。教训:确认「有没有真的送出去」比「送的格式对不对」更重要。

即时对讲(从浏览器麦克风直接说话)没做成。go2rtc 的 exec: 来源有 backchannel 支援,理论上可以,但浏览器切到对讲模式时 go2rtc 没有启动 producer。试了各种音讯格式和架构都一样,没查到根因。目前的替代方案是 HA 里放一个按钮,按了播放预录的提示音——用来叫猫吃饭够用了。

喂食器镜头上的分猫辨识#

喂食器的镜头装在机器上方,不锈钢碗在画面最下缘。猫吃饭的时候脸正对镜头,距离大约 40 公分,脸在画面里占约 37%——比 C210 从 3 公尺外拍到的 6% 大得多。理论上分辨 aki 和 kuro 应该更容易。

碗前区域#

在 Frigate 里定义了一个 feeder_bowl 区域,涵盖画面底部 5%。只有在这个区域里待满 10 秒的猫才算「到访」,过滤掉路过碗边的情况。

用历史事件验证这个设定:框底座标 ≥ 0.95 的事件,抽 8 张、8 张都在吃。0.88 到 0.95 之间的,8 张只有 2 张在吃。10 秒门槛加上区域限制,误判率很低。

加 zone 之前,daemon 算的是「猫在整个客厅停留 ≥10 秒」——镜头拍得到整个客厅,一天被灌到 30 次以上,多半是猫在餐桌或柜子上,根本不是吃饭。加了之后降到每天约 7.5 次,合理多了。

每餐合并#

daemon 从 Frigate 的事件 API 拉「今天在 feeder_bowl 区域被侦测到的猫」,同一只猫两笔事件间隔小于 2 分钟的合并成同一餐(Frigate 常把一顿饭切成好几段事件)。HA 里有两个 sensor:aki 和 kuro 的今日到访次数。

NOTE

命名是「到访」不是「进食」。镜头只能确定猫在碗前面待了多久,不能确定牠真的在吃——可能只是蹲着或理毛。最初命名成「今日进食」,被指正了:把推论当成事实。

整套跑起来的样子#

项目规格
硬体成本NT$3,990(FurSpark Big Eye,quoview 最低价)
月费NT$0
影像1664×2224 直式、25fps、本地即时串流(TCP 6200 → go2rtc → Frigate)
录影连续录影 3 天(~15 GB/天,跟 C210 的 ~10 GB/天 在同一个碟上)
喂食控制HA 里可切开关、调份量、编辑排程、一键喂食
音讯即时收音(TUTK 桥接 → Opus/AAC → WebRTC),HA 按钮播提示音
分猫碗前区域 + 10 秒门槛,每日到访次数 × 两只
侦测Frigate 物件侦测(cat / person),GPU 使用率 6%
常驻服务furspark-ha.service(喂食控制)、tutk-audio-bridge(音讯)、go2rtc(串流)

跟 Furbo 对照:

Furbo 360° + 宠物保姆订阅这套
硬体NT$5,399(不绑约单机)NT$3,990
年费NT$1,840 起NT$0
录影AI 片段,保留 3–7 天连续录影,保留天数自己设
回看限订阅随时
双向通话有播预录提示音
丢零食有定时喂食(不丢零食)
宠物日记订阅功能上一篇做的
分辨个体订阅功能(多宠辨识)Frigate 自训分类(碗前近拍命名率 ~89%)
资料位置Furbo 云端本地硬碟

最后一件事。这台喂食器的 RTSP daemon 是活着的,媒体源没挂上去。韧体里有两个看起来像是「开启 RTSP」的指令(名字是从社群公开的行车记录器韧体字串表里找到的),试了的话说不定能直接拿到带音轨的 RTSP 串流。但那会改变装置状态,有中断现在已经稳定的 6200 直播的风险,我还没试。等哪天喂食器韧体更新坏了什么东西、被迫重新设定的时候再说。