package com.android.example.cameraappxjava.util;
import android.graphics.ImageFormat;
import android.media.Image;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.Log;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* 修复后:YUV_420_888 自定义GL渲染器
*/
public class CameraGLRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "CameraGLRenderer";
private static final int TEXTURE_COUNT = 3; // Y/U/V 三个纹理
// ---------------------- OpenGL ES 2.0 核心配置 ----------------------
// 顶点着色器(全屏显示)
private static final String VERTEX_SHADER =
"attribute vec4 vPosition;\n" +
"attribute vec2 vTexCoord;\n" +
"varying vec2 texCoord;\n" +
"void main() {\n" +
" gl_Position = vPosition;\n" +
" texCoord = vTexCoord;\n" +
"}";
// 片段着色器(YUV转RGB)
private static final String FRAGMENT_SHADER =
"precision mediump float;\n" +
"varying vec2 texCoord;\n" +
"uniform sampler2D yTex;\n" +
"uniform sampler2D uTex;\n" +
"uniform sampler2D vTex;\n" +
"void main() {\n" +
" // YUV转RGB(BT.601标准)\n" +
" float y = texture2D(yTex, texCoord).r;\n" +
" float u = texture2D(uTex, texCoord).r - 0.5;\n" +
" float v = texture2D(vTex, texCoord).r - 0.5;\n" +
" float r = y + 1.402 * v;\n" +
" float g = y - 0.34414 * u - 0.71414 * v;\n" +
" float b = y + 1.772 * u;\n" +
" // 颜色范围限制(0.0~1.0)\n" +
" r = clamp(r, 0.0, 1.0);\n" +
" g = clamp(g, 0.0, 1.0);\n" +
" b = clamp(b, 0.0, 1.0);\n" +
" gl_FragColor = vec4(r, g, b, 1.0);\n" +
"}";
// 全屏顶点坐标(左手坐标系:左上→左下→右上→右下)
private static final float[] VERTEX_COORDS = {
-1.0f, 1.0f, 0.0f, // 左上
-1.0f, -1.0f, 0.0f, // 左下
1.0f, 1.0f, 0.0f, // 右上
1.0f, -1.0f, 0.0f // 右下
};
// 纹理坐标(适配竖屏:解决画面颠倒,与顶点坐标对应)
private static final float[] TEX_COORDS = {
0.0f, 1.0f, // 左上(对应顶点左上)
1.0f, 1.0f, // 左下(对应顶点左下)
0.0f, 0.0f, // 右上(对应顶点右上)
1.0f, 0.0f // 右下(对应顶点右下)
};
// ---------------------- 动态变量 ----------------------
private final SurfaceSizeCallback sizeCallback; // GL尺寸回调
private int shaderProgram; // 着色器程序ID
private int[] textureIds = new int[TEXTURE_COUNT]; // Y/U/V纹理ID
private FloatBuffer vertexBuffer; // 顶点坐标缓冲区
private FloatBuffer texCoordBuffer; // 纹理坐标缓冲区
// YUV数据(线程安全管理)
private final Object yuvLock = new Object();
private Image pendingImage; // 待处理的YUV图像
private byte[] yData, uData, vData; // 提取后的Y/U/V数据
private int yuvWidth, yuvHeight; // YUV图像尺寸
// 纹理尺寸记录(避免重复创建纹理)
private int yTexWidth = 0, yTexHeight = 0;
private int uvTexWidth = 0, uvTexHeight = 0;
// ---------------------- 构造方法(传入尺寸回调) ----------------------
public CameraGLRenderer(SurfaceSizeCallback callback) {
this.sizeCallback = callback;
}
// ---------------------- 对外接口 ----------------------
/**
* 设置待处理的YUV图像(从Camera2 ImageReader回调调用)
*/
public void setYUVData(Image image) {
if (image == null || image.getFormat() != ImageFormat.YUV_420_888) {
Log.w(TAG, "无效Image:格式非YUV_420_888或为空");
if (image != null) image.close();
return;
}
synchronized (yuvLock) {
// 关闭未处理的旧图像(避免内存泄漏)
if (pendingImage != null) {
pendingImage.close(); // 关闭旧图像
Log.d(TAG, "关闭未处理的旧Image");
}
pendingImage = image;
Log.e(TAG, "调用setYUVData,pendingImage:"+pendingImage);
}
}
/**
* 释放渲染器资源(Activity销毁时调用)
*/
public void release() {
synchronized (yuvLock) {
// 关闭待处理图像
if (pendingImage != null) {
pendingImage.close();
pendingImage = null;
}
// 清空YUV数据
yData = uData = vData = null;
yuvWidth = yuvHeight = 0;
}
// 释放OpenGL资源(必须在GL线程调用,此处通过GLSurfaceView队列)
GLES20.glDeleteTextures(TEXTURE_COUNT, textureIds, 0);
GLES20.glDeleteProgram(shaderProgram);
Log.d(TAG, "渲染器资源已释放");
}
// ---------------------- OpenGL生命周期回调 ----------------------
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.d(TAG, "onSurfaceCreated:初始化OpenGL");
// 初始化OpenGL状态
GLES20.glDisable(GLES20.GL_BLEND); // 关闭混合(避免透明)
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // 背景黑色
// 初始化坐标缓冲区(native内存,避免GC)
vertexBuffer = createFloatBuffer(VERTEX_COORDS);
texCoordBuffer = createFloatBuffer(TEX_COORDS);
// 编译着色器程序
shaderProgram = compileShaderProgram(VERTEX_SHADER, FRAGMENT_SHADER);
if (shaderProgram == 0) {
Log.e(TAG, "着色器程序创建失败,预览不可用");
return;
}
// 创建Y/U/V三个纹理
GLES20.glGenTextures(TEXTURE_COUNT, textureIds, 0);
initTexture(textureIds[0]); // Y纹理
initTexture(textureIds[1]); // U纹理
initTexture(textureIds[2]); // V纹理
// 检查OpenGL错误
int glError = GLES20.glGetError();
if (glError != GLES20.GL_NO_ERROR) {
Log.e(TAG, "onSurfaceCreated OpenGL错误: " + glError);
}
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
Log.d(TAG, "onSurfaceChanged:GL尺寸=" + width + "x" + height);
// 设置视口(全屏显示)
GLES20.glViewport(0, 0, width, height);
// 通知Activity更新相机预览尺寸
if (sizeCallback != null) {
sizeCallback.onSurfaceSizeChanged(width, height);
}
// 重置纹理尺寸记录(避免尺寸变化导致纹理不匹配)
yTexWidth = yTexHeight = uvTexWidth = uvTexHeight = 0;
}
@Override
public void onDrawFrame(GL10 gl) {
Log.e(TAG, "调用着色器onDrawFrame");
// 1. 处理待处理的YUV数据
boolean hasNewData = processPendingYUV();
if (!hasNewData) {
// 无新数据:清空屏幕(黑色背景)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
return;
}
// 2. 检查着色器程序是否有效
if (shaderProgram == 0 || textureIds == null) {
Log.e(TAG, "着色器程序或纹理无效,跳过渲染");
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
return;
}
// 3. 清空上一帧
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 4. 使用着色器程序
GLES20.glUseProgram(shaderProgram);
// 5. 上传Y/U/V纹理数据
uploadTexture(textureIds[0], yData, yuvWidth, yuvHeight, true); // Y纹理
uploadTexture(textureIds[1], uData, uvTexWidth, uvTexHeight, false); // U纹理
uploadTexture(textureIds[2], vData, uvTexWidth, uvTexHeight, false); // V纹理
// 6. 绑定纹理到着色器采样器
bindTexturesToSamplers();
// 7. 传递顶点坐标和纹理坐标
passVertexAndTexCoord();
// 8. 绘制(三角形带:4个顶点→2个三角形→全屏)
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX_COORDS.length / 3);
// 9. 禁用顶点属性(避免后续干扰)
int vPositionLoc = GLES20.glGetAttribLocation(shaderProgram, "vPosition");
int vTexCoordLoc = GLES20.glGetAttribLocation(shaderProgram, "vTexCoord");
GLES20.glDisableVertexAttribArray(vPositionLoc);
GLES20.glDisableVertexAttribArray(vTexCoordLoc);
// 检查渲染错误
int glError = GLES20.glGetError();
if (glError != GLES20.GL_NO_ERROR) {
Log.e(TAG, "onDrawFrame OpenGL错误: " + glError);
}
}
// ---------------------- OpenGL辅助方法 ----------------------
/**
* 创建Float缓冲区(native内存,避免Java堆内存拷贝)
*/
private FloatBuffer createFloatBuffer(float[] data) {
if (data == null || data.length == 0) return null;
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4); // float占4字节
byteBuffer.order(ByteOrder.nativeOrder()); // 匹配 native 字节序
FloatBuffer floatBuffer = byteBuffer.asFloatBuffer();
floatBuffer.put(data);
floatBuffer.position(0); // 重置读取位置
return floatBuffer;
}
/**
* 编译着色器程序(顶点+片段)
*/
private int compileShaderProgram(String vertexCode, String fragmentCode) {
// 1. 编译顶点着色器
int vertexShader = compileSingleShader(GLES20.GL_VERTEX_SHADER, vertexCode);
if (vertexShader == 0) return 0;
// 2. 编译片段着色器
int fragmentShader = compileSingleShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode);
if (fragmentShader == 0) {
GLES20.glDeleteShader(vertexShader); // 清理已创建的顶点着色器
return 0;
}
// 3. 链接着色器程序
int program = GLES20.glCreateProgram();
GLES20.glAttachShader(program, vertexShader);
GLES20.glAttachShader(program, fragmentShader);
GLES20.glLinkProgram(program);
// 4. 检查链接错误
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
String errorLog = GLES20.glGetProgramInfoLog(program);
Log.e(TAG, "着色器程序链接失败: " + errorLog);
GLES20.glDeleteProgram(program);
program = 0;
}
// 5. 清理临时着色器(链接后不再需要)
GLES20.glDeleteShader(vertexShader);
GLES20.glDeleteShader(fragmentShader);
return program;
}
/**
* 编译单个着色器(顶点或片段)
*/
private int compileSingleShader(int shaderType, String shaderCode) {
int shader = GLES20.glCreateShader(shaderType);
if (shader == 0) {
Log.e(TAG, "创建着色器失败,类型: " + (shaderType == GLES20.GL_VERTEX_SHADER ? "顶点" : "片段"));
return 0;
}
// 加载着色器代码并编译
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
// 检查编译错误
int[] compileStatus = new int[1];
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
if (compileStatus[0] != GLES20.GL_TRUE) {
String errorLog = GLES20.glGetShaderInfoLog(shader);
Log.e(TAG, (shaderType == GLES20.GL_VERTEX_SHADER ? "顶点" : "片段") + "着色器编译失败: " + errorLog);
GLES20.glDeleteShader(shader);
shader = 0;
}
return shader;
}
/**
* 初始化纹理参数(Y/U/V通用)
*/
private void initTexture(int textureId) {
if (textureId == 0) return;
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
// 纹理过滤:线性插值(画质更平滑)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
// 纹理包裹:边缘拉伸(避免黑边)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
// 解绑纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
}
/**
* 处理待处理的YUV图像(提取Y/U/V数据,考虑内存对齐)
*/
private boolean processPendingYUV() {
Image image = null;
Log.e(TAG, "调用着色器onDrawFrame2,pendingImage:"+pendingImage);
synchronized (yuvLock) {
if (pendingImage == null) {
return false; // 无新数据
}
Log.e(TAG, "调用着色器onDrawFrame4");
// 取出待处理图像(释放锁,避免长时间占用)
image = pendingImage;
pendingImage = null;
Log.e(TAG, "调用着色器onDrawFrame4");
}
Log.e(TAG, "调用着色器onDrawFrame3");
try {
Log.e(TAG,"image是否可用:"+image);
// 1. 获取YUV图像尺寸
yuvWidth = image.getWidth();
yuvHeight = image.getHeight();
Image.Plane[] planes = image.getPlanes();
if (planes.length < 3) {
Log.e(TAG, "YUV平面数量不足3,无法提取数据");
return false;
}
// 2. 提取Y数据(Plane 0:Y通道,pixelStride=1,rowStride可能有对齐)
Image.Plane yPlane = planes[0];
int yRowStride = yPlane.getRowStride();
int yPixelStride = yPlane.getPixelStride();
ByteBuffer yBuffer = yPlane.getBuffer();
yData = extractPlaneData(yBuffer, yRowStride, yPixelStride, yuvWidth, yuvHeight);
if (yData == null || yData.length != yuvWidth * yuvHeight) {
Log.e(TAG, "Y数据提取失败,长度不匹配: " + (yData != null ? yData.length : 0) + " vs " + (yuvWidth * yuvHeight));
return false;
}
// 3. 提取U/V数据(Plane 1:U通道,Plane 2:V通道,或交错)
Image.Plane uPlane = planes[1];
Image.Plane vPlane = planes[2];
int uvRowStride = uPlane.getRowStride();
int uvPixelStride = uPlane.getPixelStride();
int uvWidth = yuvWidth / 2; // YUV_420:U/V尺寸是Y的1/2
int uvHeight = yuvHeight / 2;
uvTexWidth = uvWidth;
uvTexHeight = uvHeight;
// 处理Planar(U/V分离)或Semi-Planar(UV交错)
if (uvPixelStride == 2) {
// Semi-Planar(UV交错,如NV21):UPlane包含UV数据,VPlane为空
ByteBuffer uvBuffer = uPlane.getBuffer();
uData = new byte[uvWidth * uvHeight];
vData = new byte[uvWidth * uvHeight];
// 每2字节对应一个U和一个V(U在前,V在后)
for (int row = 0; row < uvHeight; row++) {
for (int col = 0; col < uvWidth; col++) {
int pos = row * uvRowStride + col * uvPixelStride;
if (pos >= uvBuffer.limit() - 1) { // 需要访问pos和pos+1,所以要保证pos+1 < limit
break;
}
vData[row * uvWidth + col] = uvBuffer.get(pos);
uData[row * uvWidth + col] = uvBuffer.get(pos + 1);
}
}
} else {
// Planar(U/V分离,如I420):U和V各自在独立Plane
ByteBuffer uBuffer = uPlane.getBuffer();
ByteBuffer vBuffer = vPlane.getBuffer();
uData = extractPlaneData(uBuffer, uvRowStride, uvPixelStride, uvWidth, uvHeight);
vData = extractPlaneData(vBuffer, uvRowStride, uvPixelStride, uvWidth, uvHeight);
}
// 4. 验证U/V数据长度
if (uData == null || vData == null || uData.length != uvWidth * uvHeight || vData.length != uvWidth * uvHeight) {
Log.e(TAG, "U/V数据提取失败,长度不匹配");
return false;
}
Log.d(TAG, "YUV数据处理成功: " + yuvWidth + "x" + yuvHeight + ",Y长度=" + yData.length + ",U/V长度=" + uData.length);
return true;
} catch (Exception e) {
Log.e(TAG, "处理YUV数据异常: " + e.getMessage(), e);
return false;
} finally {
// 必须关闭Image,否则内存泄漏
if (image != null) {
image.close();
}
}
}
/**
* 提取平面数据(处理rowStride和pixelStride,避免读取padding字节)
*/
private byte[] extractPlaneData(ByteBuffer buffer, int rowStride, int pixelStride, int width, int height) {
if (buffer == null || rowStride <= 0 || pixelStride <= 0 || width <= 0 || height <= 0) {
Log.w(TAG, "提取平面数据参数无效");
return null;
}
byte[] data = new byte[width * height];
int dataIdx = 0;
// 按行读取(跳过rowStride中的padding字节)
for (int row = 0; row < height; row++) {
// 每行的起始位置
int bufferRowStart = row * rowStride;
// 读取当前行的有效数据(width个像素,每个像素pixelStride字节)
for (int col = 0; col < width; col++) {
int bufferPos = bufferRowStart + col * pixelStride;
data[dataIdx++] = buffer.get(bufferPos);
}
}
return data;
}
/**
* 上传数据到纹理(首次创建纹理,后续更新数据)
*/
private void uploadTexture(int textureId, byte[] data, int width, int height, boolean isYTexture) {
if (textureId == 0 || data == null || width <= 0 || height <= 0) {
Log.w(TAG, "上传纹理参数无效");
return;
}
// 绑定纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
// 设置像素对齐(YUV数据是1字节对齐,默认是4字节,必须修改)
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
// 检查纹理是否已创建(尺寸匹配则更新,否则重新创建)
boolean textureCreated = false;
if (isYTexture) {
textureCreated = (yTexWidth == width && yTexHeight == height);
} else {
textureCreated = (uvTexWidth == width && uvTexHeight == height);
}
ByteBuffer dataBuffer = ByteBuffer.wrap(data);
if (!textureCreated) {
// 首次创建纹理(GL_LUMINANCE:单通道亮度数据)
GLES20.glTexImage2D(
GLES20.GL_TEXTURE_2D, 0,
GLES20.GL_LUMINANCE,
width, height, 0,
GLES20.GL_LUMINANCE,
GLES20.GL_UNSIGNED_BYTE,
dataBuffer
);
// 更新纹理尺寸记录
if (isYTexture) {
yTexWidth = width;
yTexHeight = height;
} else {
uvTexWidth = width;
uvTexHeight = height;
}
Log.d(TAG, "创建纹理: " + (isYTexture ? "Y" : "UV") + ",尺寸=" + width + "x" + height);
} else {
// 纹理已存在,更新数据(只更新像素,不重新创建纹理)
GLES20.glTexSubImage2D(
GLES20.GL_TEXTURE_2D, 0,
0, 0, // 起始坐标(x,y)
width, height,
GLES20.GL_LUMINANCE,
GLES20.GL_UNSIGNED_BYTE,
dataBuffer
);
}
// 解绑纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
}
/**
* 绑定纹理到着色器的采样器(yTex/uTex/vTex)
*/
private void bindTexturesToSamplers() {
// 绑定Y纹理到TEXTURE0,对应着色器的yTex
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[0]);
int yTexLoc = GLES20.glGetUniformLocation(shaderProgram, "yTex");
GLES20.glUniform1i(yTexLoc, 0);
// 绑定U纹理到TEXTURE1,对应着色器的uTex
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[1]);
int uTexLoc = GLES20.glGetUniformLocation(shaderProgram, "uTex");
GLES20.glUniform1i(uTexLoc, 1);
// 绑定V纹理到TEXTURE2,对应着色器的vTex
GLES20.glActiveTexture(GLES20.GL_TEXTURE2);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[2]);
int vTexLoc = GLES20.glGetUniformLocation(shaderProgram, "vTex");
GLES20.glUniform1i(vTexLoc, 2);
// 检查采样器位置是否有效
if (yTexLoc == -1 || uTexLoc == -1 || vTexLoc == -1) {
Log.e(TAG, "着色器采样器位置无效: y=" + yTexLoc + ", u=" + uTexLoc + ", v=" + vTexLoc);
}
}
/**
* 传递顶点坐标和纹理坐标到着色器
*/
private void passVertexAndTexCoord() {
// 传递顶点坐标(vPosition)
int vPositionLoc = GLES20.glGetAttribLocation(shaderProgram, "vPosition");
GLES20.glEnableVertexAttribArray(vPositionLoc);
GLES20.glVertexAttribPointer(
vPositionLoc,
3, // 每个顶点3个坐标(x,y,z)
GLES20.GL_FLOAT,
false, // 不归一化
3 * 4, // 顶点步长(3个float,每个4字节)
vertexBuffer
);
// 传递纹理坐标(vTexCoord)
int vTexCoordLoc = GLES20.glGetAttribLocation(shaderProgram, "vTexCoord");
GLES20.glEnableVertexAttribArray(vTexCoordLoc);
GLES20.glVertexAttribPointer(
vTexCoordLoc,
2, // 每个纹理坐标2个值(s,t)
GLES20.GL_FLOAT,
false,
2 * 4, // 纹理坐标步长(2个float,每个4字节)
texCoordBuffer
);
// 检查坐标位置是否有效
if (vPositionLoc == -1 || vTexCoordLoc == -1) {
Log.e(TAG, "着色器坐标位置无效: vPosition=" + vPositionLoc + ", vTexCoord=" + vTexCoordLoc);
}
}
// ---------------------- GLSurface尺寸回调接口 ----------------------
public interface SurfaceSizeCallback {
void onSurfaceSizeChanged(int width, int height);
}
}
你直接来修改优化性能
最新发布