Android5之后,Camera的api有了很大變化,稱為Camera2
Camera2的功能更多更強大,同時也更加復雜
在camera2中管道pipeline(也可以叫做流),照片,視頻都是從流中獲取的。
分析這張圖,可以簡單得到預覽其實有三步
- 打開相機,創建與相機的會話(即建立管道)
- 發起CaptureRequest,獲取流內容
- 建立Surface,將內容輸出到Surface(即輸出到手機屏幕)
當然,這只是將相機預覽流程進行了抽象,實際相機預覽流程沒有那么簡單,下面是一個簡單的demo,實現相機預覽
入口MainActivity
package com.example.camera2test;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.FrameLayout;
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
private Camera2Preview mCameraPreview;
private FrameLayout parentView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCameraPreview = new Camera2Preview(this);
parentView = (FrameLayout) findViewById(R.id.camera_preview);
parentView.addView((View) mCameraPreview);
findViewById(R.id.button_switch).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCameraPreview.switchCameraFacing();
}
});
}
@Override
protected void onResume() {
super.onResume();
if (mCameraPreview != null)
mCameraPreview.onResume();
}
@Override
protected void onPause() {
super.onPause();
if (mCameraPreview != null)
mCameraPreview.onPause();
}
}
接口CameraView
/**
* 功能描述
*
* @author y50003490
* @since 2019-08-20
*/
public interface CameraView {
/**
* 與生命周期onResume調用
*/
void onResume();
/**
* 與生命周期onPause調用
*/
void onPause();
/**
* 切換相機攝像頭
*/
void switchCameraFacing();
}
相機預覽Camera2Preview
package com.example.camera2test;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import java.util.Arrays;
public class Camera2Preview extends TextureView implements CameraView {
private static final String TAG = "Camera2Preview";
private Context mContext;
public Camera2Preview(Context context) {
this(context, null);
}
public Camera2Preview(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Camera2Preview(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
}
/**
* ID of the current {@link CameraDevice}.
*/
private String mCameraId;
/**
* 相機預覽要用到的{@link CameraCaptureSession } .
*/
private CameraCaptureSession mCaptureSession;
/**
* A reference to the opened {@link CameraDevice}.
*/
private CameraDevice mCameraDevice;
/**
* 相機預覽的 {@link android.util.Size}
*/
private Size mPreviewSize;
/**
* 用於運行不應該阻止UI的任務的附加線程。
*/
private HandlerThread mBackgroundThread;
/**
* 在后台運行任務的Handler。
*/
private Handler mBackgroundHandler;
/**
* {@link CaptureRequest.Builder} for the camera1 preview
*/
private CaptureRequest.Builder mPreviewRequestBuilder;
/**
* {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder}
*/
private CaptureRequest mPreviewRequest;
/**
* 當前相機(前置、后置)
*/
private int mCurrentCameraFacing = CameraCharacteristics.LENS_FACING_BACK;
/**
* 同生命周期 onResume
*/
@Override
public void onResume() {
startBackgroundThread();
// 當關閉並打開屏幕時,SurfaceTexture已經可用,並且不會調用“onSurfaceTextureAvailable”。
// 在這種情況下,我們可以打開相機並從這里開始預覽(否則,我們將等待,直到Surface在SurfaceTextureListener中准備好)。
if (isAvailable()) {
Log.d(TAG, "onResume: openCamera");
openCamera();
} else {
setSurfaceTextureListener(mSurfaceTextureListener);
Log.d(TAG, "onResume: is not Available");
}
}
/**
* 同生命周期 onPause
*/
@Override
public void onPause() {
closeCamera();
stopBackgroundThread();
Log.d(TAG, "onPause: close Camera");
}
/**
* 打開指定的相機(mCameraId)
*/
private void openCamera() {
Log.d(TAG, "openCamera: ");
if (mCameraId == null) {
mCameraId = String.valueOf(0);
}
CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
try {
//檢查相機權限
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "openCamera: no Camera Permission");
return;
}
//打開對應mCameraId的攝像頭
Log.d(TAG, "openCamera: 2");
manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
Log.d(TAG, "openCamera: ERROR");
e.printStackTrace();
} finally {
Log.d(TAG, "finally: openCamera");
}
}
/**
* 關閉相機
*/
private void closeCamera() {
if (null != mCaptureSession) {
mCaptureSession.close();
mCaptureSession = null;
}
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
}
/**
* {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
*/
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
Log.d(TAG, "onOpened: ");
// 當相機打開時調用此方法。我們在這里開始相機預覽。
mCameraDevice = cameraDevice;
//創建相機預覽會話
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
Log.d(TAG, "onDisconnected: ");
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
Log.d(TAG, "onError: ");
cameraDevice.close();
mCameraDevice = null;
Log.e(TAG, "CameraDevice.StateCallback onError errorCode= " + error);
}
};
/**
* 創建一個新的 {@link CameraCaptureSession} 用於相機預覽.
*/
private void createCameraPreviewSession() {
try {
//獲得SurfaceTexture
SurfaceTexture texture = getSurfaceTexture();
if(texture == null){
Log.d(TAG, "createCameraPreviewSession: texture is null");
}
// 我們需要開始預覽輸出Surface
Surface surface = new Surface(texture);
// 我們建立了一個具有輸出Surface的捕獲器。
mPreviewRequestBuilder
= mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);
// 這里,我們創建了一個用於相機預覽的CameraCaptureSession
// test delete m mImageReader.getSurface()
mCameraDevice.createCaptureSession(Arrays.asList(surface),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
Log.d(TAG, "onConfigured: ");
// 相機已經關閉
if (null == mCameraDevice) {
return;
}
// 當session准備好后,我們開始顯示預覽
mCaptureSession = cameraCaptureSession;
try {
// 相機預覽時應連續自動對焦
//mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 設置閃光燈在必要時自動打開
//setAutoFlash(mPreviewRequestBuilder);
// 最終,顯示相機預覽
mPreviewRequest = mPreviewRequestBuilder.build();
//test delte mCaptureCallback
mCaptureSession.setRepeatingRequest(mPreviewRequest,
null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) {
Log.e(TAG, "CameraCaptureSession.StateCallback onConfigureFailed");
}
}, null
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 啟動后台線程和Handler.
*/
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
/**
* 停止后台線程和Handler.
*/
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a
* {@link TextureView}.
*/
private final TextureView.SurfaceTextureListener mSurfaceTextureListener
= new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
configureTransform(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
}
};
/**
* 配置必要的 {@link android.graphics.Matrix} 轉換為 `mTextureView`.
* <p>
* 該方法應該在setUpCameraOutputs中確定相機預覽大小以及“mTextureView”的大小固定之后調用。
*
* @param viewWidth The width of `mTextureView`
* @param viewHeight The height of `mTextureView`
*/
private void configureTransform(int viewWidth, int viewHeight) {
if (null == mPreviewSize || null == mContext) {
return;
}
WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
int rotation = mWindowManager.getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / mPreviewSize.getHeight(),
(float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
} else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180, centerX, centerY);
}
setTransform(matrix);
}
@Override
public void switchCameraFacing() {
mCurrentCameraFacing = mCurrentCameraFacing == CameraCharacteristics.LENS_FACING_BACK ?
CameraCharacteristics.LENS_FACING_FRONT : CameraCharacteristics.LENS_FACING_BACK;
mCameraId = String.valueOf(mCurrentCameraFacing);
if (mCameraId == null) {
mCameraId = String.valueOf(1);
}
closeCamera();
openCamera();
}
}