聚牛网站建设公司,济宁网站建设哪家便宜,东莞哪里做网站,辽宁seo概述
基于ZLM流媒体框架以及简单RTSP服务器开源项目分析总结#xff0c;相关源码参考以下链接 H265-rtp提取Nalu逻辑
通过rtsp流地址我们可以获取视频流中的多个rtp包#xff0c;其中每个RTP包中又会包含一个或者多个Nalu#xff0c;将其提取处理 总体逻辑分析 核心逻辑在…概述
基于ZLM流媒体框架以及简单RTSP服务器开源项目分析总结相关源码参考以下链接 H265-rtp提取Nalu逻辑
通过rtsp流地址我们可以获取视频流中的多个rtp包其中每个RTP包中又会包含一个或者多个Nalu将其提取处理 总体逻辑分析 核心逻辑在于对H265 RTP解复用器的使用从RTP包中提取出来完整Nalu或者分片的Nalu
接收RTP数据包识别负载数据然后处理RTP拓展头部信息识别NALU的类型主要用于区分其是单一的Nalu还是分片的NaluFU类型为49单一Nalu处理逻辑 添加起始码通过回调函数传递Nalu 分片Nalu的处理 重组Nalu首先会在缓冲区中存储来自多个rtp包的分片数据然后逐步重组完整的Nalu通过FU头部标志S,E识别起始、中间和结束分片 在起始分片中从FU头部恢复原始的Nalu类型结束分片的时候添加起始码和重组的Nalu头部并通过回调函数传递完整的Nalu数据 最后通过回调函数进一步对H265的Nalu进行处理 参考RTP * 0 1 2 3* 0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7* --------------------------------* |V2|P|X| CC |M| PT | sequence number |* --------------------------------* | timestamp |* --------------------------------* | synchronization source (SSRC) identifier |* * | contributing source (CSRC) identifiers |* : .... :* --------------------------------**/ 代码实现分析 开始处理H265的RTP包然后计算RTP包的负载数据的起始地址以及负载数据的长度计算的主要方法就是跳过RTP头部大小
struct RtpHeader *header (struct RtpHeader *)data;
int payload_type header-payloadType;
if(payload_type ! payload_){return;
}
const uint8_t* payload data sizeof(struct RtpHeader);
size_t payload_len size - sizeof(struct RtpHeader);
处理RTP拓展头部首先需要判断其拓展头部是否存在存在拓展头部的处理 计算拓展头部的长度然后通过偏移量跳过RTP的头部和拓展头部从而使得负载数据指向争取的位置目的是保证其提取正确的Nalu数据
if (header-extension){const uint8_t *extension_data payload;size_t extension_length 4 * (extension_data[2] 8 | extension_data[3]);size_t payload_offset 4 extension_length;payload payload payload_offset;payload_len payload_len - payload_offset;
}
判断Nalu的头部和分片类型 如果是49分片类型那么需要对分片数据进行特别处理
struct H265NaluHeader *h265_header (struct H265NaluHeader *)payload;
if(h265_header-type 49){ // 分片 (Fragmentation Unit - FU)struct H265FUHeader *fu_header (struct H265FUHeader *)payload[2];// ... 分片 NALU 的处理逻辑 ...
}
else{ // 单一封包 (Single NAL Unit)// ... 单一 NALU 的处理逻辑 ...
}
首先访问负载数据的头部处理起始分片然后将起始分片后的分片放入缓冲区最后遇到结束分片的时候将其统一封装成一个Nalu即可
struct H265FUHeader *fu_header (struct H265FUHeader *)payload[2];
if(fu_header-s 1){ // 起始分片 (Start fragment)find_start_ true;if(pos_buffer_ 0){ // 首次接收到起始分片struct H265NaluHeader header *h265_header;header.type fu_header-type; // 从 FU 头部恢复原始 NALU 类型buffer_[0] 0;buffer_[1] 0;buffer_[2] 0;buffer_[3] 1; // NALU 起始码前缀memcpy(buffer_ 4, header, sizeof(struct H265NaluHeader)); // 复制 NALU 头部pos_buffer_ 4 sizeof(struct H265NaluHeader);}memcpy(buffer_ pos_buffer_, payload 3, payload_len - 3); // 复制分片数据pos_buffer_ payload_len - 3;
}
else if(fu_header-e 1){ // 结束分片 (End fragment)if(find_start_ false){ // 尚未接收到起始分片return;}memcpy(buffer_ pos_buffer_, payload 3, payload_len - 3); // 复制分片数据pos_buffer_ payload_len - 3;if(call_back_){ // 调用回调函数传递完整的 NALUcall_back_-OnVideoData(ntohl(header-timestamp), buffer_, pos_buffer_);}find_start_ false; // 重置状态准备接收下一个 NALUpos_buffer_ 0;
}
else { // 中间分片 (Middle fragment)if (!find_start_) { // 尚未接收到起始分片return;}memcpy(buffer_ pos_buffer_, payload 3, payload_len - 3); // 复制分片数据pos_buffer_ payload_len - 3;
}
处理单一封包的Nalu 直接跳过起始码即可提取出RTP包中的Nalu
else{ // 单一封包 (Single NAL Unit)buffer_[0] 0;buffer_[1] 0;buffer_[2] 0;buffer_[3] 1;memcpy(buffer_ 4, payload, payload_len);if(call_back_){call_back_-OnVideoData(ntohl(header-timestamp), buffer_, payload_len 4);}
}
H265-Nalu组成RTP包
逻辑分析 核心流程 确定 RTP 负载类型 (Payload Type, PT) 参考SDP协商出来的信息 构建 RTP 头部 (RTP Header) 版本 (Version, V): RTP 版本号通常为 2填充 (Padding, P): 指示 RTP 包末尾是否有填充字节。通常为 0扩展 (Extension, X): 指示 RTP 头部后面是否有扩展头部。通常为 0除非你需要添加扩展信息CSRC 计数器 (CSRC Count, CC): CSRC 标识符的数目。通常为 0除非有贡献源标记位 (Marker, M): 标记 RTP 包的事件例如可以用来标记帧的结束。对于视频可以用来标记每个帧的最后一个 RTP 包负载类型 (Payload Type, PT): 你选择的负载类型例如 96序列号 (Sequence Number): 每个 RTP 包的序列号从一个随机值开始然后每个包递增 1。用于检测包丢失和重排序时间戳 (Timestamp): 指示 RTP 包中第一个字节的采样时间。对于视频时间戳应该反映视频帧的显示时间。时间戳时钟频率需要根据视频编码的帧率来确定同步源标识符 (Synchronization Source Identifier, SSRC): 标识 RTP 流的源为一个 32 位的随机数在 RTP 会话中应保持唯一 Nalu分片与封装 总结如果一个Nalu的大小超过MTU最大传输单元那么就需要对其分片处理打成多个RTP包首先判断是否需要分片FU-A分片 起始分片 RTP头部同上FU Indicator (1 字节): NALU 头部的前两个字节但 NALU type 字段设置为 49 (FU 类型)FU Header (1 字节) S (Start bit): 设置为 1表示是分片的开始E (End bit): 设置为 0R (Reserved bit): 必须为 0FU type: 原始 NALU 类型的后 6 位 (从原始 NALU 头部中提取) 分片数据NALU的一部分数据 中间分片 RTP头部信息FU Indicator (1 字节):同起始分片FU Header (1 字节) S (Start bit): 设置为0E (End bit): 设置为 0R (Reserved bit): 必须为 0FU type: 这里需要与起始分片相同 分片数据Nalu的一部分数据 结束分片 RTP头部序列号和时间戳递增标记位 M 可以设置为 1如果这是当前帧的最后一个 RTP 包FU Indicator (1 字节):同起始分片FU Header (1 字节) S (Start bit): 设置为0E (End bit): 设置为 1表示分片的结束R (Reserved bit): 必须为 0FU type: 这里需要与起始分片相同 分片数据Nalu最后一部分的数据 单一分片封装 RTP头部标记位M可以设置为1吗当前帧是最后一个Nalu的时候Nalu数据这里是去除Nalu的起始码直接放入Nalu头部负载数据 时间戳和序列号管理 时间戳: 对于每个视频帧的第一个 RTP 包设置时间戳为当前帧的显示时间。对于同一帧的后续分片包时间戳保持不变。 下一个视频帧的第一个 RTP 包使用新的时间戳时间戳的增量应该与视频帧率和时钟频率一致 序列号: 为每个 RTP 包分配递增的序列号起始序列号随机选择
代码实现
#include iostream
#include vector
#include cstdint
#include cstring
#include iomanip // 用于十六进制输出格式化
#include ctime // 用于日志中的时间戳
#include sstream // 用于字符串流// --- 常量定义 ---
const uint8_t H265_PAYLOAD_TYPE 96;
const uint16_t RTP_VERSION 2;
const uint16_t VIDEO_STREAM_ID 0xE0; // 视频流的 Stream ID 示例
const size_t MAX_RTP_PAYLOAD_SIZE 1400; // RTP 负载最大尺寸示例根据 MTU 和头部开销调整
const size_t MAX_PES_PAYLOAD_SIZE 2048; // PES 负载最大尺寸示例根据需要调整
const uint32_t PS_START_CODE_PREFIX 0x000001BA;
const uint32_t PES_START_CODE_PREFIX 0x000001;// --- 结构体定义 ---
#pragma pack(push, 1) // 确保结构体内部没有填充字节// 简化的 RTP 头部
struct RTPHeader {uint8_t version_padding_extension_csrc_count; // V, P, X, CCuint8_t marker_payload_type; // M, PTuint16_t sequence_number;uint32_t timestamp;uint32_t ssrc;RTPHeader() : version_padding_extension_csrc_count(0x80), marker_payload_type(H265_PAYLOAD_TYPE), sequence_number(0), timestamp(0), ssrc(0x12345678) {} // SSRC 示例
};// FU 头部 (Fragmentation Unit A, FU-A)
struct FUHeader {uint8_t fu_header; // S, E, R, FU TypeFUHeader() : fu_header(0) {}
};// FU 指示器 (Fragmentation Unit A, FU-A)
struct FUIndicator {uint8_t fu_indicator; // Type 49 (FU), NALU type bitsFUIndicator() : fu_indicator(0) {}
};// 简化的 PES 头部 (关注 PTS)
struct PESHeader {uint32_t packet_start_code_prefix;uint8_t stream_id;uint16_t pes_packet_length; // 暂时设置为 0之后计算uint8_t pes_scrambling_control_indicator_etc; // 标志位和指示器uint8_t pes_header_data_length;uint64_t pts_dts_flags_pts; // PTS 标志和 PTS 值 (简化示例)PESHeader() : packet_start_code_prefix(PES_START_CODE_PREFIX), stream_id(VIDEO_STREAM_ID), pes_packet_length(0),pes_scrambling_control_indicator_etc(0x80), // PTS_DTS_flags: 0b10 (仅 PTS)pes_header_data_length(5), // 仅 PTS 占用 5 字节pts_dts_flags_pts(0) {} // PTS 值稍后设置
};// 极简 PS 头部 (仅用于示例)
struct PSHeader {uint32_t packet_start_code_prefix;uint64_t system_clock_reference; // SCR (System Clock Reference) - 简化PSHeader() : packet_start_code_prefix(PS_START_CODE_PREFIX), system_clock_reference(0) {}
};#pragma pack(pop) // 恢复默认 packing// --- 全局计数器和变量 ---
static uint16_t rtp_sequence_number_counter 0;
static uint32_t rtp_timestamp_counter 0;
static uint64_t pes_pts_counter 0; // PES PTS 计数器示例// --- 日志记录函数 ---
void Log(const std::string message) {std::time_t now std::time(nullptr);std::tm local_time;localtime_r(now, local_time);char timestamp_str[20];std::strftime(timestamp_str, sizeof(timestamp_str), %Y-%m-%d %H:%M:%S, local_time);std::cout [ timestamp_str ] message std::endl;
}// --- 辅助函数将数据转换为十六进制字符串 ---
std::string ToHex(const uint8_t* data, size_t size) {std::stringstream hex_stream;hex_stream std::hex std::setfill(0);for (size_t i 0; i size; i) {hex_stream std::setw(2) static_castint(data[i]) ;}return hex_stream.str();
}// --- 阶段 1: NALU 封装为 RTP 包 ---
std::vectorstd::vectoruint8_t EncapsulateNALUtoRTP(const std::vectoruint8_t nalu_data, int nalu_type) {Log(--- 开始 NALU 到 RTP 的封装 ---);std::vectorstd::vectoruint8_t rtp_packets;size_t nalu_size nalu_data.size();const uint8_t* nalu_payload nalu_data.data();size_t nalu_payload_offset 0;if (nalu_size MAX_RTP_PAYLOAD_SIZE) {// 情况 1: NALU 足够小可以放入单个 RTP 包Log(NALU 尺寸足够小可以使用单个 RTP 包。);std::vectoruint8_t rtp_packet_buffer(sizeof(RTPHeader) nalu_size);RTPHeader rtp_header;rtp_header.sequence_number htons(rtp_sequence_number_counter);rtp_header.timestamp htonl(rtp_timestamp_counter);rtp_header.marker_payload_type | (1 7); // 设置 Marker 位 (示例可能根据实际需求调整)memcpy(rtp_packet_buffer.data(), rtp_header, sizeof(RTPHeader));memcpy(rtp_packet_buffer.data() sizeof(RTPHeader), nalu_payload, nalu_size);Log(创建 RTP 头部: ToHex(rtp_packet_buffer.data(), sizeof(RTPHeader)));Log(RTP 负载 (NALU 数据): ToHex(rtp_packet_buffer.data() sizeof(RTPHeader), nalu_size));rtp_packets.push_back(rtp_packet_buffer);} else {// 情况 2: NALU 太大需要分片 (FU-A)Log(NALU 尺寸超过 RTP 负载限制需要分片 (FU-A)。);int fragment_number 0;bool first_fragment true;bool last_fragment false;while (nalu_payload_offset nalu_size) {size_t fragment_size std::min(MAX_RTP_PAYLOAD_SIZE - sizeof(FUIndicator) - sizeof(FUHeader), nalu_size - nalu_payload_offset);if (nalu_payload_offset fragment_size nalu_size) {last_fragment true;}std::vectoruint8_t rtp_packet_buffer(sizeof(RTPHeader) sizeof(FUIndicator) sizeof(FUHeader) fragment_size);RTPHeader rtp_header;rtp_header.sequence_number htons(rtp_sequence_number_counter);rtp_header.timestamp htonl(rtp_timestamp_counter);rtp_header.marker_payload_type ~(1 7); // 清除 Marker 位 (分片包通常不设置除非是帧的最后一个分片)if (last_fragment) rtp_header.marker_payload_type | (1 7); // 在最后一个分片包上设置 Marker 位 (示例)FUIndicator fu_indicator;fu_indicator.fu_indicator 49 1; // FU 类型 49fu_indicator.fu_indicator | ((nalu_type 5) 0x01); // 复制原始 NALU 头部的 forbidden_zero_bitFUHeader fu_header;fu_header.fu_header (nalu_type 0x1F); // NAL 单元类型 (原始 NALU 类型的后 5 位)if (first_fragment) fu_header.fu_header | (1 7); // 设置 S 位 (起始分片)if (last_fragment) fu_header.fu_header | (1 6); // 设置 E 位 (结束分片)memcpy(rtp_packet_buffer.data(), rtp_header, sizeof(RTPHeader));memcpy(rtp_packet_buffer.data() sizeof(RTPHeader), fu_indicator, sizeof(FUIndicator));memcpy(rtp_packet_buffer.data() sizeof(RTPHeader) sizeof(FUIndicator), fu_header, sizeof(FUHeader));memcpy(rtp_packet_buffer.data() sizeof(RTPHeader) sizeof(FUIndicator) sizeof(FUHeader),nalu_payload nalu_payload_offset, fragment_size);Log(创建 RTP 头部 (分片 std::to_string(fragment_number) ): ToHex(rtp_packet_buffer.data(), sizeof(RTPHeader)));Log(FU 指示器: ToHex(rtp_packet_buffer.data() sizeof(RTPHeader), sizeof(FUIndicator)));Log(FU 头部: ToHex(rtp_packet_buffer.data() sizeof(RTPHeader) sizeof(FUIndicator), sizeof(FUHeader)));Log(RTP 负载 (分片数据 std::to_string(fragment_number) ): ToHex(rtp_packet_buffer.data() sizeof(RTPHeader) sizeof(FUIndicator) sizeof(FUHeader), fragment_size));rtp_packets.push_back(rtp_packet_buffer);nalu_payload_offset fragment_size;first_fragment false;fragment_number;}}rtp_timestamp_counter 3600; // 时间戳递增示例 (90kHz 时钟, 约 40ms 帧时长)Log(--- NALU 到 RTP 封装完成生成 std::to_string(rtp_packets.size()) 个 RTP 包。---\n);return rtp_packets;
}// --- 阶段 2: RTP 封装为 PES (更标准的做法应为 NALU 封装为 PES) ---
std::vectoruint8_t EncapsulateRTPtoPES(const std::vectorstd::vectoruint8_t rtp_packets, const std::vectoruint8_t original_nalu_data) {Log(--- 开始 RTP (或 NALU) 到 PES 的封装 ---);std::vectoruint8_t pes_packet_buffer;// 为了更符合 PS 流标准理想情况下应该从 RTP 包中解封装出 NALU 数据然后将 NALU 放入 PES。// 但为了演示 组成 rtp 包后通过 PS 流发送出去 的字面意思这里我们将 *整个 RTP 包* 放入 PES 负载 (这不太标准但在某些特定场景下可能可行)。// 在实际系统中你可能更希望提取 RTP 中的 NALU 负载然后放入 PES。PESHeader pes_header;pes_header.pts_dts_flags_pts (static_castuint64_t(pes_pts_counter) 3) | 0x02; // 仅设置 PTS 标志uint64_t pts_33_to_1 (pes_pts_counter * 300) % 0x200000000LL; // 假设 300 ticks/ms, 90kHz clockuint32_t pts_32_to_2 pts_33_to_1 0xFFFFFFFE0LL;uint8_t pts_byte0 0x20 | ((pts_32_to_2 30) 0x07) 1 | 0x01;uint16_t pts_byte1_2 (pts_32_to_2 15) 0xFFFF;uint16_t pts_byte3_4 pts_32_to_2 0xFFFF;pes_header.pts_dts_flags_pts | (static_castuint64_t(pts_byte0) 40);pes_header.pts_dts_flags_pts | (static_castuint64_t(pts_byte1_2) 24);pes_header.pts_dts_flags_pts | (static_castuint64_t(pts_byte3_4) 8);size_t pes_payload_size 0;for (const auto rtp_packet : rtp_packets) {pes_payload_size rtp_packet.size();}pes_header.pes_packet_length htons(sizeof(PESHeader) pes_payload_size - 6); // PES 包长度不包括 包起始码前缀 和 长度字段自身pes_packet_buffer.resize(sizeof(PESHeader));memcpy(pes_packet_buffer.data(), pes_header, sizeof(PESHeader));Log(创建 PES 头部: ToHex(pes_packet_buffer.data(), sizeof(PESHeader)));Log(PES PTS Value: std::to_string(pes_pts_counter));for (const auto rtp_packet : rtp_packets) {pes_packet_buffer.insert(pes_packet_buffer.end(), rtp_packet.begin(), rtp_packet.end());}Log(PES Payload (RTP 包数据): Total size std::to_string(pes_payload_size) bytes.);Log(--- RTP to PES Encapsulation Complete. PES Packet Size: std::to_string(pes_packet_buffer.size()) bytes. ---\n);return pes_packet_buffer;
}// --- 阶段 3: 将 PES 包放入 PS 流 (简化示例仅包含 PS 头部和 PES 包) ---
std::vectoruint8_t CreatePSStream(const std::vectoruint8_t pes_packet) {Log(--- 开始创建 PS 流 ---);std::vectoruint8_t ps_stream_buffer;PSHeader ps_header;// SCR (System Clock Reference) 示例 - 非常简化ps_header.system_clock_reference (static_castuint64_t(pes_pts_counter * 300) 3) | 0x01; // 90kHz clock, marker_bits 01ps_stream_buffer.resize(sizeof(PSHeader));memcpy(ps_stream_buffer.data(), ps_header, sizeof(PSHeader));Log(创建 PS 头部: ToHex(ps_stream_buffer.data(), sizeof(PSHeader)));ps_stream_buffer.insert(ps_stream_buffer.end(), pes_packet.begin(), pes_packet.end());Log(将 PES 包添加到 PS 流. PES Packet Size: std::to_string(pes_packet.size()) bytes.);Log(--- PS 流创建完成. Total PS Stream Size: std::to_string(ps_stream_buffer.size()) bytes. ---\n);return ps_stream_buffer;
}int main() {Log(--- 示例程序开始 ---);// 示例 H.265 NALU 数据 (这里用一些虚拟数据代替实际的 H.265 NALU)std::vectoruint8_t h265_nalu_data {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,// ... 假设这里是真实的 H.265 NALU 负载数据 ...};int nalu_type 32; // 示例 NALU 类型 (VPS)Log(原始 H.265 NALU 数据: Size std::to_string(h265_nalu_data.size()) bytes, Type std::to_string(nalu_type));Log(ToHex(h265_nalu_data.data(), h265_nalu_data.size()));// 阶段 1: NALU 封装成 RTP 包std::vectorstd::vectoruint8_t rtp_packets EncapsulateNALUtoRTP(h265_nalu_data, nalu_type);// 阶段 2: RTP 包封装成 PES 包std::vectoruint8_t pes_packet EncapsulateRTPtoPES(rtp_packets, h265_nalu_data);// 阶段 3: 创建 PS 流std::vectoruint8_t ps_stream CreatePSStream(pes_packet);Log(\n--- 最终 PES 包 (放入 PS 流之前) ---);Log(PES Packet Hex Data (First 64 bytes): \n ToHex(pes_packet.data(), std::min((size_t)64, pes_packet.size())));Log(PES Packet Size: std::to_string(pes_packet.size()) bytes.);Log(\n--- 最终 PS 流 (First 64 bytes) ---);Log(PS Stream Hex Data (First 64 bytes): \n ToHex(ps_stream.data(), std::min((size_t)64, ps_stream.size())));Log(PS Stream Size: std::to_string(ps_stream.size()) bytes.);Log(--- 示例程序结束 ---);return 0;
} Nalu封装PS流
逻辑分析
PS 流是 MPEG-2 标准中用于存储程序内容的一种格式。它主要用于存储和传输已经复用的音视频基本流。要将 RTP 包放入 PS 流中通常的做法是先将 RTP 包转换为 PES包然后再将 PES 包复用进 PS 流其中GB28181平台就是需要将视频封装成PS流进行传输 注意事项 之前自己在书写项目的时候该处的逻辑有些混淆认为应该先将Nalu封装成RTP包然后通过PS流发送出去。这里就涉及了严重的概念混淆的错误。真正的流程应该如下后续补充对PS流基础概念和原理的认识
将RTP作为一个传输协议最终PS流中应该包含的是音视频的PES包所以还需要对其进行包装一层 主要流程分析 封装成PES等包在开源社区有已经封装好的代码该处只简单分析其逻辑
从RTP包中解析出来NaluNALU 封装成 PES 包 (Packetized Elementary Stream) PES 头部 (PES Header) 包起始码前缀0x00 0x00 0x01流ID主要用于标识流的类PES包长度指示 PES 包的长度不包括包起始码前缀和自身长度字段。如果长度未知可以设置为 0但通常应计算实际长度PES头部标志位指示其是否包含有PTS/DTS信息PTS/DTS PTS (显示时间戳): 指示 PES 包中数据的显示时间。应该从 RTP 包的时间戳转换过来DTS (解码时间戳): 指示 PES 包中数据的解码时间。对于 I 帧DTS 通常等于 PTS。对于 B 帧DTS 可能早于 PTS。对于 H.265如果只包含 P 和 I 帧DTS 通常可以等于 PTS PES负载 PES 负载 (PES Payload): 将一个或多个完整的 NALU 放入 PES 负载中。 你可以选择每个 PES 包放一个 NALU或者将多个小的 NALU 组合到一个 PES 包中 构建PS流 PS 头部 (Program Stream Header): 每个 PS 包的开始 包起始码前缀 (Packet Start Code Prefix)0x00 0x00 0x01系统时钟参考 (System Clock Reference, SCR): 用于同步解码器时钟复用率 (Mux Rate): 指示 PS 流的比特率 系统头部 (System Header): 描述整个 PS 流的系统级别信息通常在 PS 流的开始处出现一次 系统头部起始码 (System Header Start Code): 0x00 0x00 0x01 0xBB速率边界 (Rate Bound): 流的最大比特率音频边界 (Audio Bound): 音频流的数量视频边界 (Video Bound): 视频流的数量固定标志位 (Fixed Flag): 指示是否为固定比特率CSPS_flag, System_audio_lock_flag, System_video_lock_flag: 同步标志视频和音频流的 PID (Program ID): 标识视频和音频流 程序流映射 (Program Stream Map, PSM): 描述程序的内容包括音频和视频流的类型和 PIDPES 包: 将封装好的 PES 包按时间顺序复用进 PS 流中。可以交错放置视频和音频 PES 包 时间戳处理PTS/DTS 将 RTP 包的时间戳信息转换为 PES 包的 PTS/DTS要确保 PTS/DTS 值正确反映视频帧的显示和解码时间时间戳单位需要与 PS 流的标准时钟频率 (通常是 90kHz) 匹配
代码实现
下述代码只作为参考详细封装参考下一个章节笔记 #include iostream
#include vector
#include cstdint
#include cstring
#include iomanip // 用于十六进制输出格式化
#include ctime // 用于日志中的时间戳
#include sstream // 用于字符串流// --- 常量定义 ---
const uint16_t VIDEO_STREAM_ID 0xE0; // 视频流的 Stream ID 示例
const size_t MAX_PES_PAYLOAD_SIZE 2048; // PES 负载最大尺寸示例根据需要调整
const uint32_t PS_START_CODE_PREFIX 0x000001BA;
const uint32_t PES_START_CODE_PREFIX 0x000001;
const uint8_t NAL_START_CODE_4_BYTE[] {0x00, 0x00, 0x00, 0x01};// --- 结构体定义 ---
#pragma pack(push, 1) // 确保结构体内部没有填充字节// 简化的 PES 头部 (关注 PTS)
struct PESHeader {uint32_t packet_start_code_prefix;uint8_t stream_id;uint16_t pes_packet_length; // 暂时设置为 0之后计算uint8_t pes_scrambling_control_indicator_etc; // 标志位和指示器uint8_t pes_header_data_length;uint64_t pts_dts_flags_pts; // PTS 标志和 PTS 值 (简化示例)PESHeader() : packet_start_code_prefix(PES_START_CODE_PREFIX), stream_id(VIDEO_STREAM_ID), pes_packet_length(0),pes_scrambling_control_indicator_etc(0x80), // PTS_DTS_flags: 0b10 (仅 PTS)pes_header_data_length(5), // 仅 PTS 占用 5 字节pts_dts_flags_pts(0) {} // PTS 值稍后设置
};// 极简 PS 头部 (仅用于示例)
struct PSHeader {uint32_t packet_start_code_prefix;uint64_t system_clock_reference; // SCR (System Clock Reference) - 简化PSHeader() : packet_start_code_prefix(PS_START_CODE_PREFIX), system_clock_reference(0) {}
};#pragma pack(pop) // 恢复默认 packing// --- 全局计数器和变量 ---
static uint64_t pes_pts_counter 0; // PES PTS 计数器示例// --- 日志记录函数 ---
void Log(const std::string message) {std::time_t now std::time(nullptr);std::tm local_time;localtime_r(now, local_time);char timestamp_str[20];std::strftime(timestamp_str, sizeof(timestamp_str), %Y-%m-%d %H:%M:%S, local_time);std::cout [ timestamp_str ] message std::endl;
}// --- 辅助函数将数据转换为十六进制字符串 ---
std::string ToHex(const uint8_t* data, size_t size) {std::stringstream hex_stream;hex_stream std::hex std::setfill(0);for (size_t i 0; i size; i) {hex_stream std::setw(2) static_castint(data[i]) ;}return hex_stream.str();
}// --- 阶段 1: NALU 封装为 PES 包 ---
std::vectoruint8_t EncapsulateNALUtoPES(const std::vectoruint8_t nalu_data) {Log(--- 开始 NALU 到 PES 的封装 ---);std::vectoruint8_t pes_packet_buffer;PESHeader pes_header;pes_header.pts_dts_flags_pts (static_castuint64_t(pes_pts_counter) 3) | 0x02; // 仅设置 PTS 标志uint64_t pts_33_to_1 (pes_pts_counter * 300) % 0x200000000LL; // 假设 300 ticks/ms, 90kHz clockuint32_t pts_32_to_2 pts_33_to_1 0xFFFFFFFE0LL;uint8_t pts_byte0 0x20 | ((pts_32_to_2 30) 0x07) 1 | 0x01;uint16_t pts_byte1_2 (pts_32_to_2 15) 0xFFFF;uint16_t pts_byte3_4 pts_32_to_2 0xFFFF;pes_header.pts_dts_flags_pts | (static_castuint64_t(pts_byte0) 40);pes_header.pts_dts_flags_pts | (static_castuint64_t(pts_byte1_2) 24);pes_header.pts_dts_flags_pts | (static_castuint64_t(pts_byte3_4) 8);pes_header.pes_packet_length htons(sizeof(PESHeader) nalu_data.size() - 6); // PES 包长度不包括 包起始码前缀 和 长度字段自身pes_packet_buffer.resize(sizeof(PESHeader));memcpy(pes_packet_buffer.data(), pes_header, sizeof(PESHeader));Log(创建 PES 头部: ToHex(pes_packet_buffer.data(), sizeof(PESHeader)));Log(PES PTS Value: std::to_string(pes_pts_counter));// 将 NALU 数据直接添加到 PES 负载pes_packet_buffer.insert(pes_packet_buffer.end(), nalu_data.begin(), nalu_data.end());Log(PES Payload (NALU 数据): Size std::to_string(nalu_data.size()) bytes.);Log(--- NALU 到 PES 封装完成. PES Packet Size: std::to_string(pes_packet_buffer.size()) bytes. ---\n);return pes_packet_buffer;
}// --- 阶段 2: 将 PES 包放入 PS 流 (简化示例仅包含 PS 头部和 PES 包) ---
std::vectoruint8_t CreatePSStream(const std::vectoruint8_t pes_packet) {Log(--- 开始创建 PS 流 ---);std::vectoruint8_t ps_stream_buffer;PSHeader ps_header;// SCR (System Clock Reference) 示例 - 非常简化ps_header.system_clock_reference (static_castuint64_t(pes_pts_counter * 300) 3) | 0x01; // 90kHz clock, marker_bits 01ps_stream_buffer.resize(sizeof(PSHeader));memcpy(ps_stream_buffer.data(), ps_header, sizeof(PSHeader));Log(创建 PS 头部: ToHex(ps_stream_buffer.data(), sizeof(PSHeader)));ps_stream_buffer.insert(ps_stream_buffer.end(), pes_packet.begin(), pes_packet.end());Log(将 PES 包添加到 PS 流. PES Packet Size: std::to_string(pes_packet.size()) bytes.);Log(--- PS 流创建完成. Total PS Stream Size: std::to_string(ps_stream_buffer.size()) bytes. ---\n);return ps_stream_buffer;
}int main() {Log(--- 示例程序开始 ---);// 示例 H.265 NALU 数据 (这里用一些虚拟数据代替实际的 H.265 NALU)std::vectoruint8_t h265_nalu_data {0x00, 0x00, 0x00, 0x01, // NALU Start Code (4-byte) - 虽然 PS 流中通常不直接包含这里为了更贴近NALU理解先加上实际情况可能不需要0x40, // NALU Header (example type)0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,// ... 假设这里是真实的 H.265 NALU 负载数据 ...};int nalu_type 32; // 示例 NALU 类型 (VPS)Log(原始 H.265 NALU 数据: Size std::to_string(h265_nalu_data.size()) bytes, Type std::to_string(nalu_type));Log(ToHex(h265_nalu_data.data(), h265_nalu_data.size()));// 阶段 1: NALU 封装成 PES 包std::vectoruint8_t pes_packet EncapsulateNALUtoPES(h265_nalu_data);// 阶段 2: 创建 PS 流std::vectoruint8_t ps_stream CreatePSStream(pes_packet);Log(\n--- 最终 PES 包 (放入 PS 流之前) ---);Log(PES Packet Hex Data (First 64 bytes): \n ToHex(pes_packet.data(), std::min((size_t)64, pes_packet.size())));Log(PES Packet Size: std::to_string(pes_packet.size()) bytes.);Log(\n--- 最终 PS 流 (First 64 bytes) ---);Log(PS Stream Hex Data (First 64 bytes): \n ToHex(ps_stream.data(), std::min((size_t)64, ps_stream.size())));Log(PS Stream Size: std::to_string(ps_stream.size()) bytes.);Log(--- 示例程序结束 ---);return 0;
}
GB28181平台下H265传输逻辑总结 代码实现 roothcss-ecs-b4a9:/home/test/rtp/nalu# ./test7
[2025-02-23 21:37:20] --- 示例程序开始 ---
[2025-02-23 21:37:20] [process_request] 开始模拟推流, NALU 数量: 5
[2025-02-23 21:37:20] 处理 NALU, Type: 32, Keyframe: Yes, Size: 50 bytes.
[2025-02-23 21:37:20] RTP 分包数: 1
[2025-02-23 21:37:20] 发送网络包, 大小: 165 bytes.
[2025-02-23 21:37:20] Packet Data (First 64 bytes):
80 e0 00 00 00 00 00 00 12 34 56 78 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 ba 01
[2025-02-23 21:37:20] 处理 NALU, Type: 33, Keyframe: Yes, Size: 100 bytes.
[2025-02-23 21:37:20] RTP 分包数: 1
[2025-02-23 21:37:20] 发送网络包, 大小: 265 bytes.
[2025-02-23 21:37:20] Packet Data (First 64 bytes):
80 e0 00 01 00 00 0e a6 12 34 56 78 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66
[2025-02-23 21:37:20] 处理 NALU, Type: 34, Keyframe: Yes, Size: 30 bytes.
[2025-02-23 21:37:20] RTP 分包数: 1
[2025-02-23 21:37:20] 发送网络包, 大小: 125 bytes.
[2025-02-23 21:37:20] Packet Data (First 64 bytes):
80 e0 00 02 00 00 1d 4c 12 34 56 78 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 ba 01 00 00 4c 1d 00 00 00 00 00 00 00 00 bb 01 00 00 00 02 00 00
[2025-02-23 21:37:20] 处理 NALU, Type: 19, Keyframe: Yes, Size: 2048 bytes.
[2025-02-23 21:37:20] RTP 分包数: 2
[2025-02-23 21:37:20] 发送网络包, 大小: 1412 bytes.
[2025-02-23 21:37:20] Packet Data (First 64 bytes):
80 60 00 03 00 00 2b f2 12 34 56 78 ba 01 00 00 f2 2b 00 00 00 00 00 00 00 00 bb 01 00 00 00 02 00 00 bc 01 00 00 00 06 00 00 00 00 00 00 01 00 00 00 e0 08 0d 80 05 1e 00 00 00 00 21 00 00 00
[2025-02-23 21:37:20] 发送网络包, 大小: 713 bytes.
[2025-02-23 21:37:20] Packet Data (First 64 bytes):
80 e0 00 04 00 00 2b f2 12 34 56 78 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f
[2025-02-23 21:37:21] 处理 NALU, Type: 1, Keyframe: No, Size: 1500 bytes.
[2025-02-23 21:37:21] RTP 分包数: 2
[2025-02-23 21:37:21] 发送网络包, 大小: 1412 bytes.
[2025-02-23 21:37:21] Packet Data (First 64 bytes):
80 60 00 05 00 00 3a 98 12 34 56 78 ba 01 00 00 98 3a 00 00 00 00 00 00 00 00 01 00 00 00 e0 05 e9 80 05 27 00 00 00 00 21 00 00 00 00 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa
[2025-02-23 21:37:21] 发送网络包, 大小: 145 bytes.
[2025-02-23 21:37:21] Packet Data (First 64 bytes):
80 e0 00 06 00 00 3a 98 12 34 56 78 ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22
[2025-02-23 21:37:21] --- 示例程序结束 ---#include iostream
#include vector
#include cstdint
#include cstring
#include iomanip // 用于十六进制输出格式化
#include ctime // 用于日志中的时间戳
#include sstream // 用于字符串流
#include netinet/in.h // 包含 htons 的声明
#include chrono
#include thread
#include numeric // std::iota
#include malloc.h// --- 常量定义 ---
const uint16_t VIDEO_STREAM_ID 0xE0; // 视频流的 Stream ID 示例
const size_t MAX_RTP_PAYLOAD_SIZE 1400; // RTP 负载最大尺寸示例
const uint32_t PS_START_CODE_PREFIX 0x000001BA;
const uint32_t PES_START_CODE_PREFIX 0x000001;
const uint8_t NAL_START_CODE_4_BYTE[] {0x00, 0x00, 0x00, 0x01};const int PS_HDR_LEN 14; // 示例 PS Header Length
const int SYS_HDR_LEN 8; // 示例 System Header Length
const int PSM_HDR_LEN 12; // 示例 PSM Header Length
const int PES_HDR_LEN 19; // 示例 PES Header Length
const int RTP_HDR_LEN 12; // 示例 RTP Header Length// --- 结构体定义 ---
#pragma pack(push, 1) // 确保结构体内部没有填充字节// 简化的 PS 头部 (示例)
struct PSHeader {uint32_t packet_start_code_prefix;uint64_t system_clock_reference; // SCRPSHeader() : packet_start_code_prefix(PS_START_CODE_PREFIX), system_clock_reference(0) {}
};// 简化的系统头部 (示例)
struct SystemHeader {uint32_t system_header_start_code;uint16_t header_length;uint8_t rate_bound[3];uint8_t audio_bound;uint8_t fixed_flag_etc;SystemHeader() : system_header_start_code(0x000001BB), header_length(0), audio_bound(0), fixed_flag_etc(0) {rate_bound[0] rate_bound[1] rate_bound[2] 0;}
};// 简化的 PSM 头部 (示例)
struct PSMHeader {uint32_t program_stream_map_start_code;uint16_t psm_length;uint8_t program_number[2];uint8_t version_current_next_indicator;uint8_t section_number;uint8_t last_section_number;uint8_t program_info_length[2];// ... (省略 Program Stream Info 和 ES Info 循环) ...PSMHeader() : program_stream_map_start_code(0x000001BC), psm_length(0), version_current_next_indicator(0), section_number(0), last_section_number(0) {program_number[0] program_number[1] 0;program_info_length[0] program_info_length[1] 0;}
};// 简化的 PES 头部 (关注 PTS)
struct PESHeader {uint32_t packet_start_code_prefix;uint8_t stream_id;uint16_t pes_packet_length;uint8_t pes_scrambling_control_indicator_etc;uint8_t pes_header_data_length;uint64_t pts_dts_flags_pts;PESHeader() : packet_start_code_prefix(PES_START_CODE_PREFIX), stream_id(VIDEO_STREAM_ID), pes_packet_length(0),pes_scrambling_control_indicator_etc(0x80), // PTS_DTS_flags: 0b10 (仅 PTS)pes_header_data_length(5),pts_dts_flags_pts(0) {}
};// 简化的 RTP 头部
struct RTPHeader {uint8_t version_padding_extension_csrc_count; // V, P, X, CCuint8_t marker_payload_type; // M, PTuint16_t sequence_number;uint32_t timestamp;uint32_t ssrc;RTPHeader() : version_padding_extension_csrc_count(0x80), marker_payload_type(96), sequence_number(0), timestamp(0), ssrc(0x12345678) {} // SSRC 示例
};// Nalu 结构体
struct Nalu {int type;int length;std::vectoruint8_t packet;Nalu() : type(0), length(0) {}~Nalu() {}
};using NaluType int;#pragma pack(pop) // 恢复默认 packing// --- 全局计数器和变量 ---
static uint64_t pes_pts_counter 0; // PES PTS 计数器示例
static uint16_t rtp_seq_counter 0;// --- 日志记录函数 ---
void Log(const std::string message) {std::time_t now std::time(nullptr);std::tm local_time;localtime_r(now, local_time);char timestamp_str[20];std::strftime(timestamp_str, sizeof(timestamp_str), %Y-%m-%d %H:%M:%S, local_time);std::cout [ timestamp_str ] message std::endl;
}// --- 辅助函数将数据转换为十六进制字符串 ---
std::string ToHex(const uint8_t* data, size_t size) {std::stringstream hex_stream;hex_stream std::hex std::setfill(0);for (size_t i 0; i size; i) {hex_stream std::setw(2) static_castint(data[i]) ;}return hex_stream.str();
}// --- 将 VPS, SPS, PPS 处理为一个流 ---
std::vectoruint8_t vps_data;
std::vectoruint8_t sps_data;
std::vectoruint8_t pps_data;void out_nalu(char *buffer, int size, NaluType naluType) {if (naluType 32) { // VPSvps_data.resize(size);memcpy(vps_data.data(), buffer, size);} else if (naluType 33) { // SPSsps_data.resize(size);memcpy(sps_data.data(), buffer, size);} else if (naluType 34) { // PPSpps_data.resize(size);memcpy(pps_data.data(), buffer, size);} else {Nalu *nalu new Nalu;bool is_i_frame (naluType 19); // IDR framechar *packet (char *)malloc(is_i_frame ? (size vps_data.size() sps_data.size() pps_data.size()) : size);if (is_i_frame) {memcpy(packet, vps_data.data(), vps_data.size());memcpy(packet vps_data.size(), sps_data.data(), sps_data.size());memcpy(packet vps_data.size() sps_data.size(), pps_data.data(), pps_data.size());memcpy(packet vps_data.size() sps_data.size() pps_data.size(), buffer, size);size (vps_data.size() sps_data.size() pps_data.size());} else {memcpy(packet, buffer, size);}nalu-packet std::vectoruint8_t(packet, packet size);nalu-length size;nalu-type naluType;// 将 nalu 添加到 nalu_vector 或进行其他处理delete[] packet;}
}// --- 头部生成函数 ---
void gb28181_make_ps_header(char *header, long pts) {PSHeader ps_header_struct;ps_header_struct.system_clock_reference pts; // 简化 SCRmemcpy(header, ps_header_struct, sizeof(PSHeader));
}void gb28181_make_sys_header(char *header, int rate_bound) {SystemHeader sys_header_struct;sys_header_struct.header_length htons(SYS_HDR_LEN - 6); // Length after header_length fieldsys_header_struct.rate_bound[0] (rate_bound 16) 0xFF;sys_header_struct.rate_bound[1] (rate_bound 8) 0xFF;sys_header_struct.rate_bound[2] rate_bound 0xFF;memcpy(header, sys_header_struct, sizeof(SystemHeader));
}void gb28181_make_psm_header(char *header) {PSMHeader psm_header_struct;psm_header_struct.psm_length htons(PSM_HDR_LEN - 6); // Length after psm_length fieldmemcpy(header, psm_header_struct, sizeof(PSMHeader));
}void gb28181_make_pes_header(char *header, int stream_id, int data_len, long pts, long dts) {PESHeader pes_header_struct;pes_header_struct.stream_id stream_id;pes_header_struct.pes_packet_length htons(data_len PES_HDR_LEN - 6); // Length after pes_packet_length fieldpes_header_struct.pts_dts_flags_pts (static_castuint64_t(pes_pts_counter) 3) | 0x02; // 仅设置 PTS 标志uint64_t pts_33_to_1 (pes_pts_counter * 300) % 0x200000000LL; // 假设 300 ticks/ms, 90kHz clockuint32_t pts_32_to_2 pts_33_to_1 0xFFFFFFFE0LL;uint8_t pts_byte0 0x20 | ((pts_32_to_2 30) 0x07) 1 | 0x01;uint16_t pts_byte1_2 (pts_32_to_2 15) 0xFFFF;uint16_t pts_byte3_4 pts_32_to_2 0xFFFF;pes_header_struct.pts_dts_flags_pts | (static_castuint64_t(pts_byte0) 40);pes_header_struct.pts_dts_flags_pts | (static_castuint64_t(pts_byte1_2) 24);pes_header_struct.pts_dts_flags_pts | (static_castuint64_t(pts_byte3_4) 8);memcpy(header, pes_header_struct, sizeof(PESHeader));
}void gb28181_make_rtp_header(char *header, int seq, long pts, int ssrc, bool marker) {RTPHeader rtp_header_struct;rtp_header_struct.sequence_number htons(seq);rtp_header_struct.timestamp htonl(pts);rtp_header_struct.ssrc htonl(ssrc);if (marker) {rtp_header_struct.marker_payload_type | (1 7); // Set marker bit}memcpy(header, rtp_header_struct, sizeof(RTPHeader));
}// --- 发送网络数据包 ---
void send_network_packet(char *packet, int packet_size) {Log(发送网络包, 大小: std::to_string(packet_size) bytes.);// 模拟发送网络包实际应用中替换为 socket send 操作Log(Packet Data (First 64 bytes): \n ToHex((uint8_t*)packet, std::min((size_t)64, (size_t)packet_size)));
}// --- 判断是否为关键帧 ---
bool is_keyframe(NaluType type) {// 这里可以根据 NALU 类型判断是否为关键帧// 例如对于 H.264, IDR 帧 (type 5) 是关键帧对于 H.265, IDR_W_RADL (type 19) 和 IDR_N_LP (type 20) 是关键帧// 在你的代码中type 19 被认为是 IDR 帧return (type 19 || type 32 || type 33 || type 34); // 假设 VPS, SPS, PPS 也被视为关键帧用于某些 header 的添加逻辑
}// --- 主程序 ---
int main() {Log(--- 示例程序开始 ---);// 模拟更真实的 NALU 数组包含 VPS, SPS, PPS, IDR 关键帧, 普通帧std::vectorNalu* nalu_vector_sim;// 模拟 VPS, SPS, PPS (通常在 IDR 帧前)struct Nalu* vps_nalu new Nalu();vps_nalu-type 32; // VPS_NUTvps_nalu-length 50;vps_nalu-packet.resize(vps_nalu-length);std::iota(vps_nalu-packet.begin(), vps_nalu-packet.end(), 1);nalu_vector_sim.push_back(vps_nalu);out_nalu((char*)vps_nalu-packet.data(), vps_nalu-length, 32);struct Nalu* sps_nalu new Nalu();sps_nalu-type 33; // SPS_NUTsps_nalu-length 100;sps_nalu-packet.resize(sps_nalu-length);std::iota(sps_nalu-packet.begin(), sps_nalu-packet.end(), 51);nalu_vector_sim.push_back(sps_nalu);out_nalu((char*)sps_nalu-packet.data(), sps_nalu-length, 33);struct Nalu* pps_nalu new Nalu();pps_nalu-type 34; // PPS_NUTpps_nalu-length 30;pps_nalu-packet.resize(pps_nalu-length);std::iota(pps_nalu-packet.begin(), pps_nalu-packet.end(), 151);nalu_vector_sim.push_back(pps_nalu);out_nalu((char*)pps_nalu-packet.data(), pps_nalu-length, 34);// 模拟 IDR 关键帧struct Nalu* idr_nalu new Nalu();idr_nalu-type 19; // IDR_W_RADL (示例 IDR 类型)idr_nalu-length 2048;idr_nalu-packet.resize(idr_nalu-length);std::iota(idr_nalu-packet.begin(), idr_nalu-packet.end(), 201);nalu_vector_sim.push_back(idr_nalu);out_nalu((char*)idr_nalu-packet.data(), idr_nalu-length, 19);// 模拟 普通帧 (非关键帧)struct Nalu* non_idr_nalu new Nalu();non_idr_nalu-type 1; // TRAIL_R_NUT (示例 普通帧类型)non_idr_nalu-length 1500;non_idr_nalu-packet.resize(non_idr_nalu-length);std::iota(non_idr_nalu-packet.begin(), non_idr_nalu-packet.end(), 2200);nalu_vector_sim.push_back(non_idr_nalu);out_nalu((char*)non_idr_nalu-packet.data(), non_idr_nalu-length, 1);// RTP 发送处理略...int time_base 90000;int fps 24;int send_packet_interval 1000 / fps;int interval time_base / fps;long pts 0;int single_packet_max_length 1400;int ssrc_val 0x12345678; // 示例 SSRCstd::string rtp_protocol_type UDP/RTP/AVP; // 或 TCP/RTP/AVPLog([process_request] 开始模拟推流, NALU 数量: std::to_string(nalu_vector_sim.size()));for (auto* nalu : nalu_vector_sim) {const NaluType type nalu-type;const int length nalu-length;const uint8_t* packet nalu-packet.data();const bool is_key is_keyframe(type);Log(处理 NALU, Type: std::to_string(type) , Keyframe: (is_key ? Yes : No) , Size: std::to_string(length) bytes.);// 在遇到 VPS、SPS、PPS 时先进行封装char frame_buffer[1024 * 128]; // 帧数据缓冲区int frame_index 0;char ps_header_buf[PS_HDR_LEN];char sys_header_buf[SYS_HDR_LEN];char psm_header_buf[PSM_HDR_LEN];char pes_header_buf[PES_HDR_LEN];char rtp_packet_buf[RTP_HDR_LEN 1400];// 声明 rtp_header_bufchar rtp_header_buf[RTP_HDR_LEN];// 封装 VPS, SPS, PPSif (type 32 || type 33 || type 34) { // VPS, SPS, PPSmemcpy(frame_buffer frame_index, packet, length);frame_index length;}// --- PS 封装 ---gb28181_make_ps_header(ps_header_buf, pts);memcpy(frame_buffer frame_index, ps_header_buf, PS_HDR_LEN);frame_index PS_HDR_LEN;if (is_key) {gb28181_make_sys_header(sys_header_buf, 0x3f); // 示例 rate_boundmemcpy(frame_buffer frame_index, sys_header_buf, SYS_HDR_LEN);frame_index SYS_HDR_LEN;gb28181_make_psm_header(psm_header_buf);memcpy(frame_buffer frame_index, psm_header_buf, PSM_HDR_LEN);frame_index PSM_HDR_LEN;}// --- PES 封装 ---gb28181_make_pes_header(pes_header_buf, 0xe0, length, pts, pts);memcpy(frame_buffer frame_index, pes_header_buf, PES_HDR_LEN);frame_index PES_HDR_LEN;memcpy(frame_buffer frame_index, packet, length);frame_index length;// --- RTP 分包发送 ---int rtp_packet_count (frame_index single_packet_max_length - 1) / single_packet_max_length;Log(RTP 分包数: std::to_string(rtp_packet_count));for (int i 0; i rtp_packet_count; i) {bool is_last_packet (i rtp_packet_count - 1);gb28181_make_rtp_header(rtp_header_buf, rtp_seq_counter, pts, ssrc_val, is_last_packet);int offset i * single_packet_max_length;int data_size std::min(single_packet_max_length, frame_index - offset);int rtp_start_index 0;if (rtp_protocol_type TCP/RTP/AVP) {uint16_t packet_length RTP_HDR_LEN data_size;rtp_packet_buf[0] (packet_length 8) 0xFF;rtp_packet_buf[1] packet_length 0xFF;rtp_start_index 2;}memcpy(rtp_packet_buf rtp_start_index, rtp_header_buf, RTP_HDR_LEN);memcpy(rtp_packet_buf rtp_start_index RTP_HDR_LEN, frame_buffer offset, data_size);send_network_packet(rtp_packet_buf, rtp_start_index RTP_HDR_LEN data_size);rtp_seq_counter;}pts interval;std::this_thread::sleep_for(std::chrono::milliseconds(send_packet_interval));delete nalu; // 模拟 Device::push_rtp_stream 中的 nalu delete}Log(--- 示例程序结束 ---);return 0;
} 基本逻辑 VPS/SPS/PPS 缓冲 out_nalu函数会将 buffer 中的数据分别复制到全局变量中等待遇到关键帧的时候将其加入进入 IDR 帧处理 (关键帧) 将缓冲区中的VPS/PPS/SPS加入到Nalu之前 非 IDR 帧处理 (普通帧) 直接分配内存进行发送