ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

用Docker部署go2rtc统一接入多品牌摄像头,实现低延迟Web预览

用Docker部署go2rtc统一接入多品牌摄像头,实现低延迟Web预览 去年下半年我家里陆续加了几个摄像头一开始只有一台海康后来又添了一台大华再后来把自己折腾的树莓派摄像头也接了进去。结果就是手机上看A品牌要装A的App看B品牌要打开另一个App浏览器里想预览一下RTSP流又直接被拒。那段时间我试过写FFmpeg命令行转流、用VLC做二次流甚至专门起了个Nginx来转发折腾了好几天最后还是靠 Docker 部署 go2rtc 把问题解决了。一台小主机把所有摄像头统一收编成了一套多协议流媒体平台浏览器直接打开Web界面就能预览延迟还非常低。这篇文章就是一次完整的实战记录go2rtc 到底解决了什么问题、Docker 部署时需要注意什么、各种型号的摄像头怎么接入、跑起来之后怎么调优、以及我在实际使用中踩过且成功定位的几个坑。不管你是智能家居爱好者、家里装了多个品牌摄像头的普通用户还是准备给单位做一套轻量视频预览方案的技术人员这套流程都可以直接照搬。1. 为什么我在折腾了一周各种方案后最后留下的是 go2rtc1.1 家里的摄像头和协议有多乱先说一个很多人会忽略的现实安防摄像头市场嘴上说着标准实际上每个品牌都有自己的小算盘。海康的RTSP地址路径和大华不一样部分大华设备默认不开RTSP需要通过专门设置激活TP-LINK、萤石这类偏消费级的产品有的固件里干脆没有RTSP开关只给你一个云端App还有一些 DIY 玩家自己用 ESP32-CAM 做的MJPEG摄像头更是什么标准都不沾。我从实际使用中汇总了一下家里最常见的摄像头来源大概分成这么几类海康、大华这类传统安防摄像头主要走RTSP但地址格式、子码流命名规则各不相同。小米、360等互联网品牌摄像头很多默认不开放RTSP要么用App要么得破解或刷固件。树莓派摄像头、USB摄像头属于本地采集设备原生没有网络流需要额外推流。ESP32-CAM、OV5647这类开发板摄像头输出通常是MJPEG或者裸视频流。更麻烦的是浏览器不支持RTSP协议VLC虽然能看RTSP但手机端、Web端、大屏端总不能每台设备都装一个专业播放器。所以第一需求就是一个翻译官把各种乱七八糟的输入协议统一收进来再转成浏览器能直接播放的输出协议。1.2 go2rtc 在整个链路里扮演的角色go2rtc 是一个用 Go 写的轻量级流媒体服务Docker镜像搜索名称是alexxit/go2rtc。它做的事情可以简单理解成一台中转站左边对接摄像头的 RTSP、RTMP、MJPEG、ONVIF 等输入源右边对外输出 WebRTC、HLS、MSE、MP4 等浏览器能直接播放的格式。它最打动我的一点是按需拉流。传统方案里只要配了转流服务它就会一刻不停地从摄像头拉数据、转码、往外推哪怕根本没人看。go2rtc 不是这样这个得重点讲一下因为很多人第一次用的时候会困惑为什么不看的时候连摄像头连接都断开了它不是故障是设计。当我在 go2rtc 的 Web 界面里打开一个摄像头画面它才去建立与摄像头的连接拉取源流然后根据观看端的需求转成对应格式当我看完关掉页面连接会自动断开。这样的好处是摄像头不用一直维持多个连接CPU、内存和带宽也只有在真正需要的时候才被占用和按需供暖的逻辑完全一样。go2rtc 的另一个价值是它的原生协议支持。以前想实现类似功能至少得组合三个工具FFmpeg负责拉流转码、一个流媒体服务器负责分发、Nginx负责Web层。现在一个 go2rtc 就把这条链路的大部分工作做完了而且它内置的Web界面可以直接查看串流状态、配置文件和实时日志排障非常方便。1.3 用 Docker 跑它的理由有人会问go2rtc 本身也提供二进制文件直接装在系统里不就行了可以但我个人强烈建议用 Docker。原因有三层。第一层是依赖隔离。go2rtc 如果要处理一些特殊流源会调用 FFmpeg而 FFmpeg 在宿主机上装起来本身就是一个依赖泥潭。用Docker镜像这些依赖已经打包在镜像里了换一台机器重新拉镜像就能跑完全不用在系统里折腾编译环境。第二层是升级与回滚方便。go2rtc 更新频率不算低如果用二进制版本每次升级都要下载、覆盖、重启出了新问题还得找旧版本。用 Docker 只需要换掉镜像标签一条命令就能切换版本出问题马上回滚。第三层是迁移简单。整份配置就是go2rtc.yaml一个文件配置目录一打包到新机器上一解压、启动所有摄像头配置原样复现。对于想精简家里服务器环境的玩家来说这个可维护性非常关键。2. 部署前必须搞懂的三大底层设定2.1 协议转换输入有多杂输出就要有多全go2rtc 之所以叫这个名字是因为它把 WebRTC 作为核心输出协议。但它的输入输出能力远不止 WebRTC 一种。我在配置它之前对照官方文档把协议能力梳理了一遍列成一张表看就很清楚了方向支持的协议典型用途输入RTSP、RTMP、ONVIF、MJPEG/HTTP、HLS、WebRTC接入各类摄像头、推流软件、本地视频流输出WebRTC、MSE、HLS、MP4、RTSP、RTMP浏览器实时预览、播放器观看、外部系统取流辅助FFmpeg 管道处理无法直接读取的编码格式或采集设备这几种输出协议各有适用场景。WebRTC 延迟最低实测在局域网内能控制在半秒以内适合实时预览和对讲MSE 延迟在一两秒左右适合对实时性要求不高但希望比 HLS 更跟手的场景HLS 兼容性最好苹果手机自带的浏览器也能直接放但延迟通常会到五秒以上适合录像回放类应用。我在实际选择时的经验法则是同一个流只要设备支持就优先用 WebRTC 看实时画面用 HLS 做回放和分享链接。前者要的就是低延迟后者玩的就是兼容性各干各的活。2.2 配置文件的骨架和习惯go2rtc 的配置是一个 YAML 文件默认挂在容器里的/config/go2rtc.yaml。整个文件的结构非常清晰我刚接触时只花了一个晚上就完全搞明白了。一份最基本的配置文件长这样log: level: info api: listen: :1984 webrtc: candidates: - 192.168.1.100:8555 - stun:8555 - stun:19302 streams: test: - rtsp://admin:your_password192.168.1.50:554/Streaming/Channels/101这段配置里api.listen指定了 Web 管理界面的监听端口默认是 1984webrtc.candidates是 WebRTC 协商时对外公布的地址列表也是让 WebRTC 能在跨网段、跨NAT时连上的关键配置后面第八章的坑三里我详细展开streams就是个命名 - 源列表的映射test是我给摄像头起的名字源地址指向海康摄像头的 RTSP。需要注意一个习惯streams下面每个名字都可以接一个列表也就是一个逻辑流可以由多个源组成。比如填两个不同清晰度的源地址go2rtc 会尝试自动选择可用源当一个地址失效时还能尝试另一个。我喜欢把主码流和子码流都配进去这样既保证了画质又给远程预览留了退路。2.3 端口与网络模型选错等于白搭go2rtc 默认会监听几个常用端口理解它们的作用就能避免很多服务起来了但打不开页面的迷茫。我的理解就是这样1984是管理API和Web页面端口也是浏览器访问HLS/MSE流的入口。8554是 RTSP 输出端口外部播放器或者 NVR 可以通过rtsp://主机IP:8554/流名称来取流。9120是 ONVIF 相关功能使用的端口用于摄像头自动发现。WebRTC 还需要一段 UDP 端口做媒体数据传输Docker 部署时这段端口也要映射出来。如果你是在 Linux 服务器或者 NAS 上部署我强烈建议直接用network_mode: host也就是让容器直接共享宿主机网络。这样最省心所有端口自动暴露在局域网里WebRTC 的 UDP 协商也不会因为端口映射出问题。我的家庭服务器就是常年 host 模式跑的。但如果你用的是 Windows 或 macOS 上的 Docker Desktop情况又不一样了这个我放到下一章展开。3. 正式部署一条 docker compose 把服务拉起来3.1 准备目录和写配置我在服务器上选择把它跑在一个独立的目录里方便管理。目录结构是这样~/go2rtc/ docker-compose.yml go2rtc.yaml media/media目录先建好后面如果要接录像或者临时文件操作会用到没有也能启动。接着写docker-compose.yml我这里用的是 host 网络模式版本适合部署在 Linux 主机或 NAS 上services: go2rtc: image: alexxit/go2rtc:latest container_name: go2rtc restart: unless-stopped network_mode: host environment: - TZAsia/Shanghai volumes: - ./go2rtc.yaml:/config/go2rtc.yaml - ./media:/media然后写go2rtc.yaml。第一次先别急着把所有摄像头都填进去我建议先放一个测试流把服务跑通了再逐台接入。我就是用门口的海康摄像头做测试的log: level: info api: listen: :1984 streams: front_door: - rtsp://admin:your_password192.168.1.64:554/Streaming/Channels/101Streaming/Channels/101是海康典型的主码流路径注意不是所有品牌都一样大华就是另一种写法后面章节会给出对照。3.2 启动命令与验证启动就两条命令cd ~/go2rtc docker compose up -d然后可以看启动日志docker compose logs -f go2rtc正常启动后日志里能看到类似这样的信息API 服务监听在:1984RTSP 服务监听在:8554这说明核心服务已经起来了。接着在浏览器打开http://你的服务器IP:1984应该能进入 go2rtc 的 Web 控制台。Web 控制台里有两个地方我建议第一时间使用。一个是左侧的 Streams 页面它可以看到所有配置的摄像头状态点进去就能预览画面另一个是页面上的输入框可以直接临时输入一个 RTSP 链接来测试不用改配置文件非常适合验证摄像头地址是否写对。如果能看到画面恭喜基础链路已经通了。如果打不开先检查防火墙是否放行了 1984 端口再看 Docker 容器的端口映射状态。3.3 Docker Desktop 用户与 Linux/NAS 用户的差别这里必须单独提醒一下在 Windows 或 macOS 上用 Docker Desktop 的朋友因为这两类平台的网络模型完全不同。在 Linux 和群晖、威联通这类 NAS 上network_mode: host是真正让容器完全共享宿主机网络局域网内其他设备可以直接访问容器监听的所有端口ONVIF 自动发现、WebRTC UDP 通信都很自然。而在 Windows / macOS 的 Docker Desktop 上这个 host 模式是假的实际上还是跑在虚拟机里没法真正共享宿主机网络。所以我建议这种情况下改用 bridge 模式显式映射端口services: go2rtc: image: alexxit/go2rtc:latest container_name: go2rtc restart: unless-stopped ports: - 1984:1984 - 8554:8554 - 9120:9120 - 50000-50010:50000-50010/udp environment: - TZAsia/Shanghai volumes: - ./go2rtc.yaml:/config/go2rtc.yaml - ./media:/media其中50000-50010/udp是 WebRTC 媒体端口范围不映射的话浏览器通过 WebRTC 播放很可能会卡在连接状态。另外Windows 下如果希望长期稳定运行我建议把整个 Docker 环境切到 WSL2 后端比默认的 Hyper-V 后端在网络兼容性上要省心很多。4. 接入各路摄像头从手动 RTSP 到自动 ONVIF4.1 手动添加 RTSP手把手示例接入摄像头最通用的方式就是填 RTSP 地址。但不同品牌的地址规则差异很大我花了不少时间整理自己设备的地址也推荐你在配置之前先用 VLC 或者 ffprobe 验证一遍源地址是否能出流这样能减少很多暗中排查的时间。常见品牌大致是这些写法每个品牌的用户密码都是摄像头的Web管理账号品牌主码流 RTSP 地址示例海康威视rtsp://用户:密码IP:554/Streaming/Channels/101大华rtsp://用户:密码IP:554/cam/realmonitor?channel1subtype0TP-LINKrtsp://用户:密码IP:554/stream1通用ONVIF设备rtsp://用户:密码IP:554/onvif1海康的101代表第一通道主码流102是第一通道子码流大华那边是subtype0主码流、subtype1子码流。子码流分辨率低、码率小如果只是手机端远程瞄一眼用子码流绝对更划算。对应地我在go2rtc.yaml里是这么配的streams: front_door: - rtsp://user:pass192.168.1.64:554/Streaming/Channels/101 - rtsp://user:pass192.168.1.64:554/Streaming/Channels/102 backyard_sub: - rtsp://user:pass192.168.1.65:554/cam/realmonitor?channel1subtype1把主码流和子码流都写进同一个流下面go2rtc 会自动选路。我发现如果某个源不可用它会在请求时切换尝试另一个这个机制对弱网环境特别友好。4.2 让 go2rtc 自动发现局域网摄像头手动填地址固然可靠但如果你的摄像头是支持 ONVIF 协议的go2rtc 还有一个更好用的方式自动发现。ONVIF 是安防设备互通的工业标准支持它的摄像头就像一个即插即用的设备可以通过标准接口被自动识别、取到 RTSP 地址和媒体参数。go2rtc 里可以用onvif://加网段的方式做扫描配置非常直接streams: lan_cameras: - onvif://admin:password192.168.1.0/24它会扫描192.168.1.x整个网段里支持 ONVIF 的设备。扫描成功后你经常可以在界面上看到设备信息甚至直接拿到 RTSP 地址省得挨个去说明书里翻账号密码和端口配置。但这里有个现实情况要提醒很多主打云服务的家用摄像头比如小米、360 的部分型号默认是不开放 ONVIF 的。有些需要在摄像头的设置里手动打开ONVIF协议开关有些干脆不支持。我在给朋友弄设备时就遇到过——摄像头页面里找了一圈压根没有 ONVIF 选项那就老老实实按 4.1 的手动方式处理。4.3 非 RTSP 设备USB摄像头、ESP32、树莓派的接入思路如果你的摄像头不提供 RTSP 接口呢这就轮到 go2rtc 的 FFmpeg 管道和 MJPEG 直连能力上场了。先讲最简单的 MJPEG。很多 ESP32-CAM 模块和一些低端USB摄像头对外直接就是一个 HTTP 网页输出动态JPEG流这种流在浏览器里本来就能看但接入 go2rtc 之后就能和所有视频流统一管理。配置就很直白streams: esp32_cam: - http://192.168.1.30:8080/stream再讲树莓派 CSI 摄像头、OV5647 模块这类东西。它们本质是设备引脚上的摄像头不是网络流要把它们接入 go2rtc常规操作是先在树莓派上通过 GStreamer 或 FFmpeg 推送一个 RTSP 流然后再让 go2rtc 去拉。这个前提条件有一点繁琐但也因此让设备脱离了只能在树莓派本机用的限制。还有一种更通用的玩法把设备和 FFmpeg 采集直接交给 go2rtc。比如树莓派上用 v4l2 驱动读取摄像头设备可以给 go2rtc 配置一条 FFmpeg 管道源streams: usb_cam: - ffmpeg:video4linux2:///dev/video0#video_size1280x720#input_formatmjpeg#videocopy如果是在容器里跑这条配置记得把宿主机设备传进容器services: go2rtc: ... devices: - /dev/video0:/dev/video0这种配置的含义是让 go2rtc 启动一个 FFmpeg 进程去读取宿主机/dev/video0这个摄像头设备参数里指定了分辨率和输入格式为 MJPEG。看起来复杂实际这条链路就三件事读设备、转格式、往外送。OBS 那种软件推流也一样只要 OBS 往本地或局域网推一个 RTMP 流go2rtc 里加一个以rtmp://开头的源即可。说白了go2rtc 接的不是某个品牌而是所有能输出网络流的协议。5. 跑通后就要调优延迟、音频、多路并发这些事5.1 用哪个协议看画面延迟差异很大摄像头画面接进来了紧接着就要面对第二个问题怎么让画面好用。其中最直观的就是延迟。我第一次接好后用浏览器试看页面自带的播放协议默认走 WebRTC画质很好延迟基本感觉不出来。后来我为了在电视浏览器上放手动切到 HLS 地址发现延迟老高走近门口好几秒画面才跟上。这不是 go2rtc 出问题了是协议本身的特性。我把几种主流观看协议的延迟表现整理了一下方便你按场景选择协议典型延迟适用场景WebRTC0.3 ~ 0.5 秒实时预览、呼叫对讲、远程操作MSE1 ~ 2 秒浏览器内较高实时性播放HLS5 ~ 10 秒兼容性优先、录像回放、分享链接RTSP0.5 ~ 1 秒专业播放器、NVR 设备取流结论就是想看实时画面无脑选 WebRTC想发个链接给朋友看用 HLS。5.2 摄像头端配置比服务端配置更能影响延迟第二件让我印象深刻的事是很多延迟问题根源根本不在 go2rtc而在摄像头自己的配置。你摄像头如果输出的是 4K 主码流而观看端只是个手机浏览器go2rtc 或浏览器就得处理巨大的数据量延迟自然高。我的做法是在摄像头管理端把子码流的参数优化一下分辨率调到 720P 左右码率限制在 2-4Mbps固定帧率而不是让摄像头按场景动态调整比特率。动态码率听着先进但在弱网或公网预览时会让画面突然卡顿。还有一个很多人没注意的参数I帧间隔也叫 GOP。这个参数决定了播放器从中间开始播放时多久才能找到一个关键帧。GOP 设置得太长比如超过 4 秒切换画面或者拉流时就会出现卡黑屏几秒的现象。我的建议是把这个值配到 2 到 4 秒之间能明显改善预览和录像回放的起播体验。音频编码也一样。很多摄像头默认输出 G.711 音频浏览器 WebRTC 播放的时候支持度并不好容易没有声音。如果摄像头端能改编码格式我通常会优先改成 AAC。这些都不是 go2rtc 能替你优化的所以我把它们归为摄像头端配置。5.3 多路并发时的资源占用逻辑我们把好几个摄像头都接进去了于是自然想到了性能问题这个容器吃多少资源我使用下来可以负责任地说go2rtc 的资源占用逻辑非常适合家庭和中小型场景。因为它是按需拉流没有任何人观看时容器处于一种空转状态不连摄像头、不转码、不推流。只有当有人打开页面看画面它才去摄像头取流按观看协议进行转换。多路人同时看可以理解为这个资源的占用就是按需动态增量的。我最开始用的时候把它跑在一台 1 核 2G 内存的小主机上同时挂了三台摄像头日常内存占用基本在几百兆字节以内。平时不开画面时CPU 更是几乎不动。当然如果你全部用主码流同时推 WebRTC又开了好几路实时查看肯定会有明显负载但这属于使用方式的问题不是 go2rtc 本身的问题。另外如果你的场景里还要接一套 NVR 录像机我建议让 NVR 从 go2rtc 的 RTSP 输出口去拉流而不是 NVR 和 go2rtc 每次都各自直接连摄像头。这样摄像头只需要维持一个连接压力会小很多。6. 我踩过且成功定位的三个坑6.1 坑一容器重启后所有摄像头离线了这是我最开始遇到的最莫名的一回。有一次我更新系统重启了宿主机容器也随着 Docker 自启动了但打开 go2rtc 界面所有摄像头全部显示 offline。我第一反应是配置文件被覆盖了检查了一遍go2rtc.yaml东西都在。接着看日志里面打印的是摄像头连接超时。我刚开始怀疑是容器网络问题又重启了一遍 Docker没用。最后我实在没办法在宿主机上直接 ping 了一下摄像头IP发现完全 ping 不通。原因说出来可能很丢人家里的路由器在重启之后DHCP 分配地址顺序变了我的摄像头IP从192.168.1.64变成了192.168.1.67而配置文件里写的还是旧地址。摄像头没坏是地址变了。从那以后我立下规矩所有摄像头一律在路由器后台做 IP 与 MAC 地址绑定也就是静态租约。个别摄像头支持直接在设备里设置静态 IP 的也尽量设置。这一步比任何流媒体调优都重要因为地址都不稳定后面全白搭。6.2 坑二能出画面但声音死活出不来第二个坑是有一次朋友问我画面正常但喇叭不出声是不是 go2rtc 不支持音频。我说 go2rtc 是支持音频的问题大概率出在摄像头自己的音频编码。排查链路是这样的先用 VLC 直接打开摄像头的源 RTSP 地址发现 VLC 里也没有声音那就说明根本不是 go2rtc 的问题是摄像头没输出音频。进摄像头管理后台一看音频通道压根没启用。打开后 VLC 有声音了但 go2rtc WebRTC 播放还是没声音。继续查发现这个摄像头输出的是 G.711 音频格式。WebRTC 在浏览器里对 G.711 的兼容性并不好很多情况下会直接静音。解决思路有两种一是进摄像头后台把音频编码改成 AAC二是如果摄像头不支持改编码就只能让 go2rtc 借助 FFmpeg 把音频转成 AAC 再输出。配置文件里用ffmpeg:前缀的源就可以做到我当时的写法类似这样streams: door_phone: - ffmpeg:rtsp://user:pass192.168.1.66:554/stream#videocopy#audioaac这种配置的含义是让 go2rtc 启动 FFmpeg 进程处理这个源视频直接拷贝、音频转成 AAC。完成后浏览器里的声音就正常了。所以遇到没声音我现在的排查顺序永远是源有没有声音、编码对不对、转码需不需要。6.3 坑三局域网流畅换到外面就转圈这个坑是在外面出差时发现的。局域网内打开 Web 界面预览画面秒开但只要人不在家里网络环境里通过域名或端口映射访问画面就一直转圈有时候能出来两三秒后又断掉。一开始我以为是端口转发没配好检查路由器发现 1984 端口已经映射了页面能打开说明 HTTP 链路是通的。但 WebRTC 不只有 HTTP它还需要 UDP 端口传输媒体流而且浏览器需要知道用哪个地址来建立媒体连接。这就是前面提到的webrtc.candidates配置的用武之地。WebRTC 在协商过程中会向浏览器提供一组候选中继地址如果这些地址不对媒体流就建立不起来。我的解决办法是在公网环境下把 go2rtc 的webrtc.candidates里配置上公网IP或STUN服务器地址webrtc: candidates: - 8555 - stun:8555 - stun:19302同时把 WebRTC 需要的 UDP 端口段在路由器里一并映射出去。这样的话浏览器在外面就能通过这些候选地址完成媒体协商和传输。需要说明的是这种调试只适用于你已经通过自家路由器端口转发或者已有内网穿透组网服务的情况下。我建议如果你确实有远程访问需求最好先理清楚自己的网络拓扑再针对性地配置候选地址否则容易越调越乱。这也是我踩了这个坑之后最深刻的体会。7. 配置与排障速查表最后放一张我整理出的速查表是我在使用过程中频繁参考的。它不能替代完整文档但能在关键时刻帮你快速定位问题。场景/问题检查点处理建议容器无法启动端口是否被占用、日志报错docker compose logs go2rtc看失败原因占用就换端口页面打不开1984端口映射、防火墙试curl http://本机IP:1984按返回值判断流显示 offlineRTSP地址、账号密码、网络连通性先用 VLC 验证源地址再检查摄像头IP是否变化ONVIF 扫描不到设备摄像头是否开启ONVIF、是否同网段手动先填RTSP地址兜底画面黑屏编码是否为 H.265、子码流格式优先选 H.264 子码流试试延迟过高观看协议、摄像头码流配置换 WebRTC、改子码流、缩小 GOP有画面没声音源是否有音频、编码是否为G.711改为AAC输出或用 ffmpeg 转码外网看不了/频繁断WebRTC 候选地址、UDP端口映射配置webrtc.candidates和 UDP 映射修改配置后不生效是否重启容器改完 yaml 后执行docker compose restart go2rtc这几个月用下来我的总体感受是 go2rtc 把多摄像头、多协议、多观看端这个组合问题简化成了一个服务、一个配置、一个入口。它没有把每个协议做到极致但它用极其轻量、稳定、按需调用的方式把所有设备统一到了一个体系里。我已经把它当成整个视频链路的基座下一步计划在这个容器外面再挂一个录像模块做统一存储和分类回放目前来看这条路走得很顺。如果你也准备入手我的建议是先从一台支持RTSP的摄像头开始配通一条链路再逐步把其他设备加进来这样每一步都在掌控之中。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进