Add Camera Support

添加相机支持

This lesson teaches you to

本节课您将学到

  1. Connect a CSI-2 peripheral

  2. 连接一个 CSI-2 外设

  3. Add camera features to an app
  1. 在应用中添加相机功能
  2. Handle I/O threading
  1. 处理 I/O 线程
  2. Trigger still image capture
  1. 触发静态图像拍摄
  2. Receive image data from the camera
  1. 接收相机图片数据

Try it out

试试看

A smart doorbell should capture an image of who (or what) is at the door, allowing the owner to remotely make a decision about whether or not to answer.

一个智能的门铃应该在门外有人或物时自动拍摄图像,使主人能够远程查看,并决定是否应答。

Because Android Things builds on the Android framework, you can access the same robust camera APIs that an Android mobile developer would use. In this lesson, you’ll access the camera peripheral using Android camera APIs and capture an image for later processing.

由于 Android Things 基于 Android 框架进行构建,您可以使用 Android 移动开发者所使用的成熟的相机 APIs 。在本节课,您将使用 Android 相机 APIs 来控制相机外设然拍摄一张图片以便后续处理。

Connect the camera

连接摄像头


Connect a supported camera module to the CSI-2 camera port on your board. Ensure that the cable is fully inserted and sits evenly before closing the connector latch.

将一个开发板所支持的相机模块连接到您的开发板的 CSI-2 相机端口。在固定连接器之前确认线缆完全插入并且结合处平整。

""

Add permissions and required features

添加权限和必要的功能


Add the required permissions to your app’s manifest file:

在您应用的 manifest 文件中添加必要的权限:

  1. <uses-permission android:name="android.permission.CAMERA" />

Set up an I/O thread

创建一个 I/O 线程


Communicating with peripheral hardware introduces blocking operations into the flow of your app. To avoid blocking the app’s main thread, and thus delaying framework events, create a background worker thread to process input and handle commands. The HandlerThread works very nicely for this purpose.

与外设硬件通信将阻塞您的应用。为了避免应用的主线程被阻塞,从而导致框架事件被延迟,需要新开启一个后台线程来处理输入和处理指令。在这种情况下, HandlerThread 将会是一个很好的选择。

  1. public class DoorbellActivity extends Activity {
  2. /**
  3. * A Handler for running tasks in the background.
  4. */
  5. private Handler mCameraHandler;
  6. /**
  7. * An additional thread for running tasks that shouldn't block the UI.
  8. *
  9. */
  10. private HandlerThread mCameraThread;
  11. @Override
  12. protected void onCreate(Bundle savedInstanceState) {
  13. super.onCreate(savedInstanceState);
  14. // Creates new handlers and associated threads for camera and networking operations.
  15. mCameraThread = new HandlerThread("CameraBackground");
  16. mCameraThread.start();
  17. mCameraHandler = new Handler(mCameraThread.getLooper());
  18. }
  19. @Override
  20. protected void onDestroy() {
  21. super.onDestroy();
  22. mCameraThread.quitSafely();
  23. }
  24. }

Initialize the camera session

初始化相机会话


The first step to capture a camera image is to discover the hardware and open a device connection. To connect to a camera device:

捕捉图像的第一步是找到硬件并且打开设备连接。为了连接相机设备,您需要:

  1. Use the CameraManager system service to discover the list of available camera devices with getCameraIdList().
  1. 使用 CameraManager 系统服务中的 getCameraIdList() 方法找到的可用的相机设备列表。

  2. Create an ImageReader instance to process the raw camera data and produce a JPEG-encoded image to your app. The reader processes data asynchronously, and invokes the provided OnImageAvailableListener when the image is ready.

  1. 在您的应用中创建一个 ImageReader 实例用于处理原始的相机数据并生成一个 JPEG 编码图像。读取数据是异步的操作,当图像准备好将会调用 OnImageAvailableListener 方法。
  2. Open a connection to the appropriate camera device using openCamera().
  1. 使用 openCamera() 方法来连接一个合适的相机。
  2. The CameraDevice.StateCallback reports that the camera was opened successfully through the onOpened() callback method.
  1. 利用 CameraDevice.StateCallback 中的回调方法 onOpened() 来通知相机已经被成功打开。
  2. Close the CameraDevice when it is not in use to free the system resources:
  1. 当不再使用相机时,调用 CameraDevice 方法关闭相机,释放系统资源:
  1. public class DoorbellCamera {
  2. // Camera image parameters (device-specific)
  3. private static final int IMAGE_WIDTH = ...;
  4. private static final int IMAGE_HEIGHT = ...;
  5. private static final int MAX_IMAGES = ...;
  6. // Image result processor
  7. private ImageReader mImageReader;
  8. // Active camera device connection
  9. private CameraDevice mCameraDevice;
  10. // Active camera capture session
  11. private CameraCaptureSession mCaptureSession;
  12. // Initialize a new camera device connection
  13. public void initializeCamera(Context context,
  14. Handler backgroundHandler,
  15. ImageReader.OnImageAvailableListener imageAvailableListener) {
  16. // Discover the camera instance
  17. CameraManager manager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
  18. String[] camIds = {};
  19. try {
  20. camIds = manager.getCameraIdList();
  21. } catch (CameraAccessException e) {
  22. Log.d(TAG, "Cam access exception getting IDs", e);
  23. }
  24. if (camIds.length < 1) {
  25. Log.d(TAG, "No cameras found");
  26. return;
  27. }
  28. String id = camIds[0];
  29. Log.d(TAG, "Using camera id " + id);
  30. // Initialize image processor
  31. mImageReader = ImageReader.newInstance(IMAGE_WIDTH, IMAGE_HEIGHT,
  32. ImageFormat.JPEG, MAX_IMAGES);
  33. mImageReader.setOnImageAvailableListener(imageAvailableListener, backgroundHandler);
  34. // Open the camera resource
  35. try {
  36. manager.openCamera(id, mStateCallback, backgroundHandler);
  37. } catch (CameraAccessException cae) {
  38. Log.d(TAG, "Camera access exception", cae);
  39. }
  40. }
  41. // Callback handling devices state changes
  42. private final CameraDevice.StateCallback mStateCallback =
  43. new CameraDevice.StateCallback() {
  44. @Override
  45. public void onOpened(CameraDevice cameraDevice) {
  46. mCameraDevice = cameraDevice;
  47. }
  48. ...
  49. };
  50. // Close the camera resources
  51. public void shutDown() {
  52. if (mCameraDevice != null) {
  53. mCameraDevice.close();
  54. }
  55. }
  56. }

Trigger an image capture

触发图像拍摄事件


Once the camera device connection is active, create a CameraCaptureSession to facilitate image requests from the hardware. To open a capture session:

一旦相机设备连接处于活动状态,创建一个 CameraCaptureSession 类来处理硬件拍摄图片的请求。开始一个拍摄会话:

  1. Build a new CameraCaptureSession instance with the createCaptureSession() method.

  2. 创建一个新的 CameraCaptureSession 实例并实现 createCaptureSession() 方法。

  3. Pass the method a list of potential target surfaces for individual image requests. For this example, pass the surface connected to the ImageReader constructed previously.

  4. 将一系列独立图像请求的预览所组成的列表传递给一个方法。在本例中,我们将这个预览列表传递给之前所构建的的 ImageReader

  5. Attach a CameraCaptureSession.StateCallback to report when the session is configured and active. The callback invokes the onConfigured() method if everything is successful.

  6. 当配置和激活成功后将会回调 CameraCaptureSession.StateCallback 中的 onConfigured() 方法。

  1. public class DoorbellCamera {
  2. ...
  3. public void takePicture() {
  4. if (mCameraDevice == null) {
  5. Log.w(TAG, "Cannot capture image. Camera not initialized.");
  6. return;
  7. }
  8. // Here, we create a CameraCaptureSession for capturing still images.
  9. try {
  10. mCameraDevice.createCaptureSession(
  11. Collections.singletonList(mImageReader.getSurface()),
  12. mSessionCallback,
  13. null);
  14. } catch (CameraAccessException cae) {
  15. Log.d(TAG, "access exception while preparing pic", cae);
  16. }
  17. }
  18. // Callback handling session state changes
  19. private CameraCaptureSession.StateCallback mSessionCallback =
  20. new CameraCaptureSession.StateCallback() {
  21. @Override
  22. public void onConfigured(CameraCaptureSession cameraCaptureSession) {
  23. // The camera is already closed
  24. if (mCameraDevice == null) {
  25. return;
  26. }
  27. // When the session is ready, we start capture.
  28. mCaptureSession = cameraCaptureSession;
  29. triggerImageCapture();
  30. }
  31. @Override
  32. public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
  33. Log.w(TAG, "Failed to configure camera");
  34. }
  35. };
  36. }

Your app can now use the active CameraCaptureSession to request image data from the camera hardware. To begin an image capture request inside the capture session:

现在,您的应用可以使用激活的 CameraCaptureSession 对象来请求相机硬件的图像数据。为了在图像会话中开始一个图像拍摄请求,您需要:

  1. Initialize a new CaptureRequest using the builder interface. To capture a single still image, use the TEMPLATE_STILL_CAPTURE parameter.

  2. 使用建造者接口初始化一个新的 CaptureRequest 实例,并通过设置 TEMPLATE_STILL_CAPTURE 参数来拍摄一张静态图像。

  3. Indicate the target surface for the request. For this example, this is the same ImageReader surface provided to the capture session.

  4. 指出请求的目标图像。在本例中,同获取图像预览和拍照会话是同一个 ImageReader

  5. Set any additional capture parameters, such as auto-focus and auto-exposure, using the request builder.

  6. 设置额外的拍摄参数,例如自动对焦和自动曝光。

  7. Initiate the capture request on the CameraCaptureSession using the capture() method.

  8. 使用 CameraCaptureSessioncapture() 方法发起拍摄请求。

  9. When the capture is complete, close the active session.

  10. 当拍摄完成,关闭会话。

  1. public class DoorbellCamera {
  2. // Active camera device connection
  3. private CameraDevice mCameraDevice;
  4. // Active camera capture session
  5. private CameraCaptureSession mCaptureSession;
  6. // Image result processor
  7. private ImageReader mImageReader;
  8. ...
  9. private void triggerImageCapture() {
  10. try {
  11. final CaptureRequest.Builder captureBuilder =
  12. mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
  13. captureBuilder.addTarget(mImageReader.getSurface());
  14. captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
  15. Log.d(TAG, "Session initialized.");
  16. mCaptureSession.capture(captureBuilder.build(), mCaptureCallback, null);
  17. } catch (CameraAccessException cae) {
  18. Log.d(TAG, "camera capture exception");
  19. }
  20. }
  21. // Callback handling capture progress events
  22. private final CameraCaptureSession.CaptureCallback mCaptureCallback =
  23. new CameraCaptureSession.CaptureCallback() {
  24. ...
  25. @Override
  26. public void onCaptureCompleted(CameraCaptureSession session,
  27. CaptureRequest request,
  28. TotalCaptureResult result) {
  29. if (session != null) {
  30. session.close();
  31. mCaptureSession = null;
  32. Log.d(TAG, "CaptureSession closed");
  33. }
  34. }
  35. };
  36. }

Handle the image result

处理图像结果


From your activity, initialize the camera and invoke the takePicture() method when the doorbell button is pressed. During capture, the camera hardware streams the image data to the provided ImageReader surface and invokes the OnImageAvailableListener with the result.

在您的界面,初始化相机并且当门铃按钮被按下时调用 takePicture() 方法。在拍照时,使用 ImageReader 对象预览相机图像数据并在 OnImageAvailableListener 监听方法中获取图片。

To obtain the image after capture completes:

为了在拍摄结束后获取图片,您需要:

  1. Obtain the latest Image from the ImageReader provided to the onImageAvailable() method.

  2. 使用 ImageReader 对象提供的 onImageAvailable() 方法获取最新的 Image

  3. Retrieve the JPEG-encoded image as a byte[] from the buffer returned by the getBuffer() method:

  4. 使用 getBuffer() 方法将缓冲区中返回的 JPEG 编码的图像转为 byte[] 格式。

  1. public class DoorbellActivity extends Activity {
  2. /**
  3. * Camera capture device wrapper
  4. */
  5. private DoorbellCamera mCamera;
  6. @Override
  7. protected void onCreate(Bundle savedInstanceState) {
  8. super.onCreate(savedInstanceState);
  9. ...
  10. mCamera = DoorbellCamera.getInstance();
  11. mCamera.initializeCamera(this, mCameraHandler, mOnImageAvailableListener);
  12. }
  13. @Override
  14. protected void onDestroy() {
  15. super.onDestroy();
  16. ...
  17. mCamera.shutDown();
  18. }
  19. @Override
  20. public boolean onKeyUp(int keyCode, KeyEvent event) {
  21. if (keyCode == KeyEvent.KEYCODE_ENTER) {
  22. // Doorbell rang!
  23. Log.d(TAG, "button pressed");
  24. mCamera.takePicture();
  25. return true;
  26. }
  27. return super.onKeyUp(keyCode, event);
  28. }
  29. // Callback to receive captured camera image data
  30. private ImageReader.OnImageAvailableListener mOnImageAvailableListener =
  31. new ImageReader.OnImageAvailableListener() {
  32. @Override
  33. public void onImageAvailable(ImageReader reader) {
  34. // Get the raw image bytes
  35. Image image = reader.acquireLatestImage();
  36. ByteBuffer imageBuf = image.getPlanes()[0].getBuffer();
  37. final byte[] imageBytes = new byte[imageBuf.remaining()];
  38. imageBuf.get(imageBytes);
  39. image.close();
  40. onPictureTaken(imageBytes);
  41. }
  42. };
  43. private void onPictureTaken(final byte[] imageBytes) {
  44. if (imageBytes != null) {
  45. // ...process the captured image...
  46. }
  47. }
  48. }