接收解析封装H264为PS数据的RTP包

基于RFC3984和GB28181标准的H264 over RTP/PS封装解析实现


一、RTP包解析流程

sequenceDiagram
    participant Receiver as 接收端
    participant Parser as RTP解析器
    participant PSParser as PS解析器
    participant Decoder as H264解码器

    Receiver->>Parser: 接收RTP包
    Parser->>Parser: 解析RTP头部
    Parser->>Parser: 检查负载类型(PT=96/98)
    Parser->>PSParser: 提取RTP负载(PS数据)
    PSParser->>PSParser: 解析PS包头
    PSParser->>PSParser: 解析系统头
    PSParser->>PSParser: 解析节目映射表
    PSParser->>PSParser: 提取PES包
    PSParser->>Decoder: 重组H264 NALU

二、关键数据结构定义

1. RTP头部结构

typedef struct {
    uint8_t version_p_x_cc;    // V=2,P=0,X=0,CC=0
    uint8_t m_payload_type;    // M=0, PT=96(PS)/98(H264)
    uint16_t sequence_number;  // 16位序列号
    uint32_t timestamp;        // 32位时间戳(90kHz)
    uint32_t ssrc;             // 同步源标识
} RtpHeader;

2. PS包头结构

typedef struct {
    uint8_t start_code[4];     // 0x000001BA
    uint16_t system_clock_ref_base; // 系统时钟基准
    uint8_t stuffing_length;   // 填充长度(3位)
    // 其他字段省略
} PsHeader;

三、核心处理流程

1. RTP包接收与解析

void process_rtp_packet(uint8_t *buffer, int len) {
    RtpHeader *rtp_hdr = (RtpHeader*)buffer;
    
    // 验证RTP头部
    if(rtp_hdr->version_p_x_cc != 0x80) return; // 非标准RTP
    
    // 提取PS负载
    uint8_t *ps_payload = buffer + 12; // 跳过12字节RTP头
    int ps_len = len - 12;
    
    // 处理PS数据
    parse_ps_stream(ps_payload, ps_len);
}

2. PS流解析与重组

void parse_ps_stream(uint8_t *data, int len) {
    while(len >= 4) {
        if(memcmp(data, "\x00\x00\x01\xBA", 4) == 0) {
            // 解析PS头
            PsHeader *ps_hdr = (PsHeader*)data;
            data += sizeof(PsHeader);
            len -= sizeof(PesHeader);
            
            // 处理系统头
            parse_system_header(data);
            
            // 处理节目映射表
            parse_program_map_table(data);
        } else if(memcmp(data, "\x00\x00\x01\xBB", 4) == 0) {
            // 跳过系统标题
            data += 12;
            len -= 12;
        } else {
            // 处理PES包
            process_pes_packet(data, len);
            break;
        }
    }
}

3. H264 NALU重组

void process_pes_packet(uint8_t *data, int len) {
    PESHeader *pes_hdr = (PESHeader*)data;
    
    if(pes_hdr->stream_id == 0xE0) { // 视频流
        uint8_t *nal_data = data + sizeof(PESHeader);
        int nal_len = len - sizeof(PESHeader);
        
        // 处理H264 NALU
        while(nal_len > 4) {
            if(memcmp(nal_data, "\x00\x00\x00\x01", 4) == 0) {
                // 提取NALU头
                uint8_t nalu_hdr = nal_data[4];
                uint8_t nalu_type = nalu_hdr & 0x1F;
                
                // 处理分片
                if(nalu_type == 28) { // FU-A
                    handle_fu_a_fragment(nal_data);
                } else {
                    // 完整NALU处理
                    process_nalu(nal_data + 5, nal_len - 5);
                }
            }
            nal_data++;
            nal_len--;
        }
    }
}

四、关键算法实现

1. FU-A分片重组

typedef struct {
    uint8_t nalu_type;
    uint8_t nri;
    bool start_bit;
    bool end_bit;
    std::vector<uint8_t> payload;
} FuFragment;

void handle_fu_a_fragment(uint8_t *data) {
    FuFragment frag;
    frag.nalu_type = (data[0] >> 3) & 0x1F;
    frag.nri = (data[0] >> 1) & 0x03;
    frag.start_bit = (data[1] & 0x80) ? true : false;
    frag.end_bit = (data[1] & 0x40) ? true : false;
    
    if(frag.start_bit) {
        current_nalu = frag;
    } else {
        current_nalu.payload.insert(current_nalu.payload.end(), data+2, data+1400);
    }
    
    if(frag.end_bit) {
        // 完成重组
        process_nalu(current_nalu.payload.data(), current_nalu.payload.size());
        current_nalu = {};
    }
}

2. NALU头解析

void parse_nalu_header(uint8_t hdr) {
    uint8_t forbidden_zero_bit = (hdr >> 7) & 0x01;
    uint8_t nri = (hdr >> 5) & 0x03;
    uint8_t nalu_type = hdr & 0x1F;
    
    // 类型判断
    switch(nalu_type) {
        case 7:  // SPS
        case 8:  // PPS
        case 5:  // IDR
        case 1:  // 非IDR
            process_nalu_data(hdr, ...);
            break;
        default:
            // 处理其他类型
            break;
    }
}

五、时间戳同步机制

uint32_t last_video_ts = 0;
uint32_t audio_ts = 0;

void sync_timestamps(uint32_t rtp_ts) {
    static uint32_t base_ts = 0;
    if(base_ts == 0) {
        base_ts = rtp_ts;
    }
    current_ts = base_ts + (rtp_ts - base_ts) * 1000 / 90; // 转换为毫秒
    
    // 音视频同步
    if(abs(current_ts - audio_ts) > 100) {
        adjust_audio_clock(current_ts);
    }
}

参考代码 接收解析封装H264为PS数据的RTP包 youwenfan.com/contentcnb/72715.html

六、错误恢复策略

  1. 丢包检测

    if(rtp_hdr->sequence_number != expected_sn) {
        handle_packet_loss(expected_sn, rtp_hdr->sequence_number);
        expected_sn = rtp_hdr->sequence_number + 1;
    }
    
  2. 关键帧请求

    void on_packet_loss(int lost_count) {
        if(lost_count > 5) {
            send_rtcp_pli(); // 请求PLI
        }
    }
    

七、性能优化建议

  1. 零拷贝技术

    // 使用内存池管理NALU缓冲区
    std::vector<uint8_t> nal_buffer(MTU_SIZE);
    
  2. SIMD加速

    #pragma unroll(4)
    for(int i=0; i<16; i++) {
        __m128i data = _mm_loadu_si128((__m128i*)(buffer+i));
        // SIMD处理
    }
    
  3. 多线程处理

    // 分离接收线程与解析线程
    std::thread receiver_thread(receive_rtp);
    std::thread parser_thread(parse_ps_stream);
    

八、完整代码结构

h264_rtp_demuxer/
├── src/
│   ├── rtp_parser.cpp       # RTP包解析
│   ├── ps_demuxer.cpp       # PS流解析
│   ├── h264_processor.cpp   # H264数据处理
│   └── sync_engine.cpp      # 时间戳同步
├── include/
│   ├── rtp_header.h
│   ├── ps_header.h
│   └── h264_types.h
└── tests/
    └── test_demuxer.cpp     # 单元测试

九、调试工具推荐

  1. Wireshark:抓包分析RTP负载类型和PS结构

  2. FFmpeg:验证解码结果

    ffplay -protocol_whitelist "file,udp,rtp" -i input.ps
    
  3. H264 Analyzer:查看NALU类型分布

该方案通过严格遵循RFC3984和GB28181标准,实现了H264在RTP/PS封装下的可靠传输。实际部署时需根据网络环境调整MTU大小(通常1400-1500字节)和缓冲区策略。

posted @ 2025-07-28 15:33  老夫写代码  阅读(9)  评论(0)    收藏  举报