Camera2 实现预览、拍照和录制视频并保存本地

Camera2 实现预览、拍照和录制视频并保存本地

1、创建主视图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">

    <androidx.viewpager.widget.ViewPager
        android:layout_gravity="center"
        android:id="@+id/change_page"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <!--    <androidx.viewpager.widget.PagerTitleStrip-->
        <!--        android:layout_gravity="top"-->
        <!--        android:id="@+id/page_title"-->
        <!--        android:layout_width="wrap_content"-->
        <!--        android:layout_height="wrap_content"-->
        <!--        >-->
        <!--    </androidx.viewpager.widget.PagerTitleStrip>-->


    </androidx.viewpager.widget.ViewPager>

</RelativeLayout>

2、创建一个Adapter适配器 MyPagerAdapter

package com.tsinglink.android.camera2videodemo;

import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;

import java.util.List;

public  class MyPagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> layoutList;
    public MyPagerAdapter(FragmentManager manager, List<Fragment> layoutList){
        super(manager);
        this.layoutList = layoutList;
    }
    @Override
    public int getCount() {
        // 页面数
        return layoutList.size();
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
        return layoutList.get(position);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        super.setPrimaryItem(container, position, object);
    }
}

3、分别创建TakePicturedFragment、RecoverVideoFragment 用来分别展示拍照和视频界面

TakePicturedFragment

package com.tsinglink.android.camera2videodemo;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.MeteringRectangle;
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.Looper;
import android.os.Message;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;

import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;

import org.jetbrains.annotations.NotNull;

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

import timber.log.Timber;

/**
 * @author zhuang
 */
public class TakePicturedFragment extends Fragment implements View.OnClickListener {
    private static final String TAG = "TakePicturedFragment";
    private int previewWidth = 1920;
    private int previewHeight = 1080;
    private int previewPictureWidth = 640;
    private int previewPictureHeight = 480;

    private static final SparseIntArray ORIENTATION = new SparseIntArray();

    static {
        //手机ROTATION逆时针旋转
        ORIENTATION.append(Surface.ROTATION_0, 90);
        ORIENTATION.append(Surface.ROTATION_90, 0);
        ORIENTATION.append(Surface.ROTATION_180, 270);
        ORIENTATION.append(Surface.ROTATION_270, 180);
    }

    private TextureView textureView;  //预览框控件
    private ImageButton takePicture;    //拍照按钮
    private ImageButton change;      //前后摄像头切换按钮
    private ImageView mImageView;     // 缩略图显示
    private String mCameraId;         // 摄像头Id
    private Size mPreviewSize;      //获取分辨率
    private ImageReader mImageReader;  //图片阅读器
    private static CameraDevice mCameraDevice;   //摄像头设备
    private static CameraCaptureSession mCaptureSession;   //获取会话
    private CaptureRequest mPreviewRequest;      //获取预览请求
    private CaptureRequest.Builder mPreviewRequestBuilder;   //获取到预览请求的Builder通过它创建预览请求
    private Surface mPreviewSurface;  //预览显示图

    //权限申请
    private String[] permissions = {Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.RECORD_AUDIO};
    private List<String> permissionList = new ArrayList();

    private ArrayList<String> imageList = new ArrayList<>();  //图片集合
    protected boolean isCreated=false;   //Fragment是否创建成功
    private boolean isVisible;   //Fragment是否可见


    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Timber.d("onCreateView: success");
        View view = inflater.inflate(R.layout.fragment_take_picture, container, false);
        //注册监听控件
        initView(view);
        textureView.setSurfaceTextureListener(textureListener);   //surfaceView回调里面配置相机打开相机
        takePicture.setOnClickListener(this);      //拍照监听
        mImageView.setOnClickListener(this);   //缩略图监听
        change.setOnClickListener(this);     //摄像头切换监听
        getPermission();         //申请权限
        //显示最后一张图
        isCreated = true;     //Fragment View 创建成功
        return view;      //显示当前View
    }

    // 第一步:获取权限

    /**
     * 获取拍照和读写权限
     */
    private void getPermission() {
        Timber.d("getPermission: success");
        //版本判断 当手机系统大于23时,才有必要去判断权限是否获取
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //权限是否已经 授权 GRANTED-授权  DINIED-拒绝
            for (String permission : permissions) {
                //检查权限是否全部授予
                if (ContextCompat.checkSelfPermission(getContext(), permission) != PackageManager.PERMISSION_GRANTED) {
                    //如果没有就添加到权限集合
                    permissionList.add(permission);
                }
            }
            //是空返回ture
            if (!permissionList.isEmpty()) {
                requestPermissions(permissionList.toArray(new String[permissionList.size()]), 1);
            } else {
                //表示全都授权了
                textureView.setSurfaceTextureListener(textureListener);
                //显示最后一张图片  最新
                setLastImagePath();
            }
        }
    }
    //权限回调
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] mPermissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        Timber.d("onRequestPermissionsResult: success");
        if (requestCode == 1) {
            //权限请求失败
            if (grantResults.length > 0) {
                //存放没授权的权限
                List<String> deniedPermissions = new ArrayList<>();
                for (int i = 0; i < grantResults.length; i++) {
                    int grantResult = grantResults[i];
                    String permission = permissions[grantResult];
                    if (grantResult != PackageManager.PERMISSION_GRANTED) {
                        deniedPermissions.add(permission);
                    }
                }
                if (deniedPermissions.isEmpty()) {
                    //说明都授权了  打开相机
                    openCamera();
                    //显示最新的一张图片或者视频的第一帧
                    setLastImagePath();
                } else {
                    // 继续申请权限
                    getPermission();
                }
            }
        }
    }
    //判断Fragment是否可见
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        //判断是否是第一次创建
        if (!isCreated) {
            return;
        }
        //如果可见
        if (isVisibleToUser) {
            isVisible = true;
            //显示第一张照片
            setLastImagePath();
            //如果textureView 可用
            if (textureView.isAvailable()) {
                //打开相机
                openCamera();
            } else {
                //先配置相机再打开相机
                textureView.setSurfaceTextureListener(textureListener);
            }
        } else {
            //当切换成录像时,将当前fragment 置为不可见
            Timber.d(TAG + " releaseCamera");
            isVisible = false;
            closeCamera();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        //如果fragment 可见
        if (isVisible) {
            //textureView 可用
            if (textureView.isAvailable()) {
                //打开相机
                openCamera();
            } else {
                //设置相机参数
                textureView.setSurfaceTextureListener(textureListener);
            }
        }
    }
    //注册视图上监听控件ID
    private void initView(View view) {
        Timber.d("initView: success");
        //预览框控件
        textureView = view.findViewById(R.id.textureView);
        //拍照控件
        takePicture = view.findViewById(R.id.takePicture);
        //缩略图控件
        mImageView = view.findViewById(R.id.image_show);
        //前后摄像头切换控件
        change = view.findViewById(R.id.change);
    }
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.takePicture:
                //拍照
                takePhoto();
                break;
            case R.id.change:
                //切换摄像头
                changeCamera();
                break;
            case R.id.image_show:
                //打开相册
                openAlbum();
                break;
        }
    }


    //第二步  写回调函数
    // 1.  SurfaceView状态回调
    TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
            Timber.d("onSurfaceTextureAvailable: success");
            setupCamera();
//            configureTransform(width, height);   //旋转
            openCamera();             //打开相机
        }

        @Override
        public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
//            configureTransform(width, height);
        }

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

        @Override
        public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
        }
    };

    // 2.  摄像头状态回调
    private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        //打开成功获取到camera设备
        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            Timber.d("onOpened: success");
            mCameraDevice = cameraDevice;
            //开启预览
            startPreview();
        }

        //打开失败
        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            mCameraDevice = cameraDevice;
            closeCameraDevice();
            openCamera();
        }

        //打开错误
        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int i) {
            String msg = "";
            switch(i) {
                case ERROR_CAMERA_DEVICE:
                    msg = "Fatal (device)";
                    break;
                case ERROR_CAMERA_DISABLED:
                    msg = "Device policy";
                    break;
                case ERROR_CAMERA_IN_USE:
                    msg = "Camera in use";
                    break;
                case ERROR_CAMERA_SERVICE:
                    msg = "Fatal (service)";
                    break;
                case ERROR_MAX_CAMERAS_IN_USE:
                    msg = "Maximum cameras in use";
                    break;
                default:
                    msg = "Unknown";
                    break;
            }
            RuntimeException exc = new RuntimeException("Camera " + mCameraId + "error: (" + i + ")" + msg);
            exc.printStackTrace();
            closeCameraDevice();
            openCamera();
        }
    };

    @MainThread private
    void closeCameraDevice() {
        if (mCameraDevice != null) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
    }

    // 3.实现PreviewCallback  拍照时调用
    private CameraCaptureSession.CaptureCallback mPreviewCaptureCallback = new CameraCaptureSession.CaptureCallback() {
        @Override
        // 一旦捕获完成
        public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
            super.onCaptureCompleted(session, request, result);
        }
    };

    //第三步  设置(配置)相机
    //设置摄像机 id  参数
    private void setupCamera() {
        Timber.d("setupCamera: success");
        //获取摄像头的管理者CameraManager
        CameraManager manager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
        try {
            //遍历所有摄像头
            for (String cameraId : manager.getCameraIdList()) {
                CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); //获取摄像机的特征
                //默认打开后置  - 忽略前置 LENS(镜头)
                if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)
                {
                    continue;
                }
                //获取StreamConfigurationMap,他是管理摄像头支持的所有输出格式
                StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                mPreviewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class),
                        720, 960); //获取最佳的预览大小
                Timber.i("mPreviewSize: 选择的分辨率宽度=" + mPreviewSize.getWidth());
                Timber.i("mPreviewSize: 选择的分辨率高度=" + mPreviewSize.getHeight());
                //进入回调设置相机然后打开
                textureView.setSurfaceTextureListener(textureListener);
                mCameraId = cameraId;
                break;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    //旋转屏幕
    private void configureTransform(int viewWidth, int viewHeight) {
        Timber.d("configureTransform: success");
        if (textureView == null || mPreviewSize == null) {
            return;
        }
        int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
        Timber.i("rotation: "+rotation);
        Matrix matrix = new Matrix();
        RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
        RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
        float centerX = viewRect.centerX();
        float centerY = viewRect.centerY();
        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
            float scale = Math.max((float) viewHeight / mPreviewSize.getHeight(),
                    (float) viewWidth / mPreviewSize.getWidth());
            matrix.postScale(scale, scale, centerX, centerY);
            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
        } else if (rotation == Surface.ROTATION_180) {
            Timber.i("rotation  --- : "+rotation);
            matrix.postRotate(180, centerX, centerY);
        }
        textureView.setTransform(matrix);
    }

    //选择sizeMap中大于并且接近width和height的size
    private Size getOptimalSize(Size[] sizeMap, int width, int height) {
        List<Size> sizeList = new ArrayList<>();
        for (Size option : sizeMap) {
            //当width > height
            if (width > height) {
                //选取官渡大于surface的宽度并且选取的高度大于surface的高度
                if (option.getWidth() > width && option.getHeight() > height) {
                    //符合的添加到sizeList
                    sizeList.add(option);
                }
            } else {
                //如果选择宽度大于surface的高度并且选择的高度大于surface的宽度
                if (option.getWidth() > height && option.getHeight() > width) {
                    //符合的添加到sizeList
                    sizeList.add(option);
                }
            }
        }
        if (sizeList.size() > 0) {
            return Collections.min(sizeList, new Comparator<Size>() {
                @Override
                public int compare(Size size, Size t1) {
                    return Long.signum(size.getWidth() * size.getHeight() - t1.getWidth() * t1.getHeight());
                }
            });
        }
        return sizeMap[0];
    }

    //创建预览请求的Builder (TEMPLATE_PREVIEW表示预览请求)
    private void setPreviewRequestBuilder() {
        Timber.d("setPreviewRequestBuilder: success");
        try {
            //通过cameraDevice获取到预览请求的Builder
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        //设置预览的显示图
        mPreviewRequestBuilder.addTarget(mPreviewSurface);
        MeteringRectangle[] meteringRectangles = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AE_REGIONS);
        if (meteringRectangles != null && meteringRectangles.length > 0) {
            Timber.d("PreviewRequestBuilder: AF_REGIONS");
        }
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_MODE_AUTO);
        mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
    }
    // 重复预览
    private void repeatPreview() {
        Timber.d("repeatPreview: success");
        mPreviewRequestBuilder.setTag(TAG);
        //通过预览请求的builder的.build获取到预览请求
        mPreviewRequest = mPreviewRequestBuilder.build();
        //设置反复捕获会话的请求,这样预览界面就会一直有数据显示
        try {
            //第一个参数就是预览求情,第二个参数是PreviewCallback,第三个是处理的线程
            mCaptureSession.setRepeatingRequest(mPreviewRequest, mPreviewCaptureCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    //第四步    打开相机
    private void openCamera() {
        //获取摄像头的管理者 CameraManager
        CameraManager manager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
        //检查权限
        try {
            if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) !=
                    PackageManager.PERMISSION_GRANTED) {
                return;
            } else {
                //通过manager.openCamera(id,cameraStateCallback,处理的线程)
                manager.openCamera(mCameraId, stateCallback, null);
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    //  关闭相机
    public static void closeCamera() {
        Timber.d("closeCamera: success");
        if (mCaptureSession != null) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
        if (mCameraDevice != null) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
    }

    //第五步 开启相机预览
    private void startPreview() {
        Timber.d("startPreview: success");
        //设置图片阅读器
        setupImageReader();
        SurfaceTexture mSurfaceTexture = textureView.getSurfaceTexture();
        //设置TextureView的缓冲区大小
        mSurfaceTexture.setDefaultBufferSize(previewWidth, previewHeight);
        //获取Surface显示预览数据
        mPreviewSurface = new Surface(mSurfaceTexture);
        try {
            //创建预览请求的Builder
            setPreviewRequestBuilder();
            //通过CameraDevice创建相机捕捉会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,
            // 当他创建好后会回调onCconfigured方法
            //第三个参数用来确定Callback在那个线程执行,null表示在当前线程执行
            mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mImageReader.getSurface()),
                    new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                            mCaptureSession = cameraCaptureSession;
                            repeatPreview();
                        }

                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                        }
                    }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    //第七步 设置图片阅读
    private void setupImageReader() {
        Timber.d("setupImageReader: success");
        //前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据
        mImageReader = ImageReader.newInstance(previewPictureWidth, previewPictureHeight, ImageFormat.JPEG, 1);
        //监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader imageReader) {
                Toast.makeText(getContext(), "图片已保存", Toast.LENGTH_SHORT).show();
                //获得mage
                Image image = imageReader.acquireNextImage();

                //开启线程一部保存图片
                ImageSaver imageSaver = new ImageSaver(getContext(), image);
                new Thread(imageSaver).start();

            }
        }, null);
    }

    // 第八步 拍照
    private void takePhoto() {
        Timber.d("takePhoto: success");
        try {
            //首先创建拍照的请求 CaptureRequest
            final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest
                    (CameraDevice.TEMPLATE_STILL_CAPTURE);
            //获取屏幕方向
            int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
            //获取到当前预览窗口的图
            mCaptureBuilder.addTarget(mImageReader.getSurface());
            //设置拍照方向
            if (mCameraId.equals("1")) {
                rotation = 2;
            }
            //设置图片的方向    因为默认的是横屏   我们使用手机一般是竖屏所以需要处理
//            mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
            //停止预览
            mCaptureSession.stopRepeating();
            //开始拍照,然后回调上面的接口重启预览,因为mCaptureBuilder设置ImageReader作为target,
            // 所以会自动回调ImageReader的onImageAvailable()方法保存图片
            CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
                                               @NonNull TotalCaptureResult result) {
                    super.onCaptureCompleted(session, request, result);
                    repeatPreview();
                }
            };
            mCaptureSession.capture(mCaptureBuilder.build(), captureCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    // 第九步 创建子线程保存图片
    public class ImageSaver implements Runnable {
        private Image mImage;//图片
        private Context mContext;
        public ImageSaver(Context context, Image image) {
            Timber.d("ImageSaver: success");
            mImage = image;
            mContext = context;
        }
        @Override
        public void run() {
            //将照片转字节
            ByteBuffer buffer = mImage.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();
                Message message = new Message();
                message.what = 0;
                Bundle mBundle = new Bundle();
                mBundle.putString("myPath",path);
                message.setData(mBundle);
                handler.sendMessage(message);
                mImage.close(); // 必须关闭 不然拍第二章会报错
            }
        }
        // 异步消息处理
        private Handler handler = new Handler(Looper.myLooper()) {

            @Override
            public void handleMessage(@NonNull Message message) {
                super.handleMessage(message);
                switch (message.what) {
                    case 0:
                        Bundle bundle = message.getData();
                        //通过指定的键值对获取到刚刚发送过来的地址
                        String myPath = bundle.getString("myPath");
                        imageList.add(myPath);
                        //imageList.add(bundle.getString(myPath));   // 这样不行必须分开写 不然 arrList没有数据
                        setLastImagePath();
                        break;
                    default:
                        throw new IllegalStateException("Unexpected value: " + message.what);
                }

            }
        };
        // 广播通知相册更新
        public void broadcast() {
            Timber.d("broadcast: success");
            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);
            mContext.sendBroadcast(intent);
        }
    }

    // 改变前后摄像头
    private void changeCamera() {
        Timber.d("changeCamera: success");
        if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK))) {
            Toast.makeText(getContext(), "前置转后置", Toast.LENGTH_SHORT).show();
            mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
        } else {
            Toast.makeText(getContext(), "后置转前置", Toast.LENGTH_SHORT).show();
            mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_BACK);
        }
        mCameraDevice.close();
        openCamera();
    }

    //找到最后一张的路径
    private void setLastImagePath() {
        Timber.d("setLastImagePath: success");
        imageList = GetImageFilePath.getFilePath();
        if (imageList.size() == 0) {
            return;
        }
        String string = imageList.get(imageList.size() - 1);
        if(string.contains(".jpg")){
            setImageBitmap(string);
        }else {
            MediaMetadataRetriever retriever = new MediaMetadataRetriever();
            retriever.setDataSource(string);
            Bitmap bitmap = retriever.getFrameAtTime(1);
            mImageView.setImageBitmap(bitmap);
        }
    }
    // 设置缩略图
    private void setImageBitmap(String path){
        Timber.d("setImageBitmap: success");
        Bitmap bitmap = (Bitmap) BitmapFactory.decodeFile(path);
        mImageView.setImageBitmap(bitmap);
    }
    //打开相册
    private void openAlbum() {
        Timber.d("openAlbum: success");
        Intent intent = new Intent();
        // 在ImageShowActivity中直接从相册中遍历 不需要传递过去
        //intent.putStringArrayListExtra("myList", imageList);
        intent.setClass(getContext(), ImageShowActivity.class);
        startActivity(intent);
    }
}

RecoverVideoFragment

package com.tsinglink.android.camera2videodemo;


import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
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.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.MediaMetadataRetriever;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Chronometer;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

import VideoHandle.EpEditor;
import VideoHandle.EpVideo;
import VideoHandle.OnEditorListener;
import timber.log.Timber;

/**
 * @author zhuang
 */
public class RecoverVideoFragment extends Fragment implements View.OnClickListener {
    private String TAG = "RecoverVideoFragment";
    private ImageButton videoButton; //用来重新设置录像按钮
    private TextureView mTextureView; //预览框控件
    private CaptureRequest.Builder mPreviewCaptureRequest; //获取请求创建者
    private CameraDevice mCameraDevice; //camera设备
    private MediaRecorder mMediaRecorder; //音视频录制
    //摄像头ID 默认置为后置BACK FRONT值为0 == BACK
    private String mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
    private Chronometer timer; //计时器
    private ArrayList<String> imageList = new ArrayList<>();   // 路径集合
    private static CameraCaptureSession mCameraCaptureSession;   //获取会话
    private Handler mChildHandler;   //子线程
    private CameraManager mCameraManager; //摄像头管理者
    private boolean isVisible = false;  //fragment是否可见
    private boolean isRecording = false;   //是否在录制视频
    private HandlerThread mHandlerThread;  //线程处理者
    private ImageView mImageView;     //缩略图按钮
    private ImageButton change;    //前后置切换按钮
    private int previewWidth = 1280;
    private int previewHeight = 720;

    private static final SparseIntArray ORIENTATION = new SparseIntArray();

    static {
        ORIENTATION.append(Surface.ROTATION_0, 90);
        ORIENTATION.append(Surface.ROTATION_90, 0);
        ORIENTATION.append(Surface.ROTATION_180, 270);
        ORIENTATION.append(Surface.ROTATION_270, 180);
    }

    //Fragment 中 onCreateView返回的就是fragment要显示的view.
    @Nullable
    @Override
    /**
     * 第一个参数LayoutInflater inflater第二个参数ViewGroup container第三个参数 Bundle savedInstanceState
     * LayoutInflater inflater:作用类似于findViewById()用来寻找xml布局下的具体的控件Button、TextView等,
     * LayoutInflater inflater()用来找res/layout/下的xml布局文件
     * ViewGroup container:表示容器,View放在里面
     * Bundle savedInstanceState:保存当前的状态,在活动的生命周期中,只要离开了可见阶段,活动很可能就会被进程终止,
     * 这种机制能保存当时的状态
     */
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onCreateView: success");
        View view = inflater.inflate(R.layout.fragment_recorder, container, false);
        initView(view);
        //监听视频按钮
        videoButton.setOnClickListener(this);
        //监听缩略图按钮
        mImageView.setOnClickListener(this);
        //监听前后置切换按钮
        change.setOnClickListener(this);
        return view;
    }

    //判断Fragment是否可见
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
            //如果Fragment可见 把isVisible置为true
            isVisible = true;
            Log.d(TAG, "setUserVisibleHint: true");
            //设置显示最后一张图片(视频第一帧)
            setLastImagePath();
            //初始化子线程
            initChildHandler();
            //如果textureView可用
            if (mTextureView.isAvailable()) {
                openCamera();
            } else {
                initTextureView();
            }
        } else {
            closeCamera();
            return;
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (isVisible) {
            //初始化子线程
            initChildHandler();
            if (mTextureView.isAvailable()) {
                openCamera();
            } else {
                initTextureView();
            }
        }
    }
    //初始化监听控件
    private void initView(View view){
        mImageView = view.findViewById(R.id.image_show);
        mTextureView = view.findViewById(R.id.textureView);
        timer = view.findViewById(R.id.timer);
        videoButton = view.findViewById(R.id.recording);
        change = view.findViewById(R.id.change);
    }
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.recording:
                if (isRecording) {
                    //再次按下将停止录制
                    stopRecorder();
                    isRecording = false;
                } else {
                    //第一次按下将isRecording置为ture
                    //配置并开始录制
                    isRecording = true;
                    configSession();
                    startRecorder();
                }
                break;
            case R.id.image_show:
                openAlbum();
                break;
            case R.id.change:
                changeCamera();
                break;

        }
    }


    //1.摄像头状态回调
    private CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            //摄像头被打开
            mCameraDevice = camera;
            startPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            //摄像头断开
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            //异常
        }
    };
    //2.录像时消息捕获回调
    private CameraCaptureSession.CaptureCallback mSessionCaptureCallback = new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
                                     long timestamp, long frameNumber) {
            super.onCaptureStarted(session, request, timestamp, frameNumber);
        }

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

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

        @Override
        public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request,
                                    @NonNull CaptureFailure failure) {
            super.onCaptureFailed(session, request, failure);
        }
    };
    //3.录像时会话状态回调
    private CameraCaptureSession.StateCallback mSessionStateCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            mCameraCaptureSession = session;
            updatePreview();
            try {
                //执行重复获取数据请求,等于一直获取数据呈现预览画面,mSessionCaptureCallback会返回此次操作的信息回调
                mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest.build(),
                        mSessionCaptureCallback, mChildHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

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

    //设置模式  闪光灯用
    private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) {
        builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
    }

    /**
     * 初始化TextureView的纹理生成监听,只有纹理生成准备好了。才能去进行摄像头的初始化工作让TextureView接收摄像头预览画面
     */
    private void initTextureView() {
        mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                //可以使用纹理
                initCameraManager();
                selectCamera();
                openCamera();
            }

            @Override
            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
                //纹理尺寸变化

            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                //纹理被销毁
                return false;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
                //纹理更新

            }
        });
    }

    /**
     * 计算需要的使用的摄像头分辨率
     *
     * @return
     */
    private Size getMatchingSize() {
        Size selectSize = null;
        try {
            CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);
            StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get
                    (CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);
            //这里是将预览铺满屏幕,所以直接获取屏幕分辨率
            DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
            //屏幕分辨率宽
            int deviceWidth = displayMetrics.widthPixels;
            //屏幕分辨率高
            int deviceHeight = displayMetrics.heightPixels;
            /**
             * 循环40次,让宽度范围从最小逐步增加,找到最符合屏幕宽度的分辨率,
             * 你要是不放心那就增加循环,肯定会找到一个分辨率,不会出现此方法返回一个null的Size的情况
             * ,但是循环越大后获取的分辨率就越不匹配
             */
            for (int j = 1; j < 41; j++) {
                for (int i = 0; i < sizes.length; i++) { //遍历所有Size
                    Size itemSize = sizes[i];
                    //判断当前Size高度小于屏幕宽度+j*5  &&  判断当前Size高度大于屏幕宽度-j*5  &&  判断当前Size宽度小于当前屏幕高度
                    if (itemSize.getHeight() < (deviceWidth + j * 5) && itemSize.getHeight() > (deviceWidth - j * 5)) {
                        if (selectSize != null) { //如果之前已经找到一个匹配的宽度
                            if (Math.abs(deviceHeight - itemSize.getWidth()) < Math.abs(deviceHeight - selectSize.getWidth())) { //求绝对值算出最接近设备高度的尺寸
                                selectSize = itemSize;
                                continue;
                            }
                        } else {
                            selectSize = itemSize;
                        }
                    }
                }
                if (selectSize != null) { //如果不等于null 说明已经找到了 跳出循环
                    break;
                }
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        Timber.i("getMatchingSize: 选择的分辨率宽度=" + selectSize.getWidth());
        Timber.i("getMatchingSize: 选择的分辨率高度=" + selectSize.getHeight());
        return selectSize;
    }

    /**
     * 初始化Camera2的相机管理,CameraManager用于获取摄像头分辨率,摄像头方向,摄像头id与打开摄像头的工作
     */
    private void initCameraManager() {
        mCameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
    }

    /**
     * 选择一颗我们需要使用的摄像头,主要是选择使用前摄还是后摄或者是外接摄像头
     */
    private void selectCamera() {
        if (mCameraManager != null) {
            Timber.e("selectCamera: CameraManager is null");

        }
        try {
            String[] cameraIdList = mCameraManager.getCameraIdList();   //获取当前设备的全部摄像头id集合
            if (cameraIdList.length == 0) {
                Timber.e("selectCamera: cameraIdList length is 0");
            }
            for (String cameraId : cameraIdList) {
                //遍历所有摄像头
                //屏幕方向
                int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
                //设置拍照方向
//                if (mCameraId.equals("1")) {
//                    rotation = 2;
//                }
                mPreviewCaptureRequest.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
                if (rotation == CameraCharacteristics.LENS_FACING_BACK) {
                    //这里选择了后摄像头
                    mCameraId = cameraId;
                }
            }

        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    @SuppressLint("MissingPermission")
    private void openCamera() {
        try {
            if (mCameraManager == null) {
                initCameraManager();
            }
            mCameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, mChildHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void closeCamera() {
        // 关闭预览就是关闭捕获会话
        stopPreview();
        // 关闭当前相机
        if (null != mCameraDevice) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
        if (null != mMediaRecorder) {
            mMediaRecorder.release();
            mMediaRecorder = null;
        }
        if (mHandlerThread != null) {
            stopBackgroundThread();
        }
    }

    /**
     * 开启预览
     * 使用TextureView显示相机预览数据,
     * 预览和拍照数据都是使用CameraCaptureSession会话来请求
     */
    private void startPreview() {
        stopPreview();
        SurfaceTexture mSurfaceTexture = mTextureView.getSurfaceTexture();
        Size cameraSize = getMatchingSize();
        //设置TextureView的缓冲区大小
        mSurfaceTexture.setDefaultBufferSize(previewWidth,
                previewHeight);
        //获取Surface显示预览数据
        Surface previewSurface = new Surface(mSurfaceTexture);
        try {
            //创建CaptureRequestBuilder,TEMPLATE_PREVIEW比表示预览请求
            mPreviewCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            //设置Surface作为预览数据的显示界面
            mPreviewCaptureRequest.addTarget(previewSurface);
            //创建相机捕获会话,第一个参数是捕获数据Surface列表,
            // 第二个参数是CameraCaptureSession的状态回调接口,
            //当他创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行
            mCameraDevice.createCaptureSession(Collections.singletonList(previewSurface),
                    new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession session) {
                            mCameraCaptureSession = session;
                            updatePreview();
                        }

                        @Override
                        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                            Toast.makeText(getActivity().getApplicationContext(), "Faileedsa ", Toast.LENGTH_SHORT).show();
                        }
                    }, mChildHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 更新预览
     */
    private void updatePreview() {
        if (null == mCameraDevice) {
            return;
        }
        try {
            setUpCaptureRequestBuilder(mPreviewCaptureRequest);
            HandlerThread thread = new HandlerThread("CameraPreview");
            thread.start();
            mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest.build(), null, mChildHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭预览
     */
    private void stopPreview() {
        //关闭预览就是关闭捕获会话
        if (mCameraCaptureSession != null) {
            mCameraCaptureSession.close();
            mCameraCaptureSession = null;
        }
    }

    /**
     * 初始化子线程Handler,操作Camera2需要一个子线程的Handler
     */
    private void initChildHandler() {
        mHandlerThread = new HandlerThread("DangJunHaoDemo");
        mHandlerThread.start();
        mChildHandler = new Handler(mHandlerThread.getLooper());
    }

    /**
     * 关闭线程
     */
    public void stopBackgroundThread() {
        if (mHandlerThread != null) {
            //quitSafely 安全退出
            mHandlerThread.quitSafely();
            try {
                mHandlerThread.join();
                mHandlerThread = null;
                mHandlerThread = null;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    private File file;
    /**
     * 配置录制视频相关数据
     */
    private void configMediaRecorder() {
        file = new File(Environment.getExternalStorageDirectory() +
                "/DCIM/camera/myMp4" + System.currentTimeMillis() + ".mp4");
        if (file.exists()) {
            file.delete();
        }
        if (mMediaRecorder == null) {
            mMediaRecorder = new MediaRecorder();
        }
        //设置音频来源
        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 * 1024 * 1920);
        //设置帧数 选择 30即可, 过大帧数也会让视频文件更大当然也会更流畅,但是没有多少实际提升。人眼极限也就30帧了。
        mMediaRecorder.setVideoFrameRate(30);
//        mMediaRecorder.setOrientationHint(180);
        mMediaRecorder.setVideoSize(previewWidth, previewHeight);
//        mMediaRecorder.setOrientationHint(90);
        //如果是前置
        if(mCameraId.equals("1")){
            mMediaRecorder.setOrientationHint(270);
        }
        Surface surface = new Surface(mTextureView.getSurfaceTexture());
        mMediaRecorder.setPreviewDisplay(surface);
        mMediaRecorder.setOutputFile(file.getAbsolutePath());
        try {
            mMediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 配置录制视频时的CameraCaptureSession
     */
    private void configSession() {
        try {
            if (mCameraCaptureSession != null) {
                mCameraCaptureSession.stopRepeating();//停止预览,准备切换到录制视频
                mCameraCaptureSession.close();//关闭预览的会话,需要重新创建录制视频的会话
                mCameraCaptureSession = null;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        configMediaRecorder();
        Size cameraSize = getMatchingSize();
        SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
        surfaceTexture.setDefaultBufferSize(previewWidth, previewHeight);
        Surface previewSurface = new Surface(surfaceTexture);
        Surface recorderSurface = mMediaRecorder.getSurface();//从获取录制视频需要的Surface
        try {
            mPreviewCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            mPreviewCaptureRequest.addTarget(previewSurface);
            mPreviewCaptureRequest.addTarget(recorderSurface);
            //请注意这里设置了Arrays.asList(previewSurface,recorderSurface) 2个Surface,很好理解录制视频也需要有画面预览,
            // 第一个是预览的Surface,第二个是录制视频使用的Surface
            mCameraDevice.createCaptureSession(Arrays.asList(previewSurface, recorderSurface),
                    mSessionStateCallback, mChildHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    /**
     * 开始录制视频
     */
    private void startRecorder() {
        mMediaRecorder.start();
        //开始计时
        startTime();
    }
    /**
     * 暂停录制视频(暂停后视频文件会自动保存)
     */
    private void stopRecorder() {
        //前置摄像头时,左右镜像
        if (mCameraId == "1") {
            EpVideo epVideo = new EpVideo(file.getAbsolutePath());
            //视频旋转180度,再镜像
            epVideo.rotation(0,true);
            EpEditor.exec(epVideo, new EpEditor.OutputOption(file.getAbsolutePath()), new OnEditorListener() {
                @Override
                public void onSuccess() {
                    Timber.e("EpEditor onSuccess");
                }

                @Override
                public void onFailure() {
                    Timber.e("EpEditor onFailure");
                }

                @Override
                public void onProgress(float progress) {
                    Timber.e("EpEditor onProgress " + progress);
                }
            });
        }
        //停止计时
        endTime();
        broadcast();
        setLastImagePath();
        startPreview();
        if (mMediaRecorder != null) {
            mMediaRecorder.stop();
            mMediaRecorder.reset();
        }
    }
    // 广播通知相册更新
    public void broadcast() {
        Timber.d("broadcast: success");
        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);
        getActivity().sendBroadcast(intent);
    }
    // 改变前后摄像头
    private void changeCamera() {
        Timber.d("changeCamera: success");
        if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK))) {
            Toast.makeText(getContext(), "前置转后置", Toast.LENGTH_SHORT).show();
            mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
        } else {
            Toast.makeText(getContext(), "后置转前置", Toast.LENGTH_SHORT).show();
            mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_BACK);
        }
        mCameraDevice.close();
        openCamera();
    }

    //找到最后一张的路径
    private void setLastImagePath() {
        Log.d(TAG, "setLastImagePath: success");
        imageList = GetImageFilePath.getFilePath();
        if (imageList.size() == 0) {
            return;
        }
        String string = imageList.get(imageList.size() - 1);
        if (string.contains(".jpg")) {
            setImageBitmap(string);
        } else {
            MediaMetadataRetriever retriever = new MediaMetadataRetriever();
            retriever.setDataSource(string);
            //获取第1帧
            Bitmap bitmap = retriever.getFrameAtTime(1);
            mImageView.setImageBitmap(bitmap);
        }
    }

    // 设置缩略图显示
    private void setImageBitmap(String path) {
        Timber.d("setImageBitmap: success");
        Bitmap bitmap = BitmapFactory.decodeFile(path);
        //通过ImageView显示缩略图
        mImageView.setImageBitmap(bitmap);
    }
    //打开相册
    private void openAlbum() {
        Timber.d("openAlbum: success");
        Intent intent = new Intent();
        // 在ImageShowActivity中直接从相册中遍历 不需要传递过去
        //intent.putStringArrayListExtra("myList", imageList);
        intent.setClass(getContext(), ImageShowActivity.class);
        startActivity(intent);
    }
    private void startTime() {
        timer.setBase(SystemClock.elapsedRealtime());//计时器清零
        timer.start();
    }
    private void endTime(){
        timer.stop();
        timer.setBase(SystemClock.elapsedRealtime());//计时器清零
    }
}

4、创建拍照和视频布局fragment_take_picture.xml fragment_recorder.xml

fragment_take_picture

<?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">

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

    <ImageButton
        android:background="@drawable/circle_shape_white"
        android:id="@+id/takePicture"
        android:layout_width="@dimen/camera_bottom_side_bt_size"
        android:layout_height="@dimen/camera_bottom_side_bt_size"
        android:src="@drawable/bt_snapshot_circle"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="50dp"
        />

    <ImageView
        android:id="@+id/image_show"
        android:layout_width="@dimen/camera_bottom_side_bt_size"
        android:layout_height="@dimen/camera_bottom_side_bt_size"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="50dp"
        android:layout_marginBottom="50dp" />

    <ImageButton
        android:id="@+id/change"
        android:layout_width="@dimen/camera_bottom_side_bt_size"
        android:layout_height="@dimen/camera_bottom_side_bt_size"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="50dp"
        android:layout_marginBottom="50dp"
        android:background="@drawable/circle_shape_gray"
        android:src="@drawable/asl_switch" />
</RelativeLayout>

fragment_recorder

<?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">

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

    <ImageButton
        android:background="@drawable/circle_shape_gray"
        android:id="@+id/recording"
        android:layout_width="@dimen/camera_bottom_side_bt_size"
        android:layout_height="@dimen/camera_bottom_side_bt_size"
        android:src="@drawable/asl_record"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="50dp"
        />

    <ImageView
        android:id="@+id/image_show"
        android:layout_width="@dimen/camera_bottom_side_bt_size"
        android:layout_height="@dimen/camera_bottom_side_bt_size"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="50dp"
        android:layout_marginBottom="50dp" />

    <ImageButton
        android:id="@+id/change"
        android:layout_width="@dimen/camera_bottom_side_bt_size"
        android:layout_height="@dimen/camera_bottom_side_bt_size"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="50dp"
        android:layout_marginBottom="50dp"
        android:background="@drawable/circle_shape_gray"
        android:src="@drawable/asl_switch"/>

    <Chronometer
        android:id="@+id/timer"
        android:textColor="#f00"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:format="%s"
        android:gravity="center"
        android:textSize="40sp" />



</RelativeLayout>

5、MainActivity绑定碎片

package com.tsinglink.android.camera2videodemo;

import android.Manifest;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.viewpager.widget.ViewPager;

import java.util.ArrayList;
import java.util.List;

/**
 * @author zhuang
 */

public class MainActivity extends AppCompatActivity {
    /*
    拍照录像切换
     */
    private ViewPager change_page;
    /*
    布局集合(拍照录像切换)
     */
    private List<Fragment> layoutList;
    /*
    标题集合(拍照录像切换)
     */
    private List<String> titleList;

    private MyPagerAdapter myPagerAdapter;

    /**
     * 初始化视图界面(拍照/摄像)
     */
    private void initView(){
        change_page = findViewById(R.id.change_page);
        layoutList = new ArrayList<>();
        layoutList.add(new TakePicturedFragment());
        layoutList.add(new RecoverVideoFragment());

        //设置适配器
        myPagerAdapter = new MyPagerAdapter(getSupportFragmentManager(), layoutList);
        change_page.setAdapter(myPagerAdapter);
    }

    @Override
    protected void onCreate(Bundle saveInstanceState) {
        //隐藏通知栏状态栏
        if (Build.VERSION.SDK_INT >= 21) {
            View decorView=getWindow().getDecorView();//获取当前界面的decorView
            int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    |View.SYSTEM_UI_FLAG_FULLSCREEN//隐藏状态栏
                    |View.SYSTEM_UI_FLAG_LAYOUT_STABLE//保持整个View的稳定,使其不会随着SystemUI的变化而变化;
                    |View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION//让导航栏悬浮在Activity上
                    //  |View.SYSTEM_UI_FLAG_HIDE_NAVIGATION//隐藏导航栏
                    |View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;//沉浸模式且状态栏和导航栏出现片刻后会自动隐藏
            decorView.setSystemUiVisibility(option);
            getWindow().setStatusBarColor(Color.TRANSPARENT);//设置透明颜色
            getWindow().setNavigationBarColor(Color.TRANSPARENT);
        }
        super.onCreate(saveInstanceState);
        setContentView(R.layout.activity_main);
        askPermission();
        initView();
        ActionBar actionBar=getSupportActionBar();
        actionBar.hide();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        TakePicturedFragment.closeCamera();
    }

    private void askPermission(){
        boolean sSRPR= ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)|
                ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)|
                ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)|
                ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO);
        Log.e("msg",Boolean.toString(sSRPR));
        if(sSRPR){
            //5.20更新,直接写下面这一行
            //begin
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.CAMERA
            },0);
            //end
        }

    }
}

6、创建预览最近拍摄图片与照片 ImageShowActivity

package com.tsinglink.android.camera2videodemo;

import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;

import timber.log.Timber;


/**
 * @author zhuang
 */
public class ImageShowActivity extends AppCompatActivity  {
    private final static String TAG = "ImageShowActivity";
    private String lastImagePath;

    //图片集合
    private ArrayList<String> imageList = new ArrayList<>();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Timber.i("onCreate success !!!!!");
        setContentView(R.layout.activity_image_show);
        // 调用获取图片路径类中的静态方法
        imageList = GetImageFilePath.getFilePath();
        lastImagePath = imageList.get(imageList.size() - 1);
        gotoGallery(lastImagePath);
    }

    // 转到画廊   就是图库
    public void gotoGallery(String path) {
        Timber.i("gotoGallery success !!!!!");
        Uri uri =getMediaUriFromPath(this, path);
        Timber.d("uri: "+uri);
        Intent intent = new Intent("com.android.camera.action.REVIEW", uri);
        intent.setData(uri);
        startActivity(intent);
        finish();

    }

    @SuppressLint("Range")
    public  Uri getMediaUriFromPath(Context context, String path) {
        Timber.d("getMediaUriFromPath success !!!!!");
        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;
    }



}

7、获取相册camera 图片路径

package com.tsinglink.android.camera2videodemo;

import android.os.Environment;
import android.util.Log;

import java.io.File;
import java.util.ArrayList;

import timber.log.Timber;

/**
 * @author zhuang
 */
public class GetImageFilePath {
    //获取相册camera 图片路径
    static ArrayList<String> imageList = new ArrayList<>() ;
    public static ArrayList<String> getFilePath() {
        File file= new File(Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera");
        if (!file.exists()) {
            file.mkdirs();
        }
        File[] dirEpub = file.listFiles();
        if (dirEpub.length != 0){
            for (int i = 0; i < dirEpub.length; i++){
                String fileName = dirEpub[i].toString();
                imageList.add(fileName);
                Timber.i("File name = " + fileName);
            }
        }
        return imageList;
    }
}

至此基于Camera2的预览拍照录制视频功能基本实现

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: 安卓camera2拍照录制视频demo是基于安卓相机API的一个示例应用程序,用于演示如何使用安卓相机2 API来实现拍照录制视频功能。 在此示例应用程序中,首先需要初始化相机设备并获取相机特性、参数等信息。然后,可以通过创建一个用于预览的SurfaceViewTextureView,并将其设置为相机的预览输出目标。 在拍照功能方面,可以通过设置拍照的一些参数,如图片格式、拍照模式、闪光灯模式等。然后,可以通过调用相机的拍照方法,触发相机拍照操作,并在拍照完成后保存图片。 在录制视频功能方面,需要创建一个用于录制视频的MediaRecorder对象,并设置视频的输出格式、编码格式、视频大小等参数。然后,可以通过设置相机的预览输出目标为MediaRecorder的Surface,并调用MediaRecorder的start方法开始录制视频,调用stop方法停止录制。 此外,在实际开发中,还需要处理相机权限的获取和申请,以及相机的生命周期管理,如在Activity的onResume和onPause方法中初始化和释放相机等。还可以根据需要添加其他功能,如自动对焦、曝光调节等。 总之,安卓camera2拍照录制视频demo提供了一个基本的框架和实现思路,供开发者参考和借鉴,在此基础上可以根据实际需求进扩展和定制。 ### 回答2: 安卓Camera2是用于在安卓设备上进相机操作的API。它提供了更多的功能和控制选项,可以实现更高级的相机操作,比如手动对焦、曝光控制、帧率控制等。 要编写一个拍照录制视频的Demo,首先需要获取相机的权限。在AndroidManifest.xml文件中添加相机权限的声明: <uses-permission android:name="android.permission.CAMERA" /> 然后,在布局文件中添加一个相机预览的SurfaceView: <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> 接下来,在Activity中获取相机实例并进相关配置。首先创建一个CameraManager对象,通过它来获取相机ID和打开相机: CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); String cameraId = cameraManager.getCameraIdList()[0]; cameraManager.openCamera(cameraId, cameraStateCallback, null); 在相机状态回调中,可以配置相机预览的Surface和启动预览CameraCaptureSession.StateCallback sessionStateCallback = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { try { // 设置相机预览的Surface CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); requestBuilder.addTarget(surfaceView.getHolder().getSurface()); // 启动预览 session.setRepeatingRequest(requestBuilder.build(), null, null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { // 配置失败处理 } }; 最后,编写点击拍照录制视频的逻辑。点击拍照时,创建一个ImageReader对象,设置监听器来处理拍照结果: ImageReader imageReader = ImageReader.newInstance(imageWidth, imageHeight, ImageFormat.JPEG, 1); imageReader.setOnImageAvailableListener(onImageAvailableListener, null); ImageReader.OnImageAvailableListener onImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { // 处理拍照结果 } }; 点击开始录制视频时,创建一个MediaRecorder对象,并配置输出文件、音频源、视频源等参数。然后使用CameraCaptureSession进录制会话: MediaRecorder mediaRecorder = new MediaRecorder(); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); // 配置输出文件,视频尺寸等参数 sessionStateCallback.onConfigured(cameraCaptureSession); // 启动预览 mediaRecorder.prepare(); mediaRecorder.start(); 以上是一个简单的安卓Camera2拍照录制视频Demo的实现。通过Camera2 API可以实现更多的相机功能和控制选项,可以根据实际需求进更多的定制和扩展。 ### 回答3: 安卓的Camera2 API是一个强大的相机框架,可以实现高效的拍照录制视频功能。 首先,我们需要在AndroidManifest.xml文件中声明相机和音频录制权限。例如: <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> 然后,我们需要在Activity或Fragment中创建一个CameraManager对象来管理相机设备。例如: private CameraManager mCameraManager; mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); 接下来,我们需要获取可用的相机列表,并选择一个需要使用的相机设备。例如: String cameraId = null; try { for (String id : mCameraManager.getCameraIdList()) { CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id); int facing = characteristics.get(CameraCharacteristics.LENS_FACING); if (facing == CameraCharacteristics.LENS_FACING_BACK) { cameraId = id; break; } } } catch (CameraAccessException e) { e.printStackTrace(); } 然后,我们可以打开相机设备并开始预览。例如: try { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(cameraId, mCameraStateCallback, null); } } catch (CameraAccessException e) { e.printStackTrace(); } 在相机预览过程中,我们可以设置一些相机参数,例如预览尺寸、拍照尺寸、闪光灯模式等。例如: CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureBuilder.addTarget(mPreviewSurface); captureBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); 然后,我们可以通过调用CameraCaptureSession的capture方法来拍照。例如: mCameraCaptureSession.capture(captureBuilder.build(), mCaptureCallback, null); 如果要录制视频,可以通过MediaRecorder来实现。例如: private void startRecording() throws IOException { mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mMediaRecorder.setOutputFile(mOutputFile.getAbsolutePath()); // 设置视频参数 mMediaRecorder.setVideoEncodingBitRate(10000000); mMediaRecorder.setVideoFrameRate(30); mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight()); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); // 设置音频参数 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mMediaRecorder.setAudioChannels(1); mMediaRecorder.setAudioSamplingRate(16000); mMediaRecorder.setAudioEncodingBitRate(32000); mMediaRecorder.prepare(); mMediaRecorder.start(); } 最后,我们可以在相机的onClosed方法中释放相机资源和停止录制。例如: @Override public void onClosed(CameraDevice camera) { mCameraDevice.close(); mCameraDevice = null; if (mMediaRecorder != null) { mMediaRecorder.stop(); mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; } } 通过上述步骤,我们可以实现一个简单的安卓Camera2拍照录制视频的demo。希望对你有所帮助!
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值