文章参考:
本文基于Android音视频开发时需要的,对基础视频流YUV格式的认识。主要描述对YUV的基本认识,YUV格式的区别,Android音视频开发时常用到的YUV格式处理,转换,显示方法等。YUV格式的认识很多引用和参考上述博文,做了一些总结,也包括一些个人的理解,还有许多开发时遇到的功能或者问题的总结。
YUV是一种颜色编码格式,可以说YUV流媒体是原始流数据,大部分的视频领域都在使用。他与RGB类似,但RGB更多的用于渲染时,而YUV则用在数据传输,因为它占用更少的频宽。当然,实时通讯为了降低带宽都会采用H264/H265编码。从字面意思理解,YUV的含义:Y代表亮度信息(灰度),UV分别代表色彩信息。YUV的常用名称有许多,如YUV422这是大部分镜头出来的数据,还有许多(yuv420,yuv444等)。
这是yuv格式的两大类
planar格式:连续存储所有像素点Y,然后是所有像素点U,接着是V
packed格式:所有像素点的YUV信息连续交错存储
比如:
YUV420P:YYYYYYYY UU VV
YUV420: YUV YUV YUV
它们分别代表在不同领域时使用的名称,总的大类都是一致的。主流上所说的YUV即是YCbCr
数字代表yuv信息在像素点中的分布状况,为了维持人的肉眼观感,通常需要每个像素点保存8bit的亮度,每2x2个点保存至少一个Cb和Cr值,如下所示(要理解它的排列就要知道,它在量化8bit之后,每个像素占用大小,可以参考文章:图文详解YUV420数据格式,它里面的描述图很好理解):
YUV444采样,每个Y对应一组UV,8bit量化,每个像素占用3个字节。
YUV422采样,每2个Y对应一组UV,由两个水平方向相邻的像素组成的宏像素需要占用4字节内存,亮度2个字节,两个色度各1个字节。
YUV411采样,每4个Y对应一组UV,由4个水平方向相邻的像素组成的宏像素需要占用6字节内存,亮度4个字节,两个色度各1个字节。
YUV420采样,每4个Y对应一组UV,每个由2x2个2行2列相邻的像素组成的宏像素需要占用6字节内存。,亮度4个字节,两个色度各1个字节。
跨距的由来,因为CPU存储和读取必须是2的密次方,故而很多分辨率的yuv格式通常会有一个stride,比如某个720*536的YUV420SP视频,它的stride是768,那么中间48就是跨距。通常如果自己去解析,可以通过偏移裁取,如果采用第三方库,一般都会有传入跨距的值。
在Android中,setPreviewFormat中就有标注YV12的跨距计算方式:
{@link android.graphics.ImageFormat#YV12}. For camera callback data,
* it can be assumed that the stride of the Y and UV data is the
* smallest possible that meets the alignment requirements. That is, if
* the preview size is width x height, then the following
* equations describe the buffer index for the beginning of row
* y for the Y plane and row c for the U and V
* planes:
*
* {@code
* yStride = (int) ceil(width / 16.0) * 16;
* uvStride = (int) ceil( (yStride / 2) / 16.0) * 16;
* ySize = yStride * height;
* uvSize = uvStride * height / 2;
* yRowIndex = yStride * y;
* uRowIndex = ySize + uvSize + uvStride * c;
* vRowIndex = ySize + uvStride * c;
* size = ySize + uvSize * 2;
* }
YUV422,大多数的Android机 sensor出来的视频流都是这个格式
YUYV:
YVYU:
YUV422P:2个Y对应一对UV,即Y00 Y01对应U00 V00
在Android Camera框架中,setPreviewFormat中可传入的格式,API给出的2个可供选择的格式分别是ImageFormat.NV21和ImageFormat.YV12
YV12和YU12都属于YUV420p,其中Y\U\V分别对应一个plane,区别在于UV的位置对调,下面是YU12的存储示意图:
NV12和NV21,其中NV12就是我们Android常见的YUV420SP,他们不像上一个YV12,有3个plane,而是由Y和UV分别两个Plane组成,UV交替排列,U在前的是NV12,V在前为NV21.
I420:或表示为IYUV,数码摄像机专用表示法.
setPreviewFormat中可以指定通常是NV12和YV12
YV12toYUV420SP:
public static byte[] YV12toYUV420SP(final byte[] input, final byte[] output, final int width, final int height) {
final int frameSize = width * height;
final int qFrameSize = frameSize / 4;
System.arraycopy(input, 0, output, 0, frameSize); // Y
for (int i = 0; i < qFrameSize; i++) {
output[frameSize + i * 2 + 1] = input[frameSize + i + qFrameSize]; // Cr (V)
output[frameSize + i * 2] = input[frameSize + i]; // Cb (U)
}
return output;
}
rotateYUV240SP:
public static void rotateYUV240SP(byte[] src, byte[] des, int width, int height) {
int wh = width * height;
//旋转Y
int k = 0;
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
des[k] = src[width * j + i];
k++;
}
}
for (int i = 0; i < width / 2; i++) {
for (int j = 0; j < height / 2; j++) {
des[k] = src[wh + width / 2 * j + i];
des[k + width * height / 4] = src[wh * 5 / 4 + width / 2 * j + i];
k++;
}
}
}
部分Android硬件平台对stride也有要求,比如MTK
// Note: the stride of resolution must be set as 16x for hard encoding with some chip like MTK
// Since Y component is quadruple size as U and V component, the stride must be set as 32x
if (!useSoftEncoder && vOutWidth % 32 != 0 || vOutHeight % 32 != 0) {
if (vmci.getName().contains("MTK")) {
throw new AssertionError("MTK encoding revolution stride must be 32x");
}
}
AndroidH264银编码时传入的MediaFormat.KEY_COLOR_FORMAT注意事项
首先时写法,不同的CPU支持编码的yuv格式不同,需要主动查询符合支持的列表
// choose the video encoder by name.
private MediaCodecInfo chooseVideoEncoder(String name) {
int nbCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i < nbCodecs; i++) {
MediaCodecInfo mci = MediaCodecList.getCodecInfoAt(i);
if (!mci.isEncoder()) {
continue;
}
String[] types = mci.getSupportedTypes();
for (int j = 0; j < types.length; j++) {
if (types[j].equalsIgnoreCase(VCODEC)) {
LogUtils.i(TAG, String.format("vencoder %s types: %s", mci.getName(), types[j]));
if (name == null) {
return mci;
}
if (mci.getName().contains(name)) {
return mci;
}
}
}
}
return null;
}
通过查看android.media.MediaCodecInfo.CodecCapabilities,常用的YUV ColorFormat项如下,我们能发现大部分是隐藏API,反而推荐我们去使用COLOR_FormatYUV420Flexible, COLOR_FormatYUV422Flexible等,接下来解释下为什么会这样
/** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
public static final int COLOR_FormatYUV411Planar = 17;
/** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
public static final int COLOR_FormatYUV411PackedPlanar = 18;
/** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
public static final int COLOR_FormatYUV420Planar = 19;
/** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
public static final int COLOR_FormatYUV420PackedPlanar = 20;
/** @deprecated Use {@link #COLOR_FormatYUV420Flexible}. */
public static final int COLOR_FormatYUV420SemiPlanar = 21;
/** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
public static final int COLOR_FormatYUV422Planar = 22;
/** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
public static final int COLOR_FormatYUV422PackedPlanar = 23;
/** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
public static final int COLOR_FormatYUV422SemiPlanar = 24;
/** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
public static final int COLOR_FormatYCbYCr = 25;
/** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
public static final int COLOR_FormatYCrYCb = 26;
/** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
public static final int COLOR_FormatCbYCrY = 27;
/** @deprecated Use {@link #COLOR_FormatYUV422Flexible}. */
public static final int COLOR_FormatCrYCbY = 28;
/** @deprecated Use {@link #COLOR_FormatYUV444Flexible}. */
public static final int COLOR_FormatYUV444Interleaved = 29;
首先看注解:
/**
* Flexible 12 bits per pixel, subsampled YUV color format with 8-bit chroma and luma
* components.
*
* Chroma planes are subsampled by 2 both horizontally and vertically.
* Use this format with {@link Image}.
* This format corresponds to {@link android.graphics.ImageFormat#YUV_420_888},
* and can represent the {@link #COLOR_FormatYUV411Planar},
* {@link #COLOR_FormatYUV411PackedPlanar}, {@link #COLOR_FormatYUV420Planar},
* {@link #COLOR_FormatYUV420PackedPlanar}, {@link #COLOR_FormatYUV420SemiPlanar}
* and {@link #COLOR_FormatYUV420PackedSemiPlanar} formats.
*
* @see Image#getFormat
*/
它大体意思是,哥们,我是个万能钥匙,对应了ImageFormat中的YUV_420_888,可以代替
COLOR_FormatYUV411PackedPlanar,COLOR_FormatYUV420Planar,COLOR_FormatYUV420PackedPlanar
以及COLOR_FormatYUV420SemiPlanar,COLOR_FormatYUV420PackedSemiPlanar使用
好吧,通常编码时查看支持列表有它都可以传入,那我们来看看它可替代的这些format的含义: