Home | History | Annotate | Download | only in testingcamera2
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.testingcamera2;
     18 
     19 import android.content.Context;
     20 import android.graphics.ImageFormat;
     21 import android.hardware.camera2.CameraAccessException;
     22 import android.hardware.camera2.CameraDevice;
     23 import android.hardware.camera2.CameraManager;
     24 import android.hardware.camera2.CameraMetadata;
     25 import android.hardware.camera2.CameraCharacteristics;
     26 import android.hardware.camera2.CaptureRequest;
     27 import android.hardware.camera2.Size;
     28 import android.media.Image;
     29 import android.media.ImageReader;
     30 import android.os.Handler;
     31 import android.os.HandlerThread;
     32 import android.os.Looper;
     33 import android.os.Message;
     34 import android.util.Log;
     35 import android.view.Surface;
     36 import android.view.SurfaceHolder;
     37 
     38 import com.android.ex.camera2.blocking.BlockingCameraManager;
     39 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
     40 import com.android.ex.camera2.blocking.BlockingStateListener;
     41 
     42 import java.util.ArrayList;
     43 import java.util.Arrays;
     44 import java.util.List;
     45 
     46 /**
     47  * A camera controller class that runs in its own thread, to
     48  * move camera ops off the UI. Generally thread-safe.
     49  */
     50 public class CameraOps {
     51 
     52     private static final String TAG = "CameraOps";
     53 
     54     private final HandlerThread mOpsThread;
     55     private final Handler mOpsHandler;
     56 
     57     private final CameraManager mCameraManager;
     58     private final BlockingCameraManager mBlockingCameraManager;
     59     private final BlockingStateListener mDeviceListener =
     60             new BlockingStateListener();
     61 
     62     private CameraDevice mCamera;
     63 
     64     private ImageReader mCaptureReader;
     65     private CameraCharacteristics mCameraCharacteristics;
     66 
     67     private int mEncodingBitRate;
     68 
     69     private CaptureRequest.Builder mPreviewRequestBuilder;
     70     private CaptureRequest.Builder mRecordingRequestBuilder;
     71     List<Surface> mOutputSurfaces = new ArrayList<Surface>(2);
     72     private Surface mPreviewSurface;
     73     // How many JPEG buffers do we want to hold on to at once
     74     private static final int MAX_CONCURRENT_JPEGS = 2;
     75 
     76     private static final int STATUS_ERROR = 0;
     77     private static final int STATUS_UNINITIALIZED = 1;
     78     private static final int STATUS_OK = 2;
     79     // low encoding bitrate(bps), used by small resolution like 640x480.
     80     private static final int ENC_BIT_RATE_LOW = 2000000;
     81     // high encoding bitrate(bps), used by large resolution like 1080p.
     82     private static final int ENC_BIT_RATE_HIGH = 10000000;
     83     private static final Size DEFAULT_SIZE = new Size(640, 480);
     84     private static final Size HIGH_RESOLUTION_SIZE = new Size(1920, 1080);
     85 
     86     private static final long IDLE_WAIT_MS = 2000;
     87     // General short wait timeout for most state transitions
     88     private static final long STATE_WAIT_MS = 500;
     89 
     90     private int mStatus = STATUS_UNINITIALIZED;
     91 
     92     CameraRecordingStream mRecordingStream;
     93 
     94     private void checkOk() {
     95         if (mStatus < STATUS_OK) {
     96             throw new IllegalStateException(String.format("Device not OK: %d", mStatus ));
     97         }
     98     }
     99 
    100     private CameraOps(Context ctx) throws ApiFailureException {
    101         mCameraManager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE);
    102         if (mCameraManager == null) {
    103             throw new ApiFailureException("Can't connect to camera manager!");
    104         }
    105         mBlockingCameraManager = new BlockingCameraManager(mCameraManager);
    106 
    107         mOpsThread = new HandlerThread("CameraOpsThread");
    108         mOpsThread.start();
    109         mOpsHandler = new Handler(mOpsThread.getLooper());
    110 
    111         mRecordingStream = new CameraRecordingStream();
    112         mStatus = STATUS_OK;
    113     }
    114 
    115     static public CameraOps create(Context ctx) throws ApiFailureException {
    116         return new CameraOps(ctx);
    117     }
    118 
    119     public String[] getDevices() throws ApiFailureException{
    120         checkOk();
    121         try {
    122             return mCameraManager.getCameraIdList();
    123         } catch (CameraAccessException e) {
    124             throw new ApiFailureException("Can't query device set", e);
    125         }
    126     }
    127 
    128     public void registerCameraListener(CameraManager.AvailabilityListener listener)
    129             throws ApiFailureException {
    130         checkOk();
    131         mCameraManager.addAvailabilityListener(listener, mOpsHandler);
    132     }
    133 
    134     public CameraCharacteristics getCameraCharacteristics() {
    135         checkOk();
    136         if (mCameraCharacteristics == null) {
    137             throw new IllegalStateException("CameraCharacteristics is not available");
    138         }
    139         return mCameraCharacteristics;
    140     }
    141 
    142     public void closeDevice()
    143             throws ApiFailureException {
    144         checkOk();
    145         mCameraCharacteristics = null;
    146 
    147         if (mCamera == null) return;
    148 
    149         try {
    150             mCamera.close();
    151         } catch (Exception e) {
    152             throw new ApiFailureException("can't close device!", e);
    153         }
    154 
    155         mCamera = null;
    156     }
    157 
    158     private void minimalOpenCamera() throws ApiFailureException {
    159         if (mCamera == null) {
    160             try {
    161                 String[] devices = mCameraManager.getCameraIdList();
    162                 if (devices == null || devices.length == 0) {
    163                     throw new ApiFailureException("no devices");
    164                 }
    165                 mCamera = mBlockingCameraManager.openCamera(devices[0],
    166                         mDeviceListener, mOpsHandler);
    167                 mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCamera.getId());
    168             } catch (CameraAccessException e) {
    169                 throw new ApiFailureException("open failure", e);
    170             } catch (BlockingOpenException e) {
    171                 throw new ApiFailureException("open async failure", e);
    172             }
    173         }
    174 
    175         mStatus = STATUS_OK;
    176     }
    177 
    178     private void configureOutputs(List<Surface> outputs) throws CameraAccessException {
    179         mCamera.configureOutputs(outputs);
    180         mDeviceListener.waitForState(BlockingStateListener.STATE_BUSY,
    181                 STATE_WAIT_MS);
    182         mDeviceListener.waitForState(BlockingStateListener.STATE_IDLE,
    183                 IDLE_WAIT_MS);
    184     }
    185 
    186     /**
    187      * Set up SurfaceView dimensions for camera preview
    188      */
    189     public void minimalPreviewConfig(SurfaceHolder previewHolder) throws ApiFailureException {
    190 
    191         minimalOpenCamera();
    192         try {
    193             CameraCharacteristics properties =
    194                     mCameraManager.getCameraCharacteristics(mCamera.getId());
    195 
    196             Size[] previewSizes = null;
    197             Size sz = DEFAULT_SIZE;
    198             if (properties != null) {
    199                 previewSizes = properties.get(
    200                         CameraCharacteristics.SCALER_AVAILABLE_PROCESSED_SIZES);
    201             }
    202 
    203             if (previewSizes != null && previewSizes.length != 0 &&
    204                     Arrays.asList(previewSizes).contains(HIGH_RESOLUTION_SIZE)) {
    205                 sz = HIGH_RESOLUTION_SIZE;
    206             }
    207             Log.i(TAG, "Set preview size to " + sz.toString());
    208             previewHolder.setFixedSize(sz.getWidth(), sz.getHeight());
    209             mPreviewSurface = previewHolder.getSurface();
    210         }  catch (CameraAccessException e) {
    211             throw new ApiFailureException("Error setting up minimal preview", e);
    212         }
    213     }
    214 
    215 
    216     /**
    217      * Update current preview with manual control inputs.
    218      */
    219     public void updatePreview(CameraControls manualCtrl) {
    220         updateCaptureRequest(mPreviewRequestBuilder, manualCtrl);
    221 
    222         try {
    223             // TODO: add capture result listener
    224             mCamera.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
    225         } catch (CameraAccessException e) {
    226             Log.e(TAG, "Update camera preview failed");
    227         }
    228     }
    229 
    230     /**
    231      * Configure streams and run minimal preview
    232      */
    233     public void minimalPreview(SurfaceHolder previewHolder) throws ApiFailureException {
    234 
    235         minimalOpenCamera();
    236 
    237         if (mPreviewSurface == null) {
    238             throw new ApiFailureException("Preview surface is not created");
    239         }
    240         try {
    241             List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
    242             outputSurfaces.add(mPreviewSurface);
    243 
    244             configureOutputs(outputSurfaces);
    245 
    246             CaptureRequest.Builder previewBuilder;
    247             mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    248 
    249             mPreviewRequestBuilder.addTarget(mPreviewSurface);
    250 
    251             mCamera.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
    252         } catch (CameraAccessException e) {
    253             throw new ApiFailureException("Error setting up minimal preview", e);
    254         }
    255     }
    256 
    257     public void minimalJpegCapture(final CaptureListener listener, CaptureResultListener l,
    258             Handler h, CameraControls cameraControl) throws ApiFailureException {
    259         minimalOpenCamera();
    260 
    261         try {
    262             CameraCharacteristics properties =
    263                     mCameraManager.getCameraCharacteristics(mCamera.getId());
    264             Size[] jpegSizes = null;
    265             if (properties != null) {
    266                 jpegSizes = properties.get(
    267                         CameraCharacteristics.SCALER_AVAILABLE_JPEG_SIZES);
    268             }
    269             int width = 640;
    270             int height = 480;
    271 
    272             if (jpegSizes != null && jpegSizes.length > 0) {
    273                 width = jpegSizes[0].getWidth();
    274                 height = jpegSizes[0].getHeight();
    275             }
    276 
    277             if (mCaptureReader == null || mCaptureReader.getWidth() != width ||
    278                     mCaptureReader.getHeight() != height) {
    279                 if (mCaptureReader != null) {
    280                     mCaptureReader.close();
    281                 }
    282                 mCaptureReader = ImageReader.newInstance(width, height,
    283                         ImageFormat.JPEG, MAX_CONCURRENT_JPEGS);
    284             }
    285 
    286             List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
    287             outputSurfaces.add(mCaptureReader.getSurface());
    288 
    289             configureOutputs(outputSurfaces);
    290 
    291             CaptureRequest.Builder captureBuilder =
    292                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
    293 
    294             captureBuilder.addTarget(mCaptureReader.getSurface());
    295 
    296             updateCaptureRequest(captureBuilder, cameraControl);
    297 
    298             ImageReader.OnImageAvailableListener readerListener =
    299                     new ImageReader.OnImageAvailableListener() {
    300                 @Override
    301                 public void onImageAvailable(ImageReader reader) {
    302                     Image i = null;
    303                     try {
    304                         i = reader.acquireNextImage();
    305                         listener.onCaptureAvailable(i);
    306                     } finally {
    307                         if (i != null) {
    308                             i.close();
    309                         }
    310                     }
    311                 }
    312             };
    313             mCaptureReader.setOnImageAvailableListener(readerListener, h);
    314 
    315             mCamera.capture(captureBuilder.build(), l, mOpsHandler);
    316         } catch (CameraAccessException e) {
    317             throw new ApiFailureException("Error in minimal JPEG capture", e);
    318         }
    319     }
    320 
    321     public void startRecording(boolean useMediaCodec) throws ApiFailureException {
    322         minimalOpenCamera();
    323         Size recordingSize = getRecordingSize();
    324         try {
    325             if (mRecordingRequestBuilder == null) {
    326                 mRecordingRequestBuilder =
    327                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
    328             }
    329             // Setup output stream first
    330             mRecordingStream.configure(recordingSize, useMediaCodec, mEncodingBitRate);
    331             mRecordingStream.onConfiguringOutputs(mOutputSurfaces, /* detach */false);
    332             mRecordingStream.onConfiguringRequest(mRecordingRequestBuilder, /* detach */false);
    333 
    334             // TODO: For preview, create preview stream class, and do the same thing like recording.
    335             mOutputSurfaces.add(mPreviewSurface);
    336             mRecordingRequestBuilder.addTarget(mPreviewSurface);
    337 
    338             // Start camera streaming and recording.
    339             configureOutputs(mOutputSurfaces);
    340             mCamera.setRepeatingRequest(mRecordingRequestBuilder.build(), null, null);
    341             mRecordingStream.start();
    342         } catch (CameraAccessException e) {
    343             throw new ApiFailureException("Error start recording", e);
    344         }
    345     }
    346 
    347     public void stopRecording() throws ApiFailureException {
    348         try {
    349             /**
    350              * <p>
    351              * Only stop camera recording stream.
    352              * </p>
    353              * <p>
    354              * FIXME: There is a race condition to be fixed in CameraDevice.
    355              * Basically, when stream closes, encoder and its surface is
    356              * released, while it still takes some time for camera to finish the
    357              * output to that surface. Then it cause camera in bad state.
    358              * </p>
    359              */
    360             mRecordingStream.onConfiguringRequest(mRecordingRequestBuilder, /* detach */true);
    361             mRecordingStream.onConfiguringOutputs(mOutputSurfaces, /* detach */true);
    362 
    363             // Remove recording surface before calling RecordingStream.stop,
    364             // since that invalidates the surface.
    365             configureOutputs(mOutputSurfaces);
    366 
    367             mRecordingStream.stop();
    368 
    369             mCamera.setRepeatingRequest(mRecordingRequestBuilder.build(), null, null);
    370         } catch (CameraAccessException e) {
    371             throw new ApiFailureException("Error stop recording", e);
    372         }
    373     }
    374 
    375     private Size getRecordingSize() throws ApiFailureException {
    376         try {
    377             CameraCharacteristics properties =
    378                     mCameraManager.getCameraCharacteristics(mCamera.getId());
    379 
    380             Size[] recordingSizes = null;
    381             if (properties != null) {
    382                 recordingSizes = properties.get(
    383                         CameraCharacteristics.SCALER_AVAILABLE_PROCESSED_SIZES);
    384             }
    385 
    386             mEncodingBitRate = ENC_BIT_RATE_LOW;
    387             if (recordingSizes == null || recordingSizes.length == 0) {
    388                 Log.w(TAG, "Unable to get recording sizes, default to 640x480");
    389                 return DEFAULT_SIZE;
    390             } else {
    391                 /**
    392                  * TODO: create resolution selection widget on UI, then use the
    393                  * select size. For now, return HIGH_RESOLUTION_SIZE if it
    394                  * exists in the processed size list, otherwise return default
    395                  * size
    396                  */
    397                 if (Arrays.asList(recordingSizes).contains(HIGH_RESOLUTION_SIZE)) {
    398                     mEncodingBitRate = ENC_BIT_RATE_HIGH;
    399                     return HIGH_RESOLUTION_SIZE;
    400                 } else {
    401                     // Fallback to default size when HD size is not found.
    402                     Log.w(TAG,
    403                             "Unable to find the requested size " + HIGH_RESOLUTION_SIZE.toString()
    404                             + " Fallback to " + DEFAULT_SIZE.toString());
    405                     return DEFAULT_SIZE;
    406                 }
    407             }
    408         } catch (CameraAccessException e) {
    409             throw new ApiFailureException("Error setting up video recording", e);
    410         }
    411     }
    412 
    413     private void updateCaptureRequest(CaptureRequest.Builder builder, CameraControls cameraControl) {
    414         if (cameraControl != null) {
    415             // Update the manual control metadata for capture request
    416             // Disable 3A routines.
    417             if (cameraControl.isManualControlEnabled()) {
    418                 Log.e(TAG, "update request: " + cameraControl.getSensitivity());
    419                 builder.set(CaptureRequest.CONTROL_MODE,
    420                         CameraMetadata.CONTROL_MODE_OFF);
    421                 builder.set(CaptureRequest.SENSOR_SENSITIVITY,
    422                         cameraControl.getSensitivity());
    423                 builder.set(CaptureRequest.SENSOR_FRAME_DURATION,
    424                         cameraControl.getFrameDuration());
    425                 builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME,
    426                         cameraControl.getExposure());
    427             } else {
    428                 builder.set(CaptureRequest.CONTROL_MODE,
    429                         CameraMetadata.CONTROL_MODE_AUTO);
    430             }
    431         }
    432     }
    433 
    434     public interface CaptureListener {
    435         void onCaptureAvailable(Image capture);
    436     }
    437 
    438     public static abstract class CaptureResultListener extends CameraDevice.CaptureListener {}
    439 }
    440