Android API 21新增了Camera2,這與之前的camera架構完全不同,使用起來也比較復雜,但是功能變得很強大。
在講解開啟預覽之前,首先需要了解camera2的幾個比較重要的類:
- CameraManager: 管理手機上的所有攝像頭設備,它的作用主要是獲取攝像頭列表和打開指定的攝像頭
- CameraDevice: 具體的攝像頭設備,它有一系列參數(預覽尺寸、拍照尺寸等),可以通過CameraManager的getCameraCharacteristics()方法獲取。它的作用主要是創建CameraCaptureSession和CaptureRequest
- CameraCaptureSession: 相機捕獲會話,用於處理拍照和預覽的工作(很重要)
- CaptureRequest: 捕獲請求,定義輸出緩沖區以及顯示界面(TextureView或SurfaceView)等
下面梳理一下使用Camera2進行預覽和拍照的主要流程:

1、定義TextureView作為預覽界面
在布局文件中加入TextureView控件,然后實現其監聽事件
textureView = (TextureView) findViewById(R.id.textureView);
然后我們可以在OnResume()方法中設置監聽SurefaceTexture的事件
textureView.setSurfaceTextureListener(textureListener);
當SurefaceTexture准備好后會回調SurfaceTextureListener 的onSurfaceTextureAvailable()方法:
TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { //當SurefaceTexture可用的時候,設置相機參數並打開相機 setupCamera(width, height); openCamera(); } };
2、設置相機參數
為了更好地預覽,我們根據TextureView的尺寸設置預覽尺寸,Camera2中使用CameraManager來管理攝像頭
private void setupCamera(int width, int height) { //獲取攝像頭的管理者CameraManager CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); try { //遍歷所有攝像頭 for (String cameraId: manager.getCameraIdList()) { 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(); } }
3、開啟相機
Camera2中打開相機也需要通過CameraManager類:
private void openCamera() { //獲取相機的管理者CameraManager CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); //檢查權限 try { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { return; } //打開相機,第一個參數指示打開哪個攝像頭,第二個參數stateCallback為相機的狀態回調接口,第三個參數用來確定Callback在哪個線程執行,為null的話就在當前線程執行 manager.openCamera(mCameraId, stateCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); } }
實現StateCallback 接口,當相機打開后會回調onOpened方法,在這個方法里面開啟預覽
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { mCameraDevice = camera; //開啟預覽 startPreview(); } }
4、開啟相機預覽
我們使用TextureView顯示相機預覽數據,Camera2的預覽和拍照數據都是使用CameraCaptureSession會話來請求的
private void startPreview() { setupImageReader(); SurfaceTexture mSurfaceTexture = textureView.getSurfaceTexture(); //設置TextureView的緩沖區大小 mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); //獲取Surface顯示預覽數據 mPreviewSurface = new Surface(mSurfaceTexture); try { getPreviewRequestBuilder(); //創建相機捕獲會話,第一個參數是捕獲數據的輸出Surface列表,第二個參數是CameraCaptureSession的狀態回調接口,當它創建好后會回調onConfigured方法,第三個參數用來確定Callback在哪個線程執行,為null的話就在當前線程執行 mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { mCaptureSession = session; repeatPreview(); } @Override public void onConfigureFailed(CameraCaptureSession session) { } }, null); } catch (CameraAccessException e) { e.printStackTrace(); } }
5、實現PreviewCallback
Camera2沒有提供Camera中的PreviewCallback,那么我們如何實現預覽幀數據呢?在Camera2中提供了CameraCaptureSession.CaptureCallback:
private CameraCaptureSession.CaptureCallback mPreviewCaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { } @Override public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { } };
6、實現拍照操作
Camera2拍照是通過ImageReader來實現的,首先先做些准備工作,比如設置拍照參數,如方向、尺寸等
private static final SparseIntArray ORIENTATION = new SparseIntArray(); static { ORIENTATION.append(Surface.ROTATION_0, 90); ORIENTATION.append(Surface.ROTATION_90, 0); ORIENTATION.append(Surface.ROTATION_180, 270); ORIENTATION.append(Surface.ROTATION_270, 180); }
創建一個ImageReader,並監聽它的事件:
private void setupImageReader() { //前三個參數分別是需要的尺寸和格式,最后一個參數代表每次最多獲取幾幀數據 mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 1); //監聽ImageReader的事件,當有圖像流數據可用時會回調onImageAvailable方法,它的參數就是預覽幀數據,可以對這幀數據進行處理 mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { Log.i(TAG, "Image Available!"); Image image = reader.acquireLatestImage(); // 開啟線程異步保存圖片 new Thread(new ImageSaver(image)).start(); } }, null); }
注意:一定要調用reader.acquireLatestImage()和close()方法,否則預覽畫面就會卡住.
創建保存圖片的線程
public static class ImageSaver implements Runnable { private Image mImage; public ImageSaver(Image image) { mImage = image; } @Override public void run() { ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); mImageFile = new File(Environment.getExternalStorageDirectory() + "/DCIM/myPicture.jpg"); FileOutputStream fos = null; try { fos = new FileOutputStream(mImageFile); fos.write(data, 0 ,data.length); } catch (IOException e) { e.printStackTrace(); } finally { mImageFile = null; if (fos != null) { try { fos.close(); fos = null; } catch (IOException e) { e.printStackTrace(); } } } } }
現在准備工作做好了,還需要響應點擊拍照事件,我們設置點擊拍照按鈕調用capture()方法,capture()方法即實現拍照:
private void capture() { try { //首先我們創建請求拍照的CaptureRequest final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); //獲取屏幕方向 int rotation = getWindowManager().getDefaultDisplay().getRotation(); mCaptureBuilder.addTarget(mPreviewSurface); mCaptureBuilder.addTarget(mImageReader.getSurface()); //設置拍照方向 mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation)); //停止預覽 mCaptureSession.stopRepeating(); //開始拍照,然后回調上面的接口重啟預覽,因為mCaptureBuilder設置ImageReader作為target,所以會自動回調ImageReader的onImageAvailable()方法保存圖片 CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { repeatPreview(); } }; mCaptureSession.capture(mCaptureBuilder.build(), captureCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); } }
示例代碼:https://github.com/renhui/RHCamera2
Google官方Demo:https://github.com/googlearchive/android-Camera2Basic
