H264码流结构讲解

news/2024/9/15 7:57:01/ 标签: 实时音视频, nginx rtmp

所谓的码流结构就是指:视频经过编码之后所得到的数据是怎样排列的,换句话说,就是编码后的码流我们该如何将一帧一帧的数据分离开来,哪一块数据是一帧图像,哪一块是另外一帧图像,只要了解了这个,后面的事情就好办了。

1.H264帧的定义

        在H264编码中有I帧,p帧和B帧。每一帧都相当于一张静止的图像。在实际的码流传输中会利用编码器来压缩图像以减少视频的体积,其中I帧、P帧、B帧最为常见。

I 帧: I 帧通常又称之为内部画面,它通常是视频编码的第一帧。它的最大特点是自带一个完整的图像信息,在解码的过程中只需要解码本帧就可以完整地提取出一个完整的画面。假设一个视频中丢失了I 帧,则整个视频则会处于黑屏状态,后面的视频则无法正常播出。由此可见, I 帧在视频编码中扮演着相当重要的角色。但是它也有自身的缺点,那就是I 帧的体积比较大,假设在传输视频中全部采用 I 帧去传输,那整个网络链路都承受着巨大的压力。 所以,I 帧就要配合 P 帧、 B 帧等进行数据的传输。
P 帧: P 帧又称之为前向参考帧,此帧的特点是需要参考前一帧的图像信息才可以正确把图像解码出来。 P 帧指的是这一帧和前一帧的差别,并通过将图像序列中已经编码后的冗余信息充分去除来压缩传输数据量的编码图像。
B 帧: B 帧也称之为双向参考帧, B 帧的特点是以前面的帧 (I 帧或者 P ) 或者后面的帧 ( 也是 I 帧、 P ) 作为参考帧找出 B 帧的预测值,并且取预测差值和预测矢量进行传送。所以在拉流端解码B 帧的时候不仅需要获得前面的缓存视频,还需要获得后面的缓存视频才能够正常解码 B 帧。所以, B 帧虽然压缩率更高,但是更消耗CPU 资源

2.GOP(I帧间隔)

     GOP 指的是两个 I 帧之间的距离,在一个 GOP 包含了一组连续的图片。在一个 GOP 中包含了 I 帧、 P 帧、 B 帧,直到下一个 I 帧的出现,一个 GOP 才算结束。
    通常来说,I 帧所占用的字节和体积大于 P 帧、而 P 帧所占用的字节大于 B 帧。所以在码率不变的情况下,可以调整 GOP 的长度去改善画质, GOP的长度越长,所得到的P 帧和 B 帧更多,画面的质量和细节就会更好。

 

3.Slice片

        一帧图像可以分成若干片,每一片又可分成若干个宏块。宏块是视频的最小单位。

        为了并行编码,所以引入了片的概念,所谓的并行编码就是把一帧图像分成几个片,每个片相互独立进行编码。

4.H264的码流格式

        H264的码流格式可以分为两类,不同的码流格式,对于编码器的处理方式不同

        1.Annexb格式:也叫字节流格式,字节流格式使用起始码来表示一个编码数据的开始,起始码不是图像数据的一部分,只起到一个分隔图像的作用。大部分的编码器默认输出的是字节流格式的,字节流格式也叫做H264裸流。字节流格式的基本数据单元是NAL(也叫NALU)单元,为了从字节流中提取出NAL单元,协议规定,每个NAL前必须加上一个开始码

0x00000001(4字节)或0x000001(三字节)

        这里要注意一下,NAL中的h264数据也有可能包含0x00000001或0x000001,一般对NAL的这种情况会做出如下的处理:
        (1)00 00 00 修改成00 00 00 03 00

        (2)00 00 01 修改成00 00 00 03 01

        (3)00 00 02 修改成00 00 00 03 02

        (4)00 00 03 修改成00 00 00 03 03

           即只能出现连续两个字节的 00 00

 

 所以基于字节流的H264裸流=起始单元+NALU + 起始单元 + NALU +............

   2.MP4格式,MP4格式没有起始单元,但是MP4编码中使用了4字节长度作为标识,用来表示编码数据的长度。每次读取4字节数据计算出编码数据的长度,然后取出编码数据,再读取4字节,计算出数据长度,然后取出数据,这样一直计算下去就可以取出编码数据了。

5.NALU组成结构

       H264裸流分层可以分成两层,一层是VCL层(视频编码的底层),另外一层是NAL层(网络提取层也叫NALU),VCL层包含的是H264的视频内容,NALU负责的是把网络视频进行打包和传输,对于开发人员我们一般是不关心CVL层的,我们关注的重点一般都是NALU

        对于NALU,我们主要抓住的是NALU Header(1byte)和RBSP(原始字节序列载荷)

       

(1)NALU Header主要包括三部分,分别为:forbidden_zero_bitnal_ref_idcnal_unit_type,它们总共占据一个字节,也就 是说,NALU Header,在整个NALU中,占据一个字节。

        forbidden_zero_bit(禁止位)占据一个bit固定为0,当它不为0时表示在整个网络传输中,产生了错误,此时解码器可以不对此段数据进行解码

        nal_ref_idc:取值0~3,代表当前NALU的重要性,数字越大越重要,就需要被优先保护。尤其是当前NALU为图像参数集、序列参数集或IDR帧,或参考图像带(片/Slice)又或者是参考图像的带数据分割时,nal_ref_idc肯定是不为0的。

        nal_unit_type:这个表示NALU Header后面的RBSP数据的类型

       pps NALU(图像参数集):pps主要包含熵编码类型、基础QP和最大参考帧数量等基本的编码信息

       sps NALU(参数序列集):主要包含图像的宽、高、YUV格式和位深等基本的信息,如果没有pps和sps中包含的基础信息,之后的I帧P帧和B帧就无法进行解码。

       IDR NALU(及时解码刷新图像):IDR帧,即:及时解码刷新图像,它是一个序列的第一个图像,H.264引入IDR图像是为了解码的重新同步。当解码器解码到IDR图像时,立即将参考帧队列清空,将已解码的数据 全部输出或抛弃,重新查找参数集,开始一个新的序列。这样一来,如果前一个序列发生重大错误,在这里就可以获得重新同步。所以IDR图像之后的图像,永远不会引用IDR图像之前的图像来解码。并且IDR图像一定是I图像,而I图像不一定是IDR图像(H264里没有图像层,图像可以理解为帧、片或宏块)。

        如何区分这几种NALU的类型:
        

NALU = NALU Header + NALU Body       

NALU Body结构如图所示:

           NALU主体涉及三个重要的名词,分别是EBSP、RBSP和SODB。EBSP包含了RBSP,RBSP中又包含了SODB

    SODB(数据比特串):就是VCL层,是最基本的编码数据,没有包含任何的附加信息。视频解码的目的就是获取SODB

    RBSP(原始字节序列载荷):在SODB的后面加上了结尾比特,一个bit1和若干个bit0,用来字节对齐

    EBSP(扩展字节序列载荷):在RBSP的基础上添加了仿效字节(0x03).这样做的原因上面也介绍过,因为NALU的起始数据是 00 00 00 00 01或 00 00 01,防止NALU中的其他数据也出现 00 00 00 01 或 00 00 01,所以NALU会把连续的两个 00 00后插入一个0x03,在解码时,会自动的把加入的0x03丢掉,这个操作叫做脱壳操作。

所以通过以上的分析我们可以得出NALU = NALU Header + EBSP(出现连续00 00的情况)或NALU = NALU Header + =RBSP(未出现连续00 00 的情况)

通常来说,一个原始的 H.264 [StartCode] [NALU Header] [NALU Payload] 。三部分组成,其中 Start Code 用于标示这是一个 NALU 单元的开始,必须是"00 00 00 01" "00 00 01" ,具体的如下图:

如图所以,画框的都是一帧NALU单元,我们介绍一下NALU数据的特殊意义:

        00 00 00 01 06 05这是SEI数据,是视频的附加信息,包含了用户的自定义信息,比如时间戳,字幕和弹幕信息等。SEI信息一般放在编码图像之前,很多时候SEI是可以忽略的

        00 00 00 01 67这是SPS数据,这指的是序列参数集,它保存了一组编码视频序列的全局参数。编码视频序列指的是原始数据经过编码后组成的一系列序列集。

        00 00 00 01 68PPS数据,这指的是图像的参数集,主要用于保存图像序列集中一个或多个独立的图像,一般情况下,配合SPS和PPS都是H264开头的两个NALU头。

        00 00 00 01 65IDR数据,IDR指的是H264一帧完整的图像数据,也就是常说的关键帧。

        所以一个标准的H264码流结构一般是:SEI+SPS+PPS+IDR

6.I帧和IDR帧的区别和联系

        IDR属于I帧,但是I帧不一定是IDR帧。只有IDR帧,才有SPS和PPS。解码器收到IDR帧时,将缓存清空;而收到I帧不会清空缓存。也就是说,对某个IDR帧之后的帧,解码器不会参考这个IDR帧之前的任何帧做解码。对某个I帧之后的帧,解码器可能会参考这个I帧之前的帧做解码。

        


http://www.ppmy.cn/news/1519837.html

相关文章

【原型模式】

原型模式 Prototype Pattern 属于创建型模式是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,调用者不需要知道任何创建细节,不调用构造函数关键点:不通过 new 关键字,而是通过方法去创建对象 原型模…

高职院校人工智能训练师边缘计算实训室建设方案

一、引言 随着人工智能技术的飞速发展,边缘计算在提升数据处理效率、降低延迟、保护数据安全等方面展现出巨大潜力。高职院校作为技能型人才培养的重要基地,建设人工智能训练师边缘计算实训室,旨在培养掌握前沿技术、具备实战能力的复合型人才…

自定义Shell程序(内附源码)

在这篇博客中,我们将深入探讨如何自行编写一个简单的Shell程序,我们的示例程序是一个用C语言编写的名为myshell的小型命令行界面。这个项目不仅是对操作系统如何通过命令行与用户互动的一个实用介绍,同时也展示了环境变量、进程创建和命令解析…

Python 全栈系列265 使用ORM、Kafka、Apscheduler实现任务的并发处理

说明 这次的尝试,从框架来说是比较成功的。但是不太走运的是,有一个小的磁盘回收没有写,结果在我外出旅游的时候磁盘打满,导致任务没有按预期执行完,这点比较遗憾。 这里快速把实现的框架梳理一下,后续可…

差旅游记|绵阳:生活的意义在于体验

哈喽,你好啊,我是雷工! 几年前在微博上有个段子广为流传,说是梁朝伟哪天烦闷了,就去机场,赶上哪班就搭哪班,比如去伦敦广场晒太阳,发呆,喂鸽子,完了再搭最近…

Azure Data Factory 多选选项集不受支持

在用ADF往外部推数据时,会碰到CRM的一种数据类型,多选下拉框,如下图中的 如果我们把多选字段输入源字段中,会得到如下的提示 查询官方文档,则有如下的说法 所以把值往外推就需要变通下,例如使用一个文本字段…

爬虫:爬取MDPI杂志中国作者单位和邮箱

Python爬虫,简单来说,就是使用Python编程语言编写的一种自动化获取网页内容的程序。它们能够模拟人类浏览网页的行为,如访问网页、解析网页内容、甚至填写表单和点击链接等,从而帮助我们从互联网上大量收集和处理数据。Python爬虫…

dart 字符串截取

截取 String str "500001"; String lastThreeDigits str.substring(str.length - 3);在这个例子中,str.length - 3计算的是开始截取的索引位置,它从字符串的倒数第三个字符开始截取,一直到字符串的末尾。因此,lastTh…

Nginx学习(第二天)

一.Nginx高级配置 1.1 Nginx状态页 基于nginx 模块 ngx_http_stub_status_module 实现, 在编译安装nginx的时候需要添加编译参数 --with-http_stub_status_module 否则配置完成之后监测会是提示法错误 注意: 状态页显示的是整个服务器的状态,而非虚拟主机的状态 …

运维问题0001:MM模块-MIGO收货报错“消息号 M7036 对于采购订单********无收货可能”

1、问题解析: 该报错为SAP标准报错类型,针对公司不同配置/业务设计/校验逻辑,导致该问题原因比较多。 常见的问题总结如下: 1)输入的PO信息有问题(例如:PO输入错误/PO删除状态/PO冻结状态/PO已完成收货等…

【Next】3. 开发规范

笔记来源:编程导航 1、约定式路由 Next.js 使用 约定式路由,根据文件夹的结构和名称,自动将对应的 URL 地址映射到页面文件。 常见的几种路由规则如下: 1)基础规则:以 app 目录作为根路径,根…

企微获客链接 中文乱码问题处理

企微获客链接 中文乱码问题处理 问题背景问题处理补充内容 问题背景 为了推广产品,同时更好的服务客户,公司在接入企业微信后,需要用到企微获客链接相关推广操作,那么通过API 接口创建企微获客链接时,出现了中文乱码问…

MYSQL————联合查询

联合多个表进行查询 设计数据时把表进行拆分,为了消除表中字段的依赖关系,比如部分函数依赖,传递依赖,这时会导致一条SQL查出来的数据,对于业务来说是不完整的,我们就可以使用联合查询把关系中的数据全部查…

Java 入门指南:Java 并发编程 —— Condition 灵活管理线程间的同步

Condition Condition 是 Java 并发编程中的一种高级同步工具,它可以协助线程之间进行等待和通信。提供了一种比传统的 wait() 和 notify() 更加灵活的方式来管理线程间的同步。Condition 接口通常与 Lock 接口一起使用,允许更细粒度的控制线程的等待和唤…

DNS服务器的配置(服务名named,端口53)

目录 前言 配置文件 DNS服务器的配置 主配置文件 扩展配置文件 区域配置文件 重启服务 配置防火墙 配置客户端dns 前言 DNS服务器的主要作用是将人类可读的域名转换为机器可读的IP地址,从而方便用户访问互联网资源。 在互联网中,设备需要通过I…

前端性能优化:使用Vue3+TS+Canvas对图片进行压缩后再上传,优化带宽,减小服务器存储成本,减少流量损耗

在上传图片之前,对图片进行压缩。看到这里是不是有点懵,前端怎么压缩图片呢,这不应该是后端做的吗? 但是我在开发的时候接到了这样一个需求,要求对用户上传的图片进行一定的压缩,而且并且尽量还原图片的清…

vue项目中scss文件导出,js文件引入scss文件时为空{}

解决办法一: 将scss文件重命名为 ‘原名.module.scss’ 解决办法二:降低vue脚手架的版本 "vue/cli-plugin-babel": "~4.5.0", "vue/cli-plugin-eslint": "~4.5.0", "vue/cli-service": "~4.5.0…

二十五、go语言的通道

目录 一、收发通信 二、将通道作为参数传递(读、写、读写) 三、select 1、先收到消息的先执行 2、一直没有收到消息退出通道 3、不知道何时退出情况下退出通道 go语言中的goroutine可以看成线程,但是又不能看成和其它语言一样的线程&am…

【Kubernetes知识点问答题】Namespace(命名空间)

目录 1. 什么是 K8s 的 namespace? 2. 系统默认创建了哪几个 namespace? 1. 什么是 K8s 的 namespace? 在 K8s 中, Namespace(命名空间)提供了一种机制,将同一集群中的资源划分为相互隔离的组…

c# net8调用vc写的dll

dll程序(vc,x86) 头文件 extern "C" int __declspec(dllexport) WINAPI add(int a, int b);实现 int WINAPI add(int a, int b) {return a b; }c#/net8 函数声明: [DllImport("dll/Dll1.dll", CallingConvention CallingCo…