简介:这是一款面向流媒体开发者的摄像机流媒体网关源码包,支持从多种音视频源拉流并通过实时流协议、实时消息协议、网页实时通信、家庭套件等标准对外分发,特别适合安防监控、智能家居以及需要低延迟多协议转换的工程场景。包体共含363个文件,其中293个Go语言源文件是核心协议与转码逻辑的实现,其余文档、容器配置、网页界面和构建脚本则用于部署、调试与二次开发,压缩包整体仅779KB,非常精巧。目前已有84人学习,适合具备基本流媒体概念的开发者研读。源码中完整呈现了双向H.264/H.265编解码协商、多源混音、自动匹配客户端能力、基于FFmpeg的实时转码以及从RTSP、RTMP、USB摄像头等来源拉流的工程化写法,并附带Dockerfile与构建命令,可直接编译运行在Windows、macOS、Linux及ARM架构设备上。
1. 一个二进制吃下 RTSP、WebRTC、HLS:零依赖流媒体网关的定位
摄像机流媒体这个领域,最大的痛点不是缺协议,而是协议太多:摄像头那边吐 RTSP,浏览器只认 WebRTC 和 MSE,苹果生态走 HLS,智能家居要 HomeKit,直播平台要 RTMP。以前我在项目里把这些协议串起来,至少得部署三四个服务,还要处理跨域、转码、端口映射,折腾一周才能稳定跑通。这份 zip 解压后是一套 Go 编写的流媒体网关源码,一个二进制同时扮演 RTSP/RTMP 拉流端、WebRTC/HLS/MSE 出流端、FFmpeg 转码调度器,零依赖、零配置,Windows、macOS、Linux、ARM 都能跑。适合做摄像头接入、智能家居联动、多平台直播分发的人,尤其是被「浏览器打不开 RTSP 地址」这个问题卡住的前后端开发者。
2. 协议矩阵与选型逻辑:为什么 9 种协议能在一套源码里共存
2.1 协议矩阵:谁负责取流,谁负责出流
这套程序把协议分成两类:源协议和输出协议。源协议负责把流拉进系统,输出协议负责把流发给客户端。搞清楚这个分类,配置的时候就不会乱。
| 协议 | 方向 | 典型场景 | 延迟特征 | 浏览器原生支持 |
|---|---|---|---|---|
| RTSP | 源 | 海康、大华等 IPC/NVR 取流 | 低(基于 RTP) | 不支持 |
| RTMP | 源/出 | 直播平台推流、Flash 遗留系统 | 中(TCP 长连接) | 不支持 |
| DVRIP | 源 | 部分私有协议摄像头(如雄迈方案) | 低 | 不支持 |
| HTTP-FLV | 源/出 | 低延迟直播网页播放 | 低 | 不支持,需 flv.js |
| MJPEG | 源/出 | 老式 USB 摄像头、简单网页嵌入 | 高(逐帧 JPEG) | 支持(img 标签) |
| WebRTC | 出 | 浏览器实时监控、低延迟对讲 | 极低(<500ms) | 支持 |
| MSE | 出 | 浏览器播放 H264/H265(MP4 封装) | 中低 | 支持 |
| HLS | 出 | iOS/macOS 原生播放、HomeKit | 中高(分片) | 部分支持 |
| HomeKit | 出 | 苹果 HomeKit 摄像头接入 | 中 | 不支持(苹果生态专用) |
选型依据很简单:延迟敏感的场景走 WebRTC,兼容性优先走 HLS/MSE,推流到平台走 RTMP。这套程序把 9 种协议塞进一个进程,核心价值不是「支持得多」,而是源协议和输出协议可以任意组合,不需要中间再套一层转换服务。
2.2 源码里那几个关键文件,暴露了它的协议实现方式
我从源码包里挑几个有代表性的文件出来看,能大致判断这套程序的实现深度。
- ffmpeg_test.go / ffmpeg.go:封装了 FFmpeg 的调用链。它不是简单执行
ffmpeg -i ...完事,而是把 FFmpeg 当成协处理器,处理不支持的编解码器(比如私有格式、老式 MJPEG),以及音频重采样这类脏活。 - amf_test.go:AMF(Action Message Format)是 RTMP 协议的消息编码格式。这个文件说明 RTMP 推拉流不是调外部库,而是自己实现了协议解析,好处是握手和 chunk 流的行为可控。
- annexb_test.go:AnnexB 是 H264/H265 的 NAL 单元封装格式,RTP 推流、TS 封装、WebRTC 的 RTP 打包都依赖它。这个测试文件说明它的音视频层是自己拆的,不是把整个包丢给 FFmpeg。
- onvif_test.go:ONVIF 是网络摄像头的标准发现和控制协议。有这个文件,意味着它可以自动发现局域网内的摄像头,不用手动填 IP。
这组文件放在一起,结论是:它把「协议解析、编解码协商、FFmpeg 兜底」分了三层。协议层自己处理,编解码层协商失败才交给 FFmpeg,这样既保证性能,又留了后路。
2.3 双向编解码协商与音轨混合:多路源进,一路出
编解码协商是我认为它最值钱的能力。传统做法是服务端把源统一转成 H264+AAC,客户端只能接受这个固定格式。这套程序的做法是反向的——先看客户端支持什么,再决定出什么流。
举个例子:同一个 RTSP 摄像头源,Chrome 浏览器访问时,它出 WebRTC+H264;Safari 访问时,它出 WebRTC+H265(Safari 原生硬件解码);iOS 上打开 HLS 播放器时,它切成 H264 TS 分片。源没变,输出流是动态协商出来的。
音轨混合的逻辑也是我见过的方案里比较省事的。多路摄像头如果都有麦克风,可以配置把两路音频混成一路推出去。对讲场景里,还能把手机麦克风采集的音频反向推到摄像头。这套程序的做法是内部维护一个音频重采样管线,把不同采样率、不同声道数的源统一处理后再混流。
3. 快速部署:build.cmd、Dockerfile 与 config 文件的正确打开方式
3.1 本地跑起来:build.cmd 与零配置启动
先看 Windows 下的构建脚本。拿到源码包,目录里有个build.cmd,这是 Windows 下的一键编译脚本。
@echo off set CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o go2rtc.exe . echo build done执行方式:在源码目录打开 CMD,运行build.cmd,目录下会生成一个go2rtc.exe单文件。关键参数是CGO_ENABLED=0,代表纯静态编译,不依赖系统动态链接库,这在嵌入式 Linux 和 ARM 设备上很有用,拷过去就能跑。
macOS 和 Linux 下不需要这个脚本,直接执行go build就行。编译完运行:
./go2rtc启动后默认监听1984端口(部分版本为1880,以实际输出为准),终端会打印 API 地址和 Web UI 地址。第一次启动不需要任何配置文件,打开浏览器访问http://localhost:1984就能看到管理界面,右侧可以看到当前所有流的实时状态和调用链。这一步验证的是「零配置」这个承诺——如果起不来,优先检查端口占用和 Go 版本,建议 Go 1.20+。
3.2 Docker 部署:Dockerfile 与 hardware.Dockerfile 的区别
源码里给了一大一小两个 Dockerfile,目的完全不同。普通Dockerfile做的是静态编译瘦身,适合 CPU 转码;hardware.Dockerfile则引入了硬件加速依赖,适合 GPU 转码。
# 基础版本,二进制约 20MB 左右 docker build -t stream-gateway:cpu . # 硬件加速版本,包含 VAAPI/QSV/NVENC 驱动 docker build -f hardware.Dockerfile -t stream-gateway:hw .跑起来的时候,如果是纯软件转码,映射一个端口就够了。如果用了硬件加速版本,还得把显卡设备映射进容器:
docker run -d --name stream-gw \ -p 1984:1984 \ -p 8554:8554 \ -v /etc/streamgw:/config \ stream-gateway:cpu8554端口是 RTSP 服务默认监听端口,如果你只需要 Web 访问,只映射1984就够。hardware 版在 Intel 平台需要加--device=/dev/dri,NVIDIA 平台要配合--gpus all。我的建议是:先跑 CPU 版验证功能,确认源和客户端链路都通,再切硬件版压性能,不要一上来就折腾 GPU 环境。
3.3 config 文件怎么写:拉流地址、API 与 Web UI
虽然零配置能启动,但要拉摄像头的流,还是得在 config 里声明源地址。配置支持 yaml 和 json 两种格式,我习惯用 yaml,因为注释写着方便。
log: level: info api: listen: ":1984" webrtc: listen: ":8555/tcp" ice_servers: - urls: [stun:stun.cloudflare.com:3478] streams: living_room: - rtsp://admin:password@192.168.1.64:554/Streaming/Channels/101 front_door: - ffmpeg:rtsp://192.168.1.65:554/h264log.level建议调试阶段设成debug,能看到协议协商的详细日志。ice_servers是 WebRTC 打洞的关键配置,局域网调试可以省略,公网访问必须加 STUN。streams下面每一项是一条逻辑流:living_room是流名称,后面跟的是源地址。源地址可以写多个,程序会自动按顺序尝试,这个机制在摄像头偶尔掉线时很有用。
启动时指定配置文件:
./go2rtc -config /etc/streamgw/go2rtc.yaml改配置不用重启。Web UI 里保存配置会自动热加载,源断了也会自动重连,这是排查问题时最省心的特性之一。验证配置有没有生效,直接访问 API:http://localhost:1984/api/streams,返回 JSON 里能看到每条流的连接状态和码率信息。
4. 三个高频场景的可抄配置:RTSP 转 WebRTC、HLS 回看、USB 摄像头入流
4.1 场景一:RTSP 摄像头转 WebRTC,浏览器直接看
这是被问得最多的场景:内网有一台海康摄像头,想在公司电脑的 Chrome 里直接看实时画面,不想装 VLC,不想用 IE 插件。RTSP 原生肯定不行,解决方案是用这套程序转成 WebRTC。
streams: office_cam: - rtsp://admin:your_password@192.168.1.100:554/Streaming/Channels/101配置写好后保存,浏览器打开 Web UI,点击office_cam这条流,页面会直接走 WebRTC 播放。这里有几个关键机制要理解:浏览器和程序之间是 WebRTC(UDP 传输),程序和摄像头之间是 RTSP(TCP 传输),两边独立,互不影响。
提示:如果你的摄像头是 H265 编码,Chrome 会黑屏,原因是 Chrome 不支持 H265 的 WebRTC 解码。解决办法是换 Safari 浏览器(支持 H265),或者在源地址上加
#video=h264后缀让服务端实时转码,代价是 CPU 占用会上去。
验证播放链路是否通畅,不用看 UI,直接调 API:
curl http://192.168.1.20:1984/api/streams返回的 JSON 里mime_type字段如果显示video/H264,说明源解析成功;clients字段可以看到当前有几个 WebRTC 客户端在拉流。这个命令在排查「UI 上没画面」的问题时比猜快得多。
4.2 场景二:HLS 输出,走苹果生态和 HomeKit
如果你要做的不是实时监控,而是让家人通过 iOS 原生播放器看摄像头画面,或者接入 HomeKit,出流协议就要换成 HLS。HLS 基于 HTTP,天然穿透性好,iOS 的 Safari 和原生播放器都支持。
streams: garden_cam: - rtsp://192.168.1.101:554/onvif1 - output: hlsoutput 参数可以用在全局,也可以挂在单条流上。HLS 的默认切片时长是 6 秒,家庭监控场景可以调小一点,减少延迟:
hls: segment: 2 playlist: 4segment是每个分片的秒数,playlist是列表里保留的分片数。数值越小延迟越低,但对播放器的兼容性要求更高,2 秒分片在 iOS 上实测没问题。有些安卓播放器对 2 秒分片兼容性差,会频繁缓冲,远程观看时建议保持默认 6 秒。
关于 HomeKit 接入,这套程序对支持 HomeKit 的摄像头(比如 Sonoff 那类)可以原生配对,不需要额外转码。普通 RTSP 摄像头想接入 HomeKit,得先在服务端转成 H264+AAC 封装,再用程序暴露 HLS 地址给家庭中枢转发。这个链路比较绕,我一般建议先确认摄像头固件是否支持 HomeKit 原生接入,不支持就直接用 HLS 曲线救国,不要在协议转换上死磕。
4.3 场景三:USB 摄像头与 FFmpeg 转码兜底
USB 摄像头是另一个常见源,尤其是做门禁、考勤机这类项目。这类设备很多只出 MJPEG,或者出的是私有编码格式,这时候就得靠 FFmpeg 层来做转码兜底。
streams: usb_cam: - ffmpeg:video=/dev/video0#video=mjpeg#audio=mic/dev/video0是 Linux 下 USB 摄像头设备节点,#video=mjpeg指定用 MJPEG 格式采集,#audio=mic同时采集麦克风。FFmpeg 层做的事情是:把 MJPEG 实时转成 H264,把 PCM 音频转成 AAC,然后封装进其他协议输出。
如果你的摄像头是海康私有格式,或者 RTSP 地址里带了特殊参数(比如需要指定分辨率、帧率),也可以强制走 FFmpeg 拉流:
streams: nvr_ch1: - ffmpeg:rtsp://admin:pass@192.168.1.50:554/h264#video=h264#audio=aac#video_width=1920#video_height=1080#video_width和#video_height是转码输出分辨率,强制缩放,适合摄像头子码流不清晰但主码流带宽又太高的场景。注意:转码不是免费的,1080p 实时转码大概要占 2~3 个 CPU 核心。如果设备多,优先考虑hardware.Dockerfile那套 GPU 转码方案。
5. 流媒体网关避坑笔记:5 条能把人逼疯的现场问题
5.1 现象一:RTSP 拉流一直重连,画面黑屏
日志里反复出现reconnect,屏幕一直是黑的,但 Web UI 显示流在线。
原因八成是摄像头 RTSP 传输机制不兼容。RTSP 底层走 RTP,传输方式有 TCP 和 UDP 两种。UDP 延迟低,但在跨交换机、开了防火墙的环境下极易丢包,丢包一多摄像头就会断开重连。海康、大华默认是 UDP 优先,而很多网关程序默认也用 UDP,两边对不上就无限重连。
解决方法是强制 RTSP 走 TCP 传输。在源地址上追加查询参数?tcp(具体写法以程序文档为准),或者在 FFmpeg 源上显式指定:
ffmpeg:rtsp://admin:pass@192.168.1.64:554/h264#rtsp_transport=tcprtsp_transport=tcp是 FFmpeg 的标准参数,强制用 TCP 承载 RTP,虽然延迟略高,但稳定性和穿透性都好很多。我的经验是:只要摄像头和设备不在同一台交换机下,一律先走 TCP,追求最低延迟再考虑 UDP。
5.2 现象二:H265 摄像头在 Chrome 里黑屏,Safari 正常
Chrome 访问 H265 源黑屏,换成 Safari 正常,这个现象基本可以断定是浏览器解码能力差异。
Chrome 桌面版不支持 H265 的 WebRTC/HTML5 解码,Safari 因为苹果生态的原因原生支持,iPhone 上的 Safari 甚至支持 H265 硬解。解决路径有两条:如果你只需要苹果设备看,那不用处理;如果要兼容 Chrome,就得让服务端转码。
streams: h265_cam: - rtsp://192.168.1.66:554/h265#video=h264#video=h264后缀会触发实时转码,把 H265 转成 H264 再推给客户端。这里有个坑:转码之后画面延迟会从原来的 300ms 涨到 1 秒以上,这是软编造成的。如果设备支持 GPU 硬编,务必用 hardware 版本部署。
5.3 现象三:画面正常但没声音,或者音画不同步
画面流畅、声音没输出,或声音比画面慢 1 秒以上,多半是音频编码和封装出了问题。
摄像头的音频编码五花八门,常见的有 PCM、AAC、G711、MP3。如果声源是 G711 而输出协议要求 AAC(比如 WebRTC 强制 AAC),服务端会重采样,转换过程中如果采样率没配对就会出杂音或无声。音画不同步则通常是因为视频转码耗时过长,音频却直通,两个轨道的时间戳基准错位。
我的处理习惯是音频也强制转码,不做直通:
streams: cam_audio: - rtsp://192.168.1.67:554/onvif1#audio=aac#audio_sample_rate=48000audio=aac指定音频输出编码,audio_sample_rate强制统一采样率,从源头掐断不同步。如果你有音轨混合需求,多路源的音频采样率、声道数不一致时,先分别在每条源上统一到同一参数,再在混合配置里合成,这样最省事。
5.4 现象四:推流到直播平台有明显延迟,或者频繁断流
有人在把 RTSP 摄像头的流转推到 YouTube 这类直播平台时,发现延迟有十几秒,而且偶尔断流重推。
这是 RTMP 推流的典型问题。直播平台接收 RTMP,一般会做缓存来保证流畅,延迟大是平台侧行为,本地链路上如果视频又是先转码再推流,CPU 扛不住就会断流。之前的经验:先定位延迟卡在哪一环。看 Web UI 里live_room这条流的producers和clients,如果服务端输出速度稳定,说明瓶颈在平台侧,治不了;如果输出波动、队列堆积,就得优化编码参数。
streams: youtube_push: - ffmpeg:rtsp://192.168.1.68:554/h264#video=h264#video_width=1280#video_height=720#video_fps=25#video_bitrate=2500kvideo_bitrate限制码率很关键。1080p 原码流可能 8Mbps,推到平台根本吃不满,强制压到 2500kbps 能显著降低 CPU 和带宽压力。断流问题,除了码率,也要检查推流地址的 key 是否过期,平台鉴权失败也会表现为「推上去就断」。
5.5 现象五:Docker 部署后 WebRTC 连不上,但 HLS 正常
服务器上用 Docker 部署,画面 HLS 能看,WebRTC 就是转不出来,或者一连就超时。
WebRTC 是 UDP 协议,需要额外的 UDP 端口协商,而 HLS 走 HTTP 只要 TCP。八成原因是 Docker 的端口映射只映射了 TCP,没把 WebRTC 需要的 UDP 端口映射出去,或者云服务器安全组没放行 UDP。
docker run -d --name stream-gw \ -p 1984:1984 \ -p 8555:8555/udp \ -p 8555:8555/tcp \ stream-gateway:cpu8555的 UDP 和 TCP 必须同时映射,判断依据:如果局域网内 WebRTC 正常、公网失败,就先查安全组 UDP 端口;如果所有人都失败,再查配置里的ice_servers。公网场景光有 STUN 还不够,如果服务器没有固定公网 IP,或者走了 NAT,需要在webrtc配置里手动指定外网 IP。这类问题排查起来很玄学,核心思路是:先局域网排除,再查 UDP 通不通,最后看 ICE 协商日志。
6. 进阶技巧:把端到端延迟压进一秒量级
6.1 检查链路上每一环的缓冲设置
要做到 WebRTC 链路端到端延迟接近实时,瓶颈往往不在协议,而在你用了什么入流方式。用rtsp_transport=tcp拉流比 UDP 慢 100~200ms,但换来的是稳定。局域网内追求极限延迟,可以切回 UDP;跨网络场景,稳定优先,别跟 200ms 较劲。FFmpeg 参与转码的情况下,.yaml里可以给 FFmpeg 层设preset:
ffmpeg:... #video_preset=ultrafastultrafast是 x264 最快预设,画质稍微牺牲一点,但编码耗时能砍掉近一半。时延敏感项目(门禁对讲、远程操控)我必加,非敏感项目没必要。
6.2 快速验证整个链路延迟的土办法
对着摄像头画面用手机秒表计时,肉眼估算 Web UI 画面和实拍画面的时间差。几百毫秒误差正常,到 2 秒以上就说明有环节缓存吃多了。如果是 HLS 链路,延迟预算本身就到 2~6 秒,这是协议特性,接受它。把 WebRTC 当默认出流、HLS 当降级兼容,是我现在做摄像头集成的基本盘。从那以后我每次部署这种流媒体网关,都会强制走一遍「源协议确认 → 出流协商 → 延迟实测 → UDP 穿透验证」的流程。希望帮到你,少踩我踩过的坑。
本文还有配套的精品资源,点击获取