原YUV格式的解析 Android NV21 视频采集

来源:互联网 发布:淘宝网钱包男士 编辑:程序博客网 时间:2024/05/04 08:17

最近在搞视频分析类的项目,android默认视频采集格式是NV21,Y Cr Cb格式,4.2.0.方式采样
还有其他采样方式,默认使用NV21

Sets the image format for preview pictures.

If this is never called, the default format will be NV21, which uses the NV21 encoding format.

intJPEGEncoded formats.intNV16YCbCr format, used for video.intNV21YCrCb format used for images, which uses the NV21 encoding format.intRGB_565RGB format used for pictures encoded as RGB_565 see setPictureFormat(int).intUNKNOWN intYUY2YCbCr format used for images, which uses YUYV (YUY2) encoding format.intYV12Android YUV format: This format is exposed to software decoders and applications.具体参见http://developer.android.com/reference/android/graphics/ImageFormat.html
-----------------------------------------------------------------------------------------------------------
首先是其数据是怎样存储的
// 我们通过下列方式获取数据
class Buf implements Camera.PreviewCallback {
public byte[] yuv420sp =null;
public void onPreviewFrame(byte[] data, Camera camera) {
// 这里出来的data和tackPic出来的有区别,这边是NV21格式
yuv420sp=data;
}
}
420这种格式采样方式如下
X  X  X  X
O     O   
X  X  X  X

X  X  X  X
O     O   
X  X  X  X
即每4个Y对应一个U和一个V
Y0U0V0             Y1                 Y2U2V2                Y3
Y4                       Y5                 Y6                          Y7
Y8U8V8             Y9                 Y10U10V10         Y11
Y12                     Y13               Y14                       Y15
通过对采样数组的研究其存放规律是
YYYYY...(采样宽度width)...YYYYY
...
...(采样高度height)
...
YYYYY...(采样宽度width)...YYYYY
(vu)(vu)(vu)...(采样宽度)...(vu)(vu)(vu)
...(采样高度除以2,height/2,遇单加1)
(vu)(vu)(vu)...(采样宽度)...(vu)(vu)(vu)
即width=320,height=480,采样Y值长度320×480,采样VU长度320×480/2.yuv420sp=data;总长度为宽高乘积的一点五倍

// YUV转RGB rgb长度即width×heght
static public void decodeYUV420SP(int[] rgb, byte[] yuv420sp, int width, int height) {
        final int frameSize = width * height;
        
        for (int j = 0, yp = 0; j < height; j++) {
                int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
                for (int i = 0; i < width; i++, yp++) {
                        int y = (0xff & ((int) yuv420sp[yp])) - 16;
                        if (y < 0) y = 0;
                        if ((i & 1) == 0) {
                                v = (0xff & yuv420sp[uvp++]) - 128;
                                u = (0xff & yuv420sp[uvp++]) - 128;
                        }
                        
                        int y1192 = 1192 * y;
                        int r = (y1192 + 1634 * v);
                        int g = (y1192 - 833 * v - 400 * u);
                        int b = (y1192 + 2066 * u);
                        
                        if (r < 0) r = 0; else if (r > 262143) r = 262143;
                        if (g < 0) g = 0; else if (g > 262143) g = 262143;
                        if (b < 0) b = 0; else if (b > 262143) b = 262143;
                        
                        rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
                }
        }
}

以上是android自带的YUV420SP转RGB的函数,但是坑爹的问题在于函数处理数据的能力有限,我们有的时候并不需要全部转完毕再解析。
以下是更改后的方法,用于取值某点的RGB,在一些实时性要求的应用中可以用到
// 自定义RGB
static public int myDecodeYUV420SP(byte[] yuv420sp, int width, int height,
int mWidth, int mHeight) {
// Y矩阵长度frameSize , V和U矩阵第一位即frameSize
final int frameSize = width * height;
// yp为Y在矩阵中的位置,yph为所需要点的高mHeight-1,ypw为所需要点的宽mWidth
int yp, yph = mHeight - 1, ypw = mWidth;
yp = width * yph + ypw;
// uvp为
// uv在数组中的位置,V和U矩阵第一位即frameSize,yph>>1取值范围(0,0,1,1,2,2...)yph从0开始,即UV数组为Y数组的1/2.
int uvp = frameSize + (yph >> 1) * width, u = 0, v = 0;
// 获取Y的数值
int y = (0xff & ((int) yuv420sp[yp])) - 16;
if (y < 0)
y = 0;
if ((ypw & 1) == 0) {
v = (0xff & yuv420sp[uvp++]) - 128;
u = (0xff & yuv420sp[uvp]) - 128;
} else {
u = (0xff & yuv420sp[uvp--]) - 128;
v = (0xff & yuv420sp[uvp]) - 128;
}

int y1192 = 1192 * y;
int r = (y1192 + 1634 * v);
int g = (y1192 - 833 * v - 400 * u);
int b = (y1192 + 2066 * u);

if (r < 0)
r = 0;
else if (r > 262143)
r = 262143;
if (g < 0)
g = 0;
else if (g > 262143)
g = 262143;
if (b < 0)
b = 0;
else if (b > 262143)
b = 262143;

return (0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff));

}

两套函数出来的数据int型需要转换成r/g/b。亮点就是转换方式

public static int red (int color)

Since: API Level 1

Return the red component of a color int. This is the same as saying (color >> 16) & 0xFF

public static int green (int color)

Since: API Level 1

Return the green component of a color int. This is the same as saying (color >> 8) & 0xFF

public static int blue (int color)

Since: API Level 1

Return the blue component of a color int. This is the same as saying color & 0xFF

至此yuv取点rgb完成
1 需要代码的童鞋可以留下你的email
2 如果描述有误请指正
3 感谢 幸福的起点 博主,更多视频格式说明请参照http://hi.baidu.com/joygogo0130/blog/item/04b707529efbec090df3e345.html
4 更多视频问题 请参照http://developer.android.com/reference/android/graphics/Camera.html
http://ai4work.blog.163.com/blog/static/189873306201171883316264/

原创粉丝点击