SIP信令服务器开发心得-信令字段与SDP来源——平台、设备、媒体服务易混淆点

张开发
2026/4/6 6:50:22 15 分钟阅读

分享文章

SIP信令服务器开发心得-信令字段与SDP来源——平台、设备、媒体服务易混淆点
在实际对接 GB/T 28181 拉流时INVITE最常踩坑的不是「会不会发 SIP」而是Via / From / To / Call-ID / Contact / Subject等 以及SDP里每个数到底代表信令平台、IPC/NVR还是流媒体(MS)。填错一种身份现象往往是设备408/481、200 但无流、ACK 对不上、或 MS 根本收不到 RTP。本文围绕本仓库现行实现GBSSender.VideoLiveInvite、AckReq、ParseToRequest、types.Request说明各字段从哪里来并总结排障时容易搞混的点。可与 详解-国标与工程实践 对照阅读。项目源码地址https://github.com/openskeye/go-vss1. 身份身份典型配置 / 数据在 INVITE 里常出现在哪信令平台Config.Sip.ID、rule.Setting推导的SipIP()、Config.Sip.Port、Config.Sip.DomainFrom、Contact、Via 的主机/端口、Subject后半「平台 ID」设备注册时写入的types.RequestSource对端地址串、TransportProtocol、Original、DeviceAddrRequest-URI / To的 host 选型、Via的transport媒体服务器MSSipVideoLiveInviteMessage里MediaServerIP/StreamPort先MS RTPPub再 INVITESDP 的o/c/m与信令监听 IP核心信令From本平台、To通道或设备域内 URI流媒体谁收RTP一般是SDP 指向 MS不要把Sip 监听地址抄进c除非MS 与信令同一台机器且你明确要这么做。2. SIP 头实现集中在core/app/sev/vss/internal/pkg/sip/gbs_send.go的doMakeHeader、toAddress、VideoLiveInvite。2.1 Viacase headerTypeVia: var port uint16(l.svcCtx.Config.Sip.Port) return sip.ViaHeader{ { ProtocolName: SIP, ProtocolVersion: 2.0, Transport: l.req.TransportProtocol, Host: l.setting.SipIP(), Port: (*sip.Port)(port),Host / Port平台侧对外信令地址协议栈回复路径依赖它来自SipIP()Sip.Port。Transport来自l.req.TransportProtocol而l.req是设备注册解析出的types.RequestParseToRequestgetTransportProtocol。易混点把 Via 写成设备网段 IP或 transport 与设备实际 REGISTER 不一致UDP/TCP 混用。2.2 Fromcase headerTypeFrom: var ( port uint16(l.svcCtx.Config.Sip.Port) fromAddr sip.Address{ DisplayName: l.req.DeviceAddr.DisplayName, Uri: sip.SipUri{ FUser: sip.String{Str: l.svcCtx.Config.Sip.ID}, FHost: l.setting.SipIP(), FPort: (*sip.Port)(port), }, Params: sip.NewParams().Add(tag, sip.String{Str: functions.RandWithString(0123456789, 9)}), } ) return fromAddr.AsFromHeader()User必须是Config.Sip.ID平台 20 位编码不是设备 ID、不是通道 ID。Host/Port仍是SipIP() 信令端口。DisplayName当前实现复用l.req.DeviceAddr.DisplayName来自设备注册 From与「谁主叫」无关属展示字段勿据此推断 URI user。易混点From 填成设备或通道会导致对端认主叫错、后续订阅/录像命名混乱。2.3 To / Request-URItoAddress()func (l *GBSSender) toAddress() *sip.Address { // 非同一域的目标地址需要使用host if l.deviceUniqueId[0:9] ! l.svcCtx.Config.Sip.Domain { return sip.Address{ Uri: sip.SipUri{ FUser: sip.String{Str: l.deviceUniqueId}, FHost: l.req.Source, }, } } return sip.Address{ Uri: sip.SipUri{ FUser: sip.String{Str: l.deviceUniqueId}, FHost: l.svcCtx.Config.Sip.Domain, }, } }注意发送直播 INVITE 时NewGBSSender的第三个参数传的是req.ChannelUniqueId见SendLogic.VideoLiveInvite因此这里的deviceUniqueId名实不符——实为通道国标 ID。User通道编码点播目标。Host跨域时用l.req.Source注册来源 / 设备可达信令地址同域用Sip.Domain。易混点把 To 的 user 写成设备主码而设备期望通道 ID。Host误用平台 SipIP设备不在该平台域内监听时应使用注册时的 Source/domain 规则否则 INVITE 根本到不了设备。2.4 Call-IDINVITE新生成随机 Call-IDheaderTypeCallId。ACK / BYE必须从200 OK / 已存对话里原样复用见下一节。2.5 Contactcase headerTypeContactCurrent: var ( port uint16(l.svcCtx.Config.Sip.Port) contact sip.ContactHeader{ Address: sip.SipUri{ FUser: sip.String{Str: l.svcCtx.Config.Sip.ID}, FHost: l.setting.SipIP(), FPort: (*sip.Port)(port), }, } )Contact与From一致平台 ID 平台信令 IP/端口。易混点填设备 Contact 或填 MS 地址——后续 BYE/MESSAGE 可能找不到正确信令终结点。2.6 SubjectVideoLiveInvite内拼接通道 / 播放标志 / ssrc 段 / 平台 ID实时与回放格式不同。此头与SDP 的 SSRC、y相关拼错会导致设备解析流目标错误细节见 SDP 文第二节。3. SDP媒体归属是 MS不是「信令服务器」VideoLiveInvite里 SDP 核心来源先 MSRTPPub拿到StreamPort等再写入 SDPHTTP 逻辑往SipSendVideoLiveInvite里塞MediaServerIP、StreamPort…。var sdpInfo sdp.Session{ Version: 0, Origin: sdp.Origin{ Username: data.ChannelUniqueId, Address: data.MediaServerIP, SessionID: 0, SessionVersion: 0, }, Name: functions.Capitalize(string(data.PlayType)), Connection: sdp.Connection{Address: data.MediaServerIP}, Media: []*sdp.Media{ { Type: video, Port: int(data.StreamPort), Proto: proto, Mode: sdp.ModeRecvOnly, // ... SSRC: fmt.Sprintf(%d%s, playFlag, ssrc), }, }, URI: fmt.Sprintf(%s:0, data.ChannelUniqueId), }SDP 项来源注意点ouser通道 ID设备主码 / 平台 IDo/caddressMediaServerIPSipIP()除非二者同机同网mportStreamPortMS 收流端口SIPSip.Portarecvonly等平台侧接收与「设备 IP」无关SSRC 相关通道号切片 回放计数与 Subject 联动可以任意生成易混点INVITE 头里的 IP 大多是「信令平台」SDP 里的 IP/端口是「媒体平台(MS)」两边 NAT、弹性网卡、内外网映射不同时抄错一方就「信令通、媒体不通」。4. ACK / BYEsend_sip_proc.go从 ACK 起 From/To/tag/Call-ID 要与后续一致。AckReq实现func (l *GBSSender) AckReq(resp sip.Response) (sip.Request, error) { // ... to, _ : resp.To() from, _ : resp.From() var request l.makeRequest( sip.ACK, []sip.Header{ l.makeHeader(headerTypeVia), from, to, l.makeHeaderWith(headerTypeCallIdWith, callId), // ... }, , )From / To直接取自INVITE 的 200 OK设备加的To-tag已在To里。禁止再用「发 INVITE 时的内存 From/To 副本」若与响应不一致。Call-ID用响应里的值否则对话不匹配。易混点自己拼 ACK把From/To 又改回「平台→设备」单向视角忽略200 OK 对调角色与 tag设备直接丢弃或回复 481。5.NewGBSSender直播 INVITE 调用形态inviteData, inviteRes, err : sip2.NewGBSSender(l.svcCtx, req.Req, req.ChannelUniqueId).VideoLiveInvite(req)req.Req注册得到的types.Request提供设备侧 transport、Source、Original等。req.ChannelUniqueId参与toAddress的 user、SDPo用户名、SSRC 推导等。若在别的分支误传设备 ID当作第三参会出现To 错、SSRC/Subject 与通道不一致一整串问题。6. 排障清单From 的 user 是否是Config.Sip.IDTo 的 user 是否是「被点播通道」而不是设备主码To 的 host 是否按「域 Source」规则指向设备可达信令地址Via 的 transport 是否与该路 REGISTER 一致SDP 的 IP/端口是否 MS 侧收流地址而不是Sip.PortACK 是否从 200 OK 抠 From/To/Call-ID而非重算一套7. 小结信令身份From / Contact / Via 主机端口→平台To user→通道To host→域或注册 Source。媒体身份SDPo/c/m→MSMediaServerIPStreamPort。对话延续Call-ID 200 OK 的 From/To(tag)锁定会话ACK/BYE 必须一致。把这三类信息记住比反复抓包猜字段快得多。文内代码路径core/app/sev/vss/internal/pkg/sip/gbs_send.go、core/app/sev/vss/internal/logic/gbs_proc/send_sip_proc.go、core/app/sev/vss/internal/pkg/sip/utils.go。

更多文章