Android openGl開發詳解(二)
https://zhuanlan.zhihu.com/p/35192609
Android openGl開發詳解(二)——通過SurfaceView,TextureView,GlSurfaceView顯示相機預覽(附Demo)
程式設計·生活·職場·思維 公眾號「aserbao」
> 最近公司在做自定義相機這一塊,之前使用的是第三方,後來需求變更,第三方不支援新增動態貼紙,所以只能自己擴充套件。當然網上有很多例子,但是關於新增動態貼紙的例子幾乎找不到,反正我是沒找到(欲哭無淚)。當然,現在是所有功能都實現了。覺得openGl還是蠻有意思的,所以從頭再梳理一遍,打算整理一個openGl的學習系列。供學習參考。如果對OpenGl沒有了解的話,推薦先看
您能從本文了解到如下知識:1. 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;
@Override
protected 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);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
// Open the Camera in preview mode
mCamera = Camera.open(0);
mCamera.setDisplayOrientation(90);
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mCamera.autoFocus(new Camera.AutoFocusCallback() {
@Override
public 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();
}
}
});
}
@Override
public 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera_surface_texture);
ButterKnife.bind(this);
mCameraTextureView.setSurfaceTextureListener(this);
}
@Override
public 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();
}
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
return false;
}
@Override
public 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;
@Override
protected 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);
}
@Override
public 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);
}
@Override
public 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();
}
@Override
public 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);
}
@Override
public 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. 原始碼連結
aserbao/AserbaosAndroidgithub.com
如果覺得對你有用的話幫忙給個star吧
7.關注打賞
If you find this repository helpful, you may make a donation to me via alipay or wechat.
堅持原創技術分享,您的支援將鼓勵我繼續創作!