You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 10:54:55