Gstreamer基础讲解
Gstreamer讲解
基础
背景
从历史的角度来看,Linux在多媒体方面已经远远落后于其他的操作系统。Microsoft's Windows 和Apple's MacOS它们对多媒体设备、多媒体创作、播放和实时处理等方面已经有了很好的支持。另一方面,Linux对多媒体应用的综合贡献比较少,这也使得Linux很难在专业级别的软件上与MS Windows和MacOS去竞争。GStreamer正是为解决Linux多媒体方面当前问题而设计的。
GStreamer 是一个非常强大而且通用的流媒体应用程序框架。GStreamer并不受限于音频和视频处理, 它能够处理任意类型的数据流。主要的优点在于: 它的可插入组件能够很方便的接入到任意的流水线(pipeline)当中。这个优点使得利用GStreamer编写一个万能的可编辑音视频应用程序成为可能。
GStreamer框架是基于插件的。所有的插件都能够被链接到任意的已经定义了的数据流水线中。
官方网站:https://gstreamer.freedesktop.org/
小结
Gstreamer的优点
1.结构清晰且威力强大
GStreamer提供一套清晰的接口,无论是构建媒体管道的应用程序员还是插件程序员,均可以方便的使用这些API。
2.面向对象的编程思想
GStreamer是依附于GLib 2.0对象模型的,采用了信号与对象属性的机制。
3.灵活的可扩展性能
所有的GStreamer对象都可以采用GObject继承的方法进行扩展
所有的插件都可以被动态装载,可以独立的扩展或升级。
4.核心库与插件(core/plugins)分离
所有的媒体处理功能都是由插件从外部提供给内核的,并告诉内核如何去处理特定的媒体类型。
元件(Element)
元件(Element)是GStreamer中最重要的概念。
可以通过创建一系列的元件,并把它们连接起来,从而让数据流在这个被连接的各个元件之间传输。
可以将若干个元件连接在一起,从而创建一个流水线(pipeline)来完成一个特殊的任务,例如,媒体播放或者录音。
对程序员来说, GStreamer中最重要的一个概念就是GstElement对象。元件是构建一个媒体流水线的基本块。每一个元件都对应一个GstElement。任何一个解码器编码器、分离器、视频/音频输出部件实际上都是一个GstElement对象。
下图为一个流水线的实例,实现 文件->音视频解码->分别播放 的功能
源元件(source element)
源元件为管道产生数据,比如从磁盘或者声卡读取数据。下图是形象化的源元件,我们总是将源衬垫(source pad)画在元件的右端。
源元件不接收数据,仅产生数据。你可从上图中明白这一点,因为上图仅有一个源衬垫(右端 )。
过滤/类过滤元件(filter/filter-like element)
过滤器(Filters)以及类过滤元件(Filter-like elements)都同时拥有输入和输出衬垫。他们对从输入衬垫得到的数据进行操作,然后将数据提供给输出衬垫。音量元件(filter) 、视频转换器(convertor) 、Ogg分流器或者Vorbis解码器都是这种类型的元件。
类过滤元件可以拥有任意个的源衬垫或者接收衬垫。像解码器只有一个源衬垫及一个接收衬垫。而视频分流器可能有一个接收衬垫以及多个源衬垫,每个接收衬垫对应一种元数据流。
接收元件(sink element)
接收元件是媒体管道的末端,它接收数据但不产生任何数据。写磁盘、利用声卡播放声音以及视频输出等都是由接收元件实现的。下图显示了接收元件。
将元件链接(link)起来
通过将一个源元件,零个或多个类过滤元件,和一个接收元件链接在一起,你可以建立起一条媒体管道。数据将在这些元件间流过。这是 GStreamer中处理媒体的基本概念。
衬垫(Pads)
衬垫(Pads)在GStreamer中被用于多个元件的链接,从而让数据流能在这样的链接中流动。衬垫是元件对外的接口,可以被看作是一个元件的插座或者端口,元件之间的链接就是依靠着衬垫。数据流从一个元件的源衬垫(source pad)到另一个元件的接收衬垫(sink pad)。衬垫的功能(capabilities)决定了一个元件所能处理的媒体类型。
衬垫有处理特殊数据的能力:一个衬垫能够限制数据流类型的通过。链接成功的条件是:只有在两个衬垫允许通过的数据类型一致的时候才被建立。这个被称为协商(negotiation)
pad的信息,通过gst-inspect-1.0即可查看
Pad Templates:
SINK template: 'sink' ------>sink pad:数据流入
Availability: Always ------>pad时效性:永久型
Capabilities: ------>pad支持的caps
video/quicktime
video/mj2
audio/x-m4a
application/x-3gp
SRC template: 'video_%u' ------>src pad:数据流出
Availability: Sometimes ------>pad时效性:随机型
Capabilities:
ANY
SRC template: 'audio_%u'
Availability: Sometimes
Capabilities:
ANY
SRC template: 'subtitle_%u'
Availability: Sometimes
Capabilities:
ANY
从上面可以看到,每个pad,都会有以下属性:padname、direction、presence、caps。
-
padname:pad名称
-
direction:pad的输入输出方向,有src和sink两种
-
presence:pad的时效性,有永久型GST_PAD_ALWAYS、随机型GST_PAD_SOMETIMES、请求型GST_PAD_REQUEST,请求型的仅在
gst_element_request_pad()
调用,随机型的则是会根据不同的输入数据使用不同的pad。三种时效性的意义顾名思义: 永久型的衬垫一直会存在,随机型的衬垫只在某种特定的条件下才存在(会随机消失的衬垫也属于随机型),请求型的衬垫只在应用程序明确发出请求时才出现。 -
caps:pad支持的功能
参考资料:
https://blog.csdn.net/houxiaoni01/article/details/98509594
Gstreamer的面向对象
Gstreamer使用C语言来模拟面向对象的实现,主要基于glib库里面的GObject库的数据类型来实现,GObject是一个程序库,它可以帮助我们使用C语言编写面向对象的程序。
很多人被灌输了这样一种概念:要写面向对象程序,那么就需要学习一种面向对象编程语言,例如C++、Java、C# 等等,而 C 语言是用来编写结构化程序的。事实上,面向对象只是一种编程思想,不是一种编程语言。换句话说,面向对象是一种游戏规则,它不是游戏。GObject 告诉我们,使用 C 语言编写程序时,可以运用面向对象这种编程思想。
在 GObject 世界里,类是两个结构体的组合,一个是实例结构体,另一个是类结构体。例如, MyObject 是实例结构体,MyObjectClass 是类结构体,它们合起来便可以称为 MyObject类。
#include <glib-object.h>
//实例结构体
typedef struct _MyObject{
GObject parent_instance;
} MyObject;
//类结构体
typedef struct _MyObjectClass {
GObjectClass parent_class;
} MyObjectClass;
//让GObjectx系统知道你定义了这个类
G_DEFINE_TYPE(MyObject, my_object, G_TYPE_OBJECT);
在GObject中一个对象的产生遵循如下原则:
如果产生的是该类的第一个实例,那么先分配Class结构体,再分配针对该实例的结构体。否则直接分配针对该实例的结构。也就是说在Class结构体中所有的内容,是通过该类生成的实例所公有的。而实例化每个对象时,为其单独分配专门的实例用结构体。
也许你会注意到,MyObject类的实例结构体的第一个成员是 GObject 结构体,MyObject类的类结构体的第一个成员是 GObjectClass 结构体。其实,GObject 结构体与 GObjectClass 结构体分别是 GObject类的实例结构体与类结构体,当它们分别作为 MyObject类的实例结构体与类结构体的第一个成员时,这意味着 MyObject类继承自 GObject类。
每个类必须定义为两个结构体:它的类结构体和它的实例结构体。所有的类结构体的第一个成员必须是一个GTypeClass结构,所有的实例结构体的第一个成员必须是GTypeInstance结构。
Gstreamer的多线程
GStreamer 是一个支持多线程的框架,而且是绝对安全的线程。
Gstreamer的多线程由元件queue来实现,queue前后的两部分被分成两个线程执行。
实用工具
gst-inspect 用于查看一个插件的信息,在gstreamer1.0版本下,为gst-inspect-1.0
gst-launch 用于启动一个流水线,在gstreamer1.0版本下,为gst-launch-1.0
gst-launch主要用于临时启用或调试一个流水线,如果一个已经定型的流水线,需要使用gstreamer提供的C函数来形成一个流水线。
为了使得我们自己的插件能够成功被gstreamer检测到,gstreamer支持添加GST_PLUGIN_PATH环境变量来增加插件的搜索路径。
在gst-launch启动前,会先扫描需要使用的各插件对应的so库,当扫描到了对应的库,但是无法载入成功时,则会把插件加入黑名单,如果需要查看黑名单,只需要gst-inspect-1.0 -b即可。
损坏的插件在第一次扫描到时会报出加入黑名单的原因,后续再扫描时将不会报出原因,如果需要查看加入黑名单的原因,需要删除相应的缓存再执行。
rm ~/.cache/gstreamer-1.0/registry.x86_64.bin
gst-inspect-1.0 -b
gstreamer调试等级,通过设置环境变量GST_PLUGIN来设置gstreamer运行时的调试等级,不同等级会打印出不同等级的信息。
总共有六个等级[0,5]
0: 什么都不打印
1: 打印GST_ERROR ()的信息
2: 打印GST_ERROR () GST_WARNING () 的信息
3: 打印GST_ERROR () GST_WARNING () GST_INFO () 的信息
4: 打印GST_ERROR () GST_WARNING () GST_INFO () GST_DEBUG () 的信息
5: 打印GST_ERROR () GST_WARNING () GST_INFO () GST_DEBUG () GST_LOG () 的信息
Gstreamer常用插件介绍
Gstreamer有一些常用的用于搭建流水线的插件,这里做一个介绍。
v4l2src(Video for Linux 2 source)
Video for Linux 2是内核提供给应用程序访问音、视频驱动的统一接口,v4l2src则是其提供给gstreamer的一个插件element,它属于插件video4linux2,动态链接库文件名为libgstvideo4linux2.so。
v4l2src属性里,常用的有
device:用于指定Linux 设备文件,例如/dev/video0
num-buffers: 用于指定从设备中读入多少帧。
v4l2src可以从外部指定caps类型。
由gst-inspect-1.0 v4l2src可发现
v4l2src实例:
gst-launch-1.0 v4l2src devce=/dev/video0 num-buffers=60 ! video/x-raw,width=1280,height=720 ! fakesink
其中fakesink是一个万能结束元件,用于在调试中结束一条流水线。
官方文档:
https://gstreamer.freedesktop.org/documentation/video4linux2/v4l2src.html?gi-language=c
内容与直接执行gst-inspect-1.0 v4l2src差不多
filesrc
filesrc是gstreamer的核心插件之一,用于从文件系统中读取某一文件。
常用属性:
location: 文件位置
num-buffers: 用于指定从设备中读入多少帧。
filesink
用于存放视频流至文件中
常用属性:
location: 文件位置
qtdemux(QuickTime Demuxer)
用于将文件中的音频和视频分开,一般读取文件后应接该元件,不过有的视频文件不需要,这个与文件本身有关。
实例:gst-launch-1.0 filesrc location=flower_groundtruth.mp4 ! qtdemux ! h264parse ! omxh264dec ! omxh264enc !filesink location=output.mp4
功能:读文件->提取视频->解析h264视频流->h264解码->h264编码->写入文件
videotestsrc
gstreamer自带的测试视频源
常用属性:
pattern:测试视频显示图案
实例:gst-launch-1.0 videotestsrc pattern=2 ! ximagesink
其中ximagesink是指窗口显示
fakesrc 与fakesink
gstreamer自带的万能src和sink
videocrop
裁剪视频,与v4l2src一样在外部指定caps
常用属性:
videoscale
改变视频尺寸,与v4l2src一样在外部指定caps
常用属性:
method:插值方法
n-threads:线程数
videoconvert
改变视频格式,通常用于两个插件格式不匹配时,作为中间插件协调两个插件的格式,与v4l2src一样在外部指定caps。
kmssink
用于将视频发送至HDMI输出。
常用属性:
sync: 同步,当为True时,当处理速度过慢时,会采用丢弃一些帧的策略来处理最新帧,当为false时,不会放弃任何帧,内部有buffer存放还没处理完的帧。
bus-id: 填写HDMI输出所对应的设备。例如a0007000.v_mix,具体信息查看需要在dmesg里查看。
jpegdec
jpeg解码,当使用USB摄像头时,送入的是JPEG图像,此时需要进行解码才能正常处理。
其他实用插件可参考gstreamer官方文档。
gstreamer工程记录
如果想把自定义的硬件集成在gstreamer框架下,需要编写gstreamer插件。
关于Gstreamer的x86环境与aarch64交叉编译环境的搭建
对于x86的Ubuntu环境
apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio
对于aarch64的交叉编译环境
首先按照交叉编译器工具链,这个除了gcc之外还包括了其他必要的工具
sudo apt-get install gcc-aarch64-linux-gnu
其次,准备好必要的依赖库。库依赖关系:
如果想准备gst-plugins-base和gstreamer core的交叉编译环境,则需要按照依赖关系准备好前述编译环境。
注意事项:交叉编译环境的版本应与开发板的gstreamer版本保持一致,这是为了保证头文件和连接的依赖库保持一致。特别地,gstreamer 1.12之前和1.14以后的版本不能通用,两个版本的插件也不能通用。
-
安装orc 0.4.27支持库
如果安装了gtk-doc-tools和libgtk2.0-doc后提示gtkdoc-mktmpl: command not found,将./autogen.sh --prefix=/usr改为下面的命令
./configure --prefix=/home/rongyitong/aarch64 --host=aarch64-linux-gnu
make
sudo make install
- zlib 1.2.11交叉编译
export CC=arm-linux-gnu-gcc #它的configure不支持用CC变量来指定交叉工具链
./configure --prefix=/home/rongyitong/aarch64 #最后生成的库、头文件和man文件都在该目录下
make && make install
-
libffi 3.4.2交叉编译
export CC=aarch64-linux-gnu-gcc ./configure --prefix=/home/rongyitong/aarch64 --host=aarch64-linux-gnu make && sudo make install
-
glib 2.45.3 交叉编译
glib根目录下新建glib.cache,里面写入
glib_cv_long_long_format=ll
glib_cv_stack_grows=no
glib_cv_have_strlcpy=no
glib_cv_have_qsort_r=yes
glib_cv_va_val_copy=yes
glib_cv_uscore=no
glib_cv_rtldglobal_broken=no
ac_cv_func_posix_getpwuid_r=yes
ac_cv_func_posix_getgrgid_r=yes
./autogen.sh --prefix=/home/rongyitong/aarch64 --host=aarch64-linux-gnu CC=aarch64-linux-gnu-gcc LIBFFI_CFLAGS="-I/home/rongyitong/aarch64/lib/libffi-3.0.13/include" LIBFFI_LIBS="-L/home/rongyitong/aarch64/lib -lffi" -cache-file=glib.cache --disable-selinux --disable-xattr --disable-libelf ZLIB_CFLAGS="-I/home/rongyitong/aarch64/include" ZLIB_LIBS="-lz -L/home/rongyitong/aarch64/lib"
如果遇到
这是编译器版本问题,在 glib/gdate.c前面加上#pragma GCC diagnostic ignored "-Wformat-nonliteral"
-
gstreamer 1.12交叉编译(因为开发板是1.12版本,因此需要保持一致)
注意在之前添加环境变量PKG_CONFIG_PATH,用于./configure里用pkg-config检测glib是否存在,如果x86版本的glib干扰,可以将/usr/local/lib临时改名
./configure --prefix=/home/rongyitong/aarch64 --host=aarch64-linux-gnu GLIB_LIBS=`-lglib-2.0 -L/home/rongyitong/aarch64/lib` GLIB_CFLAGS=`-I /home/rongyitong/aarch64/include/glib-2.0 -I /home/rongyitong/aarch64/lib/glib-2.0/include` GIO_LIBS=`-lgio-2.0 -lgobject-2.0 -lglib-2.0 -L/home/rongyitong/aarch64/lib` GIO_CFLAGS=`-pthread -I/home/rongyitong/aarch64/include/glib-2.0 -I/home/rongyitong/aarch64/lib/glib-2.0/include` LIBS=`-lzlib -L=/home/rongyitong/aarch64/lib`
- 编译gst-plugin-base库
./configure --prefix=/home/rongyitong/aarch64 --host=aarch64-linux-gnu --disable-ogg --disable-vorbis
这里已经配好了pkg-config路径,如果没有配好,需要像gstreamer一样配置好环境变量
关于动态链接库(shared object library)的补充知识
gstreamer插件库是以动态链接库的形式出现的,因此了解动态链接库的知识是非常必要的。
软件库一般分为静态链接库与动态链接库,其中静态链接库扩展名为.a(linux)或.lib(windows),动态链接库扩展名为.so(Linux)或.dll(Windows)。
动态链接的基本思想是把程序按照模块拆分成各个相对独立的部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有的程序模块都链接成一个单独的可执行文件。
Linux下的动态链接库通常为libxxx.so格式,在gcc中链接需要的动态库,只需简写为-lxxx。
静态链接库会在链接过程合并至目标程序中,而动态链接库只会在目标程序运行时需要这个函数时才会去对应的so文件中寻找该函数,因此动态链接库经常出现依赖问题。
动态链接库的依赖问题通常有两种:
-
找不到对应的.so文件,或对应的.so文件格式与目标程序不匹配。此错误通常在链接时就会报错。
-
在对应的.so文件中找不到需要的函数。此错误在可以正常链接,但是在目标程序执行时才会报错。
错误类型通常为符号未定义问题,例如undefined symbol: pthread_create动态和静态链接库中,函数和变量统称为符号(symbol),函数名或变量名称为符号名(Symbol Name),每个库中都有一个符号表(symbol table)
动态链接库的实用工具:
readelf
常见用法
readelf -h libxxx.so
选项 -h(elf header),显示elf文件开始的文件头信息。
readelf -s libxxx.so
选项 -s, 显示符号表段中的项,(如果Name显示不全,可以再加个-W参数,表示完整显示)
Ndx表示该符号所在的段,UND表示未定义,表示在该文件里没有定义该符号,只引用了该符号,符号定义在其他文件里。
ldd(list dynamic dependencies)工具
用于查看一个库或可执行文件所依赖的其他库。
有时候使用ldd工具,提示
这种情况可能是由于该文件的架构和执行命令的平台不匹配,比如aarch64平台在x86平台运行。解决方法可以使用aarch64版本的ldd工具,并移植到aarch64平台进行。
ldd工具本质上只是个shell脚本,因此修改代码即可。
1.注意保证#!/bin/bash与平台解释器一致;
2.修改变量RTLDLIST值。将其修改为aarch64平台下的链接动态库(ld-linux-xx.so),多数位于/lib目录下;
从模板开始编写一个插件
git clone https://gitlab.freedesktop.org/gstreamer/gst-template.git
进入git目录,需要把branch切换到1.18版本,master版本的模板有一些问题。利用make_element工具替换名字后,
以videocrop插件为例,基于实际插件重构
插件继承关系
GObject
╰──GInitiallyUnowned
╰──GstObject
╰──GstElement
╰──GstBaseTransform
╰──GstVideoFilter
╰──videocrop
插件重构方式,gstvideofilter.c与gstbasetransform.c分别集成至了libgstvideo.so和libgstreamer-1.0.so中,如无必要,不要修改两个的源码,直接链接相应的动态库即可。如果直接修改源码,则两个c文件会依赖一系列头文件及其他依赖库,其中有些头文件还是在编译gstreamer核心库时产生的,这个头文件环境相当复杂,因此不建议采用这种方法。
关于图像在内存中的存放方式
当视频图像存储在内存时,图像的每一行末尾也许包含一些扩展的内容,这些扩展的内容只影响图像如何存储在内存中,但是不影响图像如何显示出来;
Stride 就是这些扩展内容的名称,Stride 也被称作 Pitch,如果图像的每一行像素末尾拥有扩展内容,Stride 的值一定大于图像的宽度值,就像下图所示:
两个缓冲区包含同样大小(宽度和高度)的视频帧,却不一定拥有同样的 Stride 值,如果你处理一个视频帧,你必须在计算的时候把 Stride 考虑进去;
另外,一张图像在内存中有两种不同的存储序列(arranged),对于一个从上而下存储(Top-Down) 的图像,最顶行的像素保存在内存中最开头的部分,对于一张从下而上存储(Bottom-Up)的图像,最后一行的像素保存在内存中最开头的部分,下面图示展示了这两种情况:
YUV 图像永远都是从上而下表示的,RGB 图像保存在系统内存时通常是从下而上;
关于YUV的补充知识
YUV(YCrCb)是指将亮度参量Y和色度参量U/V分开表示的像素格式,主要用于优化彩色视频信号的传输。
由于我们眼睛的视网膜杆细胞多于视网膜的锥细胞,而视网膜的杆细胞是识别亮度的,锥细胞是识别色度的,所以我们的眼睛对于明暗的分辨要比对颜色的分辨要精细,也就是我们眼睛对于亮度的敏感程度要大于色度的敏感程度。那么,我们在存储图像信息时,为了节约空间,就没有必要将所有的色度信息全部存储下来了。
YUV像素格式来源于RGB像素格式,通过公式运算,YUV 三分量可以还原出 RGB。
YUV的存储格式
YUV格式有两大类:planar和packed。
- 对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。
- 对于packed的YUV格式,每个像素点的Y,U,V是连续交叉存储的。
YUV的采样格式
YUV码流的存储格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0
用三个图来直观地表示采集的方式吧,以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量。
- YUV 4:4:4采样,每一个Y对应一组UV分量。
- YUV 4:2:2采样,每两个Y共用一组UV分量。
- YUV 4:2:0采样,每四个Y共用一组UV分量。
存储方式实例:
<1>YUV422存储类型
<1-1>YUYV格式(采样格式为YUV422,存储格式为packed)
YUYV是YUV422采样的存储格式的一种,相邻的两个Y公用其相邻的两个Cb(U)、Cr(V)。对于像素点Y’00、Y’01而言,其Cb、Cr的值均为Cb00、Cr00,其他的像素点的YUV取值依次类推。
<1-2>UYVY格式(采样格式为YUV422,存储格式为packed)
<1-3>YUV422P(采样格式为YUV422,存储格式为planar)
YUV422P是一种Plane模式,即planar模式,并不是像上面YUV数据交错存储,而是先存储所有的Y分量,然后存储所有的U(Cb)分量,最后存储所有的V(Cr)分量。其每一个像素点的YUV值提取方法也是遵循YUV422格式的最基本提取方法,即两个Y共用一个UV。比如,对于像素点Y’00、Y’01而言,其Cb、Cr的值均为Cb00、Cr00。
<2>YUV420存储类型
基于 YUV 4:2:0 采样的格式主要有 YUV 420P 和 YUV 420SP 两种类型,每个类型又对应其他具体格式。
- YUV 420P 类型
- YU12 格式
- YV12 格式
- YUV 420SP 类型
-
NV12 格式
-
NV21 格式
-
YUV 420P 和 YUV 420SP 都是基于 Planar 平面格式 进行存储的,先存储所有的 Y 分量后, YUV420P 类型就会先存储所有的 U 分量或者 V 分量,而 YUV420SP 则是按照 UV 或者 VU 的交替顺序进行存储了,
<2-1>YUV420sp(采样格式为YUV420,存储格式为planar,分Y-planner和UV-planar,其中UV平面为packed)
NV21、NV12都属于YUV420格式,是一种two-plane模式,即Y和UV分为两个Plane,但是UV(CbCr)为交错存储,而不是分为三个plane。其提取方式与上面一种类似,即Y’00、Y’01、Y’10、Y’11共用Cr00、Cb00。
<2-2>YUV420p(采样格式为YUV420,存储格式为planar,分Y-planner和U-planar和V-planar)
YU12(又称I420)和YV12属于YUV420格式,也是一种Plane模式,将Y、U、V分量分别打包,依次存储。其没一个像素点的YUV数据提取都遵循YUV420格式的提取方式,即4个Y分量共用一组UV。如上图中,Y’00、Y’01、Y’10、Y’11共用Cr00、Cb00,其他以此类推。
注意,YU12与YV12的区别在于是先存U还是先存V。对于YU12来说,存储顺序是YUV,即YCbCr;对于YV12来说,存储顺序是YVU,即YCrCb。