ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

海康国标PS流解析实战:从RTP头到H.264/H.265裸流提取

海康国标PS流解析实战:从RTP头到H.264/H.265裸流提取 简介海康、国标PS流解析是音视频流媒体开发中常见的需求压缩包内提供了一套可直接运行的工程源码分为基于ffmpeg解析和直接解析PS流两个版本方便不同层次的开发者选择学习。资源面向具备C/C基础、希望掌握PS流解封装原理或快速集成国标流解析功能的工程师既能通过ffmpeg版本快速实现音视频帧提取也能通过不依赖第三方库的直接解析版本深入理解MPEG-2 PS流的结构、PTS/DTS及数据包分离等细节。压缩包共27个文件以cpp源文件和h头文件为主包含sln、vcxproj工程文件可直接用Visual Studio打开编译另有sdf、pdb等编译过程文件整体体积仅2.24MB。内容预览显示目录清晰区分了ffmpeg版和直接解析版并附有PsAnaly.hh和test_PsAnaly.cpp等关键文件便于边阅读源码边对照验证。已有1095人学习下载适合需要研究PS流解析细节或进行二次开发的音视频开发者。 干了几年安防平台开发跟海康设备打交道是最多的。最近又把一份“海康、国标ps流解析”的资料翻出来整理了一遍发现里面踩过的坑、总结出的经验网上能系统讲清楚的真不多。很多朋友卡在“SIP信令已经通了、Invite也发出去了、摄像头也返回200 OK了但拿到RTP包之后完全不知道从哪下手”。这篇文章就围绕国标GB/T 28181协议里最核心也最让人头疼的PS流解析把完整链路、报文结构、代码实现到排障思路全部讲透。无论你是做第三方平台接入海康设备还是在搞自己的流媒体网关只要涉及“海康”“国标”“PS流解析”这篇文章都值得你花十分钟看完。1. 国标平台接入的基本链路——从SIP信令到PS流先理清楚一个基本概念GB/T 28181不是单纯的推流协议它是一套“信令媒体”的组合。信令走SIP媒体走RTP而RTP里封装的内容就是PS流。很多刚入行的朋友会把H.264裸流和PS流搞混以为摄像头推过来的RTP包直接拆出来就是视频帧实际操作起来根本不是这么回事。1.1 SIP信令交互与媒体协商过程整个流程大概是这样的平台作为SIP客户端向海康设备的SIP服务器发起注册注册成功之后平台发送INVITE请求请求里携带SDP信息描述希望接收的媒体格式设备响应200 OK返回自己的SDP平台再发ACK确认然后设备就开始往平台指定的IP和端口推RTP流。这里关键点在SDP协商阶段。海康设备在SDP的media描述里视频编码通常标注为PS而不是H.264。也就是说设备明确告诉你“我推的是PS流”。如果你忽略这个标志直接按H.264 RTP去解包第一步就废了。还有一个细节国标规范里规定视频默认走PS封装音频可以是G.711A或G.711U而且音频和视频会打包在同一个PS流里通过同一路RTP传输。这就意味着解析PS流时不能只解视频还得把音频一起处理。1.2 为什么国标选择PS流而非TS流接触过广电级开发的朋友会问为什么不用TS流TS流抗丢包能力强、支持切片这个没错但TS流的开销更大而且国标在设计时更多考虑的是实时监控场景的简洁性。PS流结构更紧凑适合在可靠或半可靠的传输环境中使用解码端拿到完整PS包后解析也比较直接。另外一个现实原因是历史兼容性。国内的视频监控厂家包括海康、大华早期做嵌入式设备时处理PS流的代码库比TS流更成熟沿用PS封装可以最大化复用原有代码降低设备端改造成本。所以国标选PS流是一个综合考虑不只是技术层面的选择。1.3 拿到RTP流之后的第一步不是解PS而是去RTP头很多人栽在这上面。RTP包到达你的服务器端口后你要先从UDP载荷里剥离RTP头然后才是PS数据。RTP头有固定12字节但如果开启了CSRC、扩展头还要额外跳过对应长度。我记得第一次调海康设备时因为RTP扩展头没处理导致PS流的起始码一直对不齐调试了整整一个下午。后来抓包对比才发现海康在RTP头里带了一个扩展字段长度是4字节。所以解析时不能写死跳过12字节一定要动态读RTP头里的扩展位和CSRC计数。注意正确的RTP头长度计算公式是12 4 * CCCSRC计数 (X ? 4 : 0)其中X是扩展位标志。海康的部分型号开启扩展位不处理就等着数据错位吧。2. PS流结构拆解——别被一堆0x000001吓到PS流全称是Program Stream属于MPEG-2系统层封装。它的基本单位是PES包PES包外面再套PS头、系统头、节目流映射表PSM。你可能要问既然基本单位是PES为什么还要额外加这么多头这就是H.264/H.265这种现代编码和MPEG-2系统层之间的“代沟”问题。PS流设计之初是给MPEG-2视频用的H.264/H.265的NALU结构需要由PSM里的流类型来标识再由PES头里的字段来对齐边界。所以解PS流不是简单找PES而是要按固定顺序去识别人工构建的容器结构。2.1 PS流的三层结构PS头、PSM、PES包一个完整的PS流长这样按顺序排列PS头以00 00 01 BA起始里面携带系统时钟基准SCR和复用信息PSM以00 00 01 BC起始描述这个PS流里面有哪些基本流视频流类型、音频流类型以及对应的流IDPES包以00 00 01 E0起始的是视频PES以00 00 01 C0起始的通常是音频PES。PS头和PSM并不是每个PES前面都有。实际上海康设备是“一个视频帧一个PES”但PS头和PSM是间隔若干个PES才出现一次。这里必须注意解析器不能假设每个视频PES前必然有PSM否则遇到没有PSM的包就会崩。2.2 手动解析一段PS流的二进制布局我来展示一个实际的PS流字节序列解释每个字段的含义。网上很多资料只讲理论直接对二进制就懵了这里一步步过00 00 01 BA 44 59 02 04 01 10 09 10 00 00 01 BC 00 0E ...00 00 01 BAPS起始码看到这个就知道PS头来了44后面这几个字节是SCR字段精确到27MHz时钟当场不用细抠关键是知道它存在00 00 01 BCPSM起始码紧接着的00 0E是这个PSM的长度14字节PSM里会包含视频流ID通常是E0和音频流ID通常是C0还有对应的编码类型描述符。继续往下走00 00 01 E0 00 30 86 80 05 21 00 01 00 01 65 B8 04 00 00 01 09 F0 ...00 00 01 E0视频PES起始码00 30PES包长度48字节包括PES头和数据86 80PES头标志86表示MPEG-2版本80表示这后面有PTS显示时间戳05 21 00 01 00 01PTS的编码值需要做位运算还原成真正的90kHz时间戳65 B8H.264 NALU类型为5也就是IDR关键帧的起始码。不对齐的PES包特别容易混淆但只要你从00 00 01这三字节起始码出发每次按长度字段跳转基本不会迷路。PS流最核心的解析方法就是“找起始码读长度按长度跳转”就这么简单。2.3 PS流中H.264/H.265包含关系H.264在PS流里不是一个NALU一个PES而是一个视频帧一个PES里面可能包含多个NALU。比如一个IDR帧的PES里通常包含SPS、PPS、SEI和IDR Slice。所以你提取H.264裸流时要把同一个PES里的所有NALU都拆出来按顺序写入文件。H.265也是一样VPS、SPS、PPS、IDR都在同一个PES里。区别是H.265的NALU头是2字节H.264是1字节。解析时要根据PSM里声明的编码格式去选择对应的NALU头长度不然SPS和PPS边界都分不清。以前遇到一个项目从PS流提取H.265裸流播放器一直打不开。排查到最后发现是VPS丢失了。H.265没有VPS很多解码器直接罢工。所以提取裸流时VPS、SPS、PPS都不能丢。3. PS流解析实操——提取H.264/H.265裸流的核心代码这一节直接给可运行的解析方案。核心逻辑不限语言我用Python描述方便你理解流程生产环境用C或Go改写原理一致。3.1 定义解析状态机解析PS流不推荐一次性把所有数据读进内存再解析单帧PS包动辄几十KB一路码流持续几个小时内存根本扛不住。建议用流式状态机依次识别起始码然后进入对应的解析分支。class PsParser: def __init__(self): self.state SEARCH self.buffer b self.video_frames [] self.audio_frames [] self.stream_id None def feed(self, data: bytes): self.buffer data while True: if self.state SEARCH: # 查找起始码 idx self.buffer.find(b\x00\x00\x01) if idx -1: # 没找到保留末尾两个字节防止起始码跨包 self.buffer self.buffer[-2:] return if idx 0: self.buffer self.buffer[idx:] code self.buffer[3] if code 0xBA: self.state PS_HEADER elif code 0xBC: self.state PSM elif code in (0xE0, 0xC0): self.state PES else: # 未知起始码或填充数据跳过 self.buffer self.buffer[4:] # 进入对应状态后继续处理...这个状态机的核心思想是一次只解析一个包解析完回到SEARCH再找下一个起始码。这样不会有内存暴涨的问题也方便做丢包恢复。3.2 完整解析一个PES包拿到PES起始码后先读2字节的PES长度再读PES头。PES头里最需要注意的是PTS因为后续封装输出时需要它做时间同步。def parse_pes(self, pes_payload: bytes): # pes_payload 不包含起始码 00 00 01 E0 pes_len (pes_payload[0] 8) | pes_payload[1] # 第3字节是标志位第4字节低6位是头数据长度 header_data_len pes_payload[3] 0x3F pes_header_end 6 header_data_len if self.stream_id 0xE0: # 视频 nal_units self.extract_nal_units(pes_payload[pes_header_end:]) self.video_frames.append(nal_units) elif self.stream_id 0xC0: # 音频 self.audio_frames.append(pes_payload[pes_header_end:])这里有一个容易犯的错pes_len是整个PES包的长度包含PES头和数据不包含起始码。很多人按这个长度跳转时跳多了或跳少了后续所有位置全部错位。正确跳转方式是读取完起始码和长度字段后后续还有pes_len - 3个字节属于这个PES。3.3 从PES载荷中提取NALU视频PES的载荷是带起始码的NALU序列直接按00 00 01切分就行。H.264的一个帧通常由多个NALU组成全都要保留。def extract_nal_units(self, payload: bytes): nals [] start 0 while start len(payload): # 找起始码 idx payload.find(b\x00\x00\x01, start) if idx -1: break # 找下一个起始码确定本NALU结束位置 next_idx payload.find(b\x00\x00\x01, idx 3) if next_idx -1: next_idx len(payload) nals.append(payload[idx:next_idx]) start next_idx return nals切出来的NALU注意前4个字节是起始码写入裸流文件时要转成4字节长度前缀的AVCC格式或者保留起始码的Annex-B格式取决于你的解码器要求。大多数播放器两种都支持但比如FFmpeg的h264_mp4toannexb滤镜是处理反向转换的如果你给他AVCC格式反而会报错。所以这里最好根据用途灵活处理。3.4 时间戳恢复与音视频同步PS流的PTS是33位用90kHz时钟需要从编码字节恢复。PTS在PES头里的存储是分散的不能直接读要做位组装def parse_pts(data: bytes) - int: # data是包含PTS的5字节区域 pts 0 pts | (data[0] 1) 0x07 pts 15 pts | (data[1] 1) 0x7F pts 15 pts | (data[2] 1) 0x7F pts 15 pts | (data[3] 1) 0x7F return pts拿到视频和音频的PTS后做同步就简单了视频PTS和音频PTS差值在正负一个帧间隔内就认为是同步的。海康设备默认视频帧率25fps帧间隔40ms也就是3600个90kHz ticks。差值超过这个范围就需要做延迟补偿。注意有些海康设备的音频PTS并不准确尤其当音频编码为G.711A时PTS粒度比较粗。遇到音画不同步先把音频缓冲200ms再播放大部分情况能解决。之后再通过统计平均延迟做动态调整。4. 常见问题与排查技巧实录这部分是踩坑最多的内容。我在调试海康国标PS流时遇到的问题全部整理成速查表每个问题都对应一个实际案例方便你对照排查。4.1 第一帧黑屏或花屏不是解码器的问题现象播放器起播后黑屏几秒才开始出画或者直接花屏。排查方向检查是不是没有等待IDR帧。PS流解析器如果从非关键帧开始解析解码器会持续报错直到等到下一个IDR。所以起播时应该丢弃非关键帧数据直到遇到第一个IDR帧再开始送解码器。检查RTP头长度是否处理正确。海康某些型号开启了RTP扩展头长度判断错误会导致PS起始码错位表现为随机花屏、卡死。检查PSM里的流类型和实际编码是否一致。SDP协商为H.264但设备实际推的可能是MPEG4这种“表里不一”的情况在海康老型号上出现过解析前最好做一次类型探测。4.2 音频有杂音或无声G.711A的坑现象画面正常但音频是刺耳的杂音或者完全没声音。排查方向确认PSM里音频流ID是C0还是C1。有些设备把音频流ID设置成C1如果你的解析器只认C0音频直接被丢掉了。确认解码器参数。G.711A是8kHz采样、8bit编码很多播放器默认按16bit PCM处理就会出现严重杂音。需要先做G.711A到PCM的转换再做音频参数协商。音频RTP的时间戳单位是8kHz不是90kHz。如果按视频时间戳逻辑处理音频RTP时间戳会直接乱套。4.3 RTP分片与重组UDP丢包怎么处理现象网络波动时画面频繁卡顿甚至整个PS流解析中断。排查方向海康设备默认MTU是1500如果PS帧大于MTU会做RTP分片。每个分片的RTP时间戳相同需要按序列号重组后再解析。UDP丢包会导致PS流起始码错位。解析器要有“重新同步”机制比如连续解析失败时丢弃缓冲数据重新搜索00 00 01 BA。没有同步机制的解析器一旦丢包就彻底卡死必须重启拉流。常见问题典型原因处理方式花屏/黑屏未等IDR帧、RTP头长度错误丢弃非关键帧直到IDR动态计算RTP头音画不同步PTS精度不一致音频缓冲200ms统计动态调整有画面无声音流ID错误或G.711A未转换识别C1流ID做G.711A转PCM解析中断UDP丢包导致起始码错位增加重新同步机制视频绿屏H.265解成H.264检查PSM流类型动态切换解码器4.4 抓包与调试工具调试PS流最有效的工具还是Wireshark。抓包时要过滤RTP端口右键解码为RTP然后看RTP载荷的起始字节。如果载荷以00 00 01 BA开头说明PS流对齐正常。如果载荷开头是乱的说明RTP头长度判断有问题。Wireshark里还能直接看RTP的CSRC计数和扩展位帮你确认头长度。调试阶段建议用这个流程先抓包10秒过滤出RTP流看RTP头的前12字节确认SSRC、序列号、时间戳看载荷前4字节确认是不是00 00 01 BA如果不是调整RTP头长度继续抓包验证。我在实际项目中常用一个技巧把RTP载荷导出来单独存成.ps文件用FFmpeg直接解析。ffprobe dump.ps ffmpeg -i dump.ps -c:v copy video.h264如果FFmpeg能正确解析说明PS流数据本身是好的问题出在你的解析器如果FFmpeg也报错说明抓包阶段就有问题优先检查RTP头。5. 从PS裸流到播放器的最后一公里解析出H.264/H.265裸流后后续怎么处理取决于你的应用场景。这里整理几种常见方案方便你根据自己的架构选型。5.1 对接FFmpeg实现转封装与转码用FFmpeg的libavformat库可以直接把PS流封装成FLV或MP4不需要自己写解析逻辑。但对于低延迟场景不推荐直接丢给FFmpeg做转封装因为FFmpeg的PS demuxer是按文件设计的延迟较大。比较稳的做法是自己解析PS流提取裸流后通过FFmpeg的av_write_frame接口直接写FLV或RTMP。这样既能利用FFmpeg的编码能力又能保持低延迟。5.2 国标平台中PS流到WebRTC的低延迟链路如果你想在浏览器里看海康国标摄像头完整链路是PS流解析→H.264/H.265裸流→封装为RTP→推给WebRTC网关。这里最难的不是PS解析而是时间戳映射。PS流的PTS是90kHzWebRTC要求RTP时间戳也是90kHz所以可以做一个线性映射但要注意回绕问题。回绕是很容易忽略的坑33位PTS大约26.5小时回绕一次不做回绕检测的话播放超过一天后时间戳会突然变成负数导致画面冻结。处理方式是比较相邻PTS差值如果差值为负且超过0x10000000就认为是回绕加上2^33的修正值。5.3 本地录像文件的处理建议如果你需要把PS流存成录像文件建议直接存成MP4而不是PS文件。PS文件没有索引拖动播放非常痛苦。正确做法是解析PS流提取裸流再用MP4 muxer封装关键帧写入索引。录制时还要注意关键帧间隔。海康设备默认I帧间隔是50帧2秒如果想做秒级录像回放需要在SDP协商时向设备请求更小的I帧间隔。国标协议里通过SDP的sprop参数里的sar字段控制但这个字段各家实现不一致海康设备有时会忽略。实际项目中我会在录像文件里额外记录关键帧位置配合自定义的时间轴索引回放体验比纯MP4好很多。6. 调试过程中容易忽略的细节最后分享几个调试PS流时容易忽略的细节这些内容网上的教程很少涉及都是我实际调试中总结出来的。6.1 PES长度字段可能不可信你以为PES包长度字段是准的实际上部分海康设备在音频PES里填写的长度字段与实际数据长度不一致。如果你依赖长度字段严格跳转解析到音频PES后就对不齐了。安全做法是解析视频PES时如果长度字段在合理范围内就用解析音频PES时最好不依赖长度字段而是等下一个00 00 01起始码出现。虽然性能差一点但稳健性提升很大。6.2 海康国标设备对Invite信令的SDP有特殊要求国标SDP协商时海康设备比较挑剔。如果你在SDP里同时请求了TCP和UDP传输部分固件会优先走TCP但TCP模式下的PS流解析和UDP模式完全不一样TCP里没有RTP头直接是PS流。另外SDP里的y字段ssrc如果写错设备会一直推流但SSRC不匹配如果你按SSRC过滤数据就会全丢。建议收到第一个RTP包后动态学习SSRC而不是强制校验SDP里协商的SSRC。6.3 保证多看几个PSM再全局解码PSM里声明的编码类型理论上在会话生命周期内不变但海康有些设备在切换分辨率或帧率时PSM内容会变化。比如从主码流切到子码流后新的PSM里视频宽高参数更新了如果你按旧的PSM继续解析画面会撕裂。建议每次收到新的PSM都刷新参数缓存同时在关键帧PES到来时做一次完整性校验。很多小问题追根溯源都是“配置更新时机不对”。我自己的习惯是在生产环境做一个“PSM关键帧双触发参数刷新”的逻辑这样无论设备端怎么切换都能保持正确解码。写在最后PS流解析在国标GB/T 28181对接中绕不开。看似只是一堆起始码和长度字段的遍历实际做好做稳涉及RTP头动态处理、时间戳恢复、同步机制和异常恢复等细节每一层都有坑。如果只记住一句话拿到RTP流别急着解PS先确认RTP头长度解析PS时别依赖单一长度字段要以起始码为准做状态机跳转解析出来的裸流一定要等关键帧再丢给解码器。这套方法我目前已经在多个海康国标对接项目里验证过从几路到上百路都没有出过问题。如果你在解析PS流过程中还遇到过其他奇怪的坑欢迎在评论区分享大家一起把流程完善得更稳。本文还有配套的精品资源点击获取
返回列表