通过Android官方的Camera2Basic工程源码,总结出的Android摄像头获取图像的最简编程实现:
步骤1: 申请权限
因摄像头是一个隐私性非常强的设备,所以需要弹框向用户申请权限。
在工程配置文件AndroidManifest.xml中标明使用权限:
<uses-permission android:name="android.permission.CAMERA"/>
在代码中弹框申请权限:
String [] permissions = {Manifest.permission.CAMERA,};ActivityCompat.requestPermissions(this, permissions, 0);
步骤2: 准备子线程
因摄像头相关的操作是非常消耗资源的耗时工作,因此最好在一个子线程里执行。
HbgThread = new HandlerThread("CameraThread");//创建HandlerThread对象,指定线程名bgThread.start();//子线程启动,注意它的run方法是没有指定,需要通过Handler把run方法代码交过来执行bgHandler = new Handler(bgThread.getLooper());//创建用于与bgThread通信的Handler对象,通过它的post方法把任务发到子线程执行// bgHandler.post(new Runnable() { ... } );//摄像头操作相关的方法函数只需要提供bgHandler对象就会自动post相关的操作代码在bgThread里执行
步骤3. 打开摄像头
创建Android摄像头打开操作的回调方法对象,用于指定摄像头成功打开后、关闭后和打开失败后作什么操作。
private CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() {@Override //摄像头成功打开后的操作public void onOpened(@NonNull CameraDevice cameraDevice) {Log.e("myinfo", "摄像头成功打开");}@Override //摄像头关闭后的操作public void onDisconnected(@NonNull CameraDevice cameraDevice) {}@Override //摄像头出错后的操作public void onError(@NonNull CameraDevice cameraDevice, int i) {}};
获取当前Android系统的摄像头管理器,并打开指定的摄像头。
//获取当前系统的摄像头管理器CameraManager cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);String [] ids;//获取当前系统里所有摄像头设备的IDids = cameraManager.getCameraIdList();//输出所有摄像头设备的ID, 一般"0"表示前摄像头, "1"表示后摄像头for (int i = 0; i < ids.length; i++)Log.e("myinfo", "id = " + ids[i]);//打开摄像头,指定摄像头打开操作的回调方法对象,并传入可下发任务到子线程执行的Handler对象cameraManager.openCamera(ids[0], cameraCallback, bgHandler);
步骤4. 创建摄像头设备的捕捉会话对象
摄像头设备的捕捉会话对象用于向摄像头申请捕捉图像、打开闪光灯等操作的命令通道。
首先创建摄像头设备会话对象的回调方法对象,用于指定会话通道配置成功或失败后作什么操作。
private CameraCaptureSession.StateCallback captureCallback = new CameraCaptureSession.StateCallback() {@Override //会话对象创建配置成功后的操作public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {Log.e("myinfo", "摄像头会话创建成功!");}@Override //会话对象创建配置失败后的操作public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {}};
在摄像头成功打开后触发的回调方法onOpened中创建一个接收图像数据的ImageReader对象,
并创建一个摄像头会话对象,指定使用会话的回调方法对象,并传入可下发任务到子线程执行的Handler对象:
private CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() {@Override //摄像头成功打开后的操作public void onOpened(@NonNull CameraDevice cameraDevice) {Log.e("myinfo", "摄像头成功打开");//创建接收摄像头图像的ImageReader对象,指定图像分辨率、编码格式、图像队列中最多存多少张图像imageReader = ImageReader.newInstance(800, 600, ImageFormat.JPEG, 3);camDevice = cameraDevice;//记录已成功打开的摄像头设备对象//创建会话对象camDevice.createCaptureSession(Arrays.asList(imageReader.getSurface()), captureCallback, bgHandler);}...}
步骤5. 发出捕捉图像请求
为ImageReader对象创建一个监听器对象,指定当接收到摄像头图像后作什么操作。
private ImageReader.OnImageAvailableListener imgListener = new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader imageReader) { //由bgThread子线程执行的代码}};
在会话对象创建并配置成功后触发的onConfigured方法中发出捕捉图像请求, 并设置ImageReader对象使用imgListener监听器对象。
private CameraCaptureSession.StateCallback captureCallback = new CameraCaptureSession.StateCallback() {@Override //会话对象创建配置成功后的操作public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {Log.e("myinfo", "摄像头会话创建成功!");//创建捕捉请求对象,指定请求摄像的预览图像CaptureRequest.Builder builder = camDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);builder.addTarget(imageReader.getSurface());//指定捕捉到图像数据由imageReader对象处理//当ImageReader对象接收到图像后触发OnImageAvailableListener方法,并由bgThread子线程执行方法里的代码imageReader.setOnImageAvailableListener(imgListener, bgHandler);//通过会话对象提交请求,并且设置是循环的请求cameraCaptureSession.setRepeatingRequest(builder.build(), null, bgHandler);}...}
步骤6. 处理捕捉到的摄像头图像
当ImageReader对象接收到图像后触发OnImageAvailableListener方法,在此方法处理图像数据,如进行网络传输、存储、显示等操作。
private ImageReader.OnImageAvailableListener imgListener = new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader imageReader) { //由bgThread子线程执行的代码Image img = imageReader.acquireNextImage();//获取出图像ByteBuffer buffer = img.getPlanes()[0].getBuffer();//获取图像数据byte [] bytes = new byte[buffer.remaining()];buffer.get(bytes);//图像数据存入bytes数组里img.close();//关闭图像//把bytes数组图像数据转成Bitmap图像,并在ImageView组件上显示Bitmap map = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);//让主线程设置显示图像imageView.post(new Runnable() {@Overridepublic void run() {imageView.setImageBitmap(map);}});}};
获取摄像头图像并在ImageView组件上显示的完整工程代码:
public class MainActivity extends AppCompatActivity {private ImageView imageView;private Button btnStart;private HandlerThread bgThread;//可通过Handler下发任务的子线程private Handler bgHandler;//用于与bgThread通信的Handler对象private ImageReader imageReader;//用于接收图像的对象名private CameraDevice camDevice; //记录已打开的摄像头设备对象@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);imageView = findViewById(R.id.imageView);btnStart = findViewById(R.id.btnStart);btnStart.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {initializeCamera(); //摄像头初始化}});requestPermissions();//弹框请求权限}private ImageReader.OnImageAvailableListener imgListener = new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader imageReader) { //由bgThread子线程执行的代码Image img = imageReader.acquireNextImage();//获取出图像ByteBuffer buffer = img.getPlanes()[0].getBuffer();//获取图像数据byte [] bytes = new byte[buffer.remaining()];buffer.get(bytes);//图像数据存入bytes数组里img.close();//关闭图像//把bytes数组图像数据转成Bitmap图像Bitmap map = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);//让主线程设置显示图像imageView.post(new Runnable() {@Overridepublic void run() {imageView.setImageBitmap(map);}});}};private CameraCaptureSession.StateCallback captureCallback = new CameraCaptureSession.StateCallback() {@Override //会话对象创建配置成功后的操作public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {Log.e("myinfo", "摄像头会话创建成功!");try {//3.创建捕捉请求对象,指定请求摄像的预览图像CaptureRequest.Builder builder = camDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);builder.addTarget(imageReader.getSurface());//指定捕捉到图像数据由imageReader对象处理//当ImageReader对象接收到图像后触发OnImageAvailableListener方法,并由bgThread子线程执行方法里的代码imageReader.setOnImageAvailableListener(imgListener, bgHandler);//通过会话对象提交请求,并且设置是循环的请求cameraCaptureSession.setRepeatingRequest(builder.build(), null, bgHandler);}catch (Exception e) {e.printStackTrace();}}@Override //会话对象创建配置失败后的操作public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {}};private CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() {@Override //摄像头成功打开后的操作public void onOpened(@NonNull CameraDevice cameraDevice) {Log.e("myinfo", "摄像头成功打开");//创建接收摄像头图像的ImageReader对象,指定图像分辨率、编码格式、图像队列中最多存多少张图像imageReader = ImageReader.newInstance(800, 600, ImageFormat.JPEG, 3);camDevice = cameraDevice;//记录已成功打开的摄像头设备对象try {//2.创建会话对象camDevice.createCaptureSession(Arrays.asList(imageReader.getSurface()), captureCallback, bgHandler);}catch (Exception e) {e.printStackTrace();}}@Override //摄像头关闭后的操作public void onDisconnected(@NonNull CameraDevice cameraDevice) {}@Override //摄像头出错后的操作public void onError(@NonNull CameraDevice cameraDevice, int i) {}};@SuppressLint("MissingPermission")public void initializeCamera() { //初始化摄像头bgThread = new HandlerThread("CameraThread");//创建HandlerThread对象,指定线程名bgThread.start();//子线程启动,注意它的run方法是没有指定,需要通过Handler把run方法代码交过来执行bgHandler = new Handler(bgThread.getLooper());//创建用于与bgThread通信的Handler对象,通过它的post方法把任务发到子线程执行// bgHandler.post(new Runnable() { ... } );//摄像头操作相关的方法函数只需要提供bgHandler对象就会自动post相关的操作代码在bgThread里执行//获取当前系统的摄像头管理器CameraManager cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);String [] ids;try {//获取当前系统里所有摄像头设备的IDids = cameraManager.getCameraIdList();//输出所有摄像头设备的ID, 一般"0"表示前摄像头, "1"表示后摄像头for (int i = 0; i < ids.length; i++)Log.e("myinfo", "id = " + ids[i]);//1.打开摄像头cameraManager.openCamera(ids[0], cameraCallback, bgHandler);} catch (Exception e) {e.printStackTrace();}}public void requestPermissions() {String [] permissions = {Manifest.permission.CAMERA,};ActivityCompat.requestPermissions(this, permissions, 0);}
}