RTP H264 分包解包

一、 h264基础概念

SODB: 数据比特串-->最原始的编码数据

RBSP: 原始字节序列载荷-->在SODB的后面填加了结尾比特(RBSP trailing bits 一个bit“1”)若干比特“0”,以便字节对齐。

EBSP: 扩展字节序列载荷-- >在RBSP基础上填加了仿校验字节(0X03)它的原因是: 在NALU加到Annexb上时,需要填加每组NALU之前的开始码 StartCodePrefix,如果该NALU对应的slice为一帧的开始则用4位字节表示,ox00000001,否则用3位字节表示 ox000001. 为了使NALU主体中不包括与开始码相冲突的,在编码时,每遇到两个字节连续为0,就插入一个字节的0x03。解码时将0x03去掉。 也称为脱壳操作。

H.264的功能分为两层,视频编码层(VCL)和网络提取层(NAL) VCL数据即被压缩编码后的视频数据序列。在VCL数据要封装到NAL单元中之后,才可以用来传输或存储。NAL单元格式如下图: 这里写图片描述

H.264 的编码视频序列包括一系列的NAL 单元,每个NAL 单元包含一个RBSP,见表1。编码片(包括数据分割片IDR 片)和序列RBSP 结束符被定义为VCL NAL 单元,其余为NAL 单元。典型的RBSP 单元序列如图2 所示。每个单元都按独立的NAL 单元传送。单元的信息头(一个字节)定义了RBSP 单元的类型,NAL 单元的其余部分为RBSP 数据。

这里写图片描述

NAL单元

每个NAL单元是一个一定语法元素的可变长字节字符串,包括包含一个字节的头信息(用来表示数据类型),以及若干整数字节的负荷数据。一个NAL单元可以携带一个编码片、A/B/C型数据分割或一个序列或图像参数集。

NALU 头由一个字节组成, 它的语法如下:

这里写图片描述

  NAL单元按RTP序列号按序传送。其中,T为负荷数据类型,占5bit;R为重要性指示位,占2个bit;最后的F为禁止位,占1bit。具体如下:   (1)NALU类型位   可以表示NALU的32种不同类型特征,类型1~12是H.264定义的,类型24~31是用于H.264以外的,RTP负荷规范使用这其中的一些值来定义包聚合和分裂,其他值为H.264保留。   (2)重要性指示位   用于在重构过程中标记一个NAL单元的重要性,值越大,越重要。值为0表示这个NAL单元没有用于预测,因此可被解码器抛弃而不会有错误扩散;值高于0表示此NAL单元要用于无漂移重构,且值越高,对此NAL单元丢失的影响越大。   (3)禁止位 编码中默认值为0,当网络识别此单元中存在比特错误时,可将其设为1,以便接收方丢掉该单元,主要 用于适应不同种类的网络环境(比如有线无线相结合的环境)。

264常见的帧头数据为:

00 00 00 01 67 (SPS)

00 00 00 01 68 (PPS)

00 00 00 01 65 ( IDR 帧)

00 00 00 01 61 (P帧)

等等,那么他们代表的意思是什么呢?

上述的**67,68,65,61,**还有41等,都是该NALU的识别级别。

F:禁止为,0表示正常,1表示错误,一般都是0

NRI:重要级别,11表示非常重要。

TYPE:表示该NALU的类型是什么,

见下表,由此可知7为序列参数集(SPS),8为图像参数集(PPS),5代表I帧。1代表非I帧。

由此可知,61和41其实都是P帧(type值为1),只是重要级别不一样(它们的NRI一个是11BIN,一个是10BIN)

NALU类型是我们判断帧类型的利器,从官方文档中得出如下图:

这里写图片描述

###H264(NAL简介与I帧判断) 这里写图片描述

我们还是接着看最上面图的码流对应的数据来层层分析,以00 00 00 01分割之后的下一个字节就是NALU类型,将其转为二进制数据后,

解读顺序为从左往右算,如下:

(1)第1位禁止位,值为1表示语法出错

(2)第2~3位为参考级别

(3)第4~8为是nal单元类型

例如上面00000001后有67,68以及65

其中0x67的二进制码为:

0110 0111

4-8为00111,转为十进制7,参考第二幅图:7对应序列参数集SPS

其中0x68的二进制码为:

0110 1000 4-8为01000,转为十进制8,参考第二幅图:8对应图像参数集PPS

其中0x65的二进制码为:

011 00101

4-8位为00101,转为十进制5,参考第二幅图:5对应IDR图像中的片(I帧)

所以判断是否为I帧的算法为:

(NALU类型 & 0001 1111) = 5 即 (NALU类型 & 31) = 5 比如0x65 & 31 = 5

二、RTP打包发送H264之封包详解

RFC3984是H.264的baseline码流在RTP方式下传输的规范,这里只讨论FU-A分包方式,

H264的码流结构

这里写图片描述

1、单个NAL包单元

12字节的RTP头后面的就是音视频数据,比较简单。一个封装单个NAL单元包到RTP的NAL单元流的RTP序号必须符合NAL单元的解码顺序。 对于 NALU 的长度小于 MTU 大小的包, 一般采用单一 NAL 单元模式. 对于一个原始的 H.264 NALU 单元常由[Start Code] [NALU Header] [NALU Payload]三部分组成, 其中 Start Code 用于标示这是一个 NALU 单元的开始, 必须是 "00 00 00 01" 或 "00 00 01", NALU 头仅一个字节, 其后都是 NALU 单元内容.

打包时去除 "00 00 01" 或 "00 00 00 01" 的开始码, 把其他数据封包的 RTP 包即可.

 
  1. 0 1 2 3

  2. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

  3. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  4. |F|NRI| type | |

  5. +-+-+-+-+-+-+-+-+ |

  6. | |

  7. | Bytes 2..n of a Single NAL unit |

  8. | |

  9. | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  10. | :...OPTIONAL RTP padding |

  11. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  12.  

如有一个 H.264 的 NALU 是这样的:

[00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]

这是一个序列参数集 NAL 单元. [00 00 00 01] 是四个字节的开始码, 67 是 NALU 头, 42 开始的数据是 NALU 内容.

封装成 RTP 包将如下:

[ RTP Header ] [ 67 42 A0 1E 23 56 0E 2F ]

即只要去掉 4 个字节的开始码就可以了.

2. 组合封包模式

当 NALU 的长度特别小时, 可以把几个 NALU 单元封在一个 RTP 包中.

3、FU-A的分片格式

数据比较大的H264视频包,被RTP分片发送。12字节的RTP头后面跟随的就是FU-A分片: 而当 NALU 的长度超过 MTU 时, 就必须对 NALU 单元进行分片封包. 也称为 Fragmentation Units (FUs).

 
  1. 0 1 2 3

  2. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

  3. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  4. | FU indicator | FU header | |

  5. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |

  6. | |

  7. | FU payload |

  8. | |

  9. | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  10. | :...OPTIONAL RTP padding |

  11. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  12. Figure 14. RTP payload format for FU-A

FU indicator有以下格式:

 
  1. +---------------+

  2. |0|1|2|3|4|5|6|7|

  3. +-+-+-+-+-+-+-+-+

  4. |F|NRI| Type |

  5. +---------------+

  6.  

FU指示字节的类型域Type=28表示FU-A。。NRI域的值必须根据分片NAL单元的NRI域的值设置。

FU header的格式如下:

 
  1. +---------------+

  2. |0|1|2|3|4|5|6|7|

  3. +-+-+-+-+-+-+-+-+

  4. |S|E|R| Type |

  5. +---------------+

S: 1 bit 当设置成1,开始位指示分片NAL单元的开始。当跟随的FU荷载不是分片NAL单元荷载的开始,开始位设为0。

E: 1 bit 当设置成1, 结束位指示分片NAL单元的结束,即, 荷载的最后字节也是分片NAL单元的最后一个字节。 当跟随的FU荷载不是分片NAL单元的最后分片,结束位设置为0。

R: 1 bit 保留位必须设置为0,接收者必须忽略该位。

Type: 5 bits
NAL单元荷载类型定义见下表

表1. 单元类型以及荷载结构总结

 
  1. .Type Packet Type name

  2. ---------------------------------------------------------

  3. 0 undefined -

  4. 1-23 NAL unit Single NAL unit packet per H.264

  5. 24 STAP-A Single-time aggregation packet

  6. 25 STAP-B Single-time aggregation packet

  7. 26 MTAP16 Multi-time aggregation packet

  8. 27 MTAP24 Multi-time aggregation packet

  9. 28 FU-A Fragmentation unit

  10. 29 FU-B Fragmentation unit

  11. 30-31 undefined

  12. -

3、拆包和解包

拆包:当编码器在编码时需要将原有一个NAL按照FU-A进行分片,原有的NAL的单元头与分片后的FU-A的单元头有如下关系:

原始的NAL头的前三位为FU indicator的前三位,原始的NAL头的后五位为FU header的后五位,

FU indicator与FU header的剩余位数根据实际情况决定。

**解包:**当接收端收到FU-A的分片数据,需要将所有的分片包组合还原成原始的NAl包时,FU-A的单元头与还原后的NAL的关系如下:

还原后的NAL头的八位是由FU indicator的前三位加FU header的后五位组成,即:

nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
解包 RTP H.264 数据需要进行以下步骤: 1. 从 RTP 报文中提取出 H.264 数据包。 2. 解析 H.264 数据包,提取出 NAL 单元。 3. 将 NAL 单元按照 H.264 规范进行拼接,形成完整的 H.264 帧。 4. 对 H.264 帧进行解码,获取视频帧。 下面是一个基于 C++ 和 FFmpeg 库的简单示例代码: ```cpp #include <stdio.h> #include <stdlib.h> #include <string.h> extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> } #define MAX_RTP_PKT_LENGTH 1360 int main(int argc, char* argv[]) { AVFormatContext* fmt_ctx = NULL; AVCodecContext* codec_ctx = NULL; AVCodec* codec = NULL; AVPacket pkt; AVFrame* frame = NULL; uint8_t* frame_buf = NULL; int frame_size = 0; int got_frame = 0; int ret = 0; int i; av_register_all(); // 打开 RTP 输入文件 if (avformat_open_input(&fmt_ctx, "rtp://127.0.0.1:1234", NULL, NULL) != 0) { printf("Could not open input file.\n"); return -1; } // 查找视频流 if (avformat_find_stream_info(fmt_ctx, NULL) < 0) { printf("Could not find stream information.\n"); return -1; } int video_stream_index = -1; AVStream* video_stream = NULL; for (i = 0; i < fmt_ctx->nb_streams; i++) { if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; video_stream = fmt_ctx->streams[i]; break; } } if (video_stream_index == -1) { printf("Could not find video stream.\n"); return -1; } // 查找视频解码器 codec = avcodec_find_decoder(video_stream->codecpar->codec_id); if (codec == NULL) { printf("Could not find codec.\n"); return -1; } // 初始化视频解码器上下文 codec_ctx = avcodec_alloc_context3(codec); if (codec_ctx == NULL) { printf("Could not allocate codec context.\n"); return -1; } if (avcodec_parameters_to_context(codec_ctx, video_stream->codecpar) < 0) { printf("Could not copy codec parameters.\n"); return -1; } if (avcodec_open2(codec_ctx, codec, NULL) < 0) { printf("Could not open codec.\n"); return -1; } // 解码 RTP 数据包 av_init_packet(&pkt); pkt.data = (uint8_t*)malloc(MAX_RTP_PKT_LENGTH); while (1) { // 从 RTP 文件中读取数据包 ret = av_read_frame(fmt_ctx, &pkt); if (ret < 0) { break; } // 跳过 RTP 头 uint8_t* rtp_data = pkt.data + 12; // 提取 H.264 NAL 单元 while (pkt.size > 0) { int nal_start = 0, nal_end = 0; for (i = 0; i < pkt.size - 4; i++) { if (rtp_data[i] == 0x00 && rtp_data[i + 1] == 0x00 && rtp_data[i + 2] == 0x00 && rtp_data[i + 3] == 0x01) { if (nal_start != 0) { nal_end = i - 1; break; } else { nal_start = i + 4; } } } if (nal_end == 0) { nal_end = pkt.size - 1; } // 拼接 NAL 单元 int nal_size = nal_end - nal_start + 1; if (frame_buf == NULL) { frame_buf = (uint8_t*)malloc(nal_size); } else { frame_buf = (uint8_t*)realloc(frame_buf, frame_size + nal_size); } memcpy(frame_buf + frame_size, rtp_data + nal_start, nal_size); frame_size += nal_size; // 解码 H.264 帧 while (frame_size > 0) { ret = avcodec_decode_video2(codec_ctx, frame, &got_frame, &pkt); if (ret < 0) { printf("Error decoding frame.\n"); break; } if (got_frame) { // 处理解码后的视频帧 printf("Decoded frame: %dx%d\n", codec_ctx->width, codec_ctx->height); } // 移动指针 int consumed = av_parser_parse2(codec_ctx->parser, codec_ctx, &pkt.data, &pkt.size, rtp_data + nal_end + 1, pkt.size - nal_size - (rtp_data + nal_end + 1 - pkt.data), pkt.pts, pkt.dts, pkt.pos); frame_size -= nal_size + consumed; rtp_data += nal_size + consumed; } // 移动指针 pkt.size -= nal_size; } av_packet_unref(&pkt); } // 释放资源 if (codec_ctx != NULL) { avcodec_close(codec_ctx); avcodec_free_context(&codec_ctx); } if (fmt_ctx != NULL) { avformat_close_input(&fmt_ctx); } if (frame_buf != NULL) { free(frame_buf); } return 0; } ``` 这里使用了 FFmpeg 库中的函数进行 RTP 数据包的解析和 H.264 数据的解码,具体流程请参考代码注释。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值