Android公网对讲机接入GB28181平台:WebRTC与SIP协议实战解析
简介:面向移动开发者的公网对讲与国标视频监控融合方案,以完整工程形式提供,适合需要快速搭建轻量级对讲平台、接入国标视频监控或开展WebRTC语音视频通话二次开发的团队与个人。方案覆盖安卓公网对讲机、平板和嵌入式设备,兼容大屏触控与小屏按键机型。压缩包内共1732个文件,约79.22MB,代码以Java、C++、C#为主,搭配XML配置、PNG与WEBP界面素材、so动态库及WAV、MP3音频,便于按模块拆解对讲协议、国标注册流程和媒体传输逻辑。目前已有155人学习下载,具备一定参考热度。资源不仅提供可运行的服务端与多端客户端,还附带了WebRTC通话模块和国标平台对接示例,目录划分清晰,可帮助开发者缩短选型与原型验证周期,也有利于在此基础上扩展SIP对讲和警用数字集群融合通讯等后续能力。
1. 公网对讲如何绕过传统专网限制,直连GB28181平台
过去做公网对讲,一般只解决语音通道,和视频监控完全是两张皮。仓库、工地、应急现场往往既要有对讲机,又要装一套NVR,两个系统独立运行,调度员只能一边听对讲一边盯监控。这个项目要解决的正是这个问题:把Android公网对讲机(包括大屏触屏、小屏按键、平板和嵌入式终端)直接注册到GB28181标准视频监控平台,让音频对讲和视频流走同一条国标链路。它轻量,能跑在企业局域网、公网甚至基站环境下,适合物业调度、铁路巡检、应急演练这类需要语音与视频联动的场景。我拆完这套源码后发现,整个系统的核心不是“对讲”本身,而是WebRTC与GB28181之间的信令和媒体接入设计。
2. WebRTC模块拆解:从peer_connection_factory到audio_device_module
2.1 PeerConnectionFactory初始化顺序决定连接成功率
先看C++侧的WebRTC代码。 peer_connection_factory.cc 是创建整个呼叫链路的入口。在Android端调用时,必须先初始化网络线程、信令线程和工作线程,再创建 PeerConnectionFactory 。如果顺序反了,就会出现ICE状态一直卡在“new”这类诡异问题。
// Java层使用PeerConnectionFactory的典型初始化
PeerConnectionFactory.initialize(
initializationContext,
new PeerConnectionFactory.Options()
.setNetworkIgnoreMask(...)
);
PeerConnectionFactory factory = PeerConnectionFactory.builder()
.setAudioDeviceModule(audioDeviceModule)
.setVideoEncoderFactory(defaultVideoEncoderFactory)
.createAudioDeviceModule()
.createPeerConnectionFactory();
setAudioDeviceModule 是公网对讲机上最关键的注入点。传统Android手机有扬声器、耳机、蓝牙,而按键对讲机只有一颗受话器和一个功放,默认的 audio_device_module.cc 会枚举多个设备,导致啸叫。我替换成只暴露“听筒”和“麦克风”两个逻辑设备的实现,再用 setNetworkIgnoreMask 过滤掉不需要的网卡。 networkIgnoreMask 按位设置:0保留默认,1忽略Wi-Fi,2忽略蜂窝,4忽略以太网。对讲机通常只走蜂窝或Wi-Fi,我会用 setNetworkIgnoreMask(6) 同时忽略蜂窝和以太网,强制走Wi-Fi,避免基站回传时选错网卡。
2.2 audio_device_module.cc:把系统的音频路由藏起来
audio_device_module.cc 在这里的作用是提供稳定的PCM数据源。公网对讲机按下PTT时,App要立刻打开麦克风采集并编码,松开时马上停止。问题在于系统音频焦点和 MODE_IN_COMMUNICATION 的切换延迟,直接调用会使前几十毫秒出现“咔哒”声。我在采集线程里加了一个预开启缓冲:在PTT按下前200ms就初始化 AudioRecord 但不发射数据,按下时通过 writeIndex 偏移让媒体流从中间开始,冗余数据直接截断。
这个方案的关键是把音频路由控制在App层,而不是交给系统。系统路由一旦切到喇叭,WebRTC的回声消除模块会以为是免提场景,误伤近端语音。对讲机外接手咪时,更要把 setSpeakerphoneOn(false) 写死,否则手咪的麦克风收不到远端声音。
2.3 android_network_monitor.cc:基站回传场景的网卡选择
android_network_monitor.cc 负责网络类型变化通知。Android公网对讲机插的是物联网卡,经常走APN专网。运营商的基站回传环境下,IP是内网地址,经过NAT才到公网服务器。如果直接用系统默认网络, iceCandidate 会附带内网IP,对端无法连接。我的做法是禁用WebRTC自带的网络监控,自己用 Socket 探测服务器可达性,然后强制把网卡绑定到已连通的网络。
# 查看当前默认网络路由
ip route show
# 禁用某一网络接口上的WebRTC探测
adb shell settings put global captive_portal_mode 0
captive_portal_mode 改为0只是让系统不检测Wi-Fi的门户认证,避免它频繁切换网络。真正要做的还是在代码里监听 onNetworkStatusChanged ,用 ConnectivityManager.bindProcessToNetwork 绑定到已经通了的蜂窝网络。绑定后,WebRTC产生的Socket都会走这条链路,ICE的 server-reflexive 地址才会是正确的公网映射。
2.4 video_encoder_wrapper.cc:给GB28181复用的H.264出口
video_encoder_wrapper.cc 有意思的地方是它把WebRTC的 VideoFrame 转成了H.264裸流。GB28181平台要求的不是普通RTP媒体,而是PS封装里的H.264码流。我通过实现一个内部回调,把编码器输出的每帧数据直接打入PS封装器,省去了二次解码。注意编码参数:Profile必须设成Baseline,部分国标平台不支持MainProfile;关键帧间隔GOP设成2秒或4秒,不能像WebRTC默认那样根据带宽动态调,否则平台录像会花屏或无法seek。
| 模块文件 | 职责 | 对讲机场景的改动重点 |
|---|---|---|
| peer_connection.cc | ICE状态机、编解码协商 | 固定STUN服务器,关闭mDNS候选 |
| android_network_monitor.cc | 网络类型变化通知 | 基站回传时屏蔽Wi-Fi扫描 |
| audio_device_module.cc | 音频采集/播放抽象 | 替换为单设备实现,预开启缓冲 |
| video_encoder_wrapper.cc | 视频编码封装 | 强制Baseline、固定GOP |
3. Android公网对讲机多形态适配:触屏、按键、平板与嵌入式
3.1 触屏机型的PTT事件分发:从onTouch到KeyEvent
大屏触屏对讲机(比如7寸以上工业平板)操作逻辑与手机类似,但有个特殊点:用户习惯屏幕上有明显的“按住说话”按钮,同时实体侧键也要能触发PTT。在Android里要同时监听 OnTouchListener 和 KeyEvent 。
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.keyCode == KEYCODE_TALK && event.action == KeyEvent.ACTION_DOWN) {
startPushToTalk()
return true
}
if (event.keyCode == KEYCODE_TALK && event.action == KeyEvent.ACTION_UP) {
stopPushToTalk()
return true
}
return super.dispatchKeyEvent(event)
}
KEYCODE_TALK 是Android 4.4以后出现的按键码,部分对讲机固件不会上报,需要从 /proc/bus/input/devices 里解析自己的按键上报值。我见过最坑的是设备把PTT映射成 KEYCODE_F1 ,导致只能在全屏Activity里收到事件,弹窗或悬浮窗就失灵。解决办法是注册一个Kiosk模式的 DeviceAdminReceiver ,把输入焦点固定在自己进程上,再通过 WindowManager.addView 做全局PTT浮层。
3.2 小屏按键机型的方向键导航与音频路由
小屏幕按键机型通常只有2.4寸屏,没有触摸。整机运行Android系统但锁定了触摸事件。这类机器要把App的导航方式改成方向键+确认键。更重要的是音频,手机用扬声器外放,对讲机则用内置蜂鸣器或手咪。要在 AudioManager 里设置 setSpeakerphoneOn(false) 并设定 MODE_IN_COMMUNICATION ,否则系统会把音频投到喇叭而不是听筒。
AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
am.setMode(AudioManager.MODE_IN_COMMUNICATION);
am.setSpeakerphoneOn(false); // 强制走听筒/手咪
MODE_IN_COMMUNICATION 会让系统启用降噪和回声消除,这和WebRTC的音频处理模块有点重叠。如果两边都开着AEC,声音会发闷。标准做法是WebRTC里关闭软件AEC,只靠硬件DSP。按键机型的麦克风通常灵敏度低,还需要在 AudioRecord 里把 bufferSizeInBytes 设成双倍分片,否则编码线程会被拉长,PTT按下后延时感明显。
3.3 安卓平板的双摄与多窗口调度
安卓平板在项目里是调度台形态,屏幕同时显示GB28181视频画面和对讲联系人列表。Android原生Split Screen或PiP会切走Activity,造成WebRTC重新negotiation。我的做法是把对讲和视频放同一个Activity的不同Fragment里,用 WindowInsets 控制尺寸变化。摄像头切换要注意,GB28181平台一般只支持一路主码流,平板前置摄像头用于本地视频通话,后置用于监控推流,同时工作时要确认硬件编码器是否支持两个实例。
平板通常有多个麦克风,系统默认拾音会采集到环境噪声。我建议在视频监控推流时关闭语音,只做视频;语音对讲时再打开音频设备模块里的麦克风。否则扬声器播出的调度指令会被后置麦克风重新采集,形成多处回声。处理方式是监听 MediaCodec 的 INFO_OUTPUT_FORMAT_CHANGED ,在视频流稳定后主动释放音频会话。
3.4 嵌入式Linux与裁剪Android的编码器适配
嵌入式公网对讲机往往是定制Android主板,内存512MB甚至更低,没有GPU。视频编码要靠硬核VPU,而GB28181推流要求H.264。在这类板子上,不要用 MediaCodec 默认编码器,要找厂家提供的 libOMX_*.so 。同时去掉 SurfaceView ,直接用 MediaCodec.createInputSurface() 来做编码,画面数据从相机直接走,不需要渲染。
| 设备形态 | 屏幕 | 主要适配点 | 常见坑 |
|---|---|---|---|
| 大屏触屏 | 7寸以上 | UI布局、侧键映射 | 弹窗焦点被抢 |
| 小屏按键 | 2-4寸 | 方向键导航、音频路由 | 按键码不标准 |
| 安卓平板 | 10寸 | 多窗口模式、双摄切换 | 麦克风阵列回声 |
| 嵌入式 | 无屏/串口屏 | VPU编码、内存优化 | 系统裁剪导致硬解不可用 |
嵌入式Linux终端更麻烦,需要自己编译WebRTC静态库,并处理 android_network_monitor.cc 里的netlink通知。我一般会先跑一遍项目的 gradlew.bat 脚本拿到底层so,再在 external/ 目录下打补丁。用 adb shell dumpsys media.codec 能看到硬件编码器列表,如果列表为空,说明VPU驱动没加载,先解决驱动再调应用。
4. GB28181注册与音视频推流参数实战
4.1 SIP注册与设备ID编码
GB28181的注册流程和SIP协议类似。终端要发 REGISTER 请求, Authorization 字段用MD5摘要。很多开发者直接用OkHttp发HTTP,但GB28181走UDP或TCP的SIP,不是HTTP。项目里实现了一个精简的SIP栈,只处理 REGISTER 和 MESSAGE 。初次注册时用 REGISTER sip:platform@ip:port SIP/2.0 。
import hashlib
def digest(server_realm, nonce, username, password, method, uri):
ha1 = hashlib.md5(f"{username}:{server_realm}:{password}".encode()).hexdigest()
ha2 = hashlib.md5(f"{method}:{uri}".encode()).hexdigest()
return hashlib.md5(f"{ha1}:{nonce}:{ha2}".encode()).hexdigest()
server_realm 和 nonce 来自平台返回的401响应。注意设备ID编码:国标要求20位数字编码,前10位是行政区划编码,后10位是设备类型加序号。如果设备ID不对,注册会一直401或200 OK但无后续。我排查过很多次,都是因为把设备ID当成随意字符串填进去。一个可行的设备ID示例是 34020000001320000001 ,前10位 3402000000 代表海康默认域,后面依次是类型和序号。
4.2 音视频编码与PS封装参数
GB28181的视频流要在RTP上封装PS流。音频用G.711A或G.711U,视频用H.264。下表是我在局域网站点调通的参数组合,公网和基站场景会略有调整。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| H.264 Profile | Baseline | 平台兼容性最好 |
| 分辨率 | 640x480 / 1280x720 | 公网对讲机码率有限,720p足够 |
| 帧率 | 25fps / 15fps | 应急场景不需要60fps |
| GOP | 2s | 太大录像seek慢,太小码率波动 |
| 音频编码 | PCMA 8000Hz 16bit | 国标标准,不要用OPUS |
| 传输协议 | RTP over UDP | 基站场景优先UDP,局域网可用TCP |
注意音频编码:WebRTC默认用OPUS,但GB28181平台很多不支持,必须转成PCMA。我在 audio_device_module.cc 里加了重采样和编码器,把16kHz的OPUS换成8kHz的PCMA,再塞进PS封装。最初转换时不做丢包补偿,通话偶发“咔哒”,后来加了jitter buffer才稳定。PS封装头里的 PTS 和 DTS 也要换算成90kHz时基,否则平台录像时间轴会错位。
4.3 NAT穿透与保活机制
企业局域网内,对讲终端到国标平台之间通常可以传TCP,丢包少。基站或公网环境,运营商NAT会限制UDP映射时间,GB28181的RTP流可能断流。我一般会把SIP信令走TCP,但RTP走UDP,并在 Contact 头里额外注册一个备用地址。
# 查看NAT映射表(Linux服务器上)
conntrack -L | grep udp
对于基站链路,要在终端侧设置 KeepAlive 间隔而不是只靠 Expires 。国标注册过期时间建议120秒,但同时要在会话里每60秒发一个keepalive。如果把keepalive放大了,NAT会在5分钟级别断开,通话瞬间卡死。保活消息可以是一个空 MESSAGE 或 OPTIONS ,关键是要触发平台回一个事务响应,这样NAT映射会被双方刷新。
4.4 局域网/基站部署的抓包验证
部署时最容易踩的坑是SIP信令通了但媒体流方向不对。我习惯在平台服务器上抓包:
tcpdump -i eth0 -s 0 -w gb28181.pcap udp port 5060 or udp port 20000
然后打开抓到的文件,首先看平台是否回了 200 OK ,然后看是否有 INVITE 或 SUBSCRIBE 。如果只有 REGISTER 后面没有媒体协商,说明平台没有找到对应通道。再看RTP的SSRC是否和SIP里的 SDP 一致。很多平台严格要求SSRC等于设备ID的十进制后四位,不一致时直接丢包。
5. 向SIP与PDT融合演进:架构预留与验证方法
这个项目的设计目标并不止于GB28181,后续还要支持SIP对讲平台,以及和PDT(警用数字集群)融合通讯。我在代码里看到一层“统一呼叫抽象”,把所有呼叫类型都封装成 StartSession/EndSession ,这为扩展留下了清晰的接口。新增SIP协议时,只需要实现协议适配器,把SIP的 INVITE 映射到统一接口;同样的,PDT融合通过窄带网关把语音转成PCM,再塞进对讲的音频通道。
PDT的语音编解码格式和PCMA差异很大,直接接入会产生大量噪声。正确做法是PDT网关侧先做窄带到宽带的转码,转成PCMA后进入GB28181音频流。我在项目中预留了一个 AudioBridge 接口,专门处理不同协议的音频互联。如果要做PDT融合,建议加一台独立的转码服务器,不要让Android终端承担转码负载,否则对讲机CPU会飙高、耗电翻倍。
验证是否已经为SIP/PDT预留好扩展点,可以直接看项目里是否有一个 service 包和 protocol 包分离。如果协议实现和业务逻辑混在一起,后续加协议必然大改。我一般会做一个最简验证:写一个假的 SipProtocolAdapter 实现统一接口,跑一轮对所有频道的呼叫,看是否能正常建链和挂断。
最后分享一个最直接的验证技巧。推流时在平台侧用 ffprobe 查看收到的码流封装:
ffprobe -show_packets -of csv -i capture_ps.stream | head -20
看到 PCMA 或 H264 表示封装正确。如果看到 OPUS ,说明音频没有转码,平台大概率不出声。也可以用 ffplay 直接播放抓到的RTP流,立刻能听出噪声和延迟是不是符合预期。这样不用平台调试界面,也能快速定位是信令问题还是媒体问题。
更多推荐

所有评论(0)