自定义GLSurfaceView+Opengl es(使用了VBO)实现摄像头预览

https://blog.csdn.net/llxyy299/article/details/82659683这篇文章中记录了自己创建EGL环境,自己实现一个GLSurfaceView,但是还没有记录怎么用,这篇文章来记录怎么使用自定义的GLSurfaceView。

1、首先创建一个CameraHelper类,里面封装了安卓相机的一些api:


 
 
  1. package com.leilu.mycamera.camera;
  2. import android.graphics.SurfaceTexture;
  3. import android.hardware.Camera;
  4. import android.util.Log;
  5. import java.io.IOException;
  6. import java.util.List;
  7. /**
  8. * Created by ll on 2018/9/13.
  9. */
  10. public class CameraHelper {
  11. private Camera mCamera;
  12. private int mCameraId = 0;
  13. private int mDesireWidth, mDesireHeight;
  14. private boolean mIsInit;
  15. private SurfaceTexture mSurfaceTexture;
  16. private boolean mIsPortrait = true;
  17. public CameraHelper() {
  18. }
  19. public boolean init(int cameraId, int desireWidth, int desireHeight, SurfaceTexture surfaceTexture) {
  20. if (mCamera == null) {
  21. checkCameraID(cameraId);
  22. mCameraId = cameraId;
  23. mDesireWidth = desireWidth;
  24. mDesireHeight = desireHeight;
  25. mSurfaceTexture = surfaceTexture;
  26. mCamera = Camera.open(mCameraId);
  27. Camera.Parameters parameters = mCamera.getParameters();
  28. Camera.Size picSize = getFitSize(desireWidth, desireHeight, parameters.getSupportedPictureSizes());
  29. if (picSize != null) {
  30. parameters.setPictureSize(picSize.width, picSize.height);
  31. }
  32. Log.i( "==", "picSize:" + picSize.width + " " + picSize.height);
  33. Camera.Size preSize = getFitSize(desireWidth, desireHeight, parameters.getSupportedPreviewSizes());
  34. if (preSize != null) {
  35. parameters.setPreviewSize(preSize.width, preSize.height);
  36. }
  37. Log.i( "==", "preSize:" + preSize.width + " " + preSize.height);
  38. mCamera.setParameters(parameters);
  39. try {
  40. mCamera.setPreviewTexture(surfaceTexture);
  41. } catch (IOException e) {
  42. e.printStackTrace();
  43. return false;
  44. }
  45. }
  46. mIsInit = true;
  47. return true;
  48. }
  49. public boolean startPreview() {
  50. if (!mIsInit || mCamera == null) {
  51. return false;
  52. }
  53. mCamera.startPreview();
  54. return true;
  55. }
  56. public boolean stopPreview() {
  57. if (!mIsInit || mCamera == null) {
  58. return false;
  59. }
  60. mCamera.stopPreview();
  61. return true;
  62. }
  63. public boolean switchCamera(int cameraId) {
  64. release();
  65. if (!init(cameraId, mDesireWidth, mDesireHeight, mSurfaceTexture)) {
  66. return false;
  67. }
  68. startPreview();
  69. return true;
  70. }
  71. private Camera. Size getFitSize(int desireWidth, int desireHeight,
  72. List<Camera.Size> preSizeList) {
  73. int reqTmpWidth;
  74. int reqTmpHeight;
  75. // 当屏幕为垂直的时候需要把宽高值进行调换,保证宽大于高
  76. if (mIsPortrait) {
  77. reqTmpWidth = desireHeight;
  78. reqTmpHeight = desireWidth;
  79. } else {
  80. reqTmpWidth = desireWidth;
  81. reqTmpHeight = desireHeight;
  82. }
  83. //先查找preview中是否存在与控件相同宽高的尺寸
  84. for (Camera.Size size : preSizeList) {
  85. if ((size.width == reqTmpWidth) && (size.height == reqTmpHeight)) {
  86. return size;
  87. }
  88. }
  89. // 得到与传入的宽高比最接近的size
  90. float reqRatio = (( float) reqTmpWidth) / reqTmpHeight;
  91. float curRatio, deltaRatio;
  92. float deltaRatioMin = Float.MAX_VALUE;
  93. Camera.Size retSize = null;
  94. for (Camera.Size size : preSizeList) {
  95. curRatio = (( float) size.width) / size.height;
  96. deltaRatio = Math.abs(reqRatio - curRatio);
  97. if (deltaRatio < deltaRatioMin) {
  98. deltaRatioMin = deltaRatio;
  99. retSize = size;
  100. }
  101. }
  102. return retSize;
  103. }
  104. private void checkCameraID(int cameraId) {
  105. int count = Camera.getNumberOfCameras();
  106. if (cameraId < 0 || cameraId >= count) {
  107. throw new IllegalArgumentException( "Nonsupport cameraID !");
  108. }
  109. }
  110. public void release() {
  111. stopPreview();
  112. if (mCamera != null) {
  113. mCamera.release();
  114. mCamera = null;
  115. }
  116. mIsInit = false;
  117. }
  118. }

2、 创建一个CameraRender类来实现Opengl ES的渲染代码:


 
 
  1. package com.leilu.mycamera.camera;
  2. import android.content.Context;
  3. import android.graphics.SurfaceTexture;
  4. import android.opengl.GLES11Ext;
  5. import android.opengl.GLES20;
  6. import android.opengl.Matrix;
  7. import com.leilu.floatwindow.R;
  8. import com.leilu.floatwindow.ShaderUtil;
  9. import java.nio.ByteBuffer;
  10. import java.nio.ByteOrder;
  11. import java.nio.FloatBuffer;
  12. /**
  13. * Created by ll on 2018/9/13.
  14. */
  15. public class CameraRender {
  16. private static final float[] VERTEX_POSITION = {
  17. - 1, - 1,
  18. 1, - 1,
  19. - 1, 1,
  20. 1, 1
  21. };
  22. private static final float[] FRAGMENT_POSITION = {
  23. 0, 1,
  24. 1, 1,
  25. 0, 0,
  26. 1, 0
  27. };
  28. private SurfaceTexture mSurfaceTexture;
  29. private int mProgram;
  30. private int mExtTextureHandle;
  31. private int mTexturePositionHandle;
  32. private int mVertexPositionHandle;
  33. private int mProjectionMatrixHandle;
  34. private int[] mVertexBufferIds;
  35. private int[] mFragmentBufferIds;
  36. private int[] mExtTextureIds;
  37. private float[] mProjectionMatrix = new float[ 16];
  38. private Context mContext;
  39. public CameraRender(Context context) {
  40. mContext = context.getApplicationContext();
  41. Matrix.setIdentityM(mProjectionMatrix, 0);
  42. }
  43. public void init(SurfaceTexture.OnFrameAvailableListener onFrameAvailable) {
  44. if (mSurfaceTexture == null) {
  45. mProgram = ShaderUtil.createProgram(ShaderUtil.getRawResource(mContext, R.raw.camera_vertex_shader),
  46. ShaderUtil.getRawResource(mContext, R.raw.camera_fragment_shader));
  47. if (mProgram == - 1) {
  48. return;
  49. }
  50. GLES20.glUseProgram(mProgram);
  51. // 获取纹理句柄
  52. mExtTextureHandle = GLES20.glGetAttribLocation(mProgram, "sTexture");
  53. mTexturePositionHandle = GLES20.glGetAttribLocation(mProgram, "texturePosition");
  54. mVertexPositionHandle = GLES20.glGetAttribLocation(mProgram, "vertexPosition");
  55. mProjectionMatrixHandle = GLES20.glGetUniformLocation(mProgram, "projectionMatrix");
  56. // 创建vbo
  57. mVertexBufferIds = createVBO(VERTEX_POSITION);
  58. mFragmentBufferIds = createVBO(FRAGMENT_POSITION);
  59. // 创建纹理
  60. mExtTextureIds = createTexture();
  61. mSurfaceTexture = new SurfaceTexture(mExtTextureIds[ 0]);
  62. mSurfaceTexture.setOnFrameAvailableListener(onFrameAvailable);
  63. }
  64. }
  65. private int[] createVBO( float[] datas) {
  66. FloatBuffer floatBuffer = ByteBuffer.allocateDirect(datas.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
  67. floatBuffer.put(datas);
  68. floatBuffer.position( 0);
  69. int[] vbo = new int[ 1];
  70. GLES20.glGenBuffers(vbo.length, vbo, 0);
  71. GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo[ 0]);
  72. GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, datas.length * 4, null, GLES20.GL_STATIC_DRAW);
  73. GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, datas.length * 4, floatBuffer);
  74. GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
  75. floatBuffer.clear();
  76. return vbo;
  77. }
  78. private int[] createTexture() {
  79. int[] textures = new int[ 1];
  80. GLES20.glGenTextures(textures.length, textures, 0);
  81. GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[ 0]);
  82. GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
  83. GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
  84. GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
  85. GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
  86. GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
  87. return textures;
  88. }
  89. public void setViewPort(int width, int height) {
  90. GLES20.glViewport( 0, 0, width, height);
  91. }
  92. public void setProjectionMatrix(boolean isBack) {
  93. Matrix.setIdentityM(mProjectionMatrix, 0);
  94. if (isBack) {
  95. Matrix.setRotateM(mProjectionMatrix, 0, 270, 0, 0, 1);
  96. } else {
  97. Matrix.setRotateM(mProjectionMatrix, 0, 90, 0, 0, 1);
  98. }
  99. }
  100. public void draw() {
  101. if (mProgram == - 1) {
  102. return;
  103. }
  104. mSurfaceTexture.updateTexImage();
  105. // 矩阵
  106. GLES20.glUniformMatrix4fv(mProjectionMatrixHandle, 1, false, mProjectionMatrix, 0);
  107. // 绑定顶点坐标
  108. GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVertexBufferIds[ 0]);
  109. GLES20.glEnableVertexAttribArray(mVertexPositionHandle);
  110. GLES20.glVertexAttribPointer(mVertexPositionHandle, 2, GLES20.GL_FLOAT, false, 0, 0);
  111. GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
  112. // 绑定片元坐标
  113. GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mFragmentBufferIds[ 0]);
  114. GLES20.glEnableVertexAttribArray(mTexturePositionHandle);
  115. GLES20.glVertexAttribPointer(mTexturePositionHandle, 2, GLES20.GL_FLOAT, false, 0, 0);
  116. GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
  117. // 绑定纹理
  118. GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
  119. GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mExtTextureIds[ 0]);
  120. GLES20.glUniform1f(mExtTextureHandle, 0);
  121. // 绘制
  122. GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
  123. GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
  124. GLES20.glDisableVertexAttribArray(mVertexPositionHandle);
  125. GLES20.glDisableVertexAttribArray(mTexturePositionHandle);
  126. }
  127. public SurfaceTexture getSurfaceTexture() {
  128. return mSurfaceTexture;
  129. }
  130. public void release() {
  131. if (mExtTextureIds != null && mExtTextureIds[ 0] != 0) {
  132. GLES20.glDeleteTextures( 1, mExtTextureIds, 0);
  133. }
  134. if (mFragmentBufferIds != null && mFragmentBufferIds[ 0] != 0) {
  135. GLES20.glDeleteBuffers( 1, mFragmentBufferIds, 0);
  136. }
  137. if (mVertexBufferIds != null && mVertexBufferIds[ 0] != 0) {
  138. GLES20.glDeleteBuffers( 1, mVertexBufferIds, 0);
  139. }
  140. GLES20.glDeleteProgram(mProgram);
  141. }
  142. }

 3、创建一个CameraView类,该类直接继承了自定义的GLSurfaceView:


 
 
  1. package com.leilu.mycamera.camera;
  2. import android.content.Context;
  3. import android.graphics.SurfaceTexture;
  4. import android.util.AttributeSet;
  5. import com.leilu.mycamera.EGLThread;
  6. import com.leilu.mycamera.MyGLSurfaceView;
  7. import static android.opengl.GLES10.glClear;
  8. import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
  9. import static android.opengl.GLES20.glClearColor;
  10. import static com.leilu.mycamera.EGLThread.RENDERMODE_WHEN_DIRTY;
  11. /**
  12. * Created by ll on 2018/9/13.
  13. */
  14. public class CameraView extends MyGLSurfaceView implements EGLThread.OnEGLThreadListener, SurfaceTexture.OnFrameAvailableListener {
  15. private static final int FRONT_CAMERA_ID = 1;
  16. private static final int BACK_CAMERA_ID = 0;
  17. private CameraHelper mCameraHelper;
  18. private CameraRender mCameraRender;
  19. private int mCameraId = BACK_CAMERA_ID;
  20. public CameraView(Context context) {
  21. this(context, null);
  22. }
  23. public CameraView(Context context, AttributeSet attrs) {
  24. this(context, attrs, 0);
  25. }
  26. public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
  27. super(context, attrs, defStyleAttr);
  28. setRenderMode(RENDERMODE_WHEN_DIRTY);
  29. setRenderListener( this);
  30. mCameraHelper = new CameraHelper();
  31. mCameraRender = new CameraRender(context);
  32. }
  33. @Override
  34. public void onCreate() {
  35. mCameraRender.init( this);
  36. }
  37. @Override
  38. public void onFrameAvailable(SurfaceTexture surfaceTexture) {
  39. requestRender();
  40. }
  41. @Override
  42. public void onSurfaceChanged(Object surface, int width, int height) {
  43. mCameraRender.setViewPort(width, height);
  44. SurfaceTexture surfaceTexture = mCameraRender.getSurfaceTexture();
  45. if (surfaceTexture != null) {
  46. mCameraHelper.init(mCameraId, width, height, surfaceTexture);
  47. open();
  48. }
  49. }
  50. @Override
  51. public void onDrawFrame() {
  52. glClear(GL_COLOR_BUFFER_BIT);
  53. glClearColor( 0, 0, 0, 1);
  54. mCameraRender.draw();
  55. }
  56. public void open() {
  57. mCameraHelper.startPreview();
  58. mCameraRender.setProjectionMatrix(mCameraId == 0);
  59. }
  60. public void close() {
  61. mCameraHelper.stopPreview();
  62. }
  63. @Override
  64. protected void onDetachedFromWindow() {
  65. mCameraHelper.release();
  66. mCameraHelper = null;
  67. mCameraRender.release();
  68. mCameraRender = null;
  69. super.onDetachedFromWindow();
  70. }
  71. public void swithCamera() {
  72. if (mCameraId == BACK_CAMERA_ID) {
  73. mCameraId = FRONT_CAMERA_ID;
  74. } else {
  75. mCameraId = BACK_CAMERA_ID;
  76. }
  77. if (mCameraHelper.switchCamera(mCameraId)) {
  78. mCameraRender.setProjectionMatrix(mCameraId == 0);
  79. }
  80. }
  81. }

上面三个类创建完成以后使用就非常简单了,直接在布局里面嵌入CameraView这个控件即可:


 
 
  1. <?xml version= "1.0" encoding= "utf-8"?>
  2. <LinearLayout xmlns:android= "http://schemas.android.com/apk/res/android"
  3. android:layout_width= "match_parent"
  4. android:layout_height= "match_parent"
  5. android:orientation= "vertical">
  6. <com.leilu.mycamera.camera.CameraView
  7. android:id= "@+id/surface_view"
  8. android:layout_width= "match_parent"
  9. android:layout_height= "0dp"
  10. android:layout_weight= "1" />
  11. <Button
  12. android:layout_width= "match_parent"
  13. android:layout_height= "wrap_content"
  14. android:onClick= "open"
  15. android:text= "打开" />
  16. <Button
  17. android:layout_width= "match_parent"
  18. android:layout_height= "wrap_content"
  19. android:onClick= "switchCamera"
  20. android:text= "切换" />
  21. <Button
  22. android:layout_width= "match_parent"
  23. android:layout_height= "wrap_content"
  24. android:onClick= "close"
  25. android:text= "关闭" />
  26. </LinearLayout>

 


 
 
  1. package com.leilu.mycamera;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.support.annotation.Nullable;
  5. import android.view.View;
  6. import com.leilu.floatwindow.R;
  7. import com.leilu.mycamera.camera.CameraView;
  8. /**
  9. * Created by ll on 2018/9/13.
  10. */
  11. public class CameraActivity extends Activity {
  12. private CameraView mCameraView;
  13. @Override
  14. protected void onCreate(@Nullable Bundle savedInstanceState) {
  15. super.onCreate(savedInstanceState);
  16. setContentView(R.layout.activity_camera);
  17. mCameraView = findViewById(R.id.surface_view);
  18. }
  19. public void open(View view) {
  20. mCameraView.open();
  21. }
  22. public void close(View view) {
  23. mCameraView.close();
  24. }
  25. public void switchCamera(View view) {
  26. mCameraView.swithCamera();
  27. }
  28. }

此时就可以预览摄像头了。

预留的问题:

1、切换摄像头的时候画面会旋转一下,这个问题下次解决

2、在创建自定义GLSurfaceView的时候说到可以实现纹理共享,在这里还没有用到,下篇文章中就使用共享纹理

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值