android 自定義相機Camera2
阿新 • • 發佈:2019-01-13
上一篇文章我們已經運用Camera自定義了一個相機,今天我們就用Camera2自定義一個相機。Camera2是android5.0新增的api,Camera2與Camera差別比較大,採用了全新的模式,功能更加強大。今天這個例子就是Camera2拍照TextureView上面進行預覽,把之前的SurfaceView代替了。
一、開啟攝像頭
1、TextureView設定監聽
//設定TextureView監聽 tv.setSurfaceTextureListener(surfaceTextureListener);2、在監聽中,可用狀態時開啟攝像頭
/**TextureView的監聽*/ private3、開啟攝像頭(camera2採用的是CameraManager)TextureView.SurfaceTextureListener surfaceTextureListener= new TextureView.SurfaceTextureListener() { //可用 @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { MainActivity.this.width=width; MainActivity.this.height=height; openCamera(); } @Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { } //釋放 @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { stopCamera(); return true; } //更新 @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } };
/**開啟攝像頭*/ private void openCamera() { CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); //設定攝像頭特性 setCameraCharacteristics(manager); try { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { //提示使用者開戶許可權 String[] perms = {"android.permission.CAMERA"}; ActivityCompat.requestPermissions(MainActivity.this,perms, RESULT_CODE_CAMERA); }else { manager.openCamera(mCameraId, stateCallback, null); } } catch (CameraAccessException e){ e.printStackTrace(); } }4、設定攝像頭的一些特性
/**設定攝像頭的引數*/ private void setCameraCharacteristics(CameraManager manager) { try { // 獲取指定攝像頭的特性 CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId); // 獲取攝像頭支援的配置屬性 StreamConfigurationMap map = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // 獲取攝像頭支援的最大尺寸 Size largest = Collections.max( Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),new CompareSizesByArea()); // 建立一個ImageReader物件,用於獲取攝像頭的影象資料 imageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, 2); //設定獲取圖片的監聽 imageReader.setOnImageAvailableListener(imageAvailableListener,null); // 獲取最佳的預覽尺寸 previewSize = chooseOptimalSize(map.getOutputSizes( SurfaceTexture.class), width, height, largest); } catch (CameraAccessException e) { e.printStackTrace(); } catch (NullPointerException e) { } } private static Size chooseOptimalSize(Size[] choices , int width, int height, Size aspectRatio) { // 收集攝像頭支援的大過預覽Surface的解析度 List<Size> bigEnough = new ArrayList<>(); int w = aspectRatio.getWidth(); int h = aspectRatio.getHeight(); for (Size option : choices) { if (option.getHeight() == option.getWidth() * h / w && option.getWidth() >= width && option.getHeight() >= height) { bigEnough.add(option); } } // 如果找到多個預覽尺寸,獲取其中面積最小的 if (bigEnough.size() > 0) { return Collections.min(bigEnough, new CompareSizesByArea()); } else { //沒有合適的預覽尺寸 return choices[0]; } } // 為Size定義一個比較器Comparator static class CompareSizesByArea implements Comparator<Size> { @Override public int compare(Size lhs, Size rhs) { // 強轉為long保證不會發生溢位 return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()); } }二、進行拍照預覽
1、監聽攝像頭
manager.openCamera(mCameraId, stateCallback, null);2、在攝像頭開啟是進行畫面的預覽
/**攝像頭狀態的監聽*/ private CameraDevice.StateCallback stateCallback = new CameraDevice. StateCallback() { // 攝像頭被開啟時觸發該方法 @Override public void onOpened(CameraDevice cameraDevice){ MainActivity.this.cameraDevice = cameraDevice; // 開始預覽 takePreview(); } // 攝像頭斷開連線時觸發該方法 @Override public void onDisconnected(CameraDevice cameraDevice) { MainActivity.this.cameraDevice.close(); MainActivity.this.cameraDevice = null; } // 開啟攝像頭出現錯誤時觸發該方法 @Override public void onError(CameraDevice cameraDevice, int error) { cameraDevice.close(); } };3、進行預覽的設定和處理
/** * 開始預覽 */ private void takePreview() { SurfaceTexture mSurfaceTexture = tv.getSurfaceTexture(); //設定TextureView的緩衝區大小 mSurfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); //獲取Surface顯示預覽資料 Surface mSurface = new Surface(mSurfaceTexture); try { //建立預覽請求 mCaptureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // 設定自動對焦模式 mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); //設定Surface作為預覽資料的顯示介面 mCaptureRequestBuilder.addTarget(mSurface); //建立相機捕獲會話,第一個引數是捕獲資料的輸出Surface列表,第二個引數是CameraCaptureSession的狀態回撥介面,當它建立好後會回撥onConfigured方法,第三個引數用來確定Callback在哪個執行緒執行,為null的話就在當前執行緒執行 cameraDevice.createCaptureSession(Arrays.asList(mSurface,imageReader.getSurface()),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) { } }, null); } catch (CameraAccessException e) { e.printStackTrace(); } }三、拍照
1、進行拍照的處理和設定
/**拍照*/ private void takePicture() { try { if (cameraDevice == null) { return; } // 建立拍照請求 captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); // 設定自動對焦模式 captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 將imageReader的surface設為目標 captureRequestBuilder.addTarget(imageReader.getSurface()); // 獲取裝置方向 int rotation = getWindowManager().getDefaultDisplay().getRotation(); // 根據裝置方向計算設定照片的方向 captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION , ORIENTATIONS.get(rotation)); // 停止連續取景 mPreviewSession.stopRepeating(); //拍照 CaptureRequest captureRequest = captureRequestBuilder.build(); //設定拍照監聽 mPreviewSession.capture(captureRequest,captureCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); }2、監聽拍照結果(成功後恢復預覽)
/**監聽拍照結果*/ private CameraCaptureSession.CaptureCallback captureCallback= new CameraCaptureSession.CaptureCallback() { // 拍照成功 @Override public void onCaptureCompleted(CameraCaptureSession session,CaptureRequest request,TotalCaptureResult result) { // 重設自動對焦模式 captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); // 設定自動曝光模式 captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); try { //重新進行預覽 mPreviewSession.setRepeatingRequest(mCaptureRequest, null, null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) { super.onCaptureFailed(session, request, failure); } };四、拍照的圖片儲存到本地相簿(用的是ImageReader進行接收圖片)
/**監聽拍照的圖片*/ private ImageReader.OnImageAvailableListener imageAvailableListener= new ImageReader.OnImageAvailableListener() { // 當照片資料可用時激發該方法 @Override public void onImageAvailable(ImageReader reader) { //先驗證手機是否有sdcard String status = Environment.getExternalStorageState(); if (!status.equals(Environment.MEDIA_MOUNTED)) { Toast.makeText(getApplicationContext(), "你的sd卡不可用。", Toast.LENGTH_SHORT).show(); return; } // 獲取捕獲的照片資料 Image image = reader.acquireNextImage(); ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); //手機拍照都是存到這個路徑 String filePath = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/"; String picturePath = System.currentTimeMillis() + ".jpg"; File file = new File(filePath, picturePath); try { //存到本地相簿 FileOutputStream fileOutputStream = new FileOutputStream(file); fileOutputStream.write(data); fileOutputStream.close(); //顯示圖片 BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options); iv.setImageBitmap(bitmap); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { image.close(); } } };五、完整的程式碼
1、activity程式碼
package com.sunshanglei.camera.oneselfcamera; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.ImageFormat; 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.CameraMetadata; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.Image; import android.media.ImageReader; import android.os.Environment; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Size; import android.util.SparseIntArray; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.Toast; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; /** * use 自定義相機Camera2 * author 孫尚磊 * create time 2017-4-25 */ public class MainActivity extends AppCompatActivity{ private TextureView tv; private Button btn; private String mCameraId = "0";//攝像頭id(通常0代表後置攝像頭,1代表前置攝像頭) private final int RESULT_CODE_CAMERA=1;//判斷是否有拍照許可權的標識碼 private CameraDevice cameraDevice; private CameraCaptureSession mPreviewSession; private CaptureRequest.Builder mCaptureRequestBuilder,captureRequestBuilder; private CaptureRequest mCaptureRequest; private ImageReader imageReader; private int height=0,width=0; private Size previewSize; private ImageView iv; private static final SparseIntArray ORIENTATIONS = new SparseIntArray(); static { ORIENTATIONS.append(Surface.ROTATION_0, 90); ORIENTATIONS.append(Surface.ROTATION_90, 0); ORIENTATIONS.append(Surface.ROTATION_180, 270); ORIENTATIONS.append(Surface.ROTATION_270, 180); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextureView) findViewById(R.id.tv); btn = (Button) findViewById(R.id.btn); iv= (ImageView) findViewById(R.id.iv); //拍照 btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { takePicture(); } }); //設定TextureView監聽 tv.setSurfaceTextureListener(surfaceTextureListener); } @Override protected void onPause() { super.onPause(); if(cameraDevice!=null) { stopCamera(); } } @Override protected void onResume() { super.onResume(); startCamera(); } /**TextureView的監聽*/ private TextureView.SurfaceTextureListener surfaceTextureListener= new TextureView.SurfaceTextureListener() { //可用 @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { MainActivity.this.width=width; MainActivity.this.height=height; openCamera(); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { } //釋放 @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { stopCamera(); return true; } //更新 @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } }; /**開啟攝像頭*/ private void openCamera() { CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); //設定攝像頭特性 setCameraCharacteristics(manager); try { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { //提示使用者開戶許可權 String[] perms = {"android.permission.CAMERA"}; ActivityCompat.requestPermissions(MainActivity.this,perms, RESULT_CODE_CAMERA); }else { manager.openCamera(mCameraId, stateCallback, null); } } catch (CameraAccessException e){ e.printStackTrace(); } } /**設定攝像頭的引數*/ private void setCameraCharacteristics(CameraManager manager) { try { // 獲取指定攝像頭的特性 CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId); // 獲取攝像頭支援的配置屬性 StreamConfigurationMap map = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // 獲取攝像頭支援的最大尺寸 Size largest = Collections.max( Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),new CompareSizesByArea()); // 建立一個ImageReader物件,用於獲取攝像頭的影象資料 imageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, 2); //設定獲取圖片的監聽 imageReader.setOnImageAvailableListener(imageAvailableListener,null); // 獲取最佳的預覽尺寸 previewSize = chooseOptimalSize(map.getOutputSizes( SurfaceTexture.class), width, height, largest); } catch (CameraAccessException e) { e.printStackTrace(); } catch (NullPointerException e) { } } private static Size chooseOptimalSize(Size[] choices , int width, int height, Size aspectRatio) { // 收集攝像頭支援的大過預覽Surface的解析度 List<Size> bigEnough = new ArrayList<>(); int w = aspectRatio.getWidth(); int h = aspectRatio.getHeight(); for (Size option : choices) { if (option.getHeight() == option.getWidth() * h / w && option.getWidth() >= width && option.getHeight() >= height) { bigEnough.add(option); } } // 如果找到多個預覽尺寸,獲取其中面積最小的 if (bigEnough.size() > 0) { return Collections.min(bigEnough, new CompareSizesByArea()); } else { //沒有合適的預覽尺寸 return choices[0]; } } // 為Size定義一個比較器Comparator static class CompareSizesByArea implements Comparator<Size> { @Override public int compare(Size lhs, Size rhs) { // 強轉為long保證不會發生溢位 return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()); } } /**攝像頭狀態的監聽*/ private CameraDevice.StateCallback stateCallback = new CameraDevice. StateCallback() { // 攝像頭被開啟時觸發該方法 @Override public void onOpened(CameraDevice cameraDevice){ MainActivity.this.cameraDevice = cameraDevice; // 開始預覽 takePreview(); } // 攝像頭斷開連線時觸發該方法 @Override public void onDisconnected(CameraDevice cameraDevice) { MainActivity.this.cameraDevice.close(); MainActivity.this.cameraDevice = null; } // 開啟攝像頭出現錯誤時觸發該方法 @Override public void onError(CameraDevice cameraDevice, int error) { cameraDevice.close(); } }; /** * 開始預覽 */ private void takePreview() { SurfaceTexture mSurfaceTexture = tv.getSurfaceTexture(); //設定TextureView的緩衝區大小 mSurfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); //獲取Surface顯示預覽資料 Surface mSurface = new Surface(mSurfaceTexture); try { //建立預覽請求 mCaptureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // 設定自動對焦模式 mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); //設定Surface作為預覽資料的顯示介面 mCaptureRequestBuilder.addTarget(mSurface); //建立相機捕獲會話,第一個引數是捕獲資料的輸出Surface列表,第二個引數是CameraCaptureSession的狀態回撥介面,當它建立好後會回撥onConfigured方法,第三個引數用來確定Callback在哪個執行緒執行,為null的話就在當前執行緒執行 cameraDevice.createCaptureSession(Arrays.asList(mSurface,imageReader.getSurface()),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) { } }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } /**拍照*/ private void takePicture() { try { if (cameraDevice == null) { return; } // 建立拍照請求 captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); // 設定自動對焦模式 captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // 將imageReader的surface設為目標 captureRequestBuilder.addTarget(imageReader.getSurface()); // 獲取裝置方向 int rotation = getWindowManager().getDefaultDisplay().getRotation(); // 根據裝置方向計算設定照片的方向 captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION , ORIENTATIONS.get(rotation)); // 停止連續取景 mPreviewSession.stopRepeating(); //拍照 CaptureRequest captureRequest = captureRequestBuilder.build(); //設定拍照監聽 mPreviewSession.capture(captureRequest,captureCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); } } /**監聽拍照結果*/ private CameraCaptureSession.CaptureCallback captureCallback= new CameraCaptureSession.CaptureCallback() { // 拍照成功 @Override public void onCaptureCompleted(CameraCaptureSession session,CaptureRequest request,TotalCaptureResult result) { // 重設自動對焦模式 captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL); // 設定自動曝光模式 captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); try { //重新進行預覽 mPreviewSession.setRepeatingRequest(mCaptureRequest, null, null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) { super.onCaptureFailed(session, request, failure); } }; /**監聽拍照的圖片*/ private ImageReader.OnImageAvailableListener imageAvailableListener= new ImageReader.OnImageAvailableListener() { // 當照片資料可用時激發該方法 @Override public void onImageAvailable(ImageReader reader) { //先驗證手機是否有sdcard String status = Environment.getExternalStorageState(); if (!status.equals(Environment.MEDIA_MOUNTED)) { Toast.makeText(getApplicationContext(), "你的sd卡不可用。", Toast.LENGTH_SHORT).show(); return; } // 獲取捕獲的照片資料 Image image = reader.acquireNextImage(); ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); //手機拍照都是存到這個路徑 String filePath = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/"; String picturePath = System.currentTimeMillis() + ".jpg"; File file = new File(filePath, picturePath); try { //存到本地相簿 FileOutputStream fileOutputStream = new FileOutputStream(file); fileOutputStream.write(data); fileOutputStream.close(); //顯示圖片 BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2; Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options); iv.setImageBitmap(bitmap); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { image.close(); } } }; @Override public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults){ switch(permsRequestCode){ case RESULT_CODE_CAMERA: boolean cameraAccepted = grantResults[0]==PackageManager.PERMISSION_GRANTED; if(cameraAccepted){ //授權成功之後,呼叫系統相機進行拍照操作等 openCamera(); }else{ //使用者授權拒絕之後,友情提示一下就可以了 Toast.makeText(MainActivity.this,"請開啟應用拍照許可權",Toast.LENGTH_SHORT).show(); } break; } } /**啟動拍照*/ private void startCamera(){ if (tv.isAvailable()) { if(cameraDevice==null) { openCamera(); } } else { tv.setSurfaceTextureListener(surfaceTextureListener); } } /** * 停止拍照釋放資源*/ private void stopCamera(){ if(cameraDevice!=null){ cameraDevice.close(); cameraDevice=null; } } }
2、xml程式碼
<?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"> <TextureView android:id="@+id/tv" android:layout_above="@+id/ll_bottom" android:layout_width=<