美文网首页
Android Camera v2 拍照

Android Camera v2 拍照

作者: gaookey | 来源:发表于2022-02-27 16:08 被阅读0次
    image.png

    Android 全新设计的 Camera v2 API 不仅大幅提高了 Android 系统拍照的功能,还支持 RAW 照片输出,甚至允许程序调整相机的对焦模式、曝光模式、快门等。

    Android 的 Camera v2 主要涉及如下 API。

    • CameraManager 摄像头管理器。这是一个全新的系统管理器,专门用于检测系统摄像头、打开系统摄像头。除此之外,调用 CameraManagerCameraCharacteristics getCameraCharacteristics(@NonNull String cameraId) 方法即可获取指定摄像头的相关特性。
    • CameraCharacteristics 摄像头特性。该对象通过 CameraManager 来获取,用于描述特定摄像头所支持的各种特性。
    • CameraDevice 代表系统摄像头。该类的功能类似于早期的 Camera 类。
    • CameraCaptureSession 这是一个非常重要的 API,当程序需要预览、拍照时,都需要先通过该类的实例创建 Session。而且不管预览还是拍照,也都是由该对象的方法进行控制的,其中控制预览的方法为 setRepeatingRequest();控制拍照的方法为 capture()

    为了监听 CameraCaptureSession 的创建过程,以及监听 CameraCaptureSession 的拍照过程,Camera V2 API 为 CameraCaptureSession 提供了 StateCallbackCaptureCallback 等内部类。

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

    Android 9 对相机 API 做了进一步增强,它可以同时从两个或更多的物理摄像头来获得数据流(对目前的双摄像头、多摄像头提供支持)。在支持双摄像头或多摄像头的设备上,增强的相机 API 可以实现单个摄像头无法实现的功能,例如无缝缩放、散景和立体效果等。
    增强后的相机 API 还允许调用合适或融合的相机数据流,以便在不同的摄像头之间切换自如。

    控制拍照的步骤大致如下。

    1. 调用 CameraManagervoid openCamera(@NonNull String cameraId, @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler) 方法打开指定摄像头。该方法的第一个参数代表要打开的摄像头 ID;第二个参数用于监听摄像头的状态;第三个参数代表执行 callbackHandler,如果程序希望直接在当前线程中执行 callback,则可将 handler 参数设为 null
    2. 当摄像头被打开之后,程序即可获取 CameraDevice——即根据摄像头 ID 获取了指定摄像头设备,然后调用 CameraDevicevoid createCaptureSession(@NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler) 方法来创建 CameraCaptureSession。该方法的第一个参数是一个 List 集合,封装了所有需要从该摄像头获取图片的 Surface,第二个参数用于监听 CameraCaptureSession 的创建过程;第三个参数代表执行 callbackHandler,如果程序希望直接在当前线程中执行 callback,则可将 handler 参数设为 null
    3. 不管预览还是拍照,程序都调用 CameraDeviceCaptureRequest.Builder createCaptureRequest(@RequestTemplate int templateType) 方法创建 CameraRequest.Builder,该方法支持 TEMPLATE_PREVIEW (预览)、TEMPLATE_RECORD(拍摄视频)、TEMPLATE_STILL_CAPTURE(拍照)等参数。
    4. 通过第 3 步所调用方法返回的 CameraRequest.Builder 设置拍照的各种参数,比如对焦模式、曝光模式等。
    5. 调用 CameraRequest.Builderbuild() 方法即可得到 CaptureRequest 对象,接下来程序可通过 CameraCaptureSessionsetRepeatingRequest() 方法开始预览,或调用 capture() 方法拍照。

    MainActivity

    public class MainActivity extends AppCompatActivity {
        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 FrameLayout rootLayout;
        // 定义自定义的AutoFitTextureView组件,用于预览摄像头照片
        private AutoFitTextureView textureView;
        // 摄像头ID(通常0代表后置摄像头,1代表前置摄像头)
        private String mCameraId = "0";
        // 定义代表摄像头的成员变量
        private CameraDevice cameraDevice;
        // 预览尺寸
        private Size previewSize;
        private CaptureRequest.Builder previewRequestBuilder;
        // 定义用于预览照片的捕获请求
        private CaptureRequest previewRequest;
        // 定义CameraCaptureSession成员变量
        private CameraCaptureSession captureSession;
        private ImageReader imageReader;
        private final TextureView.SurfaceTextureListener mSurfaceTextureListener
                = new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture texture
                    , int width, int height) {
                // 当TextureView可用时,打开摄像头
                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) {
            }
        };
        private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
            //  摄像头被打开时激发该方法
            @Override
            public void onOpened(@NonNull CameraDevice cameraDevice) {
                MainActivity.this.cameraDevice = cameraDevice;
                // 开始预览
                createCameraPreviewSession();  // ②
            }
    
            // 摄像头断开连接时激发该方法
            @Override
            public void onDisconnected(CameraDevice cameraDevice) {
                cameraDevice.close();
                MainActivity.this.cameraDevice = null;
            }
    
            // 打开摄像头出现错误时激发该方法
            @Override
            public void onError(CameraDevice cameraDevice, int error) {
                cameraDevice.close();
                MainActivity.this.cameraDevice = null;
                MainActivity.this.finish();
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            rootLayout = findViewById(R.id.root);
            requestPermissions(new String[]{Manifest.permission.CAMERA}, 0x123);
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode,
                                               @NonNull String[] permissions, @NonNull int[] grantResults) {
            if (requestCode == 0x123 && grantResults.length == 1
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 创建预览摄像头图片的TextureView组件
                textureView = new AutoFitTextureView(MainActivity.this, null);
                // 为TextureView组件设置监听器
                textureView.setSurfaceTextureListener(mSurfaceTextureListener);
                rootLayout.addView(textureView);
                findViewById(R.id.capture).setOnClickListener(view -> captureStillPicture());
            }
        }
    
        private void captureStillPicture() {
            try {
                if (cameraDevice == null) {
                    return;
                }
                // 创建作为拍照的CaptureRequest.Builder
                CaptureRequest.Builder captureRequestBuilder = cameraDevice
                        .createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                // 将imageReader的surface作为CaptureRequest.Builder的目标
                captureRequestBuilder.addTarget(imageReader.getSurface());
                // 设置自动对焦模式
                captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                // 设置自动曝光模式
                captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                        CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                // 获取设备方向
                int rotation = getWindowManager().getDefaultDisplay().getRotation();
                // 根据设备方向计算设置照片的方向
                captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION,
                        ORIENTATIONS.get(rotation));
                // 停止连续取景
                captureSession.stopRepeating();
                // 捕获静态图像
                captureSession.capture(captureRequestBuilder.build(),
                        new CameraCaptureSession.CaptureCallback()  // ⑤
                        {
                            // 拍照完成时激发该方法
                            @Override
                            public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                                           @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                                try {
                                    // 重设自动对焦模式
                                    previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
                                            CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
                                    // 设置自动曝光模式
                                    previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                                            CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                                    // 打开连续取景模式
                                    captureSession.setRepeatingRequest(previewRequest, null, null);
                                } catch (CameraAccessException e) {
                                    e.printStackTrace();
                                }
                            }
                        }, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    
        // 根据手机的旋转方向确定预览图像的方向
        private void configureTransform(int viewWidth, int viewHeight) {
            if (null == previewSize) {
                return;
            }
            // 获取手机的旋转方向
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            Matrix matrix = new Matrix();
            RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
            RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.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 / previewSize.getHeight(),
                        (float) viewWidth / previewSize.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);
            }
            textureView.setTransform(matrix);
        }
    
        // 打开摄像头
        private void openCamera(int width, int height) {
            setUpCameraOutputs(width, height);
            configureTransform(width, height);
            CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
            try {
                // 如果用户没有授权使用摄像头,直接返回
                if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                    return;
                }
                // 打开摄像头
                manager.openCamera(mCameraId, stateCallback, null); // ①
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    
        private void createCameraPreviewSession() {
            try {
                SurfaceTexture texture = textureView.getSurfaceTexture();
                texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
                Surface surface = new Surface(texture);
                // 创建作为预览的CaptureRequest.Builder
                previewRequestBuilder = cameraDevice
                        .createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                // 将textureView的surface作为CaptureRequest.Builder的目标
                previewRequestBuilder.addTarget(new Surface(texture));
                // 创建CameraCaptureSession,该对象负责管理处理预览请求和拍照请求
                cameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()),
                        new CameraCaptureSession.StateCallback() // ③
                        {
                            @Override
                            public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                                // 如果摄像头为null,直接结束方法
                                if (null == cameraDevice) {
                                    return;
                                }
                                // 当摄像头已经准备好时,开始显示预览
                                captureSession = cameraCaptureSession;
                                // 设置自动对焦模式
                                previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                                // 设置自动曝光模式
                                previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                                        CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                                // 开始显示相机预览
                                previewRequest = previewRequestBuilder.build();
                                try {
                                    // 设置预览时连续捕获图像数据
                                    captureSession.setRepeatingRequest(previewRequest, null, null);  // ④
                                } catch (CameraAccessException e) {
                                    e.printStackTrace();
                                }
                            }
    
                            @Override
                            public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                                Toast.makeText(MainActivity.this, "配置失败!",
                                        Toast.LENGTH_SHORT).show();
                            }
                        }, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    
        private void setUpCameraOutputs(int width, int height) {
            CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
            try {
                // 获取指定摄像头的特性
                CameraCharacteristics characteristics = manager.getCameraCharacteristics(mCameraId);
                // 获取摄像头支持的配置属性
                StreamConfigurationMap map = characteristics.get(CameraCharacteristics.
                        SCALER_STREAM_CONFIGURATION_MAP);
                // 获取摄像头支持的最大尺寸
                Size largest = Collections.max(
                        Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
                        new CompareSizesByArea());
                // 创建一个ImageReader对象,用于获取摄像头的图像数据
                imageReader = ImageReader.newInstance(largest.getWidth(),
                        largest.getHeight(), ImageFormat.JPEG, 2);
                imageReader.setOnImageAvailableListener(reader -> {
                    // 当照片数据可用时激发该方法
                    // 获取捕获的照片数据
                    Image image = reader.acquireNextImage();
                    ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                    byte[] bytes = new byte[buffer.remaining()];
                    // 使用IO流将照片写入指定文件
                    File file = new File(getExternalFilesDir(null), "pic.jpg");
                    buffer.get(bytes);
                    try (
                            FileOutputStream output = new FileOutputStream(file)) {
                        output.write(bytes);
                        Toast.makeText(MainActivity.this, "保存: "
                                + file, Toast.LENGTH_SHORT).show();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        image.close();
                    }
                }, null);
                // 获取最佳的预览尺寸
                previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
                        width, height, largest);
                // 根据选中的预览尺寸来调整预览组件(TextureView的)的长宽比
                int orientation = getResources().getConfiguration().orientation;
                if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
                    textureView.setAspectRatio(previewSize.getWidth(), previewSize.getHeight());
                } else {
                    textureView.setAspectRatio(previewSize.getHeight(), previewSize.getWidth());
                }
            } catch (CameraAccessException e) {
                e.printStackTrace();
            } catch (NullPointerException e) {
                System.out.println("出现错误。");
            }
        }
    
        private static Size chooseOptimalSize(Size[] choices
                , int width, int height, Size aspectRatio) {
            // 收集摄像头支持的打过预览Surface的分辨率
            List<Size> bigEnough = new ArrayList<>();
            int w = aspectRatio.getWidth();
            int h = aspectRatio.getHeight();
            for (Size option : choices) {
                if (option.getHeight() == option.getWidth() * h / w &&
                        option.getWidth() >= width && option.getHeight() >= height) {
                    bigEnough.add(option);
                }
            }
            // 如果找到多个预览尺寸,获取其中面积最小的。
            if (bigEnough.size() > 0) {
                return Collections.min(bigEnough, new CompareSizesByArea());
            } else {
                System.out.println("找不到合适的预览尺寸!!!");
                return choices[0];
            }
        }
    
        // 为Size定义一个比较器Comparator
        static class CompareSizesByArea implements Comparator<Size> {
            @Override
            public int compare(Size lhs, Size rhs) {
                // 强转为long保证不会发生溢出
                return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
                        (long) rhs.getWidth() * rhs.getHeight());
            }
        }
    }
    

    AutoFitTextureView

    public class AutoFitTextureView extends TextureView {
        private int mRatioWidth = 0;
        private int mRatioHeight = 0;
    
        public AutoFitTextureView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        void setAspectRatio(int width, int height) {
            mRatioWidth = width;
            mRatioHeight = height;
            requestLayout();
        }
    
        @Override
        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int width = MeasureSpec.getSize(widthMeasureSpec);
            int height = MeasureSpec.getSize(heightMeasureSpec);
            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);
                }
            }
        }
    }
    

    layout/activity_main.xml

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/root"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <com.example.govideodemo.AutoFitTextureView
            android:id="@+id/texture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:ignore="MissingClass" />
    
        <ImageButton
            android:id="@+id/capture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end|bottom"
            android:src="@drawable/capture" />
    </FrameLayout>
    
        <!-- 授予该程序使用摄像头的权限 -->
        <uses-permission android:name="android.permission.CAMERA" />
    

    摘抄至《疯狂Android讲义(第4版)》

    相关文章

      网友评论

          本文标题:Android Camera v2 拍照

          本文链接:https://www.haomeiwen.com/subject/snvlrrtx.html