介绍
google已经在Android5.1之后取消了对Camera1的更新,转而提供了功能更加强大的Camera2.虽然新版本依然可以使用Camera1但是,不管是各种机型适配还是拍照参数自定义都是很鸡肋的.跟上最新的技术了解Camera2是必要的.关于Camera2的兼容一般是支持API22之后包括API22的Android版本,但是也发现一些机型(比如三星)在API22版本上并没有支持Camera2.
需要使用的API介绍
因为Camera2提供的功能更加强大,所以使用比Camera1会复杂许多.需要调用的API和回调也更多.这里简单介绍一下这些API的对应功能.好初步认识Camera2.
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 androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; 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.os.Looper; 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 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 Camera2Activity 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 Handler mMainHandler = new Handler(Looper.getMainLooper()); private Handler mChildHanler = 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 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,"TextureView 变化"); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { Log.e(TAG,"TextureView 销毁"); return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } }); } /** * 初始化子线程 */ private void initChildThread() { HandlerThread handlerThread = new HandlerThread("camera2"); handlerThread.start(); mChildHanler = new Handler(handlerThread.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 { SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture(); Size matchingSize = getMatchingSize(); surfaceTexture.setDefaultBufferSize(matchingSize.getWidth(),matchingSize.getHeight());//设置预览的图像尺寸 Surface surface = new Surface(surfaceTexture); // 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(surface); //添加目标 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(surface, mImageReader.getSurface()), mSessionStateCallback, mChildHanler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** *摄像头断开时 * @param camera */ @Override public void onDisconnected(@NonNull CameraDevice camera) { } /** * 出现异常情况时 * @param camera * @param error */ @Override public void onError(@NonNull CameraDevice camera, int error) { } /** * 摄像头关闭时 * @param camera */ @Override public void onClosed(@NonNull CameraDevice camera) { super.onClosed(camera); } }; } /** * 摄像头获取会话状态回调 */ private void initCameraCaptureSessionStateCallback(){ mSessionStateCallback = new CameraCaptureSession.StateCallback() { //摄像头完成配置,可以处理Capture请求了。 @Override public void onConfigured(@NonNull CameraCaptureSession session) { try { mCameraCaptureSession = session; //注意这里使用的是 setRepeatingRequest() 请求通过此捕获会话无休止地重复捕获图像。用它来一直请求预览图像 mCameraCaptureSession.setRepeatingRequest(mCaptureRequest.build(), mSessionCaptureCallback, mChildHanler); // mCameraCaptureSession.stopRepeating();//停止重复 取消任何正在进行的重复捕获集 // mCameraCaptureSession.abortCaptures();//终止获取 尽可能快地放弃当前挂起和正在进行的所有捕获。请只在销毁activity的时候调用它 } catch (CameraAccessException e) { e.printStackTrace(); } } //摄像头配置失败 @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { } }; } /** * 摄像头获取会话数据回调 */ private void initCameraCaptureSessionCaptureCallback(){ 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); // Log.e(TAG, "onCaptureCompleted: 触发接收数据"); // Size size = request.get(CaptureRequest.JPEG_THUMBNAIL_SIZE); } @Override public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { super.onCaptureFailed(session, request, failure); } @Override public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session, int sequenceId, long frameNumber) { super.onCaptureSequenceCompleted(session, sequenceId, frameNumber); } @Override public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session, int sequenceId) { super.onCaptureSequenceAborted(session, sequenceId); } @Override public void onCaptureBufferLost(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull Surface target, long frameNumber) { super.onCaptureBufferLost(session, request, target, frameNumber); } }; } /** * 打开摄像头 */ private void openCamera() { try { if (checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(mCurrentCameraId, mStateCallback, mMainHandler); return; } Toast.makeText(this, "没有授权", Toast.LENGTH_SHORT).show(); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 初始化图片读取器 */ private void initImageReader(){ //创建图片读取器,参数为分辨率宽度和高度/图片格式/需要缓存几张图片,我这里写的2意思是获取2张照片 mImageReader = ImageReader.newInstance(1080, 1920, ImageFormat.JPEG, 2); mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { // image.acquireLatestImage();//从ImageReader的队列中获取最新的image,删除旧的 // image.acquireNextImage();//从ImageReader的队列中获取下一个图像,如果返回null没有新图像可用 Image image = reader.acquireNextImage(); try { File path = new File(Camera2Activity.this.getExternalCacheDir().getPath()); if (!path.exists()){ Log.e(TAG, "onImageAvailable: 路径不存在"); path.mkdirs(); }else { Log.e(TAG, "onImageAvailable: 路径存在" ); } File file = new File(path,"demo.jpg"); FileOutputStream fileOutputStream = new FileOutputStream(file); // 这里的image.getPlanes()[0]其实是图层的意思,因为我的图片格式是JPEG只有一层所以是geiPlanes()[0],如果你是其他格式(例如png)的图片会有多个图层,就可以获取指定图层的图像数据 ByteBuffer byteBuffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes); fileOutputStream.write(bytes); fileOutputStream.flush(); fileOutputStream.close(); image.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } },mChildHanler); } private void takePicture(){ CaptureRequest.Builder captureRequestBuilder = null; try { captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//自动对焦 captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);//自动爆光 // // 获取手机方向,如果你的app有提供横屏和竖屏,那么就需要下面的方法来控制照片为竖立状态 // int rotation = getWindowManager().getDefaultDisplay().getRotation(); // Log.e(TAG, "takePicture: 手机方向="+rotation); // Log.e(TAG, "takePicture: 照片方向="+ORIENTATIONS.get(rotation)); captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, 270);//我的项目不需要,直接写死270度 将照片竖立 Surface surface = mImageReader.getSurface(); captureRequestBuilder.addTarget(surface); CaptureRequest request = captureRequestBuilder.build(); mCameraCaptureSession.capture(request,null,mChildHanler); //获取拍照 } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { switch (requestCode){ case 1: if (permissions.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ Toast.makeText(this, "授权成功", Toast.LENGTH_SHORT).show(); }else { Toast.makeText(this, "授权失败", Toast.LENGTH_SHORT).show(); finish(); } break; default: } } }
比较规范的拍照功能demo
上面的只是简简单单的demo,下面是实际使用的样子,其实差不多,贴出来也是多一个参考
public class FaceCameraActivity extends BaseActivity implements View.OnClickListener { private TextureView mTextureView; private Button mBtnCamera; private ImageView mBack; private MaterialDialog mHandlerImageWaitDialog; private CameraManager mCameraManager; private CameraDevice mCameraDevice; private ImageReader mImageReader; private CaptureRequest.Builder mCaptureRequest; private CameraDevice.StateCallback mCameraDeviceStateCallback; private CameraCaptureSession.StateCallback mCameraCaptureSessionStateCallback; private CameraCaptureSession.CaptureCallback mCameraCaptureSessionCaptureCallback; private CameraCaptureSession mCameraCaptureSession; private String mCurrentCameraId; private Size mCurrentSelectSize; private Handler mChildHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initChildThread(); initCameraManager(); initSelectCamera(); initHandlerMatchingSize(); initImageReader(); initTextureViewListener(); initCameraDeviceStateCallbackListener(); initCameraCaptureSessionStateCallbackListener(); initCameraCaptureSessionCaptureCallbackListener(); } @Override public int getLayout() { return R.layout.activity_face_camera; } @Override public void initView() { mBack = findViewById(R.id.back); mTextureView = findViewById(R.id.texture_view); mBtnCamera = findViewById(R.id.btn_camera); mBack.setOnClickListener(this); mBtnCamera.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_camera: if (ButtonDelayUtil.isFastClick()){ handlerImageWaitDialog().show(); stopPreview(); takePicture(); } break; case R.id.back: finish(); break; default: break; } } private void initChildThread() { HandlerThread handlerThread = new HandlerThread("faceCamera"); handlerThread.start(); mChildHandler = new Handler(handlerThread.getLooper()); } /** * 初始化相机管理 */ private void initCameraManager() { mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); } /** * 初始化选择摄像头 */ private void initSelectCamera() { try { String[] cameraIdArray = mCameraManager.getCameraIdList(); for (String itemId : cameraIdArray) { CameraCharacteristics itemCharacteristics = mCameraManager.getCameraCharacteristics(itemId); Integer facing = itemCharacteristics.get(CameraCharacteristics.LENS_FACING); if (facing == CameraCharacteristics.LENS_FACING_FRONT) { mCurrentCameraId = itemId; break; } } } catch (CameraAccessException e) { e.printStackTrace(); } if (mCurrentCameraId == null) { finish(); Toast.makeText(this, "此设备不支持前摄像头", Toast.LENGTH_SHORT).show(); } } /** * 初始化计算适合当前屏幕分辨率的拍照分辨率 * @return */ private void initHandlerMatchingSize() { try { CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentCameraId); StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG); DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); int deviceWidth = displayMetrics.widthPixels; int deviceHeigh = displayMetrics.heightPixels; L.e("当前屏幕密度宽度="+deviceWidth+"高度="+deviceHeigh); for (int j = 1; j < 81; j++) { for (int i = 0; i < sizes.length; i++) { Size itemSize = sizes[i]; if (itemSize.getHeight() < (deviceWidth + j * 5) && itemSize.getHeight() > (deviceWidth - j * 5)) { if (mCurrentSelectSize != null) { //如果之前已经找到一个匹配的宽度 if (Math.abs(deviceHeigh-itemSize.getWidth()) < Math.abs(deviceHeigh - mCurrentSelectSize.getWidth())){ //求绝对值算出最接近设备高度的尺寸 mCurrentSelectSize = itemSize; continue; } }else { mCurrentSelectSize = itemSize; } } } if (mCurrentSelectSize != null) { //如果不等于null 说明已经找到了 跳出循环 break; } } } catch (CameraAccessException e) { e.printStackTrace(); } L.e("当前预览宽度="+mCurrentSelectSize.getWidth()+"高度="+mCurrentSelectSize.getHeight()); } private void initImageReader() { L.e("初始化图片ImageReader的宽="+mCurrentSelectSize.getWidth()+"高="+mCurrentSelectSize.getHeight()); mImageReader = ImageReader.newInstance(mCurrentSelectSize.getWidth() , mCurrentSelectSize.getHeight() , ImageFormat.JPEG , 2); mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { FilePathSession.deleteFaceImageFile(); Image image = reader.acquireLatestImage(); ByteBuffer byteBuffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes); try { FileOutputStream fileOutputStream = new FileOutputStream(FilePathSession.getFaceImagePath()); fileOutputStream.write(bytes); fileOutputStream.flush(); fileOutputStream.close(); image.close(); startPreview(); handlerImageWaitDialog().dismiss(); runOnUiThread(new Runnable() { @Override public void run() { Intent startFaceConfirm = new Intent(FaceCameraActivity.this, FaceConfirmActivity.class); startActivity(startFaceConfirm); FaceCameraActivity.this.finish(); } }); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }, mChildHandler); } private void initTextureViewListener() { mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 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) { } }); } private void initCameraDeviceStateCallbackListener() { mCameraDeviceStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice camera) { //相机开启 mCameraDevice = camera; try { SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture(); surfaceTexture.setDefaultBufferSize(mCurrentSelectSize.getWidth(),mCurrentSelectSize.getHeight()); Surface surface = new Surface(surfaceTexture); mCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); mCaptureRequest.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);//自动爆光 mCaptureRequest.addTarget(surface); mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()) , mCameraCaptureSessionStateCallback , mChildHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onDisconnected(@NonNull CameraDevice camera) { } @Override public void onError(@NonNull CameraDevice camera, int error) { finish(); Toast.makeText(FaceCameraActivity.this, "相机打开失败", Toast.LENGTH_SHORT).show(); L.e("CameraDevice.StateCallback onError : 相机异常 error code="+error); } }; } private void initCameraCaptureSessionStateCallbackListener() { mCameraCaptureSessionStateCallback = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { mCameraCaptureSession = session; startPreview(); } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { finish(); Toast.makeText(FaceCameraActivity.this, "相机打开失败", Toast.LENGTH_SHORT).show(); L.e("CameraCaptureSession.StateCallback onConfigureFailed : CameraCaptureSession会话通道创建失败"); } }; } private void initCameraCaptureSessionCaptureCallbackListener() { mCameraCaptureSessionCaptureCallback = 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); //获取失败 Toast.makeText(FaceCameraActivity.this, "拍照失败", Toast.LENGTH_SHORT).show(); L.e("失败报告Reason="+failure.getReason()); } }; } @SuppressLint("MissingPermission") private void openCamera() { try { mCameraManager.openCamera(mCurrentCameraId, mCameraDeviceStateCallback, mChildHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } private MaterialDialog handlerImageWaitDialog(){ if (mHandlerImageWaitDialog == null){ mHandlerImageWaitDialog = new MaterialDialog.Builder(this) .content("正在处理图像中...") .progress(true,-1) .cancelable(false) .build(); } return mHandlerImageWaitDialog; } /** * 开始预览 */ private void startPreview(){ try { mCameraCaptureSession.setRepeatingRequest(mCaptureRequest.build(), mCameraCaptureSessionCaptureCallback, mChildHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 停止预览 */ private void stopPreview(){ try { mCameraCaptureSession.stopRepeating(); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 拍照 */ private void takePicture(){ try { CaptureRequest.Builder takePictureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); takePictureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//自动对焦 takePictureRequest.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);//自动爆光 int rotation = getWindowManager().getDefaultDisplay().getRotation(); int angle = getJpegOrientation(mCameraManager.getCameraCharacteristics(mCurrentCameraId), rotation); L.i("人脸拍照 照片角度="+angle); takePictureRequest.set(CaptureRequest.JPEG_ORIENTATION, angle); Surface surface = mImageReader.getSurface(); takePictureRequest.addTarget(surface); CaptureRequest request = takePictureRequest.build(); mCameraCaptureSession.capture(request, null, mChildHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } /** * 官方提供的JPEG图片方向算法 * @param c * @param deviceOrientation * @return */ private int getJpegOrientation(CameraCharacteristics c, int deviceOrientation) { if (deviceOrientation == OrientationEventListener.ORIENTATION_UNKNOWN){ return 0; } int sensorOrientation = c.get(CameraCharacteristics.SENSOR_ORIENTATION);//获取传感器方向 // Round device orientation to a multiple of 90 deviceOrientation = (deviceOrientation + 45) / 90 * 90; // Reverse device orientation for front-facing cameras boolean facingFront = c.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT;//判断摄像头面向 if (facingFront) { deviceOrientation = -deviceOrientation; } // Calculate desired JPEG orientation relative to camera orientation to make // the image upright relative to the device orientation int jpegOrientation = (sensorOrientation + deviceOrientation + 360) % 360; return jpegOrientation; } @Override protected void onDestroy() { super.onDestroy(); if (mImageReader != null){ mImageReader.close(); mImageReader = null; } if (mCameraCaptureSession != null){ stopPreview(); try { mCameraCaptureSession.abortCaptures(); } catch (CameraAccessException e) { e.printStackTrace(); } mCameraCaptureSession.close(); mCameraCaptureSession = null; } if (mCameraDevice != null){ mCameraDevice.close(); mCameraDevice = null; } mCameraManager = null; if (mChildHandler != null){ mChildHandler.removeCallbacksAndMessages(null); mChildHandler = null; } } }