/* * Copyright 2017 The Android Things Samples Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.androidthings.imageclassifier; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.ImageFormat; 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.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.TotalCaptureResult; import android.hardware.camera2.params.StreamConfigurationMap; import android.media.ImageReader; import android.os.Handler; import android.support.annotation.NonNull; import android.util.Log; import android.util.Pair; import android.util.Size; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; public class CameraHandler { private static final String TAG = CameraHandler.class.getSimpleName(); private static final int MAX_IMAGES = 1; private CameraDevice mCameraDevice; private CameraCaptureSession mCaptureSession; private boolean initialized; private Size mImageDimensions; /** * An {@link ImageReader} that handles still image capture. */ private ImageReader mImageReader; // Lazy-loaded singleton, so only one instance of the camera is created. private CameraHandler() { } private static class InstanceHolder { private static CameraHandler mCamera = new CameraHandler(); } public static CameraHandler getInstance() { return InstanceHolder.mCamera; } /** * Initialize the camera device */ @SuppressLint("MissingPermission") public void initializeCamera(Context context, Handler backgroundHandler, Size minSize, ImageReader.OnImageAvailableListener imageAvailableListener) throws CameraAccessException { if (initialized) { throw new IllegalStateException( "CameraHandler is already initialized or is initializing"); } initialized = true; // Discover the camera instance CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); String camId = getCameraId(context); // Initialize the image processor with the largest available size. CameraCharacteristics characteristics = manager.getCameraCharacteristics(camId); StreamConfigurationMap map = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); Size bestSize = getBestCameraSize(map.getOutputSizes(ImageFormat.JPEG), minSize); if (bestSize == null) { throw new RuntimeException("We could not find a camera resolution that is larger than " + minSize.getWidth() + "x" + minSize.getHeight()); } mImageReader = ImageReader.newInstance(bestSize.getWidth(), bestSize.getHeight(), ImageFormat.JPEG, MAX_IMAGES); mImageDimensions = bestSize; Log.d(TAG, "Will capture photos that are " + mImageDimensions.getWidth() + " x " + mImageDimensions.getHeight()); mImageReader.setOnImageAvailableListener(imageAvailableListener, backgroundHandler); // Open the camera resource try { manager.openCamera(camId, mStateCallback, backgroundHandler); } catch (CameraAccessException cae) { Log.e(TAG, "Camera access exception", cae); } } public Size getImageDimensions() { return mImageDimensions; } /** * Begin a still image capture */ public void takePicture() { if (mCameraDevice == null) { Log.w(TAG, "Cannot capture image. Camera not initialized."); return; } // Create a CameraCaptureSession for capturing still images. try { mCameraDevice.createCaptureSession( Collections.singletonList(mImageReader.getSurface()), mSessionCallback, null); } catch (CameraAccessException cae) { Log.e(TAG, "Cannot create camera capture session", cae); } } /** * Execute a new capture request within the active session */ private void triggerImageCapture() { try { final CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); captureBuilder.addTarget(mImageReader.getSurface()); captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); Log.d(TAG, "Capture request created."); mCaptureSession.capture(captureBuilder.build(), mCaptureCallback, null); } catch (CameraAccessException cae) { Log.e(TAG, "Cannot trigger a capture request"); } } private void closeCaptureSession() { if (mCaptureSession != null) { try { mCaptureSession.close(); } catch (Exception ex) { Log.w(TAG, "Could not close capture session", ex); } mCaptureSession = null; } } /** * Close the camera resources */ public void shutDown() { try { closeCaptureSession(); if (mCameraDevice != null) { mCameraDevice.close(); } mImageReader.close(); } finally { initialized = false; } } public static String getCameraId(Context context) { CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); String[] camIds = null; try { camIds = manager.getCameraIdList(); } catch (CameraAccessException e) { Log.w(TAG, "Cannot get the list of available cameras", e); } if (camIds == null || camIds.length < 1) { Log.d(TAG, "No cameras found"); return null; } return camIds[0]; } /** * Helpful debugging method: Dump all supported camera formats to log. You don't need to run * this for normal operation, but it's very helpful when porting this code to different * hardware. */ public static void dumpFormatInfo(Context context) { // Discover the camera instance CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); String camId = getCameraId(context); if (camId == null) { return; } Log.d(TAG, "Using camera id " + camId); try { CameraCharacteristics characteristics = manager.getCameraCharacteristics(camId); StreamConfigurationMap configs = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); for (int format : configs.getOutputFormats()) { Log.d(TAG, "Getting sizes for format: " + format); for (Size s : configs.getOutputSizes(format)) { Log.d(TAG, "\t" + s.toString()); } } int[] effects = characteristics.get(CameraCharacteristics.CONTROL_AVAILABLE_EFFECTS); for (int effect : effects) { Log.d(TAG, "Effect available: " + effect); } } catch (CameraAccessException e) { Log.e(TAG, "Camera access exception getting characteristics."); } } /** * Callback handling device state changes */ private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice cameraDevice) { Log.d(TAG, "Opened camera."); mCameraDevice = cameraDevice; } @Override public void onDisconnected(@NonNull CameraDevice cameraDevice) { Log.d(TAG, "Camera disconnected, closing."); closeCaptureSession(); cameraDevice.close(); } @Override public void onError(@NonNull CameraDevice cameraDevice, int i) { Log.d(TAG, "Camera device error, closing."); closeCaptureSession(); cameraDevice.close(); } @Override public void onClosed(@NonNull CameraDevice cameraDevice) { Log.d(TAG, "Closed camera, releasing"); mCameraDevice = null; } }; /** * Callback handling session state changes */ private CameraCaptureSession.StateCallback mSessionCallback = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { // The camera is already closed if (mCameraDevice == null) { return; } // When the session is ready, we start capture. mCaptureSession = cameraCaptureSession; triggerImageCapture(); } @Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { Log.w(TAG, "Failed to configure camera"); } }; /** * Callback handling capture session events */ private final CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { Log.d(TAG, "Partial result"); } @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { session.close(); mCaptureSession = null; Log.d(TAG, "CaptureSession closed"); } }; static Size getBestCameraSize(Size[] availableCameraResolutions, Size minSize) { // This should select the closest size that is not too small Arrays.sort(availableCameraResolutions, new CompareSizesByArea()); // Sort by smallest first for (Size resolution : availableCameraResolutions) { if (resolution.getWidth() >= minSize.getWidth() && resolution.getHeight() >= minSize.getHeight()) { return resolution; } } return null; } /** * Compares two {@code Size}s based on their areas ascending. */ static class CompareSizesByArea implements Comparator<Size> { @Override public int compare(Size lhs, Size rhs) { // We cast here to ensure the multiplications won't overflow return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()); } } }