go2rtc Bubble 私有流协议解析:从 dvr163 / eseecloud 摄像头接入到帧级协议实现
2026/9/14 19:21:57 网站建设 项目流程

go2rtc Bubble 私有流协议解析:从 dvr163 / eseecloud 摄像头接入到帧级协议实现

【免费下载链接】go2rtcUltimate camera streaming application项目地址: https://gitcode.com/GitHub_Trending/go/go2rtc

本篇以 go2rtc 仓库中的 internal/bubble/README.md 为主体,完整覆盖 Bubble 私有流格式的接入配置(URL 语法、参数缺省规则),并结合 pkg/bubble/client.go 与 pkg/bubble/producer.go 的源码实现,讲清从 TCP 握手、认证包构造、媒体包解包到 RTP 封帧的完整链路。读完后你将能够:为 dvr163.com / eseecloud.com 系设备正确配置bubble://源,理解该私有协议的报文结构与默认值来源,并知道 go2rtc 如何把它转成内部可复用的视频/音频轨。

1. Bubble 是什么

Bubble 是少数 dvr163.com 与 eseecloud.com 品牌摄像头/NVR 使用的私有流传输格式(内部包注释中给出的线索:请求 URL 为/bubble/live?ch=0&stream=0,响应Content-Typevideo/bubble,见 client.go 的包注释)。它对外的传输载体是普通 TCP + HTTP 请求头,但其后的数据流是自定义的二进制包头封装,既不是标准 RTSP 也不是 MJPEG,因此需要专门的 producer 才能被 go2rtc 拉流。

go2rtc 在 v1.6.1 版本引入了该格式(README 中标注new in v1.6.1)。协议支持视频编码为 H.264 或 H.265(HEVC),音频为 G.711 A-law(PCMA),这一点可以从协议实现中交叉印证:pkg/README.md 的协议一览表里bubble一行的编码列为h264, hevc, pcm_alaw,传输协议列为http

2. 快速开始:配置一个 bubble 源

按照 internal/bubble/README.md 给出的配置规则:

  • usernamepasswordportchstream在取默认值时可以省略;
  • 不同通道 / 不同码流应配置成不同的 stream 条目。
streams: camera1: bubble://username:password@192.168.1.123:34567/bubble/live?ch=0&stream=0

URL 各部分的含义与默认行为(以源码为准):

字段示例说明
schemebubble://触发 bubble 协议的 URL 处理器,见 internal/bubble/bubble.go
username / passwordusername:passwordURL userinfo 中的账号密码;源码中当 URL 未携带用户时,用户名默认填充为admin(client.go)
host192.168.1.123设备地址
port34567设备私有流端口,README 说明其为默认值时可省略
path/bubble/live私有 HTTP 请求路径,设备端据此返回video/bubble
ch0通道号,对应源码中的c.channel,默认 0
stream0码流号(如主/子码流),对应源码中的c.stream,默认 0

chstream的解析逻辑在 Dial() 中:先解析stream(缺省为"0"),再据此从设备返回的 XML 中匹配对应码流条目,最后解析ch

配置完成后,该 stream 即可被 go2rtc 的标准消费端复用(RTSP、WebRTC、HLS 等),这与其它协议源的行为一致;协议本身只负责“生产”媒体轨。

3. 协议注册:从 main.go 到 bubble.Dial

Bubble 的接入遵循 go2rtc “internal 薄壳 + pkg 协议实现”的分层:

  1. 程序入口 main.go 中注册了{"bubble", bubble.Init}
  2. internal/bubble/bubble.go 的Init()调用streams.HandleFunc("bubble", ...),把bubble://这一 scheme 映射到 producer 构造函数;
  3. 构造器直接调用 pkg/bubble 的 Dial() 完成拨号,返回的*Client实现了core.Producer接口,交由 streams 层管理生命周期。

*Client内嵌core.Listener并提供GetMedias()/GetTrack()/Start()/Stop()/Close()等接口(client.go),源码注释标注其为旧式接口(Deprecated: should be rewritten to core.Connection),从源码结构看后续版本可能迁移到core.Connection抽象。

4. 连接与认证流程(Dial 阶段)

Dial() 实现了整个握手,共四步,全部超时统一为 5 秒(const Timeout = time.Second * 5):

第 1 步:TCP 连接 + HTTP GET。解析 URL 后net.DialTimeout建立 TCP 连接,然后用 go2rtc 自实现的轻量 HTTP 客户端发送GET /bubble/live?ch=0&stream=0 HTTP/1.1请求(client.go),要求响应状态为 200,否则报wrong response

第 2 步:读取 1024 字节 XML 描述。响应体先读固定 1024 字节,源码注释特别指出:部分设备恰好返回 1024 字节,但有些设备返回 923 字节,因此这里是“尽力读取”而非精确按长度读取(client.go)。该 XML 描述了设备能力,形如:

<bubble version="1.0" vin="1"><vin0 stream="2"> <stream0 name="720p.264" size="2304x1296" x1="yes" x2="yes" x4="yes" /> <stream1 name="360p.265" size="640x360" x1="yes" x2="yes" x4="yes" /> <vin0> </bubble>

(见 client.go 的注释示例。)

第 3 步:发送 48 字节认证包。结构为size uint32(=44) + 未知 4 字节 + username 20 字节 + password 20 字节,用户名不足 20 字节以零填充;未提供用户名时默认写admin(client.go)。该包以PacketAuth(0x00)命令字、固定时间戳0x0E16C271封装发出。

第 4 步:校验认证响应并解析码流编码。设备回一个 44 字节负载的PacketAuth包,源码校验b[4] == 3 && b[8] == 1两个魔数,不满足则报wrong auth response(client.go)。随后用正则<stream{N} [^>]+从 XML 中取出所选码流条目,按名字是否包含.265决定视频编码为 H.265 还是 H.264(client.go)。

命令字常量定义在 client.go:

常量用途
SyncByte0xAA每个包的同步头
PacketAuth0x00认证包
PacketMedia0x01媒体数据包
PacketStart0x0A开始播放(Play)命令

5. 报文封装:Write / Read

Bubble 所有二进制交互共用同一套 10 字节包头封装,大端序(client.go):

0xAA | size uint32 | cmd byte | timestamp uint32 | payload (size = 5 + len(payload))
  • Write(command, timestamp, payload)负责按上述布局打包并写 socket;
  • Read()先读满 10 字节头,校验首字节必须为0xAA(否则wrong start byte),再按size - 1 - 4读出 payload,返回命令字与负载。

包头中size字段统计的是“cmd + timestamp + payload”的长度(即5 + len(payload)),这是解析该协议时最容易被算错的一处。

6. 开始播放与媒体数据处理(Play / Handle)

Start()的调用链是Play()Handle()(producer.go)。

Play 命令。Play() 发送PacketStart(0x0A)包,16 字节负载为小端序三个 uint32:channelstream1(表示 opened),时间戳魔数为0x0E16C2DF。源码注释特别强调“yeah, there's no mistake about the little endian”——认证与包头是大端,而 Play 负载是小端,属于该私有协议的既定怪异之处。

Handle 主循环。Handle() 无限读取包,仅处理PacketMedia,其余命令字直接丢弃。媒体包 payload 的前 6 字节为公共头:size uint32 + type 1b + channel 1b,其中type的语义为1= 关键帧、2= 普通帧、0= 音频。

  • 视频(b[4] > 0)b[6:]是 AnnexB 格式的一帧,经annexb.EncodeToAVCC转成 AVCC(长度前缀)格式后封装为 RTP 包(90kHz 时间戳)写入视频轨;
  • 音频(b[4] == 0):负载前 36 字节是音频元信息头(条目数、大小、PTS、G.711 魔数g711、采样率、采样宽度,见 client.go 的注释),实际 PCM A-law 数据从b[6+36:]开始,按帧长累加 RTP 时间戳后写入音频轨。

只有在对应 receiver 已建立(videoTrack/audioTrack非 nil)时才写帧,未就绪的帧直接丢弃。

7. Producer 接口:媒体轨声明与 API 输出

GetMedias() 向 streams 层声明两条只接收(recvonly)媒体轨:

编码时钟负载类型
视频H.264 或 H.265(Dial 阶段由 XML 判定)90000 HzPayloadTypeRAW(RAW 视频负载)
音频PCMA(G.711 A-law)8000 HzPayloadType 8

GetTrack()按需创建core.Receiver并回填videoTrack/audioTrack指针,供 Handle 主循环写帧(producer.go)。Stop()则先关闭所有 receiver 再断连。

MarshalJSON() 把连接状态序列化进 go2rtc 的/api接口:formatbubbleprotocolhttp,并附带远端地址与累计接收字节数(Recv),便于在 API/Net 页面观察这条私有流的实时状态。

bubble也已列入 www/schema.json 的合法 scheme 白名单,Web UI 中配置时可获得语法校验。

8. 使用限制与注意事项

  • 超时:拨号、每次读写均使用 5 秒超时(Timeout常量),弱网环境下认证失败会以超时错误呈现;
  • 设备兼容性:1024 字节 XML 头部存在“923 字节”的变体设备,解析依赖设备在头部返回正确的<streamN>条目,若设备固件行为偏离,Dial 阶段会报wrong auth response或正则匹配失败;
  • 编码范围:仅支持 H.264/H.265 视频 + G.711 A-law 音频(GetMedias的声明范围),其余编码的 bubble 设备不在当前实现覆盖内;
  • 同品牌其它协议:eseecloud 设备另有标准的 eseecloud:// 接入方式(eseecloud://user:pass@host:80/livestream/12),当设备走标准流时优先使用该协议;bubble 面向的是私有video/bubble数据面。

9. 相关文件索引

文件作用
internal/bubble/README.md本文主体:bubble 源的配置说明
internal/bubble/bubble.go注册bubble://scheme
pkg/bubble/client.go连接、认证、报文读写、Play/Handle 核心实现
pkg/bubble/producer.go媒体轨声明、producer 生命周期与 JSON 状态
main.go模块初始化注册点
pkg/README.md协议能力一览表(bubble 行)

【免费下载链接】go2rtcUltimate camera streaming application项目地址: https://gitcode.com/GitHub_Trending/go/go2rtc

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询