最近,在使用Android做一个照相机的开发。因为不能使用系统提供的相机应用,所以只能自己写一个。Android以前提供相机的api叫camera,不过在level 21被Google抛弃了。网上的教程,还有很多都是使用camera的,为了好好学习一下camera2,就去扒了Google提供的官方示例。下面给一个github的连接,可以找到全部的源码。

Camera2Basic Sample

下述的例子主要提供的功能是:相机预览、拍照、照片保存。具体如下:进入该应用后,可以看到相机的预览画面,提供一个按钮用于拍照,拍完照片后照片会保存在SD卡的根目录下。

首先来了解一下camera2的整体结构:

如上所示,整个camera2由一个CameraManager来进行统一管理,通过Context的getSystemService方法可以实例化CameraManager,然后该类主要通过三个类来对Camera进行操作。下面分别介绍一下:

  • CameraDevice:描述一个照相机设备,一个Android设备可能会有多个摄像头,通过CameraId可以进行区别。它最主要有一个相机状态的回调函数,当下达打开相机的命令后,若相机正确的打开便会回调该函数。
  • CameraCharacteristic:某个照相机设备的具体参数。本例主要用到它提供的输出格式(即输出数据的格式)。
  • CameraCaptureSession:相机捕获会话,通过这个类可以和相机进行对话(预览还是单张拍照还是录像等)。这里有两个回调函数,捕获状态的回调,和捕获数据的回调(后文会有详述)。
  • 上图左上部分所示的时Android设备和camera设备的通信情况,两者之间通过pipeline(管道)进行数据交换。当需要尽心不同的操作时,将CameraCaptureRequest通过管道传给camera,接收到请求后,camera做出相应的反应,将获取到得数据CmaeraMetadata通过管道传回给Android设备。

    本例,需要使用比较多的权限,请参看源码AndroidManifest.xml。

    Android设备的屏幕方向,与摄像头的原始方向并不一致,需要做方向转换。一般而言,当Android设备横着放时,与摄像头的方向是一致的。

    为了避免照片失真(照片被拉长或者压扁),需要保证预览的长宽比例、照片的长宽比例和相机输出格式的长宽比例三者保持一致。

    本例当中,用一个activity承载一个fragment。所有的代码都写在fragment里面,重写了fragment的几个生命周期函数:

  • onCreateView:加载fragment的布局文件;
  • onViewCreated:实例化布局控件;
  • onActivityCreated:在SD卡的目录下建立jpg文件等待待将拍到的照片写进去;
  • onResume:开始照相机线程,执行一些逻辑判断;
  • onPause:关闭照相机,停止照相机线程;
  • 正常来说,代码的整体流程如图2所示,activity将需要的fragment加载进来后,开始加载显示预览的控件texture,当控件加载完毕会执行一个回调函数 onSurfaceTextureAvailable() ,在这个回调函数里面,打开摄像头(即执行 openCamera() )。

    openCamera() 里面,首先要配置相机的输出,预览图像和拍照的图片要作不同的处理,然后根据当前的设备屏幕环境,判断是否需要进行数据的转换,最后通过cameraManager打开摄像头(调用 cameraManager.openCamera() 方法)。

    更详细的方法请看后面的源码,图2当中,中间是判断屏幕方向的逻辑,右边是自动选择最合适的显示逻辑。

    // 代码比较长,请耐心查看,注释可能有不正确的地方,请提出
    // 注意,下面代码为了配合我的使用,已经去掉了按钮,但是拍照的方法仍然保留,通过调用方法可以完成拍照
    package com.eric_lai.weeding_robot.fragment;
     * Created by ERIC_LAI on 16/3/18.
    import android.app.Activity;
    import android.app.AlertDialog;
    import android.app.Dialog;
    import android.app.DialogFragment;
    import android.app.Fragment;
    import android.content.Context;
    import android.content.DialogInterface;
    import android.content.res.Configuration;
    import android.graphics.ImageFormat;
    import android.graphics.Matrix;
    import android.graphics.Point;
    import android.graphics.RectF;
    import android.graphics.SurfaceTexture;
    import android.hardware.camera2.CameraAccessException;
    import android.hardware.camera2.CameraCaptureSession;
    import android.hardware.camera2.CameraCharacteristics;
    import android.hardware.camera2.CameraDevice;
    import android.hardware.camera2.CameraManager;
    import android.hardware.camera2.CameraMetadata;
    import android.hardware.camera2.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.support.annotation.NonNull;
    import android.util.Log;
    import android.util.Size;
    import android.util.SparseIntArray;
    import android.view.LayoutInflater;
    import android.view.Surface;
    import android.view.TextureView;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.Toast;
    import com.eric_lai.weeding_robot.R;
    import com.eric_lai.weeding_robot.view.AutoFitTextureView;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.List;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.TimeUnit;
    public class CameraFragment extends Fragment {
         * Conversion from screen rotation to JPEG orientation.
        private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
        private static final String FRAGMENT_DIALOG = "dialog";
        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);
         * 调试用TAG
        private static final String TAG = "CameraFragment";
         * 相机状态:
         * 0: 预览
         * 1: 等待上锁(拍照片前将预览锁上保证图像不在变化)
         * 2: 等待预拍照(对焦, 曝光等操作)
         * 3: 等待非预拍照(闪光灯等操作)
         * 4: 已经获取照片
        private static final int STATE_PREVIEW = 0;
        private static final int STATE_WAITING_LOCK = 1;
        private static final int STATE_WAITING_PRECAPTURE = 2;
        private static final int STATE_WAITING_NON_PRECAPTURE = 3;
        private static final int STATE_PICTURE_TAKEN = 4;
         * Camera2 API提供的最大预览宽度和高度
        private static final int MAX_PREVIEW_WIDTH = 1920;
        private static final int MAX_PREVIEW_HEIGHT = 1080;
         * SurfaceTexture监听器
        private final TextureView.SurfaceTextureListener mSurfaceTextureListener
                = new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
                // SurfaceTexture就绪后回调执行打开相机操作
                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) {
         * 正在使用的相机id
        private String mCameraId;
         * 预览使用的自定义TextureView控件
        private AutoFitTextureView mTextureView;
         * 预览用的获取会话
        private CameraCaptureSession mCaptureSession;
         * 正在使用的相机
        private CameraDevice mCameraDevice;
         * 预览数据的尺寸
        private Size mPreviewSize;
         * 相机状态改变的回调函数
        private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice cameraDevice) {
                // 当相机打开执行以下操作:
                // 1. 释放访问许可
                // 2. 将正在使用的相机指向将打开的相机
                // 3. 创建相机预览会话
                mCameraOpenCloseLock.release();
                mCameraDevice = cameraDevice;
                createCameraPreviewSession();
            @Override
            public void onDisconnected(@NonNull CameraDevice cameraDevice) {
                // 当相机失去连接时执行以下操作:
                // 1. 释放访问许可
                // 2. 关闭相机
                // 3. 将正在使用的相机指向null
                mCameraOpenCloseLock.release();
                cameraDevice.close();
                mCameraDevice = null;
            @Override
            public void onError(@NonNull CameraDevice cameraDevice, int error) {
                // 当相机发生错误时执行以下操作:
                // 1. 释放访问许可
                // 2. 关闭相机
                // 3, 将正在使用的相机指向null
                // 4. 获取当前的活动, 并结束它
                mCameraOpenCloseLock.release();
                cameraDevice.close();
                mCameraDevice = null;
                Activity activity = getActivity();
                if (null != activity) {
                    activity.finish();
         * 处理拍照等工作的子线程
        private HandlerThread mBackgroundThread;
         * 上面定义的子线程的处理器
        private Handler mBackgroundHandler;
         * 静止页面捕获(拍照)处理器
        private ImageReader mImageReader;
         * 输出照片的文件
        private File mFile;
         * ImageReader的回调函数, 其中的onImageAvailable会在照片准备好可以被保存时调用
        private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
                = new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
         * 预览请求构建器, 用来构建"预览请求"(下面定义的)通过pipeline发送到Camera device
        private CaptureRequest.Builder mPreviewRequestBuilder;
         * 预览请求, 由上面的构建器构建出来
        private CaptureRequest mPreviewRequest;
         * 当前的相机状态, 这里初始化为预览, 因为刚载入这个fragment时应显示预览
        private int mState = STATE_PREVIEW;
         * 信号量控制器, 防止相机没有关闭时退出本应用(若没有关闭就退出, 会造成其他应用无法调用相机)
         * 当某处获得这个许可时, 其他需要许可才能执行的代码需要等待许可被释放才能获取
        private Semaphore mCameraOpenCloseLock = new Semaphore(1);
         * 捕获会话回调函数
        private CameraCaptureSession.CaptureCallback mCaptureCallback
                = new CameraCaptureSession.CaptureCallback() {
            @Override
            public void onCaptureProgressed(@NonNull CameraCaptureSession session,
                                            @NonNull CaptureRequest request,