Android Camera2 预览数据格式 YUV_420_888 合集之 I420 转 Bitmap

一、简介

说简单点,就是如何将 YUV I420 格式转换为 RGBA8888 格式。

在 Camera2 API 中,相机预览不能直接使用 NV21 格式获取了,否则会报错一个 “NV21 format is not supported” 的异常。官方推荐我们使用 YUV_420_888 格式,关于这个格式的介绍,可以参考官方文档或者百度。

如果获取到相机预览数据,并转化为 YUV I420 格式的 byte[],可以参考我的这一篇博客:

Android Camera2 获取预览帧的回调数据

假设我们进一步想将 YUV I420 格式的数据转为 Bitmap 保存或者显示,该如何做呢?

二、转码过程

网上已经有很多成熟的图片转码方法和库了。这里不详细介绍转码原理,仅提供代码作为参考。

1. ColorConvertUtil

首先写一个 Java 类来负责颜色转换吧。并提供一个 yuv420pToBitmap 的方法。

import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.util.Log;

import java.nio.ByteBuffer;

public class ColorConvertUtil {

    private static final String TAG = "ColorConvertUtil";

    public static Bitmap yuv420pToBitmap(byte[] yuv420p, int width, int height) {
        if (yuv420p == null || width < 0 || height < 0) {
            Log.e(TAG, "cropNv21ToBitmap failed: illegal para !");
            return null;
        }
        byte[] rgba = new byte[width * height * 4];
        ColorConvertUtil.yuv420pToRGBA(yuv420p, width, height, rgba);
        Bitmap bitmap = byteArrayToBitmap(rgba, width, height);
        return bitmap;
    }

    public static void yuv420pToRGBA(byte[] yuv420p, int width, int height, byte[] rgba) {
        if (yuv420p == null || rgba == null) {
            Log.e(TAG, "yuv420pToRGBA failed: yuv420p or rgba is null ");
            return;
        }
        if (yuv420p.length != width * height * 3 / 2) {
            Log.e(TAG, "yuv420p length: " + yuv420p.length);
            Log.e(TAG, "yuv420pToRGBA failed: yuv420p length error!");
            return;
        }
        NativeLibrary.yuv420p2rgba(yuv420p, width, height, rgba);
    }

    /**
     * 将 rgba 的 byte[] 数据转换成 bitmap
     *
     * @param rgba   输入的 rgba 数据
     * @param width  图片宽度
     * @param height 图片高度
     * @return 得到的 bitmap
     */
    public static Bitmap byteArrayToBitmap(byte[] rgba, int width, int height) {
        ByteBuffer buffer = ByteBuffer.wrap(rgba);
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        bitmap.copyPixelsFromBuffer(buffer);
        return bitmap;
    }

2. NativeLibrary

图片转码这类操作建议还是放在底层来做,所以新建一个 NativeLibrary 类来调用底层代码。

public class NativeLibrary {

    static {
        System.loadLibrary("native-lib");
    }

    public static native void yuv420p2rgba(byte[] yuv420p,
                                          int width,
                                          int height,
                                          byte[] rgba);
}

3. NativeLibrary.cpp

NativeLibrary 类对应的 cpp 文件为:

#include "com_afei_camera2getpreview_util_NativeLibrary.h"
#include "ImageUtil.h"

JNIEXPORT void JNICALL Java_com_afei_camera2getpreview_util_NativeLibrary_yuv420p2rgba
        (JNIEnv *env, jclass type, jbyteArray yuv420p_, jint width, jint height, jbyteArray rgba_) {
    jbyte *yuv420p = env->GetByteArrayElements(yuv420p_, NULL);
    jbyte *rgba = env->GetByteArrayElements(rgba_, NULL);

    i420torgba(reinterpret_cast<const unsigned char *>(yuv420p), width, height, reinterpret_cast<unsigned char *>(rgba));

    env->ReleaseByteArrayElements(yuv420p_, yuv420p, 0);
    env->ReleaseByteArrayElements(rgba_, rgba, 0);
}

4. ImageUtil.cpp

转码代码的实际实现,如下:

#include "ImageUtil.h"

#define MAX(a, b) ((a > b) ? a : b)
#define MIN(a, b) ((a < b) ? a : b)
#define CLAP(a) (MAX((MIN(a, 0xff)), 0x00))

void i420torgba(const unsigned char *imgY,
                const int width,
                const int height,
                unsigned char *imgDst) {
    int w, h;
    int shift = 14, offset = 8192;
    int C0 = 22987, C1 = -11698, C2 = -5636, C3 = 29049;

    int y1, y2, u1, v1;

    const unsigned char *pY1 = imgY;
    const unsigned char *pY2 = imgY + width;
    const unsigned char *pU = imgY + width * height;
    const unsigned char *pV = imgY + (int) (width * height * 1.25);

    unsigned char *pD1 = imgDst;
    unsigned char *pD2 = imgDst + width * 4;

    for (h = 0; h < height; h += 2) {
        for (w = 0; w < width; w += 2) {
            v1 = *pV - 128;
            pV++;
            u1 = *pU - 128;
            pU++;

            y1 = *pY1;
            y2 = *pY2;

            *pD1++ = CLAP(y1 + ((v1 * C0 + offset) >> shift)); // r
            *pD1++ = CLAP(y1 + ((u1 * C2 + v1 * C1 + offset) >> shift)); // g
            *pD1++ = CLAP(y1 + ((u1 * C3 + offset) >> shift)); // b
            *pD1++ = 0xff; // a
            *pD2++ = CLAP(y2 + ((v1 * C0 + offset) >> shift)); // r
            *pD2++ = CLAP(y2 + ((u1 * C2 + v1 * C1 + offset) >> shift)); // g
            *pD2++ = CLAP(y2 + ((u1 * C3 + offset) >> shift)); // b
            *pD2++ = 0xff; // a

            pY1++;
            pY2++;
            y1 = *pY1;
            y2 = *pY2;

            *pD1++ = CLAP(y1 + ((v1 * C0 + offset) >> shift)); // r
            *pD1++ = CLAP(y1 + ((u1 * C2 + v1 * C1 + offset) >> shift)); // g
            *pD1++ = CLAP(y1 + ((u1 * C3 + offset) >> shift)); // b
            *pD1++ = 0xff; // a
            *pD2++ = CLAP(y2 + ((v1 * C0 + offset) >> shift)); // r
            *pD2++ = CLAP(y2 + ((u1 * C2 + v1 * C1 + offset) >> shift)); // g
            *pD2++ = CLAP(y2 + ((u1 * C3 + offset) >> shift)); // b
            *pD2++ = 0xff; // a
            pY1++;
            pY2++;
        }
        pY1 += width;
        pY2 += width;
        pD1 += 4 * width;
        pD2 += 4 * width;
    }
}

三、其它

另外一些代码和工程配置,可以参考:

https://github.com/afei-cn/Camera2GetPreview

上面工程是一个完整可直接运行的 demo,包括相机预览数据的获取和保存。

你可能感兴趣的:(Android)