简介这份资源面向从事网络视频传输、监控系统或流媒体开发的工程师与学习者聚焦于将RTP包中的H264数据解封装并保存为本地文件同时借助UDP实现摄像头数据的实时读取。包内共14个文件以6个C头文件与4个cpp源文件为核心辅以vcxproj工程文件、filters过滤配置及ReadMe说明整体约9KB结构紧凑便于直接编译调试。内容围绕RTP头部解析、NAL单元重组与H264文件写入展开涉及Annex B与MP4两种常见存储格式的取舍并包含rtpserver服务端与UDP客户端交互的代码框架可帮助读者理解RTP与H264协同工作的完整链路。目前已有1030人学习下载适合希望掌握实时视频流处理、需要可运行参考代码的开发者对照实践。1. RTP 转 H264 文件把 UDP 裸流落成可播放视频的那条链路你抓过包就知道Wireshark 里一堆 UDP 报文端口 5004、8000、甚至 30000 以上Payload 里全是80 60开头的字节看着像视频又播不了。这就是 RTP 承载的 H264 裸流。它和直接存.h264的区别在于RTP 有 12 字节固定头、有序列号、有 Marker 位、有分片规则而 H264 文件需要的是 Annex-B 起始码和完整 NALU。把 UDP 收到的 RTP 包按序还原、去掉 RTP 头、处理 FU-A 分片、补上00 00 00 01才能得到播放器认的 H264 文件。这套链路在国标平台对接、IPC 抓流、udp 网络调试里天天用也是排查start preview failed maybe rtp session false这类问题的基本功。下面按我实际落地的顺序讲清楚。2. 先搞懂 RTP 载荷里到底装了什么H264 的三种打包方式2.1 RTP 固定头与 H264 NALU 的对应关系RTP 头固定 12 字节关键字段是序列号2 字节、时间戳4 字节、Marker 位1 bit、Payload Type。H264 的 NALU 头是 1 字节结构是F(1) NRI(2) Type(5)。Type 决定这个包是单包、分片还是聚合Type 值含义处理方式1-23单 NALU 包直接加起始码写入24STAP-A 聚合包拆出多个 NALU 分别写25-27STAP-B/MTAP少见一般丢弃或特殊处理28FU-A 分片首片带原始 NALU 头后续片拼接29FU-B 分片带 DON兼容性差实际抓 IPC 流90% 以上是 Type 1、28、24 三种。搞不清这个对应关系写出来的文件要么花屏要么根本打不开。2.2 为什么不能直接把 UDP Payload 拼起来存文件有人图省事把 UDP 收到的每个包 Payload 直接 append 到文件结果播放器打开一片绿或者直接报错。原因有三个第一RTP 头 12 字节混在里面H264 解码器看到的是垃圾数据第二FU-A 分片的首片和后续片 Payload 结构不同首片第一个字节是 FU indicator第二个字节才是 FU header后续片没有原始 NALU 头第三UDP 本身不保证顺序序列号乱序时直接拼会错位。所以必须按序列号排序、按 Type 分支处理、重组后再写。2.3 最小可跑的 Python 解析脚本下面这段是我调试时最常用的骨架用 scapy 读 pcap 或者直接接 UDP socket 都行。核心逻辑是维护一个分片缓冲遇到 Marker 位为 1 或下一个包序列号不连续时输出完整 NALU。import struct # RTP 头解析返回 seq, marker, payload def parse_rtp(data): if len(data) 12: return None # 版本号、填充位、扩展位、CSRC 计数 b0 data[0] version b0 6 if version ! 2: return None cc b0 0x0F # 第二个字节的 bit7 是 Marker marker (data[1] 7) 0x01 seq struct.unpack(!H, data[2:4])[0] # 跳过固定头 CSRC offset 12 cc * 4 return seq, marker, data[offset:] # 处理 H264 载荷写入输出文件 def handle_h264_payload(payload, out_file, frag_buf): if not payload: return nalu_type payload[0] 0x1F if nalu_type 28: # FU-A 分片 fu_header payload[1] start (fu_header 7) 0x01 end (fu_header 6) 0x01 # 原始 NALU 头 FU indicator 的 F/NRI FU header 的 Type orig_header (payload[0] 0xE0) | (fu_header 0x1F) if start: frag_buf.clear() frag_buf.append(orig_header) frag_buf.extend(payload[2:]) if end: out_file.write(b\x00\x00\x00\x01 bytes(frag_buf)) frag_buf.clear() elif nalu_type 24: # STAP-A idx 1 while idx 2 len(payload): size struct.unpack(!H, payload[idx:idx2])[0] idx 2 nalu payload[idx:idxsize] out_file.write(b\x00\x00\x00\x01 nalu) idx size elif 1 nalu_type 23: # 单 NALU out_file.write(b\x00\x00\x00\x01 payload)逻辑说明parse_rtp先校验版本号非 2 直接丢handle_h264_payload按 Type 分支FU-A 用frag_buf累积首片重建原始 NALU 头尾片到达时补起始码写出。参数上frag_buf建议用bytearray而不是 list拼接效率更高输出文件用wb模式每写一个 NALU 就 flush 一次防止进程被杀丢数据。3. 从 UDP socket 到 H264 文件完整落地步骤与参数调优3.1 接收端 socket 配置与缓冲区设置直接recvfrom(65535)在流量大时会丢包因为默认 socket 接收缓冲区只有 64KB 左右。我一般这样设import socket sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 4 * 1024 * 1024) sock.bind((0.0.0.0, 5004))SO_RCVBUF设 4MB 是经验值1080p 25fps 的流大概 2-4Mbps4MB 能扛住 8 秒左右的突发。注意 Linux 下SO_RCVBUF会被内核限制在net.core.rmem_max用sysctl net.core.rmem_max看一下不够就改。Windows 下这个值上限不同设太大反而报错建议先试 1MB。3.2 序列号排序与乱序处理策略UDP 乱序是常态尤其是跨公网或经过多级交换。我的做法是维护一个滑动窗口窗口大小 64 个包收到包先按 seq 插入有序位置然后从窗口头部连续输出。如果窗口满了还有空洞说明丢包直接跳过空洞继续同时记录丢包数。丢包超过阈值比如 5%就告警因为 H264 丢一个 I 帧分片后面全花。from collections import OrderedDict class ReorderBuffer: def __init__(self, max_size64): self.buf OrderedDict() self.max_size max_size self.next_seq None def push(self, seq, payload): if self.next_seq is None: self.next_seq seq self.buf[seq] payload # 窗口溢出强制推进 while len(self.buf) self.max_size: k next(iter(self.buf)) self.buf.pop(k) self.next_seq (k 1) 0xFFFF # 输出连续部分 out [] while self.next_seq in self.buf: out.append(self.buf.pop(self.next_seq)) self.next_seq (self.next_seq 1) 0xFFFF return out参数说明max_size根据网络抖动调局域网 32 够用公网建议 128。next_seq用 16 位掩码回绕RTP 序列号是 uint16。3.3 写文件时的起始码与 SPS/PPS 处理H264 文件要能播开头必须有 SPS 和 PPS。RTP 流里 SPS/PPS 通常在每个 I 帧前以单 NALU 发送Type 7 和 8。如果抓包从中间开始可能错过 SPS/PPS这时文件播不了。解决办法是缓存最近一组 SPS/PPS遇到 I 帧时先写 SPS/PPS 再写 I 帧。判断 I 帧看 NALU Type 5。sps_pps_cache b def write_nalu(nalu, out_file): global sps_pps_cache nalu_type nalu[0] 0x1F if nalu_type 7 or nalu_type 8: sps_pps_cache b\x00\x00\x00\x01 nalu return if nalu_type 5 and sps_pps_cache: out_file.write(sps_pps_cache) out_file.write(b\x00\x00\x00\x01 nalu)注意sps_pps_cache不要无限增长每次遇到新的 SPS 就替换而不是追加否则文件头会重复写多组。3.4 用 ffmpeg 验证输出文件是否可播写完文件别急着交付先用 ffmpeg 过一遍ffmpeg -v error -i output.h264 -f null -没有报错说明 NALU 结构正确。再用ffprobe看帧信息ffprobe -v error -show_frames -select_streams v -of csv output.h264 | head -20如果报Invalid NAL unit size或no frame回去查 FU-A 重组逻辑大概率是首片 NALU 头重建错了。4. 避坑与排查RTP 转 H264 最常见的 5 个翻车现场4.1 文件能播但花屏每隔几秒绿一次现象播放器打开正常但周期性花屏。原因丢包导致 I 帧分片不完整或者 P 帧参考丢失。解决在重组逻辑里加丢包检测发现序列号空洞且空洞跨越了 FU-A 的 start 和 end直接丢弃这个不完整 NALU不要写半截数据。同时统计丢包率超过 3% 就说明网络需要优化。4.2 文件头有 SPS/PPS 但播放器仍报错现象用十六进制看文件开头确实有00 00 00 01 67但 VLC 打不开。原因SPS/PPS 写在了第一个 I 帧之后或者 SPS 和 PPS 之间插入了其他 NALU。解决确保 SPS、PPS、I 帧三者连续中间不能有 SEI 或其他类型。我一般缓存 SPS/PPS 后遇到 I 帧时按 SPS、PPS、I 的顺序一次性写出。4.3 UDP 收包丢包严重recvfrom 返回 EAGAIN现象recvfrom频繁抛BlockingIOError或EAGAIN。原因socket 设了非阻塞但没数据或者缓冲区太小。解决非阻塞模式下用select或epoll等待可读缓冲区按 3.1 节调大。另外注意 Python GIL 在高包速率下会成为瓶颈超过 2000 包/秒建议用 C 或 Go 重写接收端。4.4 序列号回绕导致排序错乱现象流跑了几小时后突然大量丢包告警。原因RTP 序列号是 uint16从 65535 回绕到 0排序逻辑没处理回绕。解决所有序列号比较都用(a - b) 0xFFFF的方式判断先后不要直接比大小。上面的ReorderBuffer已经用了掩码但自定义逻辑容易漏。4.5 国标平台对接时 start preview failed现象平台下发预览请求后返回start preview failed maybe rtp session false or preview links nun。原因SIP 信令协商的 RTP 端口和实际发流端口不一致或者 SSRC 不匹配。解决抓 SIP 包看 SDP 里的mvideo端口确认发流目标端口一致检查 RTP 头的 SSRC 是否和平台期望的一致。这类问题用 udp 网络调试工具对着端口抓一下就能定位。5. 进阶用 iperf3 打流验证接收端极限与性能调优5.1 用 iperf3 UDP 模式测接收端吞吐上限写完接收程序别只拿真实流测用 iperf3 打 UDP 流能压出极限# 服务端接收端机器 iperf3 -s -u -p 5004 # 客户端发送端机器 iperf3 -c 192.168.1.100 -u -p 5004 -b 50M -l 1400 -t 60-b 50M是目标带宽-l 1400是包长模拟 RTP 包大小。跑完后看服务端的Lost/Total和Jitter。如果 50Mbps 下丢包超过 1%说明接收端处理不过来需要优化把 Python 换成 C 扩展、用recvmmsg批量收包、或者上 DPDK。我实测 Python 单线程在 2.5GHz CPU 上大概能处理 30-40Mbps 的 RTP 流再高就丢。5.2 接收端零拷贝与批量收包技巧Linux 下用recvmmsg一次收多个包减少系统调用#define VLEN 32 struct mmsghdr msgs[VLEN]; struct iovec iovecs[VLEN]; char bufs[VLEN][2048]; for (int i 0; i VLEN; i) { iovecs[i].iov_base bufs[i]; iovecs[i].iov_len sizeof(bufs[i]); msgs[i].msg_hdr.msg_iov iovecs[i]; msgs[i].msg_hdr.msg_iovlen 1; } int n recvmmsg(sockfd, msgs, VLEN, MSG_WAITFORONE, NULL);VLEN设 32 是经验值太大延迟高太小系统调用多。MSG_WAITFORONE表示收到一个就返回适合低延迟场景。这个改动能把吞吐提升 2-3 倍。5.3 验证方法从文件反推 RTP 流质量最后分享一个我常用的验证习惯把生成的 H264 文件用 ffmpeg 转成 mp4然后看帧率和时长是否和预期一致。ffmpeg -fflags genpts -r 25 -i output.h264 -c copy output.mp4 ffprobe -v error -show_entries formatduration -of defaultnw1 output.mp4如果时长明显偏短说明丢帧多如果帧率不对检查时间戳处理。我一般会在接收端同时记录 RTP 时间戳和文件帧数做交叉验证。这套流程跑熟之后拿到任何一路 UDP RTP 流从抓包到出可播文件半小时内能搞定。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
Java解析HJ212协议实战:报文结构、CRC校验与编码处理 简介:本资源面向环保监测系统开发工程师与Java学习者,提供国标HJ212协议(污染源在线自动监控数据传输标准)的完整解析实现,可直接导入Eclipse项目调用。包内共308个文件,以197个class编译文件与100个java源… · 2026/9/25 23:54:50
开源免费|ClawManager深度解析:企业级OpenClaw集群管理神器 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 23:54:19
K8s 部署 Nacos 集群:StatefulSet 脚本与避坑指南 简介:这份资源面向需要在 Kubernetes 上部署 Nacos 集群的运维与后端开发人员,尤其适合刚接触 K8s 编排、希望跳过繁琐配置直接跑通集群的初学者。它把 Nacos 集群部署拆解为按执行顺序排列的极简脚本,覆盖数据库配置、Headless Service、Sta… · 2026/9/25 23:53:54
2026独立开发者省钱实测:6款免费AI编程软件配置TaoToken全流程对比 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:21:21
SQL Server生产级存储过程实战:校验、事务、错误捕获与性能调优 简介:本资源是一份面向SQL Server初学者与数据库开发人员的存储过程实践入门包,聚焦核心语法、参数传递与典型业务场景应用。压缩包内含3个SQL脚本文件,总大小仅4KB,轻量易学:其中两个为供应链管理类报表存储过程&… · 2026/9/26 0:20:28
高并发限流器的微架构设计:无锁滑动时间窗口与令牌桶的内存与并发优化 高并发限流器的微架构设计:无锁滑动时间窗口与令牌桶的内存与并发优化在大促活动的入口网关层(API Gateway),**限流器(Rate Limiter)**是保护下游大模型推理集群、核心数据库与支付结算服务不被突发海量流量… · 2026/9/26 0:20:28
Oracle补丁包安装指南:OPatch实战从识别到验证 简介:面向64位Linux环境的Oracle 11.2.0.4.161018季度补丁包,编号24006111,适用于Oracle 11g第二版企业级数据库的日常维护与安全加固。该补丁包涵盖自上一季度以来的累积修复,可解决已知漏洞、稳定性问题并带来性能优化ÿ… · 2026/9/26 0:20:21
语音处理:Whisper语音识别实战 语音处理:Whisper语音识别实战 专栏:AI/LLM工程化实战 - 从Prompt到Agent的完整落地指南 模块7 多模态AI应用篇 第68篇 摘要 摘要:Whisper是OpenAI开源的语音识别模型,tiny到large五档尺寸,中文转录准确率随模型增大明显提升,本文覆盖音频转文… · 2026/9/26 0:20:07
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46