camera2录像20210408

CameraManager
摄像头管理类:

主要有4个功能:

获取摄像头的ID
获取摄像头的特征信息(比如摄像头前后位置信息和支持的分辨率信息等等)
打开指定id的摄像头
打开和关闭闪光灯
CameraDevice
摄像头设备类:

主要功能有3个

创建获取数据请求类CaptureRequest.Builder(或者叫捕获请求),下面会介绍这个类
创建获取数据会话(创建预览或者拍照的会话通道)
关闭摄像头
CameraDevice.StateCallback
摄像头状态接口回调类:

主要是负责回调摄像头的开启/断开/异常/销毁.我们使用CameraManager打开指定id的摄像头时需要添加这个回调.

CameraCaptureSession.StateCallback
获取数据会话的状态接口回调类:

我们创建相机预览图像/拍照/录像都需要这个回调类,来告诉我们获取数据会话的通道状态是配置成功或者配置失败.它还负责给我们回调一个重要的CameraCaptureSession提供给我们操作,这个CameraCaptureSession类我下面会介绍

CameraCaptureSession.CaptureCallback
获取数据会话的数据接口回调类:

负责回调获取数据的生命周期(比如开始/进行中/完成/失败等等),如果你并不需要对生命周期里做操作,所以有时候没有啥作用.但是它也是必需创建的一个回调接口类,是在创建预览图像/拍照/录像的时候添加进去,但是拍照或者录像的数据都不在这个回调接口里出来(一开始很容易误解,以为拍照数据会从这里返回).除了回调获取数据的生命周期,还可以在回调方法里获取拍照或者录制过程的的一些参数信息,比如图片的Size/分辨率等等.

CaptureRequest.Builder
获取数据请求配置类:

很重要,也是我们频繁操作的一个配置类.由CameraDevice类创建.主要负责

设置返回数据的surface(显示预览View比如TextureView的surface 或者 照片ImageReader的surface)
配置预览/拍照/录制的拍照参数,比如自动对焦/自动曝光/拍照自动闪光/设置HZ值/颜色校正等等你能在系统相机上看到的功能.
数据配置完成后交给CameraCaptureSession会话类,让CameraCaptureSession操作提供我们需要的数据,例如图像预览或者拍照/录制视频

CameraCaptureSession
获取数据会话类:

很重要,是我们频繁操作的一个数据会话类,比如创建预览/停止预览/拍照/录像都要它来操作,它由CameraCaptureSession.StateCallback这个接口回调方法里回调提供给我们.

ImageReader
图片读取类:

不属于Camera2Api的类,但是是拍照功能重要的类,照片的数据流由它缓存,然后我们提取保存到本地成为图片文件或者显示在ImageView里

Camera2的操作流程
在上面的API介绍里,你是不是对这么多的配置类/会话类/接口回调类感到眼花缭乱?是的,Camera2的使用是相当眼花缭乱的,但是我们抓住一条线慢慢从上面跟到下面就应该能明白是怎么一回事了.下面我们来简单介绍一些Camera2的操作流程:

初始化流程:
初始化动态授权,这是基本操作
初始化一个子线程的Handler,Camera2的操作可以放在主线程也可以放在子线程.按例一般都是子线程里,但是Camera2只需要我们提供一个子线程的Handler就行了.
初始化ImageReader,这个没有初始化顺序要求,并且它有数据回调接口,接口回调的图片数据我们直接保存到内部存储空间,所以提前初始化提供给后续使用.
初始化TextureView,添加TextureView的接口回调.
在TextureView的接口回调里回调启用成功方法后,我们开始初始化相机管理类initCameraManager
然后继续初始化CameraDevice.StateCallback 摄像头设备状态接口回调类,先初始化提供给后续使用.(在这个接口类的开启相机的回调方法里,我们需要实现创建预览图像请求配置和创建获取数据会话)
继续初始化CameraCaptureSession.StateCallback 摄像头获取数据会话类的状态接口回调类,先初始化提供给后续使用.(在这个接口类的配置成功回调方法里,我们需要实现预览图像或者实现拍照)
继续初始化CameraCaptureSession.CaptureCallback 摄像头获取数据会话类的获取接口回调类,先初始化提供给后续使用.(啥都不干)
判断摄像头前后,选择对应id
打开指定id的摄像头
实现拍照
逻辑流程:
动态相机权限获取 >> 设置TextureView回调 >> TextureView启用成功回调方法触发 >> 选择摄像头 >> 打开相机 >> 相机开启回调方法触发 >> 创建CaptureRequest.Builder配置类 >> 设置配置类图像预览模式 >> 配置类导入需要显示预览的TextureView的surface >> 创建数据会话 >> 数据会话的配置成功回调方法触发 >> 创建预览图像 >> 预览图像显示成功 >> 按键点击拍照 >> 创建新的CaptureRequest.Builder配置类,添加目标为拍照 >> 配置类导入ImageReader的surface >> 数据会话使用这个配置类创建拍照 >> ImageReader的接口类图片可用方法触发 >> 保存图片

代码部分
实现简单的拍照功能demo

复制代码
package demo.yt.com.demo;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.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.Image;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

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

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

public class Demo2Activity extends AppCompatActivity {
private static final String TAG = Camera2Activity.class.getName();
private String[] permission = {Manifest.permission.CAMERA};
private TextureView mTextureView; //注意使用TextureView需要开启硬件加速,开启方法很简单在AndroidManifest.xml 清单文件里,你需要使用TextureView的activity添加android:hardwareAccelerated=“true”
private Button mBtnPhotograph;
private HandlerThread mHandlerThread;
private Handler mChildHandler = null;
private CameraManager mCameraManager; //相机管理类,用于检测系统相机获取相机id
private CameraDevice mCameraDevice; //Camera设备类
private CameraCaptureSession.StateCallback mSessionStateCallback; //获取的会话类状态回调
private CameraCaptureSession.CaptureCallback mSessionCaptureCallback; //获取会话类的获取数据回调
private CaptureRequest.Builder mCaptureRequest; //获取数据请求配置类
private CameraDevice.StateCallback mStateCallback; //摄像头状态回调
private CameraCaptureSession mCameraCaptureSession; //获取数据会话类
private ImageReader mImageReader; //照片读取器
private Surface mSurface;
private SurfaceTexture mSurfaceTexture;
private String mCurrentCameraId;
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();

static {// /为了使照片竖直显示
    ORIENTATIONS.append(Surface.ROTATION_0, 90);
    ORIENTATIONS.append(Surface.ROTATION_90, 0);
    ORIENTATIONS.append(Surface.ROTATION_180, 270);
    ORIENTATIONS.append(Surface.ROTATION_270, 180);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_camera2);
    mTextureView = findViewById(R.id.textureview);
    mBtnPhotograph = findViewById(R.id.btn_Photograph);
    mBtnPhotograph.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            try {
                mCameraCaptureSession.stopRepeating();//停止重复   取消任何正在进行的重复捕获集 在这里就是停止画面预览

                /*  mCameraCaptureSession.abortCaptures(); //终止获取   尽可能快地放弃当前挂起和正在进行的所有捕获。
                 * 这里有一个坑,其实这个并不能随便调用(我是看到别的demo这么使用,但是其实是错误的,所以就在这里备注这个坑).
                 * 最好只在Activity里的onDestroy调用它,终止获取是耗时操作,需要一定时间重新打开会话通道.
                 * 在这个demo里我并没有恢复预览,如果你调用了这个方法关闭了会话又拍照后恢复图像预览,会话就会频繁的开关,
                 * 导致拍照图片在处理耗时缓存时你又关闭了会话.导致照片缓存不完整并且失败.
                 * 所以切记不要随便使用这个方法,会话开启后并不需要关闭刷新.后续其他拍照/预览/录制视频直接操作这个会话即可
                 */

                takePicture();//拍照
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    });
    initPermission();
    initChildThread();
    initImageReader();
    initTextureView();

}

/**
 * 初始化权限
 */
private void initPermission() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, permission, 1);
    }

}

private void initTextureView() {
    mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
            Log.e(TAG, "TextureView 启用成功");
            initCameraManager();
            initCameraCallback();
            initCameraCaptureSessionStateCallback();
            initCameraCaptureSessionCaptureCallback();
            selectCamera();
            openCamera();

        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
            Log.e(TAG, "SurfaceTexture 变化");

        }

        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
            Log.e(TAG, "SurfaceTexture 的销毁");

//这里返回true则是交由系统执行释放,如果是false则需要自己调用surface.release();
return true;
}

        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surface) {

        }
    });
}

/**
 * 初始化子线程
 */
private void initChildThread() {
    mHandlerThread = new HandlerThread("camera2");
    mHandlerThread.start();
    mChildHandler = new Handler(mHandlerThread.getLooper());
}

/**
 * 初始化相机管理
 */
private void initCameraManager() {
    mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);

}

/**
 * 获取匹配的大小
 *
 * @return
 */
private Size getMatchingSize() {

    Size selectSize = null;
    float selectProportion = 0;
    try {
        float viewProportion = (float) mTextureView.getWidth() / (float) mTextureView.getHeight();
        CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentCameraId);
        StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);
        for (int i = 0; i < sizes.length; i++) {
            Size itemSize = sizes[i];
            float itemSizeProportion = (float) itemSize.getHeight() / (float) itemSize.getWidth();
            float differenceProportion = Math.abs(viewProportion - itemSizeProportion);
            Log.e(TAG, "相减差值比例=" + differenceProportion);
            if (i == 0) {
                selectSize = itemSize;
                selectProportion = differenceProportion;
                continue;
            }
            if (differenceProportion <= selectProportion) {
                if (differenceProportion == selectProportion) {
                    if (selectSize.getWidth() + selectSize.getHeight() < itemSize.getWidth() + itemSize.getHeight()) {
                        selectSize = itemSize;
                        selectProportion = differenceProportion;
                    }

                } else {
                    selectSize = itemSize;
                    selectProportion = differenceProportion;
                }
            }
        }

    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
    Log.e(TAG, "getMatchingSize: 选择的比例是=" + selectProportion);
    Log.e(TAG, "getMatchingSize: 选择的尺寸是 宽度=" + selectSize.getWidth() + "高度=" + selectSize.getHeight());
    return selectSize;
}

/**
 * 选择摄像头
 */
private void selectCamera() {
    try {
        String[] cameraIdList = mCameraManager.getCameraIdList();//获取摄像头id列表
        if (cameraIdList.length == 0) {
            return;
        }

        for (String cameraId : cameraIdList) {
            Log.e(TAG, "selectCamera: cameraId=" + cameraId);
            //获取相机特征,包含前后摄像头信息,分辨率等
            CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(cameraId);
            Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);//获取这个摄像头的面向
            //CameraCharacteristics.LENS_FACING_BACK 后摄像头
            //CameraCharacteristics.LENS_FACING_FRONT 前摄像头
            //CameraCharacteristics.LENS_FACING_EXTERNAL 外部摄像头,比如OTG插入的摄像头
            if (facing == CameraCharacteristics.LENS_FACING_FRONT) {
                mCurrentCameraId = cameraId;

            }

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

/**
 * 初始化摄像头状态回调
 */
private void initCameraCallback() {
    mStateCallback = new CameraDevice.StateCallback() {
        /**
         * 摄像头打开时
         * @param camera
         */
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            Log.e(TAG, "相机开启");
            mCameraDevice = camera;

            try {
                mSurfaceTexture = mTextureView.getSurfaceTexture();        //surfaceTexture    需要手动释放
                Size matchingSize = getMatchingSize();
                mSurfaceTexture.setDefaultBufferSize(matchingSize.getWidth(), matchingSize.getHeight());//设置预览的图像尺寸
                mSurface = new Surface(mSurfaceTexture);//surface最好在销毁的时候要释放,surface.release();

// CaptureRequest可以完全自定义拍摄参数,但是需要配置的参数太多了,所以Camera2提供了一些快速配置的参数,如下:
//           TEMPLATE_PREVIEW :预览
// TEMPLATE_RECORD:拍摄视频
// TEMPLATE_STILL_CAPTURE:拍照
// TEMPLATE_VIDEO_SNAPSHOT:创建视视频录制时截屏的请求
// TEMPLATE_ZERO_SHUTTER_LAG:创建一个适用于零快门延迟的请求。在不影响预览帧率的情况下最大化图像质量。
// TEMPLATE_MANUAL:创建一个基本捕获请求,这种请求中所有的自动控制都是禁用的(自动曝光,自动白平衡、自动焦点)。
mCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);//创建预览请求
mCaptureRequest.addTarget(mSurface); //添加surface 实际使用中这个surface最好是全局变量 在onDestroy的时候mCaptureRequest.removeTarget(mSurface);清除,否则会内存泄露
mCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//自动对焦
/**
* 创建获取会话
* 这里会有一个容易忘记的坑,那就是Arrays.asList(surface, mImageReader.getSurface())这个方法
* 这个方法需要你导入后面需要操作功能的所有surface,比如预览/拍照如果你2个都要操作那就要导入2个
* 否则后续操作没有添加的那个功能就报错surface没有准备好,这也是我为什么先初始化ImageReader的原因,因为在这里就可以拿到ImageReader的surface了
*/
mCameraDevice.createCaptureSession(Arrays.asList(mSurface, mImageReader.getSurface()), mSessionStateCallback, mChildHandler);
} catch (CameraAccessException e) {
e.printStackTrace

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值