什么是SEI?
在 H.264 視頻編碼標準中,補充增強信息(Supplemental Enhancement Information,SEI) 是一種特殊的 NAL(網絡抽象層)單元。它不像序列參數集(SPS)或圖像參數集(PPS)那樣直接影響解碼過程,而是提供輔助性、非強制性的信息。
SEI 可以視為視頻數據中的“便簽”或“元數據”,這些信息可以被解碼器或應用程序用來增強視頻體驗,但即使它們丟失了,解碼器仍然可以正常解碼和播放視頻。SEI 單元的引入,讓 H.264 碼流在傳輸視頻數據本身的同時,還能靈活地攜帶各種附加信息。
SEI的特點
SEI 的主要作用是為視頻流提供額外的信息:
- 計時和同步: SEI 可以包含精確的計時信息,幫助解碼器同步音頻和視頻,確保播放流暢。例如,
pic_timing
SEI 消息可以提供每一幀的顯示時間。 - 錯誤隱藏: 當視頻流在網絡傳輸中發生丟包時,SEI 消息可以提供錯誤隱藏所需的信息,幫助解碼器更好地處理損壞的圖像。例如,
recovery_point
SEI 消息可以指示一個可以安全恢復解碼的同步點。 - 顯示與渲染: SEI 消息可以提供關于如何正確顯示視頻的信息。例如,它可以包含色彩空間、亮度范圍等元數據,確保視頻在不同設備上都能有正確的顯示效果。
- 用戶自定義數據: SEI 單元可以攜帶用戶自定義的私有數據,這為開發者在視頻流中嵌入自己的信息提供了便利。例如,它可以用來攜帶版權信息、相機設置或者其他任何應用程序所需的數據。
SEI結構體
// H.264 SEI 消息結構
typedef struct H264SEI {int payloadType; // SEI 類型 (0,1,5等)int payloadSize; // SEI 數據大小(字節數)union {// (payloadType == 0) HRD 緩沖控制struct {int cpb_removal_delay; // CPB 移除延時int dpb_output_delay; // DPB 輸出延時} buffering_period;// (payloadType == 1) 圖像時間戳struct {int cpb_removal_delay; // CPB 移除延時int dpb_output_delay; // DPB 輸出延時int pic_struct; // 圖像結構 (0=幀,1=頂場,2=底場,3=頂+底場, etc.)int clock_timestamp_flag;// 是否有時鐘時間戳int ct_type; // 時鐘類型int nuit_field_based_flag;// 時間戳是否基于場int counting_type; // 計數類型int full_timestamp_flag; // 是否輸出完整時間戳int seconds, minutes, hours; // 時間信息} pic_timing;// (payloadType == 5) 用戶自定義數據struct {uint8_t uuid[16]; // 唯一標識符 (UUID)std::vector<uint8_t> user_data; // 自定義數據} user_data_unregistered;// (payloadType == 6) 錯誤恢復點struct {int recovery_frame_cnt; // 距離下一個可解碼幀的間隔} recovery_point;// 其他類型可繼續擴展...};
} H264SEI;
payloadType
payloadType | 名稱 | 說明 |
---|---|---|
0 | buffering_period | HRD 緩沖控制參數 |
1 | pic_timing | 圖像時間戳信息(CPB/DPB/顯示時間戳) |
2 | pan_scan_rect | 平移掃描窗口 |
3 | filler_payload | 填充比特流(碼率控制) |
4 | user_data_registered_itu_t_t35 | 用戶數據(ITU-T T.35 標準注冊) |
5 | user_data_unregistered | 用戶自定義數據(常用于水印、UUID 信息) |
6 | recovery_point | 錯誤恢復點 |
9 | scene_info | 場景切換信息 |
45 | frame_packing_arrangement | 3D 視頻左右眼幀排列 |
參數說明
-
payloadType / payloadSize
-
payloadType:SEI 類型,決定后續解析方式。
-
payloadSize:SEI 消息的字節長度。
-
-
Buffering period(payloadType = 0)
-
cpb_removal_delay:表示解碼時間戳(DTS)相對于 CPB 的移除延時。
-
dpb_output_delay:表示顯示時間戳(PTS)相對于解碼的延時。
-
-
Picture timing(payloadType = 1)
-
pic_struct:指示圖像顯示方式
- 0 = 幀
- 1 = 頂場
- 2 = 底場
- 3 = 頂場+底場(逐行交錯)
-
clock_timestamp_flag:是否包含時鐘時間戳。
-
seconds/minutes/hours:可選的顯示時間信息。
-
-
User data unregistered(payloadType = 5)
-
uuid:16 字節的唯一標識符,區分不同廠商/應用。
-
user_data:實際攜帶的數據(比如字幕、元數據、OSD 等)。
-
-
Recovery point(payloadType = 6)
- recovery_frame_cnt:表示從當前幀起,多少幀之后可恢復到無誤碼解碼。
示例(c++)
#include <cstdint>
#include <vector>
#include <string>
#include <cstring>
#include <iostream>// ========================
// H.264 SEI 結構體定義
// ========================
struct H264SEI {int payloadType; // SEI 類型int payloadSize; // 數據大小// 不同類型的 SEI 數據struct BufferingPeriod {int cpb_removal_delay;int dpb_output_delay;};struct PicTiming {int cpb_removal_delay;int dpb_output_delay;int pic_struct;bool clock_timestamp_flag;int ct_type;int nuit_field_based_flag;int counting_type;bool full_timestamp_flag;int seconds, minutes, hours;};struct UserDataUnregistered {uint8_t uuid[16];std::vector<uint8_t> user_data;};struct RecoveryPoint {int recovery_frame_cnt;};// union 方式存儲不同 payloadBufferingPeriod buffering_period;PicTiming pic_timing;UserDataUnregistered user_data_unregistered;RecoveryPoint recovery_point;
};// ========================
// 工具類:BitReader
// ========================
class BitReader {
public:BitReader(const uint8_t* data, int size) : data_(data), size_(size), bit_pos_(0) {}uint32_t read_bits(int n) {uint32_t val = 0;for (int i = 0; i < n; i++) {val <<= 1;val |= read_bit();}return val;}uint32_t read_bit() {if (bit_pos_ >= size_ * 8) return 0;uint32_t val = (data_[bit_pos_ / 8] >> (7 - (bit_pos_ % 8))) & 0x01;bit_pos_++;return val;}uint32_t read_ue() { // 無符號 Exp-Golombint zeros = 0;while (read_bit() == 0 && bit_pos_ < size_ * 8) zeros++;uint32_t value = (1 << zeros) - 1 + read_bits(zeros);return value;}int32_t read_se() { // 有符號 Exp-Golombuint32_t ueVal = read_ue();int32_t val = (ueVal & 1) ? (int32_t)((ueVal + 1) >> 1) : -(int32_t)(ueVal >> 1);return val;}private:const uint8_t* data_;int size_;int bit_pos_;
};// ========================
// H.264 SEI 解析器
// ========================
class H264SEIParser {
public:static std::vector<H264SEI> parse(const uint8_t* data, int size) {std::vector<H264SEI> seis;int offset = 0;while (offset < size) {// payloadTypeint payloadType = 0;while (offset < size && data[offset] == 0xFF) {payloadType += 255;offset++;}if (offset < size) payloadType += data[offset++];// payloadSizeint payloadSize = 0;while (offset < size && data[offset] == 0xFF) {payloadSize += 255;offset++;}if (offset < size) payloadSize += data[offset++];if (offset + payloadSize > size) break;H264SEI sei;sei.payloadType = payloadType;sei.payloadSize = payloadSize;const uint8_t* payload = data + offset;switch (payloadType) {case 0: // buffering_periodparse_buffering_period(payload, payloadSize, sei);break;case 1: // pic_timingparse_pic_timing(payload, payloadSize, sei);break;case 5: // user_data_unregisteredparse_user_data_unregistered(payload, payloadSize, sei);break;case 6: // recovery_pointparse_recovery_point(payload, payloadSize, sei);break;default:// 未實現的 SEI 類型break;}seis.push_back(sei);offset += payloadSize;}return seis;}private:static void parse_buffering_period(const uint8_t* payload, int size, H264SEI& sei) {BitReader br(payload, size);sei.buffering_period.cpb_removal_delay = br.read_ue();sei.buffering_period.dpb_output_delay = br.read_ue();}static void parse_pic_timing(const uint8_t* payload, int size, H264SEI& sei) {BitReader br(payload, size);sei.pic_timing.cpb_removal_delay = br.read_ue();sei.pic_timing.dpb_output_delay = br.read_ue();sei.pic_timing.pic_struct = br.read_bits(4);sei.pic_timing.clock_timestamp_flag = br.read_bit();if (sei.pic_timing.clock_timestamp_flag) {sei.pic_timing.ct_type = br.read_bits(2);sei.pic_timing.nuit_field_based_flag = br.read_bit();sei.pic_timing.counting_type = br.read_bits(5);sei.pic_timing.full_timestamp_flag = br.read_bit();sei.pic_timing.seconds = br.read_bits(6);sei.pic_timing.minutes = br.read_bits(6);sei.pic_timing.hours = br.read_bits(5);}}static void parse_user_data_unregistered(const uint8_t* payload, int size, H264SEI& sei) {if (size < 16) return;memcpy(sei.user_data_unregistered.uuid, payload, 16);sei.user_data_unregistered.user_data.assign(payload + 16, payload + size);}static void parse_recovery_point(const uint8_t* payload, int size, H264SEI& sei) {BitReader br(payload, size);sei.recovery_point.recovery_frame_cnt = br.read_ue();}
};// ========================
// 示例 main()
// ========================
int main() {// 模擬一個 SEI NALU(例子:user_data_unregistered)uint8_t sei_nalu[] = {0x05, 0x20, // payloadType=5, payloadSize=32// UUID (16 bytes)0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xAA,0xBB,0xCC,0xDD,0xEE,0xFF,0x00,// User Data (16 bytes)'T','E','S','T','_','S','E','I','_','D','A','T','A','!','!','!'};auto seis = H264SEIParser::parse(sei_nalu, sizeof(sei_nalu));for (auto& sei : seis) {std::cout << "SEI Type: " << sei.payloadType<< " Size: " << sei.payloadSize << std::endl;if (sei.payloadType == 5) {std::cout << "UUID: ";for (int i = 0; i < 16; i++) printf("%02X", sei.user_data_unregistered.uuid[i]);std::cout << std::endl;std::string str(sei.user_data_unregistered.user_data.begin(),sei.user_data_unregistered.user_data.end());std::cout << "User Data: " << str << std::endl;}}return 0;
}