news 2026/9/12 18:58:23

网络摄像机首次播放音视频延时出图像踩坑记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
网络摄像机首次播放音视频延时出图像踩坑记录

ZLMediaKit / RTSP / H.264 播放音视频踩坑记录 - SDP、SPS、PPS 完整详解

适用场景:RTSP 信令交互、addStreamProxy 拉流、WebRTC、H264 裸流、花屏、首帧黑屏、无法解码,绝大多数问题根源就是 SDP / SPS/PPS。

1、SDP 是什么

SDP:Session Description Protocol,会话描述协议,文本格式。
RTSP 中,客户端发送DESCRIBE请求,服务端返回 SDP;
SDP 告诉播放器:这个流有什么轨道(视频 / 音频)、编码格式、分辨率、采样率、RTP 端口、传输协议、SPS/PPS 等信息

SDP 只是描述元数据,不携带音视频帧数据,纯文本。

RTSP 交互片段:

  1. VLC/ZLM(PlayerProxy) →DESCRIBE rtsp://xxx/vs0 RTSP/1.0

  2. 设备 RTSP 服务端回复,Body 就是SDP 文本

SDP 典型片段 (H.264 视频)

v=0 o=- 1600000001 1600000001 IN IP4 127.0.0.1 s=MediaServer t=0 0 m=video 0 RTP/AVP 96 a=rtpmap:96 H264/90000 a=fmtp:96 packetization-mode=1;profile-level-id=42001E;sprop‑parameter‑sets=Z0IAKeKQFAe2AtwEBAaQeJEA8AAIB+BA1KGA,g48IAA==

重点字段:

  • m=video:视频媒体轨道

  • rtpmap:96 H264/90000:PT=96,H264 编码,时间戳频率 90000

  • fmtp:96:H264 私有参数

    • packetization-mode=1:模式 1,FU‑A 分片模式(RTSP/RTP 最常用)

    • profile‑level‑id:H264 profile + level

    • sprop‑parameter‑sets=base64(SPS),base64(PPS)SPS、PPS 做 base64 编码写在这里

关键点:sprop‑parameter‑sets就是把 SPS、PPS 放到 SDP 里面,播放器拿到 SDP 就提前拿到解码参数,不需要等 I 帧。


2、SPS / PPS 是什么(H.264)

H.264 码流由 NALU(NAL 单元)组成。
SPS、PPS 属于参数集 NALU,不是图像帧,是解码器初始化必须的元数据。

名称全称作用NALU 类型
SPSSequence Parameter Set 序列参数集序列全局信息:分辨率、profile、level、帧率、参考帧数量NALU type=7
PPSPicture Parameter Set 图像参数集图像相关:熵编码模式、片组参数,依附 SPSNALU type=8

⚠️解码器没有 SPS/PPS,绝对不能解码 I 帧 (IDR 帧)
现象:收到 I 帧,但是画面黑屏、花屏、绿屏,没有报错但是出不来图像。

NALU 类型简单记忆

  • 7:SPS

  • 8:PPS

  • 5:IDR‑I 帧(关键帧)

  • 1:普通 P 帧、非 I 帧

两种携带 SPS/PPS 的方式(RTSP/RTP 场景)

方式 1:SDP 内携带 sprop‑parameter‑sets(最理想,RTSP 标准)

SDP 的fmtp字段把 SPS、PPS 做 base64 编码写入。
播放器收到 DESCRIBE 应答,解析 SDP 就拿到 SPS/PPS,解码器直接初始化

ZLMediaKit addStreamProxy 拉流,RTSP 服务端输出 SDP 带 sprop,体验最好,首帧秒出。

方式 2:SDP 不带 sprop‑parameter‑sets,SPS/PPS 随码流在 IDR 前面发送

SDP 里没有 sprop‑parameter‑sets;SPS+PPS NALU 放在每一个 IDR (I 帧) 前面,作为码流一部分随 RTP 包传输。

  • 播放器必须等到第一个 IDR 帧到来,解析前面 SPS/PPS,才能开始解码。

  • 现象:VLC 打开流要等 1~2 秒才出画面;seek、断流重连容易黑屏。

很多嵌入式设备 RTSP 服务端容易踩坑:SDP 不填 sprop‑parameter‑sets,只在码流里面输出 SPS/PPS。

裸 H264 AnnexB 格式(文件 / 本地码流)

起始码0x00 00 01分割 NALU:

00 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‑sets=Z0IAKeKQFAe2AtwEBAaQeJEA8AAIB+BA1KGA,g48IAA==

逗号分隔:

  1. 逗号前 base64 串 →SPS 原始二进制

  2. 逗号后 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/PPS+IDR 到来
首帧花屏、马赛克,后面正常拿到 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 addStreamProxy)

  1. ZLM PlayerProxy(RTSP 客户端)发送 DESCRIBE

  2. A 设备 RTSP 服务端回复 SDP。

    • ✅理想:SDP fmtp 携带sprop‑parameter‑sets,ZLM 直接解析 SPS/PPS。

    • ❌缺陷:SDP 无 sprop,ZLM 只能等待 RTP 码流中出现 SPS/PPS NALU。

  3. ZLM 发送 SETUP,建立 RTP 通道(rtsp_type=0 TCP interleaved)

  4. ZLM 发送 PLAY

  5. A 设备输出 RTP 包:SPS/PPS + IDR‑I + P 帧

如果 SDP 不带 sprop,ZLM 内部会缓存,等待码流中 SPS/PPS 到来,才生成完整 MediaSource 对外分发。此时 VLC 连接 ZLM,也要等待 IDR 帧。

5、A 端设备 RTSP 服务端输出建议方法

A 端(设备端)RTSP 服务端:

  1. SDP 的 fmtp 必须填充sprop‑parameter‑sets(SPS,PPS base64),不要只放在码流里。

  2. packetization‑mode=1(FU‑A 分片,大 I 帧分片传输,RTSP over TCP 标准)。

  3. IDR 帧前面,码流中依然重复输出 SPS+PPS(双重保险,网络丢包容错)。

ZLMediaKit addStreamProxy 相关参数影响 SDP 处理

  • directProxy=1(默认):透传源 SDP,源 SDP 不带 sprop,则 ZLM 对外 SDP 也不带 sprop;WebRTC 无法播放。

  • directProxy=0:ZLM 解封装,从 RTP 码流提取 SPS/PPS,重新生成 SDP,补全 sprop‑parameter‑sets,兼容性提升,消耗少量 CPU。

curl 可以追加参数&directProxy=0

curl"http://127.0.0.1:80/index/api/addStreamProxy?secret=$SECRET&vhost=__defaultVhost__&app=live&stream=ak3918&url=rtsp%3A%2F%2F127.0.0.1%3A8554%2Fvs0&rtsp_type=0&directProxy=0"

6、快速排错手段

  1. tcpdump/wireshark 抓 RTSP 报文,查看 DESCRIBE 应答 SDP,看sprop‑parameter‑sets是否存在。

  2. ZLMediaKit 日志搜索spsppsh264,观察是否成功解析到参数集。

  3. 如果 SDP 没有 sprop,打开 directProxy=0,ZLM 会从 RTP 码流自动提取 SPS/PPS 重建 SDP。

7、C 代码:原始 SPS/PPS NALU 生成 sprop‑parameter‑sets 字符串

说明:

  1. 输入:去掉 AnnexB 起始码 (0x000001) 的纯 NALU 二进制(SPS NAL、PPS NAL,包含 NALU 头字节 0x67 / 0x68)

  2. 输出:sprop‑parameter‑sets=xxxx,yyyy字符串,可直接填入 SDP 的 fmtp 字段

  3. 依赖:需要实现 base64 编码函数;嵌入式可使用开源 base64 实现。

  4. 注意:不要带入 00 00 01 起始码,sprop 标准只接受 NALU 净荷。

#include<stdio.h>#include<string.h>#include<stdlib.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‑sets=Z0IA...,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_len==0||!pps_buf||pps_len==0){returnNULL;}// SPS base64char*sps_b64=base64_encode(sps_buf,sps_len);if(!sps_b64)returnNULL;// PPS base64char*pps_b64=base64_encode(pps_buf,pps_len);if(!pps_b64){free(sps_b64);returnNULL;}// sprop‑parameter‑sets=sps_b64,pps_b64size_tout_len=strlen("sprop‑parameter‑sets=")+strlen(sps_b64)+1+strlen(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_str=h264_gen_sprop_parameter_sets(sps_nal,sizeof(sps_nal),pps_nal,sizeof(pps_nal));if(sprop_str){printf("%sn",sprop_str);/* * 拼接进SDP fmtp行: * a=fmtp:96 packetization-mode=1;profile-level-id=42001E;%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_ti=0;i+3<=len;i++){if(buf[i]==0&&buf[i+1]==0&&buf[i+2]==1){*pos=i+3;// 跳过3字节起始码,返回NALU有效载荷起始偏移return1;}}return0;}

profile‑level‑id 生成补充

SDP fmtp 还需要profile‑level‑id=XXYYZZ

  • 取自 SPS NALU 的第 1、2、3 字节:

    • byte0:profile_idc

    • byte1:constraint_set_flags

    • byte2:level_idc
      拼接为十六进制 6 位字符串,例如42001E

集成进 RTSP SDP 输出示例片段

a=fmtp:96 packetization-mode=1;profile-level-id=42001E;sprop‑parameter‑sets=Z0IA...,g48IAA==

重要注意事项

  1. sprop‑parameter‑sets 格式:sps_base64,pps_base64逗号分隔,无空格

  2. 输入必须是 NALU 本体,剔除 AnnexB 起始码 00 00 01,否则 base64 之后参数集错误,解码器无法初始化。

  3. 编码输出的字符串直接填入 SDP,不要额外 urlencode;SDP 本身不需要 URL 编码,URL 编码只用于 HTTP API。

  4. H.265 HEVC:需要同时处理 VPS+SPS+PPS,对应字段:sprop‑vps‑sets;sprop‑sps‑sets;sprop‑pps‑sets

补充 H.265 (HEVC)

H265 对应:SPS (type=33), PPS (type=34), VPS (type=32);
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
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 18:58:05

计算机硬件基础知识点:构建系统级故障推理框架

1. 这不是教科书&#xff0c;是十年装机维修老手的“硬件认知地图”你打开电脑前&#xff0c;有没有想过&#xff1a;为什么插上电源线&#xff0c;主板上的LED灯会亮&#xff1f;为什么换一根内存条&#xff0c;系统就蓝屏了&#xff1f;为什么硬盘灯狂闪时&#xff0c;鼠标却…

作者头像 李华
网站建设 2026/9/12 18:57:53

ISP---RAW数据解析:从PixelFormat、Packed_Buffer到Bayer解码

相机SDK仅向应用层交付void* buffer裸字节流、图像宽高及基础元数据&#xff0c;如何将无结构的字节序列&#xff0c;还原为精准、无失真的二维RAW图像&#xff1f; RAW硬件-软件传输链路遵循固定范式&#xff1a; Sensor → PixelFormat → Transport Buffer → Unpack → 2D …

作者头像 李华
网站建设 2026/9/12 18:54:35

Python情感分析实战:从文本到情绪识别的技术实现

1. 项目概述&#xff1a;Python情感计算实战情感计算作为自然语言处理&#xff08;NLP&#xff09;的重要分支&#xff0c;正在深刻改变人机交互的方式。这个项目将带你用Python构建一个能读懂人类情绪的智能系统——从原始文本到精准的情绪识别&#xff0c;完整实现情感分析的…

作者头像 李华
网站建设 2026/9/12 18:54:09

LlamaIndex 系列【25】检索增强策略:ColBERT(延迟交互检索)

文章目录1. 前言2. ColBERT 是什么&#xff1f;2.1 BERT 模型2.2 ColBERT 检索模型2.3 三种架构对比3. 工作流程3.1 文档离线编码3.2 用户查询编码3.3 MaxSim 匹配3.4 计算得分4. 两种场景4.1 召回4.2 重排序1. 前言 在之前我们介绍了两种编码器&#xff1a; Bi-encoders&…

作者头像 李华
网站建设 2026/9/12 18:53:22

编译原理作业实战:词法分析、语法分析与错误恢复的完整实现

简介&#xff1a;北京邮电大学计算机科学与技术专业大三上学期编译原理课程作业完整资料包&#xff0c;作业得分97分。内容覆盖词法分析与语法分析两大核心模块&#xff0c;包含可直接运行的源代码、实验报告、文档说明及配套PPT和PDF讲义&#xff0c;适合正在学习编译原理、需…

作者头像 李华
网站建设 2026/9/12 18:48:54

MyBatis-Plus批量插入性能优化实践

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

作者头像 李华