go2rtc 集成 Flussonic 流服务器:WebSocket 拉流协议与源码级配置指南
【免费下载链接】go2rtcUltimate camera streaming application项目地址: https://gitcode.com/GitHub_Trending/go/go2rtc
Flussonic 是常见的商业流媒体服务器产品,go2rtc 自v1.9.10起增加了对 Flussonic 服务器的流接入支持(对应社区 issue #1678 的需求)。本文以该功能为主线,先给出可直接落地的配置写法,再深入 pkg/flussonic/flussonic.go 与 internal/flussonic/flussonic.go 的实现细节,讲清 go2rtc 如何通过 WebSocket 建立连接、解析 fMP4(ISO BMFF)元数据、完成编解码器协商并持续拉取音视频流。读完本文,你将掌握在 go2rtc 中接入 Flussonic 流的完整姿势,并能理解其底层工作链路,便于排查和二次开发。
功能概览:go2rtc 如何支持 Flussonic
在 go2rtc 中,Flussonic 被注册为一个流的来源(producer/source)协议,scheme 名为flussonic。接入方式与其他协议(如rtsp:、ffmpeg:)完全一致:在 YAML 配置的streams字段中,以flussonic:作为 URL 前缀即可。
原文档(internal/flussonic/README.md)对它的描述只有一句话:支持来自 Flussonic 服务器的流,自 v1.9.10 起新增。而它在整个 go2rtc 模块体系中的定位,可以从 internal/README.md 的模块总览表中进一步确认:flussonic一行的格式(format)标注为mp4、协议(protocol)标注为ws,即通过 WebSocket 传输 MP4(分段式 fMP4)数据,并且作为源(source)使用。
版本与引入背景
- 新增版本:v1.9.10(对应 go2rtc release 记录),当前仓库主版本为 1.9.14(见 main.go 中的 fallback 版本号)。
- 驱动需求:由社区 issue #1678 提出,目标是让 go2rtc 能够直接拉取 Flussonic 服务器上的流,而无需借助中间转码。
模块注册方式
在 main.go 的模块初始化列表中,flussonic模块与其他来源模块并列注册:
{"flussonic", flussonic.Init},其Init函数只有一行核心逻辑(见 internal/flussonic/flussonic.go):
func Init() { streams.HandleFunc("flussonic", flussonic.Dial) }streams.HandleFunc将 scheme 字符串"flussonic"与一个Handler(func(source string) (core.Producer, error))绑定,注册表实现在 internal/streams/handlers.go。此后,无论通过配置文件还是 Web UI / API 动态添加flussonic:...源,都会走同一条解析链路(streams.GetProducer→ 按 scheme 查表 → 调用flussonic.Dial)。同时,该 scheme 也会出现在GET /api/schemes接口的返回列表中(可参考 internal/streams/api_test.go 对该接口行为的测试),并列入 www/schema.json 的模块枚举,方便 Web UI 校验。
快速开始:在 go2rtc 中配置 Flussonic 流
配置示例
在go2rtc.yaml的streams段中定义即可:
streams: flussonic-cam: flussonic:wss://flussonic.example.com:8443/live/camera1 flussonic-backup: flussonic:ws://192.168.1.50:8080/live/backup关键点说明:
flussonic:是固定前缀,去掉该前缀后剩余部分必须是一个可被 WebSocket 客户端拨号(Dial)的 URL。从源码看,Dial先执行strings.CutPrefix(source, "flussonic:"),再把剩余 URL 直接交给websocket.DefaultDialer.Dial(见 pkg/flussonic/flussonic.go)。- 由于底层是 gorilla/websocket 的标准拨号,URL 既可以是
wss://(TLS)也可以是ws://;协议字段(Protocol)由core.Before(url, ":")推导,因此显示为wss或ws。 - 具体路径(如
/live/camera1)取决于 Flussonic 服务端暴露的 WebSocket 拉流端点配置,需与服务器端保持一致。
通过 API 动态添加
除配置文件外,也可以调用 go2rtc 的 streams API 动态添加,例如:
# 以流名 flussonic-cam 添加一个 Flussonic 源 curl -X POST "http://localhost:1984/api/streams?name=flussonic-cam&src=flussonic:wss://flussonic.example.com:8443/live/camera1"新增源会经过streams.Patch/GetOrPatch的校验(scheme 支持性检查与安全性校验,见 internal/streams/streams.go 与 internal/streams/handlers.go),通过后即纳入流管理。
与浏览器播放打通
Flussonic 源就绪后,与其他来源一样,可以直接通过 go2rtc 的 WebRTC、HLS、MP4 等输出通道在浏览器中观看,无需额外的中转协议适配——因为 Flussonic 源在内部已经被归一化为标准的core.Media/core.Codec描述与 RTP 数据流。
源码剖析一:拨号与连接建立(Dial)
Producer结构体(见 pkg/flussonic/flussonic.go)内嵌了core.Connection,并持有 WebSocket 连接、视频轨 ID、音频轨 ID 以及两路的时间缩放因子:
type Producer struct { core.Connection conn *websocket.Conn videoTrackID, audioTrackID uint32 videoTimeScale, audioTimeScale float32 }Dial的完整流程:
- 去掉
flussonic:前缀,得到原始 WebSocket URL; - 用
websocket.DefaultDialer.Dial建立连接(失败则直接返回错误); - 构造
Producer,并填充core.Connection的元信息:FormatName: "flussonic"——在连接图中标识格式;Protocol: core.Before(url, ":")——ws或wss;RemoteAddr——服务器远端地址;URL——原始 WebSocket 地址;Transport——WebSocket 连接对象,便于统一关闭;
- 调用
probe()完成流探测(读取轨道元数据); - 探测失败时关闭连接并返回错误。
core.Connection的定义(含FormatName、Protocol、Medias、Receivers、Recv等字段)位于 pkg/core/connection.go,是 go2rtc 所有来源模块统一使用的连接基座。
源码剖析二:probe 探测与轨道协商
probe()(见 pkg/flussonic/flussonic.go)是理解该协议的核心,分两步:
第一步:读取服务端首帧 JSON 元数据
连接建立后,Flussonic 服务端会先推送一条文本/JSON 消息,描述可用的轨道(tracks)。go2rtc 用conn.ReadJSON解析,只关心tracks数组中每个元素的三个字段:
content:轨道类型描述(源码中未直接使用,但为保留字段);id:轨道 ID(uint32),用于后续分片数据与轨道的对应;payload:该轨道的编解码器配置载荷,即一个ISO BMFF(fMP4)原子(atom)序列。
源码中还保留了metadata.tracks与type字段的注释结构,说明服务端协议可能存在元数据扩展位,当前实现聚焦于tracks数组。
第二步:解析 ISO BMFF 原子并提取编解码器
对每个轨道的payload,调用iso.DecodeAtoms(实现见 pkg/iso/reader.go)拆解出原子列表,随后遍历匹配:
| 原子类型 | 用途 | 提取的编解码器 |
|---|---|---|
mdhd(Media Header) | 读取TimeScale,作为该轨道媒体时间基准 | — |
avc1(H.264 视频样本描述) | 取出atom.Config(AVCC 配置) | h264.AVCCToCodec→ 视频 Codec |
mp4a(AAC 音频样本描述) | 取出atom.Config(AudioSpecificConfig) | aac.ConfigToCodec→ 音频 Codec |
对应代码(节选):
case *iso.AtomVideo: switch atom.Name { case "avc1": codec := h264.AVCCToCodec(atom.Config) p.Medias = append(p.Medias, &core.Media{ Kind: core.KindVideo, Direction: core.DirectionRecvonly, Codecs: []*core.Codec{codec}, }) p.videoTrackID = track.Id p.videoTimeScale = float32(codec.ClockRate) / float32(timeScale) } case *iso.AtomAudio: switch atom.Name { case "mp4a": codec := aac.ConfigToCodec(atom.Config) // ... 音频 Media 组装,audioTrackID / audioTimeScale 赋值 }两点值得注意:
- 编解码器配置的还原:
h264.AVCCToCodec(见 pkg/h264/avcc.go)从 AVCC 配置生成带packetization-mode=1与sprop-parameter-sets的 FMTP 行;aac.ConfigToCodec(见 pkg/aac/aac.go)解析 AAC AudioSpecificConfig 得到采样率、声道数与编码名。这两个函数在 go2rtc 的 HLS、MP4 解封装、FLV、私有云协议等众多模块中被复用(如 pkg/mp4/demuxer.go),属于仓库级的通用工具。 - 时间缩放因子的计算:
videoTimeScale = codec.ClockRate / timeScale,把 fMP4 的媒体时间基准(mdhd.TimeScale)换算到 RTP 时间戳基准(如 H.264 的 90000Hz),供后续分片时间戳转换使用。相关原子结构与解析逻辑可参见 pkg/iso/reader.go(AtomMdhd)。
probe完成后,p.Medias中已声明了视频/音频的接收方向(DirectionRecvonly)媒体描述,下游消费者(浏览器 WebRTC、HLS 等)即可据此完成编解码器协商。
源码剖析三:Start 拉流与 RTP 时间戳换算
Start()(见 pkg/flussonic/flussonic.go)负责从"探测完成"进入"持续拉流":
发送 resume 命令
if err := p.conn.WriteMessage(websocket.TextMessage, []byte("resume")); err != nil { return err }Flussonic 服务端在收到文本命令resume后开始推送媒体分片——这也是该协议的一个关键交互点:连接建立后先收元数据,再主动resume才开始推流。
建立轨道接收映射
根据已协商好的Receivers(下游消费者按需创建),把视频/音频接收器分别绑定到videoTrackID/audioTrackID上,同时记录对应的时间缩放因子:
switch receiver.Codec.Kind() { case core.KindVideo: receivers[p.videoTrackID] = receiver timeScales[p.videoTrackID] = p.videoTimeScale case core.KindAudio: receivers[p.audioTrackID] = receiver timeScales[p.audioTrackID] = p.audioTimeScale }循环读取分片并生成 RTP 包
主循环持续conn.ReadMessage(),只处理websocket.BinaryMessage(二进制消息),统计接收字节数(p.Recv += len(b))后送入带缓冲的 channel(ch := make(chan []byte, 10),容量 10 的分片缓冲用于解耦 IO 与解析)。后台 goroutine 对每个分片再次执行iso.DecodeAtoms,从中提取:
| 原子 | 提取字段 | 作用 |
|---|---|---|
tfhd(Track Fragment Header) | TrackID | 定位所属轨道 |
tfdt(Track Fragment Decode Time) | DecodeTime | 分片解码时间(媒体基准) |
mdat(Media Data) | Data | 实际音视频样本载荷 |
随后按轨道换算时间戳并构造 RTP 包写入接收器:
timestamp := uint32(float32(decodeTime) * timeScales[trackID]) packet := &rtp.Packet{ Header: rtp.Header{Timestamp: timestamp}, Payload: b, } recv.WriteRTP(packet)这里DecodeTime(媒体时间基准)× 时间缩放因子,即得到 RTP 时间戳(如 90000Hz),实现了 fMP4 分片时间轴到 RTP 时间轴的统一。相关的原子解析(AtomTfhd、AtomTfdt、AtomMdat)都在 pkg/iso/reader.go 中定义。
停止与清理
ReadMessage返回错误(连接关闭/超时)时Start返回;结合core.Connection.Stop()(见 pkg/core/connection.go)对 Receivers、Senders 以及Transport(即 WebSocket 连接)的统一关闭,整条拉流链路可以干净地收尾。
全链路小结:一个 Flussonic 流的生命周期
把以上剖析串联起来,go2rtc 中一个flussonic:...流的完整生命周期为:
- 注册:
internal/flussonic.Init把flussonicscheme 注册进 streams 处理器表(internal/flussonic/flussonic.go); - 创建:配置文件或 API 传入
flussonic:URL,streams.GetProducer命中处理器并调用flussonic.Dial; - 建连与探测:WebSocket 握手 → 读取 JSON 轨道元数据 → 解析 ISO BMFF 原子 → 生成
core.Media/core.Codec(H.264 avc1 + AAC mp4a)→ 计算时间缩放因子; - 协商:下游消费者按需选择轨道并创建
Receiver; - 拉流:发送
resume→ 循环读取二进制 fMP4 分片 → 解析tfhd/tfdt/mdat→ 换算时间戳 →WriteRTP交给下游; - 结束:连接错误触发退出,
Stop统一关闭连接与所有收发器。
注意事项与限制
- 依赖服务端 WebSocket 端点:
flussonic:之后必须是可拨号的 WebSocket URL,Flussonic 服务端需开启对应的 WS 拉流能力;具体路径与鉴权参数以服务端配置为准。 - 编解码支持范围:当前实现(v1.9.x 仓库代码)仅识别
avc1(H.264)与mp4a(AAC)两种样本描述;hev1(H.265)等在其他模块中虽有支持(见 pkg/iso/reader.go 对avc1/hev1的解析),但 Flussonic 模块的编解码器组装仅覆盖 H.264 + AAC,若流中携带其他编码会被忽略。 - 单轨道映射:
probe只记录首个avc1视频轨与首个mp4a音频轨,多码率/多音轨流取其一。 - 网络稳定性:拉流为长连接,断线会直接结束
Start;可在配置中结合 go2rtc 的多源机制添加备用源(如 RTSP/FFmpeg 转码源)提高可用性。
如需进一步阅读,可对照源码:pkg/flussonic/flussonic.go(协议实现)、pkg/iso/reader.go(ISO BMFF 解析)、internal/streams/handlers.go(scheme 注册与分发)、internal/README.md(模块总览表)。
【免费下载链接】go2rtcUltimate camera streaming application项目地址: https://gitcode.com/GitHub_Trending/go/go2rtc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考