VSS 播放流的流程

5 阅读5分钟

VSS 播放流的流程

本文基于 core/app/sev/vss/internal/logic/http/video/stream_play.go 的源码:从参数与设备查询,到按接入协议分支、触发 MS 拉流或 GB28181 Invite,再到返回 StreamResp 与异步处理。


一、接口入口与请求体

项目说明
注册internal/handler/http/routers.gorouter.POST(video.VStreamPlayLogic.Path(), ...)
Path/video/stream(挂在 Gin 的 /api 组下,完整路径为 POST /api/video/stream
LogicStreamPlayLogic.DO(req types.VideoStreamReq)

VideoStreamReqinternal/types/types.go)主要字段:

字段含义
deviceUniqueId / channelUniqueId必填;用于拉取设备+通道及绑定 MS。
startAt / endAt毫秒时间戳;均大于 0 时视为 回放playType = playback)。
download是否下载场景(透传给 GB Invite)。
speed回放倍率等(透传 Invite)。
https影响 MS 节点选择时是否按 HTTPS 能力投票(ms.New(...).WithHttps(req.Https))。

流名称覆盖:若 Handler 注入了 *gin.Context 且 Query 里存在 streamName,则用其 覆盖 stream.New().Produce(...) 的默认流名称(便于与已有会话对齐)。


二、主流程总览

flowchart TD
    A[校验 device/channel] --> B[RPC DeviceChannel]
    B --> C[解析 playType 直播或回放]
    C --> D[VoteNode 选 MS + Produce streamName]
    D --> E[组装 StreamResp 基础字段]
    E --> F{MS 是否默认节点 IsDef?}
    F -->|否| G[GetMSConf 取监听端口]
    F -->|是| H[PlayAddress 用已有节点信息]
    G --> I[PlayAddress 多协议播放地址]
    H --> I
    I --> J[回放时间格式化写入 StartAt/EndAt]
    J --> K{AccessProtocol}
    K -->|1 流媒体源| L1[设备 StreamUrl + start_relay_pull]
    K -->|2 RTMP推流| L2[不设 streamUrl 不拉流]
    K -->|3 ONVIF| L3[通道 StreamUrl + start_relay_pull]
    K -->|4 GB28181| L4[回放先 StopMultiMSStream + Invite]
    K -->|5 EHOME| L5[返回未支持]
    K -->|other| L6[未匹配协议]
    L1 --> M{streamUrl 非空?}
    L3 --> M
    L2 --> N[CDN 可选 relay_push]
    M -->|是| O[StartRelyPull MS]
    M -->|否| N
    O --> N
    N --> P[异步 saveChannelSnapshot]
    L4 --> Q[Invite 后直接返回 + 异步处理]

三、步骤说明

3.1 参数与设备通道

  • deviceUniqueIdchannelUniqueId 为空,直接返回 参数错误
  • RpcClients.Device.DeviceChannel 拉取 DeviceChannel;设备或通道为空则 设备获取失败

3.2 直播 / 回放与 MS 超时参数

  • 默认 playType = play(直播),autoStopPullAfterNoOutMs = 60000
  • req.EndAt > 0 && req.StartAt > 0playType = playbackautoStopPullAfterNoOutMs = 10000(回放更快放弃无输出拉流,避免长时间占资源)。

3.3 流名称与 MS 节点

  • streamName = stream.New().Produce(device, channel, playType)
    • 直播:stream_{deviceUniqueId}_{channelUniqueId}_play
    • 回放:带全局递增 PlaybackCount 后缀,保证多路回放流名称隔离(见 core/common/stream/main.go)。
  • ms.New(ctx, svcCtx).WithHttps(req.Https).VoteNode(device.MSIds) 选择媒体节点;nil 则返回「未设置流媒体源」

3.4 响应体 StreamResp 与播放地址 Addresses

填充 ctypes.StreamResp:接入协议文案、MS 信息、设备/通道 ID、流名称、通道在线状态、StreamUrl 占位等;通道/设备展示名优先 Label 否则 Name

MediaServerNode.IsDef 分支(是否「默认/简略」节点):

  • 非默认:再调 GetMSConf(http://{msAddress})HTTP/HTTPS/RTSP/RTMP 等端口,拼 stream.PlayAddress(StreamPlayProxyPath, MSVoteNodeResp+端口, streamName)
  • 默认:直接用已有 msNodePlayAddress

回放时间startAt/endAt > 0 时格式化为字符串写入 data.StartAt / data.EndAt(供调用端展示)。

PlayAddresscore/common/stream/main.go 中根据代理配置、MS 端口与流名称生成 多协议播放 URL 集合(HTTP-FLV、WebSocket、HLS 等——具体字段见 ctypes.PlayAddress)。

3.5 按 AccessProtocol 分支(核心)❗❗❗

常量来自 repositories/models/devices

含义stream_play 行为
1流媒体源streamUrl = device.StreamUrl;传输层 MediaProtocolMode == 0rtspMode = 1(UDP),否则 TCP。后续 StartRelyPull
2RTMP 推流不配 streamUrl,注释说明由设备 推流 触发 on_pub_start不调用 start_relay_pull
3ONVIFstreamUrl = channel.StreamUrl,同样按 MediaProtocolMode 决定 RTSP UDP/TCP,再 StartRelyPull
4GB28181回放:先 StopMultiMSStream 停掉与当前流名称前缀相关、带 playback 的旧流,睡眠 1s 降低抢流冲突;再 gbs.InviteLogic.Invite(...) 下发 SIP Invite(含起止时间、DownloadSpeed、流名称等)。成功则 go saveChannelSnapshot直接返回 Data不再走本文件末尾的 StartRelyPull)。
default其它未匹配的协议类型

传输协议transportProtocol := res.Data.Device.TransportProtocol(),用于 RTSP TCP/UDP 与后续 MS 参数一致。

3.6 MS start_relay_pull

streamUrl != "" 时调用:

POST http://{msAddress}/api/ctrl/start_relay_pull

参数包括:stream_nameurlauto_stop_pull_after_no_out_mspull_timeout_msrtsp_mode 等(见 internal/pkg/ms/api.go StartRelyPull)。失败则整体 HTTP 错误响应

3.7 CDN 转发

通道开启 CDNCdnState == 1)且 CdnUrlrtmp 开头,在返回前 go StartRelayPush 向 MS 启 relay_push,把当前流推到 CDN;错误仅打日志,不阻塞主流程返回。

3.8 异步通道快照 saveChannelSnapshot

  • go saveChannelSnapshot(streamName, res.Data)(GB 分支在 Invite 成功后也会调用)。
  • ms.Snapshot(device, streamName) 取一帧,经临时 .raw / .jpg 文件,ff.FFMpeg.SnapFile 转码,再 MvSaveVideoSnapshotDir 下由 stream.Snapshot(dir, deviceId, channelId) 规则命名的路径。
  • 全程 defer recover 与日志,失败不影响播放接口成功返回

四、与 gbs.Invite 的关系

GB28181 路径:是由 Invite 走完整链路(Catalog 在线、StreamInfoGetStreamGroup、端口范围、防重复 Invite、SipSendVideoLiveInvite 等,详见 video_live_invite.go)。

stream_play 传入的 InviteParams 包括:设备/通道业务 ID、PlayType、格式化后的 起止时间字符串DownloadSpeedStreamName、以及已查好的 DeviceItem/ChannelItemCaller 标记为 http 请求stream play invite,便于日志区分来源。


五、播放流程总结

  1. 统一出口:平台无论网页还是内部服务,只要 设备+通道 即可拿 同一套 StreamResp(MS 节点、流名称、多协议地址),客户端按能力选播。
  2. 协议差异分支(switch):流媒体源/ONVIF 走 拉流 URL + MS;RTMP 推流走 设备主动 pub;GB28181 走 信令 Invite,避免一种模式套所有协议。
  3. 直播与回放:用 时间区间 切换 playType流名称生成策略(回放带计数后缀),并差异化 autoStopPullAfterNoOutMs
  4. MS 抽象:拉流统一 StartRelyPull;GB仍由 RTP/SIP 与 MS API 协作,此接口只负责 触发 Invite
  5. 快照与播放解耦:快照慢或失败不影响 拉流流程

六、注意点

说明
GB 回放 StopMultiMSStream流名称前缀 + playback 模糊停流时,需要注意不同流流名称是否相同。
RTMP(= 2)本请求可能 只返回地址信息,需要注意MS on_pub_start 通知是否有达到。
Invite 与重复请求Invite 内部有 InviteRequestState 与 MS 上 Pub 状态判断;
stream_play 高频重入,需要注意防抖;防抖可以参考 core/pkg/dt
流图片快照临时文件与 FFmpeg 依赖配置 FFMpeg、磁盘 SaveVideoSnapshotDir;未设置会导致快照失败。

七、相关源码索引

说明路径
流播放 Logiccore/app/sev/vss/internal/logic/http/video/stream_play.go
GB Invitecore/app/sev/vss/internal/logic/http/gbs/video_live_invite.go
MS start_relay_pull / start_relay_pushcore/app/sev/vss/internal/pkg/ms/api.go
流名称与 PlayAddresscore/common/stream/main.go
响应core/common/types/stream.goStreamRespPlayAddress
HTTP 路由core/app/sev/vss/internal/handler/http/routers.go