Android openGl开发详解(二)——通过SurfaceView,TextureView,GlSurfaceView显示相机预览(附Demo)
最近公司在做自定义相机这一块,之前使用的是第三方,后来需求变更,第三方不支持添加动态贴纸,所以只能自己扩展。当然网上有很多例子,但是关于添加动态贴纸的例子几乎找不到,反正我是没找到(欲哭无泪)。当然,现在是所有功能都实现了。觉得openGl还是蛮有意思的,所以从头再梳理一遍,打算整理一个openGl的学习系列。供学习参考。如果对OpenGl没有了解的话,推荐先看Android openGl开发详解(一)——绘制简单图形
您能从本文了解到如下知识:1. SurfaceView,GlSurfaceView,SurfaceTexture,TextureView的优缺点及区别。2. 如何通过SurfaceView显示Camera预览。 3. 如何通过TextureView显示Camera预览。 4. 如何通过GlSurfaceView处理Camera预览。 5. 总结 6. 源码链接 7.关注打赏
- 1. SurfaceView,GlSurfaceView,SurfaceTexture,TextureView的优缺点及区别
- SurfaceView
- GlSurfaceView
- SurfaceTexture
- TextureView
- 2. 如何通过SurfaceView显示Camera预览。
- 基本步骤
- 代码部分
- 效果展示
- 提别提醒
- 3. 如何通过TextureView显示Camera预览
- 基本步骤
- 代码部分
- 效果展示
- 4. 如何通过GlSurfaceView处理Camera预览。
- 基本步骤
- 代码部分
- 实现效果
- 遇到的问题
- 运行效果
- 5. 总结
- 6. 源码链接
- 7.关注打赏
1. SurfaceView,GlSurfaceView,SurfaceTexture,TextureView的优缺点及区别
SurfaceView
继承自View,拥有View的大部分属性,但是由于holder的存在,不能设置透明度。
优点:可以在一个独立的线程中进行绘制,不会影响主线程,使用双缓冲机制,播放视频时画面更流畅
缺点:surface的显示不受View属性的控制,不能将其放在ViewGroup中,SurfaceView不能嵌套使用。
GlSurfaceView
GlSurfaceView继承自SurfaceView类,专门用来显示OpenGL渲染的,简单理解可以显示视频,图像及3D场景这些的。
SurfaceTexture
和SurfaceView功能类似,区别是,SurfaceTexure可以不显示在界面中。使用OpenGl对图片流进行美化,添加水印,滤镜这些操作的时候我们都是通过SurfaceTexre去处理,处理完之后再通过GlSurfaceView显示。缺点,可能会导致个别帧的延迟。本身管理着BufferQueue,所以内存消耗会多一点。
TextureView
同样继承自View,必须在开启硬件加速的设备中使用(保守估计目前百分之九十的Android设备都开启了),TextureView通过setSurfaceTextureListener的回调在子线程中进行更新UI.
优点:支持动画效果。
缺点:在5.0之前在主线程渲染,在5.0之后在单独线程渲染。
TextureView | SurfaceView | |
---|---|---|
绘制 | 稍微延时 | 及时 |
内存 | 高 | 低 |
动画 | 支持 | 不支持 |
耗电 | 高 | 低 |
适用场景(推荐) | 视频播放,相机应用 | 大量画布更新(游戏绘制) |
2. 如何通过SurfaceView显示Camera预览。
基本步骤
- 在xml文件中设置SurfaceView 。
- 实现SurfaceHolder.Callback的回调。
- 打开摄像头Camera.open(0);
- 设置摄像头相关参数;
- 将摄像头数据设置到SurfaceView中,并开启预览。
代码部分
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent" tools:context="com.aserbao.aserbaosandroid.opengl.openGlCamera.simpleCameraOpengl.simpleCamera.CameraSurfaceViewShowActivity"><FrameLayout
android:id="@+id/frame_layout"android:layout_width="match_parent"android:layout_height="match_parent"><SurfaceView
android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/mSurface" /><Button
android:id="@+id/btn_change"android:text="给动画,SurfaceView不支持设置透明度"android:textAllCaps="false"android:layout_width="wrap_content"android:layout_height="wrap_content"/></FrameLayout>
</android.support.constraint.ConstraintLayout>
/*** 使用SurfaceView预览Camera数据*/
public class CameraSurfaceViewShowActivity extends AppCompatActivity implements SurfaceHolder.Callback {@BindView(R.id.mSurface)SurfaceView mSurfaceView;public SurfaceHolder mHolder;private Camera mCamera;private Camera.Parameters mParameters;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_base_camera);ButterKnife.bind(this);mHolder = mSurfaceView.getHolder();mHolder.addCallback(this);mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);}@Overridepublic void surfaceCreated(SurfaceHolder holder) {try {// Open the Camera in preview modemCamera = Camera.open(0);mCamera.setDisplayOrientation(90);mCamera.setPreviewDisplay(holder);mCamera.startPreview();} catch (IOException e) {}}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {mCamera.autoFocus(new Camera.AutoFocusCallback() {@Overridepublic void onAutoFocus(boolean success, Camera camera) {if (success) {mParameters = mCamera.getParameters();mParameters.setPictureFormat(PixelFormat.JPEG); //图片输出格式
// mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);//预览持续发光mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);//持续对焦模式mCamera.setParameters(mParameters);mCamera.startPreview();mCamera.cancelAutoFocus();}}});}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {if (mCamera != null) {mCamera.stopPreview();mCamera.release();mCamera = null;}}@OnClick(R.id.btn_change)public void onViewClicked() {
// PropertyValuesHolder valuesHolder2 = PropertyValuesHolder.ofFloat("rotationX", 0.0f, 360.0f, 0.0F);PropertyValuesHolder valuesHolder = PropertyValuesHolder.ofFloat("rotationY", 0.0f, 360.0f, 0.0F);PropertyValuesHolder valuesHolder1 = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.5f,1.0f);PropertyValuesHolder valuesHolder3 = PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.5f,1.0f);ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mSurfaceView, valuesHolder,valuesHolder1,valuesHolder3);objectAnimator.setDuration(5000).start();}
}
效果展示
提别提醒
SurfaceView预览相机视图不支持透明度,可以设置缩放旋转属性。如果需要做动画特效的话不推荐使用SurfaceView显示视图。可以使用下面的TextureView或者GlSurfaceView来显示。
3. 如何通过TextureView显示Camera预览
基本步骤
TextureView和SurfaceView显示Camera数据其实差不多,差别就两点:
1. SurfaceView显示需要实现SurfaceHolder.Callback的回调而TextureView通过实现 TextureView.SurfaceTextureListener接口。
2. 当Camera使用SurfaceView预览时通过setPreviewDisplay(holder)方法来设置预览视图,而使用TextureView预览时使用setPreviewTexture(mCameraTextureView.getSurfaceTexture())方法来设置。
其他步骤同上。
代码部分
public class CameraTextureViewShowActivity extends AppCompatActivity implements TextureView.SurfaceTextureListener {@BindView(R.id.camera_texture_view)TextureView mCameraTextureView;public Camera mCamera;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_camera_surface_texture);ButterKnife.bind(this);mCameraTextureView.setSurfaceTextureListener(this);}@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {try {mCamera = Camera.open(0);mCamera.setDisplayOrientation(90);mCamera.setPreviewTexture(mCameraTextureView.getSurfaceTexture());mCamera.startPreview();} catch (IOException e) {e.printStackTrace();}}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {if (mCamera != null) {mCamera.stopPreview();mCamera.release();mCamera = null;}return false;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {}@OnClick(R.id.btn_texture_anim)public void onViewClicked() {PropertyValuesHolder valuesHolder = PropertyValuesHolder.ofFloat("translationX", 0.0f, 0.0f);PropertyValuesHolder valuesHolder1 = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.3f,1.0f);PropertyValuesHolder valuesHolder4 = PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.3f,1.0f);PropertyValuesHolder valuesHolder2 = PropertyValuesHolder.ofFloat("rotationX", 0.0f, 2 * 360.0f, 0.0F);PropertyValuesHolder valuesHolder5 = PropertyValuesHolder.ofFloat("rotationY", 0.0f, 2 * 360.0f, 0.0F);PropertyValuesHolder valuesHolder3 = PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.7f, 1.0F);ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mCameraTextureView, valuesHolder, valuesHolder1, valuesHolder2, valuesHolder3,valuesHolder4,valuesHolder5);objectAnimator.setDuration(5000).start();}
}
效果展示
4. 如何通过GlSurfaceView处理Camera预览。
如果你在学习自定义相机,而且你的相机想要实现美颜,滤镜,人脸识别AR场景 and so on。这时候你就必须要学习如何使用GlsurfaView罗。如果你没有openGl的基本配置的知识或者你之前完全没有学习过openGl的开发,再次强烈建议你看一下这篇文章 Android openGl开发详解(一)——绘制简单图形,否则,下面内容可能会引起你的严重不适。当然,如果你还是觉得哪部分写得不好或者有疑问的话,欢迎给我留言,或者关注同名微信号aserbao私聊我。
基本步骤
- 在xml中添加GlSurfaceView
- 创建渲染器类实现GlSurfaceView.Renderer
- 清除画布,并创建一个纹理并绑定到。
- 创建一个用来最后显示的SurfaceTexture来显示处理后的数据。
- 创建Opengl ES程序并添加着色器到该程序中,创建openGl程序的可执行文件,并释放shader资源。
- 打开摄像头,并配置相关属性。设置预览视图,并开启预览。
- 添加程序到ES环境中,并设置及启用各类句柄。
- 在onDrawFrame中进行画布的清理及绘制最新的数据到纹理图形中。
- 设置一个SurfaceTexture.OnFrameAvailableListener的回调来通知GlSurfaceview渲染新的帧数据。
建议:
GlSurfaceView作用简单的理解OpenGl对相机数据进行处理完之后的显示。我们需要明白的是渲染器的渲染周期及渲染方法的调用时机。
1. onSurfaceCreated()当surface创建(第一次进入当前页面)或者重新创建(切换后台再进入)的时候调用。
2. onSurfaceChanged()当surface大小发生改变的时候会被调用。
3. onDrawFrame()绘制当前帧数据的时候被调用。
代码部分
public class CameraGlSurfaceShowActivity extends AppCompatActivity implements SurfaceTexture.OnFrameAvailableListener {public SurfaceTexture mSurfaceTexture;public static Camera camera;private int camera_status = 1;@BindView(R.id.camera_glsurface_view)GLSurfaceView mCameraGlsurfaceView;public MyRender mRenderer;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_camera_gl_surface_show);ButterKnife.bind(this);mCameraGlsurfaceView.setEGLContextClientVersion(2);//在setRenderer()方法前调用此方法mRenderer = new MyRender();mCameraGlsurfaceView.setRenderer(mRenderer);mCameraGlsurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY);}@Overridepublic void onFrameAvailable(SurfaceTexture surfaceTexture) {mCameraGlsurfaceView.requestRender();}@OnClick({R.id.btn_gl_surface_view_animator, R.id.btn_gl_surface_view_switch})public void onViewClicked(View view) {switch (view.getId()) {case R.id.btn_gl_surface_view_animator:PropertyValuesHolder valuesHolder1 = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.5f,1.0f);PropertyValuesHolder valuesHolder4 = PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.5f,1.0f);PropertyValuesHolder valuesHolder5 = PropertyValuesHolder.ofFloat("rotationY", 0.0f, 360.0f, 0.0F);ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mCameraGlsurfaceView, valuesHolder1, valuesHolder4,valuesHolder5);objectAnimator.setDuration(3000).start();break;case R.id.btn_gl_surface_view_switch:camera_status ^= 1;if (camera != null) {camera.stopPreview();camera.release();}mRenderer.mBoolean = true;camera = Camera.open(camera_status);try {camera.setPreviewTexture(mSurfaceTexture);} catch (IOException e) {e.printStackTrace();}camera.startPreview();break;}}public class MyRender implements GLSurfaceView.Renderer {private final String vertexShaderCode = "uniform mat4 textureTransform;\n" +"attribute vec2 inputTextureCoordinate;\n" +"attribute vec4 position; \n" +//NDK坐标点"varying vec2 textureCoordinate; \n" +//纹理坐标点变换后输出"\n" +" void main() {\n" +" gl_Position = position;\n" +" textureCoordinate = inputTextureCoordinate;\n" +" }";private final String fragmentShaderCode = "#extension GL_OES_EGL_image_external : require\n" +"precision mediump float;\n" +"uniform samplerExternalOES videoTex;\n" +"varying vec2 textureCoordinate;\n" +"\n" +"void main() {\n" +" vec4 tc = texture2D(videoTex, textureCoordinate);\n" +" float color = tc.r * 0.3 + tc.g * 0.59 + tc.b * 0.11;\n" + //所有视图修改成黑白" gl_FragColor = vec4(color,color,color,1.0);\n" +
// " gl_FragColor = vec4(tc.r,tc.g,tc.b,1.0);\n" +"}\n";private FloatBuffer mPosBuffer;private FloatBuffer mTexBuffer;private float[] mPosCoordinate = {-1, -1, -1, 1, 1, -1, 1, 1};private float[] mTexCoordinateBackRight = {1, 1, 0, 1, 1, 0, 0, 0};//顺时针转90并沿Y轴翻转 后摄像头正确,前摄像头上下颠倒private float[] mTexCoordinateForntRight = {0, 1, 1, 1, 0, 0, 1, 0};//顺时针旋转90 后摄像头上下颠倒了,前摄像头正确public int mProgram;public boolean mBoolean = false;public MyRender() {Matrix.setIdentityM(mProjectMatrix, 0);Matrix.setIdentityM(mCameraMatrix, 0);Matrix.setIdentityM(mMVPMatrix, 0);Matrix.setIdentityM(mTempMatrix, 0);}private int loadShader(int type, String shaderCode) {int shader = GLES20.glCreateShader(type);// 添加上面编写的着色器代码并编译它GLES20.glShaderSource(shader, shaderCode);GLES20.glCompileShader(shader);return shader;}private void creatProgram() {//通常做法
// String vertexSource = AssetsUtils.read(CameraGlSurfaceShowActivity.this, "vertex_texture.glsl");
// int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
// String fragmentSource = AssetsUtils.read(CameraGlSurfaceShowActivity.this, "fragment_texture.glsl");
// int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);// 创建空的OpenGL ES程序mProgram = GLES20.glCreateProgram();// 添加顶点着色器到程序中GLES20.glAttachShader(mProgram, vertexShader);// 添加片段着色器到程序中GLES20.glAttachShader(mProgram, fragmentShader);// 创建OpenGL ES程序可执行文件GLES20.glLinkProgram(mProgram);// 释放shader资源GLES20.glDeleteShader(vertexShader);GLES20.glDeleteShader(fragmentShader);}private FloatBuffer convertToFloatBuffer(float[] buffer) {FloatBuffer fb = ByteBuffer.allocateDirect(buffer.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();fb.put(buffer);fb.position(0);return fb;}private int uPosHandle;private int aTexHandle;private int mMVPMatrixHandle;private float[] mProjectMatrix = new float[16];private float[] mCameraMatrix = new float[16];private float[] mMVPMatrix = new float[16];private float[] mTempMatrix = new float[16];//添加程序到ES环境中private void activeProgram() {// 将程序添加到OpenGL ES环境GLES20.glUseProgram(mProgram);mSurfaceTexture.setOnFrameAvailableListener(CameraGlSurfaceShowActivity.this);// 获取顶点着色器的位置的句柄uPosHandle = GLES20.glGetAttribLocation(mProgram, "position");aTexHandle = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate");mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "textureTransform");mPosBuffer = convertToFloatBuffer(mPosCoordinate);if(camera_status == 0){mTexBuffer = convertToFloatBuffer(mTexCoordinateBackRight);}else{mTexBuffer = convertToFloatBuffer(mTexCoordinateForntRight);}GLES20.glVertexAttribPointer(uPosHandle, 2, GLES20.GL_FLOAT, false, 0, mPosBuffer);GLES20.glVertexAttribPointer(aTexHandle, 2, GLES20.GL_FLOAT, false, 0, mTexBuffer);// 启用顶点位置的句柄GLES20.glEnableVertexAttribArray(uPosHandle);GLES20.glEnableVertexAttribArray(aTexHandle);}@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);mSurfaceTexture = new SurfaceTexture(createOESTextureObject());creatProgram();
// mProgram = ShaderUtils.createProgram(CameraGlSurfaceShowActivity.this, "vertex_texture.glsl", "fragment_texture.glsl");camera = Camera.open(camera_status);try {camera.setPreviewTexture(mSurfaceTexture);camera.startPreview();} catch (IOException e) {e.printStackTrace();}activeProgram();}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {GLES20.glViewport(0, 0, width, height);Matrix.scaleM(mMVPMatrix,0,1,-1,1);float ratio = (float) width / height;Matrix.orthoM(mProjectMatrix, 0, -1, 1, -ratio, ratio, 1, 7);// 3和7代表远近视点与眼睛的距离,非坐标点Matrix.setLookAtM(mCameraMatrix, 0, 0, 0, 3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);// 3代表眼睛的坐标点Matrix.multiplyMM(mMVPMatrix, 0, mProjectMatrix, 0, mCameraMatrix, 0);}@Overridepublic void onDrawFrame(GL10 gl) {if(mBoolean){activeProgram();mBoolean = false;}if (mSurfaceTexture != null) {GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);mSurfaceTexture.updateTexImage();GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, mPosCoordinate.length / 2);}}}public static int createOESTextureObject() {int[] tex = new int[1];//生成一个纹理GLES20.glGenTextures(1, tex, 0);//将此纹理绑定到外部纹理上GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0]);//设置纹理过滤参数GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);return tex[0];}
}
当然,大多数情况下渲染顶点着色器及片段着色器的代码会编写一个glsl的文件放到assets目录下进行访问。
下面是另外一个操作方式:
vertex_texture.glsl文件
uniform mat4 textureTransform;
attribute vec2 inputTextureCoordinate;
attribute vec4 position; //NDK坐标点
varying vec2 textureCoordinate; //纹理坐标点变换后输出void main() {gl_Position = position;textureCoordinate = inputTextureCoordinate;}
fragment_texture.glsl文件:
#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform samplerExternalOES videoTex;
varying vec2 textureCoordinate;void main() {vec4 tc = texture2D(videoTex, textureCoordinate);float color = tc.r * 0.3 + tc.g * 0.59 + tc.b * 0.11;//这里进行的颜色变换处理,传说中的黑白滤镜。gl_FragColor = vec4(color,color,color,1.0);
}
读取文件内容方式:
public static String read(Context context, String fileName) {String result = null;try {InputStream is = context.getResources().getAssets().open("Shader/" + fileName);int length = is.available();byte[] buffer = new byte[length];is.read(buffer);result = new String(buffer, "utf-8");} catch (IOException e) {e.printStackTrace();}return result;}
具体实现在上面代码creatProgram()下注释掉通常做法的那部分。
实现效果
遇到的问题
- 使用OpenGl处理数据之后再GlsurfaceView上图片镜像了,咋办?这种问题要么出在绘制顶点坐标,要么出在源数据上,但是通过其他的比如SurfaceView直接预览数据没问题,那么只能是第一种可能了。仔细回看下代码,果然修改渲染颜色矩阵显示出来的数据方向都是不同的。
下面是我改动矩阵产生的结果:
问题根本找到了,如何解决呢?起始点就四个点,一个点就最多三个连接点,而且起始点不能和不相邻的点进行连接(一旦连接就会出现上面的第二种情况),所以最后4个点只有8种可能性,将所有点的都试一遍,得出如下结论(经过几轮测试,基本结论如下):
- 还有一个关于前后摄像头的切换的问题,我上面的做法是在点击切换摄像头操作的时候只针对摄像头进行了释放重启操作,直接在onDrawFrame方法中对渲染矩阵进行了修改,没有对SurfaceTexture进行数据清除(具体看上面代码)。然而也看了一些主流的第三方Demo,这里不列出名字了。他们的做法是摄像头和surfaceTexture一块释放。当然,两种方式都可以,我上面的那种方式暂时没找到什么问题,而且我通过实测比第二种方式看到相机数据的时间要快一点。
运行效果
5. 总结
弄清楚没个步骤之后你就会发现之前感觉无从下手的东西也就这样。没什么大不了的。
个人建议:
做相机项目,最好能将每个步骤都弄清楚,逻辑理清楚了会节省很大一部分时间。刚开始的时候在做这部分是拿别人一个Demo,出错了,无从下手,又去百度找解决办法,浪费了一大堆时间,整个项目做完了,还是没有对此有个深刻的认识,真要说还说不出个所以然来。重新整理下,会了解到很多东西。
最后,不多说了,如果有什么不懂的地方可以给我留言或者关注同名微信号aserbao私聊我。如果觉得写得不错的话就给个赞呗。忘了忘了,还有源码和关注,看楼下。
6. 源码链接
Android’s Android如果觉得对你有用的话帮忙给个star吧
7.关注打赏
If you find this repository helpful, you may make a donation to me via alipay or wechat.
坚持原创技术分享,您的支持将鼓励我继续创作!
微信支付: | 支付宝支付: | 微信公众号: |
---|---|---|
Android openGl开发详解(二)——通过SurfaceView,TextureView,GlSurfaceView显示相机预览(附Demo)相关推荐
- Android openGl开发详解(二)
https://zhuanlan.zhihu.com/p/35192609 Android openGl开发详解(二)--通过SurfaceView,TextureView,GlSurfaceView ...
- SurfaceView简单理解,Android混淆,Android openGl开发详解简单图形的绘制,
SurfaceView允许你在非ui线程中去绘制. SurfaceView的帧率可以操作60FPS 在要求实时性比较高的游戏开发中,显然,view的ondraw是满足不了你的,这时候只能是用Surfa ...
- Android openGl开发详解(一)——绘制简单图形
1. What? openGl是什么?openGl ES又是什么? 2. How? Android中的openGL 如何使用? 3. GlSurfaceView是什么? GLSurfaceView的作 ...
- Android WebView 开发详解(二)
转载请注明出处 http://blog.csdn.net/typename/article/details/39495409powered by miechal zhao 概览: Android W ...
- android开发照相机啊,Android照相机开发详解(一)
Android相机开发详解(一) Android相机开发详解(一) 请支持原创,尊重原创,转载请注明出处:http://blog.csdn.net/kangweijian(来自kangweijian的 ...
- Android USB 开发详解
Android USB 开发详解 先附上 Android USB 官方文档 Android通过两种模式支持各种 USB 外设和 Android USB 附件(实现Android附件协议的硬件):USB ...
- 全志 android 编译,全志Android SDK编译详解(二)
注意要确定安装了jdk) 第一步: cd lichee; ./build.sh -p sun5i_elite -k 3.0 (apt-get install uboot-mkimage需要安装m ...
- 《Android游戏开发详解》——第1章,第1.6节函数(在Java中称为“方法”更好)...
本节书摘来自异步社区<Android游戏开发详解>一书中的第1章,第1.6节函数(在Java中称为"方法"更好),作者 [美]Jonathan S. Harbour,更 ...
- JMessage Android 端开发详解
JMessage Android 端开发详解 目前越来越多的应用会需要集成即时通讯功能,这里就为大家详细讲一下如何通过集成 JMessage 来为你的 App 增加即时通讯功能. 首先,一个最基础的 ...
最新文章
- 谷歌最新提出无需卷积、注意力,纯MLP构成的视觉架构!网友:MLP is All You Need?
- 【Discuz!】去掉版面的右侧的“收藏本版”和“订阅”
- 《自己动手写开发工具》试读版电子书及光盘
- JSP EL表达式详细介绍
- 【litrpa专题】首个rpa程序,使用litrpa采集百度地图地铁数据
- Python 进阶 —— 迭代器与生成器
- 读取JSON文件并 排序,分组,
- oracle exp-00011的解决办法
- Win10系统利用注册表完美设置桌面图标的技巧
- 如何构建全球最佳数据中心平台
- 高性能mysql学习笔记一
- java复数类实部_Java编写一个复数类Complex,具有实部、虚部成员变量,可以完成加、减、乘、除和获得实部和虚部的方法...
- 【C++ 程序】 TVJ Complex Calculator (v 2.2) 复数计算器
- NVMe-MI协议解读
- 【深度学习框架】|PyTorch|完成一个手写体识别任务
- 日语基础语法(完整篇)
- 交友盲盒源码h5开发浅谈
- 内蒙古师范大学计算机科学技术学院分数线,2019内蒙古师范大学录取分数线及历年专业分数线统计表【文科 理科】...
- [转载]modbus通讯协议详解和几张modbus图解 力求通俗易懂
- 计算机类图书按中图法类号,中图分类号 中国图书馆分类法(O类 数理科学和化学)...