AVPixFmtDescriptor结构

FFmpeg 的社群来了,想加入微信社群的朋友请购买《FFmpeg原理》VIP版 电子书,里有更高级的内容与答疑服务。


AVPixFmtDescriptor 是 FFmpeg 里面描述像素格式具体信息的一个结构,这个结构其实非常常用,像素怎么存储,排列的,都描述在这个 AVPixFmtDescriptor 结构里面。

下面来详细讲解一下这个结构体,定义在 pixdesc.h 文件里面。

typedef struct AVPixFmtDescriptor {
    const char *name;
    uint8_t nb_components; 
    uint8_t log2_chroma_w;
    uint8_t log2_chroma_h;
    uint64_t flags;
    AVComponentDescriptor comp[4];
    const char *alias;
} AVPixFmtDescriptor;

AVPixFmtDescriptor 通常是通过 av_pix_fmt_desc_get() 函数获取到的,接受的参数是 枚举 AVPixelFormat

在 pixdesc.c 里面可以看到 AVPixFmtDescriptor 的初始化,如下:


现在来逐个字段解释一下 AVPixFmtDescriptor 这个结构。

1,const char *name,这个很明显就是名称。

2,uint8_t nb_components; 这个字段代表 一个像素由多少个 部分组成,取值范围 1 - 4。像素通常是由 YUV 或者 RGB 组成的,后面可能会有个透明度,所以这个字段通常是 3 或者 4 。一个像素通常由 3 部分,或者 4 部分组成。有些像素格式只有 2 部分,例如 ya16le

3,uint8_t log2_chroma_w;,在水平方向上,亮度(luma)样本数右移多少位 能得到 色度(chroma)样本数,右移之后的值要向上取整

4,uint8_t log2_chroma_h;,在垂直方向上,亮度(luma)样本数右移多少位 能得到 色度(chroma)样本数,右移之后的值要向上取整

log2_chroma_w 跟 log2_chroma_h 其实不太容易理解,在 YUV420 里面,这两个值都是 1,也就是无论在垂直角度,还是水平角度,色度样本数 都是 亮度样本数的 2 分之一。用下面的图来比较容易理解。

X 代表亮度样本,O 代表 色度样本,无论从垂直角度还是水平角度,都是 2 分之一。

如果是 YUV422 格式,那 log2_chroma_w 就是 1,而 log2_chroma_h 是 0 。这两个值不用特别纠结,简单理解一下就行。

5,uint64_t flags;,像素标记组合,像素标记有 9 种,如下:

  • AV_PIX_FMT_FLAG_BE,代表像素是不是大端存储。
  • AV_PIX_FMT_FLAG_PAL,代表在 data[1] 里面是否有调色板。为什么需要调色板请看《RGB色彩空间》
  • AV_PIX_FMT_FLAG_BITSTREAM,比特流格式,实际上就是 packetd 格式,packetd 就是比特流, 可以简单理解为 YUV 在一个数组里面,不分开多个数组存放。比特流就是连续的数据,所以不能用多个数组切开。
  • AV_PIX_FMT_FLAG_HWACCEL,硬件加速用的标记。
  • AV_PIX_FMT_FLAG_PLANAR,平面格式,可以简单理解为 YUV 分开 3 个数组存放。
  • AV_PIX_FMT_FLAG_RGB,像素格式是 RGB 类型的。
  • AV_PIX_FMT_FLAG_ALPHA,像素里面有透明度。
  • AV_PIX_FMT_FLAG_BAYER,不清楚,直译 好像是 "贝尔模板"
  • AV_PIX_FMT_FLAG_FLOAT,像素值是浮点型。

扩展知识:可以看到 AVPixFmtDescriptor 的 flags 字段才是真正决定 像素格式 是 packetd 还是 planner 格式的。虽然命名习惯是 后面带 p 的是 planner,不带 p 的是 packetd。例如 yuv420p 的 flags 就是 AV_PIX_FMT_FLAG_PLANAR,如下:


6,AVComponentDescriptor comp[4];,这个是 AVPixFmtDescriptor 里面非常重要的一个字段,定义如下:

typedef struct AVComponentDescriptor {
    int plane;
    int step;
    int offset;
    int shift;
    int depth;
} AVComponentDescriptor;

我们需要通过一个实际的例子来理解 comp[4],就以 AV_PIX_FMT_YUV420P 跟 AV_PIX_FMT_YUYV422 为例,如下:

首先分析 AVComponentDescriptor 里面的 plane 字段。 plane 翻译成中文是 平面的 意思,你可以理解为 行,FFmpeg 里面有两个英文术语,plane 跟 components,这两个术语是不一样的,components 可以翻译为分量

例如 AV_PIX_FMT_YUV422 这个格式有 YUV 3 个分量,但是他只有一个 plane,因为他是一个 packetd 的格式,从上图可以看到 comp 里面的 plane 的值全是0 ,代表 3 个分量存储在同一行里面。

而还有一些格式,例如 AV_PIX_FMT_P010LE 也有 YUV 3 个分量,但是只有 2 个 planne,他的 Y 是单独一行,但是 UV 是混在同一行里面的。如下:

上图中,UV 的 plane 都是 1,所以他们是存储在一行里面的。



接着分析 AVComponentDescriptor 的 step 字段,step 代表步长,表示水平方向连续的两个的 Y 间隔多少,提醒,我这里的 Y 是泛指分量。以 AV_PIX_FMT_YUV420P 为例,如下:,step 的值都是 1,也就是相隔一个字节,因为 Y 占 8位,而且是 plane 格式。

//AV_PIX_FMT_YUV420P 格式,相隔一个字节
YYYYYYY
UU
VV

而对于 AV_PIX_FMT_YUYV422 ,step 就不是 1 了,而是 2,4,4,如下:

这个 2,4,4 是怎么算出来的呢?因为 AV_PIX_FMT_YUYV422 是 packetd 的格式,他的 YUV 是混合在一个 plane 里面的,也就是混在一行里面,如下:

//AV_PIX_FMT_YUYV422
Y0 Cb Y1 Cr Y3 Cb Y4 Cr Y5 Cb Y6 Cr

读者可以自己数一下, Y 与 Y 之间是不是隔了 2个字节,而 Cb 与 Cb 之间是不是隔了 4 个字节,Cr 与 Cr 之间是不是隔了 4 个字节。

这就是 step 这个字段的意思。

注意,如果 是 AV_PIX_FMT_FLAG_BITSTREAM ,step 的单位就是 bit。要不 单位是 byte。


接着分析 AVComponentDescriptor 的 offset 字段,offset 代表偏移。表示在当前 plane 中,当前 component (分量)的第一个样本之前有多少个字节的数据。

以 AV_PIX_FMT_YUV420P 为例,因为他是 一个 分量就占了 一个 plane,所以他的 offset 都是 0 ,开头就是第一个样本。如下:

如果是 AV_PIX_FMT_YUYV422 格式,offset 就分别是 0 1 3,如下:

这个 0 1 3 又是怎么算出来的呢?还是用 下面的例子来举例:

//AV_PIX_FMT_YUYV422
Y0 Cb Y1 Cr Y3 Cb Y4 Cr Y5 Cb Y6 Cr

开头第一个就是 Y,所以 是 第一个 offset 是 0,Cb 在第二个字节,所以他的 offset 是 1,而 Cr 在第 四 个字节,所以他的 offset 是 3。


接着分析 AVComponentDescriptor 的 shift 字段,这是一个右移位数。表示将对应内存单元的值右移多少位可以得到实际值。我们看一下他源代码的英文注释。

/**
* Number of least significant bits that must be shifted away
* to get the value.
*/
int shift;

least significant bits 可以翻译为 最低有效位。shift 字段就是告诉你,值的最低有效位在哪里?可能读者会觉得有点废话,对于小端存储来说,最低有效位就是第一位,这还用说嘛。

确实是的,在一个值占满内存的时候,确实是第一位就是最低有效位。所以 AV_PIX_FMT_YUYV420p ,AV_PIX_FMT_YUYV422 的 shift 都是 0 ,因为他们的 Y 位深是 8位,对齐内存之后也是 8 位。

但就是有些 像素格式,例如 AV_PIX_FMT_P010LE 的一个 Y 样本是占 10 位,对齐内存之后,就变成 16 位,实际上只用了 10位,这10位在 16 的哪个个位置,就由 shift 来确定。

可以看到 AV_PIX_FMT_P010LE 格式下,真正的值要 右值 6 位才能得到,shift 全是 6。


接着分析 AVComponentDescriptor 的 depth 字段,这是位深,实际上,通俗来说,就是用多少位 来表示一个 Y,或者 一个 U,或者一个 V。

AVComponentDescriptor 最后 3 个字段不解释,比较少用。


这里做个小总结,一个像素格式的各个 分量 YUV 的存储位置,是由 flags, plane, offsetstep,等等好多个字段来配合工作才能确认下来的。


参考文章:

1,FFmpeg libswscale源码分析3-scale滤镜源码分析

2,色彩空间与像素格式

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Loken2020

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值