网络摄像机首次播放音视频延时出图像踩坑记录
ZLMediaKit / RTSP / H.264 播放音视频踩坑记录 - SDP、SPS、PPS 完整详解适用场景RTSP 信令交互、addStreamProxy 拉流、WebRTC、H264 裸流、花屏、首帧黑屏、无法解码绝大多数问题根源就是 SDP / SPS/PPS。1、SDP 是什么SDPSession Description Protocol会话描述协议文本格式。RTSP 中客户端发送DESCRIBE请求服务端返回 SDPSDP 告诉播放器这个流有什么轨道视频 / 音频、编码格式、分辨率、采样率、RTP 端口、传输协议、SPS/PPS 等信息。SDP 只是描述元数据不携带音视频帧数据纯文本。RTSP 交互片段VLC/ZLM(PlayerProxy) →DESCRIBE rtsp://xxx/vs0 RTSP/1.0设备 RTSP 服务端回复Body 就是SDP 文本。SDP 典型片段 (H.264 视频)v0 o- 1600000001 1600000001 IN IP4 127.0.0.1 sMediaServer t0 0 mvideo 0 RTP/AVP 96 artpmap:96 H264/90000 afmtp:96 packetization-mode1;profile-level-id42001E;sprop‑parameter‑setsZ0IAKeKQFAe2AtwEBAaQeJEA8AAIBBA1KGA,g48IAA重点字段mvideo视频媒体轨道rtpmap:96 H264/90000PT96H264 编码时间戳频率 90000fmtp:96H264 私有参数packetization-mode1模式 1FU‑A 分片模式RTSP/RTP 最常用profile‑level‑idH264 profile levelsprop‑parameter‑setsbase64(SPS),base64(PPS)→SPS、PPS 做 base64 编码写在这里关键点sprop‑parameter‑sets就是把 SPS、PPS 放到 SDP 里面播放器拿到 SDP 就提前拿到解码参数不需要等 I 帧。2、SPS / PPS 是什么H.264H.264 码流由 NALUNAL 单元组成。SPS、PPS 属于参数集 NALU不是图像帧是解码器初始化必须的元数据。名称全称作用NALU 类型SPSSequence Parameter Set 序列参数集序列全局信息分辨率、profile、level、帧率、参考帧数量NALU type7PPSPicture Parameter Set 图像参数集图像相关熵编码模式、片组参数依附 SPSNALU type8⚠️解码器没有 SPS/PPS绝对不能解码 I 帧 (IDR 帧)。现象收到 I 帧但是画面黑屏、花屏、绿屏没有报错但是出不来图像。NALU 类型简单记忆7SPS8PPS5IDR‑I 帧关键帧1普通 P 帧、非 I 帧两种携带 SPS/PPS 的方式RTSP/RTP 场景方式 1SDP 内携带 sprop‑parameter‑sets最理想RTSP 标准SDP 的fmtp字段把 SPS、PPS 做 base64 编码写入。播放器收到 DESCRIBE 应答解析 SDP 就拿到 SPS/PPS解码器直接初始化。ZLMediaKit addStreamProxy 拉流RTSP 服务端输出 SDP 带 sprop体验最好首帧秒出。方式 2SDP 不带 sprop‑parameter‑setsSPS/PPS 随码流在 IDR 前面发送SDP 里没有 sprop‑parameter‑setsSPSPPS NALU 放在每一个 IDR (I 帧) 前面作为码流一部分随 RTP 包传输。播放器必须等到第一个 IDR 帧到来解析前面 SPS/PPS才能开始解码。现象VLC 打开流要等 1~2 秒才出画面seek、断流重连容易黑屏。很多嵌入式设备 RTSP 服务端容易踩坑SDP 不填 sprop‑parameter‑sets只在码流里面输出 SPS/PPS。裸 H264 AnnexB 格式文件 / 本地码流起始码0x00 00 01分割 NALU00 00 01 67 ... (SPS NALU) 00 00 01 68 ... (PPS NALU) 00 00 01 65 ... (IDR I帧)RTP 传输 RTSP 时AnnexB 起始码会被去掉用 RTP 封装SPS/PPS 可以放到 SDP (sprop) 或者穿插在 RTP 负载中。3、sprop‑parameter‑sets 解析示例sprop‑parameter‑setsZ0IAKeKQFAe2AtwEBAaQeJEA8AAIBBA1KGA,g48IAA逗号分隔逗号前 base64 串 →SPS 原始二进制逗号后 base64 串 →PPS 原始二进制解码流程base64 解码 → 得到 SPS NALU 原始字节带 NALU 头 0x67、PPS NALU 字节0x68。注意sprop 里面的 SPS/PPS不带 AnnexB 起始码 00 00 01只是纯 NALU 负载。常见故障现象与 SPS/PPS 关系现象根因VLC 打开 RTSP 流音频正常视频黑屏过 1‑2 秒出图SDP 没有 sprop‑parameter‑sets要等码流中 SPS/PPSIDR 到来首帧花屏、马赛克后面正常拿到 SPS/PPS 之前收到 I 帧解码器参数错误ZLMediaKit WebRTC 播放失败报错 no sps/ppsWebRTC 强制要求 SDP 携带 sprop‑parameter‑sets仅码流携带 SPS/PPS 不行网络抖动断流恢复后黑屏断流丢失 SPS/PPS后续 I 帧没有配套参数集WebRTC 硬性要求SDP 必须携带 sprop‑parameter‑sets只在 RTP 流里送 SPS/PPS 是无法播放 WebRTC。这是 ZLM 非常高频坑。4、RTSP 完整交互看 SDP/SPS/PPS 时序摄像机设备 A - ZLMediaKit addStreamProxyZLM PlayerProxyRTSP 客户端发送 DESCRIBEA 设备 RTSP 服务端回复 SDP。✅理想SDP fmtp 携带sprop‑parameter‑setsZLM 直接解析 SPS/PPS。❌缺陷SDP 无 spropZLM 只能等待 RTP 码流中出现 SPS/PPS NALU。ZLM 发送 SETUP建立 RTP 通道rtsp_type0 TCP interleavedZLM 发送 PLAYA 设备输出 RTP 包SPS/PPS IDR‑I P 帧如果 SDP 不带 spropZLM 内部会缓存等待码流中 SPS/PPS 到来才生成完整 MediaSource 对外分发。此时 VLC 连接 ZLM也要等待 IDR 帧。5、A 端设备 RTSP 服务端输出建议方法A 端设备端RTSP 服务端SDP 的 fmtp 必须填充sprop‑parameter‑setsSPS,PPS base64不要只放在码流里。packetization‑mode1FU‑A 分片大 I 帧分片传输RTSP over TCP 标准。IDR 帧前面码流中依然重复输出 SPSPPS双重保险网络丢包容错。ZLMediaKit addStreamProxy 相关参数影响 SDP 处理directProxy1默认透传源 SDP源 SDP 不带 sprop则 ZLM 对外 SDP 也不带 spropWebRTC 无法播放。directProxy0ZLM 解封装从 RTP 码流提取 SPS/PPS重新生成 SDP补全 sprop‑parameter‑sets兼容性提升消耗少量 CPU。curl 可以追加参数directProxy0curlhttp://127.0.0.1:80/index/api/addStreamProxy?secret$SECRETvhost__defaultVhost__applivestreamak3918urlrtsp%3A%2F%2F127.0.0.1%3A8554%2Fvs0rtsp_type0directProxy06、快速排错手段tcpdump/wireshark 抓 RTSP 报文查看 DESCRIBE 应答 SDP看sprop‑parameter‑sets是否存在。ZLMediaKit 日志搜索sps、pps、h264观察是否成功解析到参数集。如果 SDP 没有 sprop打开 directProxy0ZLM 会从 RTP 码流自动提取 SPS/PPS 重建 SDP。7、C 代码原始 SPS/PPS NALU 生成 sprop‑parameter‑sets 字符串说明输入去掉 AnnexB 起始码 (0x000001) 的纯 NALU 二进制SPS NAL、PPS NAL包含 NALU 头字节 0x67 / 0x68输出sprop‑parameter‑setsxxxx,yyyy字符串可直接填入 SDP 的 fmtp 字段依赖需要实现 base64 编码函数嵌入式可使用开源 base64 实现。注意不要带入 00 00 01 起始码sprop 标准只接受 NALU 净荷。#includestdio.h#includestring.h#includestdlib.h/** * brief base64编码函数输入二进制输出base64字符串(外部释放内存) */char*base64_encode(constunsignedchar*data,size_tlen);/** * brief 生成SDP fmtp中的 sprop‑parameter‑sets 字段字符串 * param sps_buf SPS NALU(无000001起始码含nal头0x67) * param sps_len SPS字节长度 * param pps_buf PPS NALU(无000001起始码含nal头0x68) * param pps_len PPS字节长度 * return 动态分配字符串示例sprop‑parameter‑setsZ0IA...,g48IAA; 返回NULL失败调用者free释放 */char*h264_gen_sprop_parameter_sets(constuint8_t*sps_buf,size_tsps_len,constuint8_t*pps_buf,size_tpps_len){if(!sps_buf||sps_len0||!pps_buf||pps_len0){returnNULL;}// SPS base64char*sps_b64base64_encode(sps_buf,sps_len);if(!sps_b64)returnNULL;// PPS base64char*pps_b64base64_encode(pps_buf,pps_len);if(!pps_b64){free(sps_b64);returnNULL;}// sprop‑parameter‑setssps_b64,pps_b64size_tout_lenstrlen(sprop‑parameter‑sets)strlen(sps_b64)1strlen(pps_b64)1;char*out_str(char*)malloc(out_len);if(!out_str){free(sps_b64);free(pps_b64);returnNULL;}snprintf(out_str,out_len,sprop‑parameter‑sets%s,%s,sps_b64,pps_b64);free(sps_b64);free(pps_b64);returnout_str;}/* --------------使用示例-------------- */voiddemo_usage(void){// 示例sps_nal、pps_nal 为去除00 00 01起始码的NALU原始数据uint8_tsps_nal[]{0x67,0x42,0x00,0x1E,0x8D,0x8D,0x40,0xA0};uint8_tpps_nal[]{0x68,0xCE,0x3C,0x80};char*sprop_strh264_gen_sprop_parameter_sets(sps_nal,sizeof(sps_nal),pps_nal,sizeof(pps_nal));if(sprop_str){printf(%sn,sprop_str);/* * 拼接进SDP fmtp行 * afmtp:96 packetization-mode1;profile-level-id42001E;%srn */free(sprop_str);}}配套从 AnnexB 码流提取 SPS/PPS NALU去除起始码从 H264 AnnexB 流 (带00 00 01) 中剥离起始码拿到纯 NALU作为上面函数输入。重点传给h264_gen_sprop_parameter_sets的不能包含 0x000001 起始码。/** * brief 简单查找AnnexB起始码 0x00 00 01 */intfind_annexb_startcode(constuint8_t*buf,size_tlen,size_t*pos){for(size_ti0;i3len;i){if(buf[i]0buf[i1]0buf[i2]1){*posi3;// 跳过3字节起始码返回NALU有效载荷起始偏移return1;}}return0;}profile‑level‑id 生成补充SDP fmtp 还需要profile‑level‑idXXYYZZ取自 SPS NALU 的第 1、2、3 字节byte0profile_idcbyte1constraint_set_flagsbyte2level_idc拼接为十六进制 6 位字符串例如42001E。集成进 RTSP SDP 输出示例片段afmtp:96 packetization-mode1;profile-level-id42001E;sprop‑parameter‑setsZ0IA...,g48IAA重要注意事项sprop‑parameter‑sets 格式sps_base64,pps_base64逗号分隔无空格。输入必须是 NALU 本体剔除 AnnexB 起始码 00 00 01否则 base64 之后参数集错误解码器无法初始化。编码输出的字符串直接填入 SDP不要额外 urlencodeSDP 本身不需要 URL 编码URL 编码只用于 HTTP API。H.265 HEVC需要同时处理 VPSSPSPPS对应字段sprop‑vps‑sets;sprop‑sps‑sets;sprop‑pps‑sets。补充 H.265 (HEVC)H265 对应SPS (type33), PPS (type34), VPS (type32)SDP 字段为sprop‑vps‑sets,sprop‑sps‑sets,sprop‑pps‑sets。wireshark 常用过滤表达式# 过滤RTSP DESCRIBE应答查看SDP rtsp rtsp.method DESCRIBE # 过滤H264 SPS NALU h264.nal_unit_type 7 # 过滤H264 PPS NALU h264.nal_unit_type 8

相关新闻

Java主流框架与源码:MyBatis / Hibernate

Java主流框架与源码:MyBatis / Hibernate

1. 引言 在 Java 企业级开发中,ORM(对象关系映射)框架是连接面向对象世界与关系型数据库的桥梁。MyBatis 与 Hibernate 作为两大主流持久层框架,各有千秋,其底层源码实现更是蕴含着无数精妙的设计思想。 本文将深入剖析…

2026/10/25 16:18:44 阅读更多 →
Java数据库与数据存储:分库分表实战

Java数据库与数据存储:分库分表实战

1. 引言 在互联网业务高速发展的今天,单库单表往往成为系统性能的瓶颈。当数据量达到千万级甚至亿级时,数据库的读写性能会急剧下降,索引膨胀、锁竞争、连接数耗尽等问题接踵而至。此时,分库分表便成为Java后端架构中不可或缺的优…

2026/10/26 11:24:26 阅读更多 →
Java数据库与数据存储:Redis

Java数据库与数据存储:Redis

1. 引言 在当今互联网高并发场景下,数据库的性能瓶颈往往成为系统扩展的最大障碍。Redis 作为一款高性能的内存数据库,凭借其丰富的数据结构、极快的读写速度和灵活的持久化机制,已经成为 Java 后端开发中不可或缺的组件。 本文将系统性地介绍…

2026/10/26 11:24:26 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/26 12:00:21 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/25 22:36:10 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/26 4:52:12 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/26 12:00:21 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/26 5:00:37 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/26 3:16:46 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 12:00:19 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/25 13:21:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/26 8:34:37 阅读更多 →