如何从RTP包的AP类型包,获取h265的PPS、SPS、VPS信息

本文讲述了在ffmpeg中通过解析AP包获取VPS、SPS和PPS信息,并将它们转换为Base64格式的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ffmpeg播放rtp流,为了降低首开延迟,需要在SDP文件中指定PPS、SPS、VPS信息。抓包后发现wireshark无法解析AP包。需要自己进行AP包解析。RTP协议AP包格式如下:

根据如上信息,我们可以解析AP包,效果如下

  • 40 01,type=32,VPS(视频参数集)
  • 42 01,type=33,SPS(序列参数集)
  • 44 01,type=34,PPS(图像参数集)
  • 4E 01, type=39,SEI(补充增强信息)
  • 26 01,type=19,可能有RADL图像的IDR图像的SS编码数据 IDR
  • 02 01, type=01,被参考的后置图像,且非TSA、非STSA的SS编码数据
  • 46 01,type=35,分隔符,没用。

下图中红色部分是分隔符,橙色是VPS,黑色是SPS,黄色为PPS

SDP中的VPS等信息需要转换成base64。使用下面的函数可转换
#include <stdio.h>
#include <inttypes.h>
#include <math.h>
#include <limits.h>
#include <signal.h>
#include <stdint.h>
#include <string>
#include <string.h>
#include <iostream>


#define AV_BASE64_SIZE(x)  (((x)+2) / 3 * 4 + 1)
#   define AV_RB32(x)                                \
    (((uint32_t)((const uint8_t*)(x))[0] << 24) |    \
               (((const uint8_t*)(x))[1] << 16) |    \
               (((const uint8_t*)(x))[2] <<  8) |    \
                ((const uint8_t*)(x))[3])
using namespace std;
char *av_base64_encode(char *out, int out_size, const uint8_t *in, int in_size)
{
    static const char b64[] =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    char *ret, *dst;
    unsigned i_bits = 0;
    int i_shift = 0;
    int bytes_remaining = in_size;

    if (in_size >= UINT_MAX / 4 ||
        out_size < AV_BASE64_SIZE(in_size))
        return NULL;
    ret = dst = out;
    while (bytes_remaining > 3) {
        i_bits = AV_RB32(in);
        in += 3; bytes_remaining -= 3;
        *dst++ = b64[ i_bits>>26        ];
        *dst++ = b64[(i_bits>>20) & 0x3F];
        *dst++ = b64[(i_bits>>14) & 0x3F];
        *dst++ = b64[(i_bits>>8 ) & 0x3F];
    }
    i_bits = 0;
    while (bytes_remaining) {
        i_bits = (i_bits << 8) + *in++;
        bytes_remaining--;
        i_shift += 8;
    }
    while (i_shift > 0) {
        *dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f];
        i_shift -= 6;
    }
    while ((dst - ret) & 3)
        *dst++ = '=';
    *dst = '\0';

    return ret;
}

static int doLog = 0;
void hexDump(uint8_t* data,int len){
  if(doLog == 0)
    return;
  printf("[%d]",len);
  int pos = 0;
  uint8_t d = 0;
  while(pos<len){
    d = *(data+pos);
    printf("0x%02x,",(int)d);
    pos++;
  }
  printf("[%d]\n",len);
}
//sprop-vps=QAEMAf//AWAAAAMAkAAAAwAAAwBalZgJ; sprop-sps=QgEBAWAAAAMAkAAAAwAAAwBaoAeCAIh95ZWaSTK8BacIAAADAAgAAAMA8EA=; sprop-pps=RAHBcrRiQA==
//sprop-vps=DAH//wFgAAADALAAAAMAAAMAWqwJ; sprop-sps=AQFgAAADALAAAAMAAAMAWqAPCAPBY2uSRS9NQEBAQCA=; sprop-pps=wPLOcDs0
void getXPSfromAP(uint8_t* ap){
  int pos = 0;
  while(true){
    uint16_t hdr = (ap[pos+2]<<8)|(ap[pos+3]);
    int typ = (hdr>>9)&0x3f;
    int siz = (ap[pos+0]<<8)|(ap[pos+1])-2;
    switch(typ){
      case 32:
      { 
        char str_vps[100]={0};
        uint8_t* vps = (uint8_t*)malloc(siz);
        memcpy(vps,ap+pos+4,siz);
        av_base64_encode(str_vps,100,vps,siz);
        cout<<"sprop-vps="<<str_vps;
        hexDump(vps,siz);
        free(vps);
        break;
      }
      case 33:
      { 
        char str_sps[100]={0};
        uint8_t* sps = (uint8_t*)malloc(siz);
        memcpy(sps,ap+pos+4,siz);
        av_base64_encode(str_sps,100,sps,siz);
        cout<<"; sprop-sps="<<str_sps;
        hexDump(sps,siz);
        free(sps);
        break;
      }
      case 34:
      { 
        char str_pps[100]={0};
        uint8_t* pps = (uint8_t*)malloc(siz);
        memcpy(pps,ap+pos+4,siz);
        av_base64_encode(str_pps,100,pps,siz);
        cout<<"; sprop-pps="<<str_pps<<endl;
        hexDump(pps,siz);
        free(pps);
        return;
        break;
      }
      default:
        break;
    }
    pos += 4;
    pos += siz;
  }
}
int main(int argc, char *argv[]) {
  if(argc>=2){
    doLog = atoi(argv[1]);
  }
 
  uint8_t in[]={0x67, 0x42,0xc0,0x14,0xf4,0x0b,0x04,0xb4,0x20,0x00,0x00,0x03,0x00,0x20,0x00,0x00,0x03,0x03,0xd1,0xe2,0x85,0x54};
    char str_sps[100]={0};
    av_base64_encode(str_sps,100,in,22);
    cout<<"sps'sbasecode:"<<str_sps<<endl;
    uint8_t pps[]={0x68 ,0xce ,0x04 ,0xf2};
  char str_sps2[100]={0};
    av_base64_encode(str_sps2,100,pps,4);
    cout<<"pps'sbasecode:"<<str_sps2<<endl;
 
  uint8_t ap[] = {0x00,0x17,0x40,0x01,0x0c,0x01,0xff,0xff,0x01,0x60,0x00,0x00,0x03,0x00,0xb0,0x00,0x00,0x03,0x00,0x00,0x03,0x00,0x5a,0xac,0x09,0x00,0x22,0x42,
    0x01,0x01,0x01,0x60,0x00,0x00,0x03,0x00,0xb0,0x00,0x00,0x03,0x00,0x00,0x03,0x00,0x5a,0xa0,0x0f,0x08,0x03,0xc1,0x63,0x6b,0x92,0x45,0x2f,
    0x4d,0x40,0x40,0x40,0x40,0x20,0x00,0x08,0x44,0x01,0xc0,0xf2,0xce,0x70,0x3b,0x34,0x00,0x09,0x4e,0x01,0xe5,0x04,0x00,0x00,0x70,0x00,0x80,0x00,
    0x1e,0x4e,0x01,0x89,0x18,0x3a,0x98,0x75,0x30,0x1d,0x4c,0x0b,0xb8,0x7d,0x00,0x40,0x74,0x3d,0x13,0x40,0x42,0x00,0x00,0x04,0xb0,0x00,0x00,0x03,0x00,0xc8,0x80};
  getXPSfromAP(ap);
  return 0;
}
 

bool RTSPH265Sender::writePacket(AVPacket* pkt) { // H265 RTP 分片规则 (RFC 7798) const int RTP_MTU = 1400; uint8_t *nal_data = pkt->data; int nal_size = pkt->size; // 检查NAL单元类型(必须为AP或FU-A分片类型) if (nal_size < 2) return false; // 非法NAL单元 uint8_t nal_type = (nal_data[0] >> 1) & 0x3F; // 提取H265 NAL类型(6 bits) // 需要分片:NAL单元头占2字节(H265特性) if (nal_size > RTP_MTU) { // 跳过NAL头(2字节),从payload开始分片 uint8_t *payload_data = nal_data + 2; int payload_size = nal_size - 2; int max_payload_size = RTP_MTU - 3; // RTP头(12) + FU Indicator(1) + FU Header(1) = 14,MTU预留 // 分片数量计算 int num_fragments = (payload_size + max_payload_size - 1) / max_payload_size; int offset = 0; // 构造分片 for (int i = 0; i < num_fragments; i++) { AVPacket fragment_pkt; av_new_packet(&fragment_pkt, RTP_MTU); // --- RTP头 --- // 此处应填充正确的序列号、时间戳(需与原始一致) fragment_pkt.stream_index = pkt->stream_index; // --- FU Indicator --- // forbidden_zero_bit(1) | nal_type(6) => 类型设为FU分片标识49(0x31) fragment_pkt.data[0] = (nal_data[0] & 0x81) | (49 << 1); // 保留原NAL头第1位(F)和第8位(LayerId) // --- FU Header --- uint8_t fu_header = 0; if (i == 0) { fu_header |= (1 << 7); // S (Start) 标志 fu_header |= (nal_type & 0x3F); // 原始NAL类型(低6位) } else if (i == num_fragments - 1) { fu_header |= (1 << 6); // E (End) 标志 fu_header |= (nal_type & 0x3F); } else { fu_header |= (nal_type & 0x3F); } fragment_pkt.data[1] = fu_header; // --- Payload分片 --- int frag_size = (i == num_fragments - 1) ? (payload_size - offset) : max_payload_size;
03-12
<think>嗯,用户的问题是关于如何在RTSP协议传输中实现H265视频流的RTP分片处理。首先,我需要回忆一下H265(HEVC)的RTP封装标准。H265RTP分片处理跟之前的H264有什么不同呢?我记得H264使用不同的分片模式,比如单一NAL单元、分片单元等,而H265可能也有类似的结构,但NAL单元头结构不同。 接下来,用户提到的RTSP传输,需要结合RTP协议。RTSP通常用于建立和控制媒体会话,而RTP负责实际传输媒体数据。所以,关键点在于如何将H265的视频数据按照RTP规范进行分片和打H265的NAL单元类型中,可能有聚合和分片。例如,当一帧视频数据太大,超过MTU大小时,需要分片。这时候,H265RTP分片规则是怎样的呢?根据RFC 7798,H265RTP负载格式定义了分片单元(FU)的结构。每个分片需要含特定的头信息,比如起始位、结束位、负载类型等。 然后,实现分片处理的具体步骤可能括:解析H265的NAL单元,判断其大小是否需要分片,然后创建RTP,添加RTP头,设置FU指示和头信息,最后通过RTSP传输。需要注意的是,分片后的每个RTP都需要正确的序列号和时间戳,以确保接收端能正确重组。 另外,用户提到使用MP4Box进行RTP提示轨的生成。这可能涉及到将MP4文件中的H265轨道预处理,生成适合RTP传输的提示信息,这样流媒体服务器就能根据这些提示信息正确分片和发送RTP。MP4Box的-hint参数应该就是用来做这个的,但具体如何应用到H265可能需要查阅相关文档或RFC。 在代码示例方面,可能需要展示如何解析NAL单元并进行分片。比如,读取H265的NAL单元,检查大小,然后分割成多个FU-A或FU-B。每个分片RTP头需要设置正确的标记位,比如第一个分片的起始位,最后一个分片的结束位,中间的这些位则清零。 还有需要考虑的是时间戳和序列号的自增,以及如何处理传输中的丢和重组。此外,SDP文件中的参数也需要正确设置,比如sprop-parameter-sets来传递VPSSPSPPS等参数,确保解码端能正确初始化。 可能用户在实际操作中遇到的问题括分片后的RTP无法正确重组,或者时间戳不一致导致视频无法播放。这时候需要检查分片过程中的标记位是否正确,以及RTP头的序列号是否连续,时间戳是否一致。 总结一下,回答需要涵盖H265RTP分片标准、具体实现步骤、相关工具如MP4Box的使用,以及可能的代码示例和SDP配置注意事项。同时,根据用户提供的参考引用,MP4Box的-hint指令是为RTP准备提示轨,这在H265的处理中同样适用,需要指出这一点,并引用提供的参考来源。</think>### H265 RTP分片处理实现方案 #### 一、H265 RTP分片标准 H265视频流通过RTP传输时需遵循RFC 7798标准[^1],其分片规则基于NAL单元结构: $$ \text{RTP头}(12B) + \text{FU头}(2B) + \text{载荷} $$ 关键字段说明: 1. **F位**:标记分片类型(0=正常,1=分片单元) 2. **Type**:NAL单元类型(49表示分片单元) 3. **S/E/R位**:分片状态标识(起始/结束/保留) #### 二、分片实现步骤 1. **NAL单元解析** ```python nal_header = data[0:2] # 读取2字节NAL头 nal_type = (nal_header[0] >> 1) & 0x3F ``` 2. **分片决策逻辑** ```python MAX_MTU = 1400 # 典型MTU值 if len(nal_payload) > MAX_MTU: split_fragments(nal_payload) ``` 3. **分片生成示例** ```python def create_fu_packet(nal_header, fragment, is_first, is_last): fu_header = bytes([ (nal_header[0] & 0x81) | (0x31 if is_first else 0x11), nal_header[1] ]) if is_first: fu_header += bytes([0x80]) # 设置起始位 elif is_last: fu_header += bytes([0x40]) # 设置结束位 return rtp_header + fu_header + fragment ``` #### 三、RTSP传输配置要点 1. **SDP参数示例**: ```sdp a=fmtp:96 profile-level-id=42001F; sprop-parameter-sets=QAEMQ//ARAAAAMAAAMAAAMAAAMAALUCGA==,aA48gA==; ``` 2. **MP4Box预处理指令**: ```bash mp4box -hint -sdp -mtu 1400 input.hevc ``` 该指令生成含时间戳同步的RTP提示轨 #### 四、调试注意事项 1. **时间戳同步**:同一帧的所有分片必须共享相同时间戳 2. **序列号连续性**:RTP头中sequence number需严格递增 3. **FU头验证**:接收端需检查S/E位组合有效性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无v邪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值