Android Camera2拍照流程

Android Camera2拍照流程

最近在看Camera2相關的內容,初看感受調用比較複雜,所以結合googlesamples中的示例總結一下大體的調用流程java

概述

android.hardware.camera2包向鏈接在Android設備上的相機設備提供了接口,它用來代替已經廢棄的Camera類。android

這個包將相機設備模擬爲一個管道,它接收一個捕獲一幀圖像的輸入請求,每一個請求捕獲一幀圖像,而後輸出每一次捕獲結果的元數據包,以及一組輸出圖像緩存區。git

想要獲取、查詢或者打開一個可用的相機設備,須要獲取一個CameraManager實例。github

每一個CameraDevice都提供一組靜態屬性來描述硬件設備和設備可用的設定以及輸出參數。這些信息由一個CameraCharacteristics對象 提供,它能夠經過**CameraManager.getCameraCharacteristics(cameraId)**來得到。緩存

想要從一個相機設備捕獲一張圖像或者獲取圖像流,應用程序須要調用CameraDevicecreateCaptureSession方法建立一個CameraCaptureSession對象,該方法須要一組與相機設備一塊兒使用的一組輸出的Surface,每一個Surface都須要實現配置好適當的尺寸和格式以匹配相機可用的尺寸和格式,目標Surface能夠從不少類中獲取到,好比:SurfaceView, SurfaceTexture 經過Surface(SurfaceTexture), MediaCodec, MediaRecorder, Allocation, and ImageReader。session

一般來講,相機的預覽圖像能夠發送到SurfaceView和TextureView(經過它的SurfaceTexture)來展現出來。JPEG圖像或者用於DngCreator的RAW緩衝區的捕獲能夠用ImageReader經過JPEG或RAW_SENSOR格式來完成。ide

而後咱們須要構造一個CaptureRequest對象,它定義了相機捕獲一幀數據所須要的全部參數,它還列舉了此次捕獲圖像應該以哪個配置好的Surface做爲目標,CameraDevice 有一個工廠方法createCaptureRequest來建立一個指定類型的CaptureRequest.Builder對象。oop

一旦一個CaptureRequest對象被配置好了,就能夠把它交給一個活動的CameraCaptureSession來拍一張照片或者不斷重複的捕獲圖像做爲預覽。發送一個捕獲的請求後,相機設備會產生一個TotalCaptureResult對象,它包含了捕獲時相機的狀態和最後起做用的設置等信息,相機還會發送一幀圖像數據到包含在這個請求中的每個Surface。佈局

下面是我畫的一個大體的調用流程圖,比較粗糙post

Camera.jpg

調用流程

獲取cameraId以及相關參數

用TextureView來接受預覽的圖像數據,在Activity/Fragment的onResume方法中判斷textureview是否能夠渲染,能夠的畫直接調用相機的相關方法,不然的話註冊監聽。用HandlerThread開啓一個後臺線程,建立一個該線程的Handler

private TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        openCamera(width, height);
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }
};    
@Override
protected void onResume() {
    super.onResume();
    startBackground();

    if (previewView.isAvailable()) {
        openCamera(previewView.getWidth(), previewView.getHeight());
    } else {
        previewView.setSurfaceTextureListener(surfaceTextureListener);
    }
}

private void startBackground() {
    handlerThread = new HandlerThread("cameraThread");
    handlerThread.start();
    mHandler = new Handler(handlerThread.getLooper());
}
複製代碼

下面開始經過CameraManager來獲取相機列表並獲得咱們須要的相機id已經相關參數

private void setupCamera(int width, int height) {
    CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    try {
        //獲取可用的相機列表
        String[] cameraIdList = cameraManager.getCameraIdList();
        for (String cameraId : cameraIdList) {
            //獲取該相機的CameraCharacteristics,它保存的相機相關的屬性
            CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);
            //獲取相機的方向
            Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
            //若是是前置攝像頭就continue,咱們這裏只用後置攝像頭
            if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
                continue;
            }
            //獲取相機支持的流的參數的集合
            StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            //獲取輸出格式爲ImageFormat.JPEG支持的全部尺寸
            Size[] outputSizes = map.getOutputSizes(ImageFormat.JPEG);
            //獲取支持的最大的尺寸,SizeByteConparable是一個實現了Comparator接口的類,比較簡單
            Size max = Collections.max(Arrays.asList(outputSizes), new SizeByteConparable());
            //實例化一個ImageReader對象
            imageReader = ImageReader.newInstance(max.getWidth(), max.getHeight(), ImageFormat.JPEG, 2);
            //給imageReader對象設置監聽
            imageReader.setOnImageAvailableListener(mOnImageAvaliableListener, mHandler);
            mPreviewSize = new Size(width, height);
            //保存cameraId
            this.cameraId = cameraId;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
複製代碼

上面的方法就獲取到了咱們想要的cameraid以及相關參數,其中有一個ImageReader,它內部有一個Surface對象,在須要拍照的時候,咱們將這個Surface對象設置爲CaptureRequest的target surface,那麼相機就會將捕獲的圖像數據渲染到這個surface中,咱們就能夠經過ImageReader來獲取圖像出具來保存到本地,其中OnImageAvaliableListener就是當相機將圖像數據顯然到ImageReader的Surface中時的回調。

打開相機

private CameraDevice.StateCallback cameraStateCallback = new CameraDevice.StateCallback() {
    @Override
    public void onOpened(@NonNull CameraDevice camera) {
        mCameraDevice = camera;
        createCaptureSession();
    }

    @Override
    public void onDisconnected(@NonNull CameraDevice camera) {

    }

    @Override
    public void onError(@NonNull CameraDevice camera, int error) {

    }
};

private void openCamera(int width, int height) {
    //第一步的方法
    setupCamera(width, height);
    CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
        // TODO: Consider calling
        // ActivityCompat#requestPermissions
        // here to request the missing permissions, and then overriding
        // public void onRequestPermissionsResult(int requestCode, String[] permissions,
        // int[] grantResults)
        // to handle the case where the user grants the permission. See the documentation
        // for ActivityCompat#requestPermissions for more details.
        return;
    }
    try {
        cameraManager.openCamera(cameraId, cameraStateCallback, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}
複製代碼

打開相機的方法比較簡單,就調用了CaneraManager的openCamera方法,傳入cameraId以及一個callback,當相機打開成功,失敗或者相機斷開鏈接是會調用這個callback。

建立CameraCaptureSession,開始預覽

上一步在打開相機成功的毀掉中調用了createCaptureSession方法,下面是該方法的實現:

private CameraCaptureSession.StateCallback sessionCallback = new CameraCaptureSession.StateCallback() {
    @Override
    public void onConfigured(@NonNull CameraCaptureSession session) {
        //保存爲全局變量
        mCameraCaptureSession = session;
        //設置自動對焦模式爲連續自動對焦
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
        //建立CaptureRequest
        previewRequest = previewRequestBuilder.build();
        try {
            //開始預覽,因爲咱們如今並不須要對預覽的圖像數據作處理,因此這裏的第二個參數就傳null
            mCameraCaptureSession.setRepeatingRequest(previewRequest, null, mHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onConfigureFailed(@NonNull CameraCaptureSession session) {

    }
};

private void createCaptureSession() {
    //獲取TextureView的SurfaceTexture,用來下一步建立Surface
    SurfaceTexture surfaceTexture = previewView.getSurfaceTexture();
    //設置默認的緩衝區大小
    surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
    //建立Surface
    Surface surface = new Surface(surfaceTexture);
    try {
        //獲取一個預覽的CaptureRequestBuilder,注意類型爲CameraDevice.TEMPLATE_PREVIEW
        previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        //將上面建立的Surface設置爲request的目標surface,這裏最終就會渲染到咱們佈局文件裏面的TextureView
        previewRequestBuilder.addTarget(surface);
        //建立CameraCaptureSession
        mCameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()), sessionCallback, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}
複製代碼

這個時候已經能夠預覽了

拍照

經過上面三個步驟已經能夠在屏幕上顯示預覽了,如今開始作拍照操做,從第一部分咱們能夠知道拍照也是要經過向CameraCaptureSession發送一個CaptureRequest來實現。

private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
    @Override
    public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
        super.onCaptureCompleted(session, request, result);
        try {
           //拍照完成後從新開啓預覽
            mCameraCaptureSession.setRepeatingRequest(previewRequest,previewCaptureCallback,mHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
};

@OnClick(R.id.capture)
public void onViewClicked() {
    CaptureRequest.Builder captureRequestBuilder = null;
    try {
        //用CameraDevice建立一個CaptureRequest.Builder,類型爲CameraDevice.TEMPLATE_STILL_CAPTURE,也就是說咱們須要請求以個靜態的圖像。
        captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        captureRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                CameraMetadata.CONTROL_AF_TRIGGER_START);
        //將imageReader的Surface設置爲請求的目標Surface
        captureRequestBuilder.addTarget(imageReader.getSurface());
        captureRequest = captureRequestBuilder.build();
        //中止預覽
        mCameraCaptureSession.stopRepeating();
        mCameraCaptureSession.abortCaptures();
        //開始請求拍照
        mCameraCaptureSession.capture(captureRequest, captureCallback, mHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

private ImageReader.OnImageAvailableListener mOnImageAvaliableListener = new ImageReader.OnImageAvailableListener() {
    @Override
    public void onImageAvailable(ImageReader reader) {
        //發送拍照的請求後,相機將圖像數據填充到imageReader的Surface中的時候會回調這裏
        //咱們想後臺線程post一個runnable
        mHandler.post(new SaveImageRunnable(reader.acquireNextImage()));
    }
};

//一個保存圖片的Runnable
public class SaveImageRunnable implements Runnable {
    private Image image;
    private File file;

    public SaveImageRunnable(Image image) {
        this(image, new File(getExternalFilesDir(null), "image-capture.jpg"));
    }

    public SaveImageRunnable(Image image, File file) {
        this.image = image;
        this.file = file;
    }

    @Override
    public void run() {
        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
        byte[] byteBuffer = new byte[buffer.remaining()];
        buffer.get(byteBuffer);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            fos.write(byteBuffer);
        } catch (java.io.IOException e) {
            e.printStackTrace();
        } finally {
            try {
                assert fos != null;
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
複製代碼

至此相機預覽以及拍照流程就完成了,因爲這裏只總結大體流程,簡化了不少細節,好比方向,對焦,閃光燈等,致使拍出的照片質量不高。後面會總結一下用Camera2視頻錄製的流程。

相關文章
相關標籤/搜索