从零开始精通RTSP之传输H265视频流

2024-05-04 11:36

本文主要是介绍从零开始精通RTSP之传输H265视频流,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

概述

        在上一篇文章中,我们详细介绍了使用RTP传输H264视频流的打包方法。接下来,我们继续介绍RTP传输H265视频流的打包方法。H265,正式名称为高效视频编码,英文全称为High Efficiency Video Coding(HEVC),是国际电信联盟视频编码专家组和国际标准化组织/国际电工委员会动态图像专家组共同开发的下一代视频编码标准。作为H264/MPEG-4 AVC的继任者,H265旨在提供更高的视频压缩效率,能够在保持相同视频质量的前提下大幅度减少视频文件的大小,或者在相同的比特率下提供显著提升的图像质量。当使用RTP传输H265视频流时,也需要遵循一定的打包和传输规则。

 

H265 NALU

        H265 NALU是H265编码视频流的基本数据单元,用于承载编码后的视频数据,并提供网络传输的抽象。NALU的设计旨在使视频编码与底层网络传输协议分离,使得H265编码的视频内容能够适应各种网络环境和应用需求。

        每个NALU都以一个固定长度的NAL Unit Header开始,NAL Unit Header占用两个字节,通常包含以下几个字段。

        Forbidden Zero Bit (F): 占1位,这一位必须为0。如果为1,则表示语法错误,整个NALU将被丢弃。

        NALU Type (Type): 占6位,定义了NALU所携带数据的类型。总共有64种可能的类型(范围是0-63),其中0-31是VCL(视频编码层)NAL单元,用于携带编码的视频数据;而32-63是非VCL NAL单元,用于携带控制信息或元数据。不同的NALU Type对应着不同的编码数据或控制信息,比如:P帧和B帧为1,IDR帧为19,VPS(Video Parameter Set)为32,SPS(Sequence Parameter Set)为33,PPS(Picture Parameter Set)为34,SEI(Supplemental Enhancement Information)为39等。

        LayerId: 占6位,用于表示NAL所在的Access Unit所属的层,是为了HEVC的继续扩展而设置的。在当前的HEVC标准中,这个字段通常被设置为0,但在未来的扩展中可能会用到。

        TID: 占3位,用于指定NAL单元的时间标识符,一般取值为1。它帮助解码器确定NAL单元在视频流中的时间位置,从而正确解码和播放视频。

        紧跟在NAL Unit Header之后的是NAL Unit Payload,包含了编码视频流的核心数据和辅助信息,是视频解码和播放的基础。在实际的网络传输和存储中,NALU通常还需要进一步封装成以下格式中的一种。

        Annex B格式:在Annex B格式中,每个NALU之前添加一个Start Code Prefix,可以是0x000001或0x00000001,用于标识NALU的起始位置。相邻NALU之间,以此方式明确分隔。

        AVCC (Advanced Video Coding Container) 格式:AVCC格式常见于MP4容器中,NALU不再使用Start Code Prefix,而是通过Length字段来标识每个NALU的长度。SPS和PPS等参数以NALU形式封装,并在MP4文件的hvcC盒(Box)中以字节串的形式存储。

 

封装方法

        H265 NALU在封装到 RTP包中时,需要遵循一定的规则和流程,以确保数据能够被正确地传输、接收和解码。根据NALU的大小和传输需求,可以选择以下三种常见的封装方法。

        1、单NALU封装。对于小型的NALU,(比如:P帧、B帧),可以直接将整个NALU放入一个RTP包的Payload中,无需额外处理。此时,RTP包的结构如下。

+-----------------------------+
| RTP Header (12 Byte)        |
| NALU Header (2 Byte)        |
| NALU Data ...               |
+-----------------------------+

        2、FU-A分包。对于大型NALU(比如:某些关键帧),如果其大小超过了RTP包的最大有效载荷MTU,可以使用Fragmentation Unit A方式进行分片。原始NALU会被拆分成多个片段,每个片段作为一个独立的RTP包发送。此时,RTP包的结构如下。

+-----------------------------+
| RTP Header (12 Byte)        |
| FU Indicator (2 Byte)       |
| FU Header (1 Byte)          |
| Fragmented NALU Data ...    |
+-----------------------------+

        可以看到,FU-A分包在12个字节的RTP Header后,有三个字节的分包头,分别为:FU Indicator和FU Header。

        FU Indicator占用两个字节,由以下部分组成。

        F (1 bit): 禁止位,与NALU Header的F位一致。

        Type (6 bits): 分包类型,二进制固定为110001(对应十进制的49),表示FU-A类型。

        LayerId (6 bits): 与NALU Header的LayerId一致。

        TID (3 bits): 与NALU Header的TID一致。

        FU Header占用一个字节,由以下部分组成。

        S (1 bit): 分包起始位。如果该FU是原始NALU的第一个片段,S设为1。否则,设为0。

        E (1 bit): 分包结束位。如果该FU是原始NALU的最后一个片段,E设为1。否则,设为0。

        Type (6 bits): 原始NALU类型,与NALU Header的Type一致,用于在重组时恢复原始NALU Header。

        3、STAP-A聚合

        对于多个小尺寸NALU,如果它们具有相近的解码时间戳,且合并后总尺寸仍小于MTU,可以使用Single-Time Aggregation Packet A方式将多个NALU合并到一个RTP包中。此时,RTP包的结构如下。

+-----------------------------+
| RTP Header (12 Byte)        |
| STAP-A Header (2 Byte)      |
| NALU Payload1 Size (2 Byte) |
| NALU Payload1               |
| NALU Payload2 Size (2 Byte) |
| NALU Payload2               |
| ...                         |
+-----------------------------+

        STAP-A Header紧跟在RTP Header之后,占用两个字节(与NALU Header结构类似),用于标识这是一个STAP-A包,其Type值固定为48。在每个聚合的NALU前,会有一个长度字段(通常为2个字节),表明后续NALU数据的长度。所有聚合在STAP-A包中的NALU都共享相同的时间戳,这是STAP-A包的一个重要特征。

        注意:无论采用上面的哪种封装方法,NALU Data或NALU Payload中都不包括Annex B格式中的起始码(比如:0x000001或0x00000001),因为RTP包已经提供了足够的信息来标识NALU的边界。

 

FU-A分包及重组

        在服务端,FU-A分包的大致步骤如下。

        1、原NALU切割: 大型NALU被拆分成多个连续的片段。切割位置通常选择在NALU内部的编码块边界,以避免破坏编码结构。

        2、片段标识: 每个片段(FU)在RTP Payload中添加一个FU Header,用于标识该片段属于哪个原始NALU,以及其在原始NALU中的位置。

        3、独立传输: 每个FU作为一个独立的RTP包发送,每个RTP包的Payload仅包含一个FU。

        客户端接收到FU-A分包的RTP包后,根据RTP Header解析出Payload Type,确认为H265 FU-A数据后,按照以下步骤处理。

        1、FU分包头解析: 提取FU Indicator和FU Header中的信息。

        2、片段重组: 将收到的FU片段按照RTP包的Sequence Number顺序重新组合,将所有片段的Fragmented NALU Data拼接在一起。

        3、NALU还原: 在重组后的NALU数据前添加原始NALU Header(根据FU分包头中的信息恢复),形成完整的NALU结构。

        4、解码处理: 将还原后的完整NALU提交给H265解码器进行解码。

        使用FU-A封装方法进行分包和重组时,有以下几点需要特别注意。

        1、顺序传输: FU-A分包的RTP包必须严格按照分包顺序发送和接收,以确保正确重组。

        2、丢包处理: 如果中间某个FU片段丢失,可能导致原始NALU无法正确重组。接收端可以根据RTP包的序列号和确认机制检测丢包,并尝试通过重传请求(比如:RTCP的NACK)恢复丢失片段。

        3、时间戳同步: 所有FU片段共享同一个解码时间戳,确保解码时的正确同步。

 

 

这篇关于从零开始精通RTSP之传输H265视频流的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/959109

相关文章

探索蓝牙协议的奥秘:用ESP32实现高质量蓝牙音频传输

蓝牙(Bluetooth)是一种短距离无线通信技术,广泛应用于各种电子设备之间的数据传输。自1994年由爱立信公司首次提出以来,蓝牙技术已经经历了多个版本的更新和改进。本文将详细介绍蓝牙协议,并通过一个具体的项目——使用ESP32实现蓝牙音频传输,来展示蓝牙协议的实际应用及其优点。 蓝牙协议概述 蓝牙协议栈 蓝牙协议栈是蓝牙技术的核心,定义了蓝牙设备之间如何进行通信。蓝牙协议

ROS2从入门到精通4-4:局部控制插件开发案例(以PID算法为例)

目录 0 专栏介绍1 控制插件编写模板1.1 构造控制插件类1.2 注册并导出插件1.3 编译与使用插件 2 基于PID的路径跟踪原理3 控制插件开发案例(PID算法)常见问题 0 专栏介绍 本专栏旨在通过对ROS2的系统学习,掌握ROS2底层基本分布式原理,并具有机器人建模和应用ROS2进行实际项目的开发和调试的工程能力。 🚀详情:《ROS2从入门到精通》 1 控制插

Android从零开始搭建MVVM架构(5)—— LifeCycle详解

1.Lifecycle简介 为什么要使用lifecycle? activity 和fragment 是有声明周期的,有时候,我们的很多操作需要写在声明周期的方法中,比如,下载,文件操作等,这样很多情况下回导致,我们在activity中的声明周期方法中写越来越多的代码,activity或者fragment 越来越臃肿,代码维护越来越困难。 使用lifecycle就可以很好的解决这类问题。 lifec

Android从零开始搭建MVVM架构(4)——LiveData

LiveData 介绍 Livedata 是 Google 推荐的 Android 架构组件之一,是一个存放可被观察的数据持有类,有生命周期感知功能,解决了android开发者需要去手动处理生命周期的痛点。 比如当我们使用 Retrofit+Rxjava处理接口回调数据时,需要考虑activity 或 fragment 生命周期,以解决 onStop 或 onDestory之后回调数据的问题。现

Android从零开始搭建MVVM架构(3)——ViewModel

ViewModel类是被设计用来以可感知生命周期的方式存储和管理 UI 相关数据,ViewModel中数据会一直存活即使 activity configuration发生变化。 ViewModel有什么优势? 1.数据持久化 activity 在销毁重建时,之前我们可以用 activity 的onSaveInstanceState()机制保存和恢复数据,但缺点很明显,onSaveInstan

TCP 可靠传输的工作原理

转载地址:https://my.oschina.net/xinxingegeya/blog/485233 感谢原作者 TCP 可靠传输的工作原理 ARQ(Automatic Repeat-reQuest)(自动重传请求) 停止等待ARQ协议 连续ARQ协议   停止等待ARQ协议 全双工通信的双发既是发送方也是接收方。下面为了讨论问题的方便,我们仅考虑A发送数据而B接受数据

React+TS 从零开始教程(2):简中简 HelloWolrd

源码链接:https://pan.quark.cn/s/c6fbc31dcb02 这一节,我们来见识React+TS的威力,开始上手开发第一个组件,什么组件呢? 当然是简中简的 HelloWolrd组件啦。 在src下创建一个components,然后新建Hello.tsx 为什么是tsx呢,这个目的就是告诉编译器,我这个文件是支持jsx语法的,如果遇到你看不懂的标签,就当作React Ele

从零开始学数据结构系列之第三章《平衡二叉树基础概念》

文章目录 前言什么是平衡二叉树往期回顾 前言 ​   在前面的学习过程中,我们了解到二叉排序树可以在一定程度上提高查找(搜索)的效率,但仍然会出现特殊情况,让二叉排序树失效。例如,将序列{1,2,3,4,5,6}中的元素依次插入到二叉排序树中,会得到右斜树,这就相当于一个单链表了,搜索效率降低为O(n)。   于是在 1962 年,一个姓 AV 的大佬(G. M. Ade

多路h265监控录放开发-(12)完成全部开始录制和全部停止录制代码

xviewer.h 新增 public:void StartRecord();//126 开始全部摄像头录制void StopRecord();//126 停止全部摄像头录制 xviewer.cpp 新增//视频录制static vector<XCameraRecord*> records;//126void XViewer::StartRecord() //开始全部摄像头录

H264 视频文件 帧格式 传输封装等 杂碎

rfc3984  Standards Track [Page 2] RFC 3984 RTP Payload Format for H.264 Video February 2005 1.  按照RFC3984协议实现H264视频流媒体 nalu单元 包起始 0x 00 00 00 01 H.264 NAL格式及分析器 http://hi.baidu.com/zsw%5Fdavy/b ...