介紹
google已經在Android5.1之后取消了對Camera1的更新,轉而提供了功能更加強大的Camera2.雖然新版本依然可以使用Camera1但是,不管是各種機型適配還是拍照參數自定義都是很雞肋的.跟上最新的技術了解Camera2是必要的.關於Camera2的兼容一般是支持API22之后包括API22的Android版本,但是也發現一些機型(比如三星)在API22版本上並沒有支持Camera2.
需要使用的API介紹
因為Camera2提供的功能更加強大,所以使用比Camera1會復雜許多.需要調用的API和回調也更多.這里簡單介紹一下這些API的對應功能.好初步認識Camera2.
CameraManager
攝像頭管理類:
主要有4個功能:
- 獲取攝像頭的ID
- 獲取攝像頭的特征信息(比如攝像頭前后位置信息和支持的分辨率信息等等)
- 打開指定id的攝像頭
- 打開和關閉閃光燈
CameraDevice
攝像頭設備類:
主要功能有3個
- 創建獲取數據請求類CaptureRequest.Builder(或者叫捕獲請求),下面會介紹這個類
- 創建獲取數據會話(創建預覽或者拍照的會話通道)
- 關閉攝像頭
CameraDevice.StateCallback
攝像頭狀態接口回調類:
主要是負責回調攝像頭的開啟/斷開/異常/銷毀.我們使用CameraManager打開指定id的攝像頭時需要添加這個回調.
CameraCaptureSession.StateCallback
獲取數據會話的狀態接口回調類:
我們創建相機預覽圖像/拍照/錄像都需要這個回調類,來告訴我們獲取數據會話的通道狀態是配置成功或者配置失敗.它還負責給我們回調一個重要的CameraCaptureSession提供給我們操作,這個CameraCaptureSession類我下面會介紹
CameraCaptureSession.CaptureCallback
獲取數據會話的數據接口回調類:
負責回調獲取數據的生命周期(比如開始/進行中/完成/失敗等等),如果你並不需要對生命周期里做操作,所以有時候沒有啥作用.但是它也是必需創建的一個回調接口類,是在創建預覽圖像/拍照/錄像的時候添加進去,但是拍照或者錄像的數據都不在這個回調接口里出來(一開始很容易誤解,以為拍照數據會從這里返回).除了回調獲取數據的生命周期,還可以在回調方法里獲取拍照或者錄制過程的的一些參數信息,比如圖片的Size/分辨率等等.
CaptureRequest.Builder
獲取數據請求配置類:
很重要,也是我們頻繁操作的一個配置類.由CameraDevice類創建.主要負責
- 設置返回數據的surface(顯示預覽View比如TextureView的surface 或者 照片ImageReader的surface)
- 配置預覽/拍照/錄制的拍照參數,比如自動對焦/自動曝光/拍照自動閃光/設置HZ值/顏色校正等等你能在系統相機上看到的功能.
數據配置完成后交給CameraCaptureSession會話類,讓CameraCaptureSession操作提供我們需要的數據,例如圖像預覽或者拍照/錄制視頻
CameraCaptureSession
獲取數據會話類:
很重要,是我們頻繁操作的一個數據會話類,比如創建預覽/停止預覽/拍照/錄像都要它來操作,它由CameraCaptureSession.StateCallback這個接口回調方法里回調提供給我們.
ImageReader
圖片讀取類:
不屬於Camera2Api的類,但是是拍照功能重要的類,照片的數據流由它緩存,然后我們提取保存到本地成為圖片文件或者顯示在ImageView里
Camera2的操作流程
在上面的API介紹里,你是不是對這么多的配置類/會話類/接口回調類感到眼花繚亂?是的,Camera2的使用是相當眼花繚亂的,但是我們抓住一條線慢慢從上面跟到下面就應該能明白是怎么一回事了.下面我們來簡單介紹一些Camera2的操作流程:
初始化流程:
- 初始化動態授權,這是基本操作
- 初始化一個子線程的Handler,Camera2的操作可以放在主線程也可以放在子線程.按例一般都是子線程里,但是Camera2只需要我們提供一個子線程的Handler就行了.
- 初始化ImageReader,這個沒有初始化順序要求,並且它有數據回調接口,接口回調的圖片數據我們直接保存到內部存儲空間,所以提前初始化提供給后續使用.
- 初始化TextureView,添加TextureView的接口回調.
- 在TextureView的接口回調里回調啟用成功方法后,我們開始初始化相機管理類initCameraManager
- 然后繼續初始化CameraDevice.StateCallback 攝像頭設備狀態接口回調類,先初始化提供給后續使用.(在這個接口類的開啟相機的回調方法里,我們需要實現創建預覽圖像請求配置和創建獲取數據會話)
- 繼續初始化CameraCaptureSession.StateCallback 攝像頭獲取數據會話類的狀態接口回調類,先初始化提供給后續使用.(在這個接口類的配置成功回調方法里,我們需要實現預覽圖像或者實現拍照)
- 繼續初始化CameraCaptureSession.CaptureCallback 攝像頭獲取數據會話類的獲取接口回調類,先初始化提供給后續使用.(啥都不干)
- 判斷攝像頭前后,選擇對應id
- 打開指定id的攝像頭
- 實現拍照
邏輯流程:
動態相機權限獲取 >> 設置TextureView回調 >> TextureView啟用成功回調方法觸發 >> 選擇攝像頭 >> 打開相機 >> 相機開啟回調方法觸發 >> 創建CaptureRequest.Builder配置類 >> 設置配置類圖像預覽模式 >> 配置類導入需要顯示預覽的TextureView的surface >> 創建數據會話 >> 數據會話的配置成功回調方法觸發 >> 創建預覽圖像 >> 預覽圖像顯示成功 >> 按鍵點擊拍照 >> 創建新的CaptureRequest.Builder配置類,添加目標為拍照 >> 配置類導入ImageReader的surface >> 數據會話使用這個配置類創建拍照 >> ImageReader的接口類圖片可用方法觸發 >> 保存圖片
代碼部分
實現簡單的拍照功能demo
package demo.yt.com.demo; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; 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.CaptureFailure; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.Image; import android.media.ImageReader; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; 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.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; public class Demo2Activity extends AppCompatActivity { private static final String TAG = Camera2Activity.class.getName(); private String[] permission = {Manifest.permission.CAMERA}; private TextureView mTextureView; //注意使用TextureView需要開啟硬件加速,開啟方法很簡單在AndroidManifest.xml 清單文件里,你需要使用TextureView的activity添加android:hardwareAccelerated="true" private Button mBtnPhotograph; private HandlerThread mHandlerThread; private Handler mChildHandler = null; private CameraManager mCameraManager; //相機管理類,用於檢測系統相機獲取相機id private CameraDevice mCameraDevice; //Camera設備類 private CameraCaptureSession.StateCallback mSessionStateCallback; //獲取的會話類狀態回調 private CameraCaptureSession.CaptureCallback mSessionCaptureCallback; //獲取會話類的獲取數據回調 private CaptureRequest.Builder mCaptureRequest; //獲取數據請求配置類 private CameraDevice.StateCallback mStateCallback; //攝像頭狀態回調 private CameraCaptureSession mCameraCaptureSession; //獲取數據會話類 private ImageReader mImageReader; //照片讀取器 private Surface mSurface; private SurfaceTexture mSurfaceTexture; private String mCurrentCameraId; 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_camera2); mTextureView = findViewById(R.id.textureview); mBtnPhotograph = findViewById(R.id.btn_Photograph); mBtnPhotograph.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { mCameraCaptureSession.stopRepeating();//停止重復 取消任何正在進行的重復捕獲集 在這里就是停止畫面預覽 /* mCameraCaptureSession.abortCaptures(); //終止獲取 盡可能快地放棄當前掛起和正在進行的所有捕獲。 * 這里有一個坑,其實這個並不能隨便調用(我是看到別的demo這么使用,但是其實是錯誤的,所以就在這里備注這個坑). * 最好只在Activity里的onDestroy調用它,終止獲取是耗時操作,需要一定時間重新打開會話通道. * 在這個demo里我並沒有恢復預覽,如果你調用了這個方法關閉了會話又拍照后恢復圖像預覽,會話就會頻繁的開關, * 導致拍照圖片在處理耗時緩存時你又關閉了會話.導致照片緩存不完整並且失敗. * 所以切記不要隨便使用這個方法,會話開啟后並不需要關閉刷新.后續其他拍照/預覽/錄制視頻直接操作這個會話即可 */ takePicture();//拍照 } catch (CameraAccessException e) { e.printStackTrace(); } } }); initPermission(); initChildThread(); initImageReader(); initTextureView(); } /** * 初始化權限 */ private void initPermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, permission, 1); } } private void initTextureView() { mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { Log.e(TAG, "TextureView 啟用成功"); initCameraManager(); initCameraCallback(); initCameraCaptureSessionStateCallback(); initCameraCaptureSessionCaptureCallback(); selectCamera(); openCamera(); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { Log.e(TAG, "SurfaceTexture 變化"); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { Log.e(TAG, "SurfaceTexture 的銷毀"); //這里返回true則是交由系統執行釋放,如果是false則需要自己調用surface.release(); return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } }); } /** * 初始化子線程 */ private void initChildThread() { mHandlerThread = new HandlerThread("camera2"); mHandlerThread.start(); mChildHandler = new Handler(mHandlerThread.getLooper()); } /** * 初始化相機管理 */ private void initCameraManager() { mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); } /** * 獲取匹配的大小 * * @return */ private Size getMatchingSize() { Size selectSize = null; float selectProportion = 0; try { float viewProportion = (float) mTextureView.getWidth() / (float) mTextureView.getHeight(); CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentCameraId); StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG); for (int i = 0; i < sizes.length; i++) { Size itemSize = sizes[i]; float itemSizeProportion = (float) itemSize.getHeight() / (float) itemSize.getWidth(); float differenceProportion = Math.abs(viewProportion - itemSizeProportion); Log.e(TAG, "相減差值比例=" + differenceProportion); if (i == 0) { selectSize = itemSize; selectProportion = differenceProportion; continue; } if (differenceProportion <= selectProportion) { if (differenceProportion == selectProportion) { if (selectSize.getWidth() + selectSize.getHeight() < itemSize.getWidth() + itemSize.getHeight()) { selectSize = itemSize; selectProportion = differenceProportion; } } else { selectSize = itemSize; selectProportion = differenceProportion; } } } } catch (CameraAccessException e) { e.printStackTrace(); } Log.e(TAG, "getMatchingSize: 選擇的比例是=" + selectProportion); Log.e(TAG, "getMatchingSize: 選擇的尺寸是 寬度=" + selectSize.getWidth() + "高度=" + selectSize.getHeight()); return selectSize; } /** * 選擇攝像頭 */ private void selectCamera() { try { String[] cameraIdList = mCameraManager.getCameraIdList();//獲取攝像頭id列表 if (cameraIdList.length == 0) { return; } for (String cameraId : cameraIdList) { Log.e(TAG, "selectCamera: cameraId=" + cameraId); //獲取相機特征,包含前后攝像頭信息,分辨率等 CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(cameraId); Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);//獲取這個攝像頭的面向 //CameraCharacteristics.LENS_FACING_BACK 后攝像頭 //CameraCharacteristics.LENS_FACING_FRONT 前攝像頭 //CameraCharacteristics.LENS_FACING_EXTERNAL 外部攝像頭,比如OTG插入的攝像頭 if (facing == CameraCharacteristics.LENS_FACING_FRONT) { mCurrentCameraId = cameraId; } } } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 初始化攝像頭狀態回調 */ private void initCameraCallback() { mStateCallback = new CameraDevice.StateCallback() { /** * 攝像頭打開時 * @param camera */ @Override public void onOpened(@NonNull CameraDevice camera) { Log.e(TAG, "相機開啟"); mCameraDevice = camera; try { mSurfaceTexture = mTextureView.getSurfaceTexture(); //surfaceTexture 需要手動釋放 Size matchingSize = getMatchingSize(); mSurfaceTexture.setDefaultBufferSize(matchingSize.getWidth(), matchingSize.getHeight());//設置預覽的圖像尺寸 mSurface = new Surface(mSurfaceTexture);//surface最好在銷毀的時候要釋放,surface.release(); // CaptureRequest可以完全自定義拍攝參數,但是需要配置的參數太多了,所以Camera2提供了一些快速配置的參數,如下: // TEMPLATE_PREVIEW :預覽 // TEMPLATE_RECORD:拍攝視頻 // TEMPLATE_STILL_CAPTURE:拍照 // TEMPLATE_VIDEO_SNAPSHOT:創建視視頻錄制時截屏的請求 // TEMPLATE_ZERO_SHUTTER_LAG:創建一個適用於零快門延遲的請求。在不影響預覽幀率的情況下最大化圖像質量。 // TEMPLATE_MANUAL:創建一個基本捕獲請求,這種請求中所有的自動控制都是禁用的(自動曝光,自動白平衡、自動焦點)。 mCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);//創建預覽請求 mCaptureRequest.addTarget(mSurface); //添加surface 實際使用中這個surface最好是全局變量 在onDestroy的時候mCaptureRequest.removeTarget(mSurface);清除,否則會內存泄露 mCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//自動對焦 /** * 創建獲取會話 * 這里會有一個容易忘記的坑,那就是Arrays.asList(surface, mImageReader.getSurface())這個方法 * 這個方法需要你導入后面需要操作功能的所有surface,比如預覽/拍照如果你2個都要操作那就要導入2個 * 否則后續操作沒有添加的那個功能就報錯surface沒有准備好,這也是我為什么先初始化ImageReader的原因,因為在這里就可以拿到ImageReader的surface了 */ mCameraDevice.createCaptureSession(Arrays.asList(mSurface, mImageReader.getSurface()), mSessionStateCallback, mChildHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** *攝像頭斷開時 * @param camera */ @Override public void onDisconnected(@NonNull CameraDevice camera) { } /** * 出現異常情況時 * @param camera * @param error */ @Override public void onError(@NonNull CameraDevice camera, int error) { } /** * 攝像頭關閉時 * @param camera */ @Override public void onClosed(@NonNull CameraDevice camera) { super.onClosed(camera); } }; } /** * 攝像頭獲取會話狀態回調 */ private void initCameraCaptureSessionStateCallback() { mSessionStateCallback = new CameraCaptureSession.StateCallback() { //攝像頭完成配置,可以處理Capture請求了。 @Override public void onConfigured(@NonNull CameraCaptureSession session) { try { mCameraCaptureSession = session; //注意這里使用的是 setRepeatingRequest() 請求通過此捕獲會話無休止地重復捕獲圖像。用它來一直請求預覽圖像 mCameraCaptureSession.setRepeatingRequest(mCaptureRequest.build(), mSessionCaptureCallback, mChildHandler); // mCameraCaptureSession.stopRepeating();//停止重復 取消任何正在進行的重復捕獲集 // mCameraCaptureSession.abortCaptures();//終止獲取 盡可能快地放棄當前掛起和正在進行的所有捕獲。請只在銷毀activity的時候調用它 } catch (CameraAccessException e) { e.printStackTrace(); } } //攝像頭配置失敗 @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { } }; } /** * 攝像頭獲取會話數據回調 */ private void initCameraCaptureSessionCaptureCallback() { mSessionCaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) { super.onCaptureStarted(session, request, timestamp, frameNumber); } @Override public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { super.onCaptureProgressed(session, request, partialResult); } @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { super.onCaptureCompleted(session, request, result); // Log.e(TAG, "onCaptureCompleted: 觸發接收數據"); // Size size = request.get(CaptureRequest.JPEG_THUMBNAIL_SIZE); } @Override public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { super.onCaptureFailed(session, request, failure); } @Override public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session, int sequenceId, long frameNumber) { super.onCaptureSequenceCompleted(session, sequenceId, frameNumber); } @Override public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session, int sequenceId) { super.onCaptureSequenceAborted(session, sequenceId); } @Override public void onCaptureBufferLost(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull Surface target, long frameNumber) { super.onCaptureBufferLost(session, request, target, frameNumber); } }; } /** * 打開攝像頭 */ private void openCamera() { try { if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(mCurrentCameraId, mStateCallback, mChildHandler); return; } Toast.makeText(this, "沒有授權", Toast.LENGTH_SHORT).show(); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 初始化圖片讀取器 */ private void initImageReader() { //創建圖片讀取器,參數為分辨率寬度和高度/圖片格式/需要緩存幾張圖片,我這里寫的2意思是獲取2張照片 mImageReader = ImageReader.newInstance(1080, 1920, ImageFormat.JPEG, 2); mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { // image.acquireLatestImage();//從ImageReader的隊列中獲取最新的image,刪除舊的 // image.acquireNextImage();//從ImageReader的隊列中獲取下一個圖像,如果返回null沒有新圖像可用 Image image = reader.acquireNextImage(); try { File path = new File(Camera2Activity.this.getExternalCacheDir().getPath()); if (!path.exists()) { Log.e(TAG, "onImageAvailable: 路徑不存在"); path.mkdirs(); } else { Log.e(TAG, "onImageAvailable: 路徑存在"); } File file = new File(path, "demo.jpg"); FileOutputStream fileOutputStream = new FileOutputStream(file); // 這里的image.getPlanes()[0]其實是圖層的意思,因為我的圖片格式是JPEG只有一層所以是geiPlanes()[0],如果你是其他格式(例如png)的圖片會有多個圖層,就可以獲取指定圖層的圖像數據 ByteBuffer byteBuffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes); fileOutputStream.write(bytes); fileOutputStream.flush(); fileOutputStream.close(); image.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }, mChildHandler); } private void takePicture() { CaptureRequest.Builder captureRequestBuilder = null; try { captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//自動對焦 captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);//自動爆光 // // 獲取手機方向,如果你的app有提供橫屏和豎屏,那么就需要下面的方法來控制照片為豎立狀態 // int rotation = getWindowManager().getDefaultDisplay().getRotation(); // Log.e(TAG, "takePicture: 手機方向="+rotation); // Log.e(TAG, "takePicture: 照片方向="+ORIENTATIONS.get(rotation)); captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, 270);//我的項目不需要,直接寫死270度 將照片豎立 Surface surface = mImageReader.getSurface(); captureRequestBuilder.addTarget(surface); CaptureRequest request = captureRequestBuilder.build(); mCameraCaptureSession.capture(request, null, mChildHandler); //獲取拍照 } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode) { case 1: if (permissions.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "授權成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "授權失敗", Toast.LENGTH_SHORT).show(); finish(); } break; default: } } @Override protected void onDestroy() { super.onDestroy(); if (mCaptureRequest != null) { mCaptureRequest.removeTarget(mSurface); mCaptureRequest = null; } if (mSurface != null) { mSurface.release(); mSurface = null; } if (mSurfaceTexture != null){ mSurfaceTexture.release(); mSurfaceTexture = null; } if (mCameraCaptureSession != null) { try { mCameraCaptureSession.stopRepeating(); mCameraCaptureSession.abortCaptures(); mCameraCaptureSession.close(); } catch (CameraAccessException e) { e.printStackTrace(); } mCameraCaptureSession = null; } if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } if (null != mImageReader) { mImageReader.close(); mImageReader = null; } if (mChildHandler != null) { mChildHandler.removeCallbacksAndMessages(null); mChildHandler = null; } if (mHandlerThread != null) { mHandlerThread.quitSafely(); mHandlerThread = null; } mCameraManager = null; mSessionStateCallback = null; mSessionCaptureCallback = null; mStateCallback = null; } }
拍照成功后,你下一個要面臨的麻煩可能就是內存泄露,注意要釋放的資源,否則一直持有會導致內存泄露.
一般情況下會有以下幾個操作導致內存泄露:
1.CaptureRequest 如果沒有釋放Surface, 一定操作釋放 mCaptureRequest.removeTarget(mSurface);
2.SurfaceTexture 需要被釋放,除了 mSurfaceTexture.release();
也可以用這種mTextureView.getSurfaceTextureListener().onSurfaceTextureDestroyed(mTextureView.getSurfaceTexture());方式釋放SurfaceTextur,
但是在上面的public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) 回調中你需要返回true或者自己執行 surface.release(); 這樣才會運行onSurfaceTextureDestroyed回調
3.Surface 需要被釋放
4.釋放是需要按順序一個一個釋放的
5.如果你想實現一個工具類來以建造者模式來創建相機拍照,請注意TextureView不能傳入工具類里,因為你需要實現setSurfaceTextureListener監聽,而這個監聽你是無法在工具類里釋放它的,它是需要在activity里才能被釋放,請切記!
你最好只傳入SurfaceTexture給工具類
另外:
關於理解Camera分辨率問題,獲取最合適的預覽與拍照像素請參考我這篇博客:https://www.cnblogs.com/guanxinjing/p/10943966.html
比較規范的拍照功能demo
上面的只是簡簡單單的demo,下面是實際使用的樣子,其實差不多,貼出來也是多一個參考
demo1
public class FaceCameraActivity extends BaseActivity implements View.OnClickListener { private TextureView mTextureView; private Button mBtnCamera; private ImageView mBack; private MaterialDialog mHandlerImageWaitDialog; private CameraManager mCameraManager; private CameraDevice mCameraDevice; private ImageReader mImageReader; private CaptureRequest.Builder mCaptureRequest; private CameraDevice.StateCallback mCameraDeviceStateCallback; private CameraCaptureSession.StateCallback mCameraCaptureSessionStateCallback; private CameraCaptureSession.CaptureCallback mCameraCaptureSessionCaptureCallback; private CameraCaptureSession mCameraCaptureSession; private String mCurrentCameraId; private Size mCurrentSelectSize; private Handler mChildHandler; private Surface mSurface; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initChildThread(); initCameraManager(); initSelectCamera(); initHandlerMatchingSize(); initImageReader(); initTextureViewListener(); initCameraDeviceStateCallbackListener(); initCameraCaptureSessionStateCallbackListener(); initCameraCaptureSessionCaptureCallbackListener(); } @Override public int getLayout() { return R.layout.activity_face_camera; } @Override public void initView() { mBack = findViewById(R.id.back); mTextureView = findViewById(R.id.texture_view); mBtnCamera = findViewById(R.id.btn_camera); mBack.setOnClickListener(this); mBtnCamera.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_camera: if (ButtonDelayUtil.isFastClick()){ handlerImageWaitDialog().show(); stopPreview(); takePicture(); } break; case R.id.back: finish(); break; default: break; } } private void initChildThread() { HandlerThread handlerThread = new HandlerThread("faceCamera"); handlerThread.start(); mChildHandler = new Handler(handlerThread.getLooper()); } /** * 初始化相機管理 */ private void initCameraManager() { mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); } /** * 初始化選擇攝像頭 */ private void initSelectCamera() { try { String[] cameraIdArray = mCameraManager.getCameraIdList(); for (String itemId : cameraIdArray) { CameraCharacteristics itemCharacteristics = mCameraManager.getCameraCharacteristics(itemId); Integer facing = itemCharacteristics.get(CameraCharacteristics.LENS_FACING); if (facing == CameraCharacteristics.LENS_FACING_FRONT) { mCurrentCameraId = itemId; break; } } } catch (CameraAccessException e) { e.printStackTrace(); } if (mCurrentCameraId == null) { finish(); Toast.makeText(this, "此設備不支持前攝像頭", Toast.LENGTH_SHORT).show(); } } /** * 初始化計算適合當前屏幕分辨率的拍照分辨率 * @return */ private void initHandlerMatchingSize() { try { CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentCameraId); StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG); DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); int deviceWidth = displayMetrics.widthPixels; int deviceHeigh = displayMetrics.heightPixels; L.e("當前屏幕密度寬度="+deviceWidth+"高度="+deviceHeigh); for (int j = 1; j < 81; j++) { for (int i = 0; i < sizes.length; i++) { Size itemSize = sizes[i]; if (itemSize.getHeight() < (deviceWidth + j * 5) && itemSize.getHeight() > (deviceWidth - j * 5)) { if (mCurrentSelectSize != null) { //如果之前已經找到一個匹配的寬度 if (Math.abs(deviceHeigh-itemSize.getWidth()) < Math.abs(deviceHeigh - mCurrentSelectSize.getWidth())){ //求絕對值算出最接近設備高度的尺寸 mCurrentSelectSize = itemSize; continue; } }else { mCurrentSelectSize = itemSize; } } } } } catch (CameraAccessException e) { e.printStackTrace(); } L.e("當前預覽寬度="+mCurrentSelectSize.getWidth()+"高度="+mCurrentSelectSize.getHeight()); } private void initImageReader() { L.e("初始化圖片ImageReader的寬="+mCurrentSelectSize.getWidth()+"高="+mCurrentSelectSize.getHeight()); mImageReader = ImageReader.newInstance(mCurrentSelectSize.getWidth() , mCurrentSelectSize.getHeight() , ImageFormat.JPEG , 2); mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { FilePathSession.deleteFaceImageFile(); Image image = reader.acquireLatestImage(); ByteBuffer byteBuffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes); try { FileOutputStream fileOutputStream = new FileOutputStream(FilePathSession.getFaceImagePath()); fileOutputStream.write(bytes); fileOutputStream.flush(); fileOutputStream.close(); image.close(); startPreview(); handlerImageWaitDialog().dismiss(); runOnUiThread(new Runnable() { @Override public void run() { Intent startFaceConfirm = new Intent(FaceCameraActivity.this, FaceConfirmActivity.class); startActivity(startFaceConfirm); FaceCameraActivity.this.finish(); } }); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }, mChildHandler); } private void initTextureViewListener() { mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { openCamera(); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } }); } private void initCameraDeviceStateCallbackListener() { mCameraDeviceStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice camera) { //相機開啟 mCameraDevice = camera; try { SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture(); surfaceTexture.setDefaultBufferSize(mCurrentSelectSize.getWidth(),mCurrentSelectSize.getHeight()); mSurface = new Surface(surfaceTexture); mCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); mCaptureRequest.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);//自動爆光 mCaptureRequest.addTarget(mSurface); mCameraDevice.createCaptureSession(Arrays.asList(mSurface, mImageReader.getSurface()) , mCameraCaptureSessionStateCallback , mChildHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onDisconnected(@NonNull CameraDevice camera) { } @Override public void onError(@NonNull CameraDevice camera, int error) { finish(); Toast.makeText(FaceCameraActivity.this, "相機打開失敗", Toast.LENGTH_SHORT).show(); L.e("CameraDevice.StateCallback onError : 相機異常 error code="+error); } }; } private void initCameraCaptureSessionStateCallbackListener() { mCameraCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { mCameraCaptureSession = session; startPreview(); } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { finish(); Toast.makeText(FaceCameraActivity.this, "相機打開失敗", Toast.LENGTH_SHORT).show(); L.e("CameraCaptureSession.StateCallback onConfigureFailed : CameraCaptureSession會話通道創建失敗"); } }; } private void initCameraCaptureSessionCaptureCallbackListener() { mCameraCaptureSessionCaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) { super.onCaptureStarted(session, request, timestamp, frameNumber); //獲取開始 } @Override public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { super.onCaptureProgressed(session, request, partialResult); //獲取中 } @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { super.onCaptureCompleted(session, request, result); //獲取結束 } @Override public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { super.onCaptureFailed(session, request, failure); //獲取失敗 Toast.makeText(FaceCameraActivity.this, "拍照失敗", Toast.LENGTH_SHORT).show(); L.e("失敗報告Reason="+failure.getReason()); } }; } @SuppressLint("MissingPermission") private void openCamera() { try { mCameraManager.openCamera(mCurrentCameraId, mCameraDeviceStateCallback, mChildHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } private MaterialDialog handlerImageWaitDialog(){ if (mHandlerImageWaitDialog == null){ mHandlerImageWaitDialog = new MaterialDialog.Builder(this) .content("正在處理圖像中...") .progress(true,-1) .cancelable(false) .build(); } return mHandlerImageWaitDialog; } /** * 開始預覽 */ private void startPreview(){ try { mCameraCaptureSession.setRepeatingRequest(mCaptureRequest.build(), mCameraCaptureSessionCaptureCallback, mChildHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 停止預覽 */ private void stopPreview(){ try { mCameraCaptureSession.stopRepeating(); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 拍照 */ private void takePicture(){ try { CaptureRequest.Builder takePictureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); takePictureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//自動對焦 takePictureRequest.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);//自動爆光 int rotation = getWindowManager().getDefaultDisplay().getRotation(); int angle = getJpegOrientation(mCameraManager.getCameraCharacteristics(mCurrentCameraId), rotation); L.i("人臉拍照 照片角度="+angle); takePictureRequest.set(CaptureRequest.JPEG_ORIENTATION, angle); Surface surface = mImageReader.getSurface(); takePictureRequest.addTarget(surface); CaptureRequest request = takePictureRequest.build(); mCameraCaptureSession.capture(request, null, mChildHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 官方提供的JPEG圖片方向算法 * @param c * @param deviceOrientation * @return */ private int getJpegOrientation(CameraCharacteristics c, int deviceOrientation) { if (deviceOrientation == OrientationEventListener.ORIENTATION_UNKNOWN){ return 0; } int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);//獲取傳感器方向 // Round device orientation to a multiple of 90 deviceOrientation = (deviceOrientation + 45) / 90 * 90; // Reverse device orientation for front-facing cameras boolean facingFront = c.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT;//判斷攝像頭面向 if (facingFront) { deviceOrientation = -deviceOrientation; } // Calculate desired JPEG orientation relative to camera orientation to make // the image upright relative to the device orientation int jpegOrientation = (sensorOrientation + deviceOrientation + 360) % 360; return jpegOrientation; } @Override protected void onDestroy() { super.onDestroy(); if (mImageReader != null){ mImageReader.close(); mImageReader = null; } if (mCameraCaptureSession != null){ stopPreview(); try { mCameraCaptureSession.abortCaptures(); } catch (CameraAccessException e) { e.printStackTrace(); } mCameraCaptureSession.close(); mCameraCaptureSession = null; } if (mCaptureRequest != null){ mCaptureRequest.removeTarget(mSurface);//注意釋放mSurface mCaptureRequest = null; } if (mSurface != null){ mSurface.release();//注意釋放mSurface mSurface = null; } //也可以用onSurfaceTextureDestroyed這種方式釋放SurfaceTexture 但是在上面的public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) 回調中你需要返回true或者自己執行 surface.release(); 這步資源釋放很重要 mTextureView.getSurfaceTextureListener().onSurfaceTextureDestroyed(mTextureView.getSurfaceTexture()); mCameraDeviceStateCallback = null; mCameraCaptureSessionStateCallback = null; mCameraCaptureSessionCaptureCallback = null; mCameraManager = null; if (mCameraDevice != null){ mCameraDevice.close(); mCameraDevice = null; } mCameraManager = null; if (mChildHandler != null){ mChildHandler.removeCallbacksAndMessages(null); mChildHandler = null; } } }
Demo2
import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; 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.CaptureResult; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.Image; import android.media.ImageReader; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Log; import android.util.Range; import android.util.Size; import android.view.OrientationEventListener; import android.view.Surface; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.afollestad.materialdialogs.MaterialDialog; import com.soundcloud.android.crop.Crop; import net.wt.gate.dev.R; import net.wt.gate.dev.base.BaseActivity; import net.wt.gate.dev.constant.FileConstant; import net.wt.gate.dev.libs.imageHandle.FileImageHandleListener; import net.wt.gate.dev.libs.imageHandle.ImageHandle; import net.wt.gate.dev.libs.log.L; import net.wt.gate.dev.service.state.State; import net.wt.gate.dev.service.state.StateConfig; import net.wt.gate.dev.service.state.StateMachine; import net.wt.gate.dev.util.ButtonDelayUtil; import java.io.File; import java.io.FileOutputStream; import java.nio.ByteBuffer; import java.util.Arrays; /** * @content:拍照activity * @time:2019-9-24 * @build: */ public class CameraTakeActivity extends BaseActivity implements View.OnClickListener { private static final String TAG = CameraTakeActivity.class.getSimpleName(); private File mTempImageSavePath = null; //臨時拍照圖片路徑 private File mTempCropImageSavePath = null; //臨時裁剪圖片路徑 public static final String IMAGE_SAVE_PATH_KEY = "imageSavePath"; //圖片保存路徑 public static final String CAMERA_FACING_KEY = "cameraFacing"; //攝像頭面向 public static final String CAMERA_TAKE_RESULT_KEY = "cameraTakeResult"; //回調結果 boolean值 true=成功 false=失敗 public static final String CAMERA_TAKE_RESULT_PATH_KEY = "cameraTakeResultPath"; //回調路徑 String值 public static final int FACING_BACK = 1; //后攝像頭 public static final int FACING_FRONT = 2; //前攝像頭 public static final int FACING_EXTERNAL = 3; //外置攝像頭 private File mImageSavePath = null; private Integer mCameraFacing = null; private TextureView mTextureView; private ImageView mBack //返回 , mTake //拍照 , mDelete //刪除 , mSubmit //確認 , mCropFinishImage; //裁剪完成image private TextureView.SurfaceTextureListener mSurfaceTextureListener; private Surface mSurface; private MaterialDialog mHandlerImageWaitDialog , mCompressionImageWaitDialog; private CameraManager mCameraManager; private CameraDevice mCameraDevice; private ImageReader mImageReader; private CaptureRequest.Builder mCaptureRequest; private CameraDevice.StateCallback mCameraDeviceStateCallback; private CameraCaptureSession.StateCallback mCameraCaptureSessionStateCallback; private CameraCaptureSession.CaptureCallback mCameraCaptureSessionCaptureCallback; private CameraCaptureSession mCameraCaptureSession; private String mCurrentCameraId; private Size mCurrentSelectSize; private HandlerThread mHandlerThread; private Handler mChildHandler; private Bitmap mCropBitmap; private boolean isRelease = false; private boolean isStopPreview = false; private boolean isCroping = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getIntentData(); initPath(); initChildThread(); initCameraManager(); if (!initSelectCamera()) { Toast.makeText(CameraTakeActivity.this, R.string.this_device_camera_is_unavailable, Toast.LENGTH_SHORT).show(); Intent intent = new Intent(); intent.putExtra(CAMERA_TAKE_RESULT_KEY, false); setResult(RESULT_OK, intent); finish(); return; } initHandlerMatchingSize(); initImageReader(); initTextureViewListener(); initCameraDeviceStateCallbackListener(); initCameraCaptureSessionStateCallbackListener(); initCameraCaptureSessionCaptureCallbackListener(); } @Override protected int getLayoutID() { return R.layout.activity_camera_take; } @Override protected void initViews() { mBack = findViewById(R.id.back); mTextureView = findViewById(R.id.texture_view); mTake = findViewById(R.id.take); mDelete = findViewById(R.id.delete); mSubmit = findViewById(R.id.submit); mCropFinishImage = findViewById(R.id.crop_finish_image); mDelete.setOnClickListener(this); mSubmit.setOnClickListener(this); mBack.setOnClickListener(this); mTake.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.take: if (ButtonDelayUtil.isFastClick()) { handlerImageWaitDialog().show(); takePicture(); stopPreview(); } break; case R.id.back: finish(); break; case R.id.delete: if (ButtonDelayUtil.isFastClick()) { if (mTempImageSavePath.exists()) { mTempImageSavePath.delete(); } mTake.setVisibility(View.VISIBLE); mDelete.setVisibility(View.GONE); mSubmit.setVisibility(View.GONE); startPreview(); } break; case R.id.submit: if (ButtonDelayUtil.isFastClick()) { isCroping = true; Uri inputFileUri = Uri.fromFile(mTempImageSavePath); Uri outputFileUri = Uri.fromFile(mTempCropImageSavePath); mTake.setVisibility(View.GONE); mSubmit.setVisibility(View.GONE); mDelete.setVisibility(View.GONE); mTextureView.setVisibility(View.GONE); mCropFinishImage.setVisibility(View.VISIBLE); Crop.of(inputFileUri, outputFileUri).asSquare().start(CameraTakeActivity.this); } break; default: break; } } private void getIntentData() { Intent intentData = getIntent(); String path = intentData.getStringExtra(IMAGE_SAVE_PATH_KEY); if (TextUtils.isEmpty(path)) { L.ee(TAG, "您沒有傳入圖片保存地址"); Intent intent = new Intent(); intent.putExtra(CAMERA_TAKE_RESULT_KEY, false); setResult(RESULT_OK, intent); finish(); } mImageSavePath = new File(path); mCameraFacing = intentData.getIntExtra(CAMERA_FACING_KEY, 0); if (mCameraFacing == 0) { L.ee(TAG, "您沒有傳入需要使用的攝像頭"); Intent intent = new Intent(); intent.putExtra(CAMERA_TAKE_RESULT_KEY, false); setResult(RESULT_OK, intent); finish(); } } /** * 初始化路徑 */ private void initPath() { mTempImageSavePath = new File(FileConstant.CAMERA_TAKE_TEMP_IMAGE_FILE); mTempCropImageSavePath = new File(FileConstant.TEMP_CROP_IMAGE_FILE); if (!mTempImageSavePath.getParentFile().isDirectory()) { mTempImageSavePath.getParentFile().mkdirs(); } if (!mTempCropImageSavePath.getParentFile().isDirectory()) { mTempCropImageSavePath.getParentFile().mkdirs(); } if (!mImageSavePath.getParentFile().isDirectory()) { mImageSavePath.getParentFile().mkdirs(); } if (mTempImageSavePath.exists()) { mTempImageSavePath.delete(); } if (mTempCropImageSavePath.exists()) { mTempCropImageSavePath.delete(); } if (mImageSavePath.exists()){ mImageSavePath.delete(); } } private void initChildThread() { mHandlerThread = new HandlerThread("faceCamera"); mHandlerThread.start(); mChildHandler = new Handler(mHandlerThread.getLooper()); } /** * 初始化相機管理 */ private void initCameraManager() { mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); } /** * 初始化選擇攝像頭 * * @return ture=初始化成功 false=初始化失敗 */ private boolean initSelectCamera() { try { String[] cameraIdArray = mCameraManager.getCameraIdList(); Loop:for (String itemId : cameraIdArray) { CameraCharacteristics itemCharacteristics = mCameraManager.getCameraCharacteristics(itemId); Integer facing = itemCharacteristics.get(CameraCharacteristics.LENS_FACING); switch (mCameraFacing){ case FACING_BACK: if (facing == CameraCharacteristics.LENS_FACING_BACK) { mCurrentCameraId = itemId; break Loop; } break; case FACING_FRONT: if (facing == CameraCharacteristics.LENS_FACING_FRONT) { mCurrentCameraId = itemId; break Loop; } break; case FACING_EXTERNAL: if (facing == CameraCharacteristics.LENS_FACING_EXTERNAL) { mCurrentCameraId = itemId; break Loop; } break; default: break; } } } catch (CameraAccessException e) { e.printStackTrace(); } if (mCurrentCameraId == null) { return false; } return true; } /** * 初始化計算適合當前屏幕分辨率的拍照分辨率 * * @return */ private void initHandlerMatchingSize() { try { CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentCameraId); StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG); DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); int deviceWidth = displayMetrics.widthPixels; int deviceHeigh = displayMetrics.heightPixels; for (int j = 1; j < 81; j++) { for (int i = 0; i < sizes.length; i++) { Size itemSize = sizes[i]; if (itemSize.getHeight() < (deviceWidth + j * 5) && itemSize.getHeight() > (deviceWidth - j * 5)) { if (mCurrentSelectSize != null) { //如果之前已經找到一個匹配的寬度 if (Math.abs(deviceHeigh - itemSize.getWidth()) < Math.abs(deviceHeigh - mCurrentSelectSize.getWidth())) { //求絕對值算出最接近設備高度的尺寸 mCurrentSelectSize = itemSize; } } else { mCurrentSelectSize = itemSize; } } } } } catch (CameraAccessException e) { e.printStackTrace(); } ViewGroup.LayoutParams layoutParams = mTextureView.getLayoutParams(); layoutParams.height = mCurrentSelectSize.getWidth(); mTextureView.setLayoutParams(layoutParams); } private void initImageReader() { mImageReader = ImageReader.newInstance(mCurrentSelectSize.getWidth() , mCurrentSelectSize.getHeight() , ImageFormat.JPEG , 1); mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { Image image = reader.acquireLatestImage(); ByteBuffer byteBuffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes); try { FileOutputStream fileOutputStream = new FileOutputStream(mTempImageSavePath); fileOutputStream.write(bytes); fileOutputStream.flush(); fileOutputStream.close(); image.close(); runOnUiThread(new Runnable() { @Override public void run() { mTake.setVisibility(View.GONE); mDelete.setVisibility(View.VISIBLE); mSubmit.setVisibility(View.VISIBLE); handlerImageWaitDialog().dismiss(); } }); } catch (Exception e) { e.printStackTrace(); } } }, mChildHandler); } private void initTextureViewListener() { mSurfaceTextureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { openCamera(); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { surface.release(); return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } }; mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); } private void initCameraDeviceStateCallbackListener() { mCameraDeviceStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice camera) { //相機開啟 mCameraDevice = camera; try { SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture(); surfaceTexture.setDefaultBufferSize(mCurrentSelectSize.getWidth(), mCurrentSelectSize.getHeight()); mSurface = new Surface(surfaceTexture); mCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mCaptureRequest.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, getRange()); mCaptureRequest.set(CaptureRequest.CONTROL_AE_LOCK, false); mCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); mCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_OFF);//關閉自動對焦 mCaptureRequest.addTarget(mSurface); mCameraDevice.createCaptureSession(Arrays.asList(mSurface, mImageReader.getSurface()) , mCameraCaptureSessionStateCallback , mChildHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onDisconnected(@NonNull CameraDevice camera) { } @Override public void onError(@NonNull CameraDevice camera, int error) { Intent intent = new Intent(); intent.putExtra(CAMERA_TAKE_RESULT_KEY, false); setResult(RESULT_OK, intent); finish(); Toast.makeText(CameraTakeActivity.this, "相機打開失敗", Toast.LENGTH_SHORT).show(); } }; } private void initCameraCaptureSessionStateCallbackListener() { mCameraCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { mCameraCaptureSession = session; startPreview(); } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { Intent intent = new Intent(); intent.putExtra(CAMERA_TAKE_RESULT_KEY, false); setResult(RESULT_OK, intent); finish(); Toast.makeText(CameraTakeActivity.this, "相機打開失敗", Toast.LENGTH_SHORT).show(); } }; } private void initCameraCaptureSessionCaptureCallbackListener() { mCameraCaptureSessionCaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) { super.onCaptureStarted(session, request, timestamp, frameNumber); //獲取開始 } @Override public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { super.onCaptureProgressed(session, request, partialResult); //獲取中 } @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { super.onCaptureCompleted(session, request, result); //獲取結束 } @Override public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { super.onCaptureFailed(session, request, failure); //獲取失敗 } }; } @SuppressLint("MissingPermission") private void openCamera() { try { mCameraManager.openCamera(mCurrentCameraId, mCameraDeviceStateCallback, mChildHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } private MaterialDialog handlerImageWaitDialog() { if (mHandlerImageWaitDialog == null) { mHandlerImageWaitDialog = new MaterialDialog.Builder(this) .content("正在處理圖像中...") .progress(true, -1) .cancelable(false) .build(); } return mHandlerImageWaitDialog; } private MaterialDialog compressionImageWaitDialog() { if (mCompressionImageWaitDialog == null) { mCompressionImageWaitDialog = new MaterialDialog.Builder(this) .content("壓縮圖像中...") .progress(true, -1) .cancelable(false) .build(); } return mCompressionImageWaitDialog; } /** * 開始預覽 */ private void startPreview() { try { mCameraCaptureSession.setRepeatingRequest(mCaptureRequest.build(), mCameraCaptureSessionCaptureCallback, mChildHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 停止預覽 */ private void stopPreview() { try { mCameraCaptureSession.stopRepeating(); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 拍照 */ private void takePicture() { try { CaptureRequest.Builder takePictureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); mCaptureRequest.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, getRange());//This line of code is used for adjusting the fps range and fixing the dark preview mCaptureRequest.set(CaptureRequest.CONTROL_AE_LOCK, false); mCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START); mCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_OFF);//關閉自動對焦 int rotation = getWindowManager().getDefaultDisplay().getRotation(); int angle = getJpegOrientation(mCameraManager.getCameraCharacteristics(mCurrentCameraId), rotation); takePictureRequest.set(CaptureRequest.JPEG_ORIENTATION, angle); Surface surface = mImageReader.getSurface(); takePictureRequest.addTarget(surface); CaptureRequest request = takePictureRequest.build(); if (mCameraCaptureSession != null) { mCameraCaptureSession.capture(request, null, mChildHandler); }else { Toast.makeText(context, "拍照異常", Toast.LENGTH_SHORT).show(); finish(); } } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 官方提供的JPEG圖片方向算法 * * @param c * @param deviceOrientation * @return */ private int getJpegOrientation(CameraCharacteristics c, int deviceOrientation) { if (deviceOrientation == OrientationEventListener.ORIENTATION_UNKNOWN) { return 0; } int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);//獲取傳感器方向 // Round device orientation to a multiple of 90 deviceOrientation = (deviceOrientation + 45) / 90 * 90; // Reverse device orientation for front-facing cameras boolean facingFront = c.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT;//判斷攝像頭方向 if (facingFront) { deviceOrientation = -deviceOrientation; } // Calculate desired JPEG orientation relative to camera orientation to make // the image upright relative to the device orientation return (sensorOrientation + deviceOrientation + 360) % 360; } /** * 獲取曝光范圍 * * @return */ private Range<Integer> getRange() { CameraCharacteristics chars = null; try { chars = mCameraManager.getCameraCharacteristics(mCurrentCameraId); } catch (CameraAccessException e) { e.printStackTrace(); } Range<Integer>[] ranges = chars.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); Range<Integer> result = null; for (Range<Integer> range : ranges) { //幀率不能太低,大於10 if (range.getLower() < 10) continue; if (result == null) result = range; //FPS下限小於15,弱光時能保證足夠曝光時間,提高亮度。range范圍跨度越大越好,光源足夠時FPS較高,預覽更流暢,光源不夠時FPS較低,亮度更好。 else if (range.getLower() <= 15 && (range.getUpper() - range.getLower()) > (result.getUpper() - result.getLower())) result = range; } return result; } /** * 壓縮圖片 */ private void compressionImage() { ImageHandle.fileImageConfig(mTempCropImageSavePath, mImageSavePath) .setTargetKB(30) .setHandleListener(new FileImageHandleListener() { @Override public boolean onReady(File inpFile, File outFile) { return true; } @Override public void onSuccess() { Log.e(TAG, "onFailure: 圖片壓縮成功"); runOnUiThread(new Runnable() { @Override public void run() { compressionImageWaitDialog().dismiss(); Intent intent = new Intent(); intent.putExtra(CAMERA_TAKE_RESULT_KEY, true); intent.putExtra(CAMERA_TAKE_RESULT_PATH_KEY, mImageSavePath.toString()); setResult(RESULT_OK, intent); CameraTakeActivity.this.finish(); } }); } @Override public void onFailure(String text) { Log.e(TAG, "onFailure: 失敗原因=" + text); runOnUiThread(new Runnable() { @Override public void run() { compressionImageWaitDialog().dismiss(); Toast.makeText(CameraTakeActivity.this, "壓縮失敗", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(); intent.putExtra(CAMERA_TAKE_RESULT_KEY, false); setResult(RESULT_OK, intent); CameraTakeActivity.this.finish(); } }); } @Override public void onError(Exception e) { Log.e(TAG, "onFailure: 失敗原因=" + e); runOnUiThread(new Runnable() { @Override public void run() { compressionImageWaitDialog().dismiss(); Toast.makeText(CameraTakeActivity.this, "壓縮失敗", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(); intent.putExtra(CAMERA_TAKE_RESULT_KEY, false); setResult(RESULT_OK, intent); CameraTakeActivity.this.finish(); } }); } }).build(); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == Crop.REQUEST_CROP && resultCode == RESULT_OK) { compressionImageWaitDialog().show(); mTake.setVisibility(View.GONE); mSubmit.setVisibility(View.GONE); mDelete.setVisibility(View.GONE); mTextureView.setVisibility(View.GONE); mCropFinishImage.setVisibility(View.VISIBLE); mCropBitmap = BitmapFactory.decodeFile(mTempCropImageSavePath.getAbsolutePath()); mCropFinishImage.setImageBitmap(mCropBitmap); compressionImage(); return; } if (requestCode == Crop.REQUEST_CROP && resultCode == RESULT_CANCELED){ Toast.makeText(this, R.string.cancel_cropping, Toast.LENGTH_SHORT).show(); }else { Toast.makeText(this, R.string.unknown_exception_cropping_failed, Toast.LENGTH_SHORT).show(); } Intent intent = new Intent(); intent.putExtra(CAMERA_TAKE_RESULT_KEY, false); setResult(RESULT_OK, intent); CameraTakeActivity.this.finish(); } private void release(){ if (isRelease){ return; } if (isFinishing()){ if (mTempImageSavePath != null && mTempImageSavePath.exists()) { mTempImageSavePath.delete(); } if (mTempCropImageSavePath != null && mTempCropImageSavePath.exists()) { mTempCropImageSavePath.delete(); } if (mImageReader != null) { mImageReader.close(); mImageReader = null; } if (mCameraCaptureSession != null) { mCameraCaptureSession.close(); mCameraCaptureSession = null; } if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } if (mCaptureRequest != null){ mCaptureRequest.removeTarget(mSurface); mCaptureRequest = null; } if (mSurface != null){ mSurface.release(); mSurface = null; } if (mTextureView.getSurfaceTextureListener() != null) { mTextureView.getSurfaceTextureListener().onSurfaceTextureDestroyed(mTextureView.getSurfaceTexture()); } mCameraDeviceStateCallback = null; mCameraCaptureSessionStateCallback = null; mCameraCaptureSessionCaptureCallback = null; mCameraManager = null; mSurfaceTextureListener = null; if (mChildHandler != null) { mChildHandler.removeCallbacksAndMessages(null); mChildHandler = null; } if (mHandlerThread != null){ mHandlerThread.quitSafely(); mHandlerThread = null; } if (mCompressionImageWaitDialog != null) { mCompressionImageWaitDialog.dismiss(); mCompressionImageWaitDialog = null; } if (mHandlerImageWaitDialog != null) { mHandlerImageWaitDialog.dismiss(); mHandlerImageWaitDialog = null; } mCropFinishImage.setImageDrawable(null); if (mCropBitmap != null) { mCropBitmap.recycle(); mCropBitmap = null; } StateMachine.instance().finishExit(StateConfig.TAKE_PICTURE_KEY); isRelease = true; } } @Override protected void onResume() { super.onResume(); if (isCroping){ return; } if (isStopPreview){ if (mCameraCaptureSession == null || !mCameraCaptureSession.isReprocessable()){ finish(); return; } startPreview(); } } @Override protected void onPause() { super.onPause(); if(isFinishing()){ release(); }else { isStopPreview = true; stopPreview(); } } @Override protected void onDestroy() { super.onDestroy(); release(); } }
end