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,包括相机预览数据的获取和保存。

  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
好的,针对您的问题,我可以提供以下的参考代码,可以将 Android camera2 api YUV_420_888 格式的数据换为 RGB 格式的数据: ``` private void convertYUV420ToRGB(Image imgYUV, Bitmap imgRGB) { // Get image width and height int width = imgYUV.getWidth(); int height = imgYUV.getHeight(); // Get Y, U, and V image planes from image Image.Plane[] planes = imgYUV.getPlanes(); ByteBuffer bufferY = planes[0].getBuffer(); ByteBuffer bufferU = planes[1].getBuffer(); ByteBuffer bufferV = planes[2].getBuffer(); // Calculate pixel stride and row stride for Y, U, and V image planes int pixelStrideY = planes[0].getPixelStride(); int rowStrideY = planes[0].getRowStride(); int pixelStrideU = planes[1].getPixelStride(); int rowStrideU = planes[1].getRowStride(); int pixelStrideV = planes[2].getPixelStride(); int rowStrideV = planes[2].getRowStride(); // Create arrays for storing pixel data byte[] dataY = new byte[width * height * pixelStrideY]; byte[] dataU = new byte[width * height * pixelStrideU / 4]; byte[] dataV = new byte[width * height * pixelStrideV / 4]; // Copy pixel data from image planes into arrays bufferY.get(dataY, 0, width * height * pixelStrideY); bufferU.get(dataU, 0, width * height * pixelStrideU / 4); bufferV.get(dataV, 0, width * height * pixelStrideV / 4); // Initialize RGB pixel buffer int[] pixels = new int[width * height]; int offset = 0; // Loop through each row and column of pixels for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // Get YUV pixel values for current pixel int Y = dataY[offset] & 0xff; int U = dataU[offset / 4] & 0xff; int V = dataV[offset / 4] & 0xff; // Calculate RGB pixel values for current pixel int R = (int)(Y + 1.370705f * (V - 128)); int G = (int)(Y - 0.698001f * (V - 128) - 0.337633f * (U - 128)); int B = (int)(Y + 1.732446f * (U - 128)); // Clip RGB pixel values to range [0, 255] R = Math.max(0, Math.min(255, R)); G = Math.max(0, Math.min(255, G)); B = Math.max(0, Math.min(255, B)); // Combine RGB pixel values into single pixel pixels[offset++] = (0xff << 24) | (R << 16) | (G << 8) | B; } // Move to next row of pixels offset += rowStrideY - width * pixelStrideY; } // Set RGB pixel data into Bitmap imgRGB.setPixels(pixels, 0, width, 0, 0, width, height); } ``` 这个方法将会把 Image 对象 imgYUV 中的 YUV 数据换为 RGB 数据,并且将其存储到 Bitmap 对象 imgRGB 中。其中,YUV 数据的格式为 YUV_420_888,RGB 数据的格式为 ARGB_8888。 注意,这个方法中的换公式是基于 YUV420 的格式,如果您的 YUV 数据格式不同,需要根据实际情况对换公式进行修改。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值