Android開發 Camera2開發_1_拍照功能開發

 介紹

  google已經在Android5.1以後取消了對Camera1的更新,轉而提供了功能更增強大的Camera2.雖然新版本依然可使用Camera1可是,不論是各類機型適配仍是拍照參數自定義都是很雞肋的.跟上最新的技術瞭解Camera2是必要的.關於Camera2的兼容通常是支持API22以後包括API22的Android版本,可是也發現一些機型(好比三星)在API22版本上並無支持Camera2.java

須要使用的API介紹

由於Camera2提供的功能更增強大,因此使用比Camera1會複雜許多.須要調用的API和回調也更多.這裏簡單介紹一下這些API的對應功能.好初步認識Camera2.android

CameraManager

攝像頭管理類:算法

  主要有4個功能:緩存

  1. 獲取攝像頭的ID
  2. 獲取攝像頭的特徵信息(好比攝像頭先後位置信息和支持的分辨率信息等等)
  3. 打開指定id的攝像頭
  4. 打開和關閉閃光燈

CameraDevice

攝像頭設備類:session

  主要功能有3個app

  1. 建立獲取數據請求類CaptureRequest.Builder(或者叫捕獲請求),下面會介紹這個類
  2. 建立獲取數據會話(建立預覽或者拍照的會話通道)
  3. 關閉攝像頭

CameraDevice.StateCallback

攝像頭狀態接口回調類:ide

  主要是負責回調攝像頭的開啓/斷開/異常/銷燬.咱們使用CameraManager打開指定id的攝像頭時須要添加這個回調.oop

CameraCaptureSession.StateCallback

獲取數據會話的狀態接口回調類:ui

  咱們建立相機預覽圖像/拍照/錄像都須要這個回調類,來告訴咱們獲取數據會話的通道狀態是配置成功或者配置失敗.它還負責給咱們回調一個重要的CameraCaptureSession提供給咱們操做,這個CameraCaptureSession類我下面會介紹this

CameraCaptureSession.CaptureCallback

獲取數據會話的數據接口回調類:

  負責回調獲取數據的生命週期(好比開始/進行中/完成/失敗等等),若是你並不須要對生命週期裏作操做,因此有時候沒有啥做用.可是它也是必需建立的一個回調接口類,是在建立預覽圖像/拍照/錄像的時候添加進去,可是拍照或者錄像的數據都不在這個回調接口裏出來(一開始很容易誤解,覺得拍照數據會從這裏返回).除了回調獲取數據的生命週期,還能夠在回調方法裏獲取拍照或者錄製過程的的一些參數信息,好比圖片的Size/分辨率等等.

CaptureRequest.Builder

獲取數據請求配置類:

  很重要,也是咱們頻繁操做的一個配置類.由CameraDevice類建立.主要負責

  1. 設置返回數據的surface(顯示預覽View好比TextureView的surface 或者 照片ImageReadersurface)
  2. 配置預覽/拍照/錄製的拍照參數,好比自動對焦/自動曝光/拍照自動閃光/設置HZ值/顏色校訂等等你能在系統相機上看到的功能.

數據配置完成後交給CameraCaptureSession會話類,讓CameraCaptureSession操做提供咱們須要的數據,例如圖像預覽或者拍照/錄製視頻

 

CameraCaptureSession

獲取數據會話類:

  很重要,是咱們頻繁操做的一個數據會話類,好比建立預覽/中止預覽/拍照/錄像都要它來操做,它由CameraCaptureSession.StateCallback這個接口回調方法裏回調提供給咱們.

 

ImageReader

圖片讀取類:

  不屬於Camera2Api的類,可是是拍照功能重要的類,照片的數據流由它緩存,而後咱們提取保存到本地成爲圖片文件或者顯示在ImageView裏

Camera2的操做流程

在上面的API介紹裏,你是否是對這麼多的配置類/會話類/接口回調類感到眼花繚亂?是的,Camera2的使用是至關眼花繚亂的,可是咱們抓住一條線慢慢從上面跟到下面就應該能明白是怎麼一回事了.下面咱們來簡單介紹一些Camera2的操做流程:

初始化流程:

  1. 初始化動態受權,這是基本操做
  2. 初始化一個子線程的Handler,Camera2的操做能夠放在主線程也能夠放在子線程.按例通常都是子線程裏,可是Camera2只須要咱們提供一個子線程的Handler就好了.
  3. 初始化ImageReader,這個沒有初始化順序要求,而且它有數據回調接口,接口回調的圖片數據咱們直接保存到內部存儲空間,因此提早初始化提供給後續使用.
  4. 初始化TextureView,添加TextureView的接口回調.
  5. 在TextureView的接口回調裏回調啓用成功方法後,咱們開始初始化相機管理類initCameraManager
  6. 而後繼續初始化CameraDevice.StateCallback 攝像頭設備狀態接口回調類,先初始化提供給後續使用.(在這個接口類的開啓相機的回調方法裏,咱們須要實現建立預覽圖像請求配置和建立獲取數據會話)
  7. 繼續初始化CameraCaptureSession.StateCallback 攝像頭獲取數據會話類的狀態接口回調類,先初始化提供給後續使用.(在這個接口類的配置成功回調方法裏,咱們須要實現預覽圖像或者實現拍照)
  8. 繼續初始化CameraCaptureSession.CaptureCallback 攝像頭獲取數據會話類的獲取接口回調類,先初始化提供給後續使用.(啥都不幹)
  9. 判斷攝像頭先後,選擇對應id
  10. 打開指定id的攝像頭
  11. 實現拍照

邏輯流程:

動態相機權限獲取 >> 設置TextureView回調 >> TextureView啓用成功回調方法觸發 >> 選擇攝像頭 >> 打開相機 >> 相機開啓回調方法觸發 >> 建立CaptureRequest.Builder配置類 >> 設置配置類圖像預覽模式 >>  配置類導入須要顯示預覽的TextureView的surface >> 建立數據會話 >> 數據會話的配置成功回調方法觸發 >> 建立預覽圖像 >> 預覽圖像顯示成功 >> 按鍵點擊拍照 >> 建立新的CaptureRequest.Builder配置類,添加目標爲拍照 >> 配置類導入ImageReader的surface >> 數據會話使用這個配置類建立拍照 >> ImageReader的接口類圖片可用方法觸發 >> 保存圖片

 

代碼部分

實現簡單的拍照功能demo

 

package demo.yt.com.demo;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
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.os.Looper;
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 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 Camera2Activity 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 Handler mMainHandler = new Handler(Looper.getMainLooper());
    private Handler mChildHanler = 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 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,"TextureView 變化");

            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                Log.e(TAG,"TextureView 銷燬");
                return false;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surface) {

            }
        });
    }

    /**
     * 初始化子線程
     */
    private void initChildThread() {
        HandlerThread handlerThread = new HandlerThread("camera2");
        handlerThread.start();
        mChildHanler = new Handler(handlerThread.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 {

                    SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
                    Size matchingSize = getMatchingSize();
                    surfaceTexture.setDefaultBufferSize(matchingSize.getWidth(),matchingSize.getHeight());//設置預覽的圖像尺寸
                    Surface surface = new Surface(surfaceTexture);

//                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(surface); //添加目標
                    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(surface, mImageReader.getSurface()), mSessionStateCallback, mChildHanler);
                } 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, mChildHanler);


//                    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, mMainHandler);
                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();
                }


            }
        },mChildHanler);
    }

    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,mChildHanler); //獲取拍照
        } 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:
        }
    }
}

 

 

比較規範的拍照功能demo

上面的只是簡簡單單的demo,下面是實際使用的樣子,其實差很少,貼出來也是多一個參考

 

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;

    @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;
                        }

                    }
                }
                if (mCurrentSelectSize != null) { //若是不等於null 說明已經找到了 跳出循環
                    break;
                }
            }
        } 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 false;
            }

            @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());
                    Surface surface = 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(surface);
                    mCameraDevice.createCaptureSession(Arrays.asList(surface, 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 (mCameraDevice != null){
            mCameraDevice.close();
            mCameraDevice = null;
        }
        mCameraManager = null;
       if (mChildHandler != null){
           mChildHandler.removeCallbacksAndMessages(null);
           mChildHandler = null;

       }
    }

}
相關文章
相關標籤/搜索