return (int) ((y & 0xFF) + 1.779 * ((u & 0xFF) - 128));
}
int alignIntToByte(int c) {
return c & 0xFF;
}
举个例子,对于4x2的图片,RGBA32
格式内容为:
R1 G1 B1 A1 R2 G2 B2 A2 R3 G3 B3 A3 R4 G4 B4 A4
R5 G5 B5 A5 R6 G6 B6 A6 R7 G7 B7 A7 R8 G8 B8 A8
那么若需要转化为BGR24
,内容将变成:
B1 G1 R1 B2 G2 R2 B3 G3 R3 B4 G4 R4
B5 G5 R5 B6 G6 R6 B7 G7 R7 B8 G8 R8
BGR24
内容为3个byte
一组,RGBA32
内容为4个byte
一组。因此,对于第一组RGBA32(R1 G1 B1 A1)
和第一组BGR24(B1 G1 R1)
,其对应关系为:
bgr24[0] = Rgba32[2];
bgr24[1] = Rgba32[1];
bgr24[2] = Rgba32[0];
对应的转换代码:
void rgba32ToBgr24(char *rgba32, char *bgr24, int width, int height) {
int groupNum = width * height;
int bgr24Index = 0;
int rgba32Index = 0;
for (int i = 0; i < groupNum; i++) {
*(bgr24 + bgr24Index) = *(rgba32 + rgba32Index + 2);
*(bgr24 + bgr24Index + 1) = *(rgba32 + rgba32Index + 1);
*(bgr24 + bgr24Index + 2) = *(rgba32 + rgba32Index);
bgr24Index += 3;
rgba32Index += 4;
}
}
BGR24
的每一组 B G R
数据对应了一个像素点,而NV21
是一种YUV
数据,YUV
数据的每个Y
会和对应的U
和V
组成一个像素点,NV21
的共用关系是每4个Y(例如下标为:0
,1
,width
,width+1
的四个Y
)会共用一组U
和V
(对应下标:U
:width x height + 1
,V
:width x height
)。所以对于Y
而言,需要计算每一组BGR
对应的Y
值;但是对于NV21
的U
和V
而言,U
和V
的大小都是各只有Y
的一半,所以我们可以在宽度下标和高度下标都为偶数时计算一组U
和V
。
void bgr24ToNv21(char *bgr24, char *nv21, int width, int height) {
int yIndex = 0;
int uvIndex = width * height;
int bgrIndex = 0;
int nv21Length = width * height * 3 / 2;
for (int j = 0; j < height; ++j) {
for (int i = 0; i < width; ++i) {
int b = bgr24[bgrIndex++];
int g = bgr24[bgrIndex++];
int r = bgr24[bgrIndex++];
b = alignIntToByte(b);
g = alignIntToByte(g);
r = alignIntToByte®;
int y = rgbToY(r, g, b);
nv21[yIndex++] = static_cast(alignIntToByte(y));
if ((j & 1) == 0 && ((bgrIndex >> 2) & 1) == 0 && uvIndex < nv21Length - 2) {
int u = rgbToU(r, g, b);
int v = rgbToV(r, g, b);
nv21[uvIndex++] = static_cast(alignIntToByte(v));
nv21[uvIndex++] = static_cast(alignIntToByte(u));
}
}
}
}
这是上一个介绍的逆过程,在BGR24
转换为NV21
时,宽度下标和高度下标都为偶数才进行计算U
和V
;那么若要将NV21
转换为BGR24
,则我们需要对每组U
和V
进行一次复用。
void nv21ToBgr24(char *nv21, char *bgr24, int width, int height) {
int bgrLineSize = width * 3;
//偶数行的bgr数据下标
int evenLineBgrIndex = 0;
//奇数行的bgr数据下标
int oddLineBgrIndex = bgrLineSize;
//当前一行y数据最左边的下标
int yLineStart = 0;
//uv数据的下标
int uvIndex = width * height;
//由于NV21的共用关系,每2行做一次转换
for (int i = 0; i < height; i += 2) {
for (int widthOffset = 0; widthOffset < width; widthOffset++) {
char v = nv21[uvIndex];
char u = nv21[uvIndex + 1];
char yEven = nv21[yLineStart + widthOffset];
char yOdd = nv21[yLineStart + width + widthOffset];
int r, g, b;
//偶数行YUV转RGB
r = alignIntToByte(yuvToR(yEven, u, v));
g = alignIntToByte(yuvToG(yEven, u, v));
b = alignIntToByte(yuvToB(yEven, u, v));
bgr24[evenLineBgrIndex++] = (char) b;
bgr24[evenLineBgrIndex++] = (char) g;
bgr24[evenLineBgrIndex++] = (char) r;
//奇数行YUV转RGB
r = alignIntToByte(yuvToR(yOdd, u, v));
g = alignIntToByte(yuvToG(yOdd, u, v));
b = alignIntToByte(yuvToB(yOdd, u, v));
bgr24[oddLineBgrIndex++] = (char) b;
bgr24[oddLineBgrIndex++] = (char) g;
bgr24[oddLineBgrIndex++] = (char) r;
//每两个y将uv下标增1
if ((widthOffset & 1) == 1) {
uvIndex += 2;
}
}
//由于在内层循环中已经做过width * 3次自增,所以外层循环中只需要增加一行
evenLineBgrIndex += bgrLineSize;
oddLineBgrIndex += bgrLineSize;
//y增2行
yLineStart += (width << 1);
}
}
NV21
和NV12
只是U与V的数据位置不同,只需要替换U和V的位置即可。因此,将NV21
转换为NV12
的代码同样适用于NV12
转换为NV21
。可参考如下代码:
void nv21ToNv12(char *nv21, char *nv12, int width, int height) {
int ySize = width * height;
int totalSize = width * height * 3 / 2;
//复制Y
memcpy(nv12, nv21, ySize);
//UV互换
for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
*(nv12 + uvIndex) = *(nv21 + uvIndex + 1);
*(nv12 + uvIndex + 1) = *(nv21 + uvIndex);
}
}
NV21
转化为YV12
的过程主要是将其UV数据的交叉排序修改为连续排序。可参考如下代码:
void nv21ToYv12(char *nv21, char *yv12, int width, int height) {
int ySize = width * height;
int totalSize = width * height * 3 / 2;
int i420UIndex = ySize;
int i420VIndex = ySize * 5 / 4;
//复制y
memcpy(yv12, nv21, ySize);
//复制uv
for (int uvIndex = ySize; uvIndex < totalSize; uvIndex += 2) {
*(yv12 + i420UIndex++) = *(nv21 + uvIndex);
*(yv12 + i420VIndex++) = *(nv21 + uvIndex + 1);
}
}
在YUYV
格式中,两个Y
共用一组U
和V
,而NV12
是四个Y
共用一组U
和V
,因此,这是一个YUV422
转YUV420
的过程,需要舍弃一半的U
和V
,需要注意的是,每一组YUYV
是2个像素。可参考如下代码:
void yuyvToNv12(char *yuyv, char *nv12, int width, int height) {
int ySize = width * height;
int lineDataSize = width * 2;
char *nv12UV = nv12 + ySize;
for (int i = 0; i < height; i++, yuyv += lineDataSize) {
if ((i & 1) == 0) {
for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) {
//拷贝Y
*nv12++ = *(yuyv + lineOffset);
*nv12++ = *(yuyv + lineOffset + 2);
//拷贝UV
*nv12UV++ = *(yuyv + lineOffset + 1);
*nv12UV++ = *(yuyv + lineOffset + 3);
}
} else {
for (int lineOffset = 0; lineOffset < lineDataSize; lineOffset += 4) {
//拷贝Y
*nv12++ = *(yuyv + lineOffset);
*nv12++ = *(yuyv + lineOffset + 2);
}
}
}
}
I420
和YV12
只是U
与V
的数据位置不同,因此,I420
转换为YV12
的代码同样适用于YV12
转换为I420
。可参考如下代码:
void i420ToYv12(char *i420, char *yv12, int width, int height) {
int ySize = width * height;
int uSize = ySize / 4;
int vSize = uSize;
//复制Y
memcpy(yv12, i420, ySize);
//UV互换
memcpy(yv12 + ySize + uSize, i420 + ySize, uSize);
memcpy(yv12 + ySize, i420 + ySize + vSize, vSize);
}
最后
其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。
[外链图片转存中…(img-PaTGcYc6-1714956177180)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!