Android Camera2 拍照流程

和你一起終身學習,這裏是程序員 Android

經典好文推薦,通過閱讀本文,您將收穫以下知識點:

一、Camera 2 介紹
二、Camera2包架構示意圖
三、 Camera2 的 主要類:
四、相機預覽與拍照流程

一、Camera 2 介紹

camera2 API 的加入是從AndroidV5.0(21)開始的,因此我們使用Camera2應該是在Android 5.0(含5.0)之後。同時,對於Android6.0我們需要有動態權限的管理。這兩點應該是使用Camera2使用前的最基本認知。Android 5.0對拍照API進行了全新的設計,新增了全新設計的Camera v2 API,這些API不僅大幅提高了Android系統拍照的功能,還能支持RAW照片輸出,甚至允許程序調整相機的對焦模式、曝光模式、快門等。下面不做過多介紹了,直接開擼了。

二、Camera2包架構示意圖

我們先來看看 camera2包架構示意圖(不用一下子理解,只需要有個整體印象):


這裏引用了管道的概念將安卓設備和攝像頭之間聯通起來,系統向攝像頭髮送 Capture 請求,而攝像頭會返回 CameraMetadata。這一切建立在一個叫作 CameraCaptureSession 的會話中。

三、 Camera2 的 主要類:

3.1 CameraManager:

攝像頭管理器。這是一個全新的系統管理器,專門用於檢測系統攝像頭、打開系統攝像頭。另外,調用CameraManager的getCameraCharacteristics(String cameraId)方法即可獲取指定攝像頭的相關特性。

3.2 CameraCharacteristics:

攝像頭特性。該對象通過CameraManager來獲取,用於描述特定攝像頭所支持的各種特性。類似與原來的CameraInfo 。

3.3 CameraDevice:

代表系統攝像頭。該類的功能類似於早期的Camera類。而每個CameraDevice自己會負責創建CameraCaptureSession 以及建立 CaptureRequest。

3.4 CameraCaptureSession:

這是一個非常重要的API,當程序需要預覽、拍照時,都需要先通過該類的實例創建Session。而且不管預覽還是拍照,也都是由該對象的方法進行控制的,其中控制預覽的方法爲setRepeatingRequest();控制拍照的方法爲capture()。爲了監聽CameraCaptureSession的創建過程,以及監聽CameraCaptureSession的拍照過程,Camera-v2-API爲CameraCaptureSession提供了StateCallback、CaptureCallback等內部類。

3.5 CameraRequest和CameraRequest.Builder:

當程序調用setRepeatingRequest()方法進行預覽時,或調用capture()方法進行拍照時,都需要傳入CameraRequest參數。CameraRequest代表了一次捕獲請求,用於描述捕獲圖片的各種參數設置,比如對焦模式、曝光模式……總之,程序需要對照片所做的各種控制,都通過CameraRequest參數進行設置。CameraRequest.Builder則負責生成CameraRequest對象。

四、相機預覽與拍照流程

如果你看不太懂流程圖,沒關係,待會兒我們通過代碼就可以更好的理解了。首先,Google官方推薦的Camera2控制拍照的步驟大致如下。

4.1 Camera2拍照的步驟

(1)用CameraManager的openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)方法打開指定攝像頭。該方法的第一個參數代表要打開的攝像頭ID;第二個參數用於監聽攝像頭的狀態;第三個參數代表執行callback的Handler,如果程序希望直接在當前線程中執行callback,則可將handler參數設爲null。

(2)當攝像頭被打開之後會回調接口mStateCallback.onOpened,程序即可獲取CameraDevice —— 即根據攝像頭ID獲取了指定攝像頭設備,然後調用CameraDevice的createCaptureSession(List outputs, CameraCaptureSession. StateCallback callback,Handler handler)方法來創建CameraCaptureSession。該方法的第一個參數是一個List集合,封裝了所有需要從該攝像頭獲取圖片的Surface,第二個參數用於監聽CameraCaptureSession的創建過程;第三個參數代表執行callback的Handler,如果程序希望直接在當前線程中執行callback,則可將handler參數設爲null。

(3)不管預覽還是拍照,程序都調用CameraDevice的createCaptureRequest(int templateType)方法創建CaptureRequest.Builder,該方法支持TEMPLATE_PREVIEW(預覽)、TEMPLATE_RECORD(拍攝視頻)、TEMPLATE_STILL_CAPTURE(拍照)等參數。

(4)通過第3步所調用方法返回的CaptureRequest.Builder設置拍照的各種參數,比如對焦模式、曝光模式等。

(5)調用CaptureRequest.Builder的build()方法即可得到CaptureRequest對象,接下來程序可通過CameraCaptureSession的setRepeatingRequest()方法開始預覽,或調用capture()方法拍照。相機的預覽與拍照流程我們基本瞭解了。

(6)預覽時,是將mSurfaceHolder.getSurface()作爲目標,使用setRepeatingRequest()方法,顯示拍照結果時,是將mImageReader.getSurface()作爲目標,使用capture()方法。

然後這裏還有一個大招:Google官方Camera2拍照的demo的地址:點擊跳轉github

4.2 首先是我們的layout代碼

首先權限不能忘:

uses-permission android:name=”android.permission.CAMERA” / 
uses-feature android:name=”android.hardware.camera” / 
uses-feature android:name=”android.hardware.camera.autofocus” /
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextureView
        android:id="@+id/texture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/cancelButton"
            android:text="取消"
            android:visibility="invisible"/>
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/captureButton"
            android:text="拍照"
            android:visibility="visible"/>
        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/saveButton"
            android:text="保存"
            android:visibility="invisible"/>
    </LinearLayout>
</LinearLayout>

4.3 實現代碼

public class customCarmeraActivity extends AppCompatActivity {

    private static final int REQUEST_CAMERA_PERMISSION = 1;

    private static final int STATE_PREVIEW = 1;
    private static final int STATE_WAITING_PRECAPTURE = 2;
    private int mState = STATE_PREVIEW;

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

    private CameraManager mCameraManagerm;
    private CameraDevice mCameraDevice;
    private String mCameraId;
    private HandlerThread mBackgroundThread;
    private Handler mBackgroundHandler;

    private TextureView mTextureView;

    private ImageReader mImageReader;
    private File mFile;

    private CaptureRequest.Builder mPreviewBuilder;
    private CaptureRequest mPreviewRequest;
    private CameraCaptureSession mCaptureSession;


    private Semaphore mCameraOpenCloseLock = new Semaphore(1);

    /**
     * Starts a background thread and its {@link Handler}.
     */
    private void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("CameraBackground");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }

    /**
     * Stops the background thread and its {@link Handler}.
     */
    private void stopBackgroundThread() {
        mBackgroundThread.quitSafely();
        try {
            mBackgroundThread.join();
            mBackgroundThread = null;
            mBackgroundHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @TextureView.SurfaceTextureListener:public static interface
     * @onSurfaceTextureAvailable:Invoked when a TextureView's SurfaceTexture is ready for use.
     * @onSurfaceTextureSizeChanged:Invoked when the SurfaceTexture's buffers size changed.
     * @onSurfaceTextureDestroyed:Invoked when the specified SurfaceTexture is about to be destroyed.
     * @onSurfaceTextureUpdated:Invoked when the specified SurfaceTexture is updated through updateTexImage().
     */
    private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
            openCamera(width, height);
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
            //configureTransform(width, height);
        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
            return true;
        }

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture texture) {
        }
    };

    /**
     * Shows a {@link Toast} on the UI thread.
     *
     * @param text The message to show
     */
    private void showToast(final String text) {
        runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(customCarmeraActivity.this, text, Toast.LENGTH_SHORT).show();
                }
            });
    }


    /**
     * Capture a still picture. This method should be called when we get a response in
     * {@link #mCaptureCallback} from both {@link #lockFocus()}.
     */
    private void captureStillPicture() {
        try {
            if (null == mCameraDevice) {
                return;
            }
            // This is the CaptureRequest.Builder that we use to take a picture.
            final CaptureRequest.Builder captureBuilder =
                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            captureBuilder.addTarget(mImageReader.getSurface());//拍照時,是將mImageReader.getSurface()作爲目標

            // Use the same AE and AF modes as the preview.
            captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                    CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
            // Orientation
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));

            CameraCaptureSession.CaptureCallback CaptureCallback
                    = new CameraCaptureSession.CaptureCallback() {

                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                               @NonNull CaptureRequest request,
                                               @NonNull TotalCaptureResult result) {
                    showToast("Saved: " + mFile);

                    //Log.d("customCarmeraActivity", mFile.toString());
                    unlockFocus();//恢復預覽
                }
            };

            mCaptureSession.stopRepeating();
            mCaptureSession.abortCaptures();
            mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    private CameraCaptureSession.CaptureCallback mCaptureCallback =
            new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
                                               TotalCaptureResult result) {
                    //Log.d("linc","mSessionCaptureCallback, onCaptureCompleted");
                    mCaptureSession = session;
                    checkState(result);
                }

                @Override
                public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
                                                CaptureResult partialResult) {
                    Log.d("linc","mSessionCaptureCallback,  onCaptureProgressed");
                    mCaptureSession = session;
                    checkState(partialResult);
                }

                private void checkState(CaptureResult result) {
                    switch (mState) {
                        case STATE_PREVIEW:
                            // We have nothing to do when the camera preview is working normally.
                            break;
                        case STATE_WAITING_PRECAPTURE:
                            Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
                            if (afState == null) {
                                captureStillPicture();
                            } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
                                // CONTROL_AE_STATE can be null on some devices
                                Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                                if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                                    //mState = STATE_PICTURE_TAKEN;
                                    captureStillPicture();
                                } else {
                                    //runPrecaptureSequence();//視頻拍攝
                                }
                            }
                            break;
                    }
                }

            };
    private void createCameraPreviewSession() {
        try {
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            //assert(texture != null);

            // We configure the size of default buffer to be the size of camera preview we want.
            texture.setDefaultBufferSize(mTextureView.getWidth(), mTextureView.getHeight());
            // This is the output Surface we need to start preview.
            Surface surface = new Surface(texture);

            mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewBuilder.addTarget(surface);//預覽時,是將Surface()作爲目標
            mState = STATE_PREVIEW;
            mCameraDevice.createCaptureSession(
                    Arrays.asList(surface, mImageReader.getSurface()),
                    new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                            // The camera is already closed
                            if (null == mCameraDevice) {
                                return;
                            }
                            mCaptureSession = cameraCaptureSession;
                            try {
                                mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                                mPreviewBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                                        CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                                mPreviewRequest = mPreviewBuilder.build();
                                mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);
                            } catch (CameraAccessException e) {
                                e.printStackTrace();
                                Log.e("linc","set preview builder failed."+e.getMessage());
                            }
                        }

                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                            Toast.makeText(customCarmeraActivity.this, "Camera configuration Failed", Toast.LENGTH_SHORT).show();
                        }
                    },mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    /**
     *
     * @CameraDevice.StateCallback:public static abstract class
     * @onOpened:This method is called when the camera is opened.  We start camera preview here.
     * @onDisconnected:The method called when a camera device is no longer available for use.
     * @onError:The method called when a camera device has encountered a serious error.
     */
    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(CameraDevice cameraDevice) {
            mCameraOpenCloseLock.release();
            mCameraDevice = cameraDevice;
            createCameraPreviewSession();
        }

        @Override
        public void onDisconnected(CameraDevice cameraDevice) {
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(CameraDevice cameraDevice, int error) {
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
        }
    };

    private void getCameraId() {
        try {
            //Return the list of currently connected camera devices by identifier, including cameras that may be in use by other camera API clients
            for (String cameraId : mCameraManagerm.getCameraIdList()) {
                //Query the capabilities of a camera device
                CameraCharacteristics characteristics = mCameraManagerm.getCameraCharacteristics(cameraId);
                if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
                    continue;
                }
                mCameraId = cameraId;
                return;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * Saves a JPEG {@link Image} into the specified {@link File}.
     */
    private static class ImageSaver implements Runnable {

        /**
         * The JPEG image
         */
        private final Image mImage;
        /**
         * The file we save the image into.
         */
        private final File mFile;

        ImageSaver(Image image, File file) {
            mImage = image;
            mFile = file;
        }

        @Override
        public void run() {
            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            FileOutputStream output = null;
            try {
                output = new FileOutputStream(mFile);
                output.write(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                mImage.close();
                if (null != output) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            //showToast("onImageAvailable: " );
            mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
        }
    };
    //TODO 執行完上面的請求權限後,系統會彈出提示框讓用戶選擇是否允許改權限。選擇的結果可以在回到接口中得知:
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_CAMERA_PERMISSION) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //用戶允許改權限,0表示允許,-1表示拒絕 PERMISSION_GRANTED = 0, PERMISSION_DENIED = -1
                //permission was granted, yay! Do the contacts-related task you need to do.
                //這裏進行授權被允許的處理
            } else {
                //permission denied, boo! Disable the functionality that depends on this permission.
                //這裏進行權限被拒絕的處理
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
    /**
     * Opens the camera specified by {@link Camera2BasicFragment#mCameraId}.
     */
    private void openCamera(int width, int height) {
        //檢查相機服務的訪問權限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            //Toast.makeText(this,"Lacking privileges to access camera service, please request permission first",Toast.LENGTH_SHORT).show();
            Log.e("customCarmeraActivity.openCamera","Lacking privileges to access camera service, please request permission first");
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);//API21後,向用戶請求相機使用權限,然後執行onRequestPermissionsResult回調
            return;
        }

        getCameraId();
        //assert(mCameraId != null);

        mImageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG,/*maxImages*/7);
        mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);

        try {
            if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("Time out waiting to lock camera opening.");
            }
            mCameraManagerm.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
        }
    }
    /**
     * Closes the current {@link CameraDevice}.
     */
    private void closeCamera() {
        try {
            mCameraOpenCloseLock.acquire();
            if (null != mCaptureSession) {
                mCaptureSession.close();
                mCaptureSession = null;
            }
            if (null != mCameraDevice) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
            if (null != mImageReader) {
                mImageReader.close();
                mImageReader = null;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
        } finally {
            mCameraOpenCloseLock.release();
        }
    }
    /**
     * Unlock the focus. This method should be called when still image capture sequence is
     * finished.
     */
    private void unlockFocus() {
        try {
            // Reset the auto-focus trigger
            mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                    CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
            mPreviewBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                    CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
     /*       mCaptureSession.capture(mPreviewBuilder.build(), mCaptureCallback,
                    mBackgroundHandler);*/
            // After this, the camera will go back to the normal state of preview.
            mState = STATE_PREVIEW;
            mCaptureSession.setRepeatingRequest(mPreviewBuilder.build(), mCaptureCallback, mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    /**
     * Lock the focus as the first step for a still image capture.
     */
    private void lockFocus() {
        try {
            // This is how to tell the camera to lock focus.
            mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                    CameraMetadata.CONTROL_AF_TRIGGER_START);
            // Tell #mCaptureCallback to wait for the lock.
            mState = STATE_WAITING_PRECAPTURE;
            mCaptureSession.capture(mPreviewBuilder.build(), mCaptureCallback,
                    mBackgroundHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.custom_carmera_layout);

        mCameraManagerm = (CameraManager)getSystemService(Context.CAMERA_SERVICE);
        mTextureView = (TextureView)findViewById(R.id.textureview);

        mFile = new File(getExternalFilesDir(null), "pic.jpg");

        Button tackPictureBtn = (Button)findViewById(R.id.takePictureButton);
        tackPictureBtn.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view) {
                //Log.i("linc", "take picture");
                lockFocus();
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        startBackgroundThread();

        if(mTextureView.isAvailable()) {
            openCamera(mTextureView.getWidth(), mTextureView.getHeight());
        } else{
            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        closeCamera();
        stopBackgroundThread();
    }
}

參考鏈接:https://blog.csdn.net/zhangzheng_1986/article/details/78710655

至此,本篇已結束。轉載網絡的文章,小編覺得很優秀,歡迎點擊閱讀原文,支持原創作者,如有侵權,懇請聯繫小編刪除,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章