H264码流RTP封装方式详解

文章目录

  • H264码流RTP封装方式详解
    • 1 H264基本概念
    • 2 NALU Header介绍
    • 3 RTP封装H264码流
      • 3.1 单一NALU模式
      • 3.2 组合帧封装模式
      • 3.3 分片封装模式
    • 4 代码解析

在流媒体传输领域,经常使用的传输协议是RTP/RTCP,本文将对RTP对H264进行封装的过程进行详解
有关H264视频的RTP封装格式的规范参照:RFC-3984
有关RTP协议基础知识介绍参照:RTP协议与实战

1 H264基本概念

H264是一种视频压缩的标准,与H265、SVAC等压缩方式一样,主要目的是对视频流进行压缩,以便减少网络传输对网络带宽的占用,H264压缩后的帧类型分为I帧/P帧/B帧。
这里解释下帧类型:

  • I帧,为关键帧,采用帧内压缩技术,通过H264压缩算法解对完整帧数据进行压缩,因此I帧不依赖其前后帧数据,直接可以解压成raw数据
  • P帧,为向前参考帧,对raw帧压缩时,需要参考前面的I帧或P帧,采用帧间压缩技术,因此P帧解码不能单独解析,需要依赖前面的帧数据,如果其前面帧有丢失,会导致花屏。
  • B帧,为双向参考帧,对raw进行压缩时既参考前面的帧,又参考他后面的帧,采用帧间压缩技术,因此其解码是需要前面帧及后面帧同时参与。

在baseline编码规格下,无B帧,在main profile规格下一般有B帧,通常来说B帧参与编码会提高压缩率,降低帧大小,但是会增加编解码复杂性。

H264编码后的码流又NAL(网络抽象层)和VCL(视频编码层)构成,VCL数据传输或者存储之前,会被映射到一个NALU中,H264数据包含一个个的NALU,H.264的编码帧序列包括一系列的NAL单元,每个NAL单元包含一个RBSP,单元的信息头定义了RBSP单元的类型,NAL单元其余部分为RBSP数据,这里对NALU头字节进行i二少,因为RTP封装方式,主要针对NALU头进行处理。

2 NALU Header介绍

NALU头前通常包含一个 StartCode,StartCode 必须是 0x00000001 或者 0x000001,紧接着就是一个字节的nalu header,nalu header格式如下:

这里注意上面的0~7b不是字节内位的高低,只是表示占了多少位,从左到右顺序才表示从高位到低位,其详解如下:

  • F:forbidden_zero_bit,H264 规范要求该位为 0,占1位,其所在字节的最高位(第7位)
  • NRI:nal_ref_idc,取值 03,占2位,其在字节的56位,指示该 NALU 重要性,对于 NRI=0的NALU 解码器可以丢弃它而不影响解码,该值越大说明NALU越重要。如果当前NALU属于参考帧的数据,或者是序列参数集,图像参数集等重要信息,则该值必须大于0
  • Type:NALU 数据类型,占5 位,其在字节的0~4位,取值 0 ~ 31

有关Type在H264码流中的常用取值如下:

type 描述 备注
1 非IDR的图像片 一般指P帧
5 IDR 图像的片 一般指I帧
6 SEI辅助增强信息帧 解码可不用
7 SPS序列参数集 一般SPS/PPS/I帧组合出现
8 PPS图像参数集 一般SPS/PPS/I帧组合出现
9 分节符 解码可不需要
10 序列结束 很少用到
11 码流结束 很少用到
12 填充 很少用到

以下是截取的H264视频流的二进制数据,结合此二进制数据,我们来分析下其帧类型

  • 0000000109,其中00000001是帧起始位标识,09是nalu header,二进制为00001001,F(7位)为0标识非禁止,NRI(56位)为0标识可丢弃帧,type(04位)为9,表示分隔符,此帧可丢弃,表示分隔符

  • 0000000167,同上面的分析,NALU头为67,二进制为01100111,F=0,NRI=3,type=7,帧类型为SPS帧,非常重要不可丢弃

  • 0000000168,NALU头为68,二进制为01101000,F=0,NRI=3,type=8,帧类型为PPS帧,非常重要不可丢弃

  • 0000000106,NALU头为06,二进制为00000110,F=0,NRI=0,type=6,帧类型为SEI,可丢弃

  • 0000000165,NALU头为65,二进制为01100101,F=0,NRI=3,type=5,帧类型为I帧,非常重要不可丢弃

从上上面的分析可以看出,IDR(SPS/PPS/I)帧通常一起出现,极少数编码单独出现I帧,但是IDR与I帧单独出现也符合规范。

如下为P帧的帧头,可按照上述方式进行解析。

3 RTP封装H264码流

由于H264帧大小差别较大,较小的帧小于MTU,则可单包直接发送,或者多帧组合发送,较大的帧大于MTU需要分片发送,RTP发送H264模式主要有三种:单一NALU模式、组合帧封装模式、分片封装模式,有关组合帧封包和分片封包类型包含好几种,这里介绍常用的两种:STAP-A和FU-A。

这里把RTP头格式先贴出来,具体参数详解看下RFC-3984,后面说明用到

3.1 单一NALU模式

此模式一个RTP包包含一个完整的视频帧,RTP头部之后的一个字节为 NALU Header,之后是NALU数据部分,此视频帧大小需要小于MTU,可以单帧通过网络发送,其RTP封装格式如下:

以SPS为例,SPS一般小于MTU,可采用单NALU封装模式,其封包后的二进制如下:

80 e0 be 8e 8c e8 56 d5 4a 9b 57 b3 67 64 00 29 ad 84 05 45 62 b8 ac 54 74 20 2a 2b 15 c5 62 a3 a1 01 51 58 ae 2b 15 1d 08 0a 8a c5 71 58 a8 e8 40 54 56 2b 8a c5 47 42 02 a2 b1 5c 56 2a 3a 10 24 85 21 39 3c 9f 27 e4 fe 4f c9 f2 79 b9 b3 4d 08 12 42 90 9c 9e 4f 93 f2 7f 27 e4 f9 3c dc d9 a6 b4 03 c0 11 3f 2c a9 00 00 03 00 78 00 00 15 90 60 40 00 3e 80 00 00 11 94 06 f7 be 17 84 42 35

其中80 e0 be 8e 8c e8 56 d5 4a 9b 57 b3为RTP头,按照RTP协议格式解析如下:V=10B=2,Padding=0,Extension=0,CC=0,Mark=1,PT=96,SN=48782,TS=2364036821,SSRC=0x4a9b57b3

RTP头之后一个字节为NALU头,就是SPS帧去掉00000001起始位标识,为67,之后为NALU单元RBSP数据,在编码是简单的做法就是RTP头后直接附加去除起始标识的NALU数据即可。

3.2 组合帧封装模式

此模式是针对多个较小的帧,采用组合成一个RTP包发送的方式,此种方式在H264视频传输中较少使用,一般较小的帧都是通过单一NALU模式发送,此处以STAP-A组合类型为例,组合发送SPS和PPS
组合封包模式格式如下:

假设SPS和PPS的裸流如下:

sps
00 00 00 01 67 64 00 29 ad 84 05 45 62 b8 ac 54 74 20 2a 2b 15 c5 62 a3 a1 01 51 58 ae 2b 15 1d 08 0a 8a c5 71 58 a8 e8 40 54 56 2b 8a c5 47 42 02 a2 b1 5c 56 2a 3a 10 24 85 21 39 3c 9f 27 e4 fe 4f c9 f2 79 b9 b3 4d 08 12 42 90 9c 9e 4f 93 f2 7f 27 e4 f9 3c dc d9 a6 b4 03 c0 11 3f 2c a9 00 00 03 00 78 00 00 15 90 60 40 00 3e 80 00 00 11 94 06 f7 be 17 84 42 35
pps
00 00 00 01 68 33 3c b0 00 00

SPS和PPS组合帧封包后如下:
SPS去掉起始标志,size为117,十六进制为0x75;PPS去掉起始标志,size为0x06

[RTP header 12字节][78 00 75 67 64 00 29 ad 84 05 45 62 b8 ac 54 74 20 2a 2b 15 c5 62 a3 a1 01 51 58 ae 2b 15 1d 08 0a 8a c5 71 58 a8 e8 40 54 56 2b 8a c5 47 42 02 a2 b1 5c 56 2a 3a 10 24 85 21 39 3c 9f 27 e4 fe 4f c9 f2 79 b9 b3 4d 08 12 42 90 9c 9e 4f 93 f2 7f 27 e4 f9 3c dc d9 a6 b4 03 c0 11 3f 2c a9 00 00 03 00 78 00 00 15 90 60 40 00 3e 80 00 00 11 94 06 f7 be 17 84 42 35 00 06  68 33 3c b0 00 00]

其中:

  • 78 STAP-A类型头,其中F为0 NRI为3,type为24,24标识STAP-A类型,此类型标识后续负载为组合帧

  • 00 75 表示SPS的size,后面跟的0x75个自己为去掉起始标志的SPS数据

  • 00 06 表示PPS的size,后面跟的6个字节为去掉起始位的PPS数据

3.3 分片封装模式

如果视频帧大小超过MTU,则RTP封装需要分片封装,H264较常用的分片模式位FU-A,这里详细说明的是FU-A分片方式,其格式如下:

RTP头部之后的第一个字节为 FU indicator,第二个字节为 FU header

FU indicator结构如下所示:

其与nalu header类似,F和NRI取分片的nalu header中对应的F和NRI,Type为分片类型,这里是28,不做详细说明

FU header结构如下:

其中

  • S:start标记位,当为1时表示NALU分片的起始分片。
  • E:end标记位,当为1时表示NALU分片的最后一个分片。
  • R:保留位,可忽略。
  • Type:NALU头里的Type类型,等于帧类型

这两个字节之后跟的是NALU的数据,去掉起始位及NALU header之后的数据,按照分片大小进行分包,同一个帧的分片的头两个字节除了起始和结束FU header中的S和E位不同,其他分片这两个自己都一样,这里起始分片要注意去掉H264起始字符和nalu header,通过FU indicator的F/NRI以及FU header即可组合成NALU header,RTP解封装的时候注意生成NALU头及起始标识。
实例如下:

  • 0x7c:其二进制为:01111100,F=0,NRI=3,type=28表示FU-A分片,FU indicator
  • 0x85:其二进制为:10000101,S=1,E=0,type=5,表示I帧的起始FU-A分片,fu header
    其数据通过wireshark解析后如下图:

有关rtp相关知识及抓包示例,可关注公众号:壹零仓,发送消息rtp获取。

4 代码解析

一般H264进行RTP封装,SPS/PPS采用单一NALU封装方式,I帧/P帧采用FU-A分片模式,如果带有SEI及AUD可过滤掉,也可以采用单一NALU封装方式
有关H264采用单一NALU及FU-A分片进行RTP封装发送的相关代码详解,这里引用FFMPEG源码进行解析,这里引用部分打包的代码,解码和这个过程相反
ffmpeg源码
在libavformat/rtpenc_h264_hevc.c中,如下函数对H264及H265(HEVC)打包并发送

static void nal_send(AVFormatContext *s1, const uint8_t *buf, int size, int last)
{RTPMuxContext *s = s1->priv_data;enum AVCodecID codec = s1->streams[0]->codecpar->codec_id;av_log(s1, AV_LOG_DEBUG, "Sending NAL %x of len %d M=%d\n", buf[0] & 0x1F, size, last);if (size <= s->max_payload_size) {//判断包大小是否小于等于RTP最大负载长度,一般RTP最大负载长度+RTP头小于MTU...flush_buffered(s1, 0);ff_rtp_send_data(s1, buf, size, last);//这里调用此函数直接发送,ff_rtp_send_data中会对数据直接打RTP头后直接发送,//这里由于小于MTU,所以采用单一帧发送模式...} else {//视频帧长度大于MTU时,采用FU分片...if (codec == AV_CODEC_ID_H264) {//只对H264进行注释,H265后续在说uint8_t type = buf[0] & 0x1F;//这里buf已经去掉起始标识00000001,buf[0]标识nalu header,这里取0~4位,即帧类型uint8_t nri = buf[0] & 0x60;//这里取5-6位,即:NRI,这里只是通过按位与的方式,保留了5-6位,并未真正转换为真实值,方面后买你组合//FU indicator字节s->buf[0] = 28;        /* FU Indicator; Type = 28 ---> FU-A */s->buf[0] |= nri; //因为nri只是保留了5-6位,这里直接按位或,即可组成fu-indicator// fu headers->buf[1] = type; //0~4帧类型s->buf[1] |= 1 << 7; //最高位起始位为1标识开始buf  += 1; //原始H264起始分片需要去掉nalu heder字节,这里直接跳过帧头size -= 1; //去掉头字节后,size要减去1flag_byte   = 1;header_size = 2;//fu-a 头长度} else {...}while (size + header_size > s->max_payload_size) {memcpy(&s->buf[header_size], buf, s->max_payload_size - header_size);//发送缓冲buf中已经有了FU-A的2个头字节ff_rtp_send_data(s1, s->buf, s->max_payload_size, 0);//发送分片buf  += s->max_payload_size - header_size;//h264码流指针移动到未发送的起始位置size -= s->max_payload_size - header_size;//未发送的码流数据s->buf[flag_byte] &= ~(1 << 7);//更改fu-header,中间分片为00}s->buf[flag_byte] |= 1 << 6;//更改fu-header,最后一个分片,结束标志值为1memcpy(&s->buf[header_size], buf, size);ff_rtp_send_data(s1, s->buf, size + header_size, last);//打RTP头并发送}
}

H264码流RTP封装方式详解相关推荐

  1. H265码流RTP封装方式详解

    文章目录 H265码流RTP封装方式详解 1 H265编码方式介绍 2 H265码流RTP封装方式 2.1 单一帧封装模式 2.2 组合帧封装方式 2.3 分片封装模式 2.4 代码片段解析 H265 ...

  2. 【H264/AVC 句法和语义详解】(二):h264码流格式与NALU详解一

    上一篇中,我们站在句法元素(或称语法元素)的角度,介绍了H.264的句法和语义,和句法元素的分层结构.在这篇中,我们更进一步,从比特的角度出发,来探索h264码流的组成.通过这篇的学习,我们会初步具备 ...

  3. 音视频压缩:H264码流层次结构和NALU详解

    问题背景: 前面在讲封装格式过程中,都有一个章节讲解如何将H.264的NALU单元如何打包到TS.FLV.RTP中,解装刚好相反,怎么从这些封装格式里面解析出一个个NALU单元.NALU即是编码器的输 ...

  4. sps和pps一篇好的解释 H264码流中SPS PPS详解<转>

    https://blog.csdn.net/luzubodfgs/article/details/86775940 H264码流中NALU sps pps IDR帧的理解 https://blog.c ...

  5. H264/H265码流的编码码率详解

    1.视频码率概念 视频码率是视频数据(视频色彩量.亮度量.像素量)每秒输出的位数,即单位时间传送的数据位数.一般用的单位是kbps(千位每秒).通俗一点来讲就是采样率,单位时间的采样率越大,精度就越高 ...

  6. RTP和RTCP详解

    1 RTP和RTCP详解 文章目录 1 RTP和RTCP详解 1.1 概述 1.2 RTP协议详解 1.3 RTCP协议详解 1.1 概述 在流媒体相关的领域,我们进场会看到RTP/RTCP,其用于流 ...

  7. RTP协议全解(H264码流和PS流)

    1 视频编码的原理 1.1 一个图像或者一个视频序列进行压缩,产生码流. 对图像的处理即是:帧内预测编码 其预测值P,是由已编码的图像做参考,经运动补偿得到的.预测图像P和当前帧Fn相减,得到两图像的 ...

  8. RTP协议全解(H264码流和PS流)——看完这篇可以毕业

    http://blog.csdn.net/bytxl/article/details/50395427 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 1 视频编码的原理 1. ...

  9. H264码流中SPS、PPS详解

    1 SPS和PPS从何处而来? 2 SPS和PPS中的每个参数起什么作用? 3 如何解析SDP中包含的H.264的SPS和PPS串? 1 客户端抓包 在做客户端视频解码时,一般都会使用Wireshar ...

最新文章

  1. 二分法:search insert position 插入位置
  2. 传感器的未来: 10年后我们将会生活在一个极端透明的世界
  3. Linux内核中的IPSEC实现(7)
  4. python难嘛-为什么说python入门很简单,但是在你这很难?
  5. java 树状数组模板源码
  6. BZOJ 2244: [SDOI2011]拦截导弹 DP+CDQ分治
  7. 电磁场与电磁波第一章 矢量分析
  8. centos修改磁盘uuid_CentOS 6如何修改磁盘配额限制
  9. 谷歌新 AI 实验室主管 Hugo 深度学习教程:神经网络、CV、NLP 难点解析
  10. esp8266 阿里云 arduino_ESP8266接入阿里云——基于官方SDK接入阿里云串口获取云下发数据...
  11. 【转】opencv 配置
  12. 【Python】Django CSRF问题
  13. 通过yum安装redis
  14. 使用图灵机器人api接口开发智能聊天机器人
  15. 技术团队绩效考核怎么搞?详解OKR、MBO、KPI、BSC
  16. GIS大数据解决方案
  17. 汇编指令与Intrinsics指令的对应关系汇总
  18. Python爬虫入门教程06:爬取数据后的词云图制作
  19. RBP as a streaming server(一)
  20. 猫喜欢吃鱼,可猫不会游泳

热门文章

  1. PHP留言本,非常适合新手实战操作!
  2. PMP考试-合同的类型与做题技巧-(第6版)
  3. 关于网络流量监控之5分钟流量监控的意义
  4. NRF52832基于SDK15.3 S332协议栈实现adv和rsp广播厂商自定义数据
  5. win7蓝屏0x000000f4修复_紧急提醒:关于电脑F4蓝屏的处理和预防方法
  6. 案例征集正式开启 | 洞见2018年度值得学习的100+创新案例
  7. 中望3D2022 草图绘制:绘图
  8. 自动语音识别技术(ASR)在聋哑儿童计算机辅助教学中的开发与应用
  9. 1. 第一讲 primitives(基础知识)
  10. 千亿身价之后,美团、拼多多的新增量之战