一、 项目背景与目标 🎯
我手上有一块RV1126的开发板,外接了一个SunplusIT SPCA2688 USB摄像头。我的目标是搭建一个可以从任何地方远程访问的实时监控系统。
初始本地推流实现(详细教程请参考RV1126平台(Buildroot Linux)+ SunplusIT SPCA2688 USB摄像头 RTSP推流全流程复盘与问题解决记录-优快云博客
首先,我在开发板上成功实现了局域网内的RTSP推流。我使用的是 MediaMTX
作为RTSP服务器,并用 FFmpeg
来捕获摄像头数据、进行编码,然后推送到MediaMTX
。
我将所有命令都写在了一个脚本 rtsp_usb_camera_test.sh
中,方便一键启动。脚本内容如下
#!/bin/sh
# 启动 MediaMTX RTSP 服务器,并在后台运行
/mediamtx_v1.13.0_linux_armv7/mediamtx &
# 等待3秒,确保服务器已完全启动
sleep 3
# 使用 FFmpeg 从v4l2设备捕获视频,用libx264软编码,并推送到本机的RTSP服务器
ffmpeg -f v4l2 -input_format mjpeg -video_size 640x480 -framerate 15 -i /dev/video45 \
-c:v libx264 -preset ultrafast -tune zerolatency \
-f rtsp -rtsp_transport tcp rtsp://127.0.0.1:8554/live/main_stream
在开发板上运行这个脚本后,我可以在同一个WiFi下的电脑或手机上,用VLC播放器打开地址 rtsp://192.168.1.16:8554/live/main_stream
来观看实时监控画面。
这一切在家里用起来很方便,但我萌生了一个新想法:我希望能随时随地,无论是在公司、在路上还是在朋友家,都能看到这个摄像头的画面。
我的核心目标是:
-
实现公网远程访问。
-
方案必须是永久免费的。
-
要足够稳定可靠。
二、 方案选择:为什么是ZeroTier
经过一番了解,我发现有几种主流方案,比如“公网IP+DDNS+端口转发”、“FRP内网穿透”等。但这些方案要么依赖运营商提供公网IP,要么需要自己有一台云服务器,都有些门槛。
最终,我选择了 ZeroTier。它的理念深深吸引了我:创建一个“虚拟的局域网”,把我所有的设备(开发板、手机、电脑)都连接到这个虚拟网络里。设备之间就像真的在同一个路由器下一样,可以通过虚拟IP直接通信。最关键的是,它的免费套餐对于个人使用来说完全足够,而且安全性很高,不需要把任何端口暴露在公网上。
三、 实施过程:一步步实现远程访问
我的计划很清晰:给开发板和我的手机都装上ZeroTier客户端,让它们“手拉手”,然后通过虚拟IP访问视频流。
-
注册和创建网络:先去ZeroTier官网
my.zerotier.com
注册了一个账号,过程很简单。登录后,我点击“Create A Network”,系统立刻为我生成了一个16位的网络ID(Network ID),这个ID就是我虚拟局域网的唯一门牌号。 -
手机入网:ZeroTier | Download我在手机上下载了ZeroTier One的App,输入网络ID,轻松加入了网络。并且在
my.zerotier.com
官网后台的“Members”列表里,给我的手机勾选了“Auth?”,批准了它的加入。手机很快就获得了一个10.144.x.x
开头的虚拟IP。电脑入网也是同理,在这里ZeroTier | Download下载电脑版的,安装之后打开,然后选择join New Network,把上面第一步生成的网络ID输入进去 -
在开发板上安装客户端:这是挑战的开始。我的RV1126是Buildroot制作的精简Linux系统,没有
apt-get
,连curl
命令都没有。官方提供的一键安装脚本完全用不了。 -
攻克安装难关:我最终采用了最原始也最可靠的方法:
-
在电脑上Index of /dist/debian/bullseye/下载了ZeroTier的
.deb
安装包(armhf
架构,兼容rv1126的armv7l架构
开发板)。 -
在我的Ubuntu虚拟机里,用
dpkg-deb -x
命令解压这个.deb
包,解压后,可以看到解压目录下的usr/sbin目录下的zerotier-one和zerotier-cli这两个
最核心的可执行文件 -
将
zerotier-one和zerotier-cli
文件从Ubuntu虚拟机传输到开发板。我使用的是adb push
命令,这个方式对于连接了USB的开发板来说非常方便。adb pushzerotier-cli /root
-
-
启动服务与最终连接:为了避免每次都手动敲大量命令,我编写了一个Shell脚本,命名为push_stream_to_internet.sh,将所有在开发板上需要执行的命令都整合了进去。这个脚本帮我完成了移动文件、设置权限、创建开机自启服务、启动服务、并最终加入网络的所有操作。下面是这个脚本的完整内容:
#!/bin/sh # --- 欢迎信息 --- echo "=============================================" echo "=== ZeroTier 智能安装与启动脚本 v2.0 ===" echo "=============================================" echo "" # ---------------------------------------------------------------------- # 关键改动:增加端口健康检查与自动清理功能 # ---------------------------------------------------------------------- echo "--- 步骤 1/6: 健康检查:清理端口 9993 ---" PID_TO_KILL=$(netstat -lnp | grep ':9993' | awk '{print $7}' | sed 's/\/.*//') if [ -n "$PID_TO_KILL" ]; then echo "警告:端口 9993 已被进程 PID: $PID_TO_KILL 占用。正在强制终止..." kill -9 "$PID_TO_KILL" sleep 2 echo "✅ 旧进程已清理。" else echo "✅ 端口 9993 状态正常。" fi # ---------------------------------------------------------------------- # --- 文件检查与准备 --- echo "--- 步骤 2/6: 检查 ZeroTier 执行文件 ---" if [ ! -f "/usr/bin/zerotier-one" ]; then echo "在 /usr/bin/ 未找到zerotier-one,正在从源位置拷贝..." if [ -f "/mediamtx_v1.13.0_linux_armv7/zerotier-one" ]; then cp "/mediamtx_v1.13.0_linux_armv7/zerotier-one" "/usr/bin/zerotier-one" else echo "错误:在 /mediamtx_v1.13.0_linux_armv7 目录下也未找到 'zerotier-one' 文件!" exit 1 fi fi echo "✅ 执行文件准备就绪。" # --- 获取 Network ID --- echo "--- 步骤 3/6: 配置 Network ID ---" echo "请输入您的16位 ZeroTier Network ID,然后按回车:" read NETWORK_ID if [ -z "$NETWORK_ID" ]; then echo "错误:Network ID 不能为空!" exit 1 fi # --- 安装与配置 --- echo "--- 步骤 4/6: 设置权限与目录 ---" chmod +x /usr/bin/zerotier-one mkdir -p /var/lib/zerotier-one echo "✅ 完成" echo "--- 步骤 5/6: 创建开机自启服务 ---" cat > /etc/init.d/S99zerotier << EOF #!/bin/sh start() { echo "Starting ZeroTier One..." /usr/bin/zerotier-one -d } stop() { echo "Stopping ZeroTier One..." PID=\$(ps | grep '[z]erotier-one' | awk '{print \$1}') if [ ! -z "\$PID" ]; then kill \$PID; fi } case "\$1" in start) start ;; stop) stop ;; restart) stop; start ;; *) echo "Usage: \$0 {start|stop|restart}"; exit 1 ;; esac exit 0 EOF chmod +x /etc/init.d/S99zerotier echo "✅ 完成" # --- 启动服务 --- echo "--- 步骤 6/6: 启动服务并加入网络 ---" /etc/init.d/S99zerotier start sleep 3 zerotier-cli join "$NETWORK_ID" echo "✅ '加入网络' 请求已发送!" echo "" # --- 最终提示 --- echo "======================================================" echo "🎉 恭喜!脚本执行完毕!" echo "🚨【 最后一步,非常重要!】🚨" echo "请立即登录 ZeroTier 官网后台 (my.zerotier.com)," echo "找到这台新设备,并把它前面的复选框打勾授权!" echo "======================================================"
6.最终测试:在经历了一系列问题的排查后,我在开发板上运行
zerotier-cli listnetworks
,终于看到了梦寐以求的虚拟IP!然后,我在手机VLC里,输入了rtsp://<开发板的虚拟IP>:8554/live/main_stream
,看到了清晰的画面。成功!
四、 遇到的问题与解决方案(踩坑全记录)🚧
这段旅程并非一帆风顺,我遇到了大大小小9个问题。正是解决了这些问题,才让我对嵌入式Linux和网络知识有了更深刻的理解。
问题1:开发板系统太精简,无法自动安装
-
现象: 在开发板上想用
apt-get
或curl
一键安装ZeroTier,结果提示command not found
。 -
原因分析: 我的RV1126是Buildroot构建的,为了追求极致的性能和最小的体积,系统里没有包含这些包管理器和下载工具。
-
解决方案: 手动部署。最终采用的方法是:在电脑上下载适配ARM架构的
.deb
包,在Ubuntu虚拟机中用dpkg-deb -x
命令解压,从中提取出zerotier-one
和zerotier-cli
这两个最核心的可执行文件,再通过adb push
命令传输到开发板上。
问题2:在官网和GitHub上找不到预编译包
-
现象: 按照一些教程去ZeroTier官网或GitHub Releases页面下载,发现
.tar.gz
格式的通用二进制文件都消失了,只有源代码。 -
原因分析: ZeroTier官方调整了发布策略,不再提供显式的通用二进制包下载,这给非主流发行版的用户带来了困难。
-
解决方案: 曲线救国。既然找不到通用包,我就去找特定发行版(Debian)的包。我从下面的地址下载了
armhf
架构的.deb
包,这成为了后续提取文件的基础。下载地址:
https://download.zerotier.com/dist/debian/bullseye/zerotier-one_1.12.2_armhf.deb
问题3:解压.deb
包时,提示符号链接创建失败
-
现象: 在Ubuntu虚拟机里解压
.deb
包时,报错tar: ... Cannot create symlink: Operation not supported
。 -
原因分析: 我当时偷懒,直接在Windows和Ubuntu的共享文件夹(VMware的hgfs)里进行解压。Windows的NTFS文件系统原生不支持Linux的符号链接(symlink),导致解压失败。
-
解决方案: 更换工作目录。将
.deb
文件从共享文件夹复制到Ubuntu自己的主目录(~/
)下,这里的ext4文件系统完美支持所有Linux特性,再执行解压命令,问题解决。
问题4:启动服务时,报错端口9993被占用
-
现象: 运行启动命令后,报
fatal error: cannot bind to local control interface port 9993
。但用ps
命令又可能看不到zerotier-one
进程。 -
原因分析: 这是个“假死”现象。很可能是之前的进程异常崩溃,但它占用的网络端口没有被内核及时释放,成了一个“僵尸端口”。
-
解决方案: 精准定位并强行终止。使用
netstat -anp | grep 9993
命令,它能无视进程状态,直接查出是哪个进程ID(PID)占用了该端口。然后使用kill -9 <PID>
命令,将这个“幽灵”进程彻底杀死,从而释放端口。
问题5:启动服务时,报错找不到TUN/TAP设备
-
现象: 解决了端口问题后,又出现新错误
could not open TUN/TAP device: No such file or directory
。 -
原因分析: 这是个更底层的系统问题。意味着我的Linux内核在编译时,就没有包含“虚拟网卡”(TUN)这个功能。ZeroTier依赖此功能才能工作。
-
解决方案: 重新编译内核。我回到了Buildroot开发环境,执行
make linux-menuconfig
,进入内核配置菜单。按照路径Device Drivers -> Network device support
,找到了Universal TUN/TAP device driver support
选项,并将其设置为y
(<*>
,直接编译进内核),然后重新make
生成新固件并烧录。
问题6:加入网络后,无法获取虚拟IP
-
现象: 在开发板上执行
zerotier-cli join <nwid>
后返回200 join OK
,但listnetworks
命令的结果一直只有表头,没有任何网络信息。# 解决前 [root@ATK-DLRV1126:~]# zerotier-cli listnetworks 200 listnetworks <nwid> <name> <mac> <status> <type> <dev> <ZT assigned ips>
-
原因分析: 设备发出“敲门”请求后,我作为“房主”,没有在ZeroTier官网后台批准它进门。
问题7:远程视频画面花屏、卡顿严重
-
现象: 手机在4G网络下终于能看到画面了,但画质极差,满屏的马赛克和色块,根本无法正常观看。用
zerotier-cli peers
诊断,看到了RELAY
状态# 解决前 [root@ATK-DLRV1126:/]# zerotier-cli peers 200 peers <ztaddr> <ver> <role> <lat> <link> <lastTX> <lastRX> <path> 0678ebc45f - LEAF -1 RELAY ...
-
原因分析: 我的手机和开发板之间的连接模式是
RELAY
(中继),而不是DIRECT
(直连)。数据通过服务器中转,延迟和带宽都极差,无法承载视频流。 -
解决方案: 优化网络,打通直连路径。我登录到开发板连接的路由器(天翼网关)后台,在“高级设置” -> “端口映射”功能里,为开发板的局域网IP(
192.168.1.16
)添加了一条规则,将UDP协议的9993端口转发出去。-
虚拟服务名称:
ZeroTier
-
局域网IP:
192.168.1.16
-
服务协议:
UDP
-
内部端口:
9993
-
外部端口:
9993
# 解决后 [root@ATK-DLRV1126:/]# zerotier-cli peers 200 peers <ztaddr> <ver> <role> <lat> <link> <lastTX> <lastRX> <path> 632ea29085 1.14.2 LEAF 265 DIRECT 4459 13116 35.209.122.2/28655 ...
-
问题8:手机使用移动数据时无法连接ZeroTier
-
现象: 手机关掉WiFi用流量,ZeroTier App的连接开关打不开,并提示
Not on Wi-Fi...
。 -
原因分析: ZeroTier App为了防止误耗用户流量,默认设置只在Wi-Fi下工作。
-
解决方案: 修改App设置。在ZeroTier App的设置(右上角三个点)里,找到
Use Mobile Data
(允许使用移动数据)选项,并将其开启。
问题9:远程播放时,局域网无法同时观看
-
现象: 当手机通过ZeroTier观看视频时,局域网内的另一台电脑用VLC就无法打开视频流了。
-
原因分析: 性能瓶颈。我的
ffmpeg
命令使用的是CPU进行软件编码(libx264
),这本身就消耗了大量CPU资源。当MediaMTX
服务器需要同时服务两条视频流(一条给ZeroTier,一条给局域网)时,开发板的CPU和网络I/O不堪重负,导致无法响应新的连接请求。 -
解决方案: 承认性能极限。对于RV1126这样的嵌入式设备,同时进行实时编码和多路网络分发是一项沉重的任务。这个问题暂时作为性能极限来接受。后续优化的方向可以是利用硬件编码来解放CPU,或者降低视频流的码率和分辨率。
五、如何在远端查看开发板上接的摄像头的视频流:
1.开发板已经在运行推流程序,一直在采集摄像头画面同时往外推流
2.执行push_stream_to_internet.sh脚本,保证开发板已经加入到Network ID,前面步骤已经介绍过
3.手机端打开zeroTier One软件,保持NETwork ID为加入状态,如下图所示
此时相当于你的手机和开发板已经加入了同一个虚拟局域网内(可以把NETwork ID理解成网关)
3.用手机端查看视频方法:打开vlc软件,打开串流,输入地址 rtsp://为开发板分配的虚拟地址:推流端口号/live/main_stream
然后就可以看到开发板连接摄像头采集到的视频画面:
4.用电脑查看视频方法:打开vlc软件,打开网络串流,输入地址 rtsp://为开发板分配的虚拟地址:推流端口号/live/main_stream,即可正常观看,效果图如下:
六、流程梳理与原理总结
流程总结:摄像头 -> FFmpeg -> MediaMTX -> ZeroTier虚拟网卡 -> 互联网加密隧道 -> 手机的ZeroTier虚拟网卡 -> VLC播放器
-
摄像头: 硬件设备,负责捕捉现实世界的动态画面,并将其转换成原始的数字视频信号。
-
FFmpeg: 一款强大的视频处理工具,在这里它扮演“编码和推送”的角色,将摄像头的原始信号实时编码成高效的H.264网络视频格式,然后主动推送到MediaMTX服务器。
-
MediaMTX: 一个轻量级的流媒体服务器,在这里它作为“分发中心”,接收来自FFmpeg的视频流,并按照RTSP协议,准备好随时将它分发给前来请求的播放器。
-
ZeroTier虚拟网卡 (开发板端): 这是数据的“加密打包站”,它将MediaMTX准备好的视频流数据进行加密和封装,准备通过ZeroTier的私人网络发出去。
-
互联网加密隧道: 这是ZeroTier建立的“私人高速公路”,视频数据包在这条点对点的加密通道中,安全、直接地从您家里的网络穿梭到您手机所在的网络。
-
手机的ZeroTier虚拟网卡: 这是数据的“解密接收站”,它接收从隧道传来的加密数据包,将其解密还原成原始的视频流数据,然后交给手机的操作系统。
-
VLC播放器: 最终的“电视机”,它向开发板的ZeroTier虚拟IP地址发起播放请求,接收到解密后的视频流数据,并将其解码、呈现在您的手机屏幕上。
这套方案的原理是:首先在开发板上用 FFmpeg 和 MediaMTX 搭建一个本地RTSP“电视台”,然后通过 ZeroTier 建立一个跨越互联网的加密虚拟局-域网,最后让您的手机VLC通过分配到的虚拟IP地址,直接“收看”这个电视台的节目,从而实现安全、免费的远程监控。
七:心得体会
经历了上述所有挑战后,我终于实现了最初的目标。现在,无论我身在何处,只要手机有网络,就能随时打开VLC,输入 rtsp://为开发板分配的虚拟地址:推流端口号/live/main_stream,看到RV1126上的摄像头采集到的实时画面。
这套推流方案有以下优点:
-
高度安全 :无需在路由器上暴露任何公网端口,所有视频流都经过端到端加密,只有您授权的设备才能访问,从根本上杜绝了被攻击的风险。
-
零成本运行 : 核心软件(ZeroTier, FFmpeg等)完全免费,没有商业摄像头常见的云存储或月度订阅费用,实现了永久的免费远程访问。
-
数据主权与灵活性 : 完全控制自己的视频数据,它不会上传到任何第三方服务器。同时,整套系统不受任何厂商限制,您可以自由更换硬件、软件和定制功能。
-
强大的网络适应性 : 无需公网IP,无论您家里的网络环境多么复杂,也无论您的手机在任何地方(4G、5G、Wi-Fi),都可以通过一个固定的虚拟IP地址稳定连接。