初识H.264编码原理

1、H.264的由来,了解音视频封装格式

2、了解MediaCodec,视频编解码,DSP芯片,DSP硬解码流程,简述硬编解与软编解的区别。

3、H264结构:I、P、B、GOP、pts、H264编码和解码时处理B帧的大致过程。

3.2、H264码流结构:NAL层与VCL层的作用、NALU内的起始码 与 NALU头 以及RBSP。

3.3、RBSP中的多个切片,切片内的多个宏块、SPS与PPS。

3.4、YUV与RGB的区别,为什么使用YUV。

3.5、NV21和NV12的区别,在安卓开发时要注意什么。

4、视频开发时遇到的问题及处理方案。

初识H264

什么是视频文件

比如我们常见的.mp4、.RMVB、AVI、FLV等视频格式,都是视频文件。

封装格式与编码格式

  • 封装格式: 我们常见的视频文件格式,如:MP4、RMVB、AVI等,他们都属于封装格式
  • 编码格式: 而H.264、MPEG-4、AAC等编码方式,它们属于编码格式
  • 封装格式里面封装了我们编码后的视频流以及对应的音频流、字幕等。
    封装格式(MP4、AVI)里面 装着的是 音频/视频的编码格式(h.264、aac)

什么是H264

H264的定义

比如我们录制视频,就需要对摄像头采集到的每一帧视频画面进行编码,生成视频文件。

问题:由于视频中存在空间和时间的冗余,比如每个连续的画面,他可能存在很多一样的画面,如果我们每一帧都按照完整的图片内容来保存的话,那么占用的空间内存会特别大,我们应该尽可能的把内存节约起来,提高编码效率;

为了解决这个问题,就需要使用算法来去除这些冗余。H.264则是用来专门去除这些冗余部分所诞生的算法,我们把这个算法称为H.264编码。

H264的应用

像大多数我们看到的视频,如rmvb、avi、mp4、flv封装格式 内部的视频流大都是由h.264进行编码的。当然也存在如mpeg4、vp9这种比较冷门的编码格式。

但编码格式无论是h.264、mpeg4、vp8,它们都是基于宏块的方式进行编码,原理是一样的,只是实现的算法不一致。

主流编码技术的发展史

两大电信联盟

编码标准主要围绕两大电信联盟:国际电信联盟(ITU-T)国际标准化组织(ISO)

这两大电信联盟之间,存在了很多年的视频编码标准不统一:

ITU-T组织先后推出了 h261、h262、h263、h264;而ISO组织则是推出MPEG1、MPEG2、MPEG4。

由于两大电信联盟之间视频编码标准的不统一,自然带来了很多编解码方面的麻烦。

直到2003年,这两大电信联盟共同推出了 H.264/MPEG-4 AVC 编码标准。

国际电联ITU-T和MPEG组织在发布了H.264标准之后,很快就发布公告,为下一代视频编解码标准H.265征集技术方案。为H.265设定的技术性能指标是:压缩效率比H.264提高1倍、且不明显提高编码和解码的计算量。据MPEG组织2009年西安会议的回顾,尚无一个技术提案达到上述指标。

下图是视频编码发展历程,从图中也可以看出以ITU-T为代表的H26x派,和以MPEG为代表的MPEG派,在这些年,直到H264的推出后,才算真正的视频编码标准被统一。

其他编码技术

  • Google:VP8、VP9
  • Microsoft:VC-1
  • 国产自主标准:AVS、AVS+、AVS2

H26X视频编码历史

h.26X系列是由ITU[国际电传视讯联盟]主导的,下面介绍各个版本的发展历史。

  • H.261:主要在老的视频会议和视频电话产品中使用。
  • H.263:主要用在视频会议、视频电话和网络视频中。
  • H.264:
    • H.264,同时也是MPEG-4第十部分,是由ITU-T视频编码专家组(VCEG)和ISO/IEC动态图像专家组(MPEG)联合组成的联合视频组(JVT,Joint Video Team)提出的高度压缩数字视频编解码器标准。这个标准通常被称之为H.264/AVC(或者AVC/H.264或者H.264/MPEG-4 AVC或MPEG-4/H.264 AVC),从而明确的说明它两方面的开发者。
    • H.264是在MPEG-4技术的基础之上建立起来的,其编解码流程主要包括5个部分:帧间和帧内预测(Estimation)、变换(Transform)和反变换、量化(Quantization)和反量化、环路滤波(Loop Filter)、熵编码(Entropy Coding)。
    • H.264标准的主要目标是:与其它现有的视频编码标准相比,在相同的带宽下提供更加优秀的图象质量。通过该标准,在同等图象质量下的压缩效率比以前的标准(MPEG2)提高了2倍左右。
  • H.265:高效率视频编码,是一种视频压缩标准,H.264/MPEG-4的继任者。可支持4K分辨率甚至到超高画质电视,最高分辨率可达到8192×4320(8K分辨率),这是目前发展的趋势。

音视频鼻祖H.261

为什么H261可以被称为视频的鼻祖?因为H261中开创了以下的先河:

块结构的混合编码

H.261是第一种采用"块结构的混合编码"方案的编码标准。

概念:在这之前,我们存储画面可能是将一个图片中的每个像素点的颜色进行记录,这样在高分辨率的画面中,占用的空间太大了。

那么块结构的概念则是:将一帧画面中,像素点颜色相似且连续的区域作为一个块,这么一来,一个画面就变了很多个块构成。然后我们在不影响块的显示效果情况下对每个块进行压缩,通过记录宏块的基础信息(比如渐变色宏块,记录起点终点色值基础信息),之后在解码时由解码器按照基础信息来渲染出一个宏块。之后每个宏块渲染到画面中后,人眼看起来是和原画面差不多的。但是如果放大就会发现,画面其实是由很多渐变色的小块组成的;这就是宏块的概念;这种属于有损编码 ,大多数的视频编码都是有损编码

无损编码

由于视频编码基本是有损编码,但是在电影院播放影片时,是要保证画面质量的,所以对应有损编码的是无损编码

比如电影院的影片,一个2小时的影片可能内存能够达到 2000G。因为影院用到的影片要保证画面质量,所以编码不是传统的视频格式,而是使用的 jpeg2000 格式,且jpeg2000是图片格式。

目的

视频会议、可视电话、等低码率、视频传输

数据格式

由于各国电视制式不统一,无法互通。因此在使用H.261进行编码之前,可能需要将视频源转换成CIF格式,以满足编码H.261的前提要求。

"公共中间格式 "(Common Intermediate Format,CIF)是一种标准化的视频格式,通常用于视频编解码的中间步骤,以确保在不同系统之间的互通性。CIF定义了视频的特定分辨率和帧速率,为视频处理提供了一个统一的基准。

这涉及到确保视频的分辨率和帧速率符合CIF的标准。CIF的标准分辨率为352x288、176x144像素,帧速率可以是25帧/秒(PAL制式)或30帧/秒(NTSC制式)。

转换到CIF格式的目的在于提供一个标准化的中间步骤,以便在不同的系统中进行编解码而不损失信息或造成不兼容性。这种标准化使得在H.261编码和解码的过程中,不同系统之间可以更容易地进行视频数据的交换和互通。

为什么需要对视频进行编码

可不可以将每一帧保存到文件,音频再单独保存呢?

在早期,拍摄视频、放映视频的电影院中,确实是通过保存每一帧的画面数据,音频数据单独存放;来达到保存视频文件的效果。一个胶卷30公斤重,可见保存视频数据的难度。

所以对视频进行编码的目的是为了解决两大难题:

  1. 存储困难:一张DVD或者1G的磁盘只能存储几秒钟未压缩的数字视频。
  2. 传输困难:1M的带宽传输1秒的数字电视视频需要大约4分钟。

归根结底,编码的目的就是压缩,编码等于压缩。

视频中哪些信息可以压缩

那么视频中的哪些信息是能够压缩的呢?

  1. 空间冗余:图像相邻像素之间具有较强的相关性。
  2. 时间冗余:视频序列的相邻图像之间内容相似。
  3. 编码冗余:不同像素值出现的概率不同。
  4. 视觉冗余:人的视觉系统对某些细节不敏感。
  5. 知识冗余:对于某种数据或信息中存在的有序结构或模式,我们可以通过先前获取的知识,无论是通用的先验知识还是与特定领域相关的背景知识,来理解和识别这种结构。这种先验和背景知识有助于我们更好地解释、分析和利用数据中的规律性。

初识编解码

MediaCodec

MediaCodec是安卓提供的用于对音视频进行编解码的类,它通过访问底层的编解码器来实现音视频的功能,是Android Media基础框架的一部分。

  • Android 4.1,API 16:MediaCodec的首个版本。
  • Android 4.3,API 18:扩展了一种通过Surface提供输入的方法(createInputSurface)。
  • Android 5.0,API 21:Mediacodec引入了"异步模式"。

编码与解码

编码就是将视频画面按照对应的算法,编码成指定的格式内容。

解码则是将被编码后的文件,通过解码算法,将原始画面数据还原。

编码对应了文件的压缩,而解码则对应着文件的解压。

硬编解和软编解

  • 硬编解是通过专门的硬件芯片来对视频流或其他数据流进行数据的编解码;硬编解的效率非常之快,但是过于依赖硬件,兼容性会差一些。
  • 软编解则是不借助特地的硬解芯片,而是直接使用CPU来进行编解码;软编解会大大的提高CPU的负担,但是兼容性会更高。
  • MediaCodec可以支持 硬编解码和软编解码(基本是硬编解码),具体取决于底层的编解码器是如何实现。
  • 在编码方面,硬编码是比软编码要强的多的。在解码方面,软解码是可以追上逼近硬解码的。
  • 软解和硬解的解码流程是差不多的,但软解通常是要通过Native层去调用三方库(如 FFmpeg)实现,而硬编解则可以在Java层中调用API实现(比如使用Android提供的API:Mediacodec)。

DSP芯片与视频硬解码过程

移动端硬编解码时靠的是DSP(Digital Signal Processor,数字信号处理器) 芯片,它即不是GPU也不是CPU。

DSP芯片通常是集成在SOC(System-on-Chip,片上系统)中,当然也可以作为独立的芯片使用。

独立的DSP芯片通常设计用于特定的信号处理任务,具有高度优化的硬件和指令集,以提供卓越的性能。这些独立的DSP芯片可以单独使用,或者与其他芯片(例如通用处理器、FPGA等)组合以构建更复杂的系统。

另一方面,某些SOC设计将DSP功能集成到同一芯片上,与其他处理单元(如CPU、GPU等)共享同一片上系统。这种集成可以在某些应用中提供更紧凑、节省功耗和成本的解决方案,同时确保高性能数字信号处理。

DSP芯片

DSP(数字信号处理)是一种广泛应用于处理数字信号的技术,不仅仅局限于音频领域。

虽然DSP最初被广泛应用于音频处理,例如音频滤波、均衡、压缩等,但随着技术的发展,它已经扩展到包括图像处理、通信系统、雷达系统、生物医学信号处理等多个领域。

虽然目前手机的CPU已经足够强大,但智能手机所需要处理的任务也越来越多,如果将视频编解码任务加入到CPU中,那么无疑会大大降低手机的流畅度,而专用芯片的加入可以有效地解决这个问题,DSP就是这样一款专用芯片。DSP芯片或许不如CPU那样广为人知,但它的确在智能手机中扮演着重要的角色,它可以带来更好地语音、音频、图像体验,这绝对会提升手机单项功能的能力,让手机运行速度更快。

视频硬解码的过程

指的是系统 (比如)将视频封装格式 的文件分离成h.264视频流和aac音频流,之后再将H.264的视频数据流交给DSP芯片进行编解码处理(此处是解码),DSP芯片在完成H264数据流解码后,会得到原始画面数据,之后将解码后原始画面数据转交给GPU/CPU进行渲染到屏幕上,这就是视频硬解码的过程。

H.264编码原理

陀螺仪导航原理

陀螺仪导航是一种使用陀螺仪传感器来实现导航和定位的技术。陀螺仪是一种能够测量角速度(物体围绕其自身轴旋转的速度)的传感器,它在导航系统中常被用于检测方向的变化。

陀螺仪导航方式:

  1. 先记录起始位置。
  2. 陀螺仪开始旋转,此时记录x、y、z方向的加速度。
  3. 根据初始位置、陀螺仪方向/加速度不断计算实时位置(无需依赖网络)。

需要注意的是,陀螺仪导航系统可能会面临误差累积的挑战,因此通常会结合其他传感器或使用传感器融合的技术,以提高导航系统的准确性和稳定性。

认识IPB帧

陀螺仪的位置主要围绕 起始位置+偏移量来得到当前实时位置。我们视频流中也是有这种概念。

  • I帧:完整编码的帧叫I帧。它拥有完整的一帧画面数据。
  • P帧:基于前面的关键帧(I帧或P帧),生成的只包含差异部分编码的帧叫P帧。拥有相对于上一帧画面的差异数据。
  • B帧:参考前后的帧编码的帧叫B帧。相对于前后各一帧的画面数据的差异记录。

I帧

I帧的说明

I帧是完整编码的帧,它记录了当前帧的完整画面,解码时只需要本帧数据就可以完成。

因为是一副完整的画面,所以I帧的数据量大小在GOP中往往是最大的。

I帧的特点

  1. 它是一个全帧压缩编码帧,它将全帧图像信息进行JPEG压缩编码及传输。
  2. 解码时仅用I 帧的数据就可重构完整图像。
  3. I帧描述了图像背景和运动主体的详情。
  4. I帧不需要参考其他画面而生成。
  5. I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量)。
  6. I帧不需要考虑运动矢量。
  7. I帧所占数据的信息量比较大。
  8. I帧通常是一个GOP的起始位置。

注意:所以在直播时,由于会存在很多中途加入直播的用户,那么此时用户如果想要得到更好的画面秒开体验,就需要提高编码端I帧的发送频率。否则中途加入的用户会因为得不到I帧而长时间黑屏等待,直到获取到首个I帧才会有正常画面。

P帧

P帧的说明

P是前向预测编码帧。帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别,解码时需要之前缓存的画面叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据)。

P帧的预测与重构

P帧是以 I 帧为参考帧,在 I 帧中找出P帧"某点"的预测值和运动矢量,取预测差值和运动矢量一起传送。在接收端根据运行矢量从 I 帧找出P帧"某点"的预测值并与差值相加以得到P帧"某点"样值,从而可得到完整的P帧。

P帧的特点

  1. P帧是 I 帧后面相隔1~2帧的编码帧。
  2. P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差)
  3. 解码时必须将帧中的预测值与预测误差求和后才能重构完整的P帧图像。
  4. P帧属于前向预测的帧间编码。它只参考前面最靠近它的 I 帧或P帧。
    P帧的预测可以同时基于当前GOP中前面的I帧和之前的P帧,具体的预测过程由编码器决定。
  5. 由于P帧是参考帧,它可能造成解码错误的扩散。
  6. 由于是差值传送,P帧的压缩比较高。

B帧

B帧的说明

B帧是双向预测内插编码帧。B帧是双向差别帧,也就是B帧记录的是本帧与前后帧的差别。

换言之,要解码B帧。不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。

B帧压缩率高,但是解码时CPU会比较累。

B帧的预测与重构

B帧以前面的 I 或P帧 和 后面的P帧为参考帧,"找出"B帧"某点"的预测值和两个运动矢量,并取预测差值和运动矢量传送。接收端根据运动矢量在两个参考帧中"找出(算出)"预测值并与差值求和,得到B帧"某点"样值,从而可得到完整的B帧。

B帧的特点

  1. B帧是由前面的 I 或P帧和后面的P帧进行预测的。
  2. B帧传送的是它与前面的 I 或P帧和后面的P帧之间的预测误差及运动矢量。
  3. B帧是双向预测编码帧。
  4. B帧压缩比最高,因为它只反映并参考帧间运动主体的变化情况,预测比较准确。
  5. B帧不是参考帧,不会造成解码错误的扩散。

那么,如果一条直播流中有B帧;已知直播流是实时的,在收到B帧时,由于B帧还要参考后面P帧,那么此时我们要怎么处理?

答:解码器内是有做缓冲,当我们传入B帧,但没有传入B帧后面的参考帧时,解码器会把B帧先放到缓冲区等到,当收到后面的P帧参考帧后,解码器才会开始解码B帧,把B帧渲染后,再渲染之后P帧。

所以,在解码B帧时,存在了等待耗时;那么在直播流中,我们通常都是不会编码B帧的。因为严重拖延了直播流的实时性。

B帧的一个特性

如下图,码流顺序表示的是这个帧在码流中是第几个被编码输出的,帧数表示这一帧实际的位置。

码流顺序中只有B帧才会错乱,因为在编解码时,B帧除了参考上一个关键帧之外,还需要参考后面的P帧。

因此,在编码时,编码器会先生成B帧,但是B帧此时会被缓存到传输缓冲器里不输出,等下一个P帧(即B帧向后参考的帧)生成且输出后,编码器才会将B帧输出。所以上图中的视频码流分析中可以看到客户端是先收到P帧的(码流顺序11,帧数12),再收到B帧。

帧数序号是根据视频帧的pts来排序的,pts表示当前帧处于视频中对应时间。

而码流顺序,即编码时该帧的编码输出顺序;是根据dts来决定的;dts表示编解码输出时,该帧的时间戳。

同理,解码器在接收B帧时,也是要先接收到B帧的上一个关键帧A,紧接着是B帧的下一个关键帧C,最后才是收到该B帧。

PTS与DTS

PTS(Presentation Time Stamp 显示时间戳)

PTS用于指示视频帧在实际显示时的时间戳。它特别关键,用于多媒体同步, 以及数据流的解复用。例如,一个视频文件,它可能包含了视频流和音频流,那么视频和音频就必须同步播放,这时就需要通过每个视频帧和音频帧的PTS来正确处理播放顺序。

PTS的值通常由编码器在编码过程中生成并添加到视频流中。在接收和解码视频流的过程中,解码器可以利用PTS的值来确定每个帧的正确播放时间。

PTS的计算和处理是在编码,解码,传输等过程中由相应的库或者框架(如FFmpeg,gstreamer等)处理的。你无需手动计算PTS,但是在开发多媒体应用的过程中,你需要理解PTS的基本概念,知道它的存在并可能影响到帧的播放顺序和同步问题。例如在观看网络直播或者点播时,你可能遇到过画面和声音不同步的问题,这就可能是PTS处理不当的结果。

DTS(Decoding Time Stamp 解码时间戳)

DTS也是一种时间戳,用于指示视频帧在解码器中的解码顺序。

在视频编码中,DTS是一项关键的元数据,用于告诉解码器何时应该解码特定的视频帧。

PTS与DTS区别

在H.264视频编码中,DTS(Decoding Time Stamp)和PTS(Presentation Time Stamp)是两个关键的时间戳,用于控制视频的解码和显示顺序。它们之间的区别如下:

  1. DTS(Decoding Time Stamp):
    • 作用: DTS表示解码器在解码过程中应该显示帧的时间戳。
    • 用途: 主要用于解码器,确定视频帧的解码和显示顺序。解码器根据DTS决定何时解码并显示相应的视频帧。
  1. PTS(Presentation Time Stamp):
    • 作用: PTS表示帧在实际显示时的时间戳,即在播放器上显示的时间。
    • 用途: 主要用于播放器,确保视频帧按照正确的顺序显示在屏幕上。PTS决定了帧何时被呈现给用户。

在理想情况下,DTS和PTS是相等的,这表示视频帧按照编码时的顺序被解码和显示。然而,在某些情况下,DTS和PTS可能会不同(如B帧的解码顺序与帧顺序是不一样的),以确保在最终的显示中保持正确的顺序。

总结:播放器中画面显示是以PTS为准(PTS决定当前画面属于第几帧),解码器中解码视频流时则以DTS为准(DTS决定码流的 编解码/输出 顺序)。

GOP(Group of Pictures图像)

除了I/P/B帧之外,还有图像序列组GOP。GOP图像序列可以理解成一个场景,场景的物体都是相似的。

两个I帧之间就是一个图像序列,在一个图像序列中GOP只有一个I帧,且I帧总是位于图像序列中的第一帧。

H264帧类型编码规则

  • 在相邻的几幅图像画面中,一般有差别的像素只有10%以内的像素点,亮度差值变化不超过2%,而色度差值的变化只有1%以内。
  • 那么,对于这么一段变化不大的图像画面,我们可以先编码出一个完整的图像帧A。
  • 随后的B帧就不编码全部图像,只写入与A帧的差别,这样B帧的大小就只有完整帧的十分之一或更小!B帧之后的C帧如果变化不大,那么我们可以继续以参考B的方式去编码C帧,如此循环下去。
  • 这段图像我们称为一个序列:序列就是有相同特点的一段数据。当某个图像与之前的图像变化很大,无法参考前面的帧来生成是,那我们就结束上一个序列,开始下一段序列。
    也就是对这个图像重新生成一个完整帧A1,随后的图像就参考A1生成,只写入与A1的差别内容。

I/P/B-帧类型的编码逻辑

与I帧相似度极高,达到95%以上的,编码成B帧。

相似程度70%以上,95%以下;编码成P帧。

I、P、B帧的编码,H264的如何编码,都不需要我们程序员实现,已经由H264的编码器帮我们做了。

H.264编码传输

视频编码的目的是为了方便传输(文件传输、网络流传输),我们不能把每一帧画面都完整传输过去,因为一帧完整的画面,内容太大了。

我们需要细分画面类型,才能更方便的传输。如果每次都传递一帧完整的画面,那么耗时太长了。

所以我们需要更小的传输单元以保证更好的压缩性、容错性和实时观看性。

因此,H.264作为了视频编码传输的最佳选择。

H.264码流结构

H264编码器中的NAL层和VCL层

H.264的编码传输离不开NAL(Network Abstraction Layer)层VCL(Video Coding Layer)层

它们是视频编码标准H.264/AVC中的两个重要组成部分,它们之间有密切的关系,这种关系有助于实现视频的压缩、传输和解码。

  1. VCL(视频编码层)(Video Coding Layer):
    • 职责: VCL层负责对原始视频信号进行压缩编码,包括帧的编码、量化、变换等操作,以减小数据量,提高压缩效率。
    • 输出: VCL层的输出是经过压缩编码的视频数据,其中包括关键帧(I帧)和预测帧(P帧)等不同类型的帧。
  1. NAL(网络提取层)(Network Abstraction Layer):
    • 职责: NAL层负责将VCL层输出的压缩视频数据进行封装,以适应在网络中的传输或存储。它定义了NALU(Network Abstraction Layer Unit)的格式,将压缩的视频数据打包成适合在网络上传输的单元。
    • 输出: NAL层的输出是封装后得到的NALU( Network Abstraction Layer Unit),每个NALU包含了一部分帧的数据或者一些控制信息。

NAL(Network Abstraction Layer)层是H.264/AVC中的一层抽象层,主要负责将视频编码的数据进行封装和分割。在NAL层中,视频编码的数据被分割成一个个的NAL单元,每个NAL单元都包含了视频数据的相关信息和具体的编码数据。这样设计可以使视频编码的数据更加灵活和可扩展,方便在网络传输中的使用和处理。

VCL(Video Coding Layer)层是H.264/AVC中的视频编码层,主要负责对视频数据进行压缩编码。在VCL层中,视频数据通过预测编码、变换编码和熵编码等技术进行压缩,从而实现对视频数据的高效编码和传输。

总结起来,NAL层主要负责封装和分割视频编码的数据,而VCL层则负责对视频数据进行压缩编码。两者结合起来,可以实现高效的视频编码和传输。

NALU的基础构成

H.264原始码流(又称为裸流),是由一系列的NAL单元(即NALU,网络抽象层单元)( Network Abstraction Layer Unit 构成,每个NAL单元都包含一个RBSP。

NALU是用于在网络中传输和存储视频数据的单元,它包含了起始码前缀、NAL头和负载数据。

每个NALU之间通过起始码前缀Start Code进行分隔,NALU通常由[NALU Header ]、[NALU Payload]两个部分组成:

  1. 起始码前缀(Start Code Prefix):用于标示一个NAL单元的开始;它不属于NAL单元的一部分。
  2. NALU头(NALU Header) :NALU头包含了一些关键的控制信息,例如NALU的类型(I帧、P帧、SPS、PPS等),以及一些额外的参数。
  3. 负载数据(Payload Data) :负载数据处存储的就是RBSP(Raw Byte Sequence Payload),即压缩编码后的原始视频数据。

NAL头是NALU的控制信息,用于标识NALU的类型、开始位置等,而不属于负载数据。

NALU详解之EBSP、RBSP与SODB

起始码前缀(Start Code Prefix)

起始码前缀代表着一帧(一个NAL单元)的开始。

如果NALU对应的Slice为一帧的开始,则用4字节表示,即"00 00 00 01" ;否则用3字节表示,"00 00 01"。

NALU头(NALU Header)

NALU头(NALU Header)是 H.264 标准中 NALU 的一部分,它包含了有关NALU的一些关键信息,用于识别和解析NALU。

在H.264编码中,通常NALU(Network Abstraction Layer Unit)的头部包含一个字节,也就是8位。

如果是扩展的NALU(Extended NALU),还会有额外的字节。

扩展的NALU(Extended NALU)是一种特殊的NALU,用于支持更多的NALU类型和更大的参数空间。基本的NALU头只有一个字节,而扩展的NALU头则包含两个或更多字节。

在扩展的NALU中,第一个字节的结构与基本的NALU头相同,依然包含禁止位、参考位和单元类型。但是单元类型的值会被设为24到31中的一个,这些值在基本的NALU中是保留未用的。然后,在接下来的一个或多个字节中,会包含一个新的NALU类型字段(nal_unit_type_ext),以及可能的其他参数。

这种机制允许H.264编码支持更多的NALU类型,以及每种类型的更多参数。但是,这些扩展的NALU类型在实际中并不常见,大多数实现都只使用了基本的NALU类型。

通常,NALU头包含以下字段:

  1. 禁止位(Forbidden Bit): 1 位。用于指示NALU头是否包含错误的信息,一般应该为0。
  2. NALU优先级(NRI - NALU Reference ID): 2 位。用于指示NALU的优先级,对于非关键帧,其数值表示NALU参考性(重要性)的程度。数值越大表示越重要。
    • NRI值为0: 表示NALU的优先级最低,通常对应于非关键帧的 NALU,如 P 帧。
    • NRI值为1、2: 表示NALU的优先级逐渐增高,对应于关键帧和 SPS、PPS等关键信息的 NALU。
    • NRI值为3: 保留,一般不使用。
  1. NALU类型(Type): 5 位。
    一个NAL单元的头部的第一个字节(又称为header byte)的低5位就是nal_unit_type。
    指示NALU的类型,包括以下几种常见类型:
    • 0:未使用
    • 1:非IDR图像中不采用数据划分的片段,也就是常见的P帧或B帧。
    • 2:非IDR图像中A类数据划分片段
    • 3:非IDR图像中B类数据划分片段
    • 4:非IDR图像中C类数据划分片段
    • 5:IDR图像的片段。5表示是一个关键帧(I帧,IDR帧)
    • 6:补充增强信息单元(SEI)
    • 7:序列参数集(SPS)
    • 8:图像参数集(PPS)
    • 9:分隔符
    • 10:端点参考图像通知
    • 11:端点参考图像序列循环重复数值
    • 12:填充数据
    • 13-23:保留
    • 24-31:未使用
      以上是一些常见的NALU类型,具体的类型值和含义可以在H.264标准文档中找到。

NALU Header解析实例

比如16进制的67,转为二进制后:0110 0111,它的第一位0是禁止位,他的第二~三位是11表示NALU的优先级,剩下的低五位则是NALU的类型为0 0111得到二进制7(序列参数集(SPS))。

RBSP(Raw Byte Sequence Payload)

RBSP 指的是 H.264(和 H.265/HEVC)视频编码标准中的 "Raw Byte Sequence Payload",即原始字节序列负载。

  • 在视频编码中,RBSP 是指未进行任何字节对齐和转义处理的原始字节序列。
  • 它包含了压缩编码后的视频数据,其中包括 I 帧(关键帧)和 P 帧(预测帧)等,是 NALU 的有效负载。
  • RBSP 是经过语法元素解析和分割形成的原始视频数据,不包括 起始码前缀 和 NAL 头。
  • 在传输和存储过程中,RBSP 可能需要进行特定的处理,如字节对齐和转义,以适应特定的传输要求。
  • 总体而言,RBSP 是 H.264 标准中定义的一个重要概念,代表视频编码后的原始数据,它将被封装为 NALU 进行网络传输和存储。

总结

  • 在我们视频文件的H264码流中,它们是由一个个的NAL单元组成。
  • 1个NAL单元可以理解为1帧,当然NAL单元有很多类型,有时候也不一定是画面帧,比如SPS、PPS。
  • NAL单元的组成是由:NAL头,RBSP组成的。
  • 起始码 00 00 00 01 标志着一个新的NAL单元的开始。
  • NAL头通常只占用一个字节,特殊情况除外;NAL头主要用来区分当前NAL单元的重要性和类型。
  • 我们通常会根据NALU类型(Type)来区分 I帧和其他帧,以及SPS、PPS等重要的NAL单元。

切片(Slice)

在 H.264 中,切片是图像帧的基本编码单元,表示视频帧中的一个连续区域。

通常一个帧会被划分为多个切片Slice,每个切片包含了图像帧中的一部分信息,包括运动信息、残差信息等。

一个NALU内的切片数量可以是零个(如果整个图像帧被编码为一个切片),也可以是多个。切片的数量和位置由编码器根据图像内容和编码参数来确定。

切片(Slice)是存储在NAL单元中的RBSP里面的。每个切片(Slice)由两个主要部分组成:切片头、切片数据。

  • 切片头:
    • 切片头包含了切片的一些重要信息,如切片的类型、位置、量化参数等。
    • 切片头的具体格式和内容取决于切片的类型和编码参数。
    • 切片头的信息用于指导解码器正确解析和处理切片数据。
  • 切片数据:
    • 切片内包含了多个宏块,每个宏块都包含了图像帧的一部分信息,如运动信息、残差信息等。

宏块

比如将一张图片划分成若干个小的区域,这些小的区域称之为宏块。

H264默认是使用16X16个像素大小的区域作为一个宏块;H264的宏块支持 4x4 到 16x16,最大支持16x16。

因为我们显示屏显示内容是由一个个小像素点来渲染的,但是我们视频为了压缩画面,所以引入了宏块的概念;即分析图像帧中的画面,然后将画面中连续的、差异性不大的一块区域的像素点,划分为一个宏块。

因为宏块其实也是非常小的,最多只有几百个像素点,所以即使宏块的渲染出来内容很模糊,但是只要有个大致的图形颜色,在宏观上(一副完整的画面帧中),我们是看不出来的,因为宏块太小了。

所以在编码画面时,H264编码器也会将宏块内的画面进行压缩算法,从而节约内存。

SPS与PPS

SPS和PPS ,包含了初始化H.264解码器所需要的信息参数,包括编码所用的profile,level,图像的宽和高,deblock滤波器等。

  • SPS:序列参数集。
  • PPS:图像参数集。

SPS和PPS记录都是视频的相关信息,比如视频的分辨率大小、profile_idc编码等级、各类约束等。

注意:mediacodec会在一开始的时候就将sps和pps给输出出来。sps和pps是成对出现的 且 mediacodec只会编码一次 sps和pps。

SPS、PPS的字节流读取

在H264码流中,都是以"00 00 01"或"00 00 00 01"为起始码的,找到起始码之后,读取起始码之后的第一个字节中的低5位。

如 0X67,二进制为0110 0111,它的低五位是 0 0111,转为10进制,如果它的值等于7(SPS)或等于8(PPS),那么就说明当前NAL单元类型是SPS或PPS。

代码实现:

比如起始码是00 00 00 01,那么我们NALU包头的索引则是4。假设byte[] data中是收到的一条NAL单元。

那么先获取NAL的包头字节,即:data[4]。

得到NAL头字节后,我们要获取字节的低五位,那么即可以考虑使用 & 0001 1111来只保留低五位,其余均会为0。

之后将得到的字节强转为int就能得到10进制的NAL类型。

比如 0x67,二进制是0110 0111。

我们要截取低五位,那么则 0x67 与 0x1F做与运算,

即: 0110 0111

& 0001 1111

得到: 0000 0111 --> 十进制 7。

根据H.264规范,7为我们的SPS类型。

arduino 复制代码
    /** I帧的帧类型 **/
    public static final int NAL_I = 5;
    /** SPS类型 **/
    public static final int NAL_SPS = 7;
    /** PPS类型 **/
    public static final int NAL_PPS = 8;
    //检查帧类型,i帧为5,sps为7,pps为8
    public static int getFrameType(byte[] data) {
        //offset为NALU头的偏移量。
        int offset = 4;
        if (data[2] == 0x01) {
            //区分00 00 00 01 和 00 00 01
            offset = 3;
        }
        return (data[offset] & 0x1F);
    }

为什么视频编码基本上都是采用YUV而不是RGB

YUV 和 RGB 实际上是一种颜色模型(color model),在H.264编码中采用的图像的颜色编码方式是YUV(亮度色度),而不是RGB(三原色)。

这是为什么呢?首先我们要先了解RGB和YUV原理。

RGB与YUV原理

  • RGB原理
    • RGB是从颜色发光的原理来设计的。有红、绿、蓝三盏色灯,当它们的光相互叠合的时候,色彩相混,而亮度等于两者亮度总和,越混合亮度越高,即加法混合。
      RGB颜色空间的优点是对每个像素的红、绿、蓝三个分量进行独立采样,可以精确表示每个像素的颜色信息。
    • R、G、B 分别表示红色、绿色和蓝色通道的亮度。在传输 RGB 图像时,需要分别传输这三个独立的颜色通道的信息,即红色通道、绿色通道和蓝色通道的信息。接收端接收到这三个通道的数值后,将它们组合在一起以还原原始的颜色信息。
    • 常见的几种RGB格式:
      RGB24(RGB_888):是指R、G、B三个分量各占8比特,组成24比特(位)。
      RGB16(RGB_565):即 R 分量占据 5 比特,G 分量占 6 比特,B 分量占 5 比特,总大小为 16 比特(位)。
      RGBA(ARGB_8888):即在原来的 RGB 分量上多一个 A (Alpha)透明度,总大小为 32 比特(位)。【初学者必看】基础RGB多种格式简要介绍_rgb888和rgb32-CSDN博客
  • YUV原理
    • 它是一种将亮度(Y)和色度(U、V)分离的颜色空间。亮度表示图像的亮暗程度,色度表示图像的颜色信息。YUV颜色空间的主要优点是对亮度信息进行高精度采样和编码,对色度信息进行降采样。常见的YUV格式有YUV420、YUV422、YUV444等。
    • YUV主要用于优化彩色视频信号的输出,与RGB视频信号相比,它最大的优点就是只需占用极少的带宽(通道数)(RGB要求三个独立的视频信号同时传输)。其中,Y表示明亮度(也就是灰阶值),而"U"和"V"表示的则是色度。U 表示蓝色通道和亮度之间的差异,V 表示红色通道和亮度之间的差异。色度信息描述了颜色在图像中的分布和强度。如果只有Y灰阶值而没有U、V色阶值,那么画面也能正常显示,只不过是类似黑白电视的画面。
    • 常见的几种YUV格式:
      • YUV420:"420"指的是每四个像素共享一个色度样本。这种格式下的YUV图像,亮度分量(Y)按照每个像素一个样本的方式进行采样,而色度分量(U和V)按照每四个像素共享一个样本的方式进行采样。
      • YUV422:"422"指的是每两个像素共享一个色度样本。这种格式下的YUV图像,亮度分量(Y)按照每个像素一个样本的方式进行采样,而色度分量(U和V)按照每两个像素共享一个样本的方式进行采样。
      • YUV444:这种格式下的YUV图像,亮度分量(Y)和色度分量(U和V)均按照每个像素一个样本的方式进行采样,即每个像素对应一个Y亮度样本、一个U样本和一个V样本。

RGB与YUV在数据传输的不同:

RGB在数据传输时,是R、G、B各占用了一路的独立信号,并且RGB三路信号要同时传输;而YUV的数据结构是不需要同时占用三路独立信号的,YUV三个维度是可以在一个信号通道中传输的。

视频编码使用YUV的原因:

因为视频编码就是对视频实现压缩算法,所以在颜色模型的选择上也是要综合考虑到 压缩性 和 压缩后显示效果的。

  1. 已知RGB是由红、绿、蓝三色相加得到的,那么RGB想要减小空间占用的话,就必须对R、G、B元素的表达进行降采样(比如从RGB_888降到RGB_565),但是RGB的三个维度,对其中任何一个维度降采样,都会影响他们相加之后的 亮度、颜色的精准度,这样的话画面还原度太低,所以对采用RGB方案的图像进行压缩后画面的差异对肉眼来说会比较明显。
  2. 但是YUV的三个维度则是 明亮度(灰阶值Y)、 和 色度(U、V)。
    因为我们人的肉眼对于亮度(Y)是更敏感的,而对于色度(U、V)没那么敏感。那么在YUV颜色模型的压缩中,我们可以保证肉眼敏感的 明亮度Y 是不压缩的,而去压缩肉眼不那么敏感的 色度(U、V)。

综上所述,虽然RGB在图像方面的展现是更加精确清晰的,但是在视频中我们肉眼对于每帧画面的要求实际上并不需要那么高,并且RGB在压缩后的肉眼显示效果可能比YUV要差,以及RGB在画面信号传输时需要占用三路独立信号 ,带宽高了。所以在视频编码中使用YUV是更最优的选择,因为YUV在视频编码中的优点是占用更小的带宽(不像RGB那样要求三个独立的视频信号同时传输),且压缩后的画面在宏观上影响很小(肉眼看不太出,差异不明显)。所以在视频编码中,通常是使用带宽占用低、压缩性更好、画面压缩后显示效果相对更好的YUV颜色模型。

RGB_888与YUV_420在内存上的差异:

如下图假设我们截取了视频帧的一个区域,这个区域由四个像素点组成。

假设我们使用RGB_888色彩模型,那么这四个像素点所占用的字节数为 3*4=12字节。

但如果我们使用YUV_420色彩模型,那么则是每四个像素点共用一组的色阶值(U、V),即占用字节数为:4*1 + 2=6个字节。

由此可见在下列情况下,每四个像素点中,YUV_420 色彩模型占用的字节数 为 RGB_888 色彩模型的 6/12,即一半。可见内存的占用是极大的减低了。

接着推导YUV的内存占用公式,首先按照RGB_888来推导,即 widthheight 3;因为YUV_420模型是RGB_888的一半,所以得到YUV的内存公式: widthheight3/2

GPT:视频编码采用YUV而不是RGB的原因有以下几点:

  1. 人眼对亮度更敏感:YUV颜色空间将亮度(Y)和色度(U、V)分离开来,利用了人眼对亮度变化更敏感的特性。在视频编码中,只对亮度进行高精度的采样和编码,而对色度进行降采样,从而减少了对色度信息的存储和传输开销。
  2. 降低存储和传输开销:YUV颜色空间采用了色度子采样的方式,即对色度分量进行降采样(通常是4:2:0或4:2:2),从而减少了存储和传输所需的数据量。相比之下,RGB颜色空间要求对每个像素的红、绿、蓝三个分量进行独立采样,占用的存储和传输开销更大。
  3. 兼容性和设备支持:大多数视频编码标准和视频处理设备都支持YUV颜色空间。在视频编码和解码过程中,可以直接利用硬件加速来处理YUV数据,提高效率和性能。

在视频编码中,通常会将RGB图像转换为YUV格式进行压缩编码,然后 再解码还原为RGB格式进行显示。这样可以在保持视频质量的同时降低存储和传输开销,并提高视频处理的效率和性能。

注:视频的宽高取决于Y的数量,因为在视频中每个像素点都一定会有一个Y亮度。

YUV420SP_NV21 与 YUV420P_I420(NV12)

我们现在已知YUV420、YUV444、YUV422它们只是表明YUV中三元素的比例不同。

那么现在要讲的是YUV420中,NV21与I420的区别;他们的共同点之处就是:Y与U、V的比例都是4:1:1;不同之处则是Y、U、V在数据上的排列方式。

见下图,图1是I420(也叫NV12)的排列方式,图2是NV21的编码;它们两者之间的YUV比例是一模一样的,都属于YUV420。

但是I420(NV12)的在数据排列上是 一组Y+一组U+一组V。而NV21是 一组Y + VU之间的交替排列。这就是它两的唯一不同。在安卓的视频编码中,我们主要掌握这两种格式就可以了。

安卓摄像头图像采集的差异

1、摄像头拍摄视频的方向兼容

摄像头采集数据可能需要旋转后才能正常显示;摄像头是有一个正方向的,但是手机的空间实在太小了,所以有些厂商为了把摄像头装入手机中,可能会将摄像头旋转后再放置;那么就会导致手机进行拍摄的时候,得到的预览画面是被旋转过的,非正常画面。所以就涉及到对摄像头(如camera1就有这个问题)采集到的画面进行旋转的操作(不同硬件之间要兼容适配),从而使画面显示的方向是正确的。

如何旋转视频数据?

2、网络传输安卓摄像头数据时需要先转NV12格式再传输。

在安卓摄像头采集的数据中,它使用的是NV21排列。但是其他设备IOS、Windows、Linux中,它们采集的视频数据基本是NV12格式,因此在网络传输中的视频格式都是使用的NV12(即I420)。所以,我们安卓开发时需要将摄像头采集到的图像转成NV12后再进行网络传输;不过有的情况下是不需要自己手动去转成NV12的,因为有些框架帮我们做了这个操作,我们没有感知到而已。

注意:先检查第一步,第一步摄像头方向兼容处理完之后,再进行第二步骤的格式转换。

视频开发时时注意的问题

1、编码I帧的时候,需要把sps和pps发过来(最好的策略)

但现实却是:编码器不允许这样做,它只编码一个sps

(所以在编码时,将首次编码时得到的pps和sps缓存下来,然后插入到I帧的数据流前面)

因为编码器只生成一次SPS和PPS,其余I帧前的pps和sps都是APP自己往码流里面插入的之前已缓存好的pps、sps。

2、编码文件大小与什么因素相关:

帧率、宽、高、编码等级、编码方法、宏块大小。

3、如果视频流的sps 和 pps 在中途变了,那么会怎么样?

解码器会一直按照一开始的SPS和PPS进行解码。

所以在直播推流时更换视频大小后,接收端会出现短暂的黑屏;因为SPS和PPS变化后,只有一种解决办法:重新初始化编码器(即重新开始播放)。

4、NV21数据不能直接通过surfaceView播放。

SurfaceView只支持RGB格式的数据进行渲染,因此无法直接通过SurfaceView播放NV21数据。

要在SurfaceView上播放NV21数据,需要进行格式转换。可以使用Android提供的API或者第三方库将NV21数据转换为RGB格式的数据,然后再将转换后的RGB数据渲染到SurfaceView上。常用的转换方式包括使用RenderScript、OpenGL ES等。

5、视频跳转指定帧的三种模式:

①seek到指定帧:很难保证画面同步;因为画面是一个个gop组成的,如果想直接显示非I帧,那么可能出现画面与拖动的时间不同步问题。

②seek到上一个I帧,没问题。

③seek到下一个I帧,没问题。

6、如何优化拖动视频进度条时帧预览 导致的内存耗费?

UI优化:不要实时的读取视频帧;而是将视频中每隔40ms缓存一帧图片到文件里,拖动进度条时,展示对应时间点的图片缓存。

缓存优化:在加载视频时,视频每隔200ms缓存一张图片用于帧预览。

7、音视频同步的解决方案

  • 音频为准
    以音频为准,视频与音频同步,音频只管播放,视频有超前和延后。如果超前 放慢速度,如果延后 减少视频播放等待时间,如果延后很严重,队列积累时间过长,就直接丢弃,进入最新解码帧。
  • 视频为准
    视频画面每次循环不变,音频根据视频来 延迟或等待。
  • 自定义时间为准
    根据开始加载视频时间为0时间,后面的音频帧和视频帧依赖自定义时间进行同步。

Question

1. 为什么视频编码中不选用rgb 而是用yuv

yuv带宽占用低,且做压缩时对肉眼的观感影响更小。

2. I帧 B帧 P帧的区别是什么

I帧:完整的帧

B帧:前后参考帧

P帧:向前参考帧

3. 硬编码与软编码有什么区别

硬编码使用的是专门的硬件。软编码使用的是CPU。

硬编码的速度通常比软编码快,软编码能勉强追平硬编码速度。

4. 什么是GOP序列

以I帧开头,以下一个I帧的上一个帧结尾;这一片段是一个GOP序列。

5. 解码顺序和播放顺序是一样的吗

不一样,比如B帧前后参考帧,他需要先收到向后参考的关键帧后,才能解码得到B帧。但是B帧的播放顺序却是在向后参考帧的前面。

6. 宏块是什么,为什么有宏块?

宏块是由编码器划分出来的视频完整画面的一小部分,通常宏块大小是由4x4 至 16x16个像素点组成。

因为宏块其实非常小,最多只有几百个像素点,所以即使宏块渲染出来内容很模糊,但是只要有个大致的图形颜色,在宏观上(一副完整的画面帧中),我们是看不出来的,因为宏块太小了。这样就有助于对视频帧画面的压缩。

7. 如何从码流片段中解析出完整一帧。

以00 00 00 01起始,到下一个 00 00 00 01结束,这就是一个完整的帧。使用哥伦布编码并参考H.264编码协议来解析视频流,获取当前帧信息。

8. 什么是封装格式

一般视频文件里不光有视频,还有音频,封装格式的作用就是把视频和音频打包起来。

9. 视频为什么需要进行编码,编码的意义是什么(哪些内容何以压缩和编码)

视频的原始画面太大了,为了便于传输,所以需要编码。编码就是压缩。

哪些内容何以压缩和编码?

    1. 空间冗余:图像相邻像素之间具有较强的相关性。
    2. 时间冗余:视频序列的相邻图像之间内容相似。
    3. 编码冗余:不同像素值出现的概率不同。
    4. 视觉冗余:人的视觉系统对某些细节不敏感。
    5. 知识冗余:对于某种数据或信息中存在的有序结构或模式,我们可以通过先前获取的知识,无论是通用的先验知识还是与特定领域相关的背景知识,来理解和识别这种结构。这种先验和背景知识有助于我们更好地解释、分析和利用数据中的规律性。

10. 直播中I帧间隔如何设置,短视频中又如何设置。

直播中为了考虑中途加入的用户,所以I帧的间隔通常会设置的非常短。

短视频中为了减少视频大小,所以通常I帧间隔会设置的更大写。

相关推荐
dvlinker2 天前
【音视频开发】使用支持硬件加速的D3D11绘图遇到的绘图失败与绘图崩溃问题的记录与总结
音视频开发·c/c++·视频播放·d3d11·d3d11绘图模式
音视频牛哥7 天前
Android平台GB28181实时回传流程和技术实现
音视频开发·视频编码·直播
音视频牛哥8 天前
RTMP、RTSP直播播放器的低延迟设计探讨
音视频开发·视频编码·直播
音视频牛哥12 天前
电脑共享同屏的几种方法分享
音视频开发·视频编码·直播
x007xyz2 个月前
前端纯手工绘制音频波形图
前端·音视频开发·canvas
音视频牛哥2 个月前
Android摄像头采集选Camera1还是Camera2?
音视频开发·视频编码·直播
九酒2 个月前
【harmonyOS NEXT 下的前端开发者】WAV音频编码实现
前端·harmonyos·音视频开发
涵小呆2 个月前
AV1技术学习:Constrained Directional Enhancement Filter
视频编码·av1
音视频牛哥2 个月前
结合GB/T28181规范探讨Android平台设备接入模块心跳实现
音视频开发·视频编码·直播