Android Camera2

Android 5 (API 21, L) 新增加了Camera2,和之前的Camera使用起來較複雜,但是多了許多功能,支援RAW輸出、調整對焦模式、曝光模式等。

Camera2

首先先了解幾個重要的Class

  • CameraManager : 管理攝影設備,主要功用是獲取所有攝影設備和打開指定的攝影鏡頭。
  • CameraDevice : 攝影設備,可透過CameraManager.openCamera()來取得。
  • CameraCaptureSession : 用於處理拍照及預覽工作。
  • CaptureRequest :捕捉畫面請求,用來定義輸出緩衝區及顯示 (TextureView or SurfaceView)

我們在畫面中加入一個TextureView,用來預覽攝影鏡頭目前捕捉到的畫面。
並設定Listener監聽TextureView是否已設置好。

程式參數

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private TextureView mTextureView;
private Size mPreviewSize;

private CameraDevice mCameraDevice;
private String mCameraId;

private ImageReader mImageReader;
private CaptureRequest.Builder mCaptureRequestBuilder;
private CaptureRequest mCaptureRequest;
private CameraCaptureSession mPreviewSession;

private static String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
};

private static final int RC_HANDLE_CAMERA_PERM = 2;
MainActivity: onCreate
1
2
mTextureView = findViewById(R.id.textureView);
mTextureView.setSurfaceTextureListener(textureListener);

當TextureView設置好之後,再來設置並打開攝影鏡頭。

MainActivity.java
1
2
3
4
5
6
7
8
9
TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
//設置攝影鏡頭
setupCamera(width, height);
//打開攝影鏡頭。
openCamera();
}
};

利用CamaraManager取得所有攝影鏡頭的資訊,此例子使用後置鏡頭,我們可根據CameraCharacteristics屬性略過前置鏡頭並開啟後置鏡頭。

由於攝影鏡頭支援眾多格式和大小,可由StreamConfigurationMap取得這些屬性。根據TextureView的長寬,設定攝影鏡頭的格式與大小。

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void setupCamera(int width, int height) {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
//獲取所有攝影設備
String[] cameraList = manager.getCameraIdList();
for (String cameraId: cameraList) {
//取得攝影設備屬性
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
//不擷取前置鏡頭的畫面
if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)
continue;
//取得StreamConfigurationMap,取得攝影鏡頭支持的所有輸出格式和尺寸
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
//根據TextureView的尺寸設置預覽尺寸
mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class), width, height);
mCameraId = cameraId;
break;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//選擇sizeMap中最接近width和height的尺寸
private Size getOptimalSize(Size[] sizeMap, int width, int height) {
List<Size> sizeList = new ArrayList<>();
for (Size option : sizeMap) {
if (width > height) {
if (option.getWidth() > width && option.getHeight() > height) {
sizeList.add(option);
}
} else {
if (option.getWidth() > height && option.getHeight() > width) {
sizeList.add(option);
}
}
}
if (sizeList.size() > 0) {
return Collections.min(sizeList, new Comparator<Size>() {
@Override
public int compare(Size lhs, Size rhs) {
return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getWidth() * rhs.getHeight());
}
});
}
return sizeMap[0];
}

根據mCameraId來決定要開啟的鏡頭,並設置CameraDevice.StateCallback監聽狀態。

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void openCamera() {
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
//檢查權限
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
//請求權限
ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, RC_HANDLE_CAMERA_PERM);
return;
}
//根據mCameraId來決定要開啟的鏡頭
manager.openCamera(mCameraId, mStateCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
mCameraDevice = camera;
//預覽畫面
startPreview();
}

@Override
public void onDisconnected(@NonNull CameraDevice camera) {
Log.e(TAG, "onDisconnected");
}

@Override
public void onError(@NonNull CameraDevice camera, int error) {
Log.e(TAG, "onError: " + error);
}
};

設置預覽畫面,將捕捉到的畫面顯示於TextureView中。

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void startPreview() {
SurfaceTexture mSurfaceTexture = mTextureView.getSurfaceTexture();
//設置TextureView的緩衝區大小
mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
//獲取Surface顯示預覽數據
Surface mSurface = new Surface(mSurfaceTexture);
try {
//創建預覽請求CaptureRequestBuilder,TEMPLATE_PREVIEW
mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//設置Surface作為預覽顯示界面
mCaptureRequestBuilder.addTarget(mSurface);
//創建捕捉畫面,並設置callback
mCameraDevice.createCaptureSession(Arrays.asList(mSurface),mCaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

當預覽請求設置好之後,設定Repeat,讓畫面持續更新。

MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private final CameraCaptureSession.StateCallback mCaptureCallback  = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
try {
//創建捕獲請求
mCaptureRequest = mCaptureRequestBuilder.build();
mPreviewSession = session;
//設置反覆捕獲數據,持續畫面更新
mPreviewSession.setRepeatingRequest(mCaptureRequest, null, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

@Override
public void onConfigureFailed(CameraCaptureSession session) {

}
};

執行結果

作者

Nick Lin

發表於

2018-07-17

更新於

2023-01-18

許可協議


評論