4-2.Android Camera 之预览图像编码模板(SurfaceView)
一、Camera
-
Camera 用于捕获图像和视频
-
在 Android 开发的早期阶段,Android 提供
android.hardware.Camera
API,开发者用它来访问和控制设备的摄像头硬件 -
然而,随着 Android 系统的发展,从 Android 5.0(API 级别 21)开始,Android 引入了一个新的 Camera2 API,以提供更强大和灵活的控制功能
二、Camera 预览图像
1、Util
- MyCameraTool.java
package com.my.camera.util;import android.hardware.Camera;
import android.util.Log;import com.my.camera.entity.CameraIdResult;import java.util.List;public class MyCameraTool {public static final String TAG = MyCameraTool.class.getSimpleName();/*** 得到摄像头 Id 对象** @return 摄像头 Id 对象,它有前置摄像头 Id 和后置摄像头 Id*/public static CameraIdResult getCameraIdResult() {CameraIdResult cameraIdResult = new CameraIdResult();int numberOfCameras = Camera.getNumberOfCameras();Log.i(TAG, "------------------------------ 摄像头个数:" + numberOfCameras);for (int cameraId = 0; cameraId < numberOfCameras; cameraId++) {Camera.CameraInfo cameraInfo = new Camera.CameraInfo();Camera.getCameraInfo(cameraId, cameraInfo);if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {Log.i(TAG, "------------------------------ 前置摄像头,cameraId 为:" + cameraId);cameraIdResult.setQzCameraId(cameraId);}if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {Log.i(TAG, "------------------------------ 后置摄像头,cameraId 为:" + cameraId);cameraIdResult.setHzCameraId(cameraId);} else {Log.i(TAG, "------------------------------ 其他摄像头,cameraId 为:" + cameraId);}}return cameraIdResult;}/*** 根据 SurfaceView 的尺寸和相机支持的预览尺寸来选择最优的预览尺寸** @param sizes 相机支持的预览尺寸列表* @param w SurfaceView 的宽度* @param h SurfaceView 的高度* @return 最优的预览尺寸,如果没有合适的尺寸则返回 null*/public static Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {if (sizes == null) return null;final double ASPECT_TOLERANCE = 0.1;double targetRatio = (double) h / w;Camera.Size optimalSize = null;double minDiff = Double.MAX_VALUE;int targetHeight = h;// 遍历所有支持的预览尺寸for (Camera.Size size : sizes) {// 检查宽高比是否接近目标宽高比double ratio = (double) size.width / size.height;if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;// 计算当前尺寸与目标尺寸的宽度差异// 如果差异小于当前最小差异,则更新最优尺寸和最小差异if (Math.abs(size.height - targetHeight) < minDiff) {optimalSize = size;minDiff = Math.abs(size.height - targetHeight);}}// 如果找不到接近目标宽高比的尺寸,则选择最接近目标高度的尺寸if (optimalSize == null) {minDiff = Double.MAX_VALUE;for (Camera.Size size : sizes) {if (Math.abs(size.height - targetHeight) < minDiff) {optimalSize = size;minDiff = Math.abs(size.height - targetHeight);}}}return optimalSize;}
}
- CameraIdResult.java
package com.my.camera.entity;public class CameraIdResult {private int qzCameraId = -1; // 前置摄像头 idprivate int hzCameraId = -1; // 后置摄像头 idpublic CameraIdResult() {}public CameraIdResult(int qzCameraId, int hzCameraId) {this.qzCameraId = qzCameraId;this.hzCameraId = hzCameraId;}public int getQzCameraId() {return qzCameraId;}public void setQzCameraId(int qzCameraId) {this.qzCameraId = qzCameraId;}public int getHzCameraId() {return hzCameraId;}public void setHzCameraId(int hzCameraId) {this.hzCameraId = hzCameraId;}
}
2、Activity Layout
- activity_preview_image.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".PreviewImageActivity"><SurfaceViewandroid:id="@+id/sv"android:layout_width="300dp"android:layout_height="300dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
3、Activity Code
- PreviewImageActivity.java
package com.my.camera;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import android.hardware.Camera;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;import com.my.camera.entity.CameraIdResult;
import com.my.camera.util.MyCameraTool;import java.io.IOException;
import java.util.List;public class PreviewImageActivity extends AppCompatActivity {public static final String TAG = PreviewImageActivity.class.getSimpleName();private SurfaceView sv;private SurfaceHolder surfaceHolder;private int hzCameraId;private Camera camera;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_preview_image);// 保持屏幕常亮getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);CameraIdResult cameraIdResult = MyCameraTool.getCameraIdResult();hzCameraId = cameraIdResult.getHzCameraId();sv = findViewById(R.id.sv);surfaceHolder = sv.getHolder();surfaceHolder.addCallback(new SurfaceHolder.Callback() {@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {Log.i(TAG, "------------------------------ surfaceCreated");openCamera();}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {Log.i(TAG, "------------------------------ surfaceChanged");cameraPreview(width, height);}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {Log.i(TAG, "------------------------------ surfaceDestroyed");closeCamera();}});}private void openCamera() {try {if (camera != null) return;if (hzCameraId == -1) return;camera = Camera.open(hzCameraId);camera.setPreviewDisplay(surfaceHolder);camera.setDisplayOrientation(90);camera.setPreviewCallback(new Camera.PreviewCallback() {@Overridepublic void onPreviewFrame(byte[] data, Camera camera) {Log.i(TAG, "------------------------------ 得到预览帧数据的 size 为 " + data.length);}});} catch (IOException e) {e.printStackTrace();closeCamera();}}private void cameraPreview(int width, int height) {if (camera == null) return;Camera.Parameters parameters = camera.getParameters();List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();Camera.Size previewSize = MyCameraTool.getOptimalPreviewSize(supportedPreviewSizes, width, height);parameters.setPreviewSize(previewSize.width, previewSize.height);camera.setParameters(parameters);camera.startPreview();}private void closeCamera() {if (camera == null) return;camera.setPreviewCallback(null);camera.stopPreview();camera.release();camera = null;}
}
三、Camera 预览图像案例解析
1、添加权限
- 在 AndroidManifest.xml 文件中添加相关权限,如果是 Android 6.0(API 级别 23)或之后,需要在运行时请求
<uses-permission android:name="android.permission.CAMERA" />
2、保持屏幕常亮
- 保持屏幕常亮就是不会自动关闭屏幕,不仅是预览图像,它也非常适用于阅读、视频播放等
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
3、选择摄像头
- 这里借助工具类,选择后置摄像头(手机背后的)
CameraIdResult cameraIdResult = MyCameraTool.getCameraIdResult();
hzCameraId = cameraIdResult.getHzCameraId();
4、准备 SurfaceView 作为预览图像容器
- SurfaceView 控件可用于预览图像、视频播放等
<SurfaceViewandroid:id="@+id/sv"android:layout_width="300dp"android:layout_height="300dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" />
- 通过 SurfaceView 对象得到 SurfaceHolder 对象,它作为预览图像显示,它有三个方法
-
surfaceCreated(@NonNull SurfaceHolder holder)
:当 Surface 被创建时调用,这里可以打开相机 -
surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height)
:当 Surface 的尺寸或格式发生变化时调用,这里可以调整相机预览的尺寸 -
surfaceDestroyed(@NonNull SurfaceHolder holder)
:当 Surface 被销毁时调用,这里可以关闭相机
sv = findViewById(R.id.sv);surfaceHolder = sv.getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback() {@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {Log.i(TAG, "------------------------------ surfaceCreated");openCamera();}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {Log.i(TAG, "------------------------------ surfaceChanged");cameraPreview(width, height);}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder holder) {Log.i(TAG, "------------------------------ surfaceDestroyed");closeCamera();}
});
5、打开相机
-
camera = Camera.open(hzCameraId);
:通过打开摄像头 -
camera.setPreviewDisplay(surfaceHolder);
:设置预览图像显示 -
camera.setDisplayOrientation(90);
:设置预览方向(否则图像不是正向的) -
camera.setPreviewCallback(......
:注册一个预览回调来接收预览帧数据
private void openCamera() {try {if (camera != null) return;if (hzCameraId == -1) return;camera = Camera.open(hzCameraId);camera.setPreviewDisplay(surfaceHolder);camera.setDisplayOrientation(90);camera.setPreviewCallback(new Camera.PreviewCallback() {@Overridepublic void onPreviewFrame(byte[] data, Camera camera) {Log.i(TAG, "------------------------------ 得到预览帧数据的 size 为 " + data.length);}});} catch (IOException e) {e.printStackTrace();closeCamera();}
}
6、调整相机预览的尺寸
-
Camera.Parameters parameters = camera.getParameters();
:得到相机参数对象 -
List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
:得到支持的预览尺寸列表 -
camera.setParameters(parameters);
:借助工具类选择最优的预览尺寸并设置 -
camera.startPreview();
:开始预览图像
private void cameraPreview(int width, int height) {if (camera == null) return;Camera.Parameters parameters = camera.getParameters();List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();Camera.Size previewSize = MyCameraTool.getOptimalPreviewSize(supportedPreviewSizes, width, height);parameters.setPreviewSize(previewSize.width, previewSize.height);camera.setParameters(parameters);camera.startPreview();
}
7、关闭相机
-
camera.setPreviewCallback(null);
:移除预览回调 -
camera.stopPreview();
:停止预览图像 -
camera.release();
:释放摄像头资源
private void closeCamera() {if (camera == null) return;camera.setPreviewCallback(null);camera.stopPreview();camera.release();camera = null;
}