ARTICLE DETAIL

资讯详情

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

从pcap抓包提取GB28181国标PS流:RTP分片重组与验证实战

从pcap抓包提取GB28181国标PS流:RTP分片重组与验证实战 简介从tcpdump或Wireshark抓包文件中提取符合国标的数据流是网络抓包分析与协议解析中的常见需求。这套小型工具包围绕该场景提供pcap2ps-main的轻量实现含核心C源代码与使用说明。资源共2个文件包含pcap2ps.cC语言源码与README.md压缩包整体仅5KB便于直接阅读和复用目前已有221人学习。pcap格式记录了数据包来源、目的地、时间戳、协议类型及载荷信息pcap2ps.c实现对pcap文件的解析与筛选通过字符串匹配、协议字段解析、数据校验等手段识别并提取符合国家标准的网络数据流README.md则说明工具的用途、运行方式与适用场景。对网络管理员、安全分析人员以及需要协议解析或数据格式转换的开发者来说这份小型工具包提供了可直接参考的核心实现既有助于理解国标流的筛选识别过程也能在其基础上做二次修改与功能扩展辅助网络性能分析、安全事件检测与故障诊断等后续工作。1. 从抓包里找国标流pcap 文件里藏的不是视频是 RTP 分片平台上看不到摄像机画面信令一切正常200 OK 都到了媒体流也确实在网卡上跑着可屏幕就是黑的。这种时候最直接的办法是在服务器网卡上开 tcpdump 或 wireshark 抓包但抓下来的 .pcap 文件不是现成的视频文件而是 GB28181 协商出来的 RTP 会话里一片一片的 PS 流分片。想验证码流对不对得先把这些分片重新拼成完整的国标流。标题里的 pcap2ps就是处理这件事的。这个活儿常见于三类人做设备接入的开发、做平台联调的集成商以及需要对历史抓包做码流取证的安防运维。只要你手里有一份抓包文件无论来自 tcpdump 还是 wireshark都能按这套思路把国标流抽出来。本文从封装原理讲到落地命令最后给出验证方法可以照着复现。2. 国标流在 pcap 里的封装链路SIP 会话、RTP 载荷与 PS 流的结构2.1 从 SIP 信令到 RTP 流确认媒体通道是怎么建立的GB28181 的会话建立走的是 SIP实际码流走的是 RTP。用 wireshark 打开抓包文件先在过滤器里输入sip扫一眼重点看 INVITE 和 200 OK 消息里的 SDP 描述。mvideo 40000 RTP/AVP 98这个 98 就是动态 RTP payload type在国标流场景里通常意味着负载是 PS 封装后的 H.264/H.265 码流。c行里写着媒体收发的 IP 和端口后面提取 RTP 时全靠它圈定范围。用 tcpdump 抓的包SIP 可能走 UDP 5060 端口也可能被平台厂商改过端口。wireshark 里有电话菜单里的 SIP 流统计能快速看到 INVITE、200 OK、ACK 的完整时序没有这个菜单布局时直接过滤sip.Method INVITE和sip.Method 200也能定位。这里最关键的动作是确认 SDP 里协商出来的 RTP 端口和 payload type因为后面筛选流时全靠这两个信息。如果你的抓包里有多个设备同时在会话SDP 里的o行还会带着会话 ID 和版本号可以用来区分不同设备。还有一个值得注意的点GB28181 的 SIP 消息体里SDP 之外的 XML 字段往往写着控制服务器地址和 DeviceID这些字段不是提取 RTP 必需的但如果你需要把提取出来的流关联回某台具体设备它就是唯一线索。实际排查时我一般会把 SIP 信令的时序截图和 RTP 流统计放到一起看先确认会话是正常结束的还是被中断的再决定要不要花功夫去拼流。信令侧本来就是断的媒体侧拼得再完整也意义不大。2.2 PS 流的骨架PS 头、PES 包与 RTP 分片的关系国标流的本质是一个 MPEG-2 Program Stream也就是 PS 流里面打包着 H.264 或 H.265 裸流。PS 流自带时间基PS 头里的system_clock_reference_base提供时钟PES 头里的PTS提供显示时间戳。在 GB28181 设备端视频编码器输出的 PS 流会按视频帧被切成多个 RTP 分片每个分片大小不等通常不超过 MTU。一个典型的 PS 包结构是PS 头以00 00 01 BA起始后面可选跟系统头00 00 01 BB然后是节目流映射 PSM00 00 01 BC最后是一个或多个 PES 包视频 PES 以00 00 01 E0开头。理解这个结构的意义在于当你用 wireshark 选中一个 RTP 包展开 RTP payload如果看到以00 00 01 BA开头说明这个分片恰好是某段 PS 流的起始如果只看到00 00 01 E0开头的 PES 片段说明抓包是从一段流中间开始的。实际抓包文件里最常见的情况是一个视频帧的 PS 包被切成了十几个 RTP 分片比如第一个分片带完整的 PS 头和 PSM后面的分片就是 PES 的中间部分。RTP 头的 12 字节固定字段里sequence number用来排序timestamp用来关联同一帧的多个分片ssrc用来区分不同发送源。国标流里同一路视频的 ssrc 是固定的这就是我们提取时的主要筛选键。还有个结构细节容易忽略PSM 里的program_stream_info描述了流类型和 PID 映射视频流通常是0xE0。GB28181 国标流严格说允许带音频但实际监控场景里绝大多数设备只推视频如果你在抓包里看到00 00 01 C0开头的 PES那是音频提取时可以丢弃也可以单独拼。判断的方法是看 PSM 里的stream_type字段按 PS 规范去解而不是靠猜。2.3 先做一次 wireshark 手工验证确认包里真的有完整 PS 流在写脚本或跑工具之前先用 wireshark 做一次快速验证判断这包值不值得提取。打开 pcap在过滤栏输入rtp rtp.payload_type 98先看rtp.ssrc有几个。如果有多个 ssrc说明文件里混着多路流或双向流后面提取时要先分路。选中其中一个 RTP 包右键查看载荷前几字节如果是000001ba说明这个 ssrc 承载的就是 PS 封装国标流可以继续往下走。再用 tshark 做一次自动化确认tshark -r capture.pcap -Y rtp.ssrc 0x1a2b3c4d rtp.payload_type 98 -T fields -e rtp.payload | head -n 5这里把0x1a2b3c4d换成实际的 ssrc 值-e rtp.payload会只输出 RTP 负载的 hex 字符串不含 RTP 头。如果头几行输出都以000001ba开头说明流是完整的从帧边界开始抓的否则就是从 GOP 中间开始的提取时需要多做一步关键帧对齐。注意这里 ssrc 的进制wireshark 界面里显示的是十六进制tshark 过滤时保持一致别把0x前缀漏了。这个验证步骤决定了后续用简单拼接脚本还是需要做丢包容忍。如果验证发现 PS 分片是完整的、序列号连续直接拼接就能出流如果分片乱序或者有丢包就得给脚本加排序和空洞检测逻辑。下面一章的落地命令就是在这些前提下设计的。3. 用 pcap2ps 批量提取国标流落地命令、脚本与参数说明3.1 pcap2ps 解决什么问题把 pcap 还原成一帧帧可解的 PS 流pcap2ps 这一类工具要做的就是把抓包文件里的国标流还原成干净的 PS 流文件。拿到工具包后不管它是命令行还是带界面的版本核心处理逻辑都是固定的解析 pcap 文件按 ssrc 和 payload type 圈出目标 RTP 流剥掉 RTP 头按 sequence number 重组分片最后输出一个.ps文件。这个.ps文件可以被后续的解码器、ffmpeg 或播放器直接消费。这里有个很实际的场景设备对接测试时研发经常让你抓一份 pcap 回去分析你抓完后用 wireshark 人工看了一下午也没看出问题因为你手里没有把 PS 流还原出来的手段。pcap2ps 这类工具的价值就是把这份黑匣子变成可检验的视频文件。我在实际项目里见过不少对接问题最后都是靠把 pcap 还原成 PS 流、再用 ffprobe 查编码信息定位到的而不是靠肉眼看信令。如果你拿到的 pcap2ps 版本参数不顺手或者它对特定抓包文件报错下面这套 tshark 加 Python 脚本的方案可以兜底。它不依赖特定工具只要你的电脑能跑 tshark就一定能复现。这也是我推荐你至少理解一遍封装链路的原因工具可以换原理不会变。3.2 最小落地路径tshark 导出 RTP 载荷再用脚本组装第一步用 tshark 把目标 ssrc 的 RTP 载荷导出成文本。注意-e rtp.payload输出的就是负载本身所以不需要在脚本里再剥一遍 RTP 头。tshark -r capture.pcap -Y rtp.ssrc 0x1a2b3c4d rtp.payload_type 98 -T fields -e rtp.seq -e rtp.payload rtp_seq_dump.txt这里我把rtp.seq也导出来了因为 RTP 分片在网络上可能乱序到达没有序号就无法可靠重排。输出文件的每一行是两列第一列是十进制 sequence number第二列是 hex 字符串。如果 tshark 版本较老rtp.seq字段名可能要换成rtp.seq_num具体以tshark -G fields | grep rtp.seq的输出为准。第二步用 Python 脚本读这个文件按序号排序后拼接。下面是完整脚本import sys from collections import defaultdict def parse_hex_line(line): parts line.strip().split() if len(parts) 2: return None, None seq int(parts[0]) payload bytes.fromhex(parts[1]) return seq, payload def main(dump_file, out_file, max_gap2): chunks {} with open(dump_file, r) as f: for line in f: seq, payload parse_hex_line(line) if payload and seq is not None: chunks[seq] payload # 按 sequence number 排序保证帧顺序正确 sorted_seq sorted(chunks.keys()) if not sorted_seq: print(no rtp payload found) return # 检测空洞连续缺失超过 max_gap 个分片时输出警告 gaps [] for i in range(1, len(sorted_seq)): diff sorted_seq[i] - sorted_seq[i - 1] if diff max_gap: gaps.append((sorted_seq[i - 1], sorted_seq[i], diff)) if gaps: print(fwarning: {len(gaps)} gaps found, check stream integrity) for g in gaps[:10]: print(f gap: seq {g[0]} - {g[1]}, missing {g[2] - 1} packets) # 拼接输出保留所有分片后续用解码器校验 with open(out_file, wb) as f: for seq in sorted_seq: f.write(chunks[seq]) print(fwritten {len(sorted_seq)} packets to {out_file}, size {sum(len(chunks[s]) for s in sorted_seq)} bytes) if __name__ __main__: main(sys.argv[1], sys.argv[2])这个脚本的逻辑分三段。读分片后按序号排序这一步解决网络乱序然后做空洞检测max_gap2表示允许连续缺失两个分片更大的空洞会打出警告但不中断拼接因为最终能不能解由解码器说了算最后按排序好的顺序逐片写入.ps文件。跑法很简单python3 extract_ps.py rtp_seq_dump.txt extracted.ps输出文件大小和抓包时长正相关一路 1080p 的国标流大概一小时 1.5GB抓到 10 分钟就能拼出几十 MB 的 PS 流。如果最终文件里 SPS、PPS 没有丢ffprobe 能直接读到编码信息。3.3 RTP over TCP 模式剥掉四字节帧头再拼不少平台为了穿透网络限制GB28181 的 RTP 走的是 TCP而不是默认的 UDP。wireshark 里看传输层是 TCP但 RTP 并不直接搭在 TCP 字节流上——在 TCP 模式下每个 RTP 包外面还会包一层帧头常见的写法是四字节前两字节是类型和通道号后两字节是这个 RTP 包的长度。实际项目里我见过00 01开头的标准写法也见过厂商自定的24 01开头信道字段不同而已。如果直接拿上一小节的脚本去拼 TCP 模式导出的 payload拼出来的流会夹杂这四字节帧头解码器直接翻车。处理方法是拼装前先剥掉这四字节。用 tshark 导出 TCP 流里的 RTP 载荷时wireshark 会尝试解析但解析率受帧头影响所以更靠谱的做法是先用-d参数声明端口类型tshark -r capture.pcap -d tcp.port40000,rtp -Y rtp.ssrc 0x1a2b3c4d -T fields -e rtp.payload tcp_rtp_dump.txt这里的-d是把 TCP 40000 端口强制按 RTP 解析wireshark 才能正确剥掉四字节帧头rtp.payload字段输出的才是纯 RTP 载荷。需要注意如果抓包文件里同一 TCP 连接上同时跑了多路 RTP通道号的前两字节就变成了区分依据这种情况建议先用 tshark 导出rtp.ssrc和rtp.payload一起看确认没有混流再拼。一个常见的误判是用 wireshark 界面打开 pcap 时RTP 流统计里看不到任何流或显示为 Unrecognized。这不代表没有媒体流而是因为 TCP 模式的 RTP 没有按标准端口解析。只要 SDP 协商时写明了 TCP就在抓包时把端口记下来后面用-d强制解析就能看到。这个坑在项目里至少让我白费过两个小时现在每次都先确认传输模式再谈提取。3.4 参数选择丢包容忍度、超时和输出格式脚本里真正需要调的就是两个参数max_gap和是否需要在空洞处插入填充。max_gap我一般设置成 2因为正常的 RTP 分片序号是连续的如果某帧太大被分了十几个分片中间丢一两个会导致这一帧坏掉但不影响下一帧。设置成 2 的意思是序号差值不超过 2 时认为是同一个帧的少量丢包脚本不警告超过 2 才提示。如果你抓的是无线环境或弱网抓包丢包率很高建议把max_gap调到 5 以上但拼接出来的流大概率有花屏得靠解码器容错。关于超时参数脚本本身不处理时间维度但如果你的 pcap 文件里同一 ssrc 的 RTP 流中间静默了很久比如几十秒那可能是设备断流重连。这种情况下建议按时间把输出切分成多个文件而不是拼成一个。用 tshark 导出时可以加-e frame.time_epoch字段然后在 Python 里按时间戳落差切分时间差超过 3 秒就开一个新文件。输出格式方面绝大多数场景下.ps就够了因为后续不管是 ffmpeg 转封装还是直接喂解码器PS 流都是标准输入。但有个例外如果你打算直接在浏览器里播放PS 流不行需要先用 ffmpeg 转成 MP4 或 FLV这一步放在第 5 章验证部分讲。4. 提取国标流的 5 个高频坑现象、原因、解决4.1 提取出的 PS 流能播放但花屏有连续 I 帧缺失现象拼出来的.ps文件用 ffplay 能打开但画面大面积花屏快进后甚至直接黑屏。 原因抓包是从 GOP 中间开始的或 RTP 分片里有丢包。PS 流的花屏本质是 I 帧不完整后续 P 帧全靠参考帧恢复只要有一个关键帧丢了后面的帧基本没法看。tshark 导出时没有做完整性检查脚本也不负责补帧。 解决先用十六进制检查00 00 01 65H.264 IDR 帧的 nal_type 为 5或00 00 01 26H.265 IRAP 帧在拼接结果里出现的次数少于 1 次说明抓包本身错过了关键帧提取工具没问题重新抓包或用 4.4 节的方法从 PES 头重建。如果 I 帧数量正常但依然花屏检查max_gap警告把空洞对应的分片序号打印出来去 pcap 里核对是不是抓包时就丢了。4.2 RTP over TCP 模式下拼出来的流开头有垃圾字节现象脚本输出的文件用 ffprobe 检查时提示 Invalid data用十六进制编辑器打开发现每段 payload 前都多了00 01或24 01开头的几个字节。 原因TCP 模式下的四字节帧头没有剥掉。tshark 的-e rtp.payload不一定总能正确识别 TCP 上的 RTP如果没有用-d tcp.port端口,rtp声明它会把帧头当成 payload 的一部分输出。 解决按 3.3 节命令重新导出或者在自己的脚本里手动剥检查每段 payload 的前两字节如果是00 01或24 01读取第三和第四字节拼成的长度把整段数据按长度截断。写这段逻辑时不要写死帧头值因为国标厂商私有实现里通道号可能不同只判断长度是否匹配更稳。4.3 同一路流的 ssrc 中途改变拼接出的流突然断了现象前面的帧都正常某个时间点之后脚本输出的 PS 流就停了pcap 里后面的 RTP 包依然存在但 ssrc 和之前的完全不同。 原因设备重启、平台切换或 NAT 重绑定导致 RTP 会话重建新的 ssrc 出现。有些厂商实现里同一台摄像机的视频流如果断线重连ssrc 会变。 解决不要指望一个 ssrc 拼到底。先用 tshark 导出全部 RTP 流的 ssrc 统计必要时把多个 ssrc 的 payload 按时间顺序拼接再用 PS 流的 PTS 做对齐。做法是分别提取每个 ssrc然后用 ffmpeg 把多个.ps文件按时间戳 concat而不是在二进制层面直接拼。如果两段流之间有重叠直接拼会导致 PTS 回跳播放器会花屏一下。4.4 H.265 国标流提取后无法解码现象提取出的.ps文件用 ffplay 播放黑屏或提示 could not find codec parameters但抓包时设备配置的编码明明是 H.265。 原因H.265 的 VPS/SPS/PPS 在 PS 流里是通过 PES 的私有数据携带的有些设备只在关键帧前发一次。如果抓包时间较短恰好从 GOP 中后段开始抓就可能一条参数集都没有。另外部分老设备的 PSM 里流类型字段填的是0x1BH.264但实际负载是 H.265导出后 ffprobe 按 H.264 解析自然失败。 解决用 ffprobe 直接读.ps文件的编码信息ffprobe -v error -show_streams extracted.ps如果 stream 里codec_name是h264但 you 确认设备是 H.265说明 PSM 流类型写错了。这种情况不要继续拼回源头修设备或平台。如果 ffprobe 直接报错则先用十六进制搜索00 00 01 40H.265 VPS 的 nal_unit_type 为 32确认参数集是否存在于文件里。没有参数集时可以尝试从抓包里单独提取一个关键帧分片把 VPS/SPS/PPS 人工补到输出文件头部但这条路只适合调试不适合批量。4.5 双向流里只抓到了单向 RTP现象SIP 信令里是双向媒体协商但过滤rtp时只看到设备到平台方向的 RTP 包平台到设备的媒体完全看不到。 原因平台侧不推流或者推流走了另一条网络路径你的抓包位置在设备侧网卡上只采集到了上行流量。 解决这种情况不属于提取工具的 bug是抓包位置问题。换到平台侧服务器网卡去抓或把 TAP 设备串在设备和平台之间。抓包文件里只有一路方向时pcap2ps 或脚本都能正常提取但你要知道提取出来的是单向流如果设备发送的是 PS 流而你收到的也是 PS 流那方向颠倒的影响不大如果是文本信令或媒体协商不对称就得重新抓。这个坑是我在联调现场踩得最多的一类花了一下午把工具调通才发现其实上行流量压根没问题。5. 验证与进阶把提取结果变成能直接喂解码器的裸流提取出.ps文件只是第一步验证它是否真实可用是更关键的一步。我的习惯是做三件事先用 ffprobe 读元数据再用 ffmpeg 转一次封装确认能解码最后抽几个时间点看 PTS 是否连续。ffprobe -v error -show_streams extracted.ps -show_format关注codec_name、profile、level和r_frame_rate如果 codec_name 显示 hevc 或 h264说明 PS 封装本身没被拆坏。如果要转成 MP4 存档或交付给平台用ffmpeg -i extracted.ps -c copy -f mp4 output.mp4-c copy不做重编码只改容器速度极快。如果这一步报错说明 PS 流里有解码器不认的字节回到 4.2 节检查是否有四字节帧头残留在动。进阶一点的做法是用 Python 统计 PS 流里的关键帧分布不需要解码器就能判断抓包质量。搜索00 00 01 BAPS 头和00 00 01 65H.264 IDR或00 00 01 26H.265 IRAP的出现次数如果两者的比值远远小于预期说明大部分 PS 包里没有关键帧流是碎的。这个检查可以放进批量提取脚本里作为每个 pcap 文件提取后的自动质检项。批量处理时我会写一个循环脚本对目录下每个 pcap 文件依次做 tshark 导出、Python 拼装、ffprobe 校验把校验失败的输出到一个日志文件里集中看。参数按抓包环境来内网抓包敢把max_gap设成 1公网抓包设成 5测试环境抓包设成 2。有一次线上排障我拿了一小时抓包批量提取后 90% 的文件都花屏自查发现是抓包时 tcpdump 用的-s 0没生效导致 RTP 包被截断不是提取工具的问题——从那以后我每次抓包都先抓 10 秒验证分片长度再决定要不要长时间抓。最后多说一句提取国标流这件事工具只是手段对封装链路越熟悉越能快速分辨是信令问题、网络丢包问题还是设备编码问题。那套 tshark 加 Python 的兜底脚本我用了两年先后处理过摄像头对接、平台迁移和历史取证稳定可靠。希望这篇笔记能帮你少走我当初走过的弯路。本文还有配套的精品资源点击获取
返回列表