当前位置: 首页 > news >正文

H.265 RTP 打包与拆包重组详解

📅 更新时间:2025-10-14
🏷️ 标签H.265 | HEVC | RTP | 打包 | 解包 | 组帧
🎯 目标:深度掌握 H.265 在 RTP 中的打包、解包与组帧全流程


文章目录

    • 📖 前言
    • 第一部分:H.265 NALU 结构基础
      • 1.1 H.265 NALU 基本结构
        • 📋 完整结构(Annex-B 格式)
      • 1.2 NALU Header 详解(2 字节,关键)
        • 📊 位结构分布
        • 🔑 字段详解
        • 🎯 关键 NALU 类型(Type 值)
      • 1.3 提取 NALU Header 字段(C++ 实现)
      • 1.4 典型 H.265 码流示例
    • 第二部分:RTP 打包三种模式详解
      • 2.1 RTP 基础概念回顾
        • RTP Header(12 字节)关键字段
      • 2.2 打包模式选择逻辑
    • 第三部分:模式一 单 NALU 模式
      • 3.1 适用场景
      • 3.2 Payload 结构
      • 3.3 发送端实现
      • 3.4 接收端实现
    • 第四部分:模式二 - FU 分片模式(核心重点)
      • 4.1 适用场景
      • 4.2 FU Payload 完整结构
      • 4.3 PayloadHdr(字节 0-1)详解
      • 4.4 FU Header(字节 2)详解
      • 4.5 FU Payload(字节 3~N)
      • 4.6 完整发送端实现
      • 4.7 完整接收端实现(关键:组帧逻辑)
      • 4.8 关键边界情况处理
        • ❌ 错误 1:丢包导致 NALU 不完整
        • ❌ 错误 2:收到 S=1 时上一个 NALU 未结束
        • ❌ 错误 3:TS 跳变但 NALU 未完成
    • 第五部分:模式三 - AP 聚合模式
      • 5.1 适用场景
      • 5.2 AP Payload 结构
      • 5.3 PayloadHdr(字节 0-1)
      • 5.4 NALU 单元格式
      • 5.5 发送端实现
      • 5.6 接收端实现
      • 5.7 三种模式对比总结
    • 第六部分:调试与问题排查
      • 6.1 常见问题诊断表
    • 总结


📖 前言

H.265(HEVC)相比 H.264 压缩效率提升约 50%,但在 RTP 传输中的实现细节却复杂得多。本文专注于 H.265 RTP 传输的核心技术,深入剖析:

  • 🔧 H.265 NALU 结构与 RTP Payload 格式
  • 📦 单 NALU、FU 分片、AP 聚合三种打包模式
  • 🔄 接收端解包、乱序处理与 NALU 重组
  • 🐛 常见问题、调试技巧与性能优化

第一部分:H.265 NALU 结构基础

1.1 H.265 NALU 基本结构

H.265 的 NALU(Network Abstraction Layer Unit)是编码后的基本传输单元。

📋 完整结构(Annex-B 格式)
起始码
4字节
00 00 00 01
NALU Header
2字节
Type LayerId TID
NALU Payload
可变长度
实际编码数据

起始码:

  • 00 00 00 01 (4字节) - 用于序列开头、参数集前、关键帧前

1.2 NALU Header 详解(2 字节,关键)

H.265 的 NALU Header 为 2 字节(16 bits),这是与 H.264(1字节)的最大区别。

📊 位结构分布
 Byte 0                      Byte 10 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|    Type   |  LayerId  | TID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
🔑 字段详解
字段位数位置说明典型值
F1bit 15禁止位(forbidden_zero_bit)0
Type6bit 14-9NALU 类型(nal_unit_type)见下表
LayerId6bit 8-3层标识(nuh_layer_id)0(单层)
TID3bit 2-0时间层 ID+1(nuh_temporal_id_plus1)1(最低层)
🎯 关键 NALU 类型(Type 值)
Type名称说明重要性RTP 处理
0-9TRAIL/TSA/STSA/RADL/RASL普通视频片(P/B帧)需打包
16-21BLA/CRA/IDR关键帧(I帧)⭐⭐⭐ 高需打包
19IDR_W_RADL常用 IDR 类型⭐⭐⭐ 高需打包
20IDR_N_LP无前导图像的 IDR⭐⭐⭐ 高需打包
32VPS视频参数集⭐⭐⭐ 高优先发送
33SPS序列参数集⭐⭐⭐ 高优先发送
34PPS图像参数集⭐⭐⭐ 高优先发送
39-40SEI补充增强信息可选
48AP聚合包(RTP)-接收端识别
49FU分片包(RTP)-接收端识别

⚠️ 重要:Type=48 和 49 是 RTP 专用类型,不会出现在编码器输出中,仅用于 RTP Payload。

1.3 提取 NALU Header 字段(C++ 实现)

struct H265NALUHeader {uint8_t forbidden_zero_bit;  // 1 bituint8_t nal_unit_type;       // 6 bitsuint8_t nuh_layer_id;        // 6 bitsuint8_t nuh_temporal_id_plus1; // 3 bits// 从 2 字节解析void parse(const uint8_t* data) {uint16_t header = (data[0] << 8) | data[1];forbidden_zero_bit = (header >> 15) & 0x01;nal_unit_type = (header >> 9) & 0x3F;      // 6 位!nuh_layer_id = (header >> 3) & 0x3F;nuh_temporal_id_plus1 = header & 0x07;}// 编码为 2 字节void encode(uint8_t* data) const {uint16_t header = (forbidden_zero_bit << 15)| (nal_unit_type << 9)| (nuh_layer_id << 3)| nuh_temporal_id_plus1;data[0] = (header >> 8) & 0xFF;data[1] = header & 0xFF;}
};

1.4 典型 H.265 码流示例

00 00 00 01 40 01 0C 01 FF FF ...  <- VPS (Type=32, 0x40>>1=32)
00 00 00 01 42 01 01 01 60 ...     <- SPS (Type=33, 0x42>>1=33)
00 00 00 01 44 01 C1 72 B4 ...     <- PPS (Type=34, 0x44>>1=34)
00 00 00 01 26 01 AF ...           <- IDR (Type=19, 0x26>>1=19)

Type 提取技巧:

uint8_t byte0 = nalu[0];  // 第一个字节
uint8_t type = (byte0 >> 1) & 0x3F;  // 右移1位取6位

第二部分:RTP 打包三种模式详解

2.1 RTP 基础概念回顾

RTP Header(12 字节)关键字段
 0                   1                   2                   30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X|  CC   |M|     PT      |       Sequence Number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           Timestamp                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             SSRC                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

视频场景关键规则:

字段含义规则
MMarker 位帧最后一个 RTP 包 = 1,其他 = 0
Seq序列号每包递增 1(用于排序、检测丢包)
TS时间戳同帧所有包使用相同 TS,90kHz 时钟
PTPayload TypeH.265 通常 96-127(动态协商)

2.2 打包模式选择逻辑

enum RTPPacketMode {SINGLE_NALU,  // 单 NALU 模式FU_MODE,      // FU 分片模式(Type=49)AP_MODE       // AP 聚合模式(Type=48)
};RTPPacketMode select_mode(const NALU& nalu, int mtu) {const int RTP_HEADER_SIZE = 12;const int MAX_PAYLOAD = mtu - RTP_HEADER_SIZE - 20 - 8; // IP+UDPif (nalu.size <= MAX_PAYLOAD) {return SINGLE_NALU;  // 小 NALU,直接发送} else {return FU_MODE;      // 大 NALU,需要分片}// AP 模式:多个小 NALU 聚合(本文暂不深入)
}

第三部分:模式一 单 NALU 模式

3.1 适用场景

  • ✅ NALU 大小 ≤ MTU(通常 ≤ 1400 字节)
  • ✅ VPS、SPS、PPS(参数集通常很小)
  • ✅ 小的 P 帧片段

3.2 Payload 结构

RTP_Payload
RTP_Packet
等于
NALU Payload
可变长度
Byte 2 to N
NALU Header
2字节
Byte 0-1
RTP Payload
RTP Header
12字节
V P X CC M PT Seq TS SSRC
Annex-B起始码
已移除

关键点:

  • RTP Payload 直接就是 NALU 内容(已去除起始码
  • NALU Header 的 Type 字段保持原值(如 33=SPS, 19=IDR)

3.3 发送端实现

void send_single_nalu(const uint8_t* nalu, int nalu_size,RTPContext& ctx) {// nalu: 不含起始码,第一个字节是 NALU Header[0]RTPPacket packet;// 构造 RTP Headerpacket.header.version = 2;packet.header.padding = 0;packet.header.extension = 0;packet.header.cc = 0;packet.header.marker = 1;  // 单包即完整帧,M=1packet.header.payload_type = 96;  // H.265 动态类型packet.header.seq = ctx.seq++;packet.header.timestamp = ctx.timestamp;packet.header.ssrc = ctx.ssrc;// Payload = 完整 NALUmemcpy(packet.payload, nalu, nalu_size);packet.payload_size = nalu_size;// 发送send_udp_packet(&packet);// 日志uint8_t type = (nalu[0] >> 1) & 0x3F;printf("[SEND] Single NALU: seq=%d ts=%u type=%d size=%d M=%d\n",packet.header.seq, packet.header.timestamp, type, nalu_size, packet.header.marker);
}

3.4 接收端实现

void recv_single_nalu(const RTPPacket* packet, H265OutputStream& output) {const uint8_t* payload = packet->payload;int size = packet->payload_size;// 解析 NALU Headeruint16_t nalu_header = (payload[0] << 8) | payload[1];uint8_t type = (nalu_header >> 9) & 0x3F;// 日志printf("[RECV] Single NALU: seq=%d ts=%u type=%d size=%d M=%d\n",packet->header.seq, packet->header.timestamp,type, size, packet->header.marker);// 写入起始码 + NALUuint8_t start_code[] = {0x00, 0x00, 0x00, 0x01};output.write(start_code, 4);output.write(payload, size);// 如果 M=1,通知上层帧完成if (packet->header.marker) {output.flush_frame();}
}

第四部分:模式二 - FU 分片模式(核心重点)

4.1 适用场景

  • ✅ NALU 大小 > MTU
  • ✅ 大的 I 帧(IDR)
  • ✅ 长的 P/B 帧片段

4.2 FU Payload 完整结构

RTP_Payload
RTP_Packet_FU模式
FU Header
1字节 Byte2
S E OrigType6位
PayloadHdr
2字节 Byte0-1
F Type=49 LayerId TID
FU Payload
N字节 Byte3 to N
原NALU数据片段
RTP Header
12字节
M Seq TS SSRC

4.3 PayloadHdr(字节 0-1)详解

作用: 标识这是一个 FU 包,并保留原 NALU 的层信息。

 0                   10 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|Type=49|  LayerId  |   TID   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

字段说明:

字段来源
F0固定为 0
Type49固定值,表示 FU 分片
LayerId通常 0从原 NALU Header 复制
TID通常 1从原 NALU Header 复制

构造示例:

// 假设原 NALU Header: 0x2601 (Type=19, LayerId=0, TID=1)
uint16_t orig_header = 0x2601;
uint8_t layer_id = (orig_header >> 3) & 0x3F;  // = 0
uint8_t tid = orig_header & 0x07;              // = 1// 构造 PayloadHdr (Type=49)
uint16_t payload_hdr = (0 << 15)      // F=0| (49 << 9)      // Type=49| (layer_id << 3)| tid;uint8_t payload_hdr_bytes[2];
payload_hdr_bytes[0] = (payload_hdr >> 8) & 0xFF;  // 0x62
payload_hdr_bytes[1] = payload_hdr & 0xFF;         // 0x01

4.4 FU Header(字节 2)详解

作用: 标识分片的开始/结束,保存原 NALU 类型。

 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|S|E| OrigType  |  (OrigType 占 6 位!)
+-+-+-+-+-+-+-+-+

字段说明:

名称说明取值规则
SStart分片起始标志第一片=1,其他=0
EEnd分片结束标志最后一片=1,其他=0
OrigType原始 NALU 类型6 位(0-63)从原 NALU Header 提取

典型组合:

分片位置SE说明
第一片10分片开始
中间片00继续分片
最后一片01分片结束
单片(不可能)11非法组合

构造示例:

// 原 NALU Type = 19 (IDR)
uint8_t orig_type = 19;// 第一片
uint8_t fu_header_first = (1 << 7) | (0 << 6) | orig_type;  // S=1, E=0
// = 0x93// 中间片
uint8_t fu_header_mid = (0 << 7) | (0 << 6) | orig_type;    // S=0, E=0
// = 0x13// 最后一片
uint8_t fu_header_last = (0 << 7) | (1 << 6) | orig_type;   // S=0, E=1
// = 0x53

4.5 FU Payload(字节 3~N)

  • 直接是原 NALU Payload 的连续切片
  • 不包含原 NALU Header(已在 FU Header 中保存 Type)
  • 切片大小 = MTU - RTP Header(12) - IP(20) - UDP(8) - PayloadHdr(2) - FU Header(1)
    • 典型值:约 1400 - 3 = 1397 字节

4.6 完整发送端实现

void send_fu_packets(const uint8_t* nalu, int nalu_size,RTPContext& ctx) {// nalu[0..1] = NALU Header (2 字节)// nalu[2..]  = NALU Payload// 解析原 NALU Headeruint16_t orig_header = (nalu[0] << 8) | nalu[1];uint8_t f = (orig_header >> 15) & 0x01;uint8_t orig_type = (orig_header >> 9) & 0x3F;  // 6 位!uint8_t layer_id = (orig_header >> 3) & 0x3F;uint8_t tid = orig_header & 0x07;const int MAX_PAYLOAD_SIZE = 1400;  // RTP Payload 最大const int FU_HEADER_SIZE = 3;       // PayloadHdr(2) + FU Header(1)const int MAX_FRAGMENT_SIZE = MAX_PAYLOAD_SIZE - FU_HEADER_SIZE;int payload_offset = 2;  // 跳过原 NALU Header(2字节)int payload_remaining = nalu_size - 2;bool is_first = true;while (payload_remaining > 0) {int fragment_size = std::min(payload_remaining, MAX_FRAGMENT_SIZE);bool is_last = (payload_remaining == fragment_size);RTPPacket packet;// RTP Headerpacket.header.version = 2;packet.header.padding = 0;packet.header.extension = 0;packet.header.cc = 0;packet.header.marker = is_last ? 1 : 0;  // 只有最后一片 M=1packet.header.payload_type = 96;packet.header.seq = ctx.seq++;packet.header.timestamp = ctx.timestamp;packet.header.ssrc = ctx.ssrc;// 构造 PayloadHdr (Type=49)uint16_t payload_hdr = (f << 15) | (49 << 9) | (layer_id << 3) | tid;packet.payload[0] = (payload_hdr >> 8) & 0xFF;packet.payload[1] = payload_hdr & 0xFF;// 构造 FU Headeruint8_t fu_header = orig_type;  // 低 6 位if (is_first) fu_header |= 0x80;  // S=1if (is_last)  fu_header |= 0x40;  // E=1packet.payload[2] = fu_header;// 复制分片数据memcpy(packet.payload + 3, nalu + payload_offset, fragment_size);packet.payload_size = 3 + fragment_size;// 发送send_udp_packet(&packet);// 日志printf("[SEND] FU: seq=%d ts=%u S=%d E=%d type=%d size=%d M=%d\n",packet.header.seq, packet.header.timestamp,is_first, is_last, orig_type, packet.payload_size, packet.header.marker);payload_offset += fragment_size;payload_remaining -= fragment_size;is_first = false;}
}

4.7 完整接收端实现(关键:组帧逻辑)

class H265FUAssembler {
private:struct FrameBuffer {std::vector<uint8_t> data;uint32_t timestamp;uint16_t orig_nalu_header;bool started;};std::map<uint32_t, FrameBuffer> buffers;  // key: timestamppublic:bool process_fu_packet(const RTPPacket* packet,std::vector<uint8_t>& complete_nalu) {const uint8_t* payload = packet->payload;int size = packet->payload_size;uint32_t ts = packet->header.timestamp;// 解析 PayloadHdr (字节 0-1)uint16_t payload_hdr = (payload[0] << 8) | payload[1];uint8_t type = (payload_hdr >> 9) & 0x3F;if (type != 49) {fprintf(stderr, "Error: Not a FU packet (type=%d)\n", type);return false;}// 解析 FU Header (字节 2)uint8_t fu_header = payload[2];bool is_start = (fu_header & 0x80) != 0;bool is_end = (fu_header & 0x40) != 0;uint8_t orig_type = fu_header & 0x3F;  // 6 位!// 日志printf("[RECV] FU: seq=%d ts=%u S=%d E=%d type=%d size=%d M=%d\n",packet->header.seq, ts, is_start, is_end, orig_type, size, packet->header.marker);FrameBuffer& buffer = buffers[ts];if (is_start) {// 新 NALU 开始if (buffer.started) {fprintf(stderr, "Warning: New FU start before previous end (ts=%u)\n", ts);}buffer.data.clear();buffer.timestamp = ts;buffer.started = true;// 重建原 NALU Header (2 字节)uint8_t f = (payload_hdr >> 15) & 0x01;uint8_t layer_id = (payload_hdr >> 3) & 0x3F;uint8_t tid = payload_hdr & 0x07;buffer.orig_nalu_header = (f << 15) | (orig_type << 9) | (layer_id << 3) | tid;// 先写入起始码buffer.data.push_back(0x00);buffer.data.push_back(0x00);buffer.data.push_back(0x00);buffer.data.push_back(0x01);// 写入重建的 NALU Headerbuffer.data.push_back((buffer.orig_nalu_header >> 8) & 0xFF);buffer.data.push_back(buffer.orig_nalu_header & 0xFF);}if (!buffer.started) {fprintf(stderr, "Error: FU fragment without start (ts=%u)\n", ts);return false;}// 追加分片数据(从 payload[3] 开始)buffer.data.insert(buffer.data.end(), payload + 3, payload + size);if (is_end) {// 分片完成complete_nalu = std::move(buffer.data);buffers.erase(ts);printf("[ASSEMBLE] Complete NALU: ts=%u size=%zu type=%d\n",ts, complete_nalu.size(), orig_type);return true;}return false;}
};

4.8 关键边界情况处理

❌ 错误 1:丢包导致 NALU 不完整

问题: 收到 S=1 和 E=1 的包,但中间丢了包。

检测:

if (is_start) {expected_seq = packet->header.seq;
}uint16_t actual_seq = packet->header.seq;
uint16_t seq_diff = actual_seq - expected_seq;if (seq_diff != 0) {fprintf(stderr, "Packet loss detected: expected=%d actual=%d\n",expected_seq, actual_seq);// 丢弃当前帧buffer.started = false;buffer.data.clear();return false;
}
expected_seq++;
❌ 错误 2:收到 S=1 时上一个 NALU 未结束

问题: 前一帧的 E=1 包丢失,又收到新帧的 S=1。

处理:

if (is_start && buffer.started) {fprintf(stderr, "Warning: Incomplete NALU discarded (ts=%u)\n",buffer.timestamp);// 丢弃旧帧,开始新帧buffer.data.clear();
}
❌ 错误 3:TS 跳变但 NALU 未完成

问题: 时间戳变了,但上一帧还没收到 E=1。

处理:

// 检查旧帧是否超时
for (auto it = buffers.begin(); it != buffers.end(); ) {uint32_t age = current_ts - it->second.timestamp;if (age > 3000) {  // 超过 3000 个时间戳单位(约 33ms@90kHz)fprintf(stderr, "Timeout: Incomplete NALU discarded (ts=%u)\n",it->second.timestamp);it = buffers.erase(it);} else {++it;}
}

第五部分:模式三 - AP 聚合模式

5.1 适用场景

  • ✅ 多个小 NALU 需要一起发送(如 VPS + SPS + PPS)
  • ✅ 节省 RTP 包数量,减少网络开销
  • ✅ 总大小 ≤ MTU 的多个 NALU

5.2 AP Payload 结构

AP(Aggregation Packet,聚合包) 允许将多个小 NALU 打包到一个 RTP 包中。

RTP_Payload
RTP_Packet_AP模式
NALU1 Size
2字节
PayloadHdr
2字节
F Type=48 LayerId TID
NALU1 Data
N1字节
NALU2 Size
2字节
NALU2 Data
N2字节
...
RTP Header
12字节
M Seq TS SSRC

5.3 PayloadHdr(字节 0-1)

 0                   10 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|Type=48|  LayerId  |   TID   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

关键点:

  • Type 字段 = 48(AP 标识)
  • LayerId 和 TID 通常从第一个 NALU 获取

5.4 NALU 单元格式

每个 NALU 单元的格式:

字节位置内容说明
Byte 0-1NALU Size16位无符号整数,大端序
Byte 2~NNALU Data完整 NALU(含2字节Header)

示例结构:

PayloadHdr (2B) | Size1 (2B) | NALU1 | Size2 (2B) | NALU2 | ...48            0x0020       32B       0x0015       21B

5.5 发送端实现

void send_ap_packet(const std::vector<NALU>& nalus, RTPContext& ctx) {// 计算总大小int total_size = 2;  // PayloadHdrfor (const auto& nalu : nalus) {total_size += 2 + nalu.size;  // Size(2B) + NALU Data}if (total_size > MAX_PAYLOAD_SIZE) {fprintf(stderr, "Error: AP packet too large\n");return;}RTPPacket packet;// RTP Headerpacket.header.version = 2;packet.header.padding = 0;packet.header.extension = 0;packet.header.cc = 0;packet.header.marker = 1;  // AP 包通常是完整单元packet.header.payload_type = 96;packet.header.seq = ctx.seq++;packet.header.timestamp = ctx.timestamp;packet.header.ssrc = ctx.ssrc;// 构造 PayloadHdr (Type=48)// 从第一个 NALU 获取 LayerId 和 TIDuint16_t first_nalu_hdr = (nalus[0].data[0] << 8) | nalus[0].data[1];uint8_t layer_id = (first_nalu_hdr >> 3) & 0x3F;uint8_t tid = first_nalu_hdr & 0x07;uint16_t payload_hdr = (0 << 15) | (48 << 9) | (layer_id << 3) | tid;packet.payload[0] = (payload_hdr >> 8) & 0xFF;packet.payload[1] = payload_hdr & 0xFF;int offset = 2;// 添加每个 NALUfor (const auto& nalu : nalus) {// NALU Size (大端序,2字节)packet.payload[offset++] = (nalu.size >> 8) & 0xFF;packet.payload[offset++] = nalu.size & 0xFF;// NALU Datamemcpy(packet.payload + offset, nalu.data, nalu.size);offset += nalu.size;}packet.payload_size = offset;// 发送send_udp_packet(&packet);printf("[SEND] AP: seq=%d ts=%u nalus=%zu total_size=%d M=%d\n",packet.header.seq, packet.header.timestamp,nalus.size(), packet.payload_size, packet.header.marker);
}

5.6 接收端实现

bool process_ap_packet(const RTPPacket* packet,std::vector<std::vector<uint8_t>>& nalus) {const uint8_t* payload = packet->payload;int size = packet->payload_size;// 解析 PayloadHdruint16_t payload_hdr = (payload[0] << 8) | payload[1];uint8_t type = (payload_hdr >> 9) & 0x3F;if (type != 48) {fprintf(stderr, "Error: Not an AP packet (type=%d)\n", type);return false;}int offset = 2;  // 跳过 PayloadHdrnalus.clear();// 解析每个 NALUwhile (offset < size) {// 读取 NALU Size (大端序)if (offset + 2 > size) {fprintf(stderr, "Error: Invalid AP packet (truncated size)\n");return false;}uint16_t nalu_size = (payload[offset] << 8) | payload[offset + 1];offset += 2;// 读取 NALU Dataif (offset + nalu_size > size) {fprintf(stderr, "Error: Invalid AP packet (truncated data)\n");return false;}std::vector<uint8_t> nalu;// 写入起始码nalu.push_back(0x00);nalu.push_back(0x00);nalu.push_back(0x00);nalu.push_back(0x01);// 写入 NALU 数据nalu.insert(nalu.end(), payload + offset, payload + offset + nalu_size);nalus.push_back(std::move(nalu));offset += nalu_size;// 解析 Type 用于日志uint16_t nalu_hdr = (payload[offset - nalu_size] << 8) | payload[offset - nalu_size + 1];uint8_t nalu_type = (nalu_hdr >> 9) & 0x3F;printf("[RECV] AP NALU: type=%d size=%d\n", nalu_type, nalu_size);}printf("[RECV] AP: seq=%d ts=%u nalus=%zu total_size=%d\n",packet->header.seq, packet->header.timestamp,nalus.size(), size);return true;
}

5.7 三种模式对比总结

模式Type值适用场景Payload结构优点缺点
单NALU0-47NALU ≤ MTU直接=NALU简单浪费包(小NALU)
FU分片49NALU > MTUPayloadHdr + FU Header + 片段支持大帧复杂,怕丢包
AP聚合48多个小NALUPayloadHdr + [Size+NALU]…节省包数需要额外处理

第六部分:调试与问题排查

6.1 常见问题诊断表

症状可能原因检查方法解决方案
无法解码起始码缺失hexdump output.h265 | head确保重组时添加起始码
花屏FU 分片丢包检查 Seq 连续性实现丢包检测与丢弃机制
卡顿时间戳错误检查同帧 TS 是否一致修正时间戳计算
Type 解析错误只取 5 位而非 6 位确认掩码为 & 0x3F改用 6 位掩码
PayloadHdr 错误Type 未设为 49检查 (hdr >> 9) & 0x3F确保 Type=49
FU Header 偏移错读取 payload[1]应读取 payload[2]修正偏移
NALU Header 错误重建时只用 1 字节应重建 2 字节修正重建逻辑

总结

H.265 RTP 传输的核心要点:

  1. 2 字节 NALU Header,Type 占 6 位
  2. FU 模式 Type=49,PayloadHdr(2字节) + FU Header(1字节)
  3. FU Header 在 payload[2],数据从 payload[3] 开始
  4. 重建时恢复完整 2 字节 NALU Header
  5. 严格的组帧逻辑:检测 S/E 标志,处理丢包

如果您觉得这篇文章对您有帮助,不妨点赞 + 收藏 + 关注,更多音视频开发实战技巧将持续更新 🔥!

http://www.dtcms.com/a/488206.html

相关文章:

  • 建设网站情况说明范文php网站开发实战视频
  • 建网站需要用到什么软件陕西最新消息
  • 汕头网站建设方案开发可拖动网站
  • 收录网站查询建医疗网站步骤
  • H5网站开发工程师lnmp 网站开发总结
  • 二层虚拟专用网络技术详解1:VPWS在MPLS网络中的实现与应用
  • 免费网站后台模板下载字号 wordpress
  • 水果商城网站模板阿里巴巴logo的含义
  • QT6中QChart功能与应用
  • 人工智能简史(1)
  • 2025年--Lc188--931. 下降路径最小和(多维动态规划,矩阵)--Java版
  • 中投中原建设有限公司网站进网站后台加什么原因
  • 建设网站需要哪些域名潍坊微信网站
  • 娄底建设网站制作济宁百度推广公司有几家
  • 网站公告栏设计coding搭建WordPress
  • 团购营销型网站制作wordpress文章功能
  • 只有做推广才能搜索到网站吗网络优化基础知识
  • YOLOv3:目标检测领域的经典之作
  • 深入解析C++ for循环原理
  • 数据安全运营指南 - 态势感知与威胁处置
  • 188旅游网站管理系统源码建设网站制作公司如何选择
  • 网站规划作品门户网站的细分模式有
  • 类似头条的网站怎么做怎么开发ios软件
  • 给公司申请网站用自己的账号建材网站建设功能方案
  • 购物网站含有哪些模块前端设计模板
  • DeepSeek-V3.2-Exp解析
  • 做网站需要的相关知识网站整站截图
  • 单页网站订单系统怎么改邮箱网站建设信息推荐
  • 如何做视频网站旗下账号凡科网站建设分类模块怎么弄
  • 做网站开发要学多久wordpress有多大的数据量