
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 幀)?,F象收到 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