Demostración completa de la cámara Camera2 (video de grabación de fotos de vista previa) tutorial para principiantes

1. Introducción a la función (diseño de la interfaz de usuario)

aplicación de cámara abierta

        Primero, ingresará a la interfaz de vista previa del modo de cámara:

La interfaz de vista previa de la toma de fotografías.

                                                           Un total de cuatro controles.

                                              1. El control de vista previa general TextureView

                                              2. Vista de imagen en miniatura en la esquina inferior izquierda

                                              3. El botón de foto ImageButton en el medio

                                              4. Cambie el ImageButton de la cámara

Principalmente dos Fragmentos Deslice el dedo hacia la izquierda y hacia la derecha a través de ViewPage para cambiar entre el modo de foto y el modo de video. La implementación específica se encuentra en la parte posterior. En mi demostración, deslice el dedo hacia la izquierda para cambiar al modo de video.

        Ingrese al modo de vista previa de la grabación:

Interfaz de vista previa de video

                                                            Un total de cinco controles.

                                              1. El control de vista previa general TextureView

                                              2. Vista de imagen en miniatura en la esquina inferior izquierda

                                              3. El botón de foto ImageButton en el medio

                                              4. Cambie el ImageButton de la cámara

                                              5. Cronómetro en la parte superior

2. La parte del código de la interfaz de usuario

        1. Primero en 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.ViewPager>

</RelativeLayout>

La interfaz principal es principalmente para registrar un ViewPager. La parte anotada es la barra de título preparada anteriormente. Quiero usar el título para preguntar si es una interfaz de video o una interfaz de fotos, pero no he encontrado la manera de hacer el fondo. de la barra de título transparente, así que la descarté. Si la encuentras más tarde, actualízala a tiempo aquí. 

        2. Seguido por Mainactivity.java

package com.example.camera;

import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.View;

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

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

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 TakePictureFragment());
        layoutList.add(new RecorderVideoFragment());

//        titleList = new ArrayList<>();
//        titleList.add("拍照");
//        titleList.add("录像");
        //设置适配器
        myPagerAdapter = new MyPagerAdapter(getSupportFragmentManager(), layoutList);
        change_page.setAdapter(myPagerAdapter);
    }

    //活动的创建
    @Override
    protected void onCreate(Bundle saveInstanceState) {
        super.onCreate(saveInstanceState);
        setContentView(R.layout.activity_main);
        initView();

        //隐藏通知栏状态栏
        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);
        }
        ActionBar actionBar=getSupportActionBar();
        actionBar.hide();
    }
    
    protected  void onDestroy() {
        super.onDestroy();
        TakePictureFragment.closeCamera();
    }
}

En la actividad principal:

1. Hizo la interfaz de vista de inicialización y agregó dos fragmentos TakePictureFragment y RecorderVideoFragment

2.因为ViewPage要用到PagerAdapter 将默认的界面设置成了拍照预览界面

3.onCreate中做了个沉浸式通知栏(隐藏状态栏和通知栏)

4.onDestroy中调用了TakePictureFrament中的closeCamera

5.注释掉的部分同上是标题栏

        3.MyPagerAdapter.java中

package com.example.camera;

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;
//    private List<String> titleList;
    public MyPagerAdapter(FragmentManager manager, List<Fragment> layoutList ){
        super(manager);
        this.layoutList = layoutList;
//        this.titleList = titleList;
    }
    @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);
    }
}

这个类继承FragmentPageAdapter跟ViewPage联合使用,注:ViewPage跟activity也可以不一定是需要Fragment主要的功能就是通过左右滑动来切换fragment

        3.fragment_take_picture.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">

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

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

    <ImageView
        android:id="@+id/image_show"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_marginLeft="50dp"
        android:layout_marginBottom="50dp" />
    <ImageButton
        android:id="@+id/change"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ic_baseline_flip_camera_android_24"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="50dp"
        android:layout_marginBottom="50dp"
        android:layout_alignParentRight="true"
        />
</RelativeLayout>

        4 TakePpictureFragment.java中

package com.example.camera;

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.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;

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;

public class TakePictureFragment extends Fragment implements View.OnClickListener {
    private static final String TAG = "TakePictureFragment";

    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);
        Log.d(TAG, "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() {
        Log.d(TAG, "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);
        Log.d(TAG, "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 置为不可见
            Log.d(TAG, 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) {
        Log.d(TAG, "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) {
        Log.d(TAG, "onClick: success");
        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
        //textureView.isAvailable()为ture
        public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) {
            Log.d(TAG, "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) {
            Log.d(TAG, "onOpened: success");
            mCameraDevice = cameraDevice;
            //开启预览
            startPreview();
        }

        //打开失败
        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            Toast.makeText(getContext(), "摄像头设备连接失败", Toast.LENGTH_SHORT).show();
        }

        //打开错误
        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int i) {
            Toast.makeText(getContext(), "摄像头设备连接出错", Toast.LENGTH_SHORT).show();
        }
    };

    // 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() {
        Log.d(TAG, "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); //获取最佳的预览大小
                //进入回调设置相机然后打开
                textureView.setSurfaceTextureListener(textureListener);
                mCameraId = cameraId;
                break;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    //旋转屏幕
    private void configureTransform(int viewWidth, int viewHeight) {
        Log.d(TAG, "configureTransform: success");
        if (textureView == null || mPreviewSize == null) {
            return;
        }
        int rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation();
        Log.i("TAGggg", "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) {
            Log.i("TAGggg", "rotation  --- : "+rotation);
            matrix.postRotate(180, centerX, centerY);
        }
        textureView.setTransform(matrix);
    }

    //选择sizeMap中大于并且接近width和height的size
    private Size getOptimalSize(Size[] sizeMap, int width, int height) {
        Log.d(TAG, "getOptimalSize: success");
        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() {
        Log.d(TAG, "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) {
            Log.d(TAG,"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() {
        Log.d(TAG, "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() {
        Log.d(TAG, "openCamera: success");
        //获取摄像头的管理者 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() {
        Log.d(TAG, "closeCamera: success");
        if (mCaptureSession != null) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
        if (mCameraDevice != null) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
    }

    //第五步 开启相机预览
    private void startPreview() {
        Log.d(TAG, "startPreview: success");
        //设置图片阅读器
        setupImageReader();
        SurfaceTexture mSurfaceTexture = textureView.getSurfaceTexture();
        //设置TextureView的缓冲区大小
        mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        //获取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() {
        Log.d(TAG, "setupImageReader: success");
        //前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据
        mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), 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() {
        Log.d(TAG, "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) {
            Log.d(TAG, "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() {
            Log.d(TAG, "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() {
        Log.d(TAG, "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();
        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){
        Log.d(TAG, "setImageBitmap: success");
        Bitmap bitmap = (Bitmap) BitmapFactory.decodeFile(path);
        mImageView.setImageBitmap(bitmap);
    }
    //打开相册
    private void openAlbum() {
        Log.d(TAG, "openAlbum: success");
        Intent intent = new Intent();
        // 在ImageShowActivity中直接从相册中遍历 不需要传递过去
        //intent.putStringArrayListExtra("myList", imageList);
        intent.setClass(getContext(), ImageShowActivity.class);
        startActivity(intent);
    }
}

 其中拍照流程   复制下面加粗函数在代码中查看

1.权限申请,以及权限回调,没有权限回调第一次安装程序申请权限之后还是不会进入预览界面,重启程序后才能正常预览拍照

2.因为使用的ViewPage加Fragment所以需要setUserVisibleHint函数来保证Fragment可见的时候在对其进行初始化,不然两个Fragment同时执行初始化相机打开相机等会报错.

3.初始化控件,在该类中用的是initView

4.监听控件onClick

5.初始化 Surface   并在其回调函数中执行配置相机打开相机等操作 因为我使用的是默认的TextureView所以直接写回调函数TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener()....

6. Configure los parámetros de la cámara a través de CameraManager para seleccionar el ID de la cámara  setupCamera()

7. Seleccione el tamaño getOptimalSize que es más grande y cercano al ancho y alto en el mapa de tamaño.   Si escribe la demostración usted mismo, no puede establecer un valor específico usted mismo, incluido el procesamiento de la pantalla de rotación. Acabo de comentar en él

8. Abra la cámara e impleméntela en la función openCamera() en mi demostración a través de CameraManager getSystemService(Context.CAMERA_SERVICE)

9. Para abrir la cámara, debe usar la devolución de llamada del estado de la cámara CameraDevice. StateCallback  en onOpened es principalmente para obtener el cameraDevice obtenido y ejecutar la vista previa abierta

10. Antes de abrir la vista previa, debe configurar ImageReader. En mi demostración, está implementado en setupImageReader()

11. Para crear una vista previa, primero debe crear createCaptureSession a través de cameraDevice. Cree una aplicación de vista previa y use llamada CameraCaptureSession.StateCallback . El onConfigured de esta función de devolución de llamada obtiene mCaptureSession (sesión de captura) y la llama para repetir la vista previa.

12. Cree una solicitud de vista previa repetida Obtenga la compilación de la solicitud de vista previa a través de captureRequest.Builder y luego pase mPreviewRequestBuilder.build(), obtenga la solicitud de vista previa mPreviewRequest y pásela

mCaptureSession.setRepeatingRequest(mPreviewRequest, mPreviewCaptureCallback, null);

Realice solicitudes de vista previa repetidas y sesiones de solicitud de captura repetidas Implementado en repeatPreview () en mi demostración

13. De acuerdo con la vista previa anterior, es exitoso. El siguiente paso es tomar fotografías. En la demostración, se llamará a tomarFoto() al tomar fotografías.

La mCaptureSession definida por CameraCaptureSession.CaptureCallback realiza el procesamiento de fotos

14. Guarde la foto después de tomar la foto. Cree un hilo secundario para guardar la imagen. Cree un hilo secundario para guardar la foto. El propósito de guardar la foto es mejorar la eficiencia. Guarde la foto después de tomar la foto, y guardar la foto implicará la notificación de difusión para actualizar el álbum. En esta demostración, el hilo secundario se abre a través de la clase interna ImageSaver

15. Escribí antes para enviar la ruta de toma de fotos a través del mensaje de Hander, pero luego no sirvió de nada, los lectores pueden descartarlo.

16. La actualización del álbum de notificación de transmisión se implementa principalmente a través de Itent plus uri, y se implementa específicamente en la función de transmisión () en la demostración

17. Muestre la última imagen a través de la miniatura, es decir, la última imagen. En la demostración, setLastImagePath() y setImageBitmap () se usan principalmente para convertir cadenas a mapas de bits. GetImageFilePath.getFilePath usado en el medio es otro que escribí una clase para recorrer todas las rutas del álbum del sistema. No lo ordené. Si es necesario, puedo ordenarlo según el tiempo de modificación. Olvidé agregar que también juzga si es una imagen o un video. Si es un vídeo, se muestra el primer fotograma.

18. Cambiar la cámara es muy simple. En la demostración, se implementa changCamera()

19.最后就是通过缩略图进入到系统相册openAlbum()  在这个函数中通过Itent启动另一个活动

就是ImageShowActivity 

        5.GetImageFilePath.java中

package com.example.camera;

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

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

public class GetImageFilePath {
    //获取相册camera 图片路径
    static ArrayList<String> imageList = new ArrayList<>() ;
    public static ArrayList<String> getFilePath() {
        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;
    }
}

 就是遍历系统相册下的所有文件路径

        6.ImageShowActivity.java中

package com.example.camera;

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;


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);
        Log.e(TAG, "onCreate success !!!!!");
        setContentView(R.layout.activity_image_show);
        // 调用获取图片路径类中的静态方法
        imageList = GetImageFilePath.getFilePath();
        lastImagePath = imageList.get(imageList.size() - 1);
        gotoGallery(lastImagePath);
    }

    // 转到画廊   就是图库
        public void gotoGallery(String path) {
            Log.e("TAG", "gotoGallery success !!!!!");
            Uri uri =getMediaUriFromPath(this, path);
            Log.i("asdddd", "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) {
        Log.e("TAG", "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;
    }



}

重点就是gotoGallery()中通过Itent

Intent intent = new Intent("com.android.camera.action.REVIEW", uri);

就可以实现调用相册的功能

        7.ImageShowActivity.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">
    <ImageView
        android:id="@+id/show_pictures"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

</RelativeLayout>

就一个ImageView

        8.RecoderVideoFragment.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">

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

    <ImageButton
        android:background="@drawable/shape_white_ring"
        android:id="@+id/recording"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/shape_recorder"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="50dp"
        />

    <ImageView
        android:id="@+id/image_show"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="50dp"
        android:layout_marginLeft="50dp"
        />
    <ImageButton
        android:id="@+id/change"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ic_baseline_flip_camera_android_24"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="50dp"
        android:layout_marginBottom="50dp"
        android:layout_alignParentRight="true"
        />

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

其实完全可以不用这样重写一次 完全可以包含上一个takepicture的xml包进行写就行其实是一模一样的就是加了个计时器控件而已     

         9.RecoderVideoFragment.java中

package com.example.camera;


import android.annotation.SuppressLint;
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;

public class RecorderVideoFragment extends Fragment implements View.OnClickListener {
    private String TAG = "RecorderVideoFragment";
    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 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();
        }
        Log.e(TAG, "getMatchingSize: 选择的分辨率宽度=" + selectSize.getWidth());
        Log.e(TAG, "getMatchingSize: 选择的分辨率高度=" + selectSize.getHeight());
        return selectSize;
    }

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

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

        }
        try {
            String[] cameraIdList = mCameraManager.getCameraIdList();   //获取当前设备的全部摄像头id集合
            if (cameraIdList.length == 0) {
                Log.e(TAG, "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(cameraSize.getWidth(),
                cameraSize.getHeight());
        //获取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 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();
        }
        //设置音频来源
        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);
        Size size = getMatchingSize();
        mMediaRecorder.setVideoSize(size.getWidth(), size.getHeight());
        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(cameraSize.getWidth(), cameraSize.getHeight());
        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 (mMediaRecorder != null) {
            mMediaRecorder.stop();
            mMediaRecorder.reset();
            //停止计时
            endTime();
        }
        broadcast();
        setLastImagePath();
        startPreview();
    }
    // 广播通知相册更新
    public void broadcast() {
        Log.d(TAG, "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() {
        Log.d(TAG, "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();
        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) {
        Log.d(TAG, "setImageBitmap: success");
        Bitmap bitmap = BitmapFactory.decodeFile(path);
        //通过ImageView显示缩略图
        mImageView.setImageBitmap(bitmap);
    }
    //打开相册
    private void openAlbum() {
        Log.d(TAG, "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());//计时器清零
    }
}

视频录制的流程跟拍照很相似

创建的请求不一样仅此而已

1.因为我在拍照的Fragment中获取过权限所以不在需要申请权限

2.因为使用的ViewPage加Fragment所以需要setUserVisibleHint函数来保证Fragment可见的时候在对其进行初始化,不然两个Fragment同时执行初始化相机打开相机等会报错.

3.初始化控件,在该类中用的是initView

4.监听控件onClick

5.初始化TextureView 监听 在demo中 initTextureVIew()

6. Calcule la resolución de la cámara utilizada por getMatchingSize()

7. Inicialice la gestión de cámaras initCameraManager() 

solo obtenga el CameraManager

8. La selección de la cámara es principalmente para seleccionar la cámara delantera y trasera  selectCamera ()

9 .openCamera() sigue siendo lo mismo que openCamera, debe usar CameraDevice.StateCallback para abrir la vista previa en el interior

10. Abrir vista previa Aquí se crea una solicitud de vista previa mPreviewCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

mPreviewCaptureRequest.addTarget(previewSurface);

Escribió la devolución de llamada CameraCaptureSession.StateCallback() que captura la solicitud en startPreview( ) y también la escribió en este método para llamar a repeatPreview() en la devolución de llamada

11. Repetir vista previa repeatPreview()

mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest.build(), null, mChildHandler);

mChildHandler aquí es para crear un procesamiento de subprocesos secundarios

12. Porque la grabación de video necesita detener la vista previa stopPreview()

13. Inicialice el subproceso  initChildHandler()   y cierre el subproceso stopBackgroundThread()

14. Configure los datos relacionados para grabar video configMediaRecorder()

mMediaRecorder.setOrientationHint(270); Para procesar la orientación de video guardada a través de esto, el frente debe configurarse en 270 y la parte posterior debe configurarse en 90

15. Configure CameraCaptureSession para grabar video, que es diferente de tomar fotos aquí configSession()

16. Empezar a grabar startRecorder()

dejar de grabar stopRecorder()

17. Difusión de actualización de la difusión del álbum ()

18. Cambia las cámaras delantera y trasera changeCamera()

19. Establecer para mostrar la última imagen Establecer la última ruta setLastImagePath() Convertir la ruta para mostrar la imagen setImageBitmap

20. Abra el álbum openAlbum()

21. El control del temporizador startTime() endTime() necesario para grabar video

Resuma el uso y la diferencia de varias devoluciones de llamada:

1.CameraDevice.StateCallback openCamera usará

2.CameraCaptureSession.CaptureCallback se usa al tomar fotos o grabar videos

3. Devolución de llamada de CameraCaptureSession.StateCallback para procesar solicitudes

4. Devolución de llamada de permiso onRequestPermissionResult

5.TextureView.SurfaceTextureListener() Devolución de llamada de estado de SurfaceView 

Este es mi proceso de aprendizaje. Puede que no sea correcto, pero definitivamente ayudará a Xiaobai.

 

Supongo que te gusta

Origin blog.csdn.net/I_am_a_loser/article/details/120512824
Recomendado
Clasificación