camera2Basic学习总结,Camera预览拍照流程学习总结

查看文章前最好先下载google开源的Camera2Basic代码,
Github地址:https://github.com/googlearchive/android-Camera2Basic
如果因为网络限制不能访问,可以下载我的资源:

https://download.csdn.net/download/weixin_50995731/89361609
下面的内容都出自这个,只是在Camera2Basic添加了一些注释,对内容没有修改


第一部分 camera2-Basic代码

一、首先看应用入口——MainActivity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);
        if (null == savedInstanceState) {
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.container, Camera2BasicFragment.newInstance())
                    .commit();
        }
    }
}

Camera2BasicFragment.java中可以看到,就是在首次创建时(null == savedInstanceState),将 Camera2BasicFragment 替换到容器中。

public static Camera2BasicFragment newInstance() {
    return new Camera2BasicFragment();
}

二、预览拍照流程

然后就可以根据Fragment的生命周期查看,Fragment的生命周期如下所示:
在这里插入图片描述
当然,这里只重写了Fragment的几个生命周期函数:

  • onCreateView:加载fragment的布局文件;
  • onViewCreated:实例化布局控件;
  • onActivityCreated:在SD卡的目录下建立jpg文件等待待将拍到的照片写进去;
  • onResume:开始照相机线程,执行一些逻辑判断;
  • onPause:关闭照相机,停止照相机线程;

2.1 在布局文件中加入TextureView控件

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_camera2_basic, container, false);
}

@Override
public void onViewCreated(final View view, Bundle savedInstanceState) {
    view.findViewById(R.id.picture).setOnClickListener(this);
    view.findViewById(R.id.info).setOnClickListener(this);
    mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture);
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.mfd.mycamera.AutoFitTextureView
        android:id="@+id/texture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true" />

    <FrameLayout
        android:id="@+id/control"
        android:layout_width="match_parent"
        android:layout_height="112dp"
        android:layout_alignParentStart="true"
        android:layout_alignParentBottom="true"
        android:background="@color/control_background">

        <Button
            android:id="@+id/picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="@string/picture" />

        <ImageButton
            android:id="@+id/info"
            style="@android:style/Widget.Material.Light.Button.Borderless"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical|right"
            android:contentDescription="@string/description_info"
            android:padding="20dp"
            android:src="@drawable/ic_action_info" />

    </FrameLayout>

</RelativeLayout>
/**
 * A {@link TextureView} that can be adjusted to a specified aspect ratio.
 */
public class AutoFitTextureView extends TextureView {

    private int mRatioWidth = 0;
    private int mRatioHeight = 0;

    public AutoFitTextureView(Context context) {
        this(context, null);
    }

    public AutoFitTextureView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
     * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
     * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
     *
     * @param width  Relative horizontal size
     * @param height Relative vertical size
     */
    // 设置视图的宽高比,传入的width、height是相对的宽度和高度,实际的数值大小并不重要
    public void setAspectRatio(int width, int height) {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Size cannot be negative.");
        }
        mRatioWidth = width;
        mRatioHeight = height;

        // 调用requestLayout()重新计算视图的布局,确保视图的显示结果与最新的需求一致,这里会触发视图重新测量、布局和绘制过程
        // 即调用onMeasure()、onLayout()和onDraw()方法
        requestLayout();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 首先MeasureSpec.getSize()方法获取到测量规格中的宽度和高度
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        // if(0 == mRatioWidth || 0 == mRatioHeight)表示没有设置宽高比,直接使用测量规格中的宽高
        // else表示如果设置了宽高比,则根据宽度<高度的比例关系,计算出正确的宽高,并设置为视图的测量尺寸。

        // 在 Android 中,每个 View 都需要经过测量、布局和绘制三个步骤才能最终显示出来
        // setMeasuredDimension(int measuredWidth, int measuredHeight) 方法接收两个参数:
        // 测量后的宽度(measuredWidth)和高度(measuredHeight)。
        // 这两个参数表示 View 在进行测量后,计算出来的实际尺寸。
        if (0 == mRatioWidth || 0 == mRatioHeight) {
            setMeasuredDimension(width, height);
        } else {
            if (width < height * mRatioWidth / mRatioHeight) {
                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
            } else {
                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
            }
        }
    }

}

2.2 建立jpg文件

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mFile = new File(getActivity().getExternalFilesDir(null), "pic.jpg");
}

2.3 在OnResume()方法中设置SurfaceTexture的监听事件

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

    // When the screen is turned off and turned back on, the SurfaceTexture is already
    // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
    // a camera and start preview from here (otherwise, we wait until the surface is ready in
    // the SurfaceTextureListener).

    // if -- 当屏幕关闭后重新打开, SurfaceTexture已经可用, 此时onSurfaceTextureAvailable()方法不会被回调,这种情况下直接打开相机
    // else -- 否则等待SurfaceTexture就绪后打开相机【onSurfaceTextureAvailable()回调】
    if (mTextureView.isAvailable()) {
        openCamera(mTextureView.getWidth(), mTextureView.getHeight());
    } else {
        mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
    }
}

其中:

/**
 * Starts a background thread and its {@link Handler}.
 */
/**
 * 创建一个后台线程和对应的 Handler,可以在后台线程中执行一些与相机相关的操作
 * 保证在主线程中的 UI 操作不会被阻塞
 */
private void startBackgroundThread() {
    mBackgroundThread = new HandlerThread("CameraBackground");
    mBackgroundThread.start();
    mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
/**
 * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a
 * {@link TextureView}.
 */

/**
 * 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) {
    }

};
(1)打开摄像头
/**
 * Opens the camera specified by {@link Camera2BasicFragment#mCameraId}.
 */

/**
 * 根据mCameraId打开相机
 * @param width
 * @param height
 */
private void openCamera(int width, int height) {
    // 检查当前应用程序是否拥有相机权限,并在没有权限时发起权限请求 ------- ①
    if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
        requestCameraPermission();
        return;
    }
    // 设置相机输出 ------- ②
    setUpCameraOutputs(width, height);
    // 配置格式转换 ------- ③
    configureTransform(width, height);
    Activity activity = getActivity();
    CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
    try {
        if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
            throw new RuntimeException("Time out waiting to lock camera opening.");
        }
        // 打开相机, 参数是(相机id, 相机状态回调, 子线程处理器) ------- ④
        manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
    }
}

其中①

private void requestCameraPermission() {
    if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
        new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG);
    } else {
        requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
    }
}

其中②

/**
 * Sets up member variables related to camera.
 *
 * @param width  The width of available size for camera preview
 * @param height The height of available size for camera preview
 */
/**
 * 设置相机的输出, 包括预览和拍照
 * 处理流程如下:
 * 1. 获取当前的摄像头, 并将拍照输出设置为最高画质
 * 2. 判断显示方向和摄像头传感器方向是否一致, 是否需要旋转画面
 * 3. 获取当前显示尺寸和相机的输出尺寸, 选择最合适的预览尺寸
 *
 * @param width
 * @param height
 */
@SuppressWarnings("SuspiciousNameCombination")
private void setUpCameraOutputs(int width, int height) {
    Activity activity = getActivity();
    // 获取CameraManager实例
    CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
    try {
        // 遍历运行本应用的设备的所有摄像头
        for (String cameraId : manager.getCameraIdList()) {
            // 摄像头特性,用于描述特定摄像头所支持的相关特性
            CameraCharacteristics characteristics
                    = manager.getCameraCharacteristics(cameraId);

            // We don't use a front facing camera in this sample.
            // 如果该摄像头是前置摄像头则跳过, 看下一个摄像头(本应用不使用前置摄像头)
            Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
            if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
                continue;
            }

            // 获取StreamConfigurationMap,它管理摄像头支持的所有输出格式和尺寸
            StreamConfigurationMap map = characteristics.get(
                    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            if (map == null) {
                continue;
            }

            // For still image captures, we use the largest available size.
            // 对于静止图像捕获,我们使用最大的分辨率
            Size largest = Collections.max(
                    Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
                    new CompareSizesByArea());
            // 设置ImageReader 的宽度和高度、指定图像的格式为 JPEG、maxImages是ImageReader一次可以访问的最大图片数量
            mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
                    ImageFormat.JPEG, /*maxImages*/2);
            // 设置一个监听器 ImageReader.OnImageAvailableListener,当静态图像准备好保存时回调onImageAvailable()
            mImageReader.setOnImageAvailableListener(
                    mOnImageAvailableListener, mBackgroundHandler);

            // Find out if we need to swap dimension to get the preview size relative to sensor coordinate.
            // 获取设备屏幕的旋转角度
            int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
            // noinspection ConstantConditions
            // 获取相机传感器的旋转角度
            mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
            // 传感器和设备的旋转角度不同时就需要进行转换
            boolean swappedDimensions = false;
            switch (displayRotation) {
                case Surface.ROTATION_0:
                case Surface.ROTATION_180:
                    if (mSensorOrientation == 90 || mSensorOrientation == 270) {
                        swappedDimensions = true;
                    }
                    break;
                case Surface.ROTATION_90:
                case Surface.ROTATION_270:
                    if (mSensorOrientation == 0 || mSensorOrientation == 180) {
                        swappedDimensions = true;
                    }
                    break;
                default:
                    Log.e(TAG, "Display rotation is invalid: " + displayRotation);
            }

            // 获取当前的屏幕尺寸, 放到一个Point对象里
            Point displaySize = new Point();
            activity.getWindowManager().getDefaultDisplay().getSize(displaySize);

            // 旋转前的预览宽度和高度(相机给出的), 通过传进来的参数获得
            int rotatedPreviewWidth = width;
            int rotatedPreviewHeight = height;
            // 将当前的显示尺寸赋给最大的预览尺寸(能够显示的尺寸, 用来计算用的(texture可能比它小需要适配))
            int maxPreviewWidth = displaySize.x;
            int maxPreviewHeight = displaySize.y;

            if (swappedDimensions) {
                rotatedPreviewWidth = height;
                rotatedPreviewHeight = width;
                maxPreviewWidth = displaySize.y;
                maxPreviewHeight = displaySize.x;
            }

            // 尺寸太大时的极端处理
            if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
                maxPreviewWidth = MAX_PREVIEW_WIDTH;
            }

            if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
                maxPreviewHeight = MAX_PREVIEW_HEIGHT;
            }

            // Danger, W.R.! Attempting to use too large a preview size could  exceed the camera
            // bus' bandwidth limitation, resulting in gorgeous previews but the storage of
            // garbage capture data.
            // 计算出最适合的预览尺寸
            // 第一个参数:map.getOutputSizes(SurfaceTexture.class)表示SurfaceTexture支持的尺寸List
            mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
                    rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
                    maxPreviewHeight, largest);

            // We fit the aspect ratio of TextureView to the size of preview we picked.
            // 获取当前的屏幕方向
            int orientation = getResources().getConfiguration().orientation;

            if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                // 横向
                mTextureView.setAspectRatio(
                        mPreviewSize.getWidth(), mPreviewSize.getHeight());
            } else {
                // 竖向
                mTextureView.setAspectRatio(
                        mPreviewSize.getHeight(), mPreviewSize.getWidth());
            }

            // Check if the flash is supported.
            // 检查相机设备是否支持闪光灯功能
            Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
            mFlashSupported = available == null ? false : available;

            mCameraId = cameraId;
            return;
        }
    } catch (CameraAccessException e) {
        e.printStackTrace();
    } catch (NullPointerException e) {
        // Currently an NPE is thrown when the Camera2API is used but not supported on the
        // device this code runs.
        ErrorDialog.newInstance(getString(R.string.camera_error))
                .show(getChildFragmentManager(), FRAGMENT_DIALOG);
    }
}
/**
 * Given {@code choices} of {@code Size}s supported by a camera, choose the smallest one that
 * is at least as large as the respective texture view size, and that is at most as large as the
 * respective max size, and whose aspect ratio matches with the specified value. If such size
 * doesn't exist, choose the largest one that is at most as large as the respective max size,
 * and whose aspect ratio matches with the specified value.
 *
 * @param choices           The list of sizes that the camera supports for the intended output class
 * @param textureViewWidth  The width of the texture view relative to sensor coordinate
 * @param textureViewHeight The height of the texture view relative to sensor coordinate
 * @param maxWidth          The maximum width that can be chosen
 * @param maxHeight         The maximum height that can be chosen
 * @param aspectRatio       The aspect ratio
 * @return The optimal {@code Size}, or an arbitrary one if none were big enough
 */

/**
 * 返回最合适的预览尺寸
 *
 * @param choices           相机支持的预期输出类的尺寸list
 * @param textureViewWidth  texture view 宽度
 * @param textureViewHeight texture view 高度
 * @param maxWidth          能够选择的最大宽度
 * @param maxHeight         能够选择的醉倒高度
 * @param aspectRatio       图像的比例(pictureSize, 只有当pictureSize和textureSize保持一致, 才不会失真)
 * @return 最合适的预览尺寸
 */
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
                                      int textureViewHeight, int maxWidth, int maxHeight,
                                      Size aspectRatio) {

    // Collect the supported resolutions that are at least as big as the preview Surface
    // 存放支持尺寸中所有小于等于限定最大尺寸, 大于等于texture控件尺寸的Size
    List<Size> bigEnough = new ArrayList<>();
    // Collect the supported resolutions that are smaller than the preview Surface
    // 存放支持尺寸中所有小于限定尺寸, 小于texture控件尺寸的Size
    List<Size> notBigEnough = new ArrayList<>();
    int w = aspectRatio.getWidth();
    int h = aspectRatio.getHeight();
    for (Size option : choices) {
        if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
                option.getHeight() == option.getWidth() * h / w) {
            if (option.getWidth() >= textureViewWidth &&
                    option.getHeight() >= textureViewHeight) {
                bigEnough.add(option);
            } else {
                notBigEnough.add(option);
            }
        }
    }

    // Pick the smallest of those big enough. If there is no one big enough, pick the
    // largest of those not big enough.
    // 从bigEnough列表中选择Size最小的一个作为最佳预览尺寸
    // 如果bigEnough列表中不存在数据,则从notBigEnough列表中选择Size最大的一个
    if (bigEnough.size() > 0) {
        return Collections.min(bigEnough, new CompareSizesByArea());
    } else if (notBigEnough.size() > 0) {
        return Collections.max(notBigEnough, new CompareSizesByArea());
    } else {
        Log.e(TAG, "Couldn't find any suitable preview size");
        return choices[0];
    }
}

其中③

/**
 * Configures the necessary {@link Matrix} transformation to `mTextureView`.
 * This method should be called after the camera preview size is determined in
 * setUpCameraOutputs and also the size of `mTextureView` is fixed.
 *
 * @param viewWidth  The width of `mTextureView`
 * @param viewHeight The height of `mTextureView`
 */

/**
 * 屏幕方向发生改变时调用转换数据方法
 * @param viewWidth mTextureView 的宽度
 * @param viewHeight mTextureView 的高度
 */
private void configureTransform(int viewWidth, int viewHeight) {
    Activity activity = getActivity();
    if (null == mTextureView || null == mPreviewSize || null == activity) {
        return;
    }
    int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
    Matrix matrix = new Matrix();
    RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
    RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
    float centerX = viewRect.centerX();
    float centerY = viewRect.centerY();
    if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
        bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
        matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
        float scale = Math.max(
                (float) viewHeight / mPreviewSize.getHeight(),
                (float) viewWidth / mPreviewSize.getWidth());
        matrix.postScale(scale, scale, centerX, centerY);
        matrix.postRotate(90 * (rotation - 2), centerX, centerY);
    } else if (Surface.ROTATION_180 == rotation) {
        matrix.postRotate(180, centerX, centerY);
    }
    mTextureView.setTransform(matrix);
}

其中④

/**
 * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
 */
/**
 * CameraDevice状态改变的回调函数
 */
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

    @Override
    public void onOpened(@NonNull CameraDevice cameraDevice) {
        // This method is called when the camera is opened.  We start camera preview here.
        // 当相机打开执行以下操作:
        // 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();
        }
    }

};
(2)预览过程
/**
 * Creates a new {@link CameraCaptureSession} for camera preview.
 */
/**
 * 创建预览的session
 */
private void createCameraPreviewSession() {
    try {
        // 获取texture实例
        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        assert texture != null;

        // We configure the size of default buffer to be the size of camera preview we want.
        // 设置TextureView的缓冲区大小
        texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());

        // This is the output Surface we need to start preview.
        // 用来开始预览的输出surface
        Surface surface = new Surface(texture);

        // We set up a CaptureRequest.Builder with the output Surface.
        // 获取CaptureRequest.builder,一会儿用来获取CaptureRequest实例
        mPreviewRequestBuilder
                = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        mPreviewRequestBuilder.addTarget(surface);

        // Here, we create a CameraCaptureSession for camera preview.
        // 获取用于预览的CameraCaptureSession
        mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                // new一个 CameraCaptureSession.StateCallback()的子类,调用onConfigured方法,在这里获得CaptureRequest实例
                new CameraCaptureSession.StateCallback() {

                    @Override
                    public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                        // The camera is already closed
                        // 当相机关闭时, 直接返回
                        if (null == mCameraDevice) {
                            return;
                        }

                        // When the session is ready, we start displaying the preview.
                        // 当相机打开,会话也就绪后开始创建请求
                        mCaptureSession = cameraCaptureSession;
                        try {
                            // Auto focus should be continuous for camera preview.
                            // 自动对焦
                            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                            // Flash is automatically enabled when necessary.
                            // 自动闪光
                            setAutoFlash(mPreviewRequestBuilder);

                            // Finally, we start displaying the camera preview.
                            // 构件上面的Request,这里可以发现是通过Request设置各种参数的
                            mPreviewRequest = mPreviewRequestBuilder.build();
                            // 调用CameraCaptureSession中setRepeatingRequest方法重复发送请求,这样预览画面就会一直有数据返回
                            mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                    mCaptureCallback, mBackgroundHandler);
                        } catch (CameraAccessException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onConfigureFailed(
                            @NonNull CameraCaptureSession cameraCaptureSession) {
                        showToast("Failed");
                    }
                }, null
        );
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}
(3)拍照流程

camera2Basic中拍照界面如下:

在这里插入图片描述
Camera2BasicFragment类实现了View.OnClickListener接口,重写了OnClick方法,当点击Picture按钮时:

@Override
public void onClick(View view) {
    switch (view.getId()) {
        case R.id.picture: {
            // 开始拍照
            takePicture();
            break;
        }
        case R.id.info: {
            Activity activity = getActivity();
            if (null != activity) {
                // new一个Dialog对话框
                new AlertDialog.Builder(activity)
                        .setMessage("mfd")
                        .setPositiveButton(android.R.string.ok, null)
                        .show();
            }
            break;
        }
    }
}
/**
 * Initiate a still image capture.
 */
private void takePicture() {
    lockFocus();
}
/**
 * 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.
        // 构建自动对焦请求
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                CameraMetadata.CONTROL_AF_TRIGGER_START);
        // Tell #mCaptureCallback to wait for the lock.
        // 下一步可以看到,发送拍照请求有一个CameraCaptureSession.CaptureCallback回调,这里是等待焦点锁定
        mState = STATE_WAITING_LOCK;
        // 发送拍照请求
        mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                mBackgroundHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

这里我自己把重写的两个方法调整到了上面,自定义的Process方法移动到了下面,更符合我认知的顺序

/**
 * A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture.
 */
/**
 * new一个CameraCaptureSession.CaptureCallback(),拍照完成后回调onCaptureCompleted方法,但是这里并没有开始拍照
 */
private CameraCaptureSession.CaptureCallback mCaptureCallback
        = new CameraCaptureSession.CaptureCallback() {

    @Override
    public void onCaptureProgressed(@NonNull CameraCaptureSession session,
                                    @NonNull CaptureRequest request,
                                    @NonNull CaptureResult partialResult) {
        process(partialResult);
    }

    @Override
    public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                   @NonNull CaptureRequest request,
                                   @NonNull TotalCaptureResult result) {
        process(result);
    }

    private void process(CaptureResult result) {
        switch (mState) {
            case STATE_PREVIEW: {
                // We have nothing to do when the camera preview is working normally.
                // 显示相机预览状态,不需要做任何事,直接返回
                break;
            }
            case STATE_WAITING_LOCK: {
                // 等待焦点锁定状态
                Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
                // 某些设备完成锁定后CONTROL_AF_STATE可能为null
                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
                    // 如果焦点已经锁定(不管自动对焦是否成功), 检查AE的返回, 注意某些设备CONTROL_AE_STATE可能为空
                    Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                    if (aeState == null ||
                            aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                        // 如果自动曝光(AE)设定良好, 将状态置为拍照, 执行拍照
                        mState = STATE_PICTURE_TAKEN;
                        captureStillPicture();
                    } else {
                        // 以上条件都不满足(AE设定没有完成), 执行预拍照系列操作
                        runPrecaptureSequence();
                    }
                }
                break;
            }
            case STATE_WAITING_PRECAPTURE: {
                // CONTROL_AE_STATE can be null on some devices
                // 等待自动曝光(AF),处于预拍照状态, 某些设备CONTROL_AE_STATE可能为null
                Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                if (aeState == null ||
                        aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
                        aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
                    mState = STATE_WAITING_NON_PRECAPTURE;
                }
                break;
            }
            case STATE_WAITING_NON_PRECAPTURE: {
                // CONTROL_AE_STATE can be null on some devices
                Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                // 自动曝光设定完成,不处于预拍照状态,将mState设置为拍照执行拍照
                if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
                    mState = STATE_PICTURE_TAKEN;
                    captureStillPicture();
                }
                break;
            }
        }
    }

};

上面程序主要是调用captureStillPicture()方法和runPrecaptureSequence()方法,其中runPrecaptureSequence()方法只是修改了mState,最后也是调用captureStillPicture()方法,所以先看runPrecaptureSequence()方法:

/**
 * Run the precapture sequence for capturing a still image. This method should be called when
 * we get a response in {@link #mCaptureCallback} from {@link #lockFocus()}.
 */
/**
 * 执行预拍照操作
 */
private void runPrecaptureSequence() {
    try {
        // This is how to tell the camera to trigger.
        // 构建预拍照请求
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
        // Tell #mCaptureCallback to wait for the precapture sequence to be set.
        // 修改mState值为预拍照状态,等待自动曝光(AF)
        mState = STATE_WAITING_PRECAPTURE;
        // 这里拍照没有完成,回到mCaptureCallback,由于修改了mState,就会走case STATE_WAITING_PRECAPTURE分支
        mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                mBackgroundHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

和预览流程很像,创建CameraCaptureSession,获取CaptureRequest下参数等

/**
 * 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 {
        final Activity activity = getActivity();
        if (null == activity || 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());

        // Use the same AE and AF modes as the preview.
        captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
        setAutoFlash(captureBuilder);

        // Orientation
        // 方向
        int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
        captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));

        // 拍照结束后回调onCaptureCompleted方法,重置对焦,下面能看到,拍照前停止了预览,所以拍照后还需要重新开启预览
        CameraCaptureSession.CaptureCallback CaptureCallback
                = new CameraCaptureSession.CaptureCallback() {

            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                           @NonNull CaptureRequest request,
                                           @NonNull TotalCaptureResult result) {
                showToast("Saved: " + mFile);
                Log.d(TAG, mFile.toString());
                unlockFocus();
            }
        };

        mCaptureSession.stopRepeating();
        mCaptureSession.abortCaptures();
        mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}
/**
 * Unlock the focus. This method should be called when still image capture sequence is
 * finished.
 */
/**
 * 拍照完成后的重置操作
 */
private void unlockFocus() {
    try {
        // Reset the auto-focus trigger
        // 解锁焦点
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
        // 设置自动闪光,之前拍照时设置了需要或者不需要闪光灯,这里重置为自动
        setAutoFlash(mPreviewRequestBuilder);

        mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
                mBackgroundHandler);
        // After this, the camera will go back to the normal state of preview.
        // 拍照后设置为预览状态,调用CameraCaptureSession中setRepeatingRequest方法重复预览请求
        mState = STATE_PREVIEW;
        mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback,
                mBackgroundHandler);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}
private void setAutoFlash(CaptureRequest.Builder requestBuilder) {
    if (mFlashSupported) {
        requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
    }
}
(4)图片保存

这一部分就是将拍的图片保存到制定目录

/**
 * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
 * still image is ready to be saved.
 */
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
        = new ImageReader.OnImageAvailableListener() {

    // 当静态图像准备好保存时回调
    @Override
    public void onImageAvailable(ImageReader reader) {
        mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
    }

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

}

第二部分 过程中使用的函数:

在这里插入图片描述
在这里插入图片描述

1、CameraManager.openCamera方法

@RequiresPermission(android.Manifest.permission.CAMERA)
public void openCamera(@NonNull String cameraId,
        @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
        throws CameraAccessException {

    openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
            USE_CALLING_UID);
}


  1. cameraId:要打开相机的唯一标识
  2. callback:打开相机后调用的回调
  3. handler:处理回调的handler,如果为null则使用当前线程的Looper

可能抛出的异常:

  • CameraAccessException:可能摄像头被设备禁用、已经断开连接或者正在被更高优先级的摄像头API调用
  • IllegalArgumentException:如果 cameraId 或callback为 null,或者 cameraId 不匹配任何当前或以前可⽤的相机设备
  • SecurityException – 如果应⽤程序⽆权访问相机

​ 如果设备在对getCameraIdList和openCamera的调⽤之间断开连接,或者如果更⾼优先级的相机 API 客户端开始使⽤相机设备,则打开可能会失败。⼀般来说,应⽤程序进程中运⾏最顶层的前台活动在访 问摄像头时将获得最⾼优先级,即使摄像头设备正在被另⼀个摄像头 API 客户端使⽤。 任何以这种⽅式 失去对相机的控制的低优先级应⽤程序都会收到⼀个CameraDevice.StateCallback.onDisconnected回 调。

​ 如果打开摄像头设备失败,则会调⽤设备回调的onError⽅法,后续对摄像头设备的调⽤会抛出 CameraAccessException 。

2、CameraDevice.StateCallback中方法

(1)onOpened

public abstract void onOpened(@NonNull CameraDevice camera); // Must implement

相机完成打开时调用的方法,至此,摄像头设备就可以使用了

(2)onClosed

public void onClosed(@NonNull CameraDevice camera) {
	// Default empty implementation
}

​ 使⽤android.hardware.camera2.CameraDevice#close⽅法关闭相机设备时调⽤的⽅法,将来任何尝试调⽤此 CameraDevice 上的⽅法都会抛出IllegalStateException异常

(3)onDisconnected

public abstract void onDisconnected(@NonNull CameraDevice camera);  // Must implement

​ 当相机设备不再可⽤时调⽤的⽅法。如果打开相机失败,会调⽤此回调⽽不是onOpened,任何调⽤此 CameraDevice ⽅法的尝试都会抛出CameraAccessException。断开连接可能是由于安全策略或权限的更改、移动相机设备的物理断开或更⾼优先级的客户端使⽤了相机

(4)onError

public abstract void onError(@NonNull CameraDevice camera, @ErrorCode int error); // Must implement

​ 当相机设备遇到严重错误时调⽤的⽅法。这表明相机设备或相机服务以某种⽅式出现故障。 将来任何尝试调⽤此 CameraDevice 上的⽅法都将引发带有CAMERA_ERROR原因的CameraAccessException

参数:

  • camera:报告错误的CameraDevice对象
  • error:错误代码:
    • ERROR_CAMERA_IN_USE : 由于指定ID的相机被更⾼优先级的相机 API 客户端使⽤⽽导致打开 相机失败时,可能会产⽣此错误
    • ERROR_MAX_CAMERAS_IN_USE: 已达到打开摄像头数量的系统范围限制,在关闭之前的实例 之前,⽆法打开更多摄像头设备
    • ERROR_CAMERA_DISABLED: 由于设备策略⽽⽆法打开相机设备
    • ERROR_CAMERA_DEVICE: 相机设备遇到了致命错误
    • ERROR_CAMERA_SERVICE:相机服务遇到了致命错误。Android 设备可能需要关闭并重新启 动才能恢复相机功能,或者可能存在持续的硬件问题。

3、CameraDevice#CreateCaptureRequest方法

为新的捕获请求创建⼀个CaptureRequest.Builder ,使⽤⽬标⽤例的模板进⾏初始化。

@NonNull
public abstract CaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType)
        throws CameraAccessException;

参数:

  • templateType :⽤于创建CaptureRequest的模板类型。 并⾮所有设备都⽀持所有模板类型:
    • TEMPLATE_PREVIEW:创建适⽤于相机预览窗⼝的请求。该请求通常与 CameraCaptureSession.setRepeatingRequest⽅法⼀起使⽤。具体来说,这意味着⾼帧率优先于最⾼质量的后期处理。所有相机设备都⽀持此模板
    • TEMPLATE_STILL_CAPTURE:创建适合静态图像捕获的请求。 这些请求通常与CameraCaptureSession.capture⽅法⼀起 使⽤。具体来说,这意味着优先考虑图像质量⽽不是帧速率。除了不是BACKWARD_COMPATIBLE的DEPTH_OUTPUT(这里好像)设备外,所有相机设备都保证⽀持此模板。
    • TEMPLATE_RECORD:创建适合视频录制的请求。 这些请求通常与CameraCaptureSession.setRepeatingRequest ⽅法⼀起使⽤。具体⽽⾔,这意味着使⽤稳定的帧速率,并为记录质量设置后处理。除了不是BACKWARD_COMPATIBLE的DEPTH_OUTPUT设备外,所有相机设备都保证⽀持 此模板。
    • TEMPLATE_VIDEO_SNAPSHOT:在录制视频时创建适合静态图像捕获的请求。 这些请求通常与 CameraCaptureSession.capture⽅法⼀起使⽤。具体来说,这意味着在不中断正在进⾏的录制的情况下最⼤限度地提⾼图像质量。 除了旧设备和不是BACKWARD_COMPATIBLE的DEPTH_OUTPUT设备之外,所有相机设备 都保证⽀持此模板。
    • TEMPLATE_ZERO_SHUTTER_LAG:创建适⽤于零快⻔延迟(ZSL)静态捕捉的请求。具体⽽⾔,这意味着在不影响预览帧速率的情况下最⼤限度地提⾼图像质量。AE/AWB/AF 应处于⾃动模式。⽀持PRIVATE_REPROCESSING功能或YUV_REPROCESSING功能的相机设备保证⽀持此模 板。
    • TEMPLATE_MANUAL:⽤于直接应⽤程序控制捕获参数的基本模板。禁⽤所有⾃动控制(⾃动曝光、⾃动⽩平衡、⾃动对焦)。⽀持MANUAL_SENSOR功能的相机设备保证⽀持此模板。

可能抛出异常:

  • IllegalArgumentException : 设备不⽀持指定的templateType
  • CameraAccessException:相机设备不再连接或遇到致命错误
  • IllegalStateException:相机设备已关闭

4、CaptureRequest#addTarget方法

public void addTarget(@NonNull Surface outputTarget) {
    mRequest.mSurfaceSet.add(outputTarget);
}

​ 将Surface添加到此请求的⽬标列表中。多次添加同⼀个Surface是⽆效的。 当向相机设备发出请求时,添加的Surface必须是最近⼀次调⽤CameraDevice.createCaptureSession 中包含的Surface之⼀。

5、CameraDevice#createCaptureSession方法

​ 通过向相机设备提供 Surface 的目标输出集来创建新的createCaptureSession,创建 CameraCaptureSession 后,可以使⽤capture 、 captureBurst 、 setRepeatingRequest或 setRepeatingBurst提交请求。

@Deprecated
public abstract void createCaptureSession(@NonNull List<Surface> outputs,
        @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
        throws CameraAccessException;

参数:

  • outputs :应作为捕获图像数据目标的⼀组新Surface,即添加的流的列表
  • callback:通知新捕获会话状态的回调
  • handler:应调⽤回调的处理程序,为null时使⽤当前线程的looper

6、CameraCaptureSession.StateCallback中方法

(1)onConfigured

public abstract void onConfigured(@NonNull CameraCaptureSession session);

​ 当相机设备完成⾃身配置时调⽤此⽅法,并且会话可以开始处理捕获请求。 如果会话中已经有捕获请求,它们将在调⽤此回调后开始处理,并且会话将在调⽤此回调后⽴即调⽤ onActive 。如果尚未提交任何捕获请求,则会话将在此回调之后⽴即调⽤onReady

参数:

  • session:CameraDevice#createCaptureSession的返回值

(2)onConfigureFailed

public abstract void onConfigureFailed(@NonNull CameraCaptureSession session);

​ 如果⽆法按要求配置会话,则此⽅法会被调⽤。 如果请求的输出集包含不受⽀持的⼤⼩,或者⼀次请求的输出过多,则可能会发⽣这种情况。会话被认 为是关闭的,调⽤此回调后对其调⽤的所有⽅法都将抛出 IllegalStateException。

(3)onReady

public void onReady(@NonNull CameraCaptureSession session) {
    // default empty implementation
}

每次会话没有更多捕获请求要处理时,都会调⽤此⽅法。只要会话完成处理其所有活动捕获请求,并且 没有设置repeatingRequest或burst请求,就会调⽤此回调

(4)onActive

public void onActive(@NonNull CameraCaptureSession session) {
    // default empty implementation
}

当会话开始主动处理捕获请求时调⽤此⽅法

(5)onCaptureQueueEmpty

public void onCaptureQueueEmpty(@NonNull CameraCaptureSession session) {
    // default empty implementation
}

当相机设备的输⼊捕获队列为空并准备好接受下⼀个请求时调⽤此⽅法

(6)onClosed

public void onClosed(@NonNull CameraCaptureSession session) {
    // default empty implementation
}

会话关闭时调⽤此⽅法(⽤户关闭设备,或由于摄像头设备断开连接或致命错误,会话将关闭)

(7)onSurfacePrepared

public void onSurfacePrepared(@NonNull CameraCaptureSession session,
        @NonNull Surface surface) {
    // default empty implementation
}

当输出 Surface 的缓冲区预分配完成时调⽤此⽅法

7、CaptureRequest.Builder#set方法

public <T> void set(@NonNull Key<T> key, T value) {
    mRequest.mLogicalCameraSettings.set(key, value);
}

参数:

  • key :要写⼊的元数据字段
  • value: 要将字段设置为的值,它必须是与键匹配的类型

key-value字段设置值如下,每个key对应多value:

  • CaptureRequest.CONTROL_AF_MODE:当前是否启⽤了⾃动对焦 (AF),以及设置的模式

    • CameraMetadata.CONTROL_AF_MODE_OFF:⾃动对焦程序不控制镜头;焦距由应⽤程序控制
    • CameraMetadata.CONTROL_AF_MODE_AUTO:基本⾃动对焦模式。在此模式下,除⾮调⽤⾃动对焦触发操作,否则镜头不会移动。 当该触发器被激活时,AF 将转换为 ACTIVE_SCAN,然后根据扫描的结果会进入 FOCUSED(对焦成功)或 NOT_FOCUSED(对焦失败)状态
    • CameraMetadata.CONTROL_AF_MODE_MACRO:微距模式。在此模式下,除⾮调⽤⾃动对焦触发操作,否则镜头不会移动。 当该触发器被激活时,AF 将转换为 ACTIVE_SCAN,然后根据扫描的结果会进入 FOCUSED(对焦成功)或 NOT_FOCUSED(对焦失败)状态。触发取消 AF 会将镜头位置重置为默认值,并将 AF 状态设置为 INACTIVE。
    • CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO:在此模式下,⾃动对焦算法会不断修改镜头位置,以尝试提供始终对焦的图像流。通常意味着对焦移动较慢
    • CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE:在此模式下,⾃动对焦算法会不断修改镜头位置,以尝试提供始终对焦的图像流。,通常意味着尽可能快速地进行对焦
    • CameraMetadata.CONTROL_AF_MODE_EDOF:扩展景深(数字对焦)模式。相机设备将⾃动⽣成具有扩展景深的图像; 拍照前⽆需进⾏特 殊对焦操作。AF 触发器被忽略,AF 状态将始终为 INACTIVE
  • .CaptureRequest.CONTROL_AE_MODE:相机设备的⾃动曝光动作所需的模式

    • CameraMetadata.CONTROL_AE_MODE_OFF:相机设备的⾃动曝光程序被禁⽤

    • CameraMetadata.CONTROL_AE_MODE_ON:相机设备的⾃动曝光程序处于活动状态,没有闪光灯控制

    • CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH:相机设备的⾃动曝光程序处于活动状态,相机设备还控制相机的闪光灯,在弱光条件下闪光。

    • CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH:相机设备的⾃动曝光程序处于活动状态,相机设备还控制相机的闪光灯,stillPicture始终闪光。

    • CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH _REDEYE:与CONTROL_AE_MODE_ON_AUTO_FLASH类似,,但具有⾃动红眼消除功能

      (红眼是指在使用闪光灯时,光线直接穿过瞳孔并反射回来,由于血管的反射而导致瞳孔呈现红色。这种现象通常发生在暗光环境下,因为在光线较暗的情况下,瞳孔会扩大以吸收更多的光线,从而增加了产生红眼的可能性。为了减少红眼现象,许多相机都提供了"减少红眼"的功能,它通常通过在拍摄前先发出一两个较小的闪光灯来使被摄对象的瞳孔收缩,从而降低红眼的出现概率)

  • CaptureRequest.CONTROL_AF_TRIGGER:是否会为特定请求触发自动对焦的参数

    • CameraMetadata.CONTROL_AF_TRIGGER_IDLE:触发器处于空闲状态
    • .CameraMetadata.CONTROL_AF_TRIGGER_START:在接下来的捕获请求中触发自动对焦操作。这通常用于在拍摄照片或者开始录制视频前,先触发一次自动对焦操作,以确保图像清晰度。
    • CameraMetadata.CONTROL_AF_TRIGGER_CANCEL:表示取消当前的自动对焦触发请求。如果之前已经发送了 CONTROL_AF_TRIGGER_START 请求,但后续又决定不需要进行自动对焦,可以发送这个请求来取消之前的触发请求。
  • CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER:是否会在捕获图像前执行预捕获序列以触发自动曝光(AE)的参数

    • CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE:触发器处于空闲状态
    • CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_ START:表示希望在接下来的捕获请求中触发预捕获序列以执行自动曝光操作。这通常用于在拍摄照片或者开始录制视频前,先触发一次预捕获序列以确保合适的曝光参数。
    • .CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_ CANCEL:表示取消当前的预捕获触发请求。如果之前已经发送了 CONTROL_AE_PRECAPTURE_TRIGGER_START 请求,但后续又决定不需要进行自动曝光预捕获,可以发送这个请求来取消之前的触发请求。
  • CaptureRequest.JPEG_ORIENTATION:用于指定捕获的 JPEG 图像的方向或旋转角度。在 Android 相机开发中,通过设置该参数,可以控制相机设备在捕获图像时对图像进行旋转,以确保最终生成的 JPEG 图像具有正确的方向。

    • 0
    • 90
    • 180
    • 270

    注:此⽅向是相对于相机传感器的⽅向

8、CameraCaptureSession#SetRepeatingRequest方法

通过此捕获会话请求不停地重复捕获图像。使⽤此⽅法,相机设备将使⽤提供的CaptureRequest中的 设置以可能的最⼤速率持续捕获图像。

  • 重复请求是应⽤程序维护预览或其他连续帧流的⼀种简单⽅法,⽽⽆需通过capture不断提交相同的请求。
  • 重复请求的优先级低于通过capture或captureBurst提交的请求,因此如果在重复请求处于活动状 态时调⽤capture ,则在处理任何进⼀步的重复请求之前将处理捕获请求。
  • 要停⽌重复捕获,需要调⽤stopRepeating 。 调⽤abortCaptures也会清除请求。
public abstract int setRepeatingRequest(@NonNull CaptureRequest request,
        @Nullable CaptureCallback listener, @Nullable Handler handler)
        throws CameraAccessException;

参数:

  • request:指定要重复捕获的捕获请求对象
  • listener:指定捕获回调的对象。 如果为 null,则不会为此请求流⽣成元数据,但仍会⽣成图像数据
  • handler: 在其上调⽤侦听器的处理程序,为null时使⽤当前线程的looper

返回值:

CameraCaptureSession.CaptureCallback#onCaptureSequenceCompleted⽅法使⽤的唯⼀的⼀个 capture sequence ID。

可能抛出的异常:

  • CameraAccessException :如果相机设备不再连接或遇到致命错误
  • IllegalStateException: 如果此会话不再处于活动状态,或者因为会话已显式关闭、新会话已创建或相 机设备已关闭
  • IllegalArgumentException: 如果请求没有引⽤ Surface 或引⽤当前未配置为输出的 Surface; 或者该请求是重新处理捕获请求; 或者捕获的⽬标是⼀个正在prepared中的Surface; 或者handler为 null,listener不为null,调⽤线程没有looper; 或者没有传⼊请求

9、CameraCaptureSession.CaptureCallback中方法

(1)onCaptureStarted

public void onCaptureStarted(@NonNull CameraCaptureSession session,
        @NonNull CaptureRequest request, long timestamp, long frameNumber) {
    // default empty implementation
}

​ 在相机捕获过程中,当某个捕获请求开始时,系统会回调onCaptureStarted方法,这个方法提供了捕获开始时的回调,让应用程序有机会在捕获开始时执行特定的操作或者处理。

参数:

  • session:与此捕获请求相关联的CameraCaptureSession
  • request:表示触发此次捕获的CaptureRequest
  • timestamp:表示捕获开始的时间戳,通常以纳秒为单位
  • frameNumber:表示捕获请求的帧号

(2)onCapturePartial

public void onCapturePartial(CameraCaptureSession session,
        CaptureRequest request, CaptureResult result) {
    // default empty implementation
}

当图像捕获的某些结果可⽤时调⽤此⽅法

(3)onCaptureProgressed

public void onCaptureProgressed(@NonNull CameraCaptureSession session,
        @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
    // default empty implementation
}

这个方法的作用是在相机捕获过程中,当部分结果可用时被调用。它允许你获取捕获过程中的部分结果数据,并根据需要进行处理。

(4)onCaptureCompleted

public void onCaptureCompleted(@NonNull CameraCaptureSession session,
        @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
    // default empty implementation
}

当图像捕获完全完成并且所有结果元数据可⽤时调⽤此⽅法。

此回调将始终在最后⼀次onCaptureProgressed之后触发; 换句话说,⼀旦完整的结果可⽤,将不再提供部分结果。

(5)onCaptureFailed

public void onCaptureFailed(@NonNull CameraCaptureSession session,
        @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
    // default empty implementation
}

当相机设备未能为请求⽣成CaptureResult时将调⽤此⽅法

(6)onCaptureSequenceCompleted

public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session,
        int sequenceId, long frameNumber) {
    // default empty implementation
}

当捕获序列在任何CaptureResult或CaptureFailure已通过此侦听器返回之前中⽌时,此⽅法独⽴于 CaptureCallback 中的其他⽅法调⽤。

(7)onCaptureSequenceAborted

public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session,
        int sequenceId) {
    // default empty implementation
}

​ 该方法在捕获序列完成时被调用,并且所有针对该序列的 CaptureResult 或 CaptureFailure 已经通过此监听器返回。在调用此回调之前,此监听器至少会返回一个CaptureResult或CaptureFailure。如果在处理任何请求之前中止捕获序列,则将调用 onCaptureSequenceAborted 方法。此⽅法独⽴于 CaptureCallback 中的其他⽅法调⽤。

(8)onCaptureSequenceAborted

public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session,
        int sequenceId) {
    // default empty implementation
}

​ 捕获序列在任何CaptureResult或CaptureFailure已通过此侦听器返回之前中⽌时调用,此⽅法独⽴于 CaptureCallback 中的其他⽅法调⽤。

(9)onCaptureBufferLost

public void onCaptureBufferLost(@NonNull CameraCaptureSession session,
        @NonNull CaptureRequest request, @NonNull Surface target, long frameNumber) {
    // default empty implementation
}

​ 如果某个捕获的单个缓冲区无法发送到其目标Surface时,将调用此方法。如果整个捕获失败,则将调用onCaptureFailed方法。如果捕获了部分但不是所有缓冲区,结果元数据不可用,则将调用onCaptureFailed方法,并且CaptureFailure#wasImageCaptured返回true,并伴随着一个或多个对于失败输出的onCaptureBufferLost的调用。

10、CameraCaptureSession.capture方法

public abstract int capture(@NonNull CaptureRequest request,
        @Nullable CaptureCallback listener, @Nullable Handler handler)
        throws CameraAccessException;

​ 提交要由相机设备捕获的图像的请求。 该请求定义了捕获单个图像的所有参数,包括传感器、镜头、闪光灯和后处理设置。 通过此⽅法提交的请求⽐通过setRepeatingRequest或setRepeatingBurst提交的请求具有更⾼的优先级,并且将在当前repeat/repeatBurst 处理完成后⽴即处理。

11、mState

​ 每次调⽤CameraCaptureSession#capture⽅法发送请求前,通常为当前 的请求指定⼀个状态,以便在CameraCaptureSession.CaptureCallback对象中根据不同的状态来进⾏ 下⼀步动作。

在Camera2Basic样例中定义了5种状态:

  1. STATE_PREVIEW:发送RepeatingRequest请求时,指定当前状态为STATE_PREVIEW,该状态下,在CameraCaptureSession.CaptureCallback回调中什么也不处理。
  2. STATE_WAITING_LOCK:向session发送动作为lock focus的Capture请求时,指定当前状态为STATE_WAITING_LOCK。 该状态下,需要从CaptureResult中获取AF和AE的状态,根据它们的状态决定是执⾏runPrecaptureSequence动作,或者是直接执⾏CaptureStillPicture下发拍照命令。⼀般,我们在向 CONTROL_AF_TRIGGER发送START命令之后,如果AE还未准备好(aeState != CaptureResult.CONTROL_AE_STATE_CONVERGED),那么需要执⾏runPrecaptureSequence动作。
  3. STATE_WAITING_PRECAPTURE:向session发送动作为runPrecaptureSequence的Capture请求时,指定当前状态为 STATE_WAITING_PRECAPTURE。具体来说,执⾏Precapture Sequence动作,要向 CONTROL_AE_PRECAPTURE_TRIGGER发送START命令,然后发送包含此命令的Capture请求。 该状态下,需要从CaptureResult中获取AE的状态,如果AE的状态为 CaptureResult.CONTROL_AE_STATE_PRECAPTURE或 CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED,更改当前状态为 STATE_WAITING_NON_PRECAPTURE,准备进⾏拍照。
  4. STATE_WAITING_NON_PRECAPTURE:PRECAPTURE 动作完成,指定当前状态为STATE_WAITING_NON_PRECAPTURE。此时,AE 的状态 会是 CONVERGED 或 FLASH_REQUIRED。 该状态下,准备执⾏拍照命令,修改当前状态为STATE_PICTURE_TAKEN
  5. STATE_PICTURE_TAKEN:AF和AE全部就绪,进⼊准备执⾏captureStillPicture命令的状态。 此状态下,不再使⽤之前预览时创建的CameraDevice.TEMPLATE_PREVIEW捕获请求,将会创建模板 为CameraDevice.TEMPLATE_STILL_CAPTURE的捕获请求。然后需要为新创建的捕获请求添加⽤于 存储照⽚的Surface,并设置捕获请求CONTROL_AF_MODE字段的值为CONTINUOUS_PICTURE以及 其他需要的字段之后,停⽌预览,开始向session发送拍照的捕获请求。

​ 拍照完成后,需要unlockFocus。具体来说,需要向CONTROL_AF_TRIGGER发送CANCEL命令,将AF和AE都设置为初始状态,然后修改当前状态为STATE_PREVIEW,向session发送setRepeatingRequest请求重新开始预览

12、StopRepeating

public abstract void stopRepeating() throws CameraAccessException;

取消由setRepeatingRequest或setRepeatingBurst设置的任何正在进⾏的重复捕获。 对通过capture 或captureBurst提交的请求没有影响。

13、abortCaptures

public abstract void abortCaptures() throws CameraAccessException;

​ 尽快丢弃所有当前pending的和正在进⾏的捕获。

​ 此⽅法是使⽤CameraDevice.createCaptureSession或 CameraDevice.createReprocessableCaptureSession将相机设备切换到新会话的最快⽅法,但代价是 丢弃正在进⾏的⼯作。 必须在创建新会话之前调⽤它。

第三部分 预览拍照流程总结

1、创建TextureView作为预览界面

2、获得CameraManager实例,用于便利可用的相机

3、获得CmeraCharacteristics实例,在打开相机前获得一些相机的特性信心,例如前置摄像头还是后置摄像头、摄像头支持的预览尺寸等

4、通过CameraManage打开相机,回调CameraDevice.StateCallback中onOpened方法,在这里开启预览

5、创建CaptureSession,回调CameraCaptureSession.StateCallback中onConfigured方法,在这里创建Request

6、获得CaptureRequest,在这里设置参数,例如自动对焦等

7、调用CameraCaptureSession中的setRepeattingReques方法,重复发送请求,这样预览界面就一直会有数据返回

8、调用CameraCaptureSession中的capture方法拍照,回调CameraCaptureSession.CaptureCallback中的onCaptureCompleted方法,在这里重置AE、AF设置,重新开启预览



作者刚接触这方面的工作,这里有很多内容都是源码中英文的简单翻译,也有一部分的个人理解,因为我觉得看别人的理解有时候可以和自己的理解互相印证,所以写了这篇学习总结,希望这篇文章对你有所帮助,当然如果您发现内容有错,欢迎批评指正,非常感谢!愿共同进步

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值