Camera2 和CameraX 从入门到精通 java实现

本文将详细讲解如何使用Camera2和CameraX库在Android应用中实现相机预览、拍照及录制视频。涵盖Camera2的相机方向、层级架构、API流程,以及CameraX的简洁API介绍。并提供了基础的代码实现。

此文章我会用Camera2 和CameraX 分别实现预览拍照录制视频

目录

此文章我会用Camera2 和CameraX 分别实现预览拍照录制视频

Camera2

APP实现 Camera2 需要提前声明

相机方向

Camera层级架构

API2流程

废话不多说 实战开始

CameraX

CameraX api

CameraX demo


Camera2

APP实现 Camera2 需要提前声明

相机方向

相机相对于手机屏幕默认方向不一致:

        前置摄像头(指向与显示屏方向相同的摄像头)的传感器相对于手机旋转 270 度(顺时针),以符合 Android 兼容性定义

手机的传感器是顺时针旋转的 左横屏就是90度 

 手机的传感器是顺时针旋转的 右横屏就是270度或者-90度 

Camera层级架构

Camera 只是Android的一部分  所以框架层级整体跟Android一样为

Applications 应用层            -----------------------------对应camera APP

Framework层                      -----------------------------对应Java Framework

Libraries 系统运行库层       -----------------------------对应Native Framework (CameraService)

Hardwre Abstraction layer HAL硬件抽象层-----------------------------对应Camera Provider

Linux Kernel 内核层           -----------------------------对应Camera Driver

Camera根据Android 架构从上至下可分为

    1)Applications: 最上层的应用,编译后生成Camera  APK

    2)Application Framework: 主要为Applications提供API;

    3)JNI: 使Application FrameworkLibraries可交互;

    4)Libraries: 包括Camera FrameworkCamera Service(camera servicecamera client);

    5)HAL: 硬件抽象层用来链接driver Camera Service;

    6)Kernel: image sensor driver的实作.

API2流程

 解释下来 其实就是CameraManager调用openCamera 下发open指令到底层,然后在CameraDevice.StateCallbackonOpened 中 拿到底层返回来的CameraDevice 通过CameraDevice去创建Session(createCaptureSession) 创建Session是需要添加对应的surface 预览的surface 有TextureView SurfaceView 拍照对应的Surface 为ImageReader 然后在Session的状态回调的onConfigured里(CameraCaptureSession.StateCallback)拿到CameraCaptureSession对象  通过CameraCaptureSession 可以申请预览 setRepeatingRequest 或者是 拍照 Capture 录制视频 需要配置 MediaRecorder 

废话不多说 实战开始

首先利用AndroidStudio 创建工程

 

Next

 Finish

打开布局文件

 一步步实现 就慢慢来,activity_main.xml布局文件代码

<?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"
    android:background="#000000"
    >

    <TextureView
        android:id="@+id/previewSurfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageButton
        android:id="@+id/takePictureButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="70dp"
        android:background="@drawable/shape_white_ring"
        android:src="@drawable/shape_take_photo" />

</RelativeLayout>

看到我放了两个控件一个TextureView 用来预览的控件 SurfaceView 等等也可以 还有一个拍照按钮 ImageButton  

首先我们实现预览 预览是一切的基础

package com.example.camera2demo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageButton;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    //预览画面控件
    private TextureView mTextureView;
    //拍照按钮
    private ImageButton mTakePictureButton;
    //日志的tag
    private final String TAG = "Camera2Demo" ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化预览控件
        mTextureView = findViewById(R.id.previewSurfaceView);
        //初始化拍照按钮
        mTakePictureButton = findViewById(R.id.takePictureButton);

    }

    @Override
    protected void onStart() {
        super.onStart();
        //设置拍照按钮监听
        mTakePictureButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.takePictureButton:
                takePicture();
                break;
        }
    }

    private void takePicture() {
        Log.d(TAG,"-----takePicture");
    }

}

可以看到 就是在onCreate 里去 初始化 预览控件和拍照按钮 并在 onStart 里去设置按钮 监听

在点击时间里 写了空函数 takePicture 基本框架已经实现 

要实现预览 我们是需要 申请Camera权限的 所以接下来我们先申请权限

Android 6.0 之前只需要在 AndroidManifest.xml 中加入 就OK  6.0 之后需要加入并且动态申请权限

<uses-permission android:name="android.permission.CAMERA" />

 代码 块如下

package com.example.camera2demo;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageButton;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    //预览画面控件
    private TextureView mTextureView;
    //拍照按钮
    private ImageButton mTakePictureButton;
    //日志的tag
    private final String TAG = "Camera2Demo";
    //权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式
    private final String[] REQUIRED_PERMISSIONS = new String[]{
            "android.permission.CAMERA",
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化预览控件
        mTextureView = findViewById(R.id.previewSurfaceView);
        //初始化拍照按钮
        mTakePictureButton = findViewById(R.id.takePictureButton);

    }

    @Override
    protected void onResume() {
        super.onResume();
        //检查权限申请权限
        if (allPermissionsGranted()) {
            Log.d(TAG, "权限已授予");
        } else {
            Log.d(TAG, "申请权限");
            ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        //设置拍照按钮监听
        mTakePictureButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.takePictureButton:
                takePicture();
                break;
        }
    }

    private boolean allPermissionsGranted() {
        Log.d(TAG, "----- 检查权限");
        for (String permission : REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    //权限回调 申请完权限之后 返回的结果在这里接收
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //这个1 是申请权限时的第三个参数
        if (requestCode == 1) {
            if (allPermissionsGranted()) {
                openCamera();
            } else {
                ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
            }
        }
    }

    private void openCamera() {
        Log.d(TAG,"----------openCamera");
    }

    private void takePicture() {
        Log.d(TAG,"-----takePicture");
    }

}

在刚刚 的基础之上加了三个函数 一个检查权限的函数 一个权限回调函数 一个 openCamera的空函数 权限获取之后我们就正式进入openCamera

package com.example.camera2demo;

import static java.lang.String.valueOf;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.annotation.SuppressLint;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageButton;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    //预览画面控件
    private TextureView mTextureView;
    //拍照按钮
    private ImageButton mTakePictureButton;
    //日志的tag
    private final String TAG = "Camera2Demo";
    //权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式
    private final String[] REQUIRED_PERMISSIONS = new String[]{
            "android.permission.CAMERA",
    };
    //处理回调函数的线程
    private HandlerThread mCameraThread;
    private Handler mCameraHandler;
    
    //具体的相机设备
    private CameraDevice mCameraDevice;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化预览控件
        mTextureView = findViewById(R.id.previewSurfaceView);
        //初始化拍照按钮
        mTakePictureButton = findViewById(R.id.takePictureButton);

        //开启线程 用于处理回调函数 可开可不开
        startCameraThread();
    }

    @Override
    protected void onResume() {
        super.onResume();
        //检查权限申请权限
        if (allPermissionsGranted()) {
            Log.d(TAG, "权限已授予");
        } else {
            Log.d(TAG, "申请权限");
            ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        //设置拍照按钮监听
        mTakePictureButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.takePictureButton:
                takePicture();
                break;
        }
    }

    private boolean allPermissionsGranted() {
        Log.d(TAG, "----- 检查权限");
        for (String permission : REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    //权限回调 申请完权限之后 返回的结果在这里接收
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //这个1 是申请权限时的第三个参数
        if (requestCode == 1) {
            if (allPermissionsGranted()) {
                openCamera();
            } else {
                ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
            }
        }
    }

    //开启线程 用于处理回调函数 可开可不开
    private void startCameraThread() {
        mCameraThread = new HandlerThread("CameraThread");
        mCameraThread.start();
        mCameraHandler = new Handler(mCameraThread.getLooper());
    }
    
    @SuppressLint("MissingPermission")
    private void openCamera() {
        Log.d(TAG, "----------openCamera");
        // 1.获取CameraManager 需要注意的是这里需要强转
        CameraManager cameraManager = (CameraManager) MainActivity.this.getSystemService(CAMERA_SERVICE);
        // 2.通过CameraManager 获取相机ID
        try {
            String cameraId[] = cameraManager.getCameraIdList();
            for (String s : cameraId) {
                Log.d(TAG, "--------- cameraId = " + s);
            }
            // 3. 获取 0 后置的相机信息
            CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics("0");
            // 例如获取分辨率的信息 通过对应的KEY 获取对应信息
            StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
            for (Size size : sizes) {
                Log.d(TAG,"size = "+size);
            }
            // 我就不去把ID 以及分辨率去放到代码中去写那么写代码很庞大 我就遍历一次输入一个相机支持的分辨率就好了
            // 4. 如果我这么写 2 3 步骤可以省略 openCamera 需要三个参数 第一个为相机ID  第二个是CameraDevice.StateCallback回调
            // 第三个是处理回调的线程 这里需要检查权限
            cameraManager.openCamera(valueOf(0),cameraDeviceStateCallback,mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    
    CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraDevice = camera;
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {

        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {

        }
    };

    private void takePicture() {
        Log.d(TAG, "-----takePicture");
    }

}

这段代码 开了个线程来处理回调函数可以要可不要,如果选择在主线程执行就可以穿null,加入了 openCamera 的关键 步骤获取CameraManger 通过CameraManger 可以获取到相机ID 如下图

 还可以获取相机的特征信息 比如支持的分辨率 如下图

 最核心的是可以下发openCamera 指令 

然后在回调里拿到 CameraDevice   拿到CameraDevice 就可以创建Session 接下来是创建Session 建立预览

package com.example.camera2demo;

import static android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_AUTO;
import static java.lang.String.valueOf;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.annotation.SuppressLint;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
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.params.StreamConfigurationMap;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.widget.ImageButton;

import java.util.Arrays;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    //预览画面控件
    private TextureView mTextureView;
    //拍照按钮
    private ImageButton mTakePictureButton;
    //日志的tag
    private final String TAG = "Camera2Demo";
    //权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式
    private final String[] REQUIRED_PERMISSIONS = new String[]{
            "android.permission.CAMERA",
    };
    //处理回调函数的线程
    private HandlerThread mCameraThread;
    private Handler mCameraHandler;

    //具体的相机设备
    private CameraDevice mCameraDevice;

    //创建Session的构建者
    private CaptureRequest.Builder mCaptureRequestBuilder;
    //CameraCaptureSession是创建预览拍照 录像请求的关键  可以理解成管道 通过这个管道下发不同的参数请求 得到不同的效果
    private CameraCaptureSession mCameraCaptureSession;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化预览控件
        mTextureView = findViewById(R.id.previewSurfaceView);
        //设置预览控件的监听  防止surface 没有渲染好就打开相机了
        mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        //初始化拍照按钮
        mTakePictureButton = findViewById(R.id.takePictureButton);

        //开启线程 用于处理回调函数 可开可不开
        startCameraThread();
    }

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


    }

    @Override
    protected void onStart() {
        super.onStart();
        //设置拍照按钮监听
        mTakePictureButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.takePictureButton:
                takePicture();
                break;
        }
    }

    private boolean allPermissionsGranted() {
        Log.d(TAG, "----- 检查权限");
        for (String permission : REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    //权限回调 申请完权限之后 返回的结果在这里接收
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //这个1 是申请权限时的第三个参数
        if (requestCode == 1) {
            if (allPermissionsGranted()) {
                openCamera();
            } else {
                ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
            }
        }
    }

    //开启线程 用于处理回调函数 可开可不开
    private void startCameraThread() {
        mCameraThread = new HandlerThread("CameraThread");
        mCameraThread.start();
        mCameraHandler = new Handler(mCameraThread.getLooper());
    }

    @SuppressLint("MissingPermission")
    private void openCamera() {
        Log.d(TAG, "----------openCamera");
        // 1.获取CameraManager 需要注意的是这里需要强转
        CameraManager cameraManager = (CameraManager) MainActivity.this.getSystemService(CAMERA_SERVICE);
        // 2.通过CameraManager 获取相机ID
        try {
            String cameraId[] = cameraManager.getCameraIdList();
            for (String s : cameraId) {
                Log.d(TAG, "--------- cameraId = " + s);
            }
            // 3. 获取 0 后置的相机信息
            CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics("0");
            // 例如获取分辨率的信息 通过对应的KEY 获取对应信息
            StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
            for (Size size : sizes) {
                Log.d(TAG, "size = " + size);
            }
            // 我就不去把ID 以及分辨率去放到代码中去写那么写代码很庞大 我就遍历一次输入一个相机支持的分辨率就好了
            // 4. 如果我这么写 2 3 步骤可以省略 openCamera 需要三个参数 第一个为相机ID  第二个是CameraDevice.StateCallback回调
            // 第三个是处理回调的线程如果不开线程可以传null 即在主线程执行  这里需要检查权限
            cameraManager.openCamera(valueOf(0), cameraDeviceStateCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraDevice = camera;
            createPreView();
        }


        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {

        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {

        }
    };

    private void createPreView() {
        //创建预览 之前创建Session 创建Session 需要接收数据的Surface
        //下面就是获取Surface
        SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
        //设置SurfaceView的缓冲区分辨率  与手机用的分辨一致 就可以防止预览拉伸
        surfaceTexture.setDefaultBufferSize(4160, 1856);
        Surface previewSurface = new Surface(surfaceTexture);
        //如果是surfaceView 如下
//      SurfaceHolder surfaceHolder = mPreviewSurfaceView.getHolder();
//      surfaceHolder.setFixedSize(1920,1080);
//      surface = surfaceHolder.getSurface();
        try {
            //创建Session的构建者
            mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            //将Surface 传递下去
            mCaptureRequestBuilder.addTarget(previewSurface);
            //通过CameraMetadata 设置闪光灯 自动对焦等等 我这里是设置自动对焦
            mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            //创建Session
            mCameraDevice.createCaptureSession(Arrays.asList(previewSurface), cameraCaptureSessionStateCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    CameraCaptureSession.StateCallback cameraCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            mCameraCaptureSession = session;
            try {
                session.setRepeatingRequest(mCaptureRequestBuilder.build(), null, mCameraHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {

        }
    };

    //很多人写到这里觉得大功告成了  是 申请权限下来 openCamera 一点问题没有 但是 发现再次进入  崩溃了   还找不到原因 这里是因为 我们openCamera时Surface
    //还么渲染好 导致的解决办法有两个
    //1
    TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
            //检查权限申请权限
            if (allPermissionsGranted()) {
                Log.d(TAG, "权限已授予");
                openCamera();
            } else {
                Log.d(TAG, "申请权限");
                ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
            }
        }

        @Override
        public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {

        }

        @Override
        public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {

        }
    };

    //2.在onCreate里添加下面这段
//        mTextureView.post(new Runnable() {
//            @Override
//            public void run() {
//                if (allPermissionsGranted()) {
//                    Log.d(TAG,"权限ok");
//                    startCamera();
//                } else {
//                    Log.d(TAG,"申请权限");
//                    ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
//                }
//            }
//        });


    private void takePicture() {
        Log.d(TAG, "-----takePicture");
    }

}

至此简单的预览完成了

预览拍照代码

package com.example.camera2demo;

import static java.lang.String.valueOf;

import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
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.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    //预览画面控件
    private TextureView mTextureView;
    //拍照按钮
    private ImageButton mTakePictureButton;
    //日志的tag
    private final String TAG = "Camera2Demo";
    //权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式
    private final String[] REQUIRED_PERMISSIONS = new String[]{
            "android.permission.CAMERA",
            "android.permission.WRITE_EXTERNAL_STORAGE",
            "android.permission.READ_EXTERNAL_STORAGE",
    };
    //处理回调函数的线程
    private HandlerThread mCameraThread;
    private Handler mCameraHandler;

    //具体的相机设备
    private CameraDevice mCameraDevice;

    //创建Session的构建者
    private CaptureRequest.Builder mCaptureRequestBuilder;
    //CameraCaptureSession是创建预览拍照 录像请求的关键  可以理解成管道 通过这个管道下发不同的参数请求 得到不同的效果
    private CameraCaptureSession mCameraCaptureSession;

    private ImageReader mImageReader = null;

    protected ImageView mThumbnail;

    //用于处理拍照方向问题
    protected static final SparseIntArray ORIENTATION = new SparseIntArray();

    static {
        ORIENTATION.append(Surface.ROTATION_0, 90);
        //90  270 还没处理
        ORIENTATION.append(Surface.ROTATION_90, 0);
        ORIENTATION.append(Surface.ROTATION_180, 270);
        ORIENTATION.append(Surface.ROTATION_270, 180);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化预览控件
        mTextureView = findViewById(R.id.previewSurfaceView);
        //设置预览控件的监听  防止surface 没有渲染好就打开相机了
        mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        //初始化拍照按钮
        mTakePictureButton = findViewById(R.id.takePictureButton);

        mThumbnail = findViewById(R.id.thumbnail);
        mThumbnail.setOnClickListener(this);

        //开启线程 用于处理回调函数 可开可不开
        startCameraThread();

        //去掉导航栏
        getSupportActionBar().hide();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            //透明状态栏
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            //透明导航栏
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
        }
    }

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


    }

    @Override
    protected void onStart() {
        super.onStart();
        //设置拍照按钮监听
        mTakePictureButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.takePictureButton:
                takePicture();
                break;
            case R.id.thumbnail:
                gotoGallery();
                break;
        }
    }

    private boolean allPermissionsGranted() {
        Log.d(TAG, "----- 检查权限");
        for (String permission : REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    //权限回调 申请完权限之后 返回的结果在这里接收
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //这个1 是申请权限时的第三个参数
        if (requestCode == 1) {
            if (allPermissionsGranted()) {
                openCamera();
            } else {
                ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
            }
        }
    }

    //开启线程 用于处理回调函数 可开可不开
    private void startCameraThread() {
        mCameraThread = new HandlerThread("CameraThread");
        mCameraThread.start();
        mCameraHandler = new Handler(mCameraThread.getLooper());
    }

    @SuppressLint("MissingPermission")
    private void openCamera() {
        Log.d(TAG, "----------openCamera");
        // 1.获取CameraManager 需要注意的是这里需要强转
        CameraManager cameraManager = (CameraManager) MainActivity.this.getSystemService(CAMERA_SERVICE);
        // 2.通过CameraManager 获取相机ID
        try {
            String cameraId[] = cameraManager.getCameraIdList();
            for (String s : cameraId) {
                Log.d(TAG, "--------- cameraId = " + s);
            }
            // 3. 获取 0 后置的相机信息
            CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics("0");
            // 例如获取分辨率的信息 通过对应的KEY 获取对应信息
            StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
            for (Size size : sizes) {
                Log.d(TAG, "size = " + size);
            }
            // 我就不去把ID 以及分辨率去放到代码中去写那么写代码很庞大 我就遍历一次输入一个相机支持的分辨率就好了
            // 4. 如果我这么写 2 3 步骤可以省略 openCamera 需要三个参数 第一个为相机ID  第二个是CameraDevice.StateCallback回调
            // 第三个是处理回调的线程如果不开线程可以传null 即在主线程执行  这里需要检查权限
            cameraManager.openCamera(valueOf(0), cameraDeviceStateCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraDevice = camera;
            createPreView();
        }


        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {

        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {

        }
    };

    private void createPreView() {
        initImageReader(); //拍照要初始化
        //创建预览 之前创建Session 创建Session 需要接收数据的Surface
        //下面就是获取Surface
        SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
        //设置SurfaceView的缓冲区分辨率  与手机用的分辨一致 就可以防止预览拉伸
        surfaceTexture.setDefaultBufferSize(1920, 1080);
        Surface previewSurface = new Surface(surfaceTexture);
        //如果是surfaceView 如下
//      SurfaceHolder surfaceHolder = mPreviewSurfaceView.getHolder();
//      surfaceHolder.setFixedSize(1920,1080);
//      surface = surfaceHolder.getSurface();
        try {
            //创建Session的构建者
            mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            //将Surface 传递下去
            mCaptureRequestBuilder.addTarget(previewSurface);
            //通过CameraMetadata 设置闪光灯 自动对焦等等 我这里是设置自动对焦
            mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            //创建Session
            mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface()), cameraCaptureSessionStateCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    CameraCaptureSession.StateCallback cameraCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            mCameraCaptureSession = session;
            try {
                session.setRepeatingRequest(mCaptureRequestBuilder.build(), null, mCameraHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {

        }
    };

    //很多人写到这里觉得大功告成了  是 申请权限下来 openCamera 一点问题没有 但是 发现再次进入  崩溃了   还找不到原因 这里是因为 我们openCamera时Surface
    //还么渲染好 导致的解决办法有两个
    //1
    TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
            //检查权限申请权限
            if (allPermissionsGranted()) {
                Log.d(TAG, "权限已授予");
                openCamera();
            } else {
                Log.d(TAG, "申请权限");
                ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
            }
            showThumbnail();
        }

        @Override
        public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {

        }

        @Override
        public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {

        }
    };

    //2.在onCreate里添加下面这段
//        mTextureView.post(new Runnable() {
//            @Override
//            public void run() {
//                if (allPermissionsGranted()) {
//                    Log.d(TAG,"权限ok");
//                    startCamera();
//                } else {
//                    Log.d(TAG,"申请权限");
//                    ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
//                }
//            }
//        });


    //拍照 开始拍照之前得配置对应的Surfac 就是ImageReader
    protected void initImageReader() {
        Log.d("djh", "--------------------initImageReader");
        //四个参数分别是照片的分辨率 格式   最多获取几帧
        mImageReader = ImageReader.newInstance(1920, 1080, ImageFormat.JPEG, 1);
        //设置ImageReader监听,当有图像流数据可用时会回调onImageAvailable
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader imageReader) {
                Toast.makeText(MainActivity.this, "图片已保存", Toast.LENGTH_SHORT).show();
                //获得Image 数据
                Image image = imageReader.acquireNextImage();
                //可以开启线程保存图片
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //字节缓冲
                        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                        byte[] data = new byte[buffer.remaining()];
                        buffer.get(data);
                        String path = Environment.getExternalStorageDirectory() + "/DCIM/camera/myPicture"
                                + System.currentTimeMillis() + ".jpg";
                        File imageFile = new File(path);
                        FileOutputStream fos = null;
                        try {
                            fos = new FileOutputStream(imageFile);
                            fos.write(data, 0, data.length);
                        } catch (IOException e) {
                            e.printStackTrace();
                        } finally {
                            if (fos != null) {
                                try {
                                    fos.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                            broadcast();
                            image.close(); // 必须关闭 不然拍第二章会报错
                            showThumbnail();
                        }
                    }
                }).start();
            }
        }, null);
    }

    private void takePicture() {
        Log.d(TAG, "-----takePicture");
        try {
            //拍照跟预览的区别就是Builder不一样回调不一样仅此而已
            CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            captureBuilder.addTarget(mImageReader.getSurface());
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
            mCameraCaptureSession.stopRepeating();
            mCameraCaptureSession.capture(captureBuilder.build(), captureCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
            super.onCaptureStarted(session, request, timestamp, frameNumber);
            try {
                session.setRepeatingRequest(mCaptureRequestBuilder.build(), null, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    };

    // 通知广播刷新相册
    protected void broadcast() {
        Log.d("djh", "--------------------broadcast");
        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/";
        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        Uri uri = Uri.fromFile(new File(path));
        intent.setData(uri);
        MainActivity.this.sendBroadcast(intent);
    }

    //预览拍照完成显示缩略图
    protected void showThumbnail() {
        ArrayList<String> imageList = getImageFilePath();
        String path = imageList.get(imageList.size() - 1);
        if (path.contains("jpg")) {
            Bitmap bitmap = (Bitmap) BitmapFactory.decodeFile(path);
            //又是bitmap 为空  忘记给mImageView finViewById
            mThumbnail.setImageBitmap(bitmap);
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            mThumbnail.setRotation(ORIENTATION.get(rotation));
        } else {
            MediaMetadataRetriever retriever = new MediaMetadataRetriever();
            retriever.setDataSource(path);
            //取第一帧
            Bitmap bitmap = retriever.getFrameAtTime(1);
            mThumbnail.setImageBitmap(bitmap);
        }
    }

    //遍历系统相册
    protected ArrayList<String> getImageFilePath() {
        ArrayList<String> imageList = new ArrayList<>();
        File file = new File(Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera");
        File[] dirEpub = file.listFiles();
        if (dirEpub.length != 0) {
            for (int i = 0; i < dirEpub.length; i++) {
                String fileName = dirEpub[i].toString();
                imageList.add(fileName);
                Log.i("File", "File name = " + fileName);
            }
        }
        return imageList;
    }

    //跳转相册 (画廊)
    protected void gotoGallery() {
        ArrayList<String> temp = getImageFilePath();
        String lastPath = temp.get(temp.size() - 1);
        Uri uri = getMediaUriFromPath(this, lastPath);
        Intent intent = new Intent("com.android.camera.action.REVIEW", uri);
        intent.setData(uri);
        startActivity(intent);
    }

    @SuppressLint("Range")
    public Uri getMediaUriFromPath(Context context, String path) {
        Uri uri = null;
        if (path.contains("jpg")) {
            Uri picUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            Cursor cursor = context.getContentResolver().query(picUri,
                    null,
                    MediaStore.Images.Media.DISPLAY_NAME + "= ?",
                    new String[]{path.substring(path.lastIndexOf("/") + 1)},
                    null);
            if (cursor.moveToFirst()) {
                uri = ContentUris.withAppendedId(picUri,
                        cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
            }
            cursor.close();
        } else if (path.contains("mp4")) {
            Uri mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            Cursor cursor = context.getContentResolver().query(mediaUri,
                    null,
                    MediaStore.Video.Media.DISPLAY_NAME + "= ?",
                    new String[]{path.substring(path.lastIndexOf("/") + 1)},
                    null);
            if (cursor.moveToFirst()) {
                uri = ContentUris.withAppendedId(mediaUri,
                        cursor.getLong(cursor.getColumnIndex(MediaStore.Video.Media._ID)));
            }
            cursor.close();
        }
        return uri;
    }

}

 activity_main.xml

<?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"
    android:background="#000000"
    >

    <TextureView
        android:id="@+id/previewSurfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageButton
        android:id="@+id/takePictureButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="70dp"
        android:background="@drawable/shape_white_ring"
        android:src="@drawable/shape_take_photo" />

    <ImageView
        android:id="@+id/thumbnail"
        android:layout_width="45dp"
        android:layout_height="55dp"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="50dp"
        android:layout_marginBottom="70dp" />

    <ImageButton
        android:id="@+id/changeCamera"
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="50dp"
        android:layout_marginBottom="77dp"
        android:background="@drawable/camera_quick_switch" />

</RelativeLayout>

再下来就是录像了

 录像跟拍照很相似 只是 CaptureRequest.Builder对象不一样 需要配置一个MediaRecorder

预览拍照录制视频的完整代码

package com.example.camera2demo;

import static java.lang.String.valueOf;

import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
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.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaMetadataRetriever;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    //预览画面控件
    private TextureView mTextureView;
    //拍照按钮
    private ImageButton mTakePictureButton;
    //日志的tag
    private final String TAG = "Camera2Demo";
    //权限字符串数组 因为还需要其他 便于添加权限 选择字符串数组的形式
    private final String[] REQUIRED_PERMISSIONS = new String[]{
            "android.permission.CAMERA",
            "android.permission.WRITE_EXTERNAL_STORAGE",
            "android.permission.READ_EXTERNAL_STORAGE",
            "android.permission.RECORD_AUDIO",
    };
    //处理回调函数的线程
    private HandlerThread mCameraThread;
    private Handler mCameraHandler;

    //具体的相机设备
    private CameraDevice mCameraDevice;

    //创建Session的构建者
    private CaptureRequest.Builder mPreviewBuilder;
    //CameraCaptureSession是创建预览拍照 录像请求的关键  可以理解成管道 通过这个管道下发不同的参数请求 得到不同的效果
    private CameraCaptureSession mCameraCaptureSession;

    private ImageReader mImageReader = null;

    protected ImageView mThumbnail;

    //用于处理拍照方向问题
    protected static final SparseIntArray ORIENTATION = new SparseIntArray();

    static {
        ORIENTATION.append(Surface.ROTATION_0, 90);
        //90  270 还没处理
        ORIENTATION.append(Surface.ROTATION_90, 0);
        ORIENTATION.append(Surface.ROTATION_180, 270);
        ORIENTATION.append(Surface.ROTATION_270, 180);
    }

    private Button mPhotoMode;
    private Button mVideoMode;

    //拍照按钮
    private ImageButton mCapTureVideoButton;

    private MediaRecorder mMediaRecorder;
    private boolean bStop = false;

    private ImageButton mChangeCamera;
    private String mCameraId = "0";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化预览控件
        mTextureView = findViewById(R.id.previewSurfaceView);
        //设置预览控件的监听  防止surface 没有渲染好就打开相机了
        mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        //初始化拍照按钮
        mTakePictureButton = findViewById(R.id.takePictureButton);

        mThumbnail = findViewById(R.id.thumbnail);
        mThumbnail.setOnClickListener(this);

        //开启线程 用于处理回调函数 可开可不开
        startCameraThread();

        //去掉导航栏
        getSupportActionBar().hide();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            //透明状态栏
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
            //透明导航栏
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
        }

        mPhotoMode = findViewById(R.id.photoMode);
        mPhotoMode.setOnClickListener(this);
        mVideoMode = findViewById(R.id.videoMode);
        mVideoMode.setOnClickListener(this);
        mCapTureVideoButton = findViewById(R.id.captureVideoButton);
        mCapTureVideoButton.setOnClickListener(this);

        mChangeCamera = findViewById(R.id.changeCamera);
        mChangeCamera.setOnClickListener(this);
    }

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


    }

    @Override
    protected void onStart() {
        super.onStart();
        //设置拍照按钮监听
        mTakePictureButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.takePictureButton:
                takePicture();
                break;
            case R.id.thumbnail:
                gotoGallery();
                break;
            case R.id.photoMode:
                mPhotoMode.setVisibility(View.GONE);
                mVideoMode.setVisibility(View.VISIBLE);

                mTakePictureButton.setVisibility(View.GONE);
                mCapTureVideoButton.setVisibility(View.VISIBLE);
                break;
            case R.id.videoMode:
                mVideoMode.setVisibility(View.GONE);
                mPhotoMode.setVisibility(View.VISIBLE);

                mTakePictureButton.setVisibility(View.VISIBLE);
                mCapTureVideoButton.setVisibility(View.GONE);
                break;
            case R.id.captureVideoButton:
                if (!bStop) {
                    bStop = true;
                    captureVideo();
                } else {
                    bStop = false;
                    stopVideo();
                }
                break;
            case R.id.changeCamera:
                changeCameraId();
                break;
        }
    }


    private boolean allPermissionsGranted() {
        Log.d(TAG, "----- 检查权限");
        for (String permission : REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    //权限回调 申请完权限之后 返回的结果在这里接收
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //这个1 是申请权限时的第三个参数
        if (requestCode == 1) {
            if (allPermissionsGranted()) {
                openCamera();
            } else {
                ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
            }
        }
    }

    //开启线程 用于处理回调函数 可开可不开
    private void startCameraThread() {
        mCameraThread = new HandlerThread("CameraThread");
        mCameraThread.start();
        mCameraHandler = new Handler(mCameraThread.getLooper());
    }

    @SuppressLint("MissingPermission")
    private void openCamera() {
        Log.d(TAG, "----------openCamera");
        // 1.获取CameraManager 需要注意的是这里需要强转
        CameraManager cameraManager = (CameraManager) MainActivity.this.getSystemService(CAMERA_SERVICE);
        // 2.通过CameraManager 获取相机ID
        try {
            String cameraId[] = cameraManager.getCameraIdList();
            for (String s : cameraId) {
                Log.d(TAG, "--------- cameraId = " + s);
            }
            // 3. 获取 0 后置的相机信息
            CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics("0");
            // 例如获取分辨率的信息 通过对应的KEY 获取对应信息
            StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
            for (Size size : sizes) {
                Log.d(TAG, "size = " + size);
            }
            // 我就不去把ID 以及分辨率去放到代码中去写那么写代码很庞大 我就遍历一次输入一个相机支持的分辨率就好了
            // 4. 如果我这么写 2 3 步骤可以省略 openCamera 需要三个参数 第一个为相机ID  第二个是CameraDevice.StateCallback回调
            // 第三个是处理回调的线程如果不开线程可以传null 即在主线程执行  这里需要检查权限
            cameraManager.openCamera(mCameraId, cameraDeviceStateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    CameraDevice.StateCallback cameraDeviceStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraDevice = camera;
            createPreView();
        }


        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {

        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {

        }
    };

    private void createPreView() {
        initImageReader(); //拍照要初始化
        configMediaRecorder();//录制视频需要初始化
        //创建预览 之前创建Session 创建Session 需要接收数据的Surface
        //下面就是获取Surface
        SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
        //设置SurfaceView的缓冲区分辨率  与手机用的分辨一致 就可以防止预览拉伸
        surfaceTexture.setDefaultBufferSize(1920, 1080);
        Surface previewSurface = new Surface(surfaceTexture);
        //如果是surfaceView 如下
//      SurfaceHolder surfaceHolder = mPreviewSurfaceView.getHolder();
//      surfaceHolder.setFixedSize(1920,1080);
//      surface = surfaceHolder.getSurface();
        try {
            //创建Session的构建者
            mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            //将Surface 传递下去
            mPreviewBuilder.addTarget(previewSurface);
            mPreviewBuilder.addTarget(mMediaRecorder.getSurface());
            //通过CameraMetadata 设置闪光灯 自动对焦等等 我这里是设置自动对焦
            mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            //创建Session
            mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, mImageReader.getSurface(), mMediaRecorder.getSurface()), cameraCaptureSessionStateCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    CameraCaptureSession.StateCallback cameraCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            mCameraCaptureSession = session;
            try {
                session.setRepeatingRequest(mPreviewBuilder.build(), null, mCameraHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {

        }
    };

    //很多人写到这里觉得大功告成了  是 申请权限下来 openCamera 一点问题没有 但是 发现再次进入  崩溃了   还找不到原因 这里是因为 我们openCamera时Surface
    //还么渲染好 导致的解决办法有两个
    //1
    TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
            //检查权限申请权限
            if (allPermissionsGranted()) {
                Log.d(TAG, "权限已授予");
                openCamera();
            } else {
                Log.d(TAG, "申请权限");
                ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, 1);
            }
            //showThumbnail();
        }

        @Override
        public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {

        }

        @Override
        public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {

        }
    };

    //2.在onCreate里添加下面这段
//        mTextureView.post(new Runnable() {
//            @Override
//            public void run() {
//                if (allPermissionsGranted()) {
//                    Log.d(TAG,"权限ok");
//                    startCamera();
//                } else {
//                    Log.d(TAG,"申请权限");
//                    ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
//                }
//            }
//        });


    //拍照 开始拍照之前得配置对应的Surfac 就是ImageReader
    protected void initImageReader() {
        Log.d("djh", "--------------------initImageReader");
        //四个参数分别是照片的分辨率 格式   最多获取几帧
        mImageReader = ImageReader.newInstance(1920, 1080, ImageFormat.JPEG, 1);
        //设置ImageReader监听,当有图像流数据可用时会回调onImageAvailable
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader imageReader) {
                Toast.makeText(MainActivity.this, "图片已保存", Toast.LENGTH_SHORT).show();
                //获得Image 数据
                Image image = imageReader.acquireNextImage();
                //可以开启线程保存图片
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //字节缓冲
                        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                        byte[] data = new byte[buffer.remaining()];
                        buffer.get(data);
                        String path = Environment.getExternalStorageDirectory() + "/DCIM/camera/myPicture"
                                + System.currentTimeMillis() + ".jpg";
                        File imageFile = new File(path);
                        FileOutputStream fos = null;
                        try {
                            fos = new FileOutputStream(imageFile);
                            fos.write(data, 0, data.length);
                        } catch (IOException e) {
                            e.printStackTrace();
                        } finally {
                            if (fos != null) {
                                try {
                                    fos.close();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                            broadcast();
                            image.close(); // 必须关闭 不然拍第二章会报错
                            //showThumbnail();
                        }
                    }
                }).start();
            }
        }, null);
    }

    private void takePicture() {
        Log.d(TAG, "-----takePicture");
        try {
            //拍照跟预览的区别就是Builder不一样回调不一样仅此而已
            CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            captureBuilder.addTarget(mImageReader.getSurface());
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            if (mCameraId.equals("1")) {
                captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation) + 180);
            } else {
                captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
            }
            mCameraCaptureSession.stopRepeating();
            mCameraCaptureSession.capture(captureBuilder.build(), captureCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
            super.onCaptureStarted(session, request, timestamp, frameNumber);
            try {
                session.setRepeatingRequest(mPreviewBuilder.build(), null, mCameraHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    };

    // 通知广播刷新相册
    protected void broadcast() {
        Log.d("djh", "--------------------broadcast");
        String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/";
        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        Uri uri = Uri.fromFile(new File(path));
        intent.setData(uri);
        MainActivity.this.sendBroadcast(intent);
    }

    //预览拍照完成显示缩略图
    protected void showThumbnail() {
        ArrayList<String> imageList = getImageFilePath();
        String path = imageList.get(imageList.size() - 1);
        if (path.contains("jpg")) {
            Bitmap bitmap = BitmapFactory.decodeFile(path);
            mThumbnail.setImageBitmap(bitmap);
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            mThumbnail.setRotation(ORIENTATION.get(rotation));
        } else if (path.contains("mp4")) {
            MediaMetadataRetriever retriever = new MediaMetadataRetriever();
            retriever.setDataSource(path);
            //获取第1帧
            Bitmap bitmap = retriever.getFrameAtTime(1);
            mThumbnail.setImageBitmap(bitmap);
        }
    }

    //遍历系统相册
    protected ArrayList<String> getImageFilePath() {
        ArrayList<String> imageList = new ArrayList<>();
        File file = new File(Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera");
        File[] dirEpub = file.listFiles();
        if (dirEpub.length != 0) {
            for (int i = 0; i < dirEpub.length; i++) {
                String fileName = dirEpub[i].toString();
                imageList.add(fileName);
                Log.i("File", "File name = " + fileName);
            }
        }
        return imageList;
    }

    //跳转相册 (画廊)
    protected void gotoGallery() {
        ArrayList<String> temp = getImageFilePath();
        String lastPath = temp.get(temp.size() - 1);
        Uri uri = getMediaUriFromPath(this, lastPath);
        Intent intent = new Intent("com.android.camera.action.REVIEW", uri);
        intent.setData(uri);
        startActivity(intent);
    }

    @SuppressLint("Range")
    public Uri getMediaUriFromPath(Context context, String path) {
        Uri uri = null;
        if (path.contains("jpg")) {
            Uri picUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            Cursor cursor = context.getContentResolver().query(picUri,
                    null,
                    MediaStore.Images.Media.DISPLAY_NAME + "= ?",
                    new String[]{path.substring(path.lastIndexOf("/") + 1)},
                    null);
            if (cursor.moveToFirst()) {
                uri = ContentUris.withAppendedId(picUri,
                        cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
            }
            cursor.close();
        } else if (path.contains("mp4")) {
            Uri mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            Cursor cursor = context.getContentResolver().query(mediaUri,
                    null,
                    MediaStore.Video.Media.DISPLAY_NAME + "= ?",
                    new String[]{path.substring(path.lastIndexOf("/") + 1)},
                    null);
            if (cursor.moveToFirst()) {
                uri = ContentUris.withAppendedId(mediaUri,
                        cursor.getLong(cursor.getColumnIndex(MediaStore.Video.Media._ID)));
            }
            cursor.close();
        }
        return uri;
    }

    /**
     * 配置录制视频相关数据
     */
    private void configMediaRecorder() {

        File file = new File(Environment.getExternalStorageDirectory() +
                "/DCIM/camera/myMp4" + System.currentTimeMillis() + ".mp4");
        if (file.exists()) {
            file.delete();
        }
        if (mMediaRecorder == null) {
            mMediaRecorder = new MediaRecorder();
            //如果是前置
            if (mCameraId.equals("1")) {
                mMediaRecorder.setOrientationHint(270);
            } else {
                mMediaRecorder.setOrientationHint(90);
            }
            //设置音频来源
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            //设置视频来源
            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
            //设置输出格式
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
            //设置音频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择AAC
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            //设置视频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择H264
            mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
            //设置比特率 一般是 1*分辨率 到 10*分辨率 之间波动。比特率越大视频越清晰但是视频文件也越大。
            mMediaRecorder.setVideoEncodingBitRate(8 * 1080 * 1920);
            //设置帧数 选择 30即可, 过大帧数也会让视频文件更大当然也会更流畅,但是没有多少实际提升。人眼极限也就30帧了。
            mMediaRecorder.setVideoFrameRate(30);
            //Size size = getMatchingSize();
            mMediaRecorder.setVideoSize(1920, 1080);
            Surface surface = new Surface(mTextureView.getSurfaceTexture());
            mMediaRecorder.setPreviewDisplay(surface);
            mMediaRecorder.setOutputFile(file.getAbsolutePath());
            try {
                mMediaRecorder.prepare();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void captureVideo() {
        Log.d(TAG, "---------  captureVideo");
        mMediaRecorder.start();
    }

    private void stopVideo() {
        Log.d(TAG, "---------  stopVideo");
        mMediaRecorder.stop();
        broadcast();
        createPreView();
    }

    private void changeCameraId() {
        Log.d(TAG, "changeCamera: success");
        if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK))) {
            Toast.makeText(this, "前置转后置", Toast.LENGTH_SHORT).show();
            mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
        } else {
            Toast.makeText(this, "后置转前置", Toast.LENGTH_SHORT).show();
            mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_BACK);
        }
        mCameraDevice.close();
        openCamera();

    }

}

xml

<?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"
    android:background="#000000">

    <TextureView
        android:id="@+id/previewSurfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageButton
        android:id="@+id/takePictureButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="70dp"
        android:visibility="visible"
        android:background="@drawable/shape_white_ring"
        android:src="@drawable/shape_take_photo" />

    <ImageButton
        android:id="@+id/captureVideoButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alagnParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="70dp"
        android:visibility="gone"
        android:background="@drawable/shape_white_ring"
        android:src="@drawable/shape_take_video" />

    <ImageView
        android:id="@+id/thumbnail"
        android:layout_width="45dp"
        android:layout_height="55dp"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="50dp"
        android:layout_marginBottom="70dp" />

    <ImageButton
        android:id="@+id/changeCamera"
        android:layout_width="45dp"
        android:layout_height="45dp"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="50dp"
        android:layout_marginBottom="77dp"
        android:background="@drawable/camera_quick_switch" />

    <Button
        android:id="@+id/photoMode"
        android:layout_width="85dp"
        android:layout_height="40dp"
        android:text="Photo"
        android:layout_centerHorizontal="true"
        android:layout_alignParentBottom="true"
        android:visibility="visible"
        android:layout_marginBottom="160dp"/>

    <Button
        android:id="@+id/videoMode"
        android:layout_width="85dp"
        android:text="Video"
        android:layout_height="40dp"
        android:visibility="gone"
        android:layout_centerHorizontal="true"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="160dp"
        />

</RelativeLayout>

CameraX

用CameraX 时需要注意的是 build.gradle里面需要添加

//CameraX
    def camerax_version = "1.0.0-alpha02"
    implementation "androidx.camera:camera-core:${camerax_version}"
    implementation "androidx.camera:camera-camera2:${camerax_version}"

CameraX api

CameraX 可以理解成将Camera2的API 进行了封装  使用变得更加简洁

实现预览 获取Camera权限之后  通过PreviewConfig去设置分辨率,画面比例,CameraId等等

再 new 一个 Preview 对象时将PreviewConfig对象传递下去,再利用bindTolifecycle 把Preview 对象 传递下去就完成预览了,相比Camera2是不是要简单很多呢

拍照也很简单 只需要用ImageCaptureConfig 配置拍照信息 比例 CameraId 拍照方式等等 

再去new一个ImageCapture对象 将ImageCaptureConfig传递下去 再给 bindTolifecycle 多一个参数 这个参数传递ImageCapture对象

录像 VideoCaptureConfig

  • Quality.UHD,适用于 4K 超高清视频大小 (2160p)
  • Quality.FHD,适用于全高清视频大小 (1080p)
  • Quality.HD,适用于高清视频大小 (720p)
  • Quality.SD,适用于标清视频大小 (480p)

CameraX demo 改天补上临时有事

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值