H.265的NAL单元的解析、封装、传输和处理的C++源码实现

本文为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

史上最全的GB/T 28181介绍(国标28181资料大全)

H.264的NAL单元的解析、封装、传输和处理的源码实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值