简介:这是一份面向网络视频传输开发者的实战代码资源,聚焦将RTP包中的H264数据解封装并保存为本地文件,同时通过UDP接收摄像头实时流。适合具备一定C++与网络编程基础、从事监控、在线会议或远程教育方向的工程师参考。压缩包共14个文件,约9KB,以6个.h头文件与4个.cpp源文件为核心,另有vcxproj工程文件、filters过滤器和ReadMe说明,整体结构围绕rtpserver服务端与CommonCode通用工具库展开,涵盖RTP头部解析、NAL单元重组、H264 Annex B写入等关键模块。资源已有1032人学习下载,读者可从中获取RTP解包与H264还原的完整实现思路,理解NAL单元边界修复与起始码处理方式,并借助UDP收发示例搭建实时读取摄像头数据的原型,适合作为流媒体协议学习的参考代码。1. 从一堆 UDP 包里捞出能播的 H264:这个项目到底在干什么抓过流的人都有体会:Wireshark 里 RTP 包刷得飞快,Payload Type 明明白白写着 96,可你把数据一股脑倒进文件,播放器要么黑屏,要么报“找不到编解码器”。问题不在数据,在于 RTP 只是运输层,它把一帧 H264 切成若干分片,还夹着时间戳、序列号、标记位,直接落盘就是一堆带 RTP 头的碎片,播放器当然不认。这个项目要解决的就是这件事:把 UDP 上收到的 RTP 流,按 H264 的 NAL 单元规则重新拼装,去掉 RTP 头,补上起始码,输出一个标准 Annex-B 格式的.h264文件。它适合做流媒体抓包分析、视频质量排查、离线转码前处理的开发者,也适合刚接触 RTP 和 H264 封装、想找一个能跑通的最小实现来练手的人。核心链路就三步:收 UDP、解 RTP、写 H264,但每一步都有细节能把人绊住。2. 协议拆解:RTP 头、NAL 单元与分片重组的对应关系2.1 RTP 固定头 12 字节里哪些字段必须用RTP 头不是随便跳过的。固定头 12 字节,后面可能跟 CSRC 列表和扩展头,但绝大多数视频流场景下 CSRC 为 0、扩展位为 0,所以实际处理时先读前 12 字节,再根据 CC 字段和 X 位决定要不要继续偏移。真正影响组包的是这几个字段:字段位宽作用组包时怎么用Sequence Number16 bit包序号检测丢包、乱序,决定是否丢弃当前帧Timestamp32 bit采样时刻同一帧的分片时间戳相同,用于判断帧边界Marker1 bit帧结束标志为 1 表示当前包是这一帧的最后一个分片Payload Type7 bit负载类型动态类型常见 96/97,需与 SDP 协商一致我一般会先把 Sequence Number 和 Timestamp 打日志出来,抓几十个包看看规律:同一帧内 Timestamp 不变,Marker 只在最后一包为 1。如果 Timestamp 频繁跳变而 Marker 从不置位,那多半是发送端没按规范打标记,后面组包逻辑就得靠时间戳变化来切帧,这是常见的翻车点。2.2 H264 的 NAL 头与 FU-A 分片格式RTP 负载里的 H264 有两种打包模式:单 NAL 单元模式和分片模式。小帧(比如 SPS、PPS、SEI)通常一个 RTP 包承载一个完整 NAL,负载第一个字节就是 NAL 头,结构是F(1) | NRI(2) | Type(5)。大帧(比如 I 帧)超过 MTU 就要分片,用 FU-A 模式,负载头变成两个字节:第一个字节的 Type 为 28,第二个字节才是真正的 NAL 头信息,其中 S 位表示分片开始,E 位表示分片结束,R 位保留。重组逻辑就是:遇到 FU-A 起始包,记下 NAL 头(由 FU indicator 的 F、NRI 和 FU header 的 Type 拼出来),把后续分片的负载依次追加,直到 E 位置 1,一个完整 NAL 就出来了。这里最容易错的是把 FU indicator 的第一个字节当成 NAL 头直接写文件,结果播放器解析出一堆 Type 为 28 的非法 NAL。2.3 Annex-B 起始码与 SPS/PPS 的插入时机H264 裸流用起始码分隔 NAL,常见是 4 字节00 00 00 01,也有 3 字节00 00 01。写文件时每个 NAL 前加起始码即可。但光有切片 NAL 还不够,播放器需要 SPS 和 PPS 才能初始化解码器。SPS/PPS 通常在流开始时以单独 NAL 发送,Type 分别为 7 和 8。如果抓包从中间开始,可能错过它们,这时要么从流里缓存最近一组 SPS/PPS,要么在写每个 I 帧前重复插入。我习惯在内存里维护一份最新的 SPS/PPS,遇到 IDR 帧(Type 5)时先写 SPS、PPS 再写 IDR,这样即使中途开始录也能播。3. 工程落地:UDP 收包、RTP 解析与文件写入的代码实现3.1 用 Python 搭一个最小可跑的接收端先看接收和主循环。用 socket 绑定端口,收一个包处理一个包,逻辑直白,方便调试。import socket # 绑定本地端口,接收 UDP 数据 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(("0.0.0.0", 5004)) sock.settimeout(5.0) # 5 秒无数据就退出,避免死等 h264_file = open("output.h264", "wb") last_seq = None frame_buffer = bytearray() # 暂存当前帧的分片 current_ts = None while True: try: data, addr = sock.recvfrom(65535) except socket.timeout: print("接收超时,结束") break if len(data) < 12: continue # 太短,不是合法 RTP # 解析 RTP 固定头 seq = int.from_bytes(data[2:4], "big") ts = int.from_bytes(data[4:8], "big") marker = data[1] & 0x80 payload = data[12:] # 简化处理,假设无 CSRC 和扩展 # 丢包检测:序号不连续就丢弃当前帧,避免写出坏帧 if last_seq is not None and seq != (last_seq + 1) & 0xFFFF: print(f"丢包: {last_seq} -> {seq},丢弃当前帧") frame_buffer.clear() current_ts = None last_seq = seq continue last_seq = seq # 时间戳变化说明新帧开始,先把上一帧落盘 if current_ts is not None and ts != current_ts: if frame_buffer: h264_file.write(frame_buffer) h264_file.flush() frame_buffer.clear() current_ts = ts # 处理负载,拼 NAL nal = parse_rtp_payload(payload) if nal: frame_buffer.extend(nal) # Marker 置位表示一帧结束,落盘 if marker: if frame_buffer: h264_file.write(frame_buffer) h264_file.flush() frame_buffer.clear() current_ts = None h264_file.close() sock.close()这段代码里几个参数值得说:settimeout(5.0)是给调试用的,生产环境可以设更长或用心跳判断;last_seq的丢包检测用 16 位回绕比较,(last_seq + 1) & 0xFFFF是标准写法;frame_buffer按帧缓存而不是按包直接写,是因为一个 NAL 可能跨多个 RTP 包,必须等 Marker 或时间戳变化才能确认帧完整。丢包时直接清空当前帧,宁可丢一帧也不要写出损坏数据,这是血泪经验——坏帧会让整个文件后续解码错乱。3.2 RTP 负载解析:单 NAL 与 FU-A 的分支处理parse_rtp_payload是核心,要区分单 NAL 和 FU-A,还要处理起始码。def parse_rtp_payload(payload): if len(payload) < 1: return b"" nal_type = payload[0] & 0x1F # 单 NAL 单元模式:Type 1-23 if 1 <= nal_type <= 23: # 加 4 字节起始码,直接返回完整 NAL return b"\x00\x00\x00\x01" + payload # FU-A 分片模式:Type 28 if nal_type == 28: if len(payload) < 2: return b"" fu_header = payload[1] start = fu_header & 0x80 end = fu_header & 0x40 real_type = fu_header & 0x1F # 重组真正的 NAL 头:F 和 NRI 来自 FU indicator,Type 来自 FU header nal_header = bytes([(payload[0] & 0xE0) | real_type]) if start: # 起始分片,加起始码和 NAL 头 return b"\x00\x00\x00\x01" + nal_header + payload[2:] else: # 中间或结束分片,只返回负载,不加起始码 return payload[2:] # 其他类型(如 STAP-A 聚合包)暂不处理 return b""逻辑说明:单 NAL 模式直接加起始码返回,因为一个包就是一个完整 NAL。FU-A 模式下,起始分片要拼出真正的 NAL 头并加起始码,后续分片只追加负载,不能重复加起始码,否则文件里会出现00 00 00 01夹在 NAL 中间,播放器直接报错。nal_header的构造是关键:payload[0] & 0xE0取出 F 和 NRI 位,| real_type补上真实类型,这样重组出来的 NAL 头才合法。参数上,start和end分别是 FU header 的最高两位,判断分片边界全靠它们。3.3 写文件时的缓冲与 flush 策略写文件看着简单,但缓冲策略影响很大。如果每收一个包就write一次,磁盘 IO 频繁,高码率下可能丢包;如果全缓存在内存最后一次性写,进程崩溃就全没了。我一般用frame_buffer按帧缓存,每帧完整后写一次并flush,兼顾效率和可恢复性。flush会强制刷盘,调试时能实时看到文件增长,生产环境可以去掉flush靠系统缓冲,但要注意进程退出前必须close。另外文件以wb二进制模式打开,避免 Windows 下换行符转换破坏数据。4. 避坑与排查:组包过程中最容易翻车的几个点4.1 播放器黑屏,文件里全是 Type 28现象:生成的.h264用播放器打开黑屏,用工具看 NAL 类型全是 28。原因:FU-A 分片的第一个字节被当成 NAL 头直接写入了文件,没有做重组。解决:按 3.2 的逻辑,FU-A 起始包要拼出真实 NAL 头,后续分片只追加负载,绝不能把 FU indicator 原样写入。4.2 画面花屏或解码到一半卡死现象:前几秒正常,后面花屏或卡死。原因:丢包后没有丢弃当前帧,损坏的 NAL 被写入文件,解码器状态错乱。解决:在接收循环里做序号连续性检测,一旦发现丢包立即清空frame_buffer并跳过当前帧,等下一个时间戳或 Marker 重新开始。宁可丢帧不可写坏帧。4.3 文件能播但没声音或时长不对现象:视频能播,但时长异常或某些播放器不识别。原因:SPS/PPS 缺失或只在文件开头出现一次,播放器 seek 后无法重新初始化。解决:缓存最新的 SPS/PPS,在每个 IDR 帧前重复插入。可以在解析时记录 Type 7 和 Type 8 的 NAL,写 IDR 前先写它们。4.4 高码率下丢包严重现象:低码率正常,高码率大量丢包。原因:UDP 接收缓冲区太小,内核来不及处理就丢弃。解决:设置sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4 * 1024 * 1024)增大接收缓冲区,同时把处理逻辑尽量轻量,避免在收包循环里做耗时操作。4.5 时间戳回绕导致帧判断错误现象:运行一段时间后帧边界判断失效。原因:RTP 时间戳是 32 位,回绕后直接比较大小会出错。解决:用无符号差值判断,(ts - current_ts) & 0xFFFFFFFF超过阈值才认为是新帧,而不是简单的不等比较。序列号同理,用 16 位回绕比较。5. 二次开发:从能跑到好用,几个改造方向5.1 支持 TCP 承载与多路流分离UDP 之外,很多场景 RTP 走 TCP,前面有 2 字节长度前缀。改造时在接收层抽象一个read_packet接口,UDP 直接返回数据报,TCP 先读 2 字节长度再读对应字节数。多路流分离则靠 SSRC 字段,在 RTP 头偏移 8 到 12 字节,按 SSRC 维护不同的frame_buffer和输出文件,这样一路端口能同时录多路。5.2 增加 SPS/PPS 解析与分辨率输出想进一步做分析,可以解析 SPS 里的分辨率信息。SPS 是 Exp-Golomb 编码,解析稍复杂,但可以借助现成库或只提取宽高字段。改造后在日志里输出1920x1080这类信息,方便确认流参数。我一般会在收到 SPS 时解析一次并缓存,避免重复计算。5.3 用配置文件替代硬编码把端口、输出路径、缓冲区大小、超时时间抽到配置文件或命令行参数,用argparse或configparser读取。这样同一份代码能跑在不同环境,不用改源码。常见做法是提供一个config.ini,启动时读取,缺省值写在代码里兜底。5.4 验证方法:用 ffprobe 快速确认文件合法性改完代码怎么确认没写坏?最直接的是ffprobe -v error -show_streams output.h264,能列出 codec 为 h264、宽高、帧率就说明基本正确。再进一步用ffmpeg -i output.h264 -f null -解码一遍,没有报错就说明 NAL 结构完整。我每次改完组包逻辑都会跑这两条命令,比肉眼检查文件头靠谱得多。5.5 一个具体技巧:用 Wireshark 导出对比调试组包逻辑时,我会用 Wireshark 抓同一段流,用它的“导出 H264”功能生成一个参考文件,然后和我的输出做二进制对比。重点看 NAL 起始码位置和 SPS/PPS 出现次数,差异往往能直接定位到代码里的分支错误。这个习惯帮我省了很多猜的时间。从那以后我每次写组包代码,都强制先用 ffprobe 验证一遍再提交,绝不靠“看起来能播”下结论。希望帮到你。本文还有配套的精品资源,点击获取
简介:这是一份面向网络视频传输开发者的实战代码资源,聚焦将RTP包中的H264数据解封装并保存为本地文件,同时通过UDP接收摄像头实时流。适合具备一定C++与网络编程基础、从事监控、在线会议或远程教育方向的工程师参考。压缩包共14个文件,约9KB,以6个.h头文件与4个.cpp源文件为核心,另有vcxproj工程文件、filters过滤器和ReadMe说明,整体结构围绕rtpserver服务端与CommonCode通用工具库展开,涵盖RTP头部解析、NAL单元重组、H264 Annex B写入等关键模块。资源已有1032人学习下载,读者可从中获取RTP解包与H264还原的完整实现思路,理解NAL单元边界修复与起始码处理方式,并借助UDP收发示例搭建实时读取摄像头数据的原型,适合作为流媒体协议学习的参考代码。1. 从一堆 UDP 包里捞出能播的 H264:这个项目到底在干什么抓过流的人都有体会:Wireshark 里 RTP 包刷得飞快,Payload Type 明明白白写着 96,可你把数据一股脑倒进文件,播放器要么黑屏,要么报“找不到编解码器”。问题不在数据,在于 RTP 只是运输层,它把一帧 H264 切成若干分片,还夹着时间戳、序列号、标记位,直接落盘就是一堆带 RTP 头的碎片,播放器当然不认。这个项目要解决的就是这件事:把 UDP 上收到的 RTP 流,按 H264 的 NAL 单元规则重新拼装,去掉 RTP 头,补上起始码,输出一个标准 Annex-B 格式的.h264文件。它适合做流媒体抓包分析、视频质量排查、离线转码前处理的开发者,也适合刚接触 RTP 和 H264 封装、想找一个能跑通的最小实现来练手的人。核心链路就三步:收 UDP、解 RTP、写 H264,但每一步都有细节能把人绊住。2. 协议拆解:RTP 头、NAL 单元与分片重组的对应关系2.1 RTP 固定头 12 字节里哪些字段必须用RTP 头不是随便跳过的。固定头 12 字节,后面可能跟 CSRC 列表和扩展头,但绝大多数视频流场景下 CSRC 为 0、扩展位为 0,所以实际处理时先读前 12 字节,再根据 CC 字段和 X 位决定要不要继续偏移。真正影响组包的是这几个字段:字段位宽作用组包时怎么用Sequence Number16 bit包序号检测丢包、乱序,决定是否丢弃当前帧Timestamp32 bit采样时刻同一帧的分片时间戳相同,用于判断帧边界Marker1 bit帧结束标志为 1 表示当前包是这一帧的最后一个分片Payload Type7 bit负载类型动态类型常见 96/97,需与 SDP 协商一致我一般会先把 Sequence Number 和 Timestamp 打日志出来,抓几十个包看看规律:同一帧内 Timestamp 不变,Marker 只在最后一包为 1。如果 Timestamp 频繁跳变而 Marker 从不置位,那多半是发送端没按规范打标记,后面组包逻辑就得靠时间戳变化来切帧,这是常见的翻车点。2.2 H264 的 NAL 头与 FU-A 分片格式RTP 负载里的 H264 有两种打包模式:单 NAL 单元模式和分片模式。小帧(比如 SPS、PPS、SEI)通常一个 RTP 包承载一个完整 NAL,负载第一个字节就是 NAL 头,结构是F(1) | NRI(2) | Type(5)。大帧(比如 I 帧)超过 MTU 就要分片,用 FU-A 模式,负载头变成两个字节:第一个字节的 Type 为 28,第二个字节才是真正的 NAL 头信息,其中 S 位表示分片开始,E 位表示分片结束,R 位保留。重组逻辑就是:遇到 FU-A 起始包,记下 NAL 头(由 FU indicator 的 F、NRI 和 FU header 的 Type 拼出来),把后续分片的负载依次追加,直到 E 位置 1,一个完整 NAL 就出来了。这里最容易错的是把 FU indicator 的第一个字节当成 NAL 头直接写文件,结果播放器解析出一堆 Type 为 28 的非法 NAL。2.3 Annex-B 起始码与 SPS/PPS 的插入时机H264 裸流用起始码分隔 NAL,常见是 4 字节00 00 00 01,也有 3 字节00 00 01。写文件时每个 NAL 前加起始码即可。但光有切片 NAL 还不够,播放器需要 SPS 和 PPS 才能初始化解码器。SPS/PPS 通常在流开始时以单独 NAL 发送,Type 分别为 7 和 8。如果抓包从中间开始,可能错过它们,这时要么从流里缓存最近一组 SPS/PPS,要么在写每个 I 帧前重复插入。我习惯在内存里维护一份最新的 SPS/PPS,遇到 IDR 帧(Type 5)时先写 SPS、PPS 再写 IDR,这样即使中途开始录也能播。3. 工程落地:UDP 收包、RTP 解析与文件写入的代码实现3.1 用 Python 搭一个最小可跑的接收端先看接收和主循环。用 socket 绑定端口,收一个包处理一个包,逻辑直白,方便调试。import socket # 绑定本地端口,接收 UDP 数据 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(("0.0.0.0", 5004)) sock.settimeout(5.0) # 5 秒无数据就退出,避免死等 h264_file = open("output.h264", "wb") last_seq = None frame_buffer = bytearray() # 暂存当前帧的分片 current_ts = None while True: try: data, addr = sock.recvfrom(65535) except socket.timeout: print("接收超时,结束") break if len(data) < 12: continue # 太短,不是合法 RTP # 解析 RTP 固定头 seq = int.from_bytes(data[2:4], "big") ts = int.from_bytes(data[4:8], "big") marker = data[1] & 0x80 payload = data[12:] # 简化处理,假设无 CSRC 和扩展 # 丢包检测:序号不连续就丢弃当前帧,避免写出坏帧 if last_seq is not None and seq != (last_seq + 1) & 0xFFFF: print(f"丢包: {last_seq} -> {seq},丢弃当前帧") frame_buffer.clear() current_ts = None last_seq = seq continue last_seq = seq # 时间戳变化说明新帧开始,先把上一帧落盘 if current_ts is not None and ts != current_ts: if frame_buffer: h264_file.write(frame_buffer) h264_file.flush() frame_buffer.clear() current_ts = ts # 处理负载,拼 NAL nal = parse_rtp_payload(payload) if nal: frame_buffer.extend(nal) # Marker 置位表示一帧结束,落盘 if marker: if frame_buffer: h264_file.write(frame_buffer) h264_file.flush() frame_buffer.clear() current_ts = None h264_file.close() sock.close()这段代码里几个参数值得说:settimeout(5.0)是给调试用的,生产环境可以设更长或用心跳判断;last_seq的丢包检测用 16 位回绕比较,(last_seq + 1) & 0xFFFF是标准写法;frame_buffer按帧缓存而不是按包直接写,是因为一个 NAL 可能跨多个 RTP 包,必须等 Marker 或时间戳变化才能确认帧完整。丢包时直接清空当前帧,宁可丢一帧也不要写出损坏数据,这是血泪经验——坏帧会让整个文件后续解码错乱。3.2 RTP 负载解析:单 NAL 与 FU-A 的分支处理parse_rtp_payload是核心,要区分单 NAL 和 FU-A,还要处理起始码。def parse_rtp_payload(payload): if len(payload) < 1: return b"" nal_type = payload[0] & 0x1F # 单 NAL 单元模式:Type 1-23 if 1 <= nal_type <= 23: # 加 4 字节起始码,直接返回完整 NAL return b"\x00\x00\x00\x01" + payload # FU-A 分片模式:Type 28 if nal_type == 28: if len(payload) < 2: return b"" fu_header = payload[1] start = fu_header & 0x80 end = fu_header & 0x40 real_type = fu_header & 0x1F # 重组真正的 NAL 头:F 和 NRI 来自 FU indicator,Type 来自 FU header nal_header = bytes([(payload[0] & 0xE0) | real_type]) if start: # 起始分片,加起始码和 NAL 头 return b"\x00\x00\x00\x01" + nal_header + payload[2:] else: # 中间或结束分片,只返回负载,不加起始码 return payload[2:] # 其他类型(如 STAP-A 聚合包)暂不处理 return b""逻辑说明:单 NAL 模式直接加起始码返回,因为一个包就是一个完整 NAL。FU-A 模式下,起始分片要拼出真正的 NAL 头并加起始码,后续分片只追加负载,不能重复加起始码,否则文件里会出现00 00 00 01夹在 NAL 中间,播放器直接报错。nal_header的构造是关键:payload[0] & 0xE0取出 F 和 NRI 位,| real_type补上真实类型,这样重组出来的 NAL 头才合法。参数上,start和end分别是 FU header 的最高两位,判断分片边界全靠它们。3.3 写文件时的缓冲与 flush 策略写文件看着简单,但缓冲策略影响很大。如果每收一个包就write一次,磁盘 IO 频繁,高码率下可能丢包;如果全缓存在内存最后一次性写,进程崩溃就全没了。我一般用frame_buffer按帧缓存,每帧完整后写一次并flush,兼顾效率和可恢复性。flush会强制刷盘,调试时能实时看到文件增长,生产环境可以去掉flush靠系统缓冲,但要注意进程退出前必须close。另外文件以wb二进制模式打开,避免 Windows 下换行符转换破坏数据。4. 避坑与排查:组包过程中最容易翻车的几个点4.1 播放器黑屏,文件里全是 Type 28现象:生成的.h264用播放器打开黑屏,用工具看 NAL 类型全是 28。原因:FU-A 分片的第一个字节被当成 NAL 头直接写入了文件,没有做重组。解决:按 3.2 的逻辑,FU-A 起始包要拼出真实 NAL 头,后续分片只追加负载,绝不能把 FU indicator 原样写入。4.2 画面花屏或解码到一半卡死现象:前几秒正常,后面花屏或卡死。原因:丢包后没有丢弃当前帧,损坏的 NAL 被写入文件,解码器状态错乱。解决:在接收循环里做序号连续性检测,一旦发现丢包立即清空frame_buffer并跳过当前帧,等下一个时间戳或 Marker 重新开始。宁可丢帧不可写坏帧。4.3 文件能播但没声音或时长不对现象:视频能播,但时长异常或某些播放器不识别。原因:SPS/PPS 缺失或只在文件开头出现一次,播放器 seek 后无法重新初始化。解决:缓存最新的 SPS/PPS,在每个 IDR 帧前重复插入。可以在解析时记录 Type 7 和 Type 8 的 NAL,写 IDR 前先写它们。4.4 高码率下丢包严重现象:低码率正常,高码率大量丢包。原因:UDP 接收缓冲区太小,内核来不及处理就丢弃。解决:设置sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4 * 1024 * 1024)增大接收缓冲区,同时把处理逻辑尽量轻量,避免在收包循环里做耗时操作。4.5 时间戳回绕导致帧判断错误现象:运行一段时间后帧边界判断失效。原因:RTP 时间戳是 32 位,回绕后直接比较大小会出错。解决:用无符号差值判断,(ts - current_ts) & 0xFFFFFFFF超过阈值才认为是新帧,而不是简单的不等比较。序列号同理,用 16 位回绕比较。5. 二次开发:从能跑到好用,几个改造方向5.1 支持 TCP 承载与多路流分离UDP 之外,很多场景 RTP 走 TCP,前面有 2 字节长度前缀。改造时在接收层抽象一个read_packet接口,UDP 直接返回数据报,TCP 先读 2 字节长度再读对应字节数。多路流分离则靠 SSRC 字段,在 RTP 头偏移 8 到 12 字节,按 SSRC 维护不同的frame_buffer和输出文件,这样一路端口能同时录多路。5.2 增加 SPS/PPS 解析与分辨率输出想进一步做分析,可以解析 SPS 里的分辨率信息。SPS 是 Exp-Golomb 编码,解析稍复杂,但可以借助现成库或只提取宽高字段。改造后在日志里输出1920x1080这类信息,方便确认流参数。我一般会在收到 SPS 时解析一次并缓存,避免重复计算。5.3 用配置文件替代硬编码把端口、输出路径、缓冲区大小、超时时间抽到配置文件或命令行参数,用argparse或configparser读取。这样同一份代码能跑在不同环境,不用改源码。常见做法是提供一个config.ini,启动时读取,缺省值写在代码里兜底。5.4 验证方法:用 ffprobe 快速确认文件合法性改完代码怎么确认没写坏?最直接的是ffprobe -v error -show_streams output.h264,能列出 codec 为 h264、宽高、帧率就说明基本正确。再进一步用ffmpeg -i output.h264 -f null -解码一遍,没有报错就说明 NAL 结构完整。我每次改完组包逻辑都会跑这两条命令,比肉眼检查文件头靠谱得多。5.5 一个具体技巧:用 Wireshark 导出对比调试组包逻辑时,我会用 Wireshark 抓同一段流,用它的“导出 H264”功能生成一个参考文件,然后和我的输出做二进制对比。重点看 NAL 起始码位置和 SPS/PPS 出现次数,差异往往能直接定位到代码里的分支错误。这个习惯帮我省了很多猜的时间。从那以后我每次写组包代码,都强制先用 ffprobe 验证一遍再提交,绝不靠“看起来能播”下结论。希望帮到你。本文还有配套的精品资源,点击获取