音視頻學習(五十八):STAP-A模式

什么是 STAP-A?

STAP-A 是一種特殊的 RTP 封裝機制,專為 H.264 和 H.265 這類視頻編碼協議設計。它的核心目的只有一個:將多個小的 NALU(網絡抽象層單元)打包進一個 RTP 包中,以此來減少網絡開銷,提高傳輸效率。

簡單來說,STAP-A 就像一個大信封,可以把多封小信件(小的 NALU)裝在一起,然后只貼一張郵票(一個 RTP 頭部)寄出去。這比一封信貼一張郵票要劃算得多。

在 RTSP、RTSP over HTTP、SRT 等基于 RTP 的流媒體協議中,STAP-A 的作用至關重要,尤其是在傳輸分辨率高、需要頻繁發送參數集的視頻流時。

為什么需要 STAP-A?

在視頻編碼的世界里,除了包含實際圖像數據的視頻幀(如 IDR、I、P、B 幀),還有許多用于描述編碼參數的 NALU。這些參數通常非常小,比如:

  • VPS(Video Parameter Set):視頻參數集,描述多個序列的共享參數,特別是分層編碼結構

  • SPS (Sequence Parameter Set):序列參數集,描述全局信息如分辨率、幀率、碼流級別。

  • PPS (Picture Parameter Set):圖像參數集,描述單幀或多幀的共享參數。

  • AUD (Access Unit Delimiter):訪問單元分隔符,標記一幀的開始。

  • SEI (Supplemental Enhancement Information):補充增強信息,包含時序等輔助數據。

這些 NALU 有時只有幾十字節。如果不使用 STAP-A,每個 NALU 都需要獨立封裝。一個 RTP 包至少有 12 字節的 RTP 頭部,加上 UDP/IP 頭部,總開銷通常超過 40 字節。對于一個 50 字節的 NALU 來說,協議開銷甚至比數據本身還要大。

STAP-A 的出現完美解決了這個問題。它將這些小 NALU 聚合起來,只需一個 RTP 頭部,就能傳輸多個 NALU,顯著降低了協議開銷(Protocol Overhead),從而節省了帶寬。

STAP-A 的封裝結構

一個使用 STAP-A 封裝的 RTP 包,其負載(Payload)結構如下:

  1. RTP 頭部:標準的 12 字節 RTP 頭部,包含序列號、時間戳等信息。
  2. STAP-A 指示器:一個 1 字節的特殊頭部,用于標識這是一個聚合包。
    • 對于 H.264,其類型(Type)字段值為 24
    • 對于 H.265,其類型(Type)字段值為 48
  3. 聚合 NALU 負載:由一個或多個 NALU 組成。特別的是,每個 NALU 在被放入負載之前,都會先加上一個 2 字節的大小字段

下面是其結構示意圖:

+-----------------------------------+
|          RTP Header               |
+-----------------------------------+
|     STAP-A Indicator              |  <- H.264: Type 24 / H.265: Type 48
+-------------------+---------------+
|   NALU 1 Size     |  (2 bytes)    |
+-------------------+---------------+
|       NALU 1 Data                 |
+-------------------+---------------+
|   NALU 2 Size     |  (2 bytes)    |
+-------------------+---------------+
|       NALU 2 Data                 |
+-------------------+---------------+
|           ... and so on ...       |
+-------------------+---------------+

STAP-A 的工作原理

當一個視頻流被編碼后,視頻發送端會有一個 RTP 封裝模塊。這個模塊會緩沖所有新生成的 NALU。它會根據 NALU 的大小和類型,決定如何封裝:

  1. 聚合判斷:如果隊列里有多個小的、非視頻幀 NALU(如 SPS、PPS),并且將它們打包后總大小不超過 MTU(通常為 1500 字節),那么模塊就會選擇 STAP-A 模式。
  2. 構建負載
    • 首先,創建一個新的 RTP 包,并寫入標準的 RTP 頭部。
    • 其次,寫入 STAP-A 指示器(類型為 24 或 48)。
    • 然后,對于每個要聚合的 NALU:
      • 將該 NALU 的大小(2 字節,大端字節序)寫入負載。
      • 將該 NALU 的完整數據寫入負載。
  3. 發送:當所有 NALU 都被封裝進一個 RTP 包后,該包通過網絡發送給接收端。

接收端的解析流程

接收端收到一個 RTP 包后,首先會解析其 RTP 頭部。然后,它會檢查負載的第一個字節來確定封裝類型:

  1. 識別類型:如果負載的第一個字節(去除 FNRI 位)是 2448,接收端就知道這是一個 STAP-A 包。
  2. 逐個解包
    • 接收端進入一個循環,從負載的第二個字節開始。
    • 讀取接下來的 2 個字節,得到第一個 NALU 的大小 L
    • 讀取接下來的 L 個字節,得到第一個完整的 NALU 數據。
    • 重復上述步驟,直到 RTP 包的負載數據被全部讀取完畢。
  3. 處理 NALU:接收端會將解包出的每個完整的 NALU 分發給相應的處理模塊,例如將 SPS 和 PPS 送給解碼器進行初始化。

STAP-A 的重要性與應用場景

STAP-A 是高效流媒體傳輸的關鍵,其應用場景主要有:

  • 會話啟動:在建立 RTSP/RTMP/SRT 等會話時,服務器通常會用 STAP-A 將 SPS 和 PPS 打包發送給客戶端。這確保了客戶端可以在第一時間獲取所有必要的解碼參數,無需等待數據流中的關鍵幀。
  • 參數更新:如果編碼參數在流媒體過程中發生變化(比如分辨率或幀率改變),新的 SPS/PPS 會被打包進 STAP-A 包中發送。
  • 低開銷數據傳輸:任何小的、零散的 NALU(如 AUD、SEI)都可以通過 STAP-A 封裝,從而最大化網絡利用率。

STAP-A 與 FU-A 的關系

STAP-A 和 FU-A 是兩種互補而非競爭的機制。

  • STAP-A 用于將多個小 NALU 聚合在一起,其目的是節省開銷
  • FU-A 用于將一個大 NALU 分片成小塊,其目的是適應 MTU 限制

在實踐中,一個完整的 RTP 視頻流通常會同時使用這三種封裝模式:

  • 單 NALU 模式:傳輸大多數普通的視頻幀(如 P/B 幀)。
  • STAP-A 模式:傳輸 SPS/PPS 等參數集。
  • FU-A 模式:傳輸大的關鍵幀(如 I/IDR 幀)。

STAP-A封包和解包示例

#include <iostream>
#include <vector>
#include <numeric>
#include <cstdint>
#include <stdexcept>// 模擬 RTP 數據包的有效載荷
// 在實際應用中,這部分數據將跟在 RTP 頭部之后
using RtpPayload = std::vector<uint8_t>;// 模擬 NALU 列表
using NalUnitList = std::vector<std::vector<uint8_t>>;// STAP-A H.264/H.265 類型值
#define H264_STAP_A_TYPE 24
#define H265_STAP_A_TYPE 48class StapAPacker {
public:// H.264 封裝: 將多個 H.264 NALU 聚合為一個 STAP-A 負載RtpPayload pack_h264_nalus(const NalUnitList& nalus) {if (nalus.empty()) {return RtpPayload();}RtpPayload payload;// 1. 寫入 STAP-A 指示器 (H.264: 類型 24)// 這里的 RefIdc 位可以根據第一個 NALU 的 RefIdc 來設置uint8_t stap_a_indicator = (nalus[0][0] & 0x60) | H264_STAP_A_TYPE;payload.push_back(stap_a_indicator);// 2. 寫入每個 NALU 的大小和數據for (const auto& nalu : nalus) {// NALU 大小 (2 字節, 大端字節序)uint16_t nalu_size = nalu.size();payload.push_back(static_cast<uint8_t>((nalu_size >> 8) & 0xFF));payload.push_back(static_cast<uint8_t>(nalu_size & 0xFF));// NALU 數據payload.insert(payload.end(), nalu.begin(), nalu.end());}return payload;}// H.265 封裝: 將多個 H.265 NALU 聚合為一個 STAP-A 負載RtpPayload pack_h265_nalus(const NalUnitList& nalus) {if (nalus.empty()) {return RtpPayload();}RtpPayload payload;// 1. 寫入 STAP-A 指示器 (H.265: 類型 48)// 使用第一個 NALU 的 2 字節頭部的 forbidden_zero_bit, layer_id, temporal_id 等uint8_t stap_a_indicator_byte0 = (nalus[0][0] & 0x81) | (H265_STAP_A_TYPE << 1);uint8_t stap_a_indicator_byte1 = nalus[0][1];payload.push_back(stap_a_indicator_byte0);payload.push_back(stap_a_indicator_byte1);// 2. 寫入每個 NALU 的大小和數據for (const auto& nalu : nalus) {// NALU 大小 (2 字節, 大端字節序)uint16_t nalu_size = nalu.size();payload.push_back(static_cast<uint8_t>((nalu_size >> 8) & 0xFF));payload.push_back(static_cast<uint8_t>(nalu_size & 0xFF));// NALU 數據payload.insert(payload.end(), nalu.begin(), nalu.end());}return payload;}// 解包: 從一個 STAP-A 負載中分離出所有 NALUNalUnitList unpack_stap_a(const RtpPayload& payload, bool is_h264) {NalUnitList nalus;size_t offset = 0;if (payload.empty()) {throw std::runtime_error("Payload is empty.");}// 1. 讀取并驗證 STAP-A 指示器if (is_h264) {uint8_t type = payload[0] & 0x1F;if (type != H264_STAP_A_TYPE) {throw std::runtime_error("Not a H.264 STAP-A packet.");}offset = 1;} else { // H.265uint8_t type = (payload[0] >> 1) & 0x3F;if (type != H265_STAP_A_TYPE) {throw std::runtime_error("Not a H.265 STAP-A packet.");}offset = 2; // H.265 STAP-A 頭部是 2 字節}// 2. 循環讀取每個 NALUwhile (offset < payload.size()) {// 檢查剩余數據是否足夠讀取 NALU 大小字段if (offset + 2 > payload.size()) {throw std::runtime_error("Truncated STAP-A packet: missing NALU size.");}// 讀取 NALU 大小uint16_t nalu_size = (payload[offset] << 8) | payload[offset + 1];offset += 2;// 檢查剩余數據是否足夠讀取整個 NALUif (offset + nalu_size > payload.size()) {throw std::runtime_error("Truncated STAP-A packet: NALU data incomplete.");}// 讀取 NALU 數據std::vector<uint8_t> nalu(payload.begin() + offset, payload.begin() + offset + nalu_size);nalus.push_back(nalu);offset += nalu_size;}return nalus;}
};void print_nalu_info(const std::vector<uint8_t>& nalu, bool is_h264) {if (nalu.empty()) return;uint8_t type = 0;if (is_h264) {type = nalu[0] & 0x1F;std::cout << "  - H.264 NALU, Type: " << (int)type << ", Size: " << nalu.size() << " bytes." << std::endl;} else {type = (nalu[0] >> 1) & 0x3F;std::cout << "  - H.265 NALU, Type: " << (int)type << ", Size: " << nalu.size() << " bytes." << std::endl;}
}int main() {StapAPacker packer;// --- 1. 模擬 H.264 NALU 聚合 ---std::cout << "--- H.264 STAP-A Aggregation Example ---" << std::endl;// 模擬 SPS (類型 7) 和 PPS (類型 8)NalUnitList h264_nalus_to_pack;std::vector<uint8_t> h264_sps = {0x67, 0x00, 0x40, 0x0a}; // 示例 SPSstd::vector<uint8_t> h264_pps = {0x68, 0xee, 0x01, 0x32}; // 示例 PPSh264_nalus_to_pack.push_back(h264_sps);h264_nalus_to_pack.push_back(h264_pps);RtpPayload h264_stap_a_payload = packer.pack_h264_nalus(h264_nalus_to_pack);std::cout << "H.264 Payload created, total size: " << h264_stap_a_payload.size() << " bytes." << std::endl;// --- 2. 模擬 H.264 解包 ---std::cout << "\n--- H.264 STAP-A Unpacking Example ---" << std::endl;try {NalUnitList unpacked_h264_nalus = packer.unpack_stap_a(h264_stap_a_payload, true);std::cout << "Successfully unpacked " << unpacked_h264_nalus.size() << " NALUs." << std::endl;for (const auto& nalu : unpacked_h264_nalus) {print_nalu_info(nalu, true);}} catch (const std::exception& e) {std::cerr << "Error unpacking H.264 payload: " << e.what() << std::endl;}// --- 3. 模擬 H.265 NALU 聚合 ---std::cout << "\n--- H.265 STAP-A Aggregation Example ---" << std::endl;// 模擬 VPS (類型 32), SPS (類型 33), PPS (類型 34)NalUnitList h265_nalus_to_pack;std::vector<uint8_t> h265_vps = {0x40, 0x01, 0x0a}; // 示例 VPSstd::vector<uint8_t> h265_sps = {0x42, 0x01, 0x01}; // 示例 SPSstd::vector<uint8_t> h265_pps = {0x44, 0x01, 0x01}; // 示例 PPSh265_nalus_to_pack.push_back(h265_vps);h265_nalus_to_pack.push_back(h265_sps);h265_nalus_to_pack.push_back(h265_pps);RtpPayload h265_stap_a_payload = packer.pack_h265_nalus(h265_nalus_to_pack);std::cout << "H.265 Payload created, total size: " << h265_stap_a_payload.size() << " bytes." << std::endl;// --- 4. 模擬 H.265 解包 ---std::cout << "\n--- H.265 STAP-A Unpacking Example ---" << std::endl;try {NalUnitList unpacked_h265_nalus = packer.unpack_stap_a(h265_stap_a_payload, false);std::cout << "Successfully unpacked " << unpacked_h265_nalus.size() << " NALUs." << std::endl;for (const auto& nalu : unpacked_h265_nalus) {print_nalu_info(nalu, false);}} catch (const std::exception& e) {std::cerr << "Error unpacking H.265 payload: " << e.what() << std::endl;}return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/920227.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/920227.shtml
英文地址,請注明出處:http://en.pswp.cn/news/920227.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

管理型交換機通過VLAN劃分實現不同IP跨網段通信配置方法

管理型交換機應用場景豐富&#xff0c;如果要實現不同IP跨網段通信(比如172.22.106.X和192.168.100.X實現通信)&#xff0c;通過VLAN劃分是可以滿足&#xff0c;下面分享基于弱三層交換機RTL9301方案核心模塊SW-24G4F-301EM配置方法&#xff01; 1. 一般結合交換機的應用場景&a…

什么是高防服務器?如何進行防御?

高防服務器是指能為用戶提供防御網絡攻擊&#xff0c;是主要針對DDOS等流量型攻擊能力的服務器&#xff0c;通過部署專業的硬件設備與軟件系統&#xff0c;具備高帶寬、大流量清洗能力&#xff0c;能有效抵御各類惡意流量沖擊&#xff0c;確保服務器穩定運行&#xff0c;保障網…

SW - 增加導出STL數據中的三角面數,增加別人逆向建模的難度

文章目錄SW - 增加導出STL數據中的三角面數&#xff0c;增加別人逆向建模的難度概述筆記SW版本導出時&#xff0c;選擇STL的導出選項默認導出(精細)導出粗糙自定義導出 - 將誤差和角度改為最大自定義導出 - 將誤差,角度,三角面數改為最大備注這幾天的感想關于我不參考人家零件&…

四十一、【高級特性篇】API 文檔驅動:OpenAPI/Swagger 一鍵導入測試用例

四十一、【高級特性篇】API 文檔驅動:OpenAPI/Swagger 一鍵導入測試用例 前言 準備工作 第一部分:后端實現 - OpenAPI 解析與批量創建 API 1. 創建 OpenAPI 解析服務 2. 創建批量用例導入 API 3. 注冊新 API 路由 第二部分:前端實現 - OpenAPI 導入界面 1. 更新 `api/testca…

K8S-Service資源對象

一、概述在kubernetes中&#xff0c;pod是應用程序的載體&#xff0c;我們可以通過pod的ip來訪問應用程序&#xff0c;但是pod的ip地址不是固定的&#xff0c;這也就意味著不方便直接采用pod的ip對服務進行訪問。為了解決這個問題&#xff0c;kubernetes提供了Service資源&…

【STM32】CubeMX(十三):RT-THREAD

本篇博客描述的是 RT-Thread STM32 CubeMX 的使用方法。本文也為大家提供了基于 STM32 使用 CubeMX 添加 RT-Thread 并創建閃爍 LED 任務 的操作流程。 便于您更好的理解。 一、RT-Thread 是什么&#xff1f; RT-Thread 是一個開源、輕量級的實時操作系統&#xff0c;適用于…

基于Ubuntu22.04系統PaddleX和PaddleClas訓練推理MMAFEDB人臉表情識別數據集(詳細教程)

目錄 基于Ubuntu22.04系統PaddleX和PaddleClas訓練推理MMAFEDB人臉表情識別數據集(詳細教程) 超實用的Paddle圖像分類訓練推理教程&#xff0c;助力深度學習研究&#xff01; 1、環境準備(重要???) 構建虛擬環境 安裝PaddlePaddle 安裝PaddleX 安裝PaddleClas插件 2…

Mistral AI音頻大模型Voxtral解讀

1. 引言 傳統的語音處理系統(如OpenAI的Whisper)在ASR任務上取得了巨大成功,能將語音高精度地轉換為文本。但這只是第一步。真正的“語音理解”意味著: 內容推理:不僅知道說了什么,還能理解話語背后的含義、情感和意圖。 長篇摘要:能夠聽完一段長達數十分鐘的播客或會議…

使用Docker+WordPress部署個人博客

一、通過docker compose 自動一鍵部署WordPress 1. 準備工作 安裝 Docker 和 Docker Compose確保服務器有公網 IP&#xff08;如果需要外部訪問&#xff09;域名&#xff08;可選&#xff0c;用于綁定網站&#xff09; 2. 創建 Docker Compose 配置文件 創建一個docker-compose…

http與https配置

Web 服務詳解&#xff1a;HTTP 與 HTTPS 配置 一、HTTP 服務概述 HTTP&#xff08;Hypertext Transfer Protocol&#xff0c;超文本傳輸協議&#xff09;是用于在網絡上傳輸網頁數據的基礎協議&#xff0c;默認使用80 端口&#xff0c;以明文形式傳輸數據。常見的 HTTP 服務軟件…

Python爬蟲實戰:研究amazon-scrapy,構建亞馬遜電商數據采集和分析系統

1 引言 1.1 研究背景 電子商務的高速發展使電商平臺成為數據價值的核心載體。亞馬遜作為全球領先的電商生態,截至 2024 年第二季度,其平臺商品總量突破 1.5 億,日均活躍用戶超 3 億,每日產生 PB 級的交易數據與用戶行為記錄。這些數據包含商品特征(價格、規格、品牌)、…

基于ERNIE 4.5的多智能體協作的自動化視頻輿情分析報告生成器

多智能體協作的自動化視頻輿情分析報告生成器 1. 項目的意義與價值 從“非結構化視頻”中挖掘“結構化洞察”的通用挑戰 在當今的數字生態中&#xff0c;視頻已成為信息傳播、知識分享和消費者意見表達的核心媒介。從企業內部的會議錄屏、技術培訓&#xff0c;到外部的市場宣傳…

Java全棧開發面試實錄:從基礎到實戰的深度解析

Java全棧開發面試實錄&#xff1a;從基礎到實戰的深度解析 面試官與應聘者的對話記錄 第一輪&#xff1a;基礎問題與項目背景 面試官&#xff08;中年男性&#xff0c;穿著整潔&#xff09;&#xff1a; 你好&#xff0c;歡迎來到我們公司。我是今天的面試官&#xff0c;可以先…

如何清除webview138、139版本軟鍵盤占用的區域

好的&#xff0c;這個問題非常具體且關鍵。在 Android System WebView 的 138 和 139 版本&#xff08;基于 Chromium 113&#xff09;的上下文中&#xff0c;“清除軟鍵盤占用的區域”通常意味著&#xff1a;在軟鍵盤收起后&#xff0c;WebView 的布局或視口沒有正確恢復&…

深度學習:卷積神經網絡(CNN)

文章目錄一、CNN 基礎認知1.1 圖像在計算機中的存儲形式1.2 圖像識別的核心需求&#xff1a;畫面不變性1.3 傳統神經網絡的局限二、CNN 核心原理&#xff1a;三大核心層與關鍵操作2.1 卷積層&#xff08;1&#xff09;什么是卷積操作&#xff1f;&#xff08;2&#xff09;卷積…

iOS 26 正式版即將發布,Flutter 完成全新 devicectl + lldb 的 Debug JIT 運行支持

在之前的 《Flutter 又雙叒叕可以在 iOS 26 的真機上 hotload》 和 《Flutter 在 iOS 真機 Debug 運行出現 Timed out *** to update》 我們聊過&#xff0c;由于 iOS 26 開始&#xff0c;Apple 正式禁止了 Debug 時 mprotect 的 RX 權限&#xff0c;導致了 Flutter 在 Debug 運…

機器學習全流程拆解 _ 從數據到模型的科學之道

-—— 避開80%項目失敗的隱形成本&#xff0c;掌握高效建模方法論*&#x1f4cc; 一、明確目標&#xff1a;成敗的起點 1. 問題定位 分類任務&#xff1a;區分二分類/多分類/多標簽分類預測任務&#xff1a;標量預測&#xff08;如房價&#xff09;vs 向量預測&#xff08;如股…

Android 廣告輪播全實現:圖片與視頻混合展示的完整方案

廣告輪播是移動應用中提升用戶轉化率的核心組件&#xff0c;尤其在電商、資訊類應用中應用廣泛。傳統輪播僅支持圖片展示&#xff0c;而現代應用需要兼顧圖片和視頻內容以增強吸引力。本文將詳細講解如何實現一個支持圖片與視頻混合播放的高性能廣告輪播&#xff0c;涵蓋布局設…

AI大模型企業落地指南-筆記01

前言AI技術的發展趨勢必然是越來越普及&#xff0c;越來越“技術平權”的。在未來10年內&#xff0c;AI將以各種方式“融入”人類世界&#xff0c;與人類乃至世界深度融合。一. 概念第1章 AI與大模型概述1.1 什么是AI人工智能&#xff08;全稱Artificial Intelligence&#xff…

Linux-孤兒進程和僵死進程

文章目錄孤兒進程概述僵死進程概述孤兒進程 概述 父進程運行結束&#xff0c;子進程還在運行&#xff0c;此時&#xff0c;子進程就成了孤兒進程&#xff08;Orphan Process&#xff09;每當出現一個孤兒進程的時候&#xff0c;內核就把孤兒進程的父進程設置為 init &#xf…