Image转YuvImage 方法及解析
如何将Image图像装YUV格式。图像算法大多数是以yuv格式输入,所以如何将其转换很重要。
java代码部分
public static YuvImage convImageToYuvImage(int imageWidth, Image image, int rotation, boolean needMirror) {
int width = image.getWidth();
if (imageWidth > 0) {
width = imageWidth;
}
int height = image.getHeight();
//对于常见的 YUV 420 格式来说,每个像素由一个 Y 分量和一个 U、V 分量对组成,其中每个像素的 Y 分量占据一个字节,而 U、V 分量每个像素共享一个字节,因此每个像素总共占用 1.5 个字节。
int yuvSize = width * height * 3 / 2;
byte[] dstNV21 = new byte[yuvSize];
Image.Plane[] planes = image.getPlanes();
Image.Plane yPlane = planes[0];
Image.Plane uPlane = planes[1];
Image.Plane vPlane = planes[2];
YuvUtils.convertNVToNV21(yPlane.getBuffer(), yPlane.getRowStride()
, uPlane.getBuffer(), vPlane.getBuffer(), vPlane.getRowStride()
, dstNV21, width, image.getHeight(), rotation, needMirror);
// it is better to close image outside.
// image.close();
if (rotation == 90 || rotation == 270) {
int temp = width;
width = height;
height = temp;
}
return new YuvImage(dstNV21, ImageFormat.NV21, width, height, null);
}
JNI代码部分
extern "C"
JNIEXPORT void JNICALL
Java_com_camera_yuvutils_YuvUtils_convertNVToNV21(JNIEnv *env, jclass clazz, jobject y,
jint row_stride_y, jobject u, jobject v,
jint row_stride_uv, jbyteArray out,
jint width, jint height, jint rotation,
jboolean mirror) {
auto* src_y = static_cast<uint8_t *>(env->GetDirectBufferAddress(y));
auto* src_u = static_cast<uint8_t *>(env->GetDirectBufferAddress(u));
auto* src_v = static_cast<uint8_t *>(env->GetDirectBufferAddress(v));
auto *dst = (uint8_t*)env->GetByteArrayElements(out, nullptr);
auto *dst_y = dst;
auto *dst_u = dst_y + width * height;
auto *dst_v = dst_u + (width * height >> 2);
RotationMode rotationMode = getRotationMode(rotation);
rotation = rotation % 360;
int outW = width;
int outH = height;
if (rotation % 180 != 0) {
outW = height;
outH = width;
}
int outW_half = outW >> 1;
int outW_quarter = outW_half >> 1;
if (src_u > src_v) {
NV12ToI420Rotate(src_y, row_stride_y,
src_v, row_stride_uv,
dst_y, outW,
dst_u, outW_half,
dst_v, outW_half,
width, height, rotationMode);
} else {
NV12ToI420Rotate(src_y, row_stride_y,
src_u, row_stride_uv,
dst_y, outW,
dst_v, outW_half,
dst_u, outW_half,
width, height, rotationMode);
}
uint8_t* temp = static_cast<uint8_t *>(malloc(outW * outH * 3 / 2));
auto* tmp_y = temp;
auto* tmp_u = tmp_y + width * height;
auto* tmp_v = tmp_u + (width * height >> 2);
if (mirror) {
I420Mirror(dst_y, outW, dst_u, outW_half, dst_v, outW_half,
tmp_y, outW, tmp_u, outW_half, tmp_v, outW_half,
outW, outH);
} else {
I420Copy(dst_y, outW, dst_u, outW_half, dst_v, outW_half,
tmp_y, outW, tmp_u, outW_half, tmp_v, outW_half,
outW, outH);
}
libyuv::I420ToNV21(tmp_y, outW, tmp_u, outW_half, tmp_v, outW_half,
dst_y, outW,dst_y + width * height, outW, outW, outH);
free(temp);
env->ReleaseByteArrayElements(out, reinterpret_cast<jbyte *>(dst), 0);
}
auto src_y = static_cast<uint8_t >(env->GetDirectBufferAddress(y));
env
是 JNI 提供的环境变量,用于在 Java 代码和 Native 代码之间进行通信。GetDirectBufferAddress
是 JNI 环境变量env
的一个方法,用于获取DirectByteBuffer
对象的数据地址。y
、u
、v
是分别表示 Y、U、V 三个分量的DirectByteBuffer
对象,它们可能包含了图像的 YUV 数据。static_cast<uint8_t *>
是 C++ 中的静态类型转换,用于将返回的void *
类型的地址转换为uint8_t *
类型的指针,即无符号 8 位整数类型的指针。
作用是将 Java 中的 DirectByteBuffer
对象转换为 C/C++ 中的指针,以便在后续的代码中直接操作 YUV 数据。
auto *dst = (uint8_t*)env->GetByteArrayElements(out, nullptr);
auto *dst_y = dst;
auto *dst_u = dst_y + width * height;
auto *dst_v = dst_u + (width * height >> 2);
env->GetByteArrayElements(out, nullptr)
:这行代码通过 JNI 获取 Java 中的 byte[]
数组对象 out
的元素,并返回对应的 C/C++ 中的指针。这个指针 dst
指向了数组中第一个元素的地址。
auto *dst_y = dst;
:这行代码将 dst
指针赋值给 dst_y
,表示 dst_y
指向了 Y 分量的起始位置。
auto *dst_u = dst_y + width * height;
:这行代码将 dst_y
指针加上 width * height
,得到了 U 分量的起始位置,即 dst_u
指向了 U 分量的起始位置。这里的 width * height
表示 Y 分量的像素数量,因为 U 分量和 V 分量的像素数量通常是 Y 分量的四分之一。
auto *dst_v = dst_u + (width * height >> 2);
:这行代码将 dst_u
指针加上 (width * height >> 2)
,得到了 V 分量的起始位置,即 dst_v
指向了 V 分量的起始位置。这里的 (width * height >> 2)
表示将 Y 分量的像素数量除以 4,得到了 V 分量的像素数量。
int outW = width;
int outH = height;
if (rotation % 180 != 0) {
outW = height;
outH = width;
}
Android Camera旋转角度_android 手机camera orientation-CSDN博客
所以在当为90度或270度的时候需要将宽高交换
int outW_half = outW >> 1;
int outW_quarter = outW_half >> 1;
if (src_u > src_v) {
NV12ToI420Rotate(src_y, row_stride_y,
src_v, row_stride_uv,
dst_y, outW,
dst_u, outW_half,
dst_v, outW_half,
width, height, rotationMode);
} else {
NV12ToI420Rotate(src_y, row_stride_y,
src_u, row_stride_uv,
dst_y, outW,
dst_v, outW_half,
dst_u, outW_half,
width, height, rotationMode);
}
int outW_half = outW >> 1;
原因如下
在 YUV 420 格式中,每个像素对应一个 Y 值,但是对应的 U 和 V 值是共享的。具体地说,U 和 V 值的采样率是 Y 值的四分之一,也就是说,每 4 个像素共享一个 U 值,每 4 个像素共享一个 V 值。
因此,如果 YUV 图像的总像素数量为 width * height,那么其中包含的 Y 像素数量就是 width * height,而 U 和 V 像素的数量分别是 width * height / 4。
至于宽度的计算,因为 U 和 V 分量的采样率通常是 Y 分量的一半,所以 U 和 V 分量的宽度通常是 Y 分量宽度的一半。这就是为什么在这段代码中,U 和 V 分量的宽度被设置为 outW_half(即 outW 的一半)的原因。
if (rotation == 90 || rotation == 270) {
int temp = width;
width = height;
height = temp;
}
return new YuvImage(dstNV21, ImageFormat.NV21, width, height, null);
前面通过地址传入YUV库中,处理后获得yuvByte数组。通过new YuvImage获取到yuvImage对象。自从完成了Image到YUVIamge转换。
项目需要的库文件及配置
GitHub - lemenkov/libyuv: Unofficial libyuv mirror. Please submit any issues or PRs upstream.
库及头文件下载。
CMakeList.txt 中使用
cmake_minimum_required(VERSION 3.10.2)
# Declares and names the project.
project("yuvutils")
# jniLibs
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
include_directories(${PROJECT_SOURCE_DIR}/libyuv/include)
#add_subdirectory(${PROJECT_SOURCE_DIR}/libyuv)
add_library( # Sets the name of the library.
yuvutils
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
yuvutils.cpp)
add_library(yuv STATIC IMPORTED)
set_target_properties(
yuv
PROPERTIES IMPORTED_LOCATION
${CMAKE_CURRENT_SOURCE_DIR}/libyuv/jniLibs/${CMAKE_ANDROID_ARCH_ABI}/libyuv.a)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
yuvutils
# Links the target library to the log library
# included in the NDK.
yuv)
build.gradle中配置
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
library.
yuvutils
# Links the target library to the log library
# included in the NDK.
yuv)
build.gradle中配置
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}