WVP-GB28181-pro 国标级联语音喊话全流程解析:从 SIP 信令到 RTP 转发
【免费下载链接】wvp-GB28181-pro基于GB28181-2016、部标808、部标1078标准实现的开箱即用的网络视频平台。自带管理页面,支持NAT穿透,支持海康、大华、宇视等品牌的IPC、NVR接入。支持国标级联,支持将普通摄像机/直播流/直播推流转国标共享到国标平台。项目地址: https://gitcode.com/GitHub_Trending/wv/wvp-GB28181-pro
本篇技术文章基于 WVP-GB28181-pro 的官方文档 级联语音喊话流程说明,系统梳理国标级联场景下语音喊话(Audio Broadcast)的完整 SIP 信令时序、SDP 协商细节与底层转发动作。读完本文,你将掌握:上级平台如何通过INFO(Broadcast)命令驱动下级平台向设备发起喊话、下级平台又如何向上级发起INVITE(broadcast)接收推流,以及broadcastForPlatform三种传输模式在 SDP 中的具体体现,并能对照源码定位每个环节的实现在哪一层。
一、级联语音喊话的完整信令时序
在国标级联拓扑中,"上级平台"发起喊话,WVP-PRO 作为"下级平台"居中调度,最终将语音流送到摄像机/NVR。文档给出的完整时序如下:
@startuml "上级平台" -> "下级平台": 1. 发起语音喊话请求 "上级平台" <-- "下级平台": 2. 200OK "上级平台" <- "下级平台": 3. 回复Result OK "上级平台" --> "下级平台": 4. 200OK "下级平台" -> "设备": 5. 发起语音喊话请求 "下级平台" <-- "设备": 6. 200OK "下级平台" <- "设备": 7. 回复Result OK "下级平台" --> "设备": 8. 200OK "下级平台" <- "设备": 9. invite(broadcast) "下级平台" --> "设备": 10. 100 trying "下级平台" --> "设备": 11. 200OK SDP "下级平台" <-- "设备": 12. ack "上级平台" <- "下级平台": 13. invite(broadcast) "上级平台" --> "下级平台": 14. 100 trying "上级平台" --> "下级平台": 15. 200OK SDP "上级平台" <-- "下级平台": 16. ack "上级平台" -> "下级平台": 17. 推送RTP "下级平台" -> "设备": 18. 推送RTP @enduml整个流程分为四个阶段:
- INFO 命令下发阶段(1-4):上级平台向 WVP-PRO 发送
CmdType=Broadcast的 INFO 请求;WVP-PRO 先回200 OK确认收到,再以Result OK的 INFO 响应通知命令受理结果,上级平台再回200 OK完成确认。 - 设备侧建立阶段(5-12):WVP-PRO 向目标设备转发同样的喊话请求并收到
Result OK,随后主动向设备发起INVITE,SDP 中标明要接收设备回传的媒体流,设备返回100 Trying→200 OK(SDP)→ WVP-PRO 回ACK建立会话。 - 上级平台收流阶段(13-16):WVP-PRO 反过来向上级平台发起
INVITE(broadcast),上级平台按 SDP 中指定的 ZLMediaKit 端口推送 RTP 语音流。 - RTP 双向流转阶段(17-18):上级平台的 RTP 到达 WVP-PRO 侧的 ZLMediaKit,再被转发到设备的收流端口,形成"上级平台 → 下级平台 → 设备"的语音链路。
值得注意的两个设计点:
- Result OK 的异步确认:WVP-PRO 收到 INFO 后先回
200 OK,真正的执行结果通过独立的 INFO 响应(Result: OK/ERROR)异步回传。这与 GB/T 28181 协议中命令应答与结果通知分离的设计一致。 - 双向 INVITE:喊话链路需要两条 SIP 会话——一条对设备(收设备的反馈/占用控制),一条对上级平台(收语音推流)。WVP-PRO 在其中充当信令和媒体流的"双枢纽"。
二、接收上级平台喊话命令:BroadcastNotifyMessageHandler
上级平台下发的 INFO 消息(CmdType=Broadcast)由 BroadcastNotifyMessageHandler 处理,该类通过afterPropertiesSet()将自身注册到 NOTIFY 消息处理器的Broadcast命令分支下。
其handForPlatform()方法完整对应时序图中的 1-4 步,关键处理逻辑如下:
- 参数校验:解析 INFO 消息体中的
SN(消息序列号)与TargetID(目标通道国标编号),任一缺失直接回400 BAD_REQUEST;SourceID可选,缺省时取TargetID的值。 - 通道定位与类型校验:通过
channelService.queryOneWithPlatform(platform.getId(), targetId)在该级联平台共享的通道列表中定位通道;若通道不存在回404 NOT_FOUND;若通道数据类型不是 GB28181(例如共享了 1078 或流媒体通道),则回403 FORBIDDEN——源码注释明确"只支持国标的语音喊话"。 - 200 OK 应答:找到设备和通道后立即
responseAck(request, Response.OK),对应时序第 2 步。 - 占用检查:调用
playService.audioBroadcastInUse(device, deviceChannel)检查该设备语音通道是否已被占用,若已占用则通过broadcastResultCmd向上级回复Result: ERROR,避免并发喊话抢占同一语音通道。 - 回复 Result OK 并触发后续流程:
commanderForPlatform.broadcastResultCmd(platform, channel, sn, true, ...)在 SIP MESSAGE 发送成功的回调中,才调用platformService.broadcastInvite(...)向上级平台发起 INVITE。
其中broadcastResultCmd的实现位于 SIPCommanderForPlatform,它构造如下 XML 响应体(对应时序第 3 步的 "回复Result OK"):
<?xml version="1.0" encoding="GBK"?> <Response> <CmdType>Broadcast</CmdType> <SN>消息序列号</SN> <DeviceID>目标通道国标编号</DeviceID> <Result>OK 或 ERROR</Result> </Response>三、向上级平台发起 INVITE 接收推流:SDP 消息体逐行解读
platformService.broadcastInvite(...)的核心实现在 PlatformServiceImpl,处理流程为:
- 复用已有会话:先从 Redis 中查询该通道
InviteSessionType.BROADCAST类型的旧InviteInfo。若旧流在 ZLMediaKit 中仍然存在(isStreamReady为 true),直接回调 hook 复用,避免重复建流;若流已不存在则清理 Redis 中的脏数据。 - 打开收流端口:
receiveRtpServerService.openGbRTPServerForBroadcast(mediaServerItem, platform, channel, ...)在选中的 ZLMediaKit 节点上分配一个 RTP 收流端口与 SSRC,端口打开失败时向上抛端口监听失败错误。 - 写入会话状态:构造
InviteInfo(状态ready)写入 Redis,供后续事件关联。 - 发送 INVITE:调用
commanderForPlatform.broadcastInviteCmd(...),收到200 OK后构建SsrcTransaction(类型InviteSessionType.BROADCAST)放入sessionManager,供 BYE 时按 Call-ID 或流标识定位会话。
INVITE 的 SDP 消息体由broadcastInviteCmd拼装,逐行对应如下(这是理解时序第 13-16 步的关键):
| SDP 字段 | 内容示例 | 说明 |
|---|---|---|
v=0 | - | SDP 版本 |
o= | <平台国标编号> 0 0 IN IP4 <sdpIp> | 发送方为下级平台的国标编号,sdpIp取 MediaServer 的 SDP 对外地址 |
s=Play | Play | 国标约定:实时点播/喊话使用Play(文档中点播流程第 2 步同样强调 s 字段为 "Play" 代表实时点播) |
u= | <通道国标编号>:0 | 目标用户(被喊话通道) |
c= | IN IP4 <sdpIp> | RTP 接收地址 |
m=audio | <端口> UDP/TCP ... 8 96 | 媒体端口即上一步打开的收流端口;载荷 8 为 PCMA(G.711A)、96 为 PS 封装 |
a=recvonly | - | 本方向只接收,明确"下级平台是收流方" |
a=rtpmap:8 | PCMA/8000 | G.711A 编码、8kHz 采样率 |
y= | SSRC | 国标私用字段,指定本流 SSRC |
f= | v/2/5/25/1/4096a/1/8/1 | 国标私用媒体参数字段:视频编码/分辨率/帧率/码率 + 音频编码/码率/采样率,a/1/8/1表示 G.711、8kHz |
传输模式:broadcastForPlatform配置项
SDP 中m=行与a=setup行由配置项broadcastForPlatform决定,该配置定义在 UserSetting,默认值为UDP:
/** * 国标级联语音喊话发流模式 * UDP:udp传输 TCP-ACTIVE:tcp主动模式 TCP-PASSIVE:tcp被动模式 */ private String broadcastForPlatform = "UDP";三种模式在 SDP 中的差异(见 SIPCommanderForPlatform#broadcastInviteCmd):
- UDP(默认):
m=audio <port> RTP/AVP 8 96,无 setup 行; - TCP-PASSIVE:
m=audio <port> TCP/RTP/AVP 8 96+a=setup:passive+a=connection:new,由上级平台主动建立 TCP 连接,适合下级平台在 NAT 之后的场景; - TCP-ACTIVE:
m=audio <port> TCP/RTP/AVP 8 96+a=setup:active+a=connection:new,由下级平台主动连接上级。
200 OK的后续处理在inviteOKHandler中完成:先解析响应 SDP 中的y=字段并与本端 SSRC 比对(兼容上级未回传 SSRC 及上级自定义 SSRC 的情况),若为 TCP-ACTIVE 模式则解析响应 SDP 中的音频端口,调用connectRtpServer主动连接上级平台的媒体端口;收流超时则回BYE并释放端口、SSRC 与 SSRC 会话(streamByeCmd+closeRTPServer+sessionManager.removeByStream)。
四、收到上级推流后:如何转发到设备
时序第 17 步"上级平台推送 RTP"落到 WVP-PRO 侧后,处理链如下:
- ZLMediaKit on_publish 事件:ZLM 收到上级推流后回调 WVP-PRO,触发
PlatformServiceImpl中openGbRTPServerForBroadcast注册的回调——此时先更新InviteInfo状态为ok(onPublishHandlerForBroadcast),再把HookData(app/stream/MediaServer)交给BroadcastNotifyMessageHandler中预设的hookEvent。 - 定位设备侧发送任务:
BroadcastNotifyMessageHandler用sendRtpServerService.queryByStream(hookData.getStream(), targetId)查找已存在的SendRtpInfo。若存在(即设备侧会话已建立),直接调用mediaServerService.startSendRtp(...)让 ZLM 把收到的流转发到设备收流端口——对应时序第 18 步。 - 不存在则补发设备 INVITE:若
SendRtpInfo缺失(说明设备侧会话尚未建立或已断开),则重新调用playService.audioBroadcastCmd(device, deviceChannel, mediaServer, app, stream, 60, true, ...),以isFromPlatform=true标记"来自上级平台的喊话",向设备重发 INVITE 并等待设备200 OK中携带的收流端口。 - 并发占用兜底:若此时
audioBroadcastInUse返回 true(例如本地网页恰好也在喊话同一设备),说明设备侧会话已被占用,则调用platformService.stopBroadcast(...)向上级回 BYE 并清理资源,保证一条语音通道同时只服务一个喊话会话。
五、本地喊话与级联喊话的复用关系
级联喊话并非独立实现,而是完全复用了本地(Web/API 发起)喊话的底层通道。从源码结构看:
- 本地喊话入口是 PlayController 的
/api/play/broadcast/{deviceId}/{channelId}(GET/POST 均可,支持timeout、broadcastMode参数)与/api/play/broadcast/stop/{deviceId}/{channelId},内部调用playService.audioBroadcast(...); - 设备/通道级统一入口由 SourceBroadcastServiceForGbImpl 实现,
startBroadcast(纯广播)与startTalk(对讲,带回流播放地址)最终都落到同一个playService.audioBroadcast; - 会话占用状态由 AudioBroadcastManager 基于
ConcurrentHashMap<channelId, AudioBroadcastCatch>维护,exit()判断占用、get()/update()/del()管理生命周期; - 流媒体侧转发由
SendRtpInfo任务描述,mediaServerService.startSendRtp/startSendRtpPassive按broadcastForPlatform模式选择推流方式。
这也解释了为什么级联喊话与本地喊话互斥——两者共享同一个AudioBroadcastManager占用表,任何一侧先建立会话,另一侧都会被audioBroadcastInUse拦截。
六、会话终止与资源回收
喊话链路的拆除涉及三条清理动作,均集中在 PlatformServiceImpl#stopBroadcast:
public void stopBroadcast(Platform platform, CommonGBChannel channel, String app, String stream, boolean sendBye, MediaServer mediaServerItem) { if (sendBye) { commanderForPlatform.streamByeCmd(platform, channel, app, stream, null, null); } finally { receiveRtpServerService.closeRTPServer(mediaServerItem, app, stream); // 清理 Redis 中的 InviteInfo inviteStreamService.removeInviteInfo(inviteInfo); sessionManager.removeByStream(app, stream); } }即:向上级回 BYE(释放对端会话)→ 关闭 ZLM 收流端口 → 删除 Redis InviteInfo → 移除内存 SSRC 会话。触发时机包括:上级平台主动 BYE、收流超时(InviteInfo查询不到时自动回 BYE)、设备侧异常导致的自动重建失败,以及停止喊话 API。此外SIPCommanderForPlatform.streamByeCmd支持按 Call-ID 或app/stream双路定位SsrcTransaction,保证即使一侧先收到 BYE 也能正确清理。
七、关键要点小结
- 信令顺序不可变:INFO(Broadcast) → 200 OK → Result OK 是上级平台的触发链;随后才是对设备与对上级平台各自的 INVITE/ACK 会话建立,两条 INVITE 会话互相独立但共同支撑同一条 RTP 链路。
- SDP 三要素:
s=Play声明实时流类型,y=指定 SSRC,a=recvonly+m=audio ... 8 96声明只接收 G.711A 语音;f=字段携带国标媒体参数(a/1/8/1对应 G.711/8kHz)。 - 传输模式可配:
broadcastForPlatform支持UDP(默认)、TCP-PASSIVE、TCP-ACTIVE三种模式,分别映射到 SDP 的m=行协议与a=setup行,NAT 环境建议按网络位置选择被动或主动模式。 - 单通道单会话:
AudioBroadcastManager保证一个设备语音通道同一时刻只有一个喊话会话,级联喊话与本地喊话互相抢占检查。 - 全链路资源回收:BYE、收流超时、失败回调三条路径都会执行"关端口 + 清 Redis + 删 SSRC 会话"的完整清理,避免 ZLM 端口与 Redis 状态泄漏。
如需进一步了解本地(非级联)语音对讲的媒体流转,可参考 语音对讲原理图,其中描述了 ZLMediaKit on_publish 回调驱动 WVP-PRO 向设备发 INVITE 的通用链路,与本文级联场景共享同一套底层转发机制。
【免费下载链接】wvp-GB28181-pro基于GB28181-2016、部标808、部标1078标准实现的开箱即用的网络视频平台。自带管理页面,支持NAT穿透,支持海康、大华、宇视等品牌的IPC、NVR接入。支持国标级联,支持将普通摄像机/直播流/直播推流转国标共享到国标平台。项目地址: https://gitcode.com/GitHub_Trending/wv/wvp-GB28181-pro
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考