FEATURED · 精选文章

海康国标GB/T 28181 PS流解析实战:从RTP抓包到H.264/H.265裸流提取

发布时间 / 2026/9/8 14:37:23
来源 / 创域科博编辑部
栏目 / 资讯中心
海康国标GB/T 28181 PS流解析实战:从RTP抓包到H.264/H.265裸流提取 简介面向音视频开发与流媒体技术人员的实用资源聚焦海康威视设备及国标PS流Program Stream解析提供基于ffmpeg的解封装实现与不依赖第三方库的直接解析两种方案。前者适合快速集成与多格式兼容后者可深入理解PS流结构、PTS/DTS时间戳提取及音视频数据包分离逻辑。资源共27个文件以C源码为主含6个cpp源文件和5个头文件另有Visual Studio工程文件sln、vcxproj便于直接打开编译调试压缩包仅2.24MB体量轻巧。已有1095人学习适合希望从代码层面掌握PS流解复用的开发者。压缩包内两个独立版本目录结构清晰可直接对比实现差异也可作为二次开发或项目移植的参考基础。 做国标GB/T 28181接入这几年我收到过好几份名为“海康、国标ps流解析”的压缩包打开之后基本是同一类东西一两段信令抓包、几段PS流二进制样本、外加各路网友写的解析代码。说白了凡是需要把海康摄像头接入自建视频平台的人最后几乎都会撞上同一个坎——信令通了、设备上线了、INVITE也发出去了但视频流就是黑屏。这问题九成出在PS流Program Stream解析上。这篇文章我就以这份资料里沉淀的实践笔记为线索把海康设备通过国标GB/T 28181上报的PS流从RTP抓包到H.264/H.265裸流提取再到送进解码器播放的完整链路讲清楚。适合三类人看一是自研视频平台、正在对接国标设备的服务端开发者二是做视频网关、需要把国标流转换成RTSP/FLV/WebRTC等格式的中间件开发者三是负责安防平台运维经常要对着抓包文件排查“设备在线但不出流”的工程师。1. 会碰到PS流解析的场景比预想的多得多1.1 自研平台直接接入海康设备最典型的就是自研平台作为SIP服务器把海康的IPC、NVR、模数混合设备直接注册进来。很多人拿到海康设备后第一步都是开ONVIF或者RTSP因为这两个协议的资料多、工具全用VLC就能验证。但客户现场往往要求走国标GB/T 28181尤其是新建安防平台或政府类项目信令层面必须按国标规范来。设备注册上线之后平台向设备发送INVITE请求设备在200 OK里回SDP然后就开始通过RTP向平台推送媒体流。这个媒体流默认封装就是PS流。也就是说平台侧不仅要做SIP信令处理还要在媒体面接收RTP、重组PS、提取出H.264/H.265裸流最后送进解码器或转封装模块。任何一环出了问题用户看到的就是黑屏或者花屏。这里有一个容易忽略的点SDP协商结果并不只影响推流参数它还直接决定了解析器的启动方式。比如视频编码是H.264还是H.265音频是G.711还是AACRTP打包是基于TCP还是UDP负载类型是96还是98这些都要先解析出来再决定后续怎么处理PS包。1.2 平台向上级平台级联推送第二种场景是把自己平台的视频流向上级国标平台级联。这时候自己的平台既是SIP客户端也是媒体源。平台内网里的摄像头可能走的是RTSP、ONVIF或者厂商私有SDK但要推给上级平台时必须把编码数据重新封装成PS流再通过RTP推出去。很多人在这个场景下会踩一个坑直接把H.264的Annex-B裸流塞进RTP负载里发出去上级平台要么黑屏要么花屏。原因就是国标GB/T 28181规定媒体流采用PS封装而且PS包头、系统头、PSM、PES这些结构基本都要有。视频编码数据在HDMI线里传送是一回事在国标信令协商出的媒体通道里传输又是另一回事容器格式必须对得上。级联场景的难点还不只是封装而是码率适配和并发控制。一个平台下面挂着几百路摄像头上级平台拉流时按需发送INVITE平台要动态地从内部存储或实时流中取数据重新封装成PS并推出去。每路流的封装逻辑要做到“互不干扰”资源回收也要及时否则长时间运行内存会持续上涨。1.3 运维排查时如何用抓包定位问题第三类场景是运维排查。设备不上线、上线了不出流、推了几分钟黑屏、图像花掉又恢复这些现场问题最后都会落到抓包分析上。我自己的排查顺序是先看SIP信令是否正常交互再看媒体端口是否通、是否有RTP包到达最后才看RTP负载里能不能找到PS起始码00 00 01 BA。很多运维同事对信令非常熟一到媒体分析就卡住。原因在于信令是文本协议肉眼能读懂而PS流是二进制容器需要工具或脚本去展开。如果对PS结构不熟抓包文件里明明是一堆有效数据也看不出问题在哪。反过来如果理解了PS流的结构用Wireshark的“Follow UDP Stream”再导出原始字节很快就能判断是设备没发流、RTP被防火墙丢弃还是PS负载本身异常。2. 开始解PS前先把容器、编码、传输这三层关系理顺2.1 一次国标INVITE协商之后能拿到哪些关键信息在写解析代码之前必须能看懂SDP里每一行是什么意思。国标GB/T 28181的INVITE请求和响应中SDP会明确给出媒体格式和传输参数。比如常见的SDP片段里会有这样几行mvideo 6000 RTP/AVP 96 98 cIN IP4 192.168.1.100 artpmap:96 PS/90000 artpmap:98 H264/90000 asendonly其中rtpmap里写着“PS/90000”意思是96号负载类型承载的是PS封装时钟频率90000赫兹。这就告诉接收端收到的RTP负载要先按PS容器去解解出来的编码数据再根据SDP里协商的编码格式去解码。SDP里还能看到RTP传输协议。国标GB/T 28181-2016支持TCP和UDP两种RTP传输方式。UDP模式下要处理丢包和乱序解析器要有状态恢复能力TCP模式下RTP包前面会加4字节的长度前缀解析前要先剥离这段长度信息。很多人第一次对接国标平台时只用VLC去拉流测试等到自己写代码才发现UDP和TCP的组包逻辑完全不同。2.2 PS流的关键结构不是一堆随机的二进制PS流是MPEG-2系统层定义的一种容器格式最显著的特征就是到处都有起始码。和TS流固定188字节一个包不同PS流是可变长包一般用于相对可靠的点对点传输像光盘存储、RTP承载这类场景。国标GB/T 28181选择PS作为媒体封装因为它适合在RTP这种面向连接的传输通道上承载整帧数据。一个典型的PS流由四类结构组成包头部、系统头、PSMProgram Stream Map和PES包。它们分别用起始码区分起始码十六进制类型解析要点00 00 01 BAPS包头部包含SCR、program_mux_rate等头部长度可变主要用来定界00 00 01 BB系统头描述码流中的流数量、带宽上下限等大多数解析器可跳过00 00 01 BCPSM关键结构描述各路的stream_id与编码格式映射关系00 00 01 E0–EF视频PES承载H.264/H.265等视频编码数据00 00 01 C0–DF音频PES承载G.711、AAC等音频编码数据00 00 01 BD私有流1国标里常用于承载音频或厂商私有信息需根据PSM判断拿到一个PS包之后第一件事就是识别起始码然后根据起始码类型分流。很多人解PS时不喜欢处理PSM直接按固定的stream_id去抓视频PES这在单路视频、无音频的情况下确实能跑通但一旦设备同时推送视频和音频或者视频编码信息变了不解析PSM就会出错。2.3 海康设备RTP负载的真实形态不是每次都从PS头开始海康设备走国标上报时绝大多数情况下RTP负载的第一个字节就是PS起始码00 00 01 BA。但我也遇到过例外设备在RTP负载前面加了一段私有扩展头导致按PS标准去解析时根本找不到包头视频自然出不来。判断方法很简单收到RTP包后先取负载前四个字节如果不等于00 00 01 BA不要急着判定“不是PS流”先把这段数据显示成十六进制看看。如果是海康的私有扩展通常会有固定长度的头字段把扩展头的长度算出来再往后找往往就能看到PS头了。还有一个普遍现象海康NVR多路通道同时出流时每一路的RTP包是独立的SSRC不同RTP时间戳也不同。但同一个通道的PS流里视频和音频会被复用到一个PS包序列里。如果只按video的RTP SSRC去过滤数据把音频PES丢掉那音频就丢了如果不去区分stream_id把音频数据误当成视频送进解码器图像就会花掉。3. 手写一个PS解复用器的完整思路3.1 先别碰PS头把RTP包整理干净不管负载里装的是PS还是裸H.264第一步都是先把RTP这一层处理好。RTP包头里最需要关注的是seq、timestamp和payload type。seq用于判断丢包和乱序timestamp用于音视频同步payload type用于确认是不是自己要的PS流。UDP场景下RTP包可能乱序到达如果直接按到达顺序把负载拼起来PS包内部的数据就是乱的。正确做法是按seq从小到大重排或者至少做一个重排序窗口窗口内的乱序包先缓存等seq连续后再送往解析器。窗口大小不用太大国标设备的RTP包间隔通常很稳定缓存32个包左右就够用。TCP场景下RTP包前面有4字节的长度字段这个长度是整个RTP包的长度不是负载长度。解析时要先读这4个字节确定包边界再把RTP头剥掉剩下的才是RTP负载。很多人用TCP方式拉流黑屏就是忘了这4字节的前缀把长度信息当成负载的一部分去解析。3.2 从PS头一路解析到PES拿到一段以00 00 01 BA开头的字节流后先判断PS头版本。第二个字节的高四位是版本标识一般看第4个字节从0开始算的话是buf[4]的高四位如果是“10”就是MPEG-1版本“01”是MPEG-2版本。海康和绝大多数国标设备推送的都是MPEG-2版本。MPEG-2版本的PS头长度不固定包含SCR、program_mux_rate等字段还有可变的stuffing字节。实际开发时一般不会去完整解析每个字段只需要正确计算出PS头的长度然后跳过它继续往下找系统头、PSM或者PES起始码。PES包的解析是关键。PES起始码是00 00 01加上stream_idE0到EF是视频C0到DF是音频。PES头里有16位的PES_packet_length这个字段表示后面还有多少字节属于当前PES包。但要注意在海康推流中一个视频帧数据量可能超过65535字节一个PES包并不一定刚好承载一帧。解析逻辑不能认为一个PES就是一帧还要在PES载荷里通过00 00 01起始码去切分H.264/H.265的NALU。PES头里还有PTS和DTS信息这两个值是90kHz时钟单位是90分之一毫秒。换算成毫秒时直接除以90就行。如果PES头标志位里PTS_DTS_flags为“10”表示只有PTS为“11”表示PTS和DTS都有。解码和显示时PTS用来做音视频同步DTS用来控制解码顺序B帧多的码流两者区别明显。3.3 从PES载荷提取H.264/H.265裸流视频PES的载荷里装的就是编码后的ES流以NALU为单位。H.264的NALU起始码是00 00 01或者00 00 00 01H.265也是同样的起始码规则。解析时直接扫描00 00 01找到起始码后从下一个起始码开始才算新的NALU。实际解析时有一个很核心的问题PES包的边界和NALU边界不是对齐的。一个NALU可能横跨两个PES包一个PES包也可能包含多个NALU。所以解析器不能在PES包结束时就把数据直接交给解码器而是要把PES载荷先追加到一个缓冲区里按照起始码切出一个个完整的NALU再把NALU交给解码器或转封装模块。H.264的SPS和PPS非常重要前者是序列参数集包含分辨率、帧率等信息后者是图像参数集包含熵编码模式等。解码器初始化时需要SPS/PPS否则拿到IDR帧也没法解。海康设备一般会在视频流开始时的第一个IDR帧前带上SPS/PPS但后续的关键帧不一定重复携带。所以解析器应该缓存最近一次收到的SPS/PPS在解码器初始化时主动注入。H.265除了SPS/PPS还有VPS视频参数集。H.265的PES里如果显示数据是十六进制的40 01 0C开头的片段那就是VPS。解析H.265时VPS、SPS、PPS三件套要一起缓存缺一个都可能出现解码器初始化失败。3.4 一个可以照着写的简化状态机下面这段是核心解析流程的简化伪代码省略了长度校验和错误处理逻辑但状态机的骨架是完整的。// state: 0 找PS头, 1 跳过PS头, 2 解析PSM/PES, 3 提取ES while (read_rtp_payload(buf, len) 0) { if (type PS_PACK_HEADER buf[0]0 buf[1]0 buf[2]1 buf[3]0xBA) { ps_header_len parse_ps_header(buf, len); offset ps_header_len; while (offset len) { if (buf[offset]0 buf[offset1]0 buf[offset2]1) { if (buf[offset3]0xBC) { parse_psm(bufoffset, len-offset); } else if (buf[offset3]0xE0 buf[offset3]0xEF) { pes_len parse_pes_header(bufoffset, len-offset); es_data get_es_payload(bufoffset, pes_len); split_nalus(es_data); } } offset; } } }实际生产环境要比这个复杂得多比如RTP分包导致的“一个PS包横跨多个RTP包”、UDP丢包后找不到完整PS头等情况都需要加状态保存和错误恢复。我的经验是解析器每次收到RTP包时先尝试在新负载里找00 00 01 BA如果找不到就把负载追加到上一段残留缓冲区里继续解析。这样无论PS包怎么被RTP分包都能正确重组。4. 海康国标PS流调试中印象最深的几个坑4.1 RTP负载的第一个字节不是00 00 01 BA这个坑我前前后后踩过三次。第一次是在一个老项目上平台收到的RTP负载前面有好几个字节怎么都找不到PS头最终发现是海康某型号IPC固件会在RTP负载前面加私有扩展。后来换了新固件扩展头又消失了兼容性真是让人头疼。建议处理方案不要在解析器里硬编码“RTP负载第一个字节必须是PS头”而是做一次适应性检测。如果负载前4字节不是00 00 01 BA就从头字节开始扫描最多扫描到第64个字节找到00 00 01 BA后就从该位置开始解析同时记录下这个偏移量后续包复用同一个偏移值。这样可以兼容多种设备的私有前缀。不过需要提醒的是如果扫描了256字节还找不到PS头不要再继续扫了基本可以断定这是非PS负载或者RTP payload type配错了。继续盲目搜索只会浪费CPU还会把正常数据切坏。4.2 SPS/PPS不是每个IDR都带缓存策略要果断海康设备在国标推流时通常只在视频流的起始位置送SPS/PPS。如果平台在推流中途才加入解码或者解析器缓存被误清掉那么后面收到的IDR帧也会因为缺少参数集而解不出来。正确的做法是在解析器内部维护一个编码参数缓存。当收到类型为7的NALUH.264 SPS和类型为8的NALUH.264 PPS时直接覆盖更新缓存。当解码器初始化或切换通道时先把缓存的SPS/PPS以“00 00 00 01 NALU”的格式拼好连同IDR帧一起送下去。H.265同理类型为32的NALU是VPS33是SPS34是PPS。这三类参数集的优先级最高解析时要保证“先于关键帧送达解码器”。如果参数集缓存为空即使收到了IDR也应该先缓存不送去解码等到参数集到位后再拼帧。4.3 FFmpeg直接解析海康PS流报错别急着怀疑自己很多人习惯拿到PS流后先扔给FFmpeg用avformat_open_input直接打开结果FFmpeg返回Invalid data found when processing input就开始怀疑自己抓包抓错了。实际上FFmpeg对PS容器的解析严格遵循MPEG-2标准而一些海康设备推送的PS流在字段细节上并不完全标准比如PSM的版本号不递增、PES长度与实际数据不匹配等。FFmpeg通常会容忍部分问题但超过了容错范围就报错。不用在一棵树上吊死。遇到这种情况正确姿势是自己先按PS起始码做一次粗略的切分提取出H.264/H.265裸流然后用ffprobe工具验证裸流是否正常。把提取出来的裸流存成文件用ffprobe看一下能否识别出编码格式、分辨率和帧率基本就能判断是解析问题还是设备推流问题。4.4 时间戳跳变与音视频不同步海康设备国标推流时RTP时间戳和PES头里的PTS都是90kHz时钟两边理论上应该一致。但实际抓包会发现有些设备在码流切换分辨率、网络波动重推关键帧时RTP时间戳会出现跳变跳变幅度甚至能达到几千甚至几万。如果解码端完全按照RTP时间戳去驱动显示就会出现画面卡顿或快进。更合理的方式是以PES头里的PTS作为主时间基准RTP时间戳只用于组包和排序。解析时把PTS换算成毫秒显示时间直接用这个毫秒值驱动。音视频同步也要按PTS对齐。如果PS包里同时有视频PES和音频PES音频PTS和视频PTS必须换算到同一时间base后进行比较。音频超前或滞后超过一定阈值时再做丢帧或插帧处理。否则观众看到的就是口型对不上画面和声音各走各的。4.5 视音频之外的私有流别当垃圾扔海康NVR或部分IPC的PS流里除了视频PES和音频PES还会出现BD起始码的私有流。这个流在部分设备上承载的是设备侧叠加的OSD信息、智能分析结果或IO状态。遇到私有流时有两个选择一是通过PSM里的stream_type判断其真实用途如果是音视频就直接送解码器做同步二是确认是厂商私有协议再根据自己的业务决定是否解析。最忌讳的是把BD流的数据当成音频或视频塞给解码器。我在一个项目里见过解码器持续报错排查了两天才发现是有设备把私有流数据混在视频PES后面解析器没做stream_id过滤。5. 解析出裸流之后怎么送进解码器5.1 自己喂解码器还是交给FFmpeg拿到H.264/H.265裸流之后有两条路可选。一条是把裸流重新封装成MP4或者FLV再交给播放器另一条是直接用解码器解码后在自研播放器里渲染。前者转发简单做好封装头就行后者延迟更低适合实时预览场景。我的经验是如果项目只要求“能在播放器里看”、不要求极低延迟就重新封装。用FFmpeg的libavformat把H.264裸流封装成FLV或者fMP4再通过HTTP-FLV或HLS分发代码量小、稳定。如果项目要求毫秒级实时预览、还要做AI分析那最好直接对接解码器自己管理缓冲和渲染。5.2 解码前的数据规范化start code和extradata缺一不可裸流送解码器之前编码数据必须保证Annex-B格式也就是每个NALU前面都有起始码。如果是从PS流里提取出来的NALU本身不带起始码长度字段的话要自己补上。H.264的SPS/PPS需要额外设置到解码器的extradata里这个设置时机比送第一帧数据还要早。用FFmpeg解码时如果直接用avcodec可以把SPS/PPS和Extradata关联好也可以直接把Annex-B格式的数据填进AVPacket让解码器内部去解析。只要保证每个AVPacket里包含完整的视频帧数据不要把一个视频帧拆成两个包送进去解码就不会出大问题。5.3 低延迟场景下的缓冲改进低延迟场景下最影响延迟的就是解析器和解码器之间的缓冲。常见的做法是在解析器里设置一个最大缓冲时长比如200毫秒如果缓冲超过这个值就果断丢弃旧的缓存帧只保留最新的关键帧。这样在弱网环境下图像会“跳帧”但不至于越拉越慢、延迟越积越大。还要注意关键帧和非关键帧的处理差异。如果当前丢帧了后续收到的普通P帧和B帧都依赖前面的参考帧解码出来也是花屏不如直接丢弃这些帧等下一个IDR到了再恢复显示。海康设备支持信令请求关键帧平台可以主动发媒体控制消息请求I帧比傻等下一个自然关键帧快得多。6. 抓包和样本文件比十段代码更有用最后分享一点日常习惯。我在排查国标对接问题时永远优先抓包而不是直接在代码里加日志。抓包文件可以保存下来反复用Wireshark分析也能发给设备厂商定位。Wireshark里过滤国标推流最常用的两个过滤器组合是“rtp ip.src设备IP”和“rtp.ssrc目标SSRC”。抓到RTP包后选中任意一个RTP包右键“Follow”再选择“UDP Stream”就能在导出原始数据时看到PS起始码00 00 01 BA。把这段数据导出成.bin文件就可以用ffprobe或自定义脚本做离线解析。我会在项目里留一个“样本库”目录按设备品牌和型号存放抓包文件比如“海康-DS-2CD7A47-RTV-PS.bin”“海康-NVR-国标-音频视频混合.bin”。下次对接新设备时遇到问题先拿样本库里的已知正常文件跑解析器能快速区分是解析器bug还是新设备的差异。这个方法帮我省掉了大量和厂商来回沟通的时间。至于“海康、国标ps流解析”这类资料包里常见的代码片段我的建议是参考结构、不要盲抄。每家设备的PS流细节差异不小抄来的代码大概率没法直接跑通生产环境。先把PS容器的层次关系理解透再结合抓包数据逐层验证遇到问题时才不会被表象带偏。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻