Android Kotlin Camera2中如何将Image.hardwareBuffer转为OpenGL ES 3.0纹理?
Android Kotlin + Camera2:将HardwareBuffer转为OpenGL ES 3.0纹理
一、无C++的替代方案(低性能)
如果对性能要求不高,可以绕过HardwareBuffer,直接从Image的像素平面读取数据并上传到纹理。这种方法依赖CPU拷贝像素,适合低分辨率场景,无需JNI开发:
val onImageAvailableListener = ImageReader.OnImageAvailableListener { reader -> val image = reader.acquireLatestImage() ?: return@OnImageAvailableListener try { val plane = image.planes[0] val buffer = plane.buffer val width = image.width val height = image.height val rowStride = plane.rowStride val pixelStride = plane.pixelStride // 处理YUV格式的像素数据(Camera2默认输出YUV_420_888) val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) val pixels = IntArray(width * height) buffer.position(0) // 逐行读取Y通道数据并转成RGB(简化处理,实际需完整YUV转RGB逻辑) for (y in 0 until height) { for (x in 0 until width) { val yValue = buffer.get(x * pixelStride).toInt() and 0xFF pixels[y * width + x] = 0xFF shl 24 or (yValue shl 16) or (yValue shl 8) or yValue } buffer.position(buffer.position() + rowStride - width * pixelStride) } bitmap.setPixels(pixels, 0, width, 0, 0, width, height) // 绑定并上传纹理 GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId) GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0) bitmap.recycle() } finally { image.close() } }
二、高效方案:JNI调用EGL扩展函数(必须用C++)
要实现HardwareBuffer到GLES纹理的零拷贝,必须使用你提到的EGL扩展函数,这些函数仅在C++层暴露,需要通过JNI对接。以下是完整示例:
1. Kotlin层代码
定义JNI方法并在Image回调中调用:
class HardwareBufferTextureHelper { init { System.loadLibrary("hardwarebuffer-texture") } external fun createTextureFromBuffer(buffer: HardwareBuffer): Int external fun releaseTexture(textureId: Int): Boolean external fun waitForTextureReady(textureId: Int): Boolean } // 初始化纹理助手 val textureHelper = HardwareBufferTextureHelper() var targetTextureId = -1 val onImageAvailableListener = ImageReader.OnImageAvailableListener { reader -> val image = reader.acquireLatestImage() ?: return@OnImageAvailableListener try { image.hardwareBuffer?.let { buffer -> // 创建或更新纹理 if (targetTextureId != -1) { textureHelper.releaseTexture(targetTextureId) } targetTextureId = textureHelper.createTextureFromBuffer(buffer) // 等待纹理数据就绪后再渲染 if (targetTextureId != -1 && textureHelper.waitForTextureReady(targetTextureId)) { // 绑定纹理到采样器,供着色器使用 GLES30.glActiveTexture(GLES30.GL_TEXTURE0) GLES30.glBindTexture(GLES30.GL_TEXTURE_EXTERNAL_OES, targetTextureId) // 执行绘制逻辑... } } } finally { image.close() } }
2. C++ JNI代码
创建hardwarebuffer-texture.cpp,实现EGL扩展函数调用:
#include <jni.h> #include <android/hardware_buffer.h> #include <EGL/egl.h> #include <EGL/eglext.h> #include <GLES3/gl3.h> #include <unordered_map> // 扩展函数指针 PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR = nullptr; PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR = nullptr; PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC eglGetNativeClientBufferANDROID = nullptr; PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = nullptr; PFNEGLCLIENTWAITSYNCKHRPROC eglClientWaitSyncKHR = nullptr; PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES = nullptr; // 存储纹理关联的EGL资源 struct TextureRes { EGLImageKHR image; EGLSyncKHR sync; }; static std::unordered_map<GLuint, TextureRes> textureMap; jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) return JNI_ERR; // 加载EGL扩展函数 eglCreateImageKHR = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); eglDestroyImageKHR = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); eglGetNativeClientBufferANDROID = (PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC)eglGetProcAddress("eglGetNativeClientBufferANDROID"); eglCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR"); eglClientWaitSyncKHR = (PFNEGLCLIENTWAITSYNCKHRPROC)eglGetProcAddress("eglClientWaitSyncKHR"); glEGLImageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); return JNI_VERSION_1_6; } extern "C" JNIEXPORT jint JNICALL Java_com_example_yourpackage_HardwareBufferTextureHelper_createTextureFromBuffer( JNIEnv* env, jobject, jobject buffer) { if (!eglCreateImageKHR || !glEGLImageTargetTexture2DOES) return -1; // 获取HardwareBuffer原生句柄 AHardwareBuffer* ahwb = AHardwareBuffer_fromHardwareBuffer(env, buffer); if (!ahwb) return -1; // 获取当前EGL上下文 EGLDisplay display = eglGetCurrentDisplay(); EGLContext context = eglGetCurrentContext(); if (display == EGL_NO_DISPLAY || context == EGL_NO_CONTEXT) { AHardwareBuffer_release(ahwb); return -1; } // 创建EGLImage EGLClientBuffer clientBuffer = eglGetNativeClientBufferANDROID(ahwb); EGLint attrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; EGLImageKHR eglImage = eglCreateImageKHR(display, context, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs); AHardwareBuffer_release(ahwb); if (eglImage == EGL_NO_IMAGE_KHR) return -1; // 创建GLES纹理 GLuint textureId; glGenTextures(1, &textureId); glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureId); glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 绑定EGLImage到纹理 glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, eglImage); // 创建同步对象,确保纹理数据就绪 EGLSyncKHR sync = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, nullptr); if (sync == EGL_NO_SYNC_KHR) { eglDestroyImageKHR(display, eglImage); glDeleteTextures(1, &textureId); return -1; } textureMap[textureId] = {eglImage, sync}; return textureId; } extern "C" JNIEXPORT jboolean JNICALL Java_com_example_yourpackage_HardwareBufferTextureHelper_releaseTexture( JNIEnv*, jobject, jint textureId) { auto it = textureMap.find(textureId); if (it == textureMap.end()) return JNI_FALSE; EGLDisplay display = eglGetCurrentDisplay(); eglDestroySyncKHR(display, it->second.sync); eglDestroyImageKHR(display, it->second.image); glDeleteTextures(1, &textureId); textureMap.erase(it); return JNI_TRUE; } extern "C" JNIEXPORT jboolean JNICALL Java_com_example_yourpackage_HardwareBufferTextureHelper_waitForTextureReady( JNIEnv*, jobject, jint textureId) { auto it = textureMap.find(textureId); if (it == textureMap.end()) return JNI_FALSE; EGLDisplay display = eglGetCurrentDisplay(); EGLint result = eglClientWaitSyncKHR(display, it->second.sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, 100000000); return (result == EGL_CONDITION_SATISFIED_KHR || result == EGL_ALREADY_SIGNALED_KHR) ? JNI_TRUE : JNI_FALSE; }
3. CMake配置(CMakeLists.txt)
cmake_minimum_required(VERSION 3.22.1) project("hardwarebuffer-texture") add_library( hardwarebuffer-texture SHARED hardwarebuffer-texture.cpp) find_library(log-lib log) target_link_libraries( hardwarebuffer-texture ${log-lib} EGL GLESv3 android)
4. 着色器适配
因为使用GL_TEXTURE_EXTERNAL_OES类型纹理,片段着色器需启用对应扩展,并处理YUV转RGB:
顶点着色器:
#version 300 es layout(location = 0) in vec4 aPosition; layout(location = 1) in vec2 aTexCoord; out vec2 vTexCoord; void main() { gl_Position = aPosition; vTexCoord = aTexCoord; }
片段着色器:
#version 300 es #extension GL_OES_EGL_image_external : require precision mediump float; in vec2 vTexCoord; uniform samplerExternalOES sTexture; out vec4 fragColor; // YUV_420_888转RGB矩阵 const mat3 YUV2RGB = mat3( 1.0, 1.0, 1.0, 0.0, -0.3441, 1.772, 1.402, -0.7141, 0.0 ); void main() { vec3 yuv = texture(sTexture, vTexCoord).rgb; yuv -= vec3(0.0627, 0.5020, 0.5020); // 格式偏移量 vec3 rgb = YUV2RGB * yuv; fragColor = vec4(rgb, 1.0); }
内容的提问来源于stack exchange,提问作者porente
相关产品推荐
相关产品推荐

