
简介面向FreeSWITCH开发者与国标视频接入场景的GB28181协议支持模块源码包可直接编译集成到FreeSWITCH用于对接符合GB/T 28181-2016标准的网络摄像机、NVR及视频平台解决异构设备统一接入问题。包内共9个文件以核心C实现mod_gb28181.c、跨平台构建脚本Makefile.am/Makefile.in、Windows工程配置.vcxproj为主另有Python示例脚本、配置模板、自动加载配置说明和README文档压缩包整体仅17KB结构紧凑且各文件职责明确适合快速阅读和二次修改。已有57人学习参考。功能覆盖SIP信令注册、心跳保活、RTP over UDP实时拉流、设备目录查询、云台控制、录像回放等关键能力适配主流FreeSWITCH版本。conf目录提供基础配置模板autoload_configs说明自动加载方式省去自行摸索配置的时间配合README中的编译步骤和部署场景无需额外中间件即可作为国标视频统一接入网关适合安防集成或统一通信项目中需要国标接入能力的中高级开发者。1. GB28181 国标接入不是加个模块那么简单FreeSWITCH 在这里到底管哪一段凌晨两点的值班室监控平台上的一路摄像机离线了故障单转到我这。这不是普通网络问题是国标 28181 注册掉线了。我接过的项目里把 GB28181 视频接入模块跑在 FreeSWITCH 下是最容易让融合通信团队集体翻车的一件事听起来是“加一个模块”的事做起来要同时懂 SIP、懂 RTP、懂 PS 封装还得摸清各厂商设备那些不按协议走的行为。这个源码包解决的就是“让 FreeSWITCH 能看懂国标设备的信令和视频流”这最后一公里——网上搜“海康威视gb28181接入平台开源”或“gb28181客户端”的人多半是想找一个现成方案但源码包到手不等于能跑起来。本文从协议拆解讲到编译落地再到四个高频坑适合正在做应急调度、视频联动、语音对讲或者要把监控摄像头接到已有呼叫中心里的人。2. 拆开国标协议栈信令、媒体、对讲三条线在模块里各走什么路2.1 国标信令的 SIP 外壳与 FreeSWITCH 原生协议栈的差异先说结论FreeSWITCH 自带的 sofia 协议栈不认识 GB28181 的私有扩展你不能直接把摄像头注册到 sofia profile 上。GB28181 规范全称《公共安全视频监控联网系统信息传输、交换、控制技术要求》信令部分基于 SIP 2.0但做了一堆私有扩展。比如 REGISTER 请求里携带的 User-Agent 是设备厂商自己的标识INVITE 请求的 SDP 里媒体描述是PS/90000而不是 FreeSWITCH 认识的H264/90000。更麻烦的是国标设备的注册周期短、重传快不少设备收到 401 之后不会像 RFC3261 那样严格重发 REGISTER而是直接发一个 CSeq 不连续的 REGISTER。sofia 处理这种包时因为 CSeq 不连续直接回 403设备侧就报注册失败。所以市面上的 GB28181 接入模块有个通用架构取向模块自身维护一套独立的 SIP 协议栈常见基于 eXosip2在一个独立 UDP 端口上监听国标设备信令而不是让设备直接注册到 sofia。模块收到 REGISTER 之后自己完成鉴权和心跳维护收到 INVITE 之后通过 ESL 接口向 FreeSWITCH 发起 originate 呼叫把国标会话“翻译”成一个 FreeSWITCH 能处理的普通呼叫。这个设计在源码包的目录结构里一眼就能看出来——凡是拆成sip、media、esl三个子目录的走的就是这条路。用独立协议栈还有个现实好处国标设备注册周期名义上是 3600 秒但实际很多设备 600 秒就重发一次 REGISTER而且凌晨批量上线时会瞬间涌入几百个 REGISTER。这些流量如果全进 sofia会被当作异常 REGISTER 处理放在独立栈里模块可以做限流和会话收敛把同一个域下多台设备的注册统一管理。2.2 媒体通道PS 流解封装与 RTP 负载类型映射GB28181 的媒体封装是 RTP over UDP但 RTP 的 payload 通常不是裸 H.264而是 PS 流Program Stream。PS 流是 MPEG-2 系统层封装格式里面以 PES 包形式同时携带视频H.264/H.265/MPEG4和音频G.711/AAC。FreeSWITCH 的媒体调度器只认标准 RTP 封装比如 RFC3984 的单一 NAL 单元模式它不知道 PS 流里怎么区分帧边界。所以模块的媒体通道要做两件事。第一是 RTP 负载类型映射国标设备发的 RTP 包 PT 可能是 96、98、112而 FreeSWITCH 会话期望的是一个固定 PT。模块需要在收到第一个 RTP 包时解析实际 PT并为每个会话维护一张从设备 PT 到 FreeSWITCH 侧 PT 的映射表。第二是 PS 解封装把 PS 流的系统头、节目流映射表跳过定位到 PES 包再从 PES 里剥离出 H.264 的 NAL 单元按 RFC3984 重新打成 FreeSWITCH 认识的 RTP 包。最容易忽略的是时间戳。PS 流里 PES 头的 PTS 是 90kHz 时钟但设备实现差异很大有的 PTS 从零开始有的从 Unix 时间戳换算。模块重打包时如果直接转发原始时间戳FreeSWITCH 侧 jitter buffer 会乱跳画面一顿一顿。我的做法是模块维护会话级 RTP 时间戳基准第一个包到达时记下base_pts后续包用pts - base_pts 固定偏移让 FreeSWITCH 看到连续递增的时间戳序列。2.3 语音对讲为什么是另一个世界GB28181 的语音对讲是双向的但上行和下行走的不是同一条路。上行方向设备采集麦克风音频通常以 G.711A 或 G.711U 编码进 PS 流和视频一起封装下行方向平台要对设备喊话需要把 FreeSWITCH 侧的音频以 RTP 单流发给设备设备回 200 OK 后开始播放。麻烦在容易翻车的是编码协商FreeSWITCH 默认用 PCMUG.711U而国内监控设备 90% 默认只收 PCMAG.711A还有一批只认 G.722。如果模块不干预协商直接转发 FreeSWITCH 的 200 OK 给设备设备发现 SDP 里是 PCMU 直接回 488或者干脆不响应。网上搜“gb28181语音对讲”常见到的“设备已呼通但听不到声音”基本就是这个原因。所以模块处理对讲下行时必须在 FreeSWITCH 的 dialplan 或 originate 拨号串里强制音频编码为设备期望的那一种。这一点在后面第 4 章和第 5 章会再展开。3. 把源码包变成能跑的模块最小依赖、编译要点与一台设备注册进来3.1 先看源码包的目录设计判断它是不是“正经包”拿到源码包先别急着编译花十分钟把目录过一遍。一个设计合理的 GB28181 接入模块顶层目录至少能看出三层职责mod_gb28181/ ├── src/ │ ├── gb28181_sip.c # eXosip 信令处理REGISTER/INVITE/BYE/MESSAGE │ ├── gb28181_media.c # RTP 收发、PS 解封装、PT 映射 │ ├── gb28181_session.c # 会话状态机与超时回收 │ ├── gb28181_esl.c # ESL 客户端向 FreeSWITCH 发 originate 命令 │ └── mod_gb28181.c # FreeSWITCH 模块入口load/unload ├── conf/ │ ├── gb28181.conf.xml # 模块运行参数 │ └── acl.conf.xml ├── scripts/ │ └── install.sh └── README.md这个结构里最重要的判断标准mod_gb28181.c是否和业务逻辑分离。如果整个包就一个.c文件把信令和媒体逻辑全塞在模块回调里那线上跑的时候很容易拖垮 FreeSWITCH 主进程——协议栈的 IO 线程和 FS 的 session 线程互相抢锁。我见过不止一次这种单文件模块设备一批量上线FS 进程 CPU 冲满同机的其他 VoIP 呼叫全部受影响。信令层和媒体层分离还有个实际好处信令掉线时媒体流不一定马上断。模块可以在gb28181_session.c里独立维护会话超时信令 60 秒没心跳但媒体 5 秒内还有包就先不拆会话等媒体也超时再回收端口。这个“先媒体后信令”的回收顺序能减少大屏上“视频还在播放设备状态却显示离线”的割裂问题。3.2 编译最小模块依赖安装与手动编译的三个关键点编译前先确认 FreeSWITCH 版本。源码包如果明确写了兼容 FreeSWITCH 1.10 或 2.0就对好版本再编不要拿 1.10 的头文件编 2.0 的模块mod 目录下的 ABI 不兼容会导致模块加载失败。Debian/Ubuntu 系统上一套最小依赖安装步骤如下# 安装编译依赖 apt-get install -y build-essential libexosip2-dev libosip2-dev \ libssl-dev libavformat-dev libavcodec-dev libavutil-dev # 确认 FreeSWITCH 头文件存在 ls /usr/local/freeswitch/include/freeswitch/switch.h # 进入源码包主目录编译安装 cd /usr/local/src/mod_gb28181 make make install如果源码包没有提供 Makefile只给了源码就需要手动编译。手动编译时最关键的三个点是头文件路径、链接库顺序、以及 eXosip 的 API 版本export FS_INC/usr/local/freeswitch/include/freeswitch export FS_LIB/usr/local/freeswitch/lib gcc -shared -fPIC -o mod_gb28181.so \ src/mod_gb28181.c src/gb28181_sip.c src/gb28181_media.c \ src/gb28181_session.c src/gb28181_esl.c \ -I$FS_INC -I/usr/include/eXosip2 \ -L$FS_LIB -lfreeswitch \ -leXosip2 -losip2 -lssl -lavformat -lavcodec -lavutil \ -Wl,-rpath,$FS_LIB cp mod_gb28181.so $FS_LIB/mod/说明一下参数-I$FS_INC指向 FreeSWITCH 头文件编译主要用到switch.h、switch_types.h、switch_module_interface.h这几个头文件定义了模块加载接口和会话 API-leXosip2链接的是国标信令栈库。注意 eXosip 4.x 里库名是eXosip22.x 里叫eXosip链接前用dpkg -L libexosip2-dev确认实际库名避免“undefined reference to eXosip_init”这类报错-lavformat用于 PS 解封装如果源码包是自己实现的 PS 解析器可以不链接 ffmpeg 相关库。还有一个容易踩的坑有人在 Windows 上编译好后把 .dll 或 .so 拷到 Linux 服务器。GB28181 接入模块牵扯到 UDP 端口绑定和网卡多队列这种代码最好在目标服务器上用相同内核版本编译。内核的 epoll 行为、UDP 收包缓冲区大小不一致搬过去经常出现“编译没报错、跑起来丢包”这种玄学问题。注意编译报错时先看第一条错误不要翻最后一条。绝大多数是头文件路径不对或库版本不匹配第一条错误指向的缺失头文件或符号就是根因。3.3 最小配置让第一台海康/大华设备注册进来编译安装完成在 FreeSWITCH 的autoload_configs目录下放一个最小配置configuration namegb28181.conf descriptionGB28181 Module settings param namesip-port value15060/ param namesip-id value34020000002000000001/ param namesip-domain value3402000000/ param namesip-password value12345678/ param nameesl-host value127.0.0.1/ param nameesl-port value8021/ param nameesl-password valueClueCon/ param namemedia-port-range value40000-41000/ param namertp-pt-video value96/ param namertp-pt-audio value98/ /settings /configurationsip-port是模块自己监听的国标信令端口不能和 FreeSWITCH 的 sofia 端口冲突建议避开 5060、5080用 15060 这种高位端口sip-id是平台侧在国标系统里的 20 位编码前 10 位是行政区划代码中间 2 位是类型200 代表平台后 8 位是序号sip-domain填设备所属的域编码。esl-password默认的ClueCon是 FreeSWITCH 出厂密码生产环境必须改掉。media-port-range是模块给国标设备发流的 RTP 端口段这个段要同时加到防火墙白名单里第 5 章会具体讲。配置好后在 FreeSWITCH 控制台执行module load mod_gb28181然后把海康威视摄像机或大华相机的国标配置页打开填入平台 SIP 服务器地址、端口 15060、设备自己的 20 位 SIP 用户 ID、域编码和密码。设备保存配置后模块日志会打出一行类似REGISTER from 34020000001320000001的记录到这里第一台设备就算接入进来了。4. 打通 FreeSWITCH 与国标设备dialplan 路由和媒体协商的几个坑4.1 用 ESL 把国标 INVITE 变成 FreeSWITCH 的 originate 呼叫模块收到国标设备的 INVITE通常是上级平台或客户端要拉流要把这次请求变成 FreeSWITCH 能处理的一次呼叫。最常见做法是模块通过 ESL 连接 Event Socket发一条 originate 命令。gb28181_esl.c里的核心逻辑大致是这样import socket import time def send_originate(session_id, rtp_host, rtp_port): sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((127.0.0.1, 8021)) sock.send(bauth ClueCon\r\n\r\n) time.sleep(0.3) cmd ( bgapi originate {gb28181_session_id%s,hangup_after_bridgetrue} user/1000 bridge(rtp://%s:%d)\r\n\r\n % (session_id, rtp_host, rtp_port) ) sock.send(cmd.encode())这段代码的逻辑是把国标会话 ID 作为自定义 channel 变量传给 FreeSWITCH让 FreeSWITCH 呼一个本地分机user/1000分机应答后通过bridge桥接到模块的 RTP 端口上。这样 FreeSWITCH 侧的呼叫状态就是一个普通呼叫挂断、保持、转接都能用现成 API。参数说明bgapi是异步执行不会阻塞 ESL 连接多路并发拉流时其中一路卡住不会拖累其他请求hangup_after_bridgetrue表示桥接结束后自动挂断避免残留会话gb28181_session_id变量必须传给 dialplan后续路由靠它反查会话。有个细节值得说不要直接在 originate 命令里bridge(rtp://...)应该让呼叫先进入 dialplan。国标设备的 SDP 里音频视频参数组合复杂dialplan 里可以用变量和条件做更灵活的路由。4.2 dialplan 怎么写按国标会话 ID 路由并保持呼叫存活常见做法是约定一个特殊被叫号码前缀比如gb_后面跟国标会话 IDextension namegb28181-router condition fielddestination_number expression^gb_(\d)$ action applicationanswer/ action applicationset datagb28181_session_id$1/ action applicationset datartp_autoadjust_ratetrue/ action applicationset datartp_append_audio_codecPCMA/ action applicationset datartp_append_video_codecH264/ action applicationplayback datasilence_stream://1/ /condition /extension这个 dialplan 的核心是保持信道存活把媒体控制权交给模块。answer让 FreeSWITCH 进入应答状态否则媒体通道不会建立rtp_autoadjust_ratetrue允许 FreeSWITCH 自动调整 RTP 发送速率国标设备码率波动大时不容易溢出rtp_append_audio_codecPCMA和rtp_append_video_codecH264是给媒体协商强制指定编码避免 FreeSWITCH 默认的 PCMU 把设备带偏。playback silence_stream://1放一秒静音流让 FreeSWITCH 的媒体调度器开始跑起来音频视频通道才真正生效。注意这里没有做真正的桥接因为国标模块自己在 RTP 层面负责媒体转换dialplan 只负责把呼叫状态保持在“通话中”。如果信道状态掉到hangup模块会收到 BYE设备端就会断流。大屏上出现“呼叫还在但画面卡住”时先查 FreeSWITCH 里这个 channel 状态是不是还是ACTIVE。4.3 媒体协商参数PT 映射、SSRC 和编码优先级GB28181 设备发来的 SDP 长这样mvideo 30000 RTP/AVP 96 98 112 artpmap:96 PS/90000 artpmap:98 H264/90000 artpmap:112 MPEG4/90000 assrc:12345678FreeSWITCH 不认 PS 这种媒体描述模块要在 INVITE 转发给 FreeSWITCH 之前做 SDP 重写。这里两个参数值得仔细调rtp-pt-video模块把 PS 和 H264 统一映射到这个 PT 上再告诉 FreeSWITCH 这是H264/90000FS 侧不需要知道底层是 PS另一个是 SSRC 处理FreeSWITCH 默认用自己的 SSRC如果国标平台侧强制校验 SSRC模块重写 SDP 时要把设备 SSRC 透传过去。我一般建议在模块配置里加一组优先级参数param namevideo-priority valueH264,PS,MPEG4/ param nameaudio-priority valuePCMA,PCMU,G722/这组参数的意思是设备 SDP 里同时列出多种编码时模块优先选 H264 处理音频优先选 PCMA。原因很现实PS 解封装在 ffmpeg 里走的是 mpegts 那套逻辑对厂商自封装的 PS 流兼容性参差不齐很多设备 PS 头打得不规范解出来花屏而 H264 直封包相对规整。音频选 PCMA 是因为国内监控设备几乎默认支持FreeSWITCH 侧默认 PCMU协商到 PCMA 后模块只做很小的负载类型转换不用转码。4.4 多域接入和重复注册一个 FreeSWITCH 接两个国标平台实际项目里经常出现一个 FreeSWITCH 既要接运营商级国标平台又要接本地监控平台两边设备域编码不同。模块配置里通常要开domain-checking开关按域隔离会话param namedomain-checking valueon/ param namemax-registers-per-domain value1024/domain-checkingon的含义是同一个设备 ID 在不同域下算不同实体允许同时注册同一个域下相同设备 ID 重复注册才踢掉旧会话。这个开关不开的话本地平台和设备直连同时注册时后注册的会把先注册的顶掉。排查时非常隐蔽两边看设备状态都在线只有拉流时发现其中一路平台总是超时。5. GB28181 接入排查手记注册不上、黑屏、对讲没声的 4 个真实原因5.1 设备一直显示“注册中”UDP 信令被防火墙吃掉现象海康设备国标配置页一直显示“注册中”模块日志里一条 REGISTER 都看不到。这不是协议问题是网络问题。原因模块监听的 UDP 15060 端口没放通。很多运维习惯只放 TCP不知道国标信令是 UDP。另一种是双网卡服务器模块绑定了 0.0.0.0设备请求从网卡 A 进来回包走了网卡 B 的默认路由设备侧等不到响应。解决先在服务器上用 tcpdump 确认信令是否到达tcpdump -i any udp port 15060 -nn -v看不到设备 IP 的包检查设备到服务器的路由和防火墙能看到请求但模块没回包检查模块日志有没有绑定失败记录再用ss -ulnp | grep 15060确认模块确实在监听。5.2 设备已上线但拉流黑屏RTP 端口段被防火墙挡住现象设备状态在线用平台或 VLC 拉流画面黑屏抓包能看到设备在发 RTP但 FreeSWITCH 侧没收到。原因国标设备发流是设备主动向平台侧 RTP 端口推流不是平台去设备那拉。设备在 NAT 后面或跨公网时向模块的media-port-range推 RTP端口未映射或运营商 UDP 限速流量根本到不了模块。解决确认模块的media-port-range对应 UDP 端口段在防火墙全部放通iptables -A INPUT -p udp --dport 40000:41000 -j ACCEPT同时抓包确认模块侧有没有收到设备发来的 RTPtcpdump -i any udp portrange 40000-41000 -nn这个场景最容易踩坑的点SIP 注册走 15060 端口通了但媒体流走的是配置里完全不同的端口段没通。两个端口段都通设备才算真正“可用”。注意改完防火墙后不用重启模块但需要重新拉流才会建立新的 RTP 会话。已经在黑屏状态的旧会话要先挂断。5.3 有画面没声音PCMA 与 PCMU 的编码偏好导致协商失败现象视频出来了对讲或上行音频没声音FreeSWITCH 侧呼叫日志里音频read_codec是 PCMU。原因FreeSWITCH 默认音频编码是 PCMUG.711U国内摄像头 90% 默认只发 PCMAG.711A。模块没在 SDP 重写时强制音频编码FreeSWITCH 协商结果是 PCMU设备发现这个编码不在能力集里直接不发音频 RTP。解决在模块配置里把音频 PT 映射写死dialplan 里显式指定param nameaudio-pt value98/ param nameforce-audio-codec valuePCMA/dialplan 里 originate 拨号串附加编码变量bgapi originate {gb28181_session_id%s,absolute_codec_stringPCMA}user/1000 bridge(...)absolute_codec_stringPCMA让 FreeSWITCH 侧只支持 PCMA设备发现协商结果里有 PCMA 才发音频。注意改完要module reload mod_gb28181并重新拉流新参数才生效不用重启 FreeSWITCH。5.4 设备离线后媒体端口一直占用UDP 端口池耗尽现象跑了一周之后模块报no available RTP port新设备拉流全部失败。原因国标设备异常掉线时只发 BYE 或者干脆不发模块会话超时回收太慢媒体端口没及时释放。尤其设备断电、断网时TCP 的 FIN/RST 根本不存在UDP 层面只能靠超时判断。解决把会话空闲超时调短打开“媒体先于信令回收”开关param namesession-timeout value180/ param namemedia-timeout value60/ param namerecycle-media-first valuetrue/这里的逻辑是信令 180 秒无心跳判定会话死亡但 RTP 媒体 60 秒无包就先释放端口。recycle-media-firsttrue后媒体端口释放和会话销毁是两个独立动作端口池不会被死会话占满。调完重启模块日志里会出现media port recycled端口池使用率应稳定在 60% 以下。6. 验证与进阶用抓包和日志把一次国标呼叫从头看到尾6.1 一条 tcpdump 命令覆盖信令和媒体全程验证国标接入最有效的做法不是看平台界面而是抓包。信令端口 15060媒体端口段 40000-41000一次抓全tcpdump -i eth0 -s 0 -w /tmp/gb28181.pcap \ udp port 15060 or udp portrange 40000-41000抓完用 Wireshark 打开重点看三个时间点的时序REGISTER 有没有回 200 OK、INVITE 后有没有 200 OK、以及 200 OK 后 1 秒内有没有 RTP 包到达。RTP 包在 Wireshark 里手动设置 payload type 为 96 并选 MP2T 解复用否则 Wireshark 不认 PS 流。6.2 日志调试开关分级打开再收敛模块日志通常从 error 到 debug 分级。我的做法是先开 info 跑一天确认无异常出了问题再临时开 debug 抓 10 分钟问题复现完立刻降回 info。FreeSWITCH 控制台执行console log gb28181,debugdebug 级别下会打印每个 RTP 包的 PT、SSRC 和序列号最容易定位“协商是 96 实际发 98”这类设备不按协议走的毛刺。做国标接入这几年我的习惯是维护一张“设备 ID、信令端口、媒体端口、当前会话状态”四列表格排查时先看表格再抓包。这个习惯帮我省掉了很多无头苍蝇式的翻日志时间。把这个思路用到你的项目里上手速度会比直接翻代码快很多希望帮到你。本文还有配套的精品资源点击获取