前言
CameraX 是一个 Jetpack 支持库,旨在帮助您简化相机应用的开发工作。笔者看了下网上关于CameraX的资料虽然很多,但是很多基本上都是官网资料的翻版,学习的价值很没有直接看官网的高。
也有些博客介绍了CameraX结合OpenGL渲染的的例子,但好像都建立在Preview
类的setOnPreviewOutputUpdateListener
这个方法中进行处理,但是笔者更新CameraX版本之后发现setOnPreviewOutputUpdateListener
这个
方法直接没了,完犊子了...
当然本文所介绍的方法随着CameraX的发展也会过时,但也希望能起到一点抛砖引玉的作用。。。。
show me the code
首先自定义一个OpenGL的渲染View,继承于GLSurfaceView,GLCameraView.java:
public class GLCameraView extends GLSurfaceView implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
private static final String LOG_TAG = "OpenGLCameraX";
private Executor executor = Executors.newSingleThreadExecutor();
private int textureId;
private SurfaceTexture surfaceTexture;
private int vPosition;
private int vCoord;
private int programId;
private int textureMatrixId;
private float[] textureMatrix = new float[16];
protected FloatBuffer mGLVertexBuffer;
protected FloatBuffer mGLTextureBuffer;
public GLCameraView(Context context) {
this(context, null);
}
public GLCameraView(Context context, AttributeSet attrs) {
super(context, attrs);
setEGLContextClientVersion(2);
setRenderer(this);
// 设置非连续渲染
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
@SuppressLint("UnsafeExperimentalUsageError")
public void attachPreview(Preview preview) {
preview.setSurfaceProvider(new Preview.SurfaceProvider() {
@Override
public void onSurfaceRequested(@NonNull SurfaceRequest request) {
Surface surface = new Surface(surfaceTexture);
request.provideSurface(surface, executor, new Consumer<SurfaceRequest.Result>() {
@Override
public void accept(SurfaceRequest.Result result) {
surface.release();
surfaceTexture.release();
Log.v(LOG_TAG, "--accept------");
}
});
}
});
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
int[] ids = new int[1];
// OpenGL相关
GLES20.glGenTextures(1, ids, 0);
textureId = ids[0];
surfaceTexture = new SurfaceTexture(textureId);
surfaceTexture.setOnFrameAvailableListener(this::onFrameAvailable);
String vertexShader = OpenGLUtils.readRawTextFile(getContext(), R.raw.camera_vertex);
String fragmentShader = OpenGLUtils.readRawTextFile(getContext(), R.raw.camera_frag);
programId = OpenGLUtils.loadProgram(vertexShader, fragmentShader);
vPosition = GLES20.glGetAttribLocation(programId, "vPosition");
vCoord = GLES20.glGetAttribLocation(programId, "vCoord");
textureMatrixId = GLES20.glGetUniformLocation(programId, "textureMatrix");
// 4个顶点,每个顶点有两个浮点型,每个浮点型占4个字节
mGLVertexBuffer = ByteBuffer.allocateDirect(4 * 4 * 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
mGLVertexBuffer.clear();
// 顶点坐标
float[] VERTEX = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
};
mGLVertexBuffer.put(VERTEX);
// 纹理坐标
mGLTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
mGLTextureBuffer.clear();
// 正常的纹理贴图坐标,但是贴出的图是上下颠倒的,所以需要修改一下
// float[] TEXTURE = {
// 0.0f, 1.0f,
// 1.0f, 1.0f,
// 0.0f, 0.0f,
// 1.0f, 0.0f
// };
// 修复上下颠倒后的纹理贴图坐标
float[] TEXTURE = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f
};
mGLTextureBuffer.put(TEXTURE);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
// 清屏
GLES20.glClearColor(1, 0, 0, 0);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 更新纹理
surfaceTexture.updateTexImage();
surfaceTexture.getTransformMatrix(textureMatrix);
GLES20.glUseProgram(programId);
//变换矩阵
GLES20.glUniformMatrix4fv(textureMatrixId, 1, false, textureMatrix, 0);
// 传递坐标数据
mGLVertexBuffer.position(0);
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, mGLVertexBuffer);
GLES20.glEnableVertexAttribArray(vPosition);
// 传递纹理坐标
mGLTextureBuffer.position(0);
GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, mGLTextureBuffer);
GLES20.glEnableVertexAttribArray(vCoord);
//绑定纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
// 解绑纹理
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
}
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
requestRender();
}
}
编写顶点着色器camera_vertex.glsl:
attribute vec4 vPosition;
attribute vec4 vCoord;
varying vec2 aCoord;
uniform mat4 textureMatrix;
void main(){
gl_Position = vPosition;
aCoord = (textureMatrix * vCoord).xy;
}
编写片段着色器camera_frag.glsl:
#extension GL_OES_EGL_image_external : require
//SurfaceTexture比较特殊
//float数据是什么精度的
precision mediump float;
//采样点的坐标
varying vec2 aCoord;
//采样器
uniform samplerExternalOES vTexture;
void main(){
//变量 接收像素值
// texture2D:采样器 采集 aCoord的像素
//赋值给 gl_FragColor 就可以了
gl_FragColor = texture2D(vTexture,aCoord);
}
加载及编译着色器程序OpenGLUtils.java:
public static String readRawTextFile(Context context, int rawId) {
InputStream is = context.getResources().openRawResource(rawId);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String line;
StringBuilder sb = new StringBuilder();
try {
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
} catch (Exception e) {
e.printStackTrace();
}
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
/**
* 价值着色器并编译成GPU程序
* @param vSource
* @param fSource
* @return
*/
public static int loadProgram(String vSource, String fSource){
/**
* 顶点着色器
*/
int vShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
//加载着色器代码
GLES20.glShaderSource(vShader,vSource);
//编译(配置)
GLES20.glCompileShader(vShader);
//查看配置 是否成功
int[] status = new int[1];
GLES20.glGetShaderiv(vShader, GLES20.GL_COMPILE_STATUS,status,0);
if(status[0] != GLES20.GL_TRUE){
//失败
throw new IllegalStateException("load vertex shader:"+ GLES20.glGetShaderInfoLog(vShader));
}
/**
* 片元着色器
* 流程和上面一样
*/
int fShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
//加载着色器代码
GLES20.glShaderSource(fShader,fSource);
//编译(配置)
GLES20.glCompileShader(fShader);
//查看配置 是否成功
GLES20.glGetShaderiv(fShader, GLES20.GL_COMPILE_STATUS,status,0);
if(status[0] != GLES20.GL_TRUE){
//失败
throw new IllegalStateException("load fragment shader:"+ GLES20.glGetShaderInfoLog(vShader));
}
/**
* 创建着色器程序
*/
int program = GLES20.glCreateProgram();
//绑定顶点和片元
GLES20.glAttachShader(program,vShader);
GLES20.glAttachShader(program,fShader);
//链接着色器程序
GLES20.glLinkProgram(program);
//获得状态
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS,status,0);
if(status[0] != GLES20.GL_TRUE){
throw new IllegalStateException("link program:"+ GLES20.glGetProgramInfoLog(program));
}
GLES20.glDeleteShader(vShader);
GLES20.glDeleteShader(fShader);
return program;
}
结合CameraX用起来MainActivity.java:
public class MainActivity extends AppCompatActivity {
private GLCameraView camera_preview;
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
camera_preview = findViewById(R.id.camera_preview);
if (allPermissionsGranted()) {
startCamera();
} else {
ActivityCompat.requestPermissions(
this, new String[]{Manifest.permission.CAMERA}, 100);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 100) {
if (allPermissionsGranted()) {
startCamera();
} else {
Toast.makeText(this, "没有相机权限", Toast.LENGTH_LONG).show();
}
}
}
private boolean allPermissionsGranted() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
}
private void startCamera() {
Executor executor = Executors.newSingleThreadExecutor();
ListenableFuture<ProcessCameraProvider> processCameraProvider = ProcessCameraProvider.getInstance(this);
processCameraProvider.addListener(new Runnable() {
@Override
public void run() {
try {
ProcessCameraProvider cameraProvider = processCameraProvider.get();
Preview preview = new Preview.Builder()
.build();
camera_preview.attachPreview(preview);
cameraProvider.unbindAll();
cameraProvider.bindToLifecycle(MainActivity.this, CameraSelector.DEFAULT_BACK_CAMERA,preview);
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, ContextCompat.getMainExecutor(this));
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
关键代码点加了点注释,打完收工。
举一反三
1、目前的预览竖屏看起来挺正常的,但是横屏的时候预览界面明显发生变形了,这个问题怎么解决呢?有兴趣的童鞋可以了解下OpenGL的矩阵变换的相关知识,利用矩阵变换来解决这个问题。
2、预览使用的默认的比较低的分辨率,如果需要预览高分辨率需要怎么修改呢?
3、笔者在预览的时候测试了一下帧率,大概是每秒26帧作用,如果要做到预览每秒60帧又要怎么改呢?
4、入门OpenGL的童鞋应该知道VBO
、 VAO
、FBO
等相关概念,想进一步深入学习的童鞋也可以将VBO
、 VAO
、FBO
与CameraX结合起来做一个实践。
哔哔两句
CameraX虽然已经提出了两年多了,但是一直还没有发布正式版,貌似最近发布了一个beat版本,而且笔者在学习的过程中发现相关的api也一直在变化。
所以笔者觉得CameraX是未来,但不是现在。
虽然说CameraX还不稳定,甚至可能还存在着各种各样的问题,但是机会更加青睐的是那些未雨绸缪的人,持续关注学习CameraX的演进,本身就像跟着谷歌工程师学习的一个过程。
参考资料:《谷歌官方》
vx公号:思想觉悟
关注我,一起进步,人生不止coding!!!
网友评论