android camera2 拍照流程

来源:互联网 发布:遥感大数据的特点 编辑:程序博客网 时间:2024/06/08 04:41

正文

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包中的主要类:

这里写图片描述
CameraManager:摄像头管理器。这是一个全新的系统管理器,专门用于检测系统摄像头、打开系统摄像头。另外,调用CameraManager的getCameraCharacteristics(String cameraId)方法即可获取指定摄像头的相关特性。

CameraCharacteristics:摄像头特性。该对象通过CameraManager来获取,用于描述特定摄像头所支持的各种特性。类似与原来的CameraInfo 。

CameraDevice:代表系统摄像头。该类的功能类似于早期的Camera类。而每个 CameraDevice 自己会负责建立 CameraCaptureSession 以及建立 CaptureRequest。

CameraCaptureSession:这是一个非常重要的API,当程序需要预览、拍照时,都需要先通过该类的实例创建Session。而且不管预览还是拍照,也都是由该对象的方法进行控制的,其中控制预览的方法为setRepeatingRequest();控制拍照的方法为capture()。
为了监听CameraCaptureSession的创建过程,以及监听CameraCaptureSession的拍照过程,Camera v2 API为CameraCaptureSession提供了StateCallback、CaptureCallback等内部类。

CameraRequest和CameraRequest.Builder:当程序调用setRepeatingRequest()方法进行预览时,或调用capture()方法进行拍照时,都需要传入CameraRequest参数。CameraRequest代表了一次捕获请求,用于描述捕获图片的各种参数设置,比如对焦模式、曝光模式……总之,程序需要对照片所做的各种控制,都通过CameraRequest参数进行设置。CameraRequest.Builder则负责生成CameraRequest对象。

相机预览与拍照流程

这里写图片描述

如果你看不太懂流程图,没关系,待会儿我们通过代码就可以更好的理解了。首先,Google官方推荐的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


首先是我们的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>

下面是我的代码

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();    }}
原创粉丝点击