這篇文章將介紹相機預覽的實現(xiàn),網(wǎng)上可以找到很多關于Camera2
預覽的文章募谎,Camera1已經(jīng)過時扶关,所以暫時不考慮什么兼容性,就用Camera2來搞数冬〗诨保可以使用TextureView作為載體來實現(xiàn)預覽功能,但是我們后面要在相機上添加濾鏡吉执、貼紙等功能疯淫,所以載體就選擇了GLSurfaceView
。
之前的文章
《OpenGL從入門到放棄01 》一些基本概念
《OpenGL從入門到放棄02 》GLSurfaceView和Renderer
《OpenGL從入門到放棄03 》相機和視圖
《OpenGL從入門到放棄04 》畫一個長方形
《OpenGL從入門到放棄05 》著色器語言
《OpenGL從入門到放棄06 》紋理圖片顯示
《OpenGL從入門到放棄07》濾鏡戳玫,一定要看熙掺,不虧
開始擼擼代碼...
1、簡單的布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.opengl.GLSurfaceView
android:id="@+id/glsurfaceview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_margin="16dp"
android:gravity="center"
android:onClick="takePicture"
android:text="拍照" />
</RelativeLayout>
預留個拍照按鈕咕宿,后面文章會講拍照功能
2币绩、Activity代碼
public class Camera2Demo_SurfaceView_Activity extends AppCompatActivity {
private static final String TAG = "Camera2Demo_SurfaceView";
private GLSurfaceView mCameraV2GLSurfaceView;
private CameraV2Renderer mCameraV2Renderer;
private CameraV2 mCamera;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camera2_surfaceview_activity);
initView();
}
private void initView() {
//Camera2 API 進行封裝
mCamera = new CameraV2(this);
mCameraV2GLSurfaceView = findViewById(R.id.glsurfaceview);
mCameraV2GLSurfaceView.setEGLContextClientVersion(2);
mCameraV2Renderer = new CameraV2Renderer();
mCameraV2Renderer.init(mCameraV2GLSurfaceView, mCamera, this);
mCameraV2GLSurfaceView.setRenderer(mCameraV2Renderer);
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mCamera != null) {
mCamera.closeCamea();
}
}
public void takePicture(View view) {
Log.d(TAG, "onclick takePicture: ");
}
}
說明:
initView
中代碼不多蜡秽,因為Camera2 的API被封裝在 CameraV2
這個類里了,接下來將主要分析 CameraV2
和 CameraV2Renderer
這兩個類缆镣。
3芽突、CameraV2
CameraV2 封裝了Camera2 的API,聽我細細道來~
3.1 構(gòu)造方法
public CameraV2(Activity activity) {
mActivity = activity;
//1.啟動Camera線程
startCameraThread();
//2.準備Camera董瞻,獲取cameraId寞蚌,獲取Camera預覽大小
setupCamera();
//打開Camera
openCamera();
}
3.2 啟動Camera線程
public void startCameraThread() {
mCameraThread = new HandlerThread("CameraThread");
mCameraThread.start();
mCameraHandler = new Handler(mCameraThread.getLooper());
}
openCamera
方法里面需要傳一個Handler,這個Handler必須是子線程的Handler钠糊,等下會介紹挟秤。
3.2 setupCamera
setupCamera 中主要是獲取cameraId和camera 預覽大小,這個預覽大小什么意思呢抄伍?
public String setupCamera() {
CameraManager cameraManager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
try {
mCameraIdList = cameraManager.getCameraIdList();
Log.d(TAG, "setupCamera: mCameraIdList:"+mCameraIdList.length);
for (String id : mCameraIdList) {
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
continue;
}
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
mPreviewSize = map.getOutputSizes(SurfaceTexture.class)[0];
mCameraId = id;
Log.d(TAG, "setupCamera: preview width = " + mPreviewSize.getWidth() + ", height = " + mPreviewSize.getHeight() + ", cameraId = " + mCameraId);
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
return mCameraId;
}
通過CameraManager獲取cameraIdList艘刚,如果有前后攝像頭,那么cameraIdList這個數(shù)組大小就是2了截珍,大家可以點擊
getCameraIdList
這個方法進去看源碼攀甚,這里就先不說源碼了。for 循環(huán)里面過濾掉前置攝像頭岗喉,最終 mCameraId 對應后置攝像頭的id;
預覽大小
mPreviewSize
的獲取都是模板代碼秋度,知道就行
準備好了,可以打開相機了
3.3 openCamera 打開相機
public boolean openCamera() {
CameraManager cameraManager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
mActivity.requestPermissions(new String[]{Manifest.permission.CAMERA, Manifest
.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
cameraManager.openCamera(mCameraId, mStateCallback, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
return false;
}
return true;
}
打開相機就是通過獲取 CameraManager
對象钱床,然后調(diào)用它的 openCamera
方法即可静陈,這里要說下三個參數(shù)
-
mCameraId
在上一步 setupCamera方法已經(jīng)獲取了 -
mStateCallback
是一個 CameraDevice.StateCallback 對象
public CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCameraDevice = camera;
startPreview();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
camera.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
mCameraDevice = null;
}
};
主要是接收相機打開狀態(tài),相機打開成功诞丽,斷開連接、出現(xiàn)錯誤拐格,會分別回調(diào)上面三個方法僧免,最重要的是 onOpened
回調(diào),我們可以在onOpened
回調(diào)中獲取 CameraDevice
對象捏浊,一般的做法都是獲取 CameraDevice
對象之后懂衩,打開預覽。
-
mCameraHandler
金踪,在startCameraThread
方法中初始化好了
3.4 startPreview 開始預覽
public void startPreview() {
if (mStartPreview || mSurfaceTexture == null || mCameraDevice == null){
return;
}
mStartPreview = true;
//給 SurfaceTexture 設置默認大小浊洞,mPreviewSize是相機預覽大小
mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
//Surface 需要接收一個 SurfaceTexture
Surface surface = new Surface(mSurfaceTexture);
try {
// 通過CameraDevice創(chuàng)建request
mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//surface 作為輸出的目標,預覽的數(shù)據(jù)會傳到 Surface 中
mCaptureRequestBuilder.addTarget(surface);
//創(chuàng)建會話胡岔, surface 這里傳進去法希,然后只需關心回調(diào)
mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
try {
CaptureRequest mCaptureRequest = mCaptureRequestBuilder.build();
//設置一直重復捕獲圖像,不設置就只有一幀靶瘸,沒法預覽
session.setRepeatingRequest(mCaptureRequest, null, mCameraHandler);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
}
}, mCameraHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
注釋應該很清楚了~
mCameraDevice
上面說過了苫亦,在openCamera方法的回調(diào)拿到的毛肋。
主要就是通過 mCameraDevice.createCaptureSession 創(chuàng)建一個Session,然后傳入 surface屋剑,預覽的圖片數(shù)據(jù)會輸出到 surface 中>
需要注意的是:在 onConfigured
回調(diào)方法中润匙,需要調(diào)用 session.setRepeatingRequest
設置重復捕獲圖像,不然只能拿到一幀唉匾,不能實時預覽孕讳。
surface中有一個 SurfaceTexture
,這個是重點:
public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
this.mSurfaceTexture = surfaceTexture;
}
...
Surface surface = new Surface(mSurfaceTexture);
SurfaceTexture
并不是在這里隨意創(chuàng)建的巍膘,而是跟OpenGL掛鉤起來的厂财,setSurfaceTexture
方法在 CameraV2Renderer
中調(diào)用的,繼續(xù)看~
4典徘、CameraV2Renderer
老規(guī)矩蟀苛,先貼代碼再解釋
public class CameraV2Renderer implements GLSurfaceView.Renderer {
public static final String TAG = "CameraV2Renderer";
private Context mContext;
GLSurfaceView mGLSurfaceView;
CameraV2 mCamera;
private int mTextureId = -1;
private SurfaceTexture mSurfaceTexture;
private float[] mTransformMatrix = new float[16];
Camera2BaseFilter mCamera2BaseFilter;
public void init(GLSurfaceView surfaceView, CameraV2 camera, Context context) {
mContext = context;
mGLSurfaceView = surfaceView;
mCamera = camera;
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
ShaderManager.init(mContext);
initSurfaceTexture();
mCamera2BaseFilter = new Camera2FilterNone(mContext, mTextureId);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
glViewport(0, 0, width, height);
Log.i(TAG, "onSurfaceChanged: " + width + ", " + height);
}
@Override
public void onDrawFrame(GL10 gl) {
//mSurfaceTexture.updateTexImage()更新預覽上的圖像
mSurfaceTexture.updateTexImage();
//直接通過 SurfaceTexture 獲取變換矩陣
mSurfaceTexture.getTransformMatrix(mTransformMatrix);
glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
mCamera2BaseFilter.draw(mTransformMatrix);
}
//創(chuàng)建 SurfaceTexture,CameraV2.startPreview()方法需要SurfaceTexture
public boolean initSurfaceTexture() {
//1逮诲、獲取一個紋理id
mTextureId = GLUtil.getOESTextureId();
//2帜平、紋理id設置到 SurfaceTexture 中,
mSurfaceTexture = new SurfaceTexture(mTextureId);
//圖片數(shù)據(jù)固定的梅鹦,而攝像頭數(shù)據(jù)是變換的裆甩,所以每當攝像頭有新的數(shù)據(jù)來時,我們需要通過surfaceTexture.updateTexImage()更新預覽上的圖像
// updateTexImage 不應該在OnFrameAvailableLister的回調(diào)方法中直接調(diào)用齐唆,而應該在onDrawFrame中執(zhí)行嗤栓。而調(diào)用requestRender,可以觸發(fā)onDrawFrame
mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
Log.d(TAG, "onFrameAvailable: ");
mGLSurfaceView.requestRender();
}
});
//將 SurfaceTexture 設置給CameraV2,然后調(diào)用startPreview
mCamera.setSurfaceTexture(mSurfaceTexture);
mCamera.startPreview();
return true;
}
}
CameraV2Renderer
代碼不多箍邮,說一下流程:
4.1 onSurfaceCreated 中初始化數(shù)據(jù)
4.1.1 ShaderManager.init(mContext)
ShaderManager.init(mContext);
這個其實是后面一節(jié)濾鏡功能加的茉帅,加了幾個相機的著色器
/**相機部分*/
insertParam(CAMERA_BASE_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_base.glsl"));
//黑白
insertParam(CAMERA_GRAY_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_gray.glsl"));
insertParam(CAMERA_COOL_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_cool.glsl"));
insertParam(CAMERA_WARM_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_warm.glsl"));
insertParam(CAMERA_FOUR_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_four.glsl"));
insertParam(CAMERA_ZOOM_SHADER, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_vertex_shader_base.glsl")
, GLUtil.loadFromAssetsFile(context, "shader/camera2/camera2_fragment_shader_zoom.glsl"));
這個是下一節(jié)的內(nèi)容,這里簡單說一下即可
4.1.2 initSurfaceTexture
這個方法很重要锭弊,一定要好好理解
//創(chuàng)建 SurfaceTexture堪澎,CameraV2.startPreview()方法需要SurfaceTexture
public void initSurfaceTexture() {
//1、獲取一個紋理id
mTextureId = GLUtil.getOESTextureId();
//2味滞、紋理id設置到 SurfaceTexture 中樱蛤,
mSurfaceTexture = new SurfaceTexture(mTextureId);
//圖片數(shù)據(jù)固定的,而攝像頭數(shù)據(jù)是變換的剑鞍,所以每當攝像頭有新的數(shù)據(jù)來時昨凡,我們需要通過surfaceTexture.updateTexImage()更新預覽上的圖像
// updateTexImage 不應該在OnFrameAvailableLister的回調(diào)方法中直接調(diào)用,而應該在onDrawFrame中執(zhí)行蚁署。而調(diào)用requestRender便脊,可以觸發(fā)onDrawFrame
mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
Log.d(TAG, "onFrameAvailable: ");
mGLSurfaceView.requestRender();
}
});
//將 SurfaceTexture 設置給CameraV2,然后調(diào)用startPreview
mCamera.setSurfaceTexture(mSurfaceTexture);
mCamera.startPreview();
}
//class : GLUtil
/**
* 相機預覽使用EXTERNAL_OES紋理,創(chuàng)建方式與2D紋理創(chuàng)建基本相同
* @return
*/
public static int getOESTextureId() {
int[] texture = new int[1];
GLES20.glGenTextures(1, texture, 0);
//于我們創(chuàng)建的是擴展紋理光戈,所以綁定的時候我們也需要綁定到擴展紋理上才可以正常使用就轧,
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[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 texture[0];
}
這個方法就是創(chuàng)建 SurfaceTexture证杭,然后傳給 CameraV2,再開啟預覽妒御。
分為兩步:
- 創(chuàng)建一個擴展紋理解愤,getOESTextureId方法返回紋理id :mTextureId
- 有了紋理id,創(chuàng)建 SurfaceTexture
- mSurfaceTexture 設置監(jiān)聽乎莉,在預覽數(shù)據(jù)更新的時候送讲,調(diào)用
mGLSurfaceView.requestRender() ,可以觸發(fā)
onDrawFrame` - 把SurfaceTexture設置給CameraV2惋啃,然后就可以愉快地開啟預覽了哼鬓,
然后預覽就會不斷回調(diào) onFrameAvailable,不斷調(diào)用
mGLSurfaceView.requestRender()
边灭,不斷觸發(fā)onDrawFrame
...
4.2 onDrawFrame
這個方法里很清晰
//mSurfaceTexture.updateTexImage()更新預覽上的圖像
mSurfaceTexture.updateTexImage();
//直接通過 SurfaceTexture 獲取變換矩陣
mSurfaceTexture.getTransformMatrix(mTransformMatrix);
更新預覽圖像异希,更新變換矩陣,然后其它的繪制過程都交給 Camera2BaseFilter
為什么命名為 xxFilter绒瘦,因為下一節(jié)要介紹濾鏡称簿,所以將不同濾鏡共有的代碼抽取到 Camera2BaseFilter
中。
5惰帽、Camera2BaseFilter
這個類代碼注釋非常清晰了
public abstract class Camera2BaseFilter {
private static final String TAG = "Camera2BaseFilter";
private FloatBuffer mVertexBuffer; //頂點坐標數(shù)據(jù)要轉(zhuǎn)化成FloatBuffer格式
private FloatBuffer mTexCoordBuffer;//頂點紋理坐標緩存
//當前繪制的頂點位置句柄
protected int vPositionHandle;
//變換矩陣句柄
protected int mMVPMatrixHandle;
//這個可以理解為一個OpenGL程序句柄
protected int mProgram;
//紋理坐標句柄
protected int mTexCoordHandle;
//紋理id
protected int mTextureId;
private Context mContext;
public Camera2BaseFilter(Context context, int textureId) {
this.mContext = context;
this.mTextureId = textureId;
//初始化Buffer憨降、Shader、紋理
initBuffer();
initShader();
}
//數(shù)據(jù)轉(zhuǎn)換成Buffer
private void initBuffer() {
float vertices[] = new float[]{
-1, 1, 0,
-1, -1, 0,
1, 1, 0,
1, -1, 0,
};//頂點位置
//這里經(jīng)過測試该酗,左下角才是紋理坐標的原點授药,右上角是(1,1)
float[] colors = new float[]{
0, 1,
0, 0,
1, 1,
1, 0,
};//紋理頂點數(shù)組
mVertexBuffer = GLUtil.floatArray2FloatBuffer(vertices);
mTexCoordBuffer = GLUtil.floatArray2FloatBuffer(colors);
}
/**
* 著色器
*/
private void initShader() {
//獲取程序呜魄,封裝了加載悔叽、鏈接等操作
ShaderManager.Param param = getProgram();
mProgram = param.program;
vPositionHandle = param.positionHandle;
// 獲取變換矩陣的句柄
mMVPMatrixHandle = param.mMVPMatrixHandle;
//紋理位置句柄
mTexCoordHandle = param.mTexCoordHandle;
Log.d(TAG, "initShader: mProgram = " + mProgram + ",vPositionHandle="+vPositionHandle +",mMVPMatrixHandle="+mMVPMatrixHandle+
",mTexCoordHandle="+mTexCoordHandle);
}
/**
* 濾鏡子類重寫這個方法,加載不同的OpenGL程序
*
* @return
*/
protected abstract ShaderManager.Param getProgram();
public void draw( float[] transformMatrix) {
// 將程序添加到OpenGL ES環(huán)境
GLES20.glUseProgram(mProgram);
// 啟用頂點屬性爵嗅,最后對應禁用
GLES20.glEnableVertexAttribArray(vPositionHandle);
GLES20.glEnableVertexAttribArray(mTexCoordHandle);
//綁定紋理骄蝇,跟圖片不同的是,這里是擴展紋理
glActiveTexture(GL_TEXTURE_EXTERNAL_OES);
glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId);
//設置轉(zhuǎn)換矩陣數(shù)據(jù)
glUniformMatrix4fv(mMVPMatrixHandle, 1, false, transformMatrix, 0);
//設置三角形坐標數(shù)據(jù)(一個頂點三個坐標)
GLES20.glVertexAttribPointer(vPositionHandle, 3,
GLES20.GL_FLOAT, false,
3 * 4, mVertexBuffer);
//設置紋理坐標數(shù)據(jù)
GLES20.glVertexAttribPointer(mTexCoordHandle, 2,
GLES20.GL_FLOAT, false,
2 * 4, mTexCoordBuffer);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
public void onDestroy() {
GLES20.glDeleteProgram(mProgram);
mProgram = 0;
}
}
這是一個抽象類操骡,ShaderManager.Param getProgram()
方法需要子類實現(xiàn),返回不同著色器對應的OpenGL程序參數(shù)赚窃。Camera2FilterNone
是沒有濾鏡效果
/**
* 沒有濾鏡效果
*/
public class Camera2FilterNone extends Camera2BaseFilter {
public Camera2FilterNone(Context context, int textureId) {
super(context, textureId);
}
@Override
protected ShaderManager.Param getProgram() {
return ShaderManager.getParam(ShaderManager.CAMERA_BASE_SHADER);
}
}
對應的著色器是
頂點著色器:
assets/shader/camera2/camera2_vertex_shader_base.glsl
attribute vec4 aPosition;
uniform mat4 uMVPMatrix;
attribute vec4 aTexCoord;
varying vec2 vTextureCoord; //傳給片元著色器
void main()
{
vTextureCoord = (uMVPMatrix * aTexCoord).xy;
gl_Position = aPosition;
}
片元著色器:
assets/shader/camera2/camera2_fragment_shader_base.glsl
#extension GL_OES_EGL_image_external : require
precision mediump float;
uniform samplerExternalOES uTextureSampler;
varying vec2 vTextureCoord;
void main()
{
vec4 vCameraColor = texture2D(uTextureSampler, vTextureCoord);
float fGrayColor = (0.3*vCameraColor.r + 0.59*vCameraColor.g + 0.11*vCameraColor.b);
gl_FragColor = vCameraColor;
}
最終效果圖:
總結(jié):
已是深夜12:22册招,
就不總結(jié)了,從頭看勒极,有什么不理解的地方可以留言提出是掰。
平時11點多睡覺的我,寫文章又熬夜了...
預覽的濾鏡功能放到下一節(jié)辱匿,敬請期待...