fwrite

fwrite

twitter
github
email

图像与视频

图像编码#

一张图片可以使用一个二维矩阵表示,矩阵中的每一个点被称为像素。每个像素的颜色使用三原色来表示,即红、绿、蓝。

bafkreifxg6vo5wnigcns2fokhhyxu6kg6y5zb4wtu4tmdignqnkyw3jqqy

每个像素可以用不同的数据位数来表示,常用的量化位数有 16 位、24 位、32 位等。

  • 24 位比特模式:每像素 24 位(bits per pixel,bpp)编码的 RGB 值:使用三个 8 位无符号整数(0 到 255)表示红色、绿色和蓝色的强度。
  • 16 位比特模式:分配给每种原色各为 5 比特,其中绿色为 6 比特,因为人眼对绿色分辨的色调更精确。但某些情况下每种原色各占 5 比特,余下的 1 比特不使用。
  • 32 位比特模式:同 24 位比特模式,余下的 8 比特用来表示象素的透明度(Alpha)。

图片还有一个很重要的属性就是分辨率,采用宽 x 高表示。

bafkreiah5ux6zbguyiokny6fgwcdeqkcswsuetjgtdxntwbpee6yeytg5u

在处理图像或视频时另一个属性是长宽比,它描述了图像或像素的宽度和高度之间的比例关系。常见的比例有 4:3、16:9、21:9,通常指显示宽高比(DAR),同样像素也有不同的宽高比,称之为像素长宽比(PAR)。

bafkreiewwvii2dpjlsifsxqn74wlyiuzmi6djeojorkczkftfgsfhmlbsy

YUV 颜色模型#

RGB 诉求于人眼对色彩的感应,YUV 则着重于视觉对于亮度的敏感程度,Y 代表的是亮度(Luminance、Luma),UV 代表的是色度(Chrominance/Chroma)(因此黑白电影可省略 UV,相近于 RGB),分别用 Cr 和 Cb 来表示,因此 YUV 的记录通常以 Y 的格式呈现。

bafkreia2dsmley7477h4uua4kvxbpklg2d57pqszxqyjdwehebsmoyab3u

为节省带宽起见,大多数 YUV 格式平均使用的每像素位数都少于 24 位。主要的抽样(subsample)格式有 YCbCr 4:2:0、YCbCr 4:2:2、YCbCr 4:1:1 和 YCbCr 4:4:4。YUV 的表示法称为 A:B 表示法:

  • 4:4:4 表示完全取样。
  • 4:2:2 表示 2:1 的水平取样,垂直完全采样。
  • 4:2:0 表示 2:1 的水平取样,垂直 2:1 采样。
  • 4:1:1 表示 4:1 的水平取样,垂直完全采样。

RGB 与 YUV 的转换#

# 第一步计算亮度
Y = 0.299R + 0.587G + 0.114B
# 一旦有了亮度,可以分割颜色(色度蓝色和红色):
Cb = 0.564(B - Y)
Cr = 0.713(R - Y)
# 也可以通过使用 YCbCr 进行转换,甚至获得 RGB
R = Y + 1.402Cr
B = Y + 1.772Cb
G = Y - 0.344Cb - 0.714Cr

采样率、码率、帧以及场的概念#

图像则是对模拟信号进行采样量化后获得,而视频则是由一系列的图像组成,采集时图像的分辨率及量化位数越高,所能表达的信息越多,画面则越清晰。视频存在一个采样频率的属性,即单位时间内采样的次数。视频的采样频率也受人眼的限制,通常在每秒 20 ~ 30 帧之间。当采样频率在每秒 10~20 帧时,对于快速运动的图像,人眼可以感觉到不流畅,而采样频率提高到 20~30 帧时,人眼看起来比较流畅了。如果将采样频率在提高,人眼是很难感觉这种差异的,这也是目前电影拍摄时使用 24 帧或者 30 帧采样频率的原因。

显示视频所需要的每秒位数称作为比特率,也叫码率。计算公式为 比特率=宽 高 位深度 每秒帧数* 例如,如果我们不采用任何类型的压缩,每秒 30 帧,每像素 24 位,480x240 分辨率的视频将需要 82,944,000 位 / 秒 或 82.944 Mbps(30x480x240x24)。

当比特率几乎恒定时,称为恒定比特率(CBR),但它也可以变化,然后称为可变比特率(VBR)。下图显示了一个受限的 VBR,在帧为黑色时不会花太多位。

bafkreid3gdtk6unvikp5pei4sdzpm75eghi4ikjyrigb7skimxou7sp77a

视频采样中通过逐行扫描得到一幅完整的图像称之为一帧,通常帧频率为 25 帧(PAL 制)、30 帧每秒(NTSC 制),而通过隔行扫描(奇、偶数行),那么一帧图像就被分成两场,通常场频为 50Hz(PAL 制)、60Hz(NTSC 制)。这是在早起,工程师们提出的一种技术,能够在不消耗额外的带宽的情况下,使得显示器的感知帧率倍增。这种技术称为隔行视频;它基本上在 1 帧中发送一半的屏幕,而在下一帧中发送另一半。

bafkreifpux2p2edxpebxghyrps5cgfiqcpzvnag5cbxc6ghbysd5bw6hpe

H264 简介#

H264 是属于视频的编码层的标准格式,视频编码显然是为了压缩大小。

概念#

  • SODB:数据比特串 -> 最原始的编码数据
  • RBSP:原始字节序列载荷 -> 在 SODB 的后面填加了结尾比特(RBSP trailing bits 一个 bit “1”)若干比特 “0”,以便字节对齐。
  • EBSP:扩展字节序列载荷 -> 在 RBSP 基础上填加了仿校验字节(0X03)它的原因是:在 NALU 加到 Annexb 上时,需要填加每组 NALU 之前的开始码 StartCodePrefix,如果该 NALU 对应的 slice 为一帧的开始则用 4 位字节表示,0x00000001,否则用 3 位字节表示 0x000001,为了使 NALU 主体中不包括与开始码相冲突的,在编码时,每遇到两个字节连续为 0,就插入一个字节的 0x03。解码时将 0x03 去掉,也称为脱壳操作。

H.264 的功能分为两层,视频编码层(VCL)和网络提取层(NAL)

VCL 数据即被压缩编码后的视频数据序列。在 VCL 数据要封装到 NAL 单元中之后,才可以用来传输或存储。

bafkreiaefcxhtams7qptc46er22wucl4iv6kxnkfffpem4z75kk7hhpyq4

H.264 的编码视频序列包括一系列的 NAL 单元,每个 NAL 单元包含一个 RBSP,见表 1。编码片(包括数据分割片 IDR 片)和序列 RBSP 结束符被定义为 VCL NAL 单元,其余为 NAL 单元。典型的 RBSP 单元序列如图 2 所示。

bafkreidubua2emr4jzasebschjflriqryrganel7embopfmydtgkngnouq

每个单元都按独立的 NAL 单元传送。单元的信息头(一个字节)定义了 RBSP 单元的类型,NAL 单元的其余部分为 RBSP 数据。

NAL 单元#

每个 NAL 单元是一个一定语法元素的可变长字节字符串,包括包含一个字节的头信息(用来表示数据类型),以及若干整数字节的负荷数据。一个 NAL 单元可以携带一个编码片、A/B/C 型数据分割或一个序列或图像参数集。

NALU 头由一个字节组成,它的语法如下:

NAL 单元按 RTP 序列号按序传送。其中,T 为负荷数据类型,占 5 bit;R 为重要性指示位,占 2 个 bit;最后的 F 为禁止位,占 1 bit。具体如下:

bafkreickxngl7hldegdehuumtirvpbfu2c7isj3pfh3nlxnuepv52mofye

  1. NALU 类型位可以表示 NALU 的 32 种不同类型特征,类型 1~12 是 H.264 定义的,类型 24~31 是用于 H.264 以外的,RTP 负荷规范使用这其中的一些值来定义包聚合和分裂,其他值为 H.264 保留。
  2. 重要性指示位用于在重构过程中标记一个 NAL 单元的重要性,值越大,越重要。值为 0 表示这个 NAL 单元没有用于预测,因此可被解码器抛弃而不会有错误扩散;值高于 0 表示此 NAL 单元要用于无漂移重构,且值越高,对此 NAL 单元丢失的影响越大。
  3. 禁止位 编码中默认值为 0,当网络识别此单元中存在比特错误时,可将其设为 1,以便接收方丢掉该单元,主要用于适应不同种类的网络环境(比如有线无线相结合的环境)。

264 常见的帧头数据为:

  • 00 00 00 01 67 (SPS)
  • 00 00 00 01 68 (PPS)
  • 00 00 00 01 65 (IDR 帧)
  • 00 00 00 01 61 (P 帧)

上述的 67、68、65、61,还有 41 等,都是该 NALU 的识别级别。

F:禁止为,0 表示正常,1 表示错误,一般都是 0

NRI:重要级别,11 表示非常重要。

TYPE:表示该 NALU 的类型是什么,

见下表,由此可知 7 为序列参数集(SPS),8 为图像参数集(PPS),5 代表 I 帧。1 代表非 I 帧。

{% asset_img h264-header.png h264-header %}

由此可知,61 和 41 其实都是 P 帧(type 值为 1),只是重要级别不一样(它们的 NRI 一个是 11 BIN,一个是 10 BIN)

H264 (NAL 简介与 I 帧判断)#

bafkreia4uo4h67h5jks4gzfzd4v5pzjmynjmdgcoeecnnevsjwee74birq

我们还是接着看最上面图的码流对应的数据来层层分析,以 00 00 00 01 分割之后的下一个字节就是 NALU 类型,将其转为二进制数据后,

解读顺序为从左往右算,如下:
(1)第 1 位禁止位,值为 1 表示语法出错

(2)第 2~3 位为参考级别

(3)第 4~8 为是 nal 单元类型

例如上面 00000001 后有 67、68 以及 65

其中 0x67 的二进制码为:

0110 0111

4-8 为 00111,转为十进制 7,参考第二幅图:7 对应序列参数集 SPS

其中 0x68 的二进制码为:

0110 1000 4-8 为 01000,转为十进制 8,参考第二幅图:8 对应图像参数集 PPS

其中 0x65 的二进制码为:

011 00101

4-8 位为 00101,转为十进制 5,参考第二幅图:5 对应 IDR 图像中的片 (I 帧)

所以判断是否为 I 帧的算法为:

(NALU 类型 & 0001 1111) = 5 即 (NALU 类型 & 31) = 5 比如 0x65 & 31 = 5

RTP 打包发送 H264 之封包详解#

RFC3984 是 H.264 的 baseline 码流在 RTP 方式下传输的规范

H264 的码流结构

bafkreiaes3rw3r4hxrwkq5hhnlmjojg3wy2duroaypfwzagybmv3vcgnxa

单个 NALU#

12 字节的 RTP 头后面的就是音视频数据,比较简单。一个封装单个 NAL 单元包到 RTP 的 NAL 单元流的 RTP 序号必须符合 NAL 单元的解码顺序。对于 NALU 的长度小于 MTU 大小的包,一般采用单一 NAL 单元模式。对于一个原始的 H.264 NALU 单元常由[Start Code] [NALU Header] [NALU Payload]三部分组成,其中 Start Code 用于标示这是一个 NALU 单元的开始,必须是 “00 00 00 01” 或 “00 00 01”, NALU 头仅一个字节,其后都是 NALU 单元内容。
打包时去除 “00 00 01” 或 “00 00 00 01” 的开始码,把其他数据封包的 RTP 包即可。

   0                   1                   2                   3
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |F|NRI|  type   |                                               |
  +-+-+-+-+-+-+-+-+                                               |
  |                                                               |
  |               Bytes 2..n of a Single NAL unit                 |
  |                                                               |
  |                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                               :...OPTIONAL RTP padding        |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

如有一个 H.264 的 NALU 是这样的:
[00 00 00 01 67 42 A0 1E 23 56 0E 2F …]

这是一个序列参数集 NAL 单元。[00 00 00 01] 是四个字节的开始码,67 是 NALU 头,42 开始的数据是 NALU 内容。
封装成 RTP 包将如下:
[RTP Header] [67 42 A0 1E 23 56 0E 2F]

即只要去掉 4 个字节的开始码就可以了。

包聚合#

当 NALU 的长度特别小时,可以把几个 NALU 单元封在一个 RTP 包中。
为了体现 / 应对有线网络和无线网络的 MTU 巨大差异,RTP 协议定义了包聚合策略:

  • STAP-A:聚合的 NALU 时间戳都一样,无 DON(decoding order number);
  • STAP-B:聚合的 NALU 时间戳都一样,有 DON;
  • MTAP16:聚合的 NALU 时间戳不同,时间戳差值用 16 bit 记录;
  • MTAP24:聚合的 NALU 时间戳不同,时间戳差值用 24 bit 记录;
  • 包聚合时,RTP 的时间戳是所有 NALU 时间戳的最小值;
0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI|  Type   |                                               |
+-+-+-+-+-+-+-+-+                                               |
|                                                               |
|             one or more aggregation units                     |
|                                                               |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               :...OPTIONAL RTP padding        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure 3.  RTP payload format for aggregation packets

STAP-A 示例:

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          RTP Header                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|STAP-A NAL HDR |         NALU 1 Size           | NALU 1 HDR    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         NALU 1 Data                           |
:                                                               :
+               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               | NALU 2 Size                   | NALU 2 HDR    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         NALU 2 Data                           |
:                                                               :
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               :...OPTIONAL RTP padding        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Figure 7.  An example of an RTP packet including an STAP-A
containing two single-time aggregation units

FU-A 的分片格式#

数据比较大的 H264 视频包,被 RTP 分片发送。12 字节的 RTP 头后面跟随的就是 FU-A 分片:而当 NALU 的长度超过 MTU 时,就必须对 NALU 单元进行分片封包。也称为 Fragmentation Units (FUs).

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator  |   FU header  |                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
|                                                               |
|                         FU payload                            |
|                                                               |
|                               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                               :...OPTIONAL RTP padding        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 14.  RTP payload format for FU-A

FU indicator 有以下格式:

+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI|  Type   |
+---------------+

FU 指示字节的类型域 Type=28 表示 FU-A。NRI 域的值必须根据分片 NAL 单元的 NRI 域的值设置。

FU header 的格式如下:

+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R|  Type   |
+---------------+

S: 1 bit 当设置成 1 表明这是 NALU 的首个 fragmnet。当跟随的 FU 荷载不是分片 NAL 单元荷载的开始,开始位设为 0。

E: 1 bit 当设置成 1 表明是 NALU 的最后一个 fragment,即,荷载的最后字节也是分片 NAL 单元的最后一个字节。当跟随的 FU 荷载不是分片 NAL 单元的最后分片,结束位设置为 0。

R: 1 bit 保留位必须设置为 0,接收者必须忽略该位。

Type: 5 bits
NAL 单元荷载类型定义见下表

单元类型以及荷载结构总结

.Type   Packet      Type name                       
  ---------------------------------------------------------
  0      undefined                                    -
  1-23   NAL unit    Single NAL unit packet per H.264  
  24     STAP-A     Single-time aggregation packet    
  25     STAP-B     Single-time aggregation packet    
  26     MTAP16    Multi-time aggregation packet     
  27     MTAP24    Multi-time aggregation packet     
  28     FU-A      Fragmentation unit                
  29     FU-B      Fragmentation unit                 
  30-31  undefined                            

拆包和解包

拆包:当编码器在编码时需要将原有一个 NAL 按照 FU-A 进行分片,原有的 NAL 的单元头与分片后的 FU-A 的单元头有如下关系:

原始的 NAL 头的前三位为 FU indicator 的前三位,原始的 NAL 头的后五位为 FU header 的后五位,

FU indicator 与 FU header 的剩余位数根据实际情况决定。

** 解包:** 当接收端收到 FU-A 的分片数据,需要将所有的分片包组合还原成原始的 NAl 包时,FU-A 的单元头与还原后的 NAL 的关系如下:

还原后的 NAL 头的八位是由 FU indicator 的前三位加 FU header 的后五位组成,即:

nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f)

参考资料#

视频技术介绍

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.