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

此文章我会用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 改天补上临时有事

猜你喜欢

转载自blog.csdn.net/I_am_a_loser/article/details/126891042