音視頻學習(五十九):H264中的SPS

在 H.264 (也稱為 AVC, Advanced Video Coding) 視頻編碼標準中,SPS (Sequence Parameter Set) 是一個至關重要的 NALU (Network Abstraction Layer Unit) 類型,它承載著整個視頻序列共有的全局性配置信息。你可以把它理解為視頻文件的“基因”,它定義了視頻流的基礎結構和解碼規則。在解碼器開始解析視頻數據之前,它必須先獲取并理解 SPS 中的內容,否則后續的圖像數據將無法正確還原。

H264中的NALU Type

NALU Type (十進制)NALU Type (十六進制)名稱描述
00x00未定義保留。
10x01非 IDR 圖像中的非分片包含一個非 IDR (Instantaneous Decoding Refresh) 圖像的編碼分片。
20x02非 IDR 圖像中的 A 型分片包含一個非 IDR 圖像的編碼分片 A。
30x03非 IDR 圖像中的 B 型分片包含一個非 IDR 圖像的編碼分片 B。
40x04非 IDR 圖像中的 C 型分片包含一個非 IDR 圖像的編碼分片 C。
50x05IDR 圖像中的分片包含一個 IDR (Instantaneous Decoding Refresh) 圖像的編碼分片。IDR 幀標志著一個序列的開始,解碼器可以在此點清空參考幀緩沖區,用于流的隨機訪問。
60x06補充增強信息 (SEI)SEI (Supplemental Enhancement Information)。它包含一些不影響解碼過程但對增強視頻使用有幫助的信息,如時間戳、用戶數據等。
70x07序列參數集 (SPS)SPS (Sequence Parameter Set)。包含全局性的視頻序列參數,如 Profile、Level、圖像尺寸、參考幀數量等。它是解碼器進行解碼的必要配置。
80x08圖像參數集 (PPS)PPS (Picture Parameter Set)。包含與一個或多個圖像相關的參數,如熵編碼模式、量化參數等。它引用 SPS,并與圖像數據結合使用。
90x09訪問單元分隔符標志一個新訪問單元 (Access Unit) 的開始,一個訪問單元通常對應一個完整的圖像。
100x0A序列結束符標志視頻序列的結束。
110x0B流結束符標志整個視頻流的結束。
120x0C填充數據用于填充以對齊數據,通常不包含實際的視頻信息。
130x0D序列參數集擴展包含了 SPS 的擴展信息,主要用于 SVC (Scalable Video Coding) 和 MVC (Multiview Video Coding)。
140x0E前綴 NALU用于 SVC 和 MVC。
150x0FSVC/MVC 分片用于 SVC/MVC 視頻編碼。
16-180x10-0x12SVC/MVC 分片用于 SVC/MVC 視頻編碼。
190x13非 IDR 圖像分片 (包含參考圖片列表)與 NALU Type 1 類似,但包含更靈活的參考圖片列表重構信息。
200x14SVC/MVC 分片用于 SVC/MVC 視頻編碼。
21-230x15-0x17未定義保留。
24-310x18-0x1F聚合或填充 NALUAggregate/Padding NALU。用于封裝多個 NALU 或作為填充,主要用于 RTP (Real-time Transport Protocol) 傳輸。

什么是 SPS?

SPS 是一種特殊的 NALU,其 NALU Header 中的 nal_unit_type 字段值為 7。它包含了與整個視頻序列相關聯的參數,例如:

  • Profile 和 Level 信息:決定了視頻流所遵循的編碼規格和復雜性等級,是解碼器兼容性檢查的關鍵。
  • 圖像尺寸:定義了視頻的寬度和高度。
  • 幀率:盡管不是直接存儲在 SPS 中,但 SPS 提供了計算幀率所需的參數。
  • 參考幀管理:定義了用于運動補償的參考幀數量。
  • 量化參數:提供了量化表的選擇和相關參數。
  • VUI (Video Usability Information):可選的,但包含了許多重要的信息,如寬高比、顏色空間、時間信息等。

SPS 可以存在于視頻流的多個位置,通常在 IDR (Instantaneous Decoding Refresh) 幀之前出現,以確保解碼器在任何時候進入視頻流時都能獲取到最新的配置信息。一個視頻流中可以有多個 SPS,每個 SPS 都有一個唯一的 ID,通過 ID 來區分和引用。

SPS結構體

標準語法(ISO/IEC 14496-10 / ITU-T H.264, Table 7-1):

seq_parameter_set_rbsp( ) {profile_idc  constraint_set_flags  level_idc  seq_parameter_set_id  log2_max_frame_num_minus4  pic_order_cnt_type  if( pic_order_cnt_type == 0 )  log2_max_pic_order_cnt_lsb_minus4  else if( pic_order_cnt_type == 1 ) {  delta_pic_order_always_zero_flag  offset_for_non_ref_pic  offset_for_top_to_bottom_field  num_ref_frames_in_pic_order_cnt_cycle  offset_for_ref_frame[ i ]  }  max_num_ref_frames  gaps_in_frame_num_value_allowed_flag  pic_width_in_mbs_minus1  pic_height_in_map_units_minus1  frame_mbs_only_flag  if( !frame_mbs_only_flag )  mb_adaptive_frame_field_flag  direct_8x8_inference_flag  frame_cropping_flag  if( frame_cropping_flag ) {  frame_crop_left_offset  frame_crop_right_offset  frame_crop_top_offset  frame_crop_bottom_offset  }  vui_parameters_present_flag  if( vui_parameters_present_flag )  vui_parameters( )  
}

結構示意圖:

Sequence Parameter Set (SPS)
│
├── profile_idc (8 bits)
├── constraint_set_flags (6 bits) + reserved_zero_2bits
├── level_idc (8 bits)
├── seq_parameter_set_id (UE)
│
├── log2_max_frame_num_minus4 (UE)
│
├── pic_order_cnt_type (UE)
│   ├── if = 0 → log2_max_pic_order_cnt_lsb_minus4 (UE)
│   └── if = 1
│        ├── delta_pic_order_always_zero_flag (1 bit)
│        ├── offset_for_non_ref_pic (SE)
│        ├── offset_for_top_to_bottom_field (SE)
│        ├── num_ref_frames_in_pic_order_cnt_cycle (UE)
│        └── offset_for_ref_frame[i] (SE) × N
│
├── max_num_ref_frames (UE)
├── gaps_in_frame_num_value_allowed_flag (1 bit)
│
├── pic_width_in_mbs_minus1 (UE)
├── pic_height_in_map_units_minus1 (UE)
├── frame_mbs_only_flag (1 bit)
│   └── if = 0 → mb_adaptive_frame_field_flag (1 bit)
├── direct_8x8_inference_flag (1 bit)
│
├── frame_cropping_flag (1 bit)
│   └── if = 1
│        ├── frame_crop_left_offset (UE)
│        ├── frame_crop_right_offset (UE)
│        ├── frame_crop_top_offset (UE)
│        └── frame_crop_bottom_offset (UE)
│
└── vui_parameters_present_flag (1 bit)└── if = 1 → vui_parameters( )

C 語言結構體示例:

typedef struct {int profile_idc;                // Profile 標識 (例如: Baseline=66, Main=77, High=100)int constraint_set_flags;       // 約束集合標志 (constraint_set0_flag ~ constraint_set5_flag, 用于兼容性說明)int level_idc;                  // Level 標識 (例如: 3.1=31, 4.0=40)int seq_parameter_set_id;       // SPS 的 ID (用于區分多個 SPS)int log2_max_frame_num_minus4;  // 最大幀號長度的 log2 值減4 → 最大 frame_num = 2^(log2_max_frame_num_minus4 + 4)int pic_order_cnt_type;         // POC (顯示順序) 類型 (0/1/2)int log2_max_pic_order_cnt_lsb_minus4; // (pic_order_cnt_type==0) 時的 POC LSB 最大值的 log2 減4int delta_pic_order_always_zero_flag;  // (pic_order_cnt_type==1) 時的標志,若為1則相關偏移量恒為0int offset_for_non_ref_pic;            // (type=1) 非參考幀的 POC 偏移int offset_for_top_to_bottom_field;    // (type=1) 頂場到底場的 POC 偏移int num_ref_frames_in_pic_order_cnt_cycle; // (type=1) POC 循環中的參考幀個數int offset_for_ref_frame[256];         // (type=1) 每個參考幀的 POC 偏移 (最多255個)int max_num_ref_frames;                // 最大參考幀數 (解碼器參考幀緩沖區大小)int gaps_in_frame_num_value_allowed_flag; // 是否允許 frame_num 不連續 (1=允許丟幀)int pic_width_in_mbs_minus1;           // 圖像寬度 (以宏塊16像素為單位, 實際寬度= (pic_width_in_mbs_minus1+1)*16)int pic_height_in_map_units_minus1;    // 圖像高度 (以宏塊為單位, 實際高度= (pic_height_in_map_units_minus1+1)*16*(2 - frame_mbs_only_flag))int frame_mbs_only_flag;               // 是否僅支持幀編碼 (1=幀編碼, 0=支持場編碼)int mb_adaptive_frame_field_flag;      // (當frame_mbs_only_flag=0時) 是否允許 MBAFF (宏塊自適應幀/場編碼)int direct_8x8_inference_flag;         // 是否啟用 8x8 直接模式推斷 (B幀運動補償相關)int frame_cropping_flag;               // 是否存在裁剪參數int frame_crop_left_offset;            // 左邊裁剪宏塊數int frame_crop_right_offset;           // 右邊裁剪宏塊數int frame_crop_top_offset;             // 上邊裁剪宏塊數int frame_crop_bottom_offset;          // 下邊裁剪宏塊數int vui_parameters_present_flag;       // 是否存在 VUI 參數 (視頻可用性信息: 時基, SAR, 色彩空間, HRD 等)// struct VUI vui;                      // VUI 擴展信息 (可選)
} SPS_t;

SPS 參數詳解

profile_idclevel_idc

這是 SPS 中最重要的兩個參數,它們一起定義了視頻流的編碼約束和復雜性。

  • profile_idc (Profile Indication): 指示了編碼器使用的編碼工具集。不同的 Profile 支持不同的編碼特性,例如 B 幀、CABAC 等。常見的 Profile 包括:
    • Baseline Profile (66): 最基礎的配置,不支持 B 幀和 CABAC,主要用于實時通信和移動設備,如視頻會議。
    • Main Profile (77): 支持 B 幀,用于標清電視廣播。
    • High Profile (100): 支持更多高級特性,如 8x8 塊變換、無損編碼等,廣泛用于高清電視廣播和藍光光盤。
    • 還有其他如 Progressive High Profile (110) 和 Multiview High Profile (118) 等,用于特殊應用場景。
  • level_idc (Level Indication): 定義了視頻流的約束級別,例如最大幀尺寸、最大比特率、最大宏塊處理速率等。Level 越高,支持的視頻分辨率、幀率和比特率就越高。這為解碼器提供了性能參考,確保它有能力處理該視頻流。例如,Level 4.1 支持 1920x1080p 分辨率,而 Level 5.1 則可以支持更高的分辨率。

seq_parameter_set_id

這是一個 0 到 31 的無符號整數,用于唯一標識當前的 SPS。當解碼器解析 PPS (Picture Parameter Set) 時,它會通過 PPS 中的 seq_parameter_set_id 字段來引用相應的 SPS,從而建立起 PPS 和 SPS 之間的關聯。這種設計使得多個 PPS 可以共享同一個 SPS,也方便了在視頻流中切換不同的配置。

圖像尺寸相關參數

SPS 中定義了視頻圖像的寬度和高度,但它們不是直接以像素為單位存儲的,而是通過宏塊 (Macroblock) 的數量來表示。

  • pic_width_in_mbs_minus1: 圖像寬度,以宏塊為單位。實際寬度為 16 * (pic_width_in_mbs_minus1 + 1) 像素。
  • pic_height_in_map_units_minus1: 圖像高度,以宏塊行數或宏塊組為單位。實際高度為 16 * (pic_height_in_map_units_minus1 + 1) 像素。

這種基于宏塊的尺寸定義方式是 H.264 的基本特性,因為所有編碼和解碼操作都是以宏塊為單位進行的。

參考幀相關參數

H.264 使用幀間預測來提高壓縮效率,這就需要管理參考幀。SPS 中的參數定義了參考幀的管理方式。

  • num_ref_frames: 指示了解碼器用于幀間預測的參考幀的最大數量。這個參數對于解碼器的內存管理至關重要,因為它需要為這些參考幀分配緩沖區。

chroma_format_idc

這個參數定義了視頻的色度格式,也就是 YUV (或 YCbCr) 格式中的 U 和 V 分量采樣方式。常見的有:

  • 4:2:0 (1): 寬高都減半采樣,最常用,適用于大多數應用場景。
  • 4:2:2 (2): 垂直方向不減半,水平方向減半,主要用于廣播和專業視頻編輯。
  • 4:4:4 (3): 不進行色度下采樣,每個像素都有完整的 YUV 數據,用于高質量無損編碼。

VUI (Video Usability Information)

VUI 是一個可選的子結構,但它包含了許多對解碼和顯示至關重要的信息。如果 SPS 中存在 vui_parameters_present_flag 為 1,則會包含 VUI。

  • aspect_ratio_idc: 定義了像素寬高比 (Pixel Aspect Ratio),告訴播放器如何正確地拉伸圖像以得到正確的顯示寬高比 (Display Aspect Ratio)。
  • timing_info_present_flag: 如果為 1,則包含時間信息,如 num_units_in_ticktime_scale。它們共同定義了視頻的幀率。
    • frame_rate = time_scale / (2 * num_units_in_tick)
  • video_signal_type_present_flag: 定義了顏色空間、色度位置和視頻信號類型。
    • video_full_range_flag: 指示亮度信號是全范圍 (0-255) 還是有限范圍 (16-235)。
    • colour_primaries, transfer_characteristics, matrix_coefficients: 這些參數共同定義了顏色空間,例如 BT.709 (高清) 或 BT.601 (標清),對于準確的色彩還原至關重要。

使用示例(c++)

// h264_sps_vui_parser.h
#pragma once
#include <cstdint>
#include <vector>
#include <stdexcept>
#include <string>
#include <limits>struct H264VUI {// aspect ratiobool aspect_ratio_info_present_flag = false;uint32_t aspect_ratio_idc = 0;uint32_t sar_width = 0;uint32_t sar_height = 0;// overscan / video signalbool overscan_info_present_flag = false;bool overscan_appropriate_flag = false;bool video_signal_type_present_flag = false;uint32_t video_format = 0;bool video_full_range_flag = false;bool colour_description_present_flag = false;uint32_t colour_primaries = 0;uint32_t transfer_characteristics = 0;uint32_t matrix_coefficients = 0;// chroma locbool chroma_loc_info_present_flag = false;uint32_t chroma_sample_loc_type_top_field = 0;uint32_t chroma_sample_loc_type_bottom_field = 0;// timingbool timing_info_present_flag = false;uint32_t num_units_in_tick = 0;uint32_t time_scale = 0;bool fixed_frame_rate_flag = false;// other flags (parsed minimally)bool nal_hrd_parameters_present_flag = false;bool vcl_hrd_parameters_present_flag = false;bool low_delay_hrd_flag = false;bool pic_struct_present_flag = false;// bitstream restrictionbool bitstream_restriction_flag = false;uint32_t max_bytes_per_pic_denom = 0;uint32_t max_bits_per_mb_denom = 0;uint32_t log2_max_mv_length_horizontal = 0;uint32_t log2_max_mv_length_vertical = 0;uint32_t num_reorder_frames = 0;uint32_t max_dec_frame_buffering = 0;
};struct H264SPS {// Basicuint8_t profile_idc = 0;uint8_t constraint_set_flags = 0; // bit0..5 validuint8_t level_idc = 0;uint32_t seq_parameter_set_id = 0;// Extendeduint32_t chroma_format_idc = 1; // default 1 (4:2:0)bool separate_colour_plane_flag = false;// Frame num & POCuint32_t log2_max_frame_num_minus4 = 0;uint32_t pic_order_cnt_type = 0;// poc type 0uint32_t log2_max_pic_order_cnt_lsb_minus4 = 0;// poc type 1uint8_t  delta_pic_order_always_zero_flag = 0;int32_t  offset_for_non_ref_pic = 0;int32_t  offset_for_top_to_bottom_field = 0;uint32_t num_ref_frames_in_pic_order_cnt_cycle = 0;int32_t  offset_for_ref_frame[256] = {0}; // standard allows up to 255// Ref frames & geometryuint32_t max_num_ref_frames = 0;uint8_t  gaps_in_frame_num_value_allowed_flag = 0;uint32_t pic_width_in_mbs_minus1 = 0;uint32_t pic_height_in_map_units_minus1 = 0;uint8_t  frame_mbs_only_flag = 1;uint8_t  mb_adaptive_frame_field_flag = 0;uint8_t  direct_8x8_inference_flag = 0;// Croppinguint8_t  frame_cropping_flag = 0;uint32_t frame_crop_left_offset = 0;uint32_t frame_crop_right_offset = 0;uint32_t frame_crop_top_offset = 0;uint32_t frame_crop_bottom_offset = 0;// VUIuint8_t  vui_parameters_present_flag = 0;H264VUI vui;// convenience: compute pixel size (considers chroma format & crop)int width() const {int pic_w = (int)((pic_width_in_mbs_minus1 + 1) * 16);// height: each map unit is 16 for frame, 32 for field pairs when frame_mbs_only_flag==0int pic_h = (int)((pic_height_in_map_units_minus1 + 1) * 16 * (2 - frame_mbs_only_flag));if (frame_cropping_flag) {int crop_unit_x = 1;int crop_unit_y = 1;// crop unit calculation depends on chroma_format_idc and separate_colour_plane_flagif (chroma_format_idc == 0) { // 4:0:0crop_unit_x = 1;crop_unit_y = 2 - frame_mbs_only_flag;} else if (chroma_format_idc == 1) { // 4:2:0crop_unit_x = 2;crop_unit_y = 2 * (2 - frame_mbs_only_flag);} else if (chroma_format_idc == 2) { // 4:2:2crop_unit_x = 2;crop_unit_y = (2 - frame_mbs_only_flag);} else if (chroma_format_idc == 3) { // 4:4:4crop_unit_x = 1;crop_unit_y = (2 - frame_mbs_only_flag);}pic_w -= (frame_crop_left_offset + frame_crop_right_offset) * crop_unit_x;pic_h -= (frame_crop_top_offset + frame_crop_bottom_offset) * crop_unit_y;}return pic_w;}int height() const {int pic_w = (int)((pic_width_in_mbs_minus1 + 1) * 16);int pic_h = (int)((pic_height_in_map_units_minus1 + 1) * 16 * (2 - frame_mbs_only_flag));if (frame_cropping_flag) {int crop_unit_x = 1;int crop_unit_y = 1;if (chroma_format_idc == 0) {crop_unit_x = 1;crop_unit_y = 2 - frame_mbs_only_flag;} else if (chroma_format_idc == 1) {crop_unit_x = 2;crop_unit_y = 2 * (2 - frame_mbs_only_flag);} else if (chroma_format_idc == 2) {crop_unit_x = 2;crop_unit_y = (2 - frame_mbs_only_flag);} else if (chroma_format_idc == 3) {crop_unit_x = 1;crop_unit_y = (2 - frame_mbs_only_flag);}pic_w -= (frame_crop_left_offset + frame_crop_right_offset) * crop_unit_x;pic_h -= (frame_crop_top_offset + frame_crop_bottom_offset) * crop_unit_y;}(void)pic_w;return pic_h;}// frame rate: if VUI timing info present, compute fps, else return 0.0// common formula: fps = time_scale / (2 * num_units_in_tick)double fps() const {if (!vui.timing_info_present_flag || vui.num_units_in_tick == 0) return 0.0;return (double)vui.time_scale / (2.0 * (double)vui.num_units_in_tick);}bool has_timing() const {return vui.timing_info_present_flag;}
};// ---- 工具:移除 0x000003 仿射字節(得到 RBSP) ----
inline std::vector<uint8_t> avc_nal_to_rbsp(const uint8_t* data, size_t size) {std::vector<uint8_t> out;out.reserve(size);int zero_count = 0;for (size_t i = 0; i < size; ++i) {uint8_t b = data[i];if (zero_count == 2 && b == 0x03) {// skip emulation prevention bytezero_count = 0;continue;}out.push_back(b);if (b == 0x00) zero_count++;else zero_count = 0;}return out;
}// ---- 比特讀取器(MSB-first)+ Exp-Golomb ----
class BitReader {
public:BitReader(const uint8_t* d, size_t n) : data_(d), size_(n) {}uint32_t readBits(int n) {if (n <= 0 || n > 32) throw std::runtime_error("readBits n out of range");uint32_t v = 0;for (int i = 0; i < n; ++i) {if (bitpos_ >= size_ * 8) throw std::runtime_error("bitstream overread");v <<= 1;v |= ((data_[bitpos_ >> 3] >> (7 - (bitpos_ & 7))) & 1);++bitpos_;}return v;}uint8_t readBit() { return (uint8_t)readBits(1); }// Unsigned Exp-Golombuint32_t readUE() {int leadingZeroBits = -1;uint8_t b = 0;do {b = readBit();++leadingZeroBits;if (bitpos_ > size_ * 8) throw std::runtime_error("UE parse overread");} while (b == 0);if (leadingZeroBits < 0) throw std::runtime_error("UE parse error");uint32_t info = 0;if (leadingZeroBits > 0) info = readBits(leadingZeroBits);return ((1u << leadingZeroBits) - 1u) + info;}// Signed Exp-Golombint32_t readSE() {uint32_t ue = readUE();int32_t v = (int32_t)((ue + 1) / 2);return (ue & 1) ? v : -v;}size_t bitsRemaining() const { size_t total = size_ * 8;return (bitpos_ <= total) ? (total - bitpos_) : 0;}private:const uint8_t* data_;size_t size_;size_t bitpos_ = 0;
};// ---- 輔助:解析 HRD parameters(用于推進比特流) ----
inline void parse_hrd_parameters(BitReader& br) {uint32_t cpb_cnt_minus1 = br.readUE();uint32_t bit_rate_scale = br.readBits(4);uint32_t cpb_size_scale = br.readBits(4);for (uint32_t i = 0; i <= cpb_cnt_minus1; ++i) {(void)br.readUE(); // bit_rate_value_minus1(void)br.readUE(); // cpb_size_value_minus1(void)br.readBit(); // cbr_flag}(void)br.readBits(5); // initial_cpb_removal_delay_length_minus1(void)br.readBits(5); // cpb_removal_delay_length_minus1(void)br.readBits(5); // dpb_output_delay_length_minus1(void)br.readBits(5); // time_offset_length
}// ---- 解析 VUI parameters(實現常用字段與 timing info) ----
inline void parse_vui_parameters(BitReader& br, H264VUI& vui) {vui.aspect_ratio_info_present_flag = br.readBit();if (vui.aspect_ratio_info_present_flag) {vui.aspect_ratio_idc = br.readBits(8);if (vui.aspect_ratio_idc == 255) { // Extended_SARvui.sar_width = br.readBits(16);vui.sar_height = br.readBits(16);}}vui.overscan_info_present_flag = br.readBit();if (vui.overscan_info_present_flag) {vui.overscan_appropriate_flag = br.readBit();}vui.video_signal_type_present_flag = br.readBit();if (vui.video_signal_type_present_flag) {vui.video_format = br.readBits(3);vui.video_full_range_flag = br.readBit();vui.colour_description_present_flag = br.readBit();if (vui.colour_description_present_flag) {vui.colour_primaries = br.readBits(8);vui.transfer_characteristics = br.readBits(8);vui.matrix_coefficients = br.readBits(8);}}vui.chroma_loc_info_present_flag = br.readBit();if (vui.chroma_loc_info_present_flag) {vui.chroma_sample_loc_type_top_field = br.readUE();vui.chroma_sample_loc_type_bottom_field = br.readUE();}// timing infovui.timing_info_present_flag = br.readBit();if (vui.timing_info_present_flag) {vui.num_units_in_tick = br.readBits(32);vui.time_scale = br.readBits(32);vui.fixed_frame_rate_flag = br.readBit();}// HRD params (nal & vcl)vui.nal_hrd_parameters_present_flag = br.readBit();if (vui.nal_hrd_parameters_present_flag) {parse_hrd_parameters(br);}vui.vcl_hrd_parameters_present_flag = br.readBit();if (vui.vcl_hrd_parameters_present_flag) {parse_hrd_parameters(br);}if (vui.nal_hrd_parameters_present_flag || vui.vcl_hrd_parameters_present_flag) {vui.low_delay_hrd_flag = br.readBit();}vui.pic_struct_present_flag = br.readBit();// bitstream restrictionvui.bitstream_restriction_flag = br.readBit();if (vui.bitstream_restriction_flag) {vui.max_bytes_per_pic_denom = br.readUE();vui.max_bits_per_mb_denom = br.readUE();vui.log2_max_mv_length_horizontal = br.readUE();vui.log2_max_mv_length_vertical = br.readUE();vui.num_reorder_frames = br.readUE();vui.max_dec_frame_buffering = br.readUE();}
}// ---- 核心:解析 SPS(輸入為 NALU payload,不含起始碼;可以含 NAL header) ----
inline H264SPS parse_h264_sps(const uint8_t* nalu, size_t nalu_size) {if (!nalu || nalu_size < 4) throw std::runtime_error("SPS NAL too small");size_t offset = 0;uint8_t nal_unit_type = (nalu[0] & 0x1F);if (nal_unit_type == 7) {offset = 1; // skip nal header}auto rbsp = avc_nal_to_rbsp(nalu + offset, nalu_size - offset);if (rbsp.empty()) throw std::runtime_error("empty RBSP after EP removal");BitReader br(rbsp.data(), rbsp.size());H264SPS sps{};sps.profile_idc = (uint8_t)br.readBits(8);sps.constraint_set_flags = (uint8_t)br.readBits(8);sps.level_idc = (uint8_t)br.readBits(8);sps.seq_parameter_set_id = br.readUE();// profile-specific extensions (chroma format, bit depth, scaling lists)bool high_profile = (sps.profile_idc == 100 || sps.profile_idc == 110 ||sps.profile_idc == 122 || sps.profile_idc == 244 ||sps.profile_idc == 44  || sps.profile_idc == 83  ||sps.profile_idc == 86  || sps.profile_idc == 118 ||sps.profile_idc == 128 || sps.profile_idc == 138 ||sps.profile_idc == 139 || sps.profile_idc == 134 ||sps.profile_idc == 135);if (high_profile) {sps.chroma_format_idc = br.readUE();if (sps.chroma_format_idc == 3) {sps.separate_colour_plane_flag = br.readBit();}(void)br.readUE(); // bit_depth_luma_minus8(void)br.readUE(); // bit_depth_chroma_minus8(void)br.readBit(); // qpprime_y_zero_transform_bypass_flaguint8_t seq_scaling_matrix_present_flag = br.readBit();if (seq_scaling_matrix_present_flag) {int count = (sps.chroma_format_idc != 3) ? 8 : 12;for (int i = 0; i < count; ++i) {uint8_t seq_scaling_list_present_flag = br.readBit();if (seq_scaling_list_present_flag) {int sizeOfScalingList = (i < 6) ? 16 : 64;int lastScale = 8;int nextScale = 8;for (int j = 0; j < sizeOfScalingList; ++j) {if (nextScale != 0) {int32_t delta_scale = br.readSE();nextScale = (lastScale + delta_scale + 256) % 256;}lastScale = (nextScale == 0) ? lastScale : nextScale;}}}}} else {sps.chroma_format_idc = 1; // default 4:2:0sps.separate_colour_plane_flag = false;}sps.log2_max_frame_num_minus4 = br.readUE();sps.pic_order_cnt_type = br.readUE();if (sps.pic_order_cnt_type == 0) {sps.log2_max_pic_order_cnt_lsb_minus4 = br.readUE();} else if (sps.pic_order_cnt_type == 1) {sps.delta_pic_order_always_zero_flag = br.readBit();sps.offset_for_non_ref_pic = br.readSE();sps.offset_for_top_to_bottom_field = br.readSE();sps.num_ref_frames_in_pic_order_cnt_cycle = br.readUE();if (sps.num_ref_frames_in_pic_order_cnt_cycle > 255) throw std::runtime_error("num_ref_frames_in_pic_order_cnt_cycle too large");for (uint32_t i = 0; i < sps.num_ref_frames_in_pic_order_cnt_cycle; ++i) {sps.offset_for_ref_frame[i] = br.readSE();}}sps.max_num_ref_frames = br.readUE();sps.gaps_in_frame_num_value_allowed_flag = br.readBit();sps.pic_width_in_mbs_minus1 = br.readUE();sps.pic_height_in_map_units_minus1 = br.readUE();sps.frame_mbs_only_flag = br.readBit();if (!sps.frame_mbs_only_flag) {sps.mb_adaptive_frame_field_flag = br.readBit();}sps.direct_8x8_inference_flag = br.readBit();sps.frame_cropping_flag = br.readBit();if (sps.frame_cropping_flag) {sps.frame_crop_left_offset   = br.readUE();sps.frame_crop_right_offset  = br.readUE();sps.frame_crop_top_offset    = br.readUE();sps.frame_crop_bottom_offset = br.readUE();}sps.vui_parameters_present_flag = br.readBit();if (sps.vui_parameters_present_flag) {parse_vui_parameters(br, sps.vui);}return sps;
}// ---- 簡易輔助:從 Annex-B 字節流中抽取 SPS NAL 并解析 ----
inline bool find_and_parse_sps_from_annexb(const uint8_t* data, size_t size, H264SPS& out) {// 搜索 00 00 01 / 00 00 00 01 start codesize_t i = 0;auto is_start_code = [&](size_t p)->int {if (p + 3 < size && data[p]==0 && data[p+1]==0 && data[p+2]==1) return 3;if (p + 4 < size && data[p]==0 && data[p+1]==0 && data[p+2]==0 && data[p+3]==1) return 4;return 0;};while (i + 4 < size) {int sc = is_start_code(i);if (sc == 0) { ++i; continue; }size_t nal_begin = i + sc;if (nal_begin >= size) break;uint8_t nal_unit_type = data[nal_begin] & 0x1F;// 找到下一個 start code 作為 NAL 結束size_t j = nal_begin + 1;while (j + 3 < size && is_start_code(j) == 0) ++j;size_t nal_end = j;if (nal_unit_type == 7) {out = parse_h264_sps(data + nal_begin, nal_end - nal_begin);return true;}i = j;}return false;
}

測試程序:

#include "h264_sps_vui_parser.h"
#include <iostream>int main() {const uint8_t sample_annexb[] = {0x00,0x00,0x00,0x01, 0x67, 0x64,0x00,0x1f, 0xac,0xd9,0x40,0x78,0x02,0x27,0xe5,0xc0,0x44,0x00,0x00,0x03,0x00,0x04,0x00,0x00,0x03,0x00,0xc8,0x3c,0x60,0xc6,0x58};H264SPS sps;if (find_and_parse_sps_from_annexb(sample_annexb, sizeof(sample_annexb), sps)) {std::cout << "Profile: " << (int)sps.profile_idc<< " Level: "  << (int)sps.level_idc<< " SPS id: " << sps.seq_parameter_set_id << "\n";std::cout << "Chroma format: " << sps.chroma_format_idc<< " separate_colour_plane_flag: " << sps.separate_colour_plane_flag << "\n";std::cout << "Width x Height = " << sps.width()<< " x " << sps.height() << "\n";if (sps.has_timing()) {std::cout << "Timing present: " << sps.vui.num_units_in_tick << " / " << sps.vui.time_scale<< " fixed_frame_rate_flag=" << sps.vui.fixed_frame_rate_flag << "\n";std::cout << "FPS ~= " << sps.fps() << "\n";} else {std::cout << "No timing info in VUI\n";}} else {std::cout << "SPS not found\n";}return 0;
}

ffmpeg處理sps

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}#include <iostream>int main(int argc, char* argv[]) {if (argc < 2) {std::cout << "Usage: " << argv[0] << " input.h264" << std::endl;return -1;}const char* filename = argv[1];AVFormatContext* fmt_ctx = nullptr;// 打開文件或流if (avformat_open_input(&fmt_ctx, filename, nullptr, nullptr) < 0) {std::cerr << "Could not open input file" << std::endl;return -1;}// 讀取流信息if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {std::cerr << "Could not find stream information" << std::endl;avformat_close_input(&fmt_ctx);return -1;}// 找到視頻流int video_stream_index = -1;for (unsigned int i = 0; i < fmt_ctx->nb_streams; i++) {if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {video_stream_index = i;break;}}if (video_stream_index == -1) {std::cerr << "Could not find video stream" << std::endl;avformat_close_input(&fmt_ctx);return -1;}AVCodecParameters* codecpar = fmt_ctx->streams[video_stream_index]->codecpar;// 創建 codec contextconst AVCodec* codec = avcodec_find_decoder(codecpar->codec_id);AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);avcodec_parameters_to_context(codec_ctx, codecpar);if (avcodec_open2(codec_ctx, codec, nullptr) < 0) {std::cerr << "Could not open codec" << std::endl;avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return -1;}// 輸出 SPS 信息 (通過 codec context 已經解析好)std::cout << "Codec: "   << avcodec_get_name(codecpar->codec_id) << std::endl;std::cout << "Profile: " << codec_ctx->profile << std::endl;   // profile_idcstd::cout << "Level: "   << codec_ctx->level << std::endl;     // level_idcstd::cout << "Width: "   << codec_ctx->width << std::endl;     // 寬度std::cout << "Height: "  << codec_ctx->height << std::endl;    // 高度// 如果需要查看原始 extradata (包含 SPS/PPS NALU)std::cout << "Extradata size: " << codecpar->extradata_size << std::endl;if (codecpar->extradata && codecpar->extradata_size > 0) {std::cout << "SPS/PPS data (hex):" << std::endl;for (int i = 0; i < codecpar->extradata_size; i++) {printf("%02X ", codecpar->extradata[i]);}std::cout << std::endl;}// 釋放資源avcodec_free_context(&codec_ctx);avformat_close_input(&fmt_ctx);return 0;
}

輸出如下:

Codec: h264
Profile: 100
Level: 40
Width: 1920
Height: 1088
Extradata size: 39
SPS/PPS data (hex):
00 00 00 01 67 64 00 28 AC 2B 40 50 1E D0 0D 41 41 ...

作用

SPS 的存在為 H.264 視頻流提供了一個清晰、可控的元數據層。其主要作用包括:

  1. 解碼配置:它是解碼器開始解碼前必須獲取的“說明書”。沒有 SPS,解碼器就無法知道視頻的分辨率、Profile、Level 等基本信息,也就無法正確地分配內存、設置解碼模式。
  2. 流式傳輸的健壯性:由于 SPS 可以多次出現,即使在傳輸過程中丟失了一部分數據,解碼器也可以在下一個 SPS 出現時重新同步,繼續解碼。
  3. 兼容性檢查profile_idclevel_idc 允許解碼器在開始解碼前檢查自身是否具備處理該視頻流的能力,從而避免了因不支持的特性而導致的解碼失敗。
  4. 互操作性:SPS 提供了視頻流的標準化描述,使得不同廠商的編碼器和解碼器可以相互兼容。

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

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

相關文章

linux實時性研究

Linux 實時性研究旨在提升 Linux 系統對外部事件的響應速度和確定性,使其能夠滿足實時應用的需求。以下是關于 Linux 實時性研究的一些關鍵內容: Linux 實時性不足的原因 中斷優先級問題:在標準 Linux 內核中,中斷具有最高優先級,包括軟中斷,這使得實時任務的優先級得不到…

Java-面試八股文-Mysql篇

MySQL篇 1、Select 語句完整的執行順序 難度系數&#xff1a;?&#x1f4cc; SQL SELECT 語句書寫順序&#xff08;開發者寫的順序&#xff09; SELECT ... FROM ... JOIN ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT ...&#x1f4cc; 實際執行順序&#…

多代理系統架構:Supervisor 與 Swarm 架構詳解

多代理&#xff08;Multi-Agent&#xff09;系統正成為構建復雜 AI 應用的重要范式。本文將深入剖析兩種熱門的多代理架構模式——Supervisor&#xff08;主管模式&#xff09;與 Swarm&#xff08;群智模式&#xff09;&#xff0c;揭示它們的執行流程、適用場景及實現細節&am…

【深度學習】思維鏈(Chain of Thought, CoT):提升大模型推理能力的關鍵技術

思維鏈&#xff08;Chain of Thought, CoT&#xff09;&#xff1a;提升大模型推理能力的關鍵技術 文章目錄思維鏈&#xff08;Chain of Thought, CoT&#xff09;&#xff1a;提升大模型推理能力的關鍵技術1 什么是思維鏈&#xff08;Chain of Thought, CoT&#xff09;&#…

GitHub 宕機自救指南:打造韌性開發體系

一、引言1.1 GitHub 宕機事件回顧與影響剖析在軟件開發的廣袤版圖中&#xff0c;GitHub 宛如一座熠熠生輝的燈塔&#xff0c;為全球超 1 億開發者照亮前行之路&#xff0c;其重要性不言而喻。它集代碼托管、版本控制、協作開發以及項目管理等核心功能于一身&#xff0c;是無數開…

移動端網頁調試實戰,iOS WebKit Debug Proxy 的應用與替代方案

在移動端開發中&#xff0c;iOS WebView 的調試一直是個難題。不同于 Android 可以依賴 Chrome DevTools 和 ADB&#xff0c;iOS 的 WKWebView 只能通過 Safari 開發者工具調試&#xff0c;而這需要 Mac 環境和設備直連。為了彌補限制&#xff0c;社區出現了一個常用工具 —— …

煥新升級,Sermant 2.0.0 release版本重磅發布!

Sermant社區在6月底正式發布了2.0.0 release版本&#xff0c;這次更新中&#xff0c;Sermant進行了項目所屬組織調整并新增了基于xDS協議的服務發現能力、預過濾啟動加速機制、Sermant Backend的配置管理能力。所屬組織調整使得Sermant淡化廠商屬性&#xff0c;以全新的姿態更好…

sqli-labs通關筆記-第28a關GET字符注入(多重關鍵字過濾繞過 腳本法)

目錄 一、sqlmap之tamper腳本 二、源碼分析 1、代碼審計 2、SQL安全性分析 三、滲透實戰 1、進入靶場 2、tamper腳本 3、sqlmap滲透 SQLI-LABS 是一個專門為學習和練習 SQL 注入技術而設計的開源靶場環境&#xff0c;本小節對第28a關Less 28a基于GET字符型的SQL注入關卡…

聯想打印機2268w安裝

聯想打印機2268w是支持無線打印的。在某度搜索&#xff0c;掀起蓋子長按開機鍵&#xff0c;成功初始化。之后按說明應該能用手機搜索到打印機的熱點&#xff0c;反復搜索都沒有出現。最后沒辦法&#xff0c;之后好用我自己的方法安裝。找了個筆記本&#xff0c;開機連接到wifi,…

【LeetCode】動態規劃——72.編輯距離、10.正則表達式匹配

LeetCode題目鏈接 https://leetcode.cn/problems/edit-distance/description/ https://leetcode.cn/problems/regular-expression-matching/description/ 題解 72.編輯距離 本題要定義為長度為i、長度為j的字符串的最少編輯次數&#xff0c;每次判斷字符的下標為i-1、j-1。dp[i…

[親測可用]Android studio配置國內鏡像源 Kotlin DSL (build.gradle.kts)

一、更改gradle下載鏡像Android studio項目需要下載和更新 Gradle 及其依賴。由于網絡環境&#xff0c;直接從 Gradle 官網下載可能會遇到速度慢或超時的問題。這里需要更換為使用國內的鏡像站點來加速下載。官網地址&#xff08;較慢&#xff09;&#xff1a;https://services…

《跳出“技術堆砌”陷阱,構建可演進的軟件系統》

很多團隊陷入了“技術焦慮式開發”—盲目追逐熱門框架&#xff0c;將“使用微服務”“引入云原生”“集成AI組件”當作架構先進的標簽&#xff0c;卻忽視了業務與技術的底層匹配邏輯。某互聯網團隊為了“彰顯技術實力”&#xff0c;在內部協同工具中強行接入機器學習推薦模塊&a…

賦能你的應用:英超實時數據接入終極指南(API vs. WebSocket)

在當今數據驅動的時代&#xff0c;為您的應用程序注入實時、準確的英超賽事數據&#xff0c;是提升用戶體驗、打造差異化競爭力的關鍵。無論是開發一款球迷必備的比分追蹤App&#xff0c;一個深度專業的賽事分析平臺&#xff0c;還是一個充滿互動性的夢幻足球游戲&#xff0c;首…

計算機網絡:(poll、epoll)

一、select的不足1. 最大監聽數受限&#xff1a;FD_SETSIZE 默認 1024&#xff08;Linux&#xff09;2. 每次調用需重置 fd_set&#xff1a;內核會修改集合&#xff0c;必須每次重新 FD_SET3. 用戶態與內核態拷貝開銷大4. 返回后仍需遍歷所有 fd 才能知道哪個就緒5. 效率隨 fd …

網絡編程之設置端口復用

首先來說一下為什么要設置端口復用&#xff0c;有些時候在調試服務器代碼時勢必會經常啟動或結束服務器進程&#xff0c;這樣就會出現當再次啟動服務器時有可能會出現端口綁定失敗的情況&#xff0c;造成這個情況的原因是由于你上次關閉服務器時有連接尚未斷開等等其他原因&…

stargo縮擴容starrocks集群,實現節點服務器替換

1.背景在企業中可能需要&#xff0c;將starrocks的某一臺服務器下架&#xff0c;換上另一臺服務器&#xff0c;如何實現這個操作&#xff0c;本篇將進行介紹&#xff1b;節點hadoop101hadoop102hadoop103hadoop104集群原集群節點新節點fe???&#xff08;下線&#xff09;?&…

Linux -- 進程間通信【命名管道】

目錄 一、命名管道定義 二、命名管道創建 1、指令 2、系統調用 3、刪除 三、匿名管道和命名管道的區別 四、命名管道的打開規則 五、代碼示例 1、comm.hpp 2、server.cc 3、client.cc 一、命名管道定義 # 匿名管道存在以下核心限制&#xff1a; 僅限親緣關系進程&a…

LinuxC系統多線程程序設計

一.多線程程序設計1. 線程概述&#xff1a;1.1 什么是線程?線程是進程中的一個實體(組成單元),是系統進程調度的最小單元。一個進程至少具有一個線程&#xff0c;如果進程僅有一個線程&#xff0c;該線程就代表進程本身。把代表進程本身的線程稱為主線程&#xff0c;一個進程…

Vue3 + TS + MapboxGL.js 三維地圖開發項目

文章目錄 1. 安裝依賴 2. 新建 Map 組件(components/MapView.vue) 3. 在頁面中使用(views/Home.vue) 4. 效果說明 1. 安裝依賴 npm install mapbox-gl @types/mapbox-gl --save?? 注意:需要去 Mapbox 官網,申請一個 access token。 package.json {"name":…

【編程語言】Rust 入門

目錄 一、Rust 是什么&#xff1f;為什么選擇它&#xff1f; 二、環境搭建&#xff0c;邁出第一步 2.1 Windows 系統安裝步驟 2.2 macOS 系統安裝步驟 2.3 Linux 系統安裝步驟 2.4 安裝過程中的常見問題及解決方案 三、基礎語法&#xff0c;構建知識大廈的基石 3.1 變量…