热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

RTP打包H264的Nalu包解析

1.预备视频:由一副副连续的图像构成,由于数据量比较大,因此为了节省带宽以及存储,就需要进行必要的压缩与解压缩,

1.  预备

      视频:

             由一副副连续的图像构成,由于数据量比较大,因此为了节省带宽以及存储,就需要进行必要的压缩与解压缩,也就是编解码。

      h264裸码流:

             对一个图像或者一个视频序列进行压缩,即产生码流,采用H264编码后形成的码流就是h264裸码流。

      码流传输:

             发送端将H264裸码流打包后进行网络传输,接收端接收后进行组包还原裸码流,然后可以再进行存储,转发,或者播放等等相关的处理。

             存储转发可以直接使用裸码流,播放则需要进行解码和显示处理

      解码显示:       

             一般会解成YUV数据,然后交给显示相关模块做处理,如使用openGL或者D3D等进行渲染(采用3d的方式来显示2d的图像称为渲染)。

              解码又分软件解码和硬件解码,

              (1)软解一般ffmpeg

              (2)硬解则各不同,由各硬件厂商开放sdk来处理,如:hisi,intel media sdk,nvida gpu,apple ios videotoolbox等。

2.  h264码流传输

      类似发送网页文本数据有http一样,视频数据在网络上传输也有专门的网络协议来支持,如:rtsp,rtmp等。

      由于码流是一帧一帧的图片数据,所以传输的时候也是一帧帧来传输的,因此这里就会涉及到各种类型的帧处理了。

      I 帧: 参考帧或者关键帧,可以理解为是一帧完整画面,解码时只需要本帧数据就解码成一幅完整的图片,数据量比较大。

      P帧: 差别帧,只有与前面的帧或I帧的差别数据,需要依赖前面已编码图象作为参考图象,才能解码成一幅完整的图片,数据量小。

      B帧: 双向差别帧,也就是B帧记录的是本帧与前后帧的差别,需要依赖前后帧和I帧才能解码出一幅完整的图片,数据量小。

      由于I帧比较大,已经超出mtu最大1500,所以需要拆包分片传输,这里说的拆包发送不是指发送超过1500的数据包时tcp的分段传输或者upd的ip分片传输,而是指rtp协议本身对264的拆包。

      rtp打包后将数据交给tcp或者upd进行传输的时候就已经控制在1500以内,这样可以提高传输效率,避免一个I帧万一丢失就会造成花屏或者重传造成延时卡顿等等问题。

      (顺便提一句,rtmp打包就比较简单,由于是基于tcp的协议,大包直接交给tcp去做分段传输,rtmp通过设置合适的trunk size去发送一帧帧数据)

      既然要进行拆包发送与接收,就少不了需要相关的包结构以及打包组包了,继续。。。              

 3.   H264在网络传输的单元:NALU

        NALU结构:NALU header(1byte) + NALU payload

        header 部分可以判断类型等信息,从右往左的低5个bit位。

SPS:  0x67    header & 0x1F = 7 I Frame: 0x65  header & 0x1F = 5 
PPS:  0x68    header & 0x1F = 8  P Frame: 0x41   header & 0x1F = 1
SEI:  0x66    header & 0x1F = 6

        payload 部分可以简单理解为编码后的264帧数据      

        详细可以去查阅 h264 NALU 语法结构

4.   h264的RTP打包

               1.  单NALU:   P帧或者B帧比较小的包,直接将NALU打包成RTP包进行传输    RTP header(12bytes) + NALU header (1byte) + NALU payload

               2.  多NALU:   特别小的包几个NALU放在一个RTP包中  

               3.  FUs(Fragment Units):   帧长度超过MTU的,就必须要拆包组成RTP包了,有FU-A,FU-B。

            对于FU这种方式的,RTP包构成为:RTP header (12bytes)+ FU Indicator (1byte)  +  FU header(1 byte) + NALU payload

           看到这里会不禁思考,

              (1)NALU头不见了,如何判断类型?实际上NALU头被分散填充到FU indicator和FU header里面了

                  如果bit位按照从左到右编号0-7来算,nalu头中0-2前三个bit放在FU indicator的0-2前三个bit中,后3-7五个bit放入FU header的后3-7五个中。还原的时候也可以从FU的indicator和header中得到,也就是:

//NALU header = (FU indicator & 0xe0) | (FU header & 0x1F)   取FU indicator前三和FU header后五 
headerStart[1] = (headerStart[0]&0xE0)|(headerStart[1]&0x1F);  

 因此查看I帧p帧类型,遇到FU分片的,直接看第二个字节,即Fu header后五位,这个跟直接看NALU头并无差异,一般有:0x85,0x05,0x45等等。对于不是FU分片的,则因为NALU头是存在的,直接看就行了。

                   2.  多个RTP包如何还原组合成回一个完整的I或者P帧? 在FU header中有标记为判断

                         照旧从左到右,Fu header前两个bit表示start和end标记,start为1表示一个I或P帧分片开始,end为1表示一个I或P帧分片结束

                  3.   如何查看是一个I帧分片开始?   

                        看第一个字节FU Indicator,照旧从左到右,Fu Indicator前三个bit是NALU头的前三个bit,后五位为类型FU-A:28(11100),FU-B:29(11101)。

                         RTP抓包看下来整个payload是0x7c开头

                         如果不是FU分片,第一字节就是NALU头,如:0x67,0x68,0x41等

5. 抓包分析如下图:

        --->RTP包中接收的264包是不含有0x00,0x00,0x00,0x01头的,这部分是rtp接收以后,另外再加上去的,解码的时候再做判断的

      1. SPS

     2.  I帧分片开始,第一个字节FU Indicator,0x7c, 后五位11100,28表示FU-A。

               第二个字节FU Header,0x85,前两个bit start位1,end位0 表示 分片开始,后五个bit值5,表示I帧。

        I帧分片,0x7c开头,第二个字节0x05, FU Header start和end位 0,后五个bit值5,I帧,表示I帧的中间分片,不是起始分片也不是终止分片。         

        I帧分片结束,7c开头,第二个字节0x45,FU Headr,start 0,end1,后五个bit值5,I帧, Mark标记一帧结束,是I帧的终止分片。

    3.  p帧,第一个字节:0x41   单帧,非FU分片形式的。

       4. P帧分片开始:第一个字节FU Indicator,0x7c, 后五位11100,28,FU-A

                                   第二个字节FU Header,0x81, 前两个bit start位1,end位0 表示 分片开始,后五个bit 值是1,p帧。

           P帧分片结束:0x5c开头,第二个字节0x41,FU Headr,start 0,end1,后五个bit值1,P帧, Mark标记一帧结束

6.  参考live555相关的源码如下:

Boolean H264VideoRTPSource
::processSpecialHeader(BufferedPacket* packet, unsigned& resultSpecialHeaderSize)
{unsigned char* headerStart &#61; packet->data();unsigned packetSize &#61; packet->dataSize();unsigned numBytesToSkip;// Check the &#39;nal_unit_type&#39; for special &#39;aggregation&#39; or &#39;fragmentation&#39; packets:if (packetSize <1) return False;fCurPacketNALUnitType &#61; (headerStart[0]&0x1F);//FU Indicator后五位即NALU类型 0x1F &#61; 0001 1111switch (fCurPacketNALUnitType) {case 24: { // STAP-AnumBytesToSkip &#61; 1; // discard the type bytebreak;}case 25: case 26: case 27: { // STAP-B, MTAP16, or MTAP24numBytesToSkip &#61; 3; // discard the type byte, and the initial DONbreak;}case 28: case 29: { // // FU-A or FU-B// For these NALUs, the first two bytes are the FU indicator and the FU header.// If the start bit is set, we reconstruct the original NAL header into byte 1:if (packetSize <2) return False;unsigned char startBit &#61; headerStart[1]&0x80; //FU Header start标记位 0x80&#61; 1000 0000unsigned char endBit &#61; headerStart[1]&0x40; //FU Header End标记位 0x40&#61; 0100 0000if (startBit) {fCurrentPacketBeginsFrame &#61; True;headerStart[1] &#61; (headerStart[0]&0xE0)|(headerStart[1]&0x1F); //还原NALU头numBytesToSkip &#61; 1;} else {// The start bit is not set, so we skip both the FU indicator and header:fCurrentPacketBeginsFrame &#61; False;numBytesToSkip &#61; 2;}fCurrentPacketCompletesFrame &#61; (endBit !&#61; 0);break;}default: {// This packet contains one complete NAL unit:fCurrentPacketBeginsFrame &#61; fCurrentPacketCompletesFrame &#61; True; //默认没有分片&#xff0c;完整的NALUnumBytesToSkip &#61; 0;break;}}resultSpecialHeaderSize &#61; numBytesToSkip;return True;
}


推荐阅读
  • 在Linux系统中,网络配置是至关重要的任务之一。本文详细解析了Firewalld和Netfilter机制,并探讨了iptables的应用。通过使用`ip addr show`命令来查看网卡IP地址(需要安装`iproute`包),当网卡未分配IP地址或处于关闭状态时,可以通过`ip link set`命令进行配置和激活。此外,文章还介绍了如何利用Firewalld和iptables实现网络流量控制和安全策略管理,为系统管理员提供了实用的操作指南。 ... [详细]
  • Ihavetwomethodsofgeneratingmdistinctrandomnumbersintherange[0..n-1]我有两种方法在范围[0.n-1]中生 ... [详细]
  • 本文回顾了作者初次接触Unicode编码时的经历,并详细探讨了ASCII、ANSI、GB2312、UNICODE以及UTF-8和UTF-16编码的区别和应用场景。通过实例分析,帮助读者更好地理解和使用这些编码。 ... [详细]
  • 最详尽的4K技术科普
    什么是4K?4K是一个分辨率的范畴,即40962160的像素分辨率,一般用于专业设备居多,目前家庭用的设备,如 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 在对WordPress Duplicator插件0.4.4版本的安全评估中,发现其存在跨站脚本(XSS)攻击漏洞。此漏洞可能被利用进行恶意操作,建议用户及时更新至最新版本以确保系统安全。测试方法仅限于安全研究和教学目的,使用时需自行承担风险。漏洞编号:HTB23162。 ... [详细]
  • Java Socket 关键参数详解与优化建议
    Java Socket 的 API 虽然被广泛使用,但其关键参数的用途却鲜为人知。本文详细解析了 Java Socket 中的重要参数,如 backlog 参数,它用于控制服务器等待连接请求的队列长度。此外,还探讨了其他参数如 SO_TIMEOUT、SO_REUSEADDR 等的配置方法及其对性能的影响,并提供了优化建议,帮助开发者提升网络通信的稳定性和效率。 ... [详细]
  • Python 程序转换为 EXE 文件:详细解析 .py 脚本打包成独立可执行文件的方法与技巧
    在开发了几个简单的爬虫 Python 程序后,我决定将其封装成独立的可执行文件以便于分发和使用。为了实现这一目标,首先需要解决的是如何将 Python 脚本转换为 EXE 文件。在这个过程中,我选择了 Qt 作为 GUI 框架,因为之前对此并不熟悉,希望通过这个项目进一步学习和掌握 Qt 的基本用法。本文将详细介绍从 .py 脚本到 EXE 文件的整个过程,包括所需工具、具体步骤以及常见问题的解决方案。 ... [详细]
  • com.sun.javadoc.PackageDoc.exceptions()方法的使用及代码示例 ... [详细]
  • 解决问题:1、批量读取点云las数据2、点云数据读与写出3、csf滤波分类参考:https:github.comsuyunzzzCSF论文题目ÿ ... [详细]
  • 在C#编程中,数值结果的格式化展示是提高代码可读性和用户体验的重要手段。本文探讨了多种格式化方法和技巧,如使用格式说明符、自定义格式字符串等,以实现对数值结果的精确控制。通过实例演示,展示了如何灵活运用这些技术来满足不同的展示需求。 ... [详细]
  • 性能测试中的关键监控指标与深入分析
    在软件性能测试中,关键监控指标的选取至关重要。主要目的包括:1. 评估系统的当前性能,确保其符合预期的性能标准;2. 发现软件性能瓶颈,定位潜在问题;3. 优化系统性能,提高用户体验。通过综合分析这些指标,可以全面了解系统的运行状态,为后续的性能改进提供科学依据。 ... [详细]
  • 小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限
    小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限 ... [详细]
author-avatar
弥晓潞_509
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有