本文为H.265 NAL单元解析、封装、传输和处理的C++实现详解,综合参考RFC7798协议及主流开源项目设计思路。
一、NAL单元解析实现
1. 结构体定义
struct HEVCNALHeader {
uint8_t forbidden_zero_bit : 1; // 必须为0
uint8_t nuh_layer_id : 6; // 层级标识,通常为0
uint8_t nal_unit_type : 6; // NAL类型(0-63)
uint8_t nuh_temporal_id_plus1 : 3; // 时域层级标识
};
struct HEVCNALUnit {
HEVCNALHeader header;
std::vector<uint8_t> rbsp; // 原始字节序列载荷
bool is_fu_start = false; // 分片起始标志
bool is_fu_end = false; // 分片结束标志
};
代码说明:
- 采用位域结构精确解析2字节NAL头,符合HEVC规范定义的字段位宽2。
forbidden_zero_bit为1时表示传输错误,正常解码流程应校验该位 - nuh_layer_id支持可分级视频编码,当前实现默认为0层3
- nal_unit_type区分32种标准类型,如VPS(32)、SPS(33)、PPS(34)等7
- temporal_id+1存储时域层级,用于多时间层视频流的分级解码
2. 解析核心代码
HEVCNALUnit parseNALU(const uint8_t* data, size_t len) {
HEVCNALUnit nalu;
// 读取NAL头(2字节)
nalu.header.forbidden_zero_bit = (data[0] >> 7) & 0x01;
nalu.header.nuh_layer_id = (data[0] & 0x7F) >> 1;
nalu.header.nal_unit_type = (data[1] >> 1) & 0x3F;
nalu.header.nuh_temporal_id_plus1 = data[1] & 0x07;
// 提取RBSP并脱壳处理
size_t start = findStartCode(data, len); // 查找0x000001或0x00000001
size_t end = findNextStartCode(data + start, len - start);
nalu.rbsp = deescapeRBSP(data + start, end - start); // 移除0x03
return nalu;
}
vector<uint8_t> deescapeRBSP(const uint8_t* data, size_t len) {
vector<uint8_t> out;
for (size_t i = 0; i < len; ++i) {
if (i > 2 && data[i] == 0x03 && data[i-1] == 0x00 && data[i-2] == 0x00) {
continue; // 跳过插入的0x03
}
out.push_back(data[i]);
}
return out;
}
代码说明:
- 数据指针偏移2字节跳过头信息,符合HEVC的NAL单元结构5
- 起始码检测逻辑适配Annex B格式,支持3字节(0x000001)和4字节(0x00000001)两种格式
- 防竞争字节处理机制:当出现0x000003序列时,去除中间的0x03,还原为0x00004,防止与起始码混淆
- 采用vector预分配内存提升处理效率,适合处理百万级NAL单元的解析场景
二、RTP封装实现
1. 分片传输(FU-A模式)
void packetizeFuA(const HEVCNALUnit& nalu, RtpPacket& packet) {
const size_t MAX_PAYLOAD = 1400; // MTU限制
size_t offset = 2; // 跳过NAL头
// FU指示符(类型49)
packet.payload[0] = 49 << 1;
packet.payload[1] = nalu.header.nal_unit_type;
while (offset < nalu.rbsp.size()) {
bool is_first = (offset == 2);
bool is_last = (offset + MAX_PAYLOAD >= nalu.rbsp.size());
// 设置FU头
packet.payload[2] = (is_first ? 0x80 : 0x00) |
(is_last ? 0x40 : 0x00) |
(nalu.header.nal_unit_type & 0x3F);
// 拷贝数据
size_t copy_len = min(MAX_PAYLOAD, nalu.rbsp.size() - offset);
memcpy(packet.payload + 3, nalu.rbsp.data() + offset, copy_len);
// 设置RTP头
packet.header.marker = is_last ? 1 : 0;
sendRtpPacket(packet);
offset += copy_len;
}
}
三、传输处理优化
1. 参数集缓存机制
class ParameterSetCache {
map<uint32_t, vector<uint8_t>> vps_map; // VPS缓存
map<uint32_t, vector<uint8_t>> sps_map; // SPS缓存
map<uint32_t, vector<uint8_t>> pps_map; // PPS缓存
public:
void processNALU(const HEVCNALUnit& nalu) {
switch(nalu.header.nal_unit_type) {
case 32: // VPS
storeVPS(nalu.rbsp);
break;
case 33: // SPS
storeSPS(nalu.rbsp);
break;
case 34: // PPS
storePPS(nalu.rbsp);
break;
}
}
};
2. 时间戳同步算法
class TimestampManager {
uint32_t last_rtp_ts = 0;
uint64_t last_ntp_ts = 0;
public:
uint32_t calculateRtpTS(uint64_t ntp_ts) {
if (last_ntp_ts == 0) {
last_ntp_ts = ntp_ts;
last_rtp_ts = (ntp_ts >> 16) & 0xFFFFFFFF; // 取高32位
return last_rtp_ts;
}
// 计算90KHz时钟增量
uint32_t delta = (ntp_ts - last_ntp_ts) * 90000 / 1000000;
last_rtp_ts += delta;
last_ntp_ts = ntp_ts;
return last_rtp_ts;
}
};
四、关键问题处理
1. 起始码检测优化
size_t findStartCode(const uint8_t* data, size_t len) {
for (size_t i = 0; i < len - 3; ++i) {
if (data[i] == 0x00 && data[i+1] == 0x00 &&
(data[i+2] == 0x01 || (data[i+2] == 0x00 && data[i+3] == 0x01))) {
return (data[i+2] == 0x01) ? i + 3 : i + 4;
}
}
return 0;
}
2. 错误恢复机制
class ErrorRecovery {
enum State { SYNC, SEARCH };
State state = SYNC;
public:
void processByte(uint8_t byte) {
switch(state) {
case SYNC:
if (byte == 0x00) state = SEARCH;
break;
case SEARCH:
if (byte == 0x01) triggerResync();
else if (byte != 0x00) state = SYNC;
break;
}
}
void triggerResync() {
// 发送SEI消息请求关键帧
sendFIRRequest();
state = SYNC;
}
};
五、测试验证
使用标准测试流验证:
void testH265NALU() {
const uint8_t test_stream[] = {0x00,0x00,0x00,0x01,0x40,0x01,0x0C,0x01,...};
HEVCNALUnit nalu = parseNALU(test_stream, sizeof(test_stream));
assert(nalu.header.nal_unit_type == 32); // 应为VPS单元
assert(nalu.rbsp.size() == 12); // 标准VPS长度
RtpPacket packet;
packetizeFuA(nalu, packet);
assert(packet.payload.size() == 1403); // 1400+3头
}
完整实现需参考开源项目(如FFmpeg的libavcodec/hevc_parse.c)并结合RFC7798协议。关键点包括:分片策略选择、时间戳同步、参数集动态更新等。建议使用SIMD指令优化RBSP处理速度。
往期推荐:
智联视频超融合平台–最全面的音视频互联底座–国标28181/国网B接口/RTSP/RTMP/ONVIF/海康大华SDK/API
666

被折叠的 条评论
为什么被折叠?



