H.264/H.265码流中SEI内容的解析方法与实现
引言
在视频编码领域,H.264/AVC和H.265/HEVC标准中的SEI(Supplemental Enhancement Information)承载着关键的元数据信息。本文将通过代码实例演示如何解析这些隐藏在码流中的重要数据。
一、SEI概述
1.1 SEI的作用
- 传输时间码、版权信息等元数据
- 携带编码器配置参数
- 传输用户自定义数据(如GPS坐标)
1.2 数据结构差异
标准 | NALU类型 | UUID长度 | 扩展机制 |
---|---|---|---|
H.264 | 6 | 16字节 | 不支持 |
H.265 | 39 | 16字节 | 支持分层结构 |
二、解析方法
2.1 技术路线
- 码流文件读取
- NALU单元分割
- SEI类型识别
- 负载解析
2.2 关键步骤
// FFmpeg解析NALU示例
AVCodecParserContext *parser = av_parser_init(AV_CODEC_ID_H264);
AVPacket *pkt = av_packet_alloc();
while (/* 读取数据 */) {
int ret = av_parser_parse2(parser, codec_ctx,
&pkt->data, &pkt->size,
data_ptr, data_size,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (parser->nal_unit_type == SEI_NUT) { // 识别SEI单元
parse_sei(pkt->data, pkt->size); // 自定义解析函数
}
}
三、代码实现
3.1 sei_packet.h
#ifndef _SEI_PACKET_HPP_
#define _SEI_PACKET_HPP_
#include <stdint.h>
#include <string>
constexpr uint32_t kUUIDSize = 16; // uuid size: 16 bytes
constexpr uint8_t kDebugEnable = 0; // debug print enable
constexpr uint32_t kSeiFrameMaxPos = 128;
constexpr uint32_t kSeiHeaderMaxSize = 8;
constexpr uint32_t kPayloadMaxSize = 64;
constexpr uint8_t kAnnexCodeLow[] = {0x00, 0x00, 0x01};
constexpr uint8_t kAnnexCode[] = {0x00, 0x00, 0x00, 0x01};
struct SEIContentInfo {
uint32_t channel_id;
uint16_t img_width;
uint16_t img_height;
uint32_t capture_sequence;
uint32_t encode_sequence;
uint64_t captureGlobalTimeStamp;
};
struct NaluHeaderH264 {
uint32_t forbidden_zero_bit : 1;
uint32_t nal_ref_idc : 2;
uint32_t nal_uint_type : 5;
};
struct NaluHeaderH265 {
uint32_t forbidden_zero_bit : 1;
uint32_t nal_uint_type : 6;
uint32_t nuh_layer_id : 6;
uint32_t nuh_temporal_id_plus1 : 3;
};
struct SEiHeader {
uint32_t header_len;
uint16_t sei_type;
uint16_t sei_size;
};
// payload = uuid + content
struct SEIPayloadInfo {
uint8_t uuid[kUUIDSize];
SEIContentInfo sei_content;
};
enum VideoCodec {
ENCODE_FMT_H264 = 0,
ENCODE_FMT_H265 = 1,
ENCODE_FMT_UNKNOWN = 2,
};
class SEIPacketHandler {
public:
// fill sei payload
int FillSeiPayload(uint8_t* payload, uint32_t payload_size, SEIContentInfo content);
void DecodeSeiContent(SEIContentInfo* pContent, uint8_t* content_buffer, const uint32_t content_size);
void DisplaySeiContent(uint32_t sei_size, int sei_type, uint8_t* sei_content);
bool IsDigitNum(std::string str);
bool ExtractSeiHeader(uint8_t* nalu_data, const uint32_t nalu_size, SEiHeader& sei_header);
uint32_t ExtractSeiBody(uint8_t* buf_in, const uint32_t buf_in_size, uint8_t* buf_out, uint32_t& buf_out_len);
void GetNextNaluTypeInfo(uint8_t* buf_in, uint32_t len, uint32_t size);
int32_t getSeiHeaderPosNew(uint8_t* buf);
VideoCodec DetectCodec(const uint8_t* data, size_t size);
void TrunOnDebug() {
debug_enable_ = true;
}
private:
bool debug_enable_{true};
VideoCodec encode_format_{ENCODE_FMT_UNKNOWN};
bool RemoveRedundancyCodes(uint8_t* ebsp_data, const uint32_t ebsp_size, uint8_t* rbsp_data, uint32_t& rbsp_size);
// check uuid
bool CheckSeiUuid(uint8_t* uuid, const uint16_t uuid_size);
};
#endif
3.2 sei_packet.cc
/******************************************************************************
* Copyright (c) 2022-2022 by ZEEKR. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* @Brief:
* @Author:
* @Date:
*****************************************************************************/
#include "sei_packet.h"
#include <stdio.h>
#include <string.h>
#include <iomanip>
#include <iostream>
#include <vector>
#define ZWARN std::cout
#define ZERROR std::cerr
#define SEI_NAL_UNIT_TYPE_H264 0x06
#define SEI_NAL_UNIT_TYPE_H265 0x4E
#define USER_DATA_UNREGISTER_TYPE 0x05
#define SEI_PAYLOAD_SIZE 0x28
// self UUID
static const uint8_t s_uuid[] = {0x54, 0x80, 0x83, 0x97, 0xf0, 0x23, 0x47, 0x4b,
0xb7, 0xf7, 0x4f, 0x32, 0xb5, 0x4e, 0x06, 0xac};
/**
* @brief Converts a NAL unit from EBSP (Encapsulated Byte Sequence Packets) to RBSP (Raw Byte Sequence Packets).
* This function removes emulation prevention bytes from the EBSP data and stores the result in the RBSP buffer.
* To address the issue of 0x000000 or 0x000001 in NALU, an anti contention mechanism has been introduced.
The specific implementation is as follows:
0x 00 00 00 ------> 0x00 00 03 00
0x 00 00 01 ------> 0x00 00 03 01
0x 00 00 02-------> 0x00 00 03 02 (0x000002 is kept as collateral and cannot be used temporarily)
0x 00 00 03-------->0x00 00 03 03 (to avoid conflicts in the original data)
So, before unpacking and sending the original NALU data to the decoder,
it is necessary to remove these inserted 03 bytes.
* @param ebsp_data Pointer to the input EBSP data.
* @param ebsp_size Size of the input EBSP data in bytes.
* @param rbsp_data Pointer to the output RBSP buffer.
* @param rbsp_size Size of the output RBSP buffer in bytes.
* @return int Returns 0 on success, or -1 if an error occurs (e.g., buffer overflow or invalid input sizes).
*/
bool SEIPacketHandler::RemoveRedundancyCodes(uint8_t *ebsp_data, const uint32_t ebsp_size, uint8_t *rbsp_data,
uint32_t &rbsp_size) {
if (ebsp_size < rbsp_size) {
return false; // ebsp_size should not be less than rbsp_size
}
uint8_t *ebsp = ebsp_data;
uint8_t *rbsp = rbsp_data;
uint32_t p = 0;
uint32_t cleaned_size = 0; // Length after removing redundancy codes
while (p < ebsp_size) {
if (ebsp_size - p > 2) {
if ((ebsp[0] == 0x00) && (ebsp[1] == 0x00) && (ebsp[2] == 0x03) && (ebsp[3] <= 0x03)) {
if (cleaned_size + 2 >= rbsp_size) {
return false; // buffer overflow
}
*rbsp++ = *ebsp++;
*rbsp++ = *ebsp++;
ebsp++;
p += 3;
cleaned_size += 2;
} else {
*rbsp++ = *ebsp++;
p++;
cleaned_size++;
}
} else {
*rbsp++ = *ebsp++;
p++;
cleaned_size++;
}
}
rbsp_size = cleaned_size;
if (true) {
ZWARN << "Before RemoveRedundancyCodes, ebsp_size:" << ebsp_size << std::endl;
for (uint32_t i = 0; i < ebsp_size; i++) {
std::cout << std::hex << (int)ebsp_data[i] << " ";
}
std::cout << std::dec << std::endl;
ZWARN << "After RemoveRedundancyCodes, rbsp_size:" << rbsp_size << std::endl;
for (uint32_t i = 0; i < rbsp_size; i++) {
std::cout << std::hex << (int)rbsp_data[i] << " ";
}
std::cout << std::endl;
}
return true;
}
// Display Sei Content
void SEIPacketHandler::DisplaySeiContent(uint32_t sei_size, int sei_type, uint8_t *sei_content) {
if (debug_enable_) {
ZWARN << "sei_type:" << sei_type << ", content_size:" << sei_size << ", content:(hex)";
for (uint32_t i = 0; i < sei_size; i++) {
ZWARN << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(sei_content[i]) << " ";
}
ZWARN << std::dec << std::endl; // Reset to decimal format
}
zdrive::platform::nvenc::SEIContentInfo content;
DecodeSeiContent(&content, sei_content, sei_size);
}
bool SEIPacketHandler::CheckSeiUuid(uint8_t *uuid, const uint16_t uuid_size) {
if (uuid == nullptr || uuid_size == 0 || uuid_size != kUUIDSize) {
return false;
}
return memcmp(uuid, s_uuid, kUUIDSize) == 0;
}
// 获取SEI内容:sei_type + sei_size + sei_content
bool SEIPacketHandler::ExtractSeiHeader(uint8_t *nalu_data, const uint32_t nalu_size, SEiHeader &sei_header) {
memset(&sei_header, 0, sizeof(SEiHeader));
// Initialize sei_size and sei_type
uint8_t *sei = nalu_data;
// Extract SEI payload type
do {
if (sei >= nalu_data + nalu_size) {
ZERROR << "Out of bounds while extracting SEI payload type";
return false;
}
sei_header.sei_type += *sei;
sei_header.header_len++;
} while (*sei++ == 255);
// Extract SEI payload size, actual length after removing redundancy codes
do {
if (sei >= nalu_data + nalu_size) {
ZERROR << "Out of bounds while extracting SEI payload size";
return false;
}
sei_header.sei_size += *sei;
sei_header.header_len++;
} while (*sei++ == 255);
// check sei_type and sei_size
if (sei_header.sei_type != USER_DATA_UNREGISTER_TYPE) {
ZERROR << "ExtractSeiHeader failed. sei_type: " << sei_header.sei_type << std::endl;
return false;
}
if (sei_header.sei_size != sizeof(SEIPayloadInfo)) {
ZERROR << "sei_len_no_redundancy is " << sei_header.sei_size << " not equal to sizeof(SEIPayloadInfo)"
<< std::endl;
return false;
}
ZWARN << "sei_type:" << sei_header.sei_type << ", sei_size:" << sei_header.sei_size << std::endl;
return true;
}
// NAL header + sei type + sei size + paylod (uuid + content);
// NAL header = 0x06 (SEI); sei type = 0x05 (user_data_unregistered)
// NAL header = 0x4E (SEI); sei type = 0x05 (user_data_unregistered)
/**
* @brief Fills the SEI payload with the provided content.
*
* This function populates the given payload buffer with a UUID followed by the SEI content.
*
* @param payload Pointer to the buffer where the SEI payload will be stored.
* @param payload_size Size of the payload buffer.
* @param content SEI content information to be included in the payload.
* @return int Returns the total size of the filled payload (UUID size + content size) on success,
* or -1 if there is an error (e.g., null payload, zero payload size, or insufficient buffer size).
*/
int SEIPacketHandler::FillSeiPayload(uint8_t *payload, uint32_t payload_size, SEIContentInfo content) {
if (nullptr == payload || payload_size == 0) {
ZWARN << "Invalid parameters: payload is null or payload_size is zero. payload:" << payload
<< " payload_size:" << payload_size;
return -1;
}
uint32_t content_size = sizeof(SEIContentInfo);
uint8_t *payload_buffer = reinterpret_cast<uint8_t *>(payload);
if (payload_size >= (kUUIDSize + content_size)) {
memcpy(payload_buffer, s_uuid, kUUIDSize); // uuid
memcpy(payload_buffer + kUUIDSize, &content, content_size); // content
} else {
ZWARN << "payload_size is too small, need at least:" << kUUIDSize + content_size;
return -1;
}
return (kUUIDSize + content_size);
}
/**
* @brief Decodes SEI (Supplemental Enhancement Information) content from a buffer.
*
* This function extracts various pieces of information from the provided SEI content buffer
* and populates the SEIContentInfo structure with the decoded values.
*
* @param pContent Pointer to the SEIContentInfo structure to be populated with decoded values.
* @param content_buffer Pointer to the buffer containing the SEI content data.
* @param content_size Size of the SEI content buffer in bytes.
*
* The function decodes the following information from the buffer:
* - Channel ID (4 bytes)
* - Image width and height (4 bytes, lower 16 bits for width, upper 16 bits for height)
* - Capture sequence number (4 bytes)
* - Encode sequence number (4 bytes)
* - Capture global timestamp (8 bytes, lower 32 bits followed by upper 32 bits)
*/
void SEIPacketHandler::DecodeSeiContent(SEIContentInfo *pContent, uint8_t *content_buffer,
const uint32_t content_size) {
if (debug_enable_) {
ZWARN << "content_size:" << content_size << ", content:(hex)";
// contest_size 多1byte的原因是多了一个停止位0x80:0b1000 0000
for (uint32_t i = 0; i < content_size; i++) {
ZWARN << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(content_buffer[i]) << " ";
}
ZWARN << std::dec << std::endl; // Reset to decimal format
}
int size = content_size;
// check uuid
if (!CheckSeiUuid(content_buffer, kUUIDSize)) {
std::cout << "CheckSeiUuid failed" << std::endl;
return;
}
uint8_t *data = content_buffer + kUUIDSize;
auto extract_data = [&](uint32_t &field, int bytes) -> bool {
if (size >= bytes) {
field = 0;
for (int i = 0; i < bytes; ++i) {
field |= data[i] << (8 * i);
}
size -= bytes;
data += bytes;
return true;
} else {
ZERROR << "Insufficient data for field requiring " << bytes << " bytes";
return false;
}
};
if (!extract_data(pContent->channel_id, 4))
return;
if (!extract_data(reinterpret_cast<uint32_t &>(pContent->img_width), 2))
return;
if (!extract_data(reinterpret_cast<uint32_t &>(pContent->img_height), 2))
return;
if (!extract_data(pContent->capture_sequence, 4))
return;
if (!extract_data(pContent->encode_sequence, 4))
return;
uint32_t timeStamp_low32 = 0;
uint32_t timeStamp_high32 = 0;
if (!extract_data(timeStamp_low32, 4))
return;
if (!extract_data(timeStamp_high32, 4))
return;
pContent->captureGlobalTimeStamp = ((uint64_t)timeStamp_high32 << 32) | (uint64_t)timeStamp_low32;
if (debug_enable_) {
ZWARN << "channel_id:" << pContent->channel_id << " width:" << pContent->img_width
<< " height:" << pContent->img_height << " cap_seq:" << pContent->capture_sequence
<< " enc_seq:" << pContent->encode_sequence << " timestamp:" << pContent->captureGlobalTimeStamp;
}
}
int32_t SEIPacketHandler::getSeiHeaderPosNew(uint8_t *buf) {
uint32_t pos = 0;
// Check for start code
if (memcmp(buf + pos, kAnnexCode, sizeof(kAnnexCode)) == 0) {
pos += sizeof(kAnnexCode);
} else if (memcmp(buf + pos, kAnnexCodeLow, sizeof(kAnnexCodeLow)) == 0) {
pos += sizeof(kAnnexCodeLow);
} else {
return -1;
}
// Check for SEI NAL unit type
if (buf[pos] == SEI_NAL_UNIT_TYPE_H264) { // H264 NALU
encode_format_ = ENCODE_FMT_H264;
if (buf[pos + 1] != USER_DATA_UNREGISTER_TYPE || buf[pos + 2] != SEI_PAYLOAD_SIZE) {
return -1;
}
return pos + 1; // H264: 06 05 28
} else if (buf[pos] == SEI_NAL_UNIT_TYPE_H265) { // H265 NALU
encode_format_ = ENCODE_FMT_H265;
if (buf[pos + 1] != 0x01 || buf[pos + 2] != USER_DATA_UNREGISTER_TYPE || buf[pos + 3] != SEI_PAYLOAD_SIZE) {
return -1;
}
return pos + 2; // H265: 4E 01 05 28
}
return -1; // start code but not sei unit
}
/**
* @brief Extracts the SEI frame size from the input buffer and processes it.
*
* This function calculates the size of the SEI frame from the input buffer, removes redundancy codes,
* and stores the processed data in the output buffer. It also prints the frame type if debugging is enabled.
*
* @param buf_in Pointer to the input buffer containing the SEI data.
* @param buf_in_size Size of the input buffer.
* @param buf_out Pointer to the output buffer where the processed SEI data will be stored.
* @param buf_out_len Reference to the size of the output buffer.
* @return The length of the SEI frame including the UUID and redundancy codes, or 0 if an error occurs.
*/
uint32_t SEIPacketHandler::ExtractSeiBody(uint8_t *buf_in, const uint32_t buf_in_size, uint8_t *buf_out,
uint32_t &buf_out_len) {
uint32_t sei_raw_len = 0; // SEI length (including UUID and redundancy code)
if (buf_in == nullptr || buf_out == nullptr) {
return sei_raw_len; // Invalid input buffer
}
// Calculate the length from SEI to the next NALU start code
while (sei_raw_len < (buf_in_size - sizeof(SEIPayloadInfo))) {
if (*(uint8_t *)(buf_in + sei_raw_len) == 0x00 && *(uint8_t *)(buf_in + sei_raw_len + 1) == 0x00 &&
*(uint8_t *)(buf_in + sei_raw_len + 2) == 0x00 && *(uint8_t *)(buf_in + sei_raw_len + 3) == 0x01) {
break;
}
sei_raw_len++;
}
// If the SEI length is smaller than the UUID length, it is not our SEI
if (sei_raw_len < kUUIDSize) {
ZWARN << "SEI length " << sei_raw_len << " is too small";
return sei_raw_len;
}
// Remove redundancy codes from the content after the UUID and store it in buf_out
buf_out_len = sei_raw_len;
if (!RemoveRedundancyCodes(buf_in, sei_raw_len, buf_out, buf_out_len)) {
ZERROR << "RemoveRedundancyCodes failed";
return sei_raw_len;
}
// Print frame type if debugging is enabled
if (debug_enable_) {
GetNextNaluTypeInfo(buf_in, sei_raw_len, buf_in_size);
std::cout << "SEI length:" << sei_raw_len << std::endl;
}
return sei_raw_len;
}
void SEIPacketHandler::GetNextNaluTypeInfo(uint8_t *buf_in, uint32_t sei_len, uint32_t buf_in_size) {
// 解析下一个nalu头,判断其类型
uint32_t pos = 0;
uint8_t *buf = buf_in + sei_len;
std::cout << std::dec;
while (pos < (buf_in_size - 4)) {
// find start code
if (*(uint8_t *)(buf + pos) == 0x00 && *(uint8_t *)(buf + pos + 1) == 0x00 &&
*(uint8_t *)(buf + pos + 2) == 0x00 && *(uint8_t *)(buf + pos + 3) == 0x01) {
break;
}
pos++;
}
static uint32_t frame_num = 0;
if (*(uint32_t *)(buf + pos + 1) == 0x26010000 || *(uint32_t *)(buf + pos + 1) == 0x65010000) {
std::cout << "IDR Frame:" << frame_num++ << " ";
} else if (*(uint32_t *)(buf + pos + 1) == 0x2a010000) {
std::cout << "I Frame:" << frame_num++ << " ";
} else if (*(uint32_t *)(buf + pos + 1) == 0x02010000 || *(uint32_t *)(buf + pos + 1) == 0x61010000) {
std::cout << "P Frame:" << frame_num++ << " ";
} else {
std::cout << "not find " << std::hex << *(uint32_t *)(buf + pos + 1) << ":" << frame_num++ << " ";
}
}
VideoCodec SEIPacketHandler::DetectCodec(const uint8_t *data, size_t size) {
VideoCodec codec = ENCODE_FMT_UNKNOWN;
if (size < 4) {
return codec; // 数据太少,无法判断
}
uint32_t pos = 0;
while (codec == ENCODE_FMT_UNKNOWN && pos < size - 4) {
while (pos < size - 4) {
// Check for start code
if (memcmp(data + pos, kAnnexCode, sizeof(kAnnexCode)) == 0) {
pos += sizeof(kAnnexCode);
break;
} else if (memcmp(data + pos, kAnnexCodeLow, sizeof(kAnnexCodeLow)) == 0) {
pos += sizeof(kAnnexCodeLow);
break;
} else {
pos++;
}
}
// Check for SEI NAL unit type
if (data[pos] == SEI_NAL_UNIT_TYPE_H264) { // H264 NALU
codec = ENCODE_FMT_H264;
} else if (data[pos] == SEI_NAL_UNIT_TYPE_H265) { // H265 NALU
codec = ENCODE_FMT_H265;
} else {
pos++;
}
}
if (encode_format_ == ENCODE_FMT_UNKNOWN) {
encode_format_ = codec;
} else if (encode_format_ != codec) {
ZERROR << "encode format not match";
return ENCODE_FMT_UNKNOWN;
}
return codec; // 未能判断
}
bool SEIPacketHandler::IsDigitNum(std::string str) {
if (str.empty()) {
return false;
}
for (char c : str) {
if (!isdigit(c)) {
return false;
}
}
int num = stoi(str);
if (num < 1 || num > 1000) {
return false;
}
return true;
}
3.3 main.cc
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
#include "sei_packet.h"
#include "string.h"
#define BUF_SIZE_MAX (1024 * 1024 * 256)
#define LOG_WARN printf
int main(int argc, char *argv[])
{
// if (argc < 2) {
// LOG_WARN("encode file not exist\n");
// return -1;
// }
std::string file1 = "camera_surround_front_image.h265";
std::string file2 = "driver_camera_front_long_image_1920_1024.h264";
int fd = open(file1.c_str(), O_RDONLY);
if (fd < 0) {
LOG_WARN("open enc file failed\n");
return -1;
}
uint32_t size = lseek(fd, 0, SEEK_END);
if (size > BUF_SIZE_MAX) {
LOG_WARN("enc file %u is too big\n", size);
exit(-1);
} else {
LOG_WARN("enc file %u\n", size);
}
lseek(fd, 0, SEEK_SET);
uint8_t *buf = (uint8_t *)malloc(size);
memset(buf, 0, size);
read(fd, buf, size);
uint32_t pos = 0;
SEIPacketHandler sei_handler;
// sei_handler.TrunOnDebug();
auto enc_fmt = sei_handler.DetectCodec(buf, size);
std::cout << "enc_fmt: " << enc_fmt << std::endl;
while (pos < (size - sizeof(SEIPayloadInfo))) {
int32_t sei_header_offset = sei_handler.getSeiHeaderPosNew(buf + pos); // 获取到sei的pos
if (sei_header_offset < 0) {
pos++;
continue;
}
pos += sei_header_offset;
SEiHeader sei_header;
// sei_header_len 为原始码流中sei bytes: sei type + sei size offset bytes
if (!sei_handler.ExtractSeiHeader(buf + pos, size - pos, sei_header)) {
std::cout << "ExtractSeiHeader failed" << std::endl;
pos++;
continue;
}
pos += sei_header.header_len; // skip sei type and sei size
// buf_out is the content of sei,not include uuid,not include redundancy codes
uint8_t buf_out[kPayloadMaxSize] = {0};
uint32_t buf_out_size = 0;
// sei_payload_size is the size of sei content, include uuid, include redundancy codes
uint32_t sei_payload_size = sei_handler.ExtractSeiBody(buf + pos, size - pos, buf_out, buf_out_size);
if (sei_payload_size < sei_header.sei_size) {
pos++;
std::cout << "ExtractSeiBody failed";
continue;
}
SEIContentInfo content_info;
sei_handler.DecodeSeiContent(&content_info, buf_out, buf_out_size);
pos = pos + sei_header_offset + sei_payload_size;
// std::cout << "pos: " << std::dec << pos << " sei_header_offset: " << sei_header_offset
// << " sei_payload_size :" << sei_payload_size << std::endl;
}
close(fd);
free(buf);
return 0;
}
3.2 运行结果
# 编译器
CXX = g++
# 编译器选项
CXXFLAGS = -std=c++11 -Wall -O0 -g
# 目标文件
TARGET = sei_parser
# 源文件
SRCS = sei_parser.cc sei_packet.cc
# 头文件
HEADERS = sei_packet.h
# 生成的对象文件
OBJS = $(SRCS:.cc=.o)
# 默认目标
all: $(TARGET) clean_objs
# 链接目标文件
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) -o $@ $^
# 编译源文件
%.o: %.cc $(HEADERS)
$(CXX) $(CXXFLAGS) -c $< -o $@
# 清理生成的文件
clean:
rm -f $(TARGET) $(OBJS)
# 清理对象文件
clean_objs:
rm -f $(OBJS)
.PHONY: all clean clean_objs
3.2 运行结果
xxxx:~/data/my_code/sei_parse$ make
g++ -std=c++11 -Wall -O0 -g -c sei_parser.cc -o sei_parser.o
g++ -std=c++11 -Wall -O0 -g -c sei_packet.cc -o sei_packet.o
g++ -std=c++11 -Wall -O0 -g -o sei_parser sei_parser.o sei_packet.o
rm -f sei_parser.o sei_packet.o
四、应用场景
- 视频编辑:通过时间码SEI实现多机位同步
- 流媒体传输:解析dynamic_hdr SEI进行实时画质调整
- 版权保护:提取数字水印SEI进行内容溯源
五、总结
本文展示了使用FFmpeg解析SEI的完整流程,关键点在于:
- 正确识别NALU类型
- 处理变长编码的type/length字段
- 区分不同版本标准的差异