目录
(3)获取相机特征CameraCharacteristics
(2)设置图片可用监听器OnImageAvailableListener
(1)创建相机设备状态回调CameraDevice.StateCallback
(2)创建捕获请求建造者CaptureRequest.Builder(参数:模板_预览)
(1)创建捕获请求建造者CaptureRequest.Builder(参数:模板_静态捕获)
(1) CaptureRequest contains unconfigured Input/Output Surface!
camera权限是必须申请的,如果需要保存图片还需要读写权限,并动态申请。
"android.permission.CAMERA"/> "android.permission.READ_EXTERNAL_STORAGE"/> "android.permission.WRITE_EXTERNAL_STORAGE"/>
requestPermissions(new String[]{"android.permission.CAMERA","android.permission.READ_EXTERNAL_STORAGE","android.permission.WRITE_EXTERNAL_STORAGE"}, 2333);
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- if (requestCode == 2333) {
- for (int i = 0; i < permissions.length; i++) {
- if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
- requestPermissions(permissions, requestCode);
- break;
- }
- }
- }
- }
流程:
相机管理者中存储了相机ID列表、相机设备。
String cameraIdList[] = cameraManager.getCameraIdList();
CameraCharacteristics中含有相机设备的信息。
cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
- //相机ID
- String cameraId=null;
- //(1)获取相机管理者
- CameraManager cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
- //(2)获取相机ID列表
- String cameraIdList[] = cameraManager.getCameraIdList();
- for (int i = 0; i < cameraIdList.length; i++) {
- //(3)获取相机特征
- CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraIdList[i]);
- //(4)获取相机朝向---后置
- if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) {
- //使用该相机ID
- cameraId = cameraIdList[i];
- break;
- }
- }
流程:
- //获取相机支持尺寸
- int output_width,output_height;
- //(1)获取当前相机的特性
- CameraCharacteristics cameraCharacteristics=cameraManager.getCameraCharacteristics(imageId);
- //(2)获取输出流配置
- StreamConfigurationMap map=cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
- //(3)获取输出尺寸数组(参数为输出目标类)
- Size[] outputSizes = map.getOutputSizes(ImageReader.class);
- //(4)选择输出尺寸
- for(int i=0;i
- //获取需要的输出尺寸
- output_height=outputSizes[i].getHeight();
- output_width=outputSizes[i].getWidth();
- //判断是否使用本尺寸代码
- //... ...
- break;
- }
4. SurfaceView及其他控件的准备
和Camera一样,Camera2也可以使用SurfaceView作为预览视图,但Camera2和Camera不同,他并不强制你拍摄前必须预览。
SurfaceView使用时请注意,一定要在SurfaceView创建完成后再进行预览的绑定,这一点和Camera、MediaPlayer等的操作是一样的。
但Camera绑定SurfaceView设置预览时使用的是SurfaceHolder,而Camera2使用的时Surface。
SurfaceView用于显示捕获的图片,作为预览捕获请求的目标。
流程:
(1)获取视图
(2)根据相机输出尺寸设置SurfaceView尺寸
(3)添加回调
surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
- //(1)获取控件
- SurfaceView surfaceView = findViewById(R.id.~);
-
- //(2)根据相机捕获尺寸设置SurfaceView尺寸
- RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) surfaceView.getLayoutParams();
- layoutParams.width = ~ ;
- layoutParams.height = ~ ;
- surfaceView.setLayoutParams(layoutParams);
-
- //Camera2绑定SurfaceView预览时使用的是Surface
- Surface surface=null;
-
- //(3)SurfaceView回调
- surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
- public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
- //构建成功
- surface = surfaceHolder.getSurface();
- }
- public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder,int i,int i1,int i2) {
- //SurfaceView改变
- }
- public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
- //构建摧毁
- surface = null;
- }
- });
-
-
- //拍照按钮
- Button button_takePicture=null;
5. 创建ImageReader用于处理捕获的图片
ImageReader用于处理捕获的图片,作为拍照捕获请求的目标。
流程:
(1)创建ImageReader
创建ImageView的前两个参数为捕获图像的宽高,请设置。第三个参数是表示在 ImageReader 队列中同时最多可以有多少张图像。如果这个参数为 1,表示 ImageReader 只能容纳一张图像。当你获取一张新的图像时,如果队列已满,则会丢弃旧的图像。这有助于避免内存溢出或过多图像堆积的问题。
(2)设置图片可用监听器OnImageAvailableListener
用ImageReader捕获图片后将调用其中的onImageAvailable()方法。
处理图像代码详解:
acquireNextImage() 方法用于从 ImageReader 中获取下一个可用的图像。acquireNextImage() 方法获取最新可用的图像,该图像会被移除并返回。如果没有可用的图像,此方法将会阻塞,直到有图像可用或者超时。返回的是一个 Image 对象,代表捕获到的图像数据。Image 对象通过 getPlanes() 方法返回一个 Image.Plane[] 数组,每个平面包含了图像的一个通道(例如:红色、绿色、蓝色、透明度等)。通常,对于 JPEG 格式的图像,这里的数组中只有一个平面,即索引为 0 的平面。ByteBuffer 是用来存储图像数据的容器。通过 getBuffer() 方法获取的 ByteBuffer 包含了图像数据。remaining() 方法返回当前位置与限制之间的元素数量,这里指的是剩余的可读取的字节数。get(bytes) 方法从 ByteBuffer 中读取数据并将其存储到 bytes 数组中。
- //(1)创建ImageReader,请注意宽高
- ImageReader imageReader = ImageReader.newInstance(output_width, output_height, ImageFormat.JPEG, 1);
- //(2)设置图片可用监听器
- imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
- public void onImageAvailable(ImageReader reader) {
-
- // 处理图像数据
- Image image = reader.acquireNextImage();
- ByteBuffer byteBuffer=image.getPlanes()[0].getBuffer();
- byte[] bytes=new byte[byteBuffer.remaining()];
- byteBuffer.get(bytes);
-
- //存储
- File file = new File(Environment.getExternalStorageDirectory() + "/picture.jpg");
- try (OutputStream output = new FileOutputStream(file)) {
- output.write(bytes);
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- // 处理完后记得释放资源
- image.close();
- }
- }, null);
6. 创建相机设备状态回调、通道并发送循环预览请求
流程:
(1)创建相机设备状态回调CameraDevice.StateCallback
CameraDevice是指具体相体设备。
(2)创建捕获请求建造者CaptureRequest.Builder(参数:模板_预览)
捕获请求需要在相机启动成功后创建。
添加目标为SurfaceView的Surface,需要确定此时的SurfaceView已经创建完成并已执行回调。
(3)创建捕获通道CaptureSession
捕获通道需要在相机启动成功后创建。
第一个参数为所有捕获请求的目标Surface集合,包括用于预览的SurfaceView的Surface和用于拍照的ImageReader的Surface,不能将捕获请求的目标设为不在本集合中的Surface,否则会报错。
(4)在捕获通道完成后要设置循环请求(预览捕获请求)
cameraCaptureSession.setRepeatingRequest(captureRequestBuilder_preview.build(), null, null);
- //捕获通道
- CameraCaptureSession captureSession;
-
- // (1) 创建相机设备状态回调-CameraDevice是指具体相体设备
- CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
- public void onOpened(@NonNull CameraDevice cameraDevice) {
- //相机设备打开
- myCameraDevice=cameraDevice;
- //捕获请求建造者
- CaptureRequest.Builder captureRequestBuilder_preview;
- try {
- // (2) 创建捕获请求建设者,参数:模板_预览
- captureRequestBuilder_preview = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
- //等待SurfaceView创建完成并回调后将它的Surface设为目标
- while (true) {
- if (surface != null) {
- //添加目标
- captureRequestBuilder_preview.addTarget(surface);
- break;
- }
- Thread.sleep(40);
- }
- } catch (CameraAccessException e) {
- throw new RuntimeException(e);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
-
- // (3) 创建捕获通道 - 捕获通道需要在相机启动后再创建
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- try {
- //创建目标合集
- List
list=new ArrayList(); - list.add(surface);
- list.add(imageReader.getSurface());
- cameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {
- public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
- //捕获通道配置完成
- captureSession=cameraCaptureSession;
- // (4) 配置完成,可以开始预览 - 设置循环请求
- try {
- cameraCaptureSession.setRepeatingRequest(captureRequestBuilder_preview.build(), null, null);
- } catch (CameraAccessException e) {
- throw new RuntimeException(e);
- }
- }
- public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
- //配置失败
- }
- }, null);
- } catch (CameraAccessException e) {
- throw new RuntimeException(e);
- }
- }
- }
-
- public void onDisconnected(@NonNull CameraDevice cameraDevice) {
- //相机设备断开
- if (myCameraDevice != null) {
- myCameraDevice.close();
- myCameraDevice = null;
- }
- }
- public void onError(@NonNull CameraDevice cameraDevice, int i) {
- //相机设备错误
- if (myCameraDevice != null) {
- myCameraDevice.close();
- myCameraDevice = null;
- }
- }
- };
7. 启动相机
使用相机管理者CameraManager的openCamera启动指定相机,参数为相机ID、相机状态回调、Handler。请注意第三个参数Handler用于指定回调方法在哪个线程上执行。通常情况下,相机相关的操作需要在主线程(UI 线程)执行,以确保与 UI 的交互是安全的。该参数不能为null。
- //Handler
- Handler handler_ui = new Handler() {
- public void handleMessage(@NonNull Message msg) {
-
- }
- };
-
- //启动相机
- cameraManager.openCamera(cameraId, stateCallback, handler_ui);
8. 拍摄照片
流程:
(1)创建捕获请求建造者CaptureRequest.Builder(参数:模板_静态捕获)
(2)捕获通道执行捕获拍照捕获请求
- button_take.setOnClickListener(new View.OnClickListener() {
- public void onClick(View view) {
- try {
-
- //(1)创建拍照的捕获请求,参数:模板_静态_捕获
- CaptureRequest.Builder captureRequestBuilder = myCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
- //用于保存拍照的Surface(在通道创建时已在目标集合中)
- Surface captureSurface = imageReader.getSurface();
- captureRequestBuilder.addTarget(captureSurface);
-
- //(2)捕获通道执行捕获请求
- captureSession.capture(captureRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
- public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
- super.onCaptureStarted(session, request, timestamp, frameNumber);
- }
- },null);
-
- } catch (CameraAccessException e) {
- throw new RuntimeException(e);
- }
- }
- });
9. 常见报错
(1) CaptureRequest contains unconfigured Input/Output Surface!
出现这个错误的原因是我们在一次捕获capture(也就是拍照的时候),cameraCaptureSession.capture(captureRequestBuilder.build(),CaptureCallback,Handler);这个captureRequestBuilder的目标Surface必须在创建通道时,是创建通道的第一个参数(目标List集合)中的子集 ,否则就报异常了。
(2) MTK零延迟的Camera机制
预览与拍照模式传给hal层的metadata键值对的key必须保持同步,也就是说,在同一次session会话中,你给预览传了什么样的metadata键值对的key,在拍照时同样也要传这个key,哪怕你不用这个metadata去调用底层工作,value可以传0或者任何底层不接受的值,切记这个key是必须传,否则打开相机的回调函数CameraDevice.StateCallback中onError就会被底层调用,从而导致相机无法打开。