【音视频】H265解码Nalu后封装rtp包

打印 上一主题 下一主题

主题 888|帖子 888|积分 2664

概述

基于ZLM流媒体框架以及简单RTSP服务器开源项目分析总结,相关源码参考以下链接

H265-rtp提取Nalu逻辑

通过rtsp流地址我们可以获取视频流中的多个rtp包,此中每个RTP包中又会包罗一个或者多个Nalu,将其提取处置惩罚
   总体逻辑分析
  核心逻辑在于对H265 RTP解复用器的利用,从RTP包中提取出来完备Nalu或者分片的Nalu


  • 吸收RTP数据包
  • 识别负载数据,然后处置惩罚RTP拓展头部信息
  • 识别NALU的类型,主要用于区分其是单一的Nalu还是分片的Nalu(FU类型为49)
  • 单一Nalu处置惩罚逻辑

    • 添加起始码,通过回调函数传递Nalu

  • 分片Nalu的处置惩罚

    • 重组Nalu:起首会在缓冲区中存储来自多个rtp包的分片数据,然后渐渐重组完备的Nalu
    • 通过FU头部标记(S,E)识别起始、中心和结束分片

      • 在起始分片中,从FU头部规复原始的Nalu类型
      • 结束分片的时间,添加起始码和重组的Nalu头部,并通过回调函数传递完备的Nalu数据


  • 最后通过回调函数进一步对H265的Nalu进行处置惩罚
   参考(RTP)
  1. *    0                   1                   2                   3
  2. *    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
  3. *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  4. *   |V=2|P|X|  CC   |M|     PT      |       sequence number         |
  5. *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  6. *   |                           timestamp                           |
  7. *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  8. *   |           synchronization source (SSRC) identifier            |
  9. *   +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
  10. *   |            contributing source (CSRC) identifiers             |
  11. *   :                             ....                              :
  12. *   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  13. *
  14. */
复制代码
  代码实现分析
  

  • 开始处置惩罚H265的RTP包,然后盘算RTP包的负载数据的起始地址以及负载数据的长度
  • 盘算的主要方法就是跳过RTP头部巨细
  1. struct RtpHeader *header = (struct RtpHeader *)data;
  2. int payload_type = header->payloadType;
  3. if(payload_type != payload_){
  4.     return;
  5. }
  6. const uint8_t* payload = data + sizeof(struct RtpHeader);
  7. size_t payload_len = size - sizeof(struct RtpHeader);
复制代码


  • 处置惩罚RTP拓展头部,起首需要判断其拓展头部是否存在
  • 存在拓展头部的处置惩罚

    • 盘算拓展头部的长度
    • 然后通过偏移量跳过RTP的头部和拓展头部,从而使得负载数据指向夺取的位置
    • 目的是保证其提取精确的Nalu数据

  1. if (header->extension){
  2.     const uint8_t *extension_data = payload;
  3.     size_t extension_length = 4 * (extension_data[2] << 8 | extension_data[3]);
  4.     size_t payload_offset = 4 + extension_length;
  5.     payload = payload + payload_offset;
  6.     payload_len = payload_len - payload_offset;
  7. }
复制代码


  • 判断Nalu的头部和分片类型

    • 如果是49分片类型,那么需要对分片数据进行特别处置惩罚

  1. struct H265NaluHeader *h265_header = (struct H265NaluHeader *)payload;
  2. if(h265_header->type == 49){ // 分片 (Fragmentation Unit - FU)
  3.     struct H265FUHeader *fu_header = (struct H265FUHeader *)&payload[2];
  4.     // ... 分片 NALU 的处理逻辑 ...
  5. }
  6. else{ // 单一封包 (Single NAL Unit)
  7.     // ... 单一 NALU 的处理逻辑 ...
  8. }
复制代码


  • 起首访问负载数据的头部
  • 处置惩罚起始分片
  • 然后将起始分片后的分片放入缓冲区
  • 最后遇到结束分片的时间,将其同一封装成一个Nalu即可
  1. struct H265FUHeader *fu_header = (struct H265FUHeader *)&payload[2];
  2. if(fu_header->s == 1){ // 起始分片 (Start fragment)
  3.     find_start_ = true;
  4.     if(pos_buffer_ == 0){ // 首次接收到起始分片
  5.         struct H265NaluHeader header = *h265_header;
  6.         header.type =  fu_header->type; // 从 FU 头部恢复原始 NALU 类型
  7.         buffer_[0] = 0;
  8.         buffer_[1] = 0;
  9.         buffer_[2] = 0;
  10.         buffer_[3] = 1; // NALU 起始码前缀
  11.         memcpy(buffer_ + 4, &header, sizeof(struct H265NaluHeader)); // 复制 NALU 头部
  12.         pos_buffer_ += 4 + sizeof(struct H265NaluHeader);
  13.     }
  14.     memcpy(buffer_ + pos_buffer_, payload + 3, payload_len - 3); // 复制分片数据
  15.     pos_buffer_ += payload_len - 3;
  16. }
  17. else if(fu_header->e == 1){ // 结束分片 (End fragment)
  18.     if(find_start_ == false){ // 尚未接收到起始分片
  19.         return;
  20.     }
  21.     memcpy(buffer_ + pos_buffer_, payload + 3, payload_len - 3); // 复制分片数据
  22.     pos_buffer_ += payload_len - 3;
  23.     if(call_back_){ // 调用回调函数,传递完整的 NALU
  24.         call_back_->OnVideoData(ntohl(header->timestamp),  buffer_, pos_buffer_);
  25.     }
  26.     find_start_ = false; // 重置状态,准备接收下一个 NALU
  27.     pos_buffer_ = 0;
  28. }
  29. else { // 中间分片 (Middle fragment)
  30.     if (!find_start_) { // 尚未接收到起始分片
  31.         return;
  32.     }
  33.     memcpy(buffer_ + pos_buffer_, payload + 3, payload_len - 3); // 复制分片数据
  34.     pos_buffer_ += payload_len - 3;
  35. }
复制代码


  • 处置惩罚单一封包的Nalu

    • 直接跳过起始码即可提取出RTP包中的Nalu

  1. else{ // 单一封包 (Single NAL Unit)
  2.     buffer_[0] = 0;
  3.     buffer_[1] = 0;
  4.     buffer_[2] = 0;
  5.     buffer_[3] = 1;
  6.     memcpy(buffer_ + 4, payload, payload_len);
  7.     if(call_back_){
  8.         call_back_->OnVideoData(ntohl(header->timestamp),  buffer_, payload_len + 4);
  9.     }
  10. }
复制代码
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): 设置为 0
          • R (Reserved bit): 必须为 0
          • FU type: 原始 NALU 类型的后 6 位 (从原始 NALU 头部中提取)

        • 分片数据:NALU的一部分数据

      • 中心分片

        • RTP头部信息
        • FU Indicator (1 字节):同起始分片
        • FU Header (1 字节)

          • S (Start bit): 设置为0
          • E (End bit): 设置为 0
          • R (Reserved bit): 必须为 0
          • FU type: 这里需要与起始分片类似

        • 分片数据:Nalu的一部分数据

      • 结束分片

        • RTP头部:序列号和时间戳递增,标记位 M 可以设置为 1,如果这是当前帧的最后一个 RTP 包
        • FU Indicator (1 字节):同起始分片
        • FU Header (1 字节)

          • S (Start bit): 设置为0
          • E (End bit): 设置为 1,表现分片的结束
          • R (Reserved bit): 必须为 0
          • FU type: 这里需要与起始分片类似

        • 分片数据:Nalu最后一部分的数据



  • 单一分片封装

    • RTP头部,标记位M可以设置为1吗,当前帧是最后一个Nalu的时间
    • Nalu数据(这里是去除Nalu的起始码,直接放入Nalu头部+负载数据)

  • 时间戳和序列号管理

    • 时间戳: 对于每个视频帧的第一个 RTP 包,设置时间戳为当前帧的显示时间。对于同一帧的后续分片包,时间戳保持稳固。

      • 下一个视频帧的第一个 RTP 包利用新的时间戳,时间戳的增量应该与视频帧率和时钟频率一致

    • 序列号: 为每个 RTP 包分配递增的序列号,起始序列号随机选择

代码实现

  1. #include <iostream>
  2. #include <vector>
  3. #include <cstdint>
  4. #include <cstring>
  5. #include <iomanip>  // 用于十六进制输出格式化
  6. #include <ctime>    // 用于日志中的时间戳
  7. #include <sstream>  // 用于字符串流
  8. // --- 常量定义 ---
  9. const uint8_t H265_PAYLOAD_TYPE = 96;
  10. const uint16_t RTP_VERSION = 2;
  11. const uint16_t VIDEO_STREAM_ID = 0xE0; // 视频流的 Stream ID 示例
  12. const size_t MAX_RTP_PAYLOAD_SIZE = 1400; // RTP 负载最大尺寸示例,根据 MTU 和头部开销调整
  13. const size_t MAX_PES_PAYLOAD_SIZE = 2048; // PES 负载最大尺寸示例,根据需要调整
  14. const uint32_t PS_START_CODE_PREFIX = 0x000001BA;
  15. const uint32_t PES_START_CODE_PREFIX = 0x000001;
  16. // --- 结构体定义 ---
  17. #pragma pack(push, 1) // 确保结构体内部没有填充字节
  18. // 简化的 RTP 头部
  19. struct RTPHeader {
  20.     uint8_t version_padding_extension_csrc_count; // V, P, X, CC
  21.     uint8_t marker_payload_type;                 // M, PT
  22.     uint16_t sequence_number;
  23.     uint32_t timestamp;
  24.     uint32_t ssrc;
  25.     RTPHeader() : version_padding_extension_csrc_count(0x80), marker_payload_type(H265_PAYLOAD_TYPE), sequence_number(0), timestamp(0), ssrc(0x12345678) {} // SSRC 示例
  26. };
  27. // FU 头部 (Fragmentation Unit A, FU-A)
  28. struct FUHeader {
  29.     uint8_t fu_header; // S, E, R, FU Type
  30.     FUHeader() : fu_header(0) {}
  31. };
  32. // FU 指示器 (Fragmentation Unit A, FU-A)
  33. struct FUIndicator {
  34.     uint8_t fu_indicator; // Type = 49 (FU), NALU type bits
  35.     FUIndicator() : fu_indicator(0) {}
  36. };
  37. // 简化的 PES 头部 (关注 PTS)
  38. struct PESHeader {
  39.     uint32_t packet_start_code_prefix;
  40.     uint8_t stream_id;
  41.     uint16_t pes_packet_length; // 暂时设置为 0,之后计算
  42.     uint8_t pes_scrambling_control_indicator_etc; // 标志位和指示器
  43.     uint8_t pes_header_data_length;
  44.     uint64_t pts_dts_flags_pts; // PTS 标志和 PTS 值 (简化示例)
  45.     PESHeader() : packet_start_code_prefix(PES_START_CODE_PREFIX), stream_id(VIDEO_STREAM_ID), pes_packet_length(0),
  46.                   pes_scrambling_control_indicator_etc(0x80), // PTS_DTS_flags: 0b10 (仅 PTS)
  47.                   pes_header_data_length(5), // 仅 PTS 占用 5 字节
  48.                   pts_dts_flags_pts(0) {} // PTS 值稍后设置
  49. };
  50. // 极简 PS 头部 (仅用于示例)
  51. struct PSHeader {
  52.     uint32_t packet_start_code_prefix;
  53.     uint64_t system_clock_reference; // SCR (System Clock Reference) - 简化
  54.     PSHeader() : packet_start_code_prefix(PS_START_CODE_PREFIX), system_clock_reference(0) {}
  55. };
  56. #pragma pack(pop) // 恢复默认 packing
  57. // --- 全局计数器和变量 ---
  58. static uint16_t rtp_sequence_number_counter = 0;
  59. static uint32_t rtp_timestamp_counter = 0;
  60. static uint64_t pes_pts_counter = 0; // PES PTS 计数器示例
  61. // --- 日志记录函数 ---
  62. void Log(const std::string& message) {
  63.     std::time_t now = std::time(nullptr);
  64.     std::tm local_time;
  65.     localtime_r(&now, &local_time);
  66.     char timestamp_str[20];
  67.     std::strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", &local_time);
  68.     std::cout << "[" << timestamp_str << "] " << message << std::endl;
  69. }
  70. // --- 辅助函数:将数据转换为十六进制字符串 ---
  71. std::string ToHex(const uint8_t* data, size_t size) {
  72.     std::stringstream hex_stream;
  73.     hex_stream << std::hex << std::setfill('0');
  74.     for (size_t i = 0; i < size; ++i) {
  75.         hex_stream << std::setw(2) << static_cast<int>(data[i]) << " ";
  76.     }
  77.     return hex_stream.str();
  78. }
  79. // --- 阶段 1: NALU 封装为 RTP 包 ---
  80. std::vector<std::vector<uint8_t>> EncapsulateNALUtoRTP(const std::vector<uint8_t>& nalu_data, int nalu_type) {
  81.     Log("--- 开始 NALU 到 RTP 的封装 ---");
  82.     std::vector<std::vector<uint8_t>> rtp_packets;
  83.     size_t nalu_size = nalu_data.size();
  84.     const uint8_t* nalu_payload = nalu_data.data();
  85.     size_t nalu_payload_offset = 0;
  86.     if (nalu_size <= MAX_RTP_PAYLOAD_SIZE) {
  87.         // 情况 1: NALU 足够小,可以放入单个 RTP 包
  88.         Log("NALU 尺寸足够小,可以使用单个 RTP 包。");
  89.         std::vector<uint8_t> rtp_packet_buffer(sizeof(RTPHeader) + nalu_size);
  90.         RTPHeader rtp_header;
  91.         rtp_header.sequence_number = htons(rtp_sequence_number_counter++);
  92.         rtp_header.timestamp = htonl(rtp_timestamp_counter);
  93.         rtp_header.marker_payload_type |= (1 << 7); // 设置 Marker 位 (示例,可能根据实际需求调整)
  94.         memcpy(rtp_packet_buffer.data(), &rtp_header, sizeof(RTPHeader));
  95.         memcpy(rtp_packet_buffer.data() + sizeof(RTPHeader), nalu_payload, nalu_size);
  96.         Log("创建 RTP 头部: " + ToHex(rtp_packet_buffer.data(), sizeof(RTPHeader)));
  97.         Log("RTP 负载 (NALU 数据): " + ToHex(rtp_packet_buffer.data() + sizeof(RTPHeader), nalu_size));
  98.         rtp_packets.push_back(rtp_packet_buffer);
  99.     } else {
  100.         // 情况 2: NALU 太大,需要分片 (FU-A)
  101.         Log("NALU 尺寸超过 RTP 负载限制,需要分片 (FU-A)。");
  102.         int fragment_number = 0;
  103.         bool first_fragment = true;
  104.         bool last_fragment = false;
  105.         while (nalu_payload_offset < nalu_size) {
  106.             size_t fragment_size = std::min(MAX_RTP_PAYLOAD_SIZE - sizeof(FUIndicator) - sizeof(FUHeader), nalu_size - nalu_payload_offset);
  107.             if (nalu_payload_offset + fragment_size == nalu_size) {
  108.                 last_fragment = true;
  109.             }
  110.             std::vector<uint8_t> rtp_packet_buffer(sizeof(RTPHeader) + sizeof(FUIndicator) + sizeof(FUHeader) + fragment_size);
  111.             RTPHeader rtp_header;
  112.             rtp_header.sequence_number = htons(rtp_sequence_number_counter++);
  113.             rtp_header.timestamp = htonl(rtp_timestamp_counter);
  114.             rtp_header.marker_payload_type &= ~(1 << 7); // 清除 Marker 位 (分片包通常不设置,除非是帧的最后一个分片)
  115.             if (last_fragment) rtp_header.marker_payload_type |= (1 << 7); // 在最后一个分片包上设置 Marker 位 (示例)
  116.             FUIndicator fu_indicator;
  117.             fu_indicator.fu_indicator = 49 << 1; // FU 类型 = 49
  118.             fu_indicator.fu_indicator |= ((nalu_type >> 5) & 0x01); // 复制原始 NALU 头部的 forbidden_zero_bit
  119.             FUHeader fu_header;
  120.             fu_header.fu_header = (nalu_type & 0x1F); // NAL 单元类型 (原始 NALU 类型的后 5 位)
  121.             if (first_fragment) fu_header.fu_header |= (1 << 7); // 设置 S 位 (起始分片)
  122.             if (last_fragment)  fu_header.fu_header |= (1 << 6); // 设置 E 位 (结束分片)
  123.             memcpy(rtp_packet_buffer.data(), &rtp_header, sizeof(RTPHeader));
  124.             memcpy(rtp_packet_buffer.data() + sizeof(RTPHeader), &fu_indicator, sizeof(FUIndicator));
  125.             memcpy(rtp_packet_buffer.data() + sizeof(RTPHeader) + sizeof(FUIndicator), &fu_header, sizeof(FUHeader));
  126.             memcpy(rtp_packet_buffer.data() + sizeof(RTPHeader) + sizeof(FUIndicator) + sizeof(FUHeader),
  127.                    nalu_payload + nalu_payload_offset, fragment_size);
  128.             Log("创建 RTP 头部 (分片 " + std::to_string(fragment_number) + "): " + ToHex(rtp_packet_buffer.data(), sizeof(RTPHeader)));
  129.             Log("FU 指示器: " + ToHex(rtp_packet_buffer.data() + sizeof(RTPHeader), sizeof(FUIndicator)));
  130.             Log("FU 头部: " + ToHex(rtp_packet_buffer.data() + sizeof(RTPHeader) + sizeof(FUIndicator), sizeof(FUHeader)));
  131.             Log("RTP 负载 (分片数据 " + std::to_string(fragment_number) + "): " + ToHex(rtp_packet_buffer.data() + sizeof(RTPHeader) + sizeof(FUIndicator) + sizeof(FUHeader), fragment_size));
  132.             rtp_packets.push_back(rtp_packet_buffer);
  133.             nalu_payload_offset += fragment_size;
  134.             first_fragment = false;
  135.             fragment_number++;
  136.         }
  137.     }
  138.     rtp_timestamp_counter += 3600; // 时间戳递增示例 (90kHz 时钟, 约 40ms 帧时长)
  139.     Log("--- NALU 到 RTP 封装完成,生成 " + std::to_string(rtp_packets.size()) + " 个 RTP 包。---\n");
  140.     return rtp_packets;
  141. }
  142. // --- 阶段 2: RTP 封装为 PES (更标准的做法应为 NALU 封装为 PES) ---
  143. std::vector<uint8_t> EncapsulateRTPtoPES(const std::vector<std::vector<uint8_t>>& rtp_packets, const std::vector<uint8_t>& original_nalu_data) {
  144.     Log("--- 开始 RTP (或 NALU) 到 PES 的封装 ---");
  145.     std::vector<uint8_t> pes_packet_buffer;
  146.     // 为了更符合 PS 流标准,理想情况下应该从 RTP 包中解封装出 NALU 数据,然后将 NALU 放入 PES。
  147.     // 但为了演示 "组成 rtp 包后通过 PS 流发送出去" 的字面意思,这里我们将 *整个 RTP 包* 放入 PES 负载 (这不太标准,但在某些特定场景下可能可行)。
  148.     // 在实际系统中,你可能更希望提取 RTP 中的 NALU 负载,然后放入 PES。
  149.     PESHeader pes_header;
  150.     pes_header.pts_dts_flags_pts = (static_cast<uint64_t>(pes_pts_counter++) << 3) | 0x02; // 仅设置 PTS 标志
  151.     uint64_t pts_33_to_1 = (pes_pts_counter * 300) % 0x200000000LL; // 假设 300 ticks/ms, 90kHz clock
  152.     uint32_t pts_32_to_2 = pts_33_to_1 & 0xFFFFFFFE0LL;
  153.     uint8_t pts_byte0 = 0x20 | ((pts_32_to_2 >> 30) & 0x07) << 1 | 0x01;
  154.     uint16_t pts_byte1_2 = (pts_32_to_2 >> 15) & 0xFFFF;
  155.     uint16_t pts_byte3_4 = pts_32_to_2 & 0xFFFF;
  156.     pes_header.pts_dts_flags_pts |= (static_cast<uint64_t>(pts_byte0) << 40);
  157.     pes_header.pts_dts_flags_pts |= (static_cast<uint64_t>(pts_byte1_2) << 24);
  158.     pes_header.pts_dts_flags_pts |= (static_cast<uint64_t>(pts_byte3_4) >> 8);
  159.     size_t pes_payload_size = 0;
  160.     for (const auto& rtp_packet : rtp_packets) {
  161.         pes_payload_size += rtp_packet.size();
  162.     }
  163.     pes_header.pes_packet_length = htons(sizeof(PESHeader) + pes_payload_size - 6); // PES 包长度不包括 包起始码前缀 和 长度字段自身
  164.     pes_packet_buffer.resize(sizeof(PESHeader));
  165.     memcpy(pes_packet_buffer.data(), &pes_header, sizeof(PESHeader));
  166.     Log("创建 PES 头部: " + ToHex(pes_packet_buffer.data(), sizeof(PESHeader)));
  167.     Log("PES PTS Value: " + std::to_string(pes_pts_counter));
  168.     for (const auto& rtp_packet : rtp_packets) {
  169.         pes_packet_buffer.insert(pes_packet_buffer.end(), rtp_packet.begin(), rtp_packet.end());
  170.     }
  171.     Log("PES Payload (RTP 包数据): Total size " + std::to_string(pes_payload_size) + " bytes.");
  172.     Log("--- RTP to PES Encapsulation Complete. PES Packet Size: " + std::to_string(pes_packet_buffer.size()) + " bytes. ---\n");
  173.     return pes_packet_buffer;
  174. }
  175. // --- 阶段 3: 将 PES 包放入 PS 流 (简化示例,仅包含 PS 头部和 PES 包) ---
  176. std::vector<uint8_t> CreatePSStream(const std::vector<uint8_t>& pes_packet) {
  177.     Log("--- 开始创建 PS 流 ---");
  178.     std::vector<uint8_t> ps_stream_buffer;
  179.     PSHeader ps_header;
  180.     // SCR (System Clock Reference) 示例 - 非常简化
  181.     ps_header.system_clock_reference = (static_cast<uint64_t>(pes_pts_counter * 300) << 3) | 0x01; // 90kHz clock, marker_bits = '01'
  182.     ps_stream_buffer.resize(sizeof(PSHeader));
  183.     memcpy(ps_stream_buffer.data(), &ps_header, sizeof(PSHeader));
  184.     Log("创建 PS 头部: " + ToHex(ps_stream_buffer.data(), sizeof(PSHeader)));
  185.     ps_stream_buffer.insert(ps_stream_buffer.end(), pes_packet.begin(), pes_packet.end());
  186.     Log("将 PES 包添加到 PS 流. PES Packet Size: " + std::to_string(pes_packet.size()) + " bytes.");
  187.     Log("--- PS 流创建完成. Total PS Stream Size: " + std::to_string(ps_stream_buffer.size()) + " bytes. ---\n");
  188.     return ps_stream_buffer;
  189. }
  190. int main() {
  191.     Log("--- 示例程序开始 ---");
  192.     // 示例 H.265 NALU 数据 (这里用一些虚拟数据代替实际的 H.265 NALU)
  193.     std::vector<uint8_t> h265_nalu_data = {
  194.         0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
  195.         // ... 假设这里是真实的 H.265 NALU 负载数据 ...
  196.     };
  197.     int nalu_type = 32; // 示例 NALU 类型 (VPS)
  198.     Log("原始 H.265 NALU 数据: Size = " + std::to_string(h265_nalu_data.size()) + " bytes, Type = " + std::to_string(nalu_type));
  199.     Log(ToHex(h265_nalu_data.data(), h265_nalu_data.size()));
  200.     // 阶段 1: NALU 封装成 RTP 包
  201.     std::vector<std::vector<uint8_t>> rtp_packets = EncapsulateNALUtoRTP(h265_nalu_data, nalu_type);
  202.     // 阶段 2: RTP 包封装成 PES 包
  203.     std::vector<uint8_t> pes_packet = EncapsulateRTPtoPES(rtp_packets, h265_nalu_data);
  204.      // 阶段 3: 创建 PS 流
  205.     std::vector<uint8_t> ps_stream = CreatePSStream(pes_packet);
  206.     Log("\n--- 最终 PES 包 (放入 PS 流之前) ---");
  207.     Log("PES Packet Hex Data (First 64 bytes): \n" + ToHex(pes_packet.data(), std::min((size_t)64, pes_packet.size())));
  208.     Log("PES Packet Size: " + std::to_string(pes_packet.size()) + " bytes.");
  209.     Log("\n--- 最终 PS 流 (First 64 bytes) ---");
  210.     Log("PS Stream Hex Data (First 64 bytes): \n" + ToHex(ps_stream.data(), std::min((size_t)64, ps_stream.size())));
  211.     Log("PS Stream Size: " + std::to_string(ps_stream.size()) + " bytes.");
  212.     Log("--- 示例程序结束 ---");
  213.     return 0;
  214. }
复制代码

Nalu封装PS流

逻辑分析

PS 流是 MPEG-2 尺度中用于存储程序内容的一种格式。它主要用于存储和传输已经复用的音视频基本流。要将 RTP 包放入 PS 流中,通常的做法是先将 RTP 包转换为 PES包,然后再将 PES 包复用进 PS 流,此中GB28181平台就是需要将视频封装成PS流进行传输
   注意事项
  之前本身在誊写项目的时间,该处的逻辑有些肴杂,以为应该先将Nalu封装成RTP包然后通过PS流发送出去。这里就涉及了严重的概念肴杂的错误。真正的流程应该如下,后续增补对PS流基础概念和原理的认识
将RTP作为一个传输协议,终极PS流中应该包罗的是音视频的PES包,所以还需要对其进行包装一层
   主要流程分析
  封装成PES等包,在开源社区有已经封装好的代码,该处只简单分析其逻辑 


  • 从RTP包中剖析出来Nalu
  • NALU 封装成 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): 形貌程序的内容,包括音频和视频流的类型和 PID
    • PES 包: 将封装好的 PES 包按时间顺序复用进 PS 流中。可以交织放置视频和音频 PES 包

  • 时间戳处置惩罚(PTS/DTS)

    • 将 RTP 包的时间戳信息转换为 PES 包的 PTS/DTS
    • 要确保 PTS/DTS 值精确反映视频帧的显示息争码时间
    • 时间戳单元需要与 PS 流的尺度时钟频率 (通常是 90kHz) 匹配

代码实现

下述代码只作为参考,具体封装参考下一个章节笔记

  1. #include <iostream>
  2. #include <vector>
  3. #include <cstdint>
  4. #include <cstring>
  5. #include <iomanip>  // 用于十六进制输出格式化
  6. #include <ctime>    // 用于日志中的时间戳
  7. #include <sstream>  // 用于字符串流
  8. // --- 常量定义 ---
  9. const uint16_t VIDEO_STREAM_ID = 0xE0; // 视频流的 Stream ID 示例
  10. const size_t MAX_PES_PAYLOAD_SIZE = 2048; // PES 负载最大尺寸示例,根据需要调整
  11. const uint32_t PS_START_CODE_PREFIX = 0x000001BA;
  12. const uint32_t PES_START_CODE_PREFIX = 0x000001;
  13. const uint8_t NAL_START_CODE_4_BYTE[] = {0x00, 0x00, 0x00, 0x01};
  14. // --- 结构体定义 ---
  15. #pragma pack(push, 1) // 确保结构体内部没有填充字节
  16. // 简化的 PES 头部 (关注 PTS)
  17. struct PESHeader {
  18.     uint32_t packet_start_code_prefix;
  19.     uint8_t stream_id;
  20.     uint16_t pes_packet_length; // 暂时设置为 0,之后计算
  21.     uint8_t pes_scrambling_control_indicator_etc; // 标志位和指示器
  22.     uint8_t pes_header_data_length;
  23.     uint64_t pts_dts_flags_pts; // PTS 标志和 PTS 值 (简化示例)
  24.     PESHeader() : packet_start_code_prefix(PES_START_CODE_PREFIX), stream_id(VIDEO_STREAM_ID), pes_packet_length(0),
  25.                   pes_scrambling_control_indicator_etc(0x80), // PTS_DTS_flags: 0b10 (仅 PTS)
  26.                   pes_header_data_length(5), // 仅 PTS 占用 5 字节
  27.                   pts_dts_flags_pts(0) {} // PTS 值稍后设置
  28. };
  29. // 极简 PS 头部 (仅用于示例)
  30. struct PSHeader {
  31.     uint32_t packet_start_code_prefix;
  32.     uint64_t system_clock_reference; // SCR (System Clock Reference) - 简化
  33.     PSHeader() : packet_start_code_prefix(PS_START_CODE_PREFIX), system_clock_reference(0) {}
  34. };
  35. #pragma pack(pop) // 恢复默认 packing
  36. // --- 全局计数器和变量 ---
  37. static uint64_t pes_pts_counter = 0; // PES PTS 计数器示例
  38. // --- 日志记录函数 ---
  39. void Log(const std::string& message) {
  40.     std::time_t now = std::time(nullptr);
  41.     std::tm local_time;
  42.     localtime_r(&now, &local_time);
  43.     char timestamp_str[20];
  44.     std::strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", &local_time);
  45.     std::cout << "[" << timestamp_str << "] " << message << std::endl;
  46. }
  47. // --- 辅助函数:将数据转换为十六进制字符串 ---
  48. std::string ToHex(const uint8_t* data, size_t size) {
  49.     std::stringstream hex_stream;
  50.     hex_stream << std::hex << std::setfill('0');
  51.     for (size_t i = 0; i < size; ++i) {
  52.         hex_stream << std::setw(2) << static_cast<int>(data[i]) << " ";
  53.     }
  54.     return hex_stream.str();
  55. }
  56. // --- 阶段 1: NALU 封装为 PES 包 ---
  57. std::vector<uint8_t> EncapsulateNALUtoPES(const std::vector<uint8_t>& nalu_data) {
  58.     Log("--- 开始 NALU 到 PES 的封装 ---");
  59.     std::vector<uint8_t> pes_packet_buffer;
  60.     PESHeader pes_header;
  61.     pes_header.pts_dts_flags_pts = (static_cast<uint64_t>(pes_pts_counter++) << 3) | 0x02; // 仅设置 PTS 标志
  62.     uint64_t pts_33_to_1 = (pes_pts_counter * 300) % 0x200000000LL; // 假设 300 ticks/ms, 90kHz clock
  63.     uint32_t pts_32_to_2 = pts_33_to_1 & 0xFFFFFFFE0LL;
  64.     uint8_t pts_byte0 = 0x20 | ((pts_32_to_2 >> 30) & 0x07) << 1 | 0x01;
  65.     uint16_t pts_byte1_2 = (pts_32_to_2 >> 15) & 0xFFFF;
  66.     uint16_t pts_byte3_4 = pts_32_to_2 & 0xFFFF;
  67.     pes_header.pts_dts_flags_pts |= (static_cast<uint64_t>(pts_byte0) << 40);
  68.     pes_header.pts_dts_flags_pts |= (static_cast<uint64_t>(pts_byte1_2) << 24);
  69.     pes_header.pts_dts_flags_pts |= (static_cast<uint64_t>(pts_byte3_4) >> 8);
  70.     pes_header.pes_packet_length = htons(sizeof(PESHeader) + nalu_data.size() - 6); // PES 包长度不包括 包起始码前缀 和 长度字段自身
  71.     pes_packet_buffer.resize(sizeof(PESHeader));
  72.     memcpy(pes_packet_buffer.data(), &pes_header, sizeof(PESHeader));
  73.     Log("创建 PES 头部: " + ToHex(pes_packet_buffer.data(), sizeof(PESHeader)));
  74.     Log("PES PTS Value: " + std::to_string(pes_pts_counter));
  75.     // 将 NALU 数据直接添加到 PES 负载
  76.     pes_packet_buffer.insert(pes_packet_buffer.end(), nalu_data.begin(), nalu_data.end());
  77.     Log("PES Payload (NALU 数据): Size " + std::to_string(nalu_data.size()) + " bytes.");
  78.     Log("--- NALU 到 PES 封装完成. PES Packet Size: " + std::to_string(pes_packet_buffer.size()) + " bytes. ---\n");
  79.     return pes_packet_buffer;
  80. }
  81. // --- 阶段 2: 将 PES 包放入 PS 流 (简化示例,仅包含 PS 头部和 PES 包) ---
  82. std::vector<uint8_t> CreatePSStream(const std::vector<uint8_t>& pes_packet) {
  83.     Log("--- 开始创建 PS 流 ---");
  84.     std::vector<uint8_t> ps_stream_buffer;
  85.     PSHeader ps_header;
  86.     // SCR (System Clock Reference) 示例 - 非常简化
  87.     ps_header.system_clock_reference = (static_cast<uint64_t>(pes_pts_counter * 300) << 3) | 0x01; // 90kHz clock, marker_bits = '01'
  88.     ps_stream_buffer.resize(sizeof(PSHeader));
  89.     memcpy(ps_stream_buffer.data(), &ps_header, sizeof(PSHeader));
  90.     Log("创建 PS 头部: " + ToHex(ps_stream_buffer.data(), sizeof(PSHeader)));
  91.     ps_stream_buffer.insert(ps_stream_buffer.end(), pes_packet.begin(), pes_packet.end());
  92.     Log("将 PES 包添加到 PS 流. PES Packet Size: " + std::to_string(pes_packet.size()) + " bytes.");
  93.     Log("--- PS 流创建完成. Total PS Stream Size: " + std::to_string(ps_stream_buffer.size()) + " bytes. ---\n");
  94.     return ps_stream_buffer;
  95. }
  96. int main() {
  97.     Log("--- 示例程序开始 ---");
  98.     // 示例 H.265 NALU 数据 (这里用一些虚拟数据代替实际的 H.265 NALU)
  99.     std::vector<uint8_t> h265_nalu_data = {
  100.         0x00, 0x00, 0x00, 0x01, // NALU Start Code (4-byte) - 虽然 PS 流中通常不直接包含,这里为了更贴近NALU理解先加上,实际情况可能不需要
  101.         0x40, // NALU Header (example type)
  102.         0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
  103.         // ... 假设这里是真实的 H.265 NALU 负载数据 ...
  104.     };
  105.     int nalu_type = 32; // 示例 NALU 类型 (VPS)
  106.     Log("原始 H.265 NALU 数据: Size = " + std::to_string(h265_nalu_data.size()) + " bytes, Type = " + std::to_string(nalu_type));
  107.     Log(ToHex(h265_nalu_data.data(), h265_nalu_data.size()));
  108.     // 阶段 1: NALU 封装成 PES 包
  109.     std::vector<uint8_t> pes_packet = EncapsulateNALUtoPES(h265_nalu_data);
  110.      // 阶段 2: 创建 PS 流
  111.     std::vector<uint8_t> ps_stream = CreatePSStream(pes_packet);
  112.     Log("\n--- 最终 PES 包 (放入 PS 流之前) ---");
  113.     Log("PES Packet Hex Data (First 64 bytes): \n" + ToHex(pes_packet.data(), std::min((size_t)64, pes_packet.size())));
  114.     Log("PES Packet Size: " + std::to_string(pes_packet.size()) + " bytes.");
  115.     Log("\n--- 最终 PS 流 (First 64 bytes) ---");
  116.     Log("PS Stream Hex Data (First 64 bytes): \n" + ToHex(ps_stream.data(), std::min((size_t)64, ps_stream.size())));
  117.     Log("PS Stream Size: " + std::to_string(ps_stream.size()) + " bytes.");
  118.     Log("--- 示例程序结束 ---");
  119.     return 0;
  120. }
复制代码
GB28181平台下H265传输逻辑总结

   代码实现
  1. root@hcss-ecs-b4a9:/home/test/rtp/nalu# ./test7
  2. [2025-02-23 21:37:20] --- 示例程序开始 ---
  3. [2025-02-23 21:37:20] [process_request] 开始模拟推流, NALU 数量: 5
  4. [2025-02-23 21:37:20] 处理 NALU, Type: 32, Keyframe: Yes, Size: 50 bytes.
  5. [2025-02-23 21:37:20] RTP 分包数: 1
  6. [2025-02-23 21:37:20] 发送网络包, 大小: 165 bytes.
  7. [2025-02-23 21:37:20] Packet Data (First 64 bytes):
  8. 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
  9. [2025-02-23 21:37:20] 处理 NALU, Type: 33, Keyframe: Yes, Size: 100 bytes.
  10. [2025-02-23 21:37:20] RTP 分包数: 1
  11. [2025-02-23 21:37:20] 发送网络包, 大小: 265 bytes.
  12. [2025-02-23 21:37:20] Packet Data (First 64 bytes):
  13. 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
  14. [2025-02-23 21:37:20] 处理 NALU, Type: 34, Keyframe: Yes, Size: 30 bytes.
  15. [2025-02-23 21:37:20] RTP 分包数: 1
  16. [2025-02-23 21:37:20] 发送网络包, 大小: 125 bytes.
  17. [2025-02-23 21:37:20] Packet Data (First 64 bytes):
  18. 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
  19. [2025-02-23 21:37:20] 处理 NALU, Type: 19, Keyframe: Yes, Size: 2048 bytes.
  20. [2025-02-23 21:37:20] RTP 分包数: 2
  21. [2025-02-23 21:37:20] 发送网络包, 大小: 1412 bytes.
  22. [2025-02-23 21:37:20] Packet Data (First 64 bytes):
  23. 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
  24. [2025-02-23 21:37:20] 发送网络包, 大小: 713 bytes.
  25. [2025-02-23 21:37:20] Packet Data (First 64 bytes):
  26. 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
  27. [2025-02-23 21:37:21] 处理 NALU, Type: 1, Keyframe: No, Size: 1500 bytes.
  28. [2025-02-23 21:37:21] RTP 分包数: 2
  29. [2025-02-23 21:37:21] 发送网络包, 大小: 1412 bytes.
  30. [2025-02-23 21:37:21] Packet Data (First 64 bytes):
  31. 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
  32. [2025-02-23 21:37:21] 发送网络包, 大小: 145 bytes.
  33. [2025-02-23 21:37:21] Packet Data (First 64 bytes):
  34. 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
  35. [2025-02-23 21:37:21] --- 示例程序结束 ---
复制代码
  1. #include <iostream>
  2. #include <vector>
  3. #include <cstdint>
  4. #include <cstring>
  5. #include <iomanip>  // 用于十六进制输出格式化
  6. #include <ctime>    // 用于日志中的时间戳
  7. #include <sstream>  // 用于字符串流
  8. #include <netinet/in.h> // 包含 htons 的声明
  9. #include <chrono>
  10. #include <thread>
  11. #include <numeric> // std::iota
  12. #include <malloc.h>
  13. // --- 常量定义 ---
  14. const uint16_t VIDEO_STREAM_ID = 0xE0; // 视频流的 Stream ID 示例
  15. const size_t MAX_RTP_PAYLOAD_SIZE = 1400; // RTP 负载最大尺寸示例
  16. const uint32_t PS_START_CODE_PREFIX = 0x000001BA;
  17. const uint32_t PES_START_CODE_PREFIX = 0x000001;
  18. const uint8_t NAL_START_CODE_4_BYTE[] = {0x00, 0x00, 0x00, 0x01};
  19. const int PS_HDR_LEN = 14; // 示例 PS Header Length
  20. const int SYS_HDR_LEN = 8; // 示例 System Header Length
  21. const int PSM_HDR_LEN = 12; // 示例 PSM Header Length
  22. const int PES_HDR_LEN = 19; // 示例 PES Header Length
  23. const int RTP_HDR_LEN = 12; // 示例 RTP Header Length
  24. // --- 结构体定义 ---
  25. #pragma pack(push, 1) // 确保结构体内部没有填充字节
  26. // 简化的 PS 头部 (示例)
  27. struct PSHeader {
  28.     uint32_t packet_start_code_prefix;
  29.     uint64_t system_clock_reference; // SCR
  30.     PSHeader() : packet_start_code_prefix(PS_START_CODE_PREFIX), system_clock_reference(0) {}
  31. };
  32. // 简化的系统头部 (示例)
  33. struct SystemHeader {
  34.     uint32_t system_header_start_code;
  35.     uint16_t header_length;
  36.     uint8_t rate_bound[3];
  37.     uint8_t audio_bound;
  38.     uint8_t fixed_flag_etc;
  39.     SystemHeader() : system_header_start_code(0x000001BB), header_length(0), audio_bound(0), fixed_flag_etc(0) {
  40.         rate_bound[0] = rate_bound[1] = rate_bound[2] = 0;
  41.     }
  42. };
  43. // 简化的 PSM 头部 (示例)
  44. struct PSMHeader {
  45.     uint32_t program_stream_map_start_code;
  46.     uint16_t psm_length;
  47.     uint8_t program_number[2];
  48.     uint8_t version_current_next_indicator;
  49.     uint8_t section_number;
  50.     uint8_t last_section_number;
  51.     uint8_t program_info_length[2];
  52.     // ... (省略 Program Stream Info 和 ES Info 循环) ...
  53.     PSMHeader() : program_stream_map_start_code(0x000001BC), psm_length(0), version_current_next_indicator(0), section_number(0), last_section_number(0) {
  54.         program_number[0] = program_number[1] = 0;
  55.         program_info_length[0] = program_info_length[1] = 0;
  56.     }
  57. };
  58. // 简化的 PES 头部 (关注 PTS)
  59. struct PESHeader {
  60.     uint32_t packet_start_code_prefix;
  61.     uint8_t stream_id;
  62.     uint16_t pes_packet_length;
  63.     uint8_t pes_scrambling_control_indicator_etc;
  64.     uint8_t pes_header_data_length;
  65.     uint64_t pts_dts_flags_pts;
  66.     PESHeader() : packet_start_code_prefix(PES_START_CODE_PREFIX), stream_id(VIDEO_STREAM_ID), pes_packet_length(0),
  67.                   pes_scrambling_control_indicator_etc(0x80), // PTS_DTS_flags: 0b10 (仅 PTS)
  68.                   pes_header_data_length(5),
  69.                   pts_dts_flags_pts(0) {}
  70. };
  71. // 简化的 RTP 头部
  72. struct RTPHeader {
  73.     uint8_t version_padding_extension_csrc_count; // V, P, X, CC
  74.     uint8_t marker_payload_type;                 // M, PT
  75.     uint16_t sequence_number;
  76.     uint32_t timestamp;
  77.     uint32_t ssrc;
  78.     RTPHeader() : version_padding_extension_csrc_count(0x80), marker_payload_type(96), sequence_number(0), timestamp(0), ssrc(0x12345678) {} // SSRC 示例
  79. };
  80. // Nalu 结构体
  81. struct Nalu {
  82.     int type;
  83.     int length;
  84.     std::vector<uint8_t> packet;
  85.     Nalu() : type(0), length(0) {}
  86.     ~Nalu() {}
  87. };
  88. using NaluType = int;
  89. #pragma pack(pop) // 恢复默认 packing
  90. // --- 全局计数器和变量 ---
  91. static uint64_t pes_pts_counter = 0; // PES PTS 计数器示例
  92. static uint16_t rtp_seq_counter = 0;
  93. // --- 日志记录函数 ---
  94. void Log(const std::string& message) {
  95.     std::time_t now = std::time(nullptr);
  96.     std::tm local_time;
  97.     localtime_r(&now, &local_time);
  98.     char timestamp_str[20];
  99.     std::strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", &local_time);
  100.     std::cout << "[" << timestamp_str << "] " << message << std::endl;
  101. }
  102. // --- 辅助函数:将数据转换为十六进制字符串 ---
  103. std::string ToHex(const uint8_t* data, size_t size) {
  104.     std::stringstream hex_stream;
  105.     hex_stream << std::hex << std::setfill('0');
  106.     for (size_t i = 0; i < size; ++i) {
  107.         hex_stream << std::setw(2) << static_cast<int>(data[i]) << " ";
  108.     }
  109.     return hex_stream.str();
  110. }
  111. // --- 将 VPS, SPS, PPS 处理为一个流 ---
  112. std::vector<uint8_t> vps_data;
  113. std::vector<uint8_t> sps_data;
  114. std::vector<uint8_t> pps_data;
  115. void out_nalu(char *buffer, int size, NaluType naluType) {
  116.     if (naluType == 32) {  // VPS
  117.         vps_data.resize(size);
  118.         memcpy(vps_data.data(), buffer, size);
  119.     } else if (naluType == 33) {  // SPS
  120.         sps_data.resize(size);
  121.         memcpy(sps_data.data(), buffer, size);
  122.     } else if (naluType == 34) {  // PPS
  123.         pps_data.resize(size);
  124.         memcpy(pps_data.data(), buffer, size);
  125.     } else {
  126.         Nalu *nalu = new Nalu;
  127.         bool is_i_frame = (naluType == 19);  // IDR frame
  128.         char *packet = (char *)malloc(is_i_frame ? (size + vps_data.size() + sps_data.size() + pps_data.size()) : size);
  129.         if (is_i_frame) {
  130.             memcpy(packet, vps_data.data(), vps_data.size());
  131.             memcpy(packet + vps_data.size(), sps_data.data(), sps_data.size());
  132.             memcpy(packet + vps_data.size() + sps_data.size(), pps_data.data(), pps_data.size());
  133.             memcpy(packet + vps_data.size() + sps_data.size() + pps_data.size(), buffer, size);
  134.             size += (vps_data.size() + sps_data.size() + pps_data.size());
  135.         } else {
  136.             memcpy(packet, buffer, size);
  137.         }
  138.         nalu->packet = std::vector<uint8_t>(packet, packet + size);
  139.         nalu->length = size;
  140.         nalu->type = naluType;
  141.         // 将 nalu 添加到 nalu_vector 或进行其他处理
  142.         delete[] packet;
  143.     }
  144. }
  145. // --- 头部生成函数 ---
  146. void gb28181_make_ps_header(char *header, long pts) {
  147.     PSHeader ps_header_struct;
  148.     ps_header_struct.system_clock_reference = pts; // 简化 SCR
  149.     memcpy(header, &ps_header_struct, sizeof(PSHeader));
  150. }
  151. void gb28181_make_sys_header(char *header, int rate_bound) {
  152.     SystemHeader sys_header_struct;
  153.     sys_header_struct.header_length = htons(SYS_HDR_LEN - 6); // Length after header_length field
  154.     sys_header_struct.rate_bound[0] = (rate_bound >> 16) & 0xFF;
  155.     sys_header_struct.rate_bound[1] = (rate_bound >> 8) & 0xFF;
  156.     sys_header_struct.rate_bound[2] = rate_bound & 0xFF;
  157.     memcpy(header, &sys_header_struct, sizeof(SystemHeader));
  158. }
  159. void gb28181_make_psm_header(char *header) {
  160.     PSMHeader psm_header_struct;
  161.     psm_header_struct.psm_length = htons(PSM_HDR_LEN - 6); // Length after psm_length field
  162.     memcpy(header, &psm_header_struct, sizeof(PSMHeader));
  163. }
  164. void gb28181_make_pes_header(char *header, int stream_id, int data_len, long pts, long dts) {
  165.     PESHeader pes_header_struct;
  166.     pes_header_struct.stream_id = stream_id;
  167.     pes_header_struct.pes_packet_length = htons(data_len + PES_HDR_LEN - 6); // Length after pes_packet_length field
  168.     pes_header_struct.pts_dts_flags_pts = (static_cast<uint64_t>(pes_pts_counter++) << 3) | 0x02; // 仅设置 PTS 标志
  169.     uint64_t pts_33_to_1 = (pes_pts_counter * 300) % 0x200000000LL; // 假设 300 ticks/ms, 90kHz clock
  170.     uint32_t pts_32_to_2 = pts_33_to_1 & 0xFFFFFFFE0LL;
  171.     uint8_t pts_byte0 = 0x20 | ((pts_32_to_2 >> 30) & 0x07) << 1 | 0x01;
  172.     uint16_t pts_byte1_2 = (pts_32_to_2 >> 15) & 0xFFFF;
  173.     uint16_t pts_byte3_4 = pts_32_to_2 & 0xFFFF;
  174.     pes_header_struct.pts_dts_flags_pts |= (static_cast<uint64_t>(pts_byte0) << 40);
  175.     pes_header_struct.pts_dts_flags_pts |= (static_cast<uint64_t>(pts_byte1_2) << 24);
  176.     pes_header_struct.pts_dts_flags_pts |= (static_cast<uint64_t>(pts_byte3_4) >> 8);
  177.     memcpy(header, &pes_header_struct, sizeof(PESHeader));
  178. }
  179. void gb28181_make_rtp_header(char *header, int seq, long pts, int ssrc, bool marker) {
  180.     RTPHeader rtp_header_struct;
  181.     rtp_header_struct.sequence_number = htons(seq);
  182.     rtp_header_struct.timestamp = htonl(pts);
  183.     rtp_header_struct.ssrc = htonl(ssrc);
  184.     if (marker) {
  185.         rtp_header_struct.marker_payload_type |= (1 << 7); // Set marker bit
  186.     }
  187.     memcpy(header, &rtp_header_struct, sizeof(RTPHeader));
  188. }
  189. // --- 发送网络数据包 ---
  190. void send_network_packet(char *packet, int packet_size) {
  191.     Log("发送网络包, 大小: " + std::to_string(packet_size) + " bytes.");
  192.     // 模拟发送网络包,实际应用中替换为 socket send 操作
  193.     Log("Packet Data (First 64 bytes): \n" + ToHex((uint8_t*)packet, std::min((size_t)64, (size_t)packet_size)));
  194. }
  195. // --- 判断是否为关键帧 ---
  196. bool is_keyframe(NaluType type) {
  197.     // 这里可以根据 NALU 类型判断是否为关键帧
  198.     // 例如,对于 H.264, IDR 帧 (type 5) 是关键帧,对于 H.265, IDR_W_RADL (type 19) 和 IDR_N_LP (type 20) 是关键帧
  199.     // 在你的代码中,type 19 被认为是 IDR 帧
  200.     return (type == 19 || type == 32 || type == 33 || type == 34); // 假设 VPS, SPS, PPS 也被视为关键帧,用于某些 header 的添加逻辑
  201. }
  202. // --- 主程序 ---
  203. int main() {
  204.     Log("--- 示例程序开始 ---");
  205.     // 模拟更真实的 NALU 数组,包含 VPS, SPS, PPS, IDR 关键帧, 普通帧
  206.     std::vector<Nalu*> nalu_vector_sim;
  207.     // 模拟 VPS, SPS, PPS (通常在 IDR 帧前)
  208.     struct Nalu* vps_nalu = new Nalu();
  209.     vps_nalu->type = 32; // VPS_NUT
  210.     vps_nalu->length = 50;
  211.     vps_nalu->packet.resize(vps_nalu->length);
  212.     std::iota(vps_nalu->packet.begin(), vps_nalu->packet.end(), 1);
  213.     nalu_vector_sim.push_back(vps_nalu);
  214.     out_nalu((char*)vps_nalu->packet.data(), vps_nalu->length, 32);
  215.     struct Nalu* sps_nalu = new Nalu();
  216.     sps_nalu->type = 33; // SPS_NUT
  217.     sps_nalu->length = 100;
  218.     sps_nalu->packet.resize(sps_nalu->length);
  219.     std::iota(sps_nalu->packet.begin(), sps_nalu->packet.end(), 51);
  220.     nalu_vector_sim.push_back(sps_nalu);
  221.     out_nalu((char*)sps_nalu->packet.data(), sps_nalu->length, 33);
  222.     struct Nalu* pps_nalu = new Nalu();
  223.     pps_nalu->type = 34; // PPS_NUT
  224.     pps_nalu->length = 30;
  225.     pps_nalu->packet.resize(pps_nalu->length);
  226.     std::iota(pps_nalu->packet.begin(), pps_nalu->packet.end(), 151);
  227.     nalu_vector_sim.push_back(pps_nalu);
  228.     out_nalu((char*)pps_nalu->packet.data(), pps_nalu->length, 34);
  229.     // 模拟 IDR 关键帧
  230.     struct Nalu* idr_nalu = new Nalu();
  231.     idr_nalu->type = 19; // IDR_W_RADL (示例 IDR 类型)
  232.     idr_nalu->length = 2048;
  233.     idr_nalu->packet.resize(idr_nalu->length);
  234.     std::iota(idr_nalu->packet.begin(), idr_nalu->packet.end(), 201);
  235.     nalu_vector_sim.push_back(idr_nalu);
  236.     out_nalu((char*)idr_nalu->packet.data(), idr_nalu->length, 19);
  237.     // 模拟 普通帧 (非关键帧)
  238.     struct Nalu* non_idr_nalu = new Nalu();
  239.     non_idr_nalu->type = 1; // TRAIL_R_NUT (示例 普通帧类型)
  240.     non_idr_nalu->length = 1500;
  241.     non_idr_nalu->packet.resize(non_idr_nalu->length);
  242.     std::iota(non_idr_nalu->packet.begin(), non_idr_nalu->packet.end(), 2200);
  243.     nalu_vector_sim.push_back(non_idr_nalu);
  244.     out_nalu((char*)non_idr_nalu->packet.data(), non_idr_nalu->length, 1);
  245.     // RTP 发送处理略...
  246.     int time_base = 90000;
  247.     int fps = 24;
  248.     int send_packet_interval = 1000 / fps;
  249.     int interval = time_base / fps;
  250.     long pts = 0;
  251.     int single_packet_max_length = 1400;
  252.     int ssrc_val = 0x12345678; // 示例 SSRC
  253.     std::string rtp_protocol_type = "UDP/RTP/AVP"; // 或 "TCP/RTP/AVP"
  254.     Log("[process_request] 开始模拟推流, NALU 数量: " + std::to_string(nalu_vector_sim.size()));
  255.     for (auto* nalu : nalu_vector_sim) {
  256.         const NaluType type = nalu->type;
  257.         const int length = nalu->length;
  258.         const uint8_t* packet = nalu->packet.data();
  259.         const bool is_key = is_keyframe(type);
  260.         Log("处理 NALU, Type: " + std::to_string(type) + ", Keyframe: " + (is_key ? "Yes" : "No") + ", Size: " + std::to_string(length) + " bytes.");
  261.         // 在遇到 VPS、SPS、PPS 时,先进行封装
  262.         char frame_buffer[1024 * 128]; // 帧数据缓冲区
  263.         int frame_index = 0;
  264.         char ps_header_buf[PS_HDR_LEN];
  265.         char sys_header_buf[SYS_HDR_LEN];
  266.         char psm_header_buf[PSM_HDR_LEN];
  267.         char pes_header_buf[PES_HDR_LEN];
  268.         char rtp_packet_buf[RTP_HDR_LEN + 1400];
  269.         // 声明 rtp_header_buf
  270.         char rtp_header_buf[RTP_HDR_LEN];
  271.         // 封装 VPS, SPS, PPS
  272.         if (type == 32 || type == 33 || type == 34) { // VPS, SPS, PPS
  273.             memcpy(frame_buffer + frame_index, packet, length);
  274.             frame_index += length;
  275.         }
  276.         // --- PS 封装 ---
  277.         gb28181_make_ps_header(ps_header_buf, pts);
  278.         memcpy(frame_buffer + frame_index, ps_header_buf, PS_HDR_LEN);
  279.         frame_index += PS_HDR_LEN;
  280.         if (is_key) {
  281.             gb28181_make_sys_header(sys_header_buf, 0x3f); // 示例 rate_bound
  282.             memcpy(frame_buffer + frame_index, sys_header_buf, SYS_HDR_LEN);
  283.             frame_index += SYS_HDR_LEN;
  284.             gb28181_make_psm_header(psm_header_buf);
  285.             memcpy(frame_buffer + frame_index, psm_header_buf, PSM_HDR_LEN);
  286.             frame_index += PSM_HDR_LEN;
  287.         }
  288.         // --- PES 封装 ---
  289.         gb28181_make_pes_header(pes_header_buf, 0xe0, length, pts, pts);
  290.         memcpy(frame_buffer + frame_index, pes_header_buf, PES_HDR_LEN);
  291.         frame_index += PES_HDR_LEN;
  292.         memcpy(frame_buffer + frame_index, packet, length);
  293.         frame_index += length;
  294.         // --- RTP 分包发送 ---
  295.         int rtp_packet_count = (frame_index + single_packet_max_length - 1) / single_packet_max_length;
  296.         Log("RTP 分包数: " + std::to_string(rtp_packet_count));
  297.         for (int i = 0; i < rtp_packet_count; ++i) {
  298.             bool is_last_packet = (i == rtp_packet_count - 1);
  299.             gb28181_make_rtp_header(rtp_header_buf, rtp_seq_counter, pts, ssrc_val, is_last_packet);
  300.             int offset = i * single_packet_max_length;
  301.             int data_size = std::min(single_packet_max_length, frame_index - offset);
  302.             int rtp_start_index = 0;
  303.             if (rtp_protocol_type == "TCP/RTP/AVP") {
  304.                 uint16_t packet_length = RTP_HDR_LEN + data_size;
  305.                 rtp_packet_buf[0] = (packet_length >> 8) & 0xFF;
  306.                 rtp_packet_buf[1] = packet_length & 0xFF;
  307.                 rtp_start_index = 2;
  308.             }
  309.             memcpy(rtp_packet_buf + rtp_start_index, rtp_header_buf, RTP_HDR_LEN);
  310.             memcpy(rtp_packet_buf + rtp_start_index + RTP_HDR_LEN, frame_buffer + offset, data_size);
  311.             send_network_packet(rtp_packet_buf, rtp_start_index + RTP_HDR_LEN + data_size);
  312.             rtp_seq_counter++;
  313.         }
  314.         pts += interval;
  315.         std::this_thread::sleep_for(std::chrono::milliseconds(send_packet_interval));
  316.         delete nalu; // 模拟 Device::push_rtp_stream 中的 nalu delete
  317.     }
  318.     Log("--- 示例程序结束 ---");
  319.     return 0;
  320. }
复制代码
  基本逻辑
  

  •  VPS/SPS/PPS 缓冲

    • out_nalu函数会将 buffer 中的数据分别复制到全局变量中,等待遇到关键帧的时间将其参加进入

  • IDR 帧处置惩罚 (关键帧)

    • 将缓冲区中的VPS/PPS/SPS参加到Nalu之前

  • 非 IDR 帧处置惩罚 (普通帧)

    • 直接分配内存进行发送







免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

莱莱

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表