说简单点,就是如何将 YUV I420 格式转换为 RGBA8888 格式。
在 Camera2 API 中,相机预览不能直接使用 NV21 格式获取了,否则会报错一个 “NV21 format is not supported”
的异常。官方推荐我们使用 YUV_420_888 格式,关于这个格式的介绍,可以参考官方文档或者百度。
如果获取到相机预览数据,并转化为 YUV I420 格式的 byte[],可以参考我的这一篇博客:
Android Camera2 获取预览帧的回调数据
假设我们进一步想将 YUV I420 格式的数据转为 Bitmap 保存或者显示,该如何做呢?
网上已经有很多成熟的图片转码方法和库了。这里不详细介绍转码原理,仅提供代码作为参考。
首先写一个 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;
}
图片转码这类操作建议还是放在底层来做,所以新建一个 NativeLibrary 类来调用底层代码。
public class NativeLibrary {
static {
System.loadLibrary("native-lib");
}
public static native void yuv420p2rgba(byte[] yuv420p,
int width,
int height,
byte[] rgba);
}
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);
}
转码代码的实际实现,如下:
#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,包括相机预览数据的获取和保存。