CameraOps.java revision d7d287b826fb83916ba526027719eb39a33ec872
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 17package com.android.testingcamera2; 18 19import android.content.Context; 20import android.graphics.ImageFormat; 21import android.hardware.camera2.CameraAccessException; 22import android.hardware.camera2.CameraDevice; 23import android.hardware.camera2.CameraManager; 24import android.hardware.camera2.CameraMetadata; 25import android.hardware.camera2.CameraCharacteristics; 26import android.hardware.camera2.CaptureRequest; 27import android.hardware.camera2.Size; 28import android.media.Image; 29import android.media.ImageReader; 30import android.os.Handler; 31import android.os.Looper; 32import android.os.Message; 33import android.util.Log; 34import android.view.Surface; 35import android.view.SurfaceHolder; 36 37import com.android.ex.camera2.blocking.BlockingCameraManager; 38import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException; 39 40import java.util.ArrayList; 41import java.util.Arrays; 42import java.util.List; 43 44/** 45 * A camera controller class that runs in its own thread, to 46 * move camera ops off the UI. Generally thread-safe. 47 */ 48public class CameraOps { 49 50 private static final String TAG = "CameraOps"; 51 52 private final Thread mOpsThread; 53 private Handler mOpsHandler; 54 55 private final CameraManager mCameraManager; 56 private final BlockingCameraManager mBlockingCameraManager; 57 private CameraDevice mCamera; 58 59 private ImageReader mCaptureReader; 60 private CameraCharacteristics mCameraCharacteristics; 61 62 private int mEncodingBitRate; 63 64 private CaptureRequest.Builder mPreviewRequestBuilder; 65 private CaptureRequest.Builder mRecordingRequestBuilder; 66 List<Surface> mOutputSurfaces = new ArrayList<Surface>(2); 67 private Surface mPreviewSurface; 68 // How many JPEG buffers do we want to hold on to at once 69 private static final int MAX_CONCURRENT_JPEGS = 2; 70 71 private static final int STATUS_ERROR = 0; 72 private static final int STATUS_UNINITIALIZED = 1; 73 private static final int STATUS_OK = 2; 74 // low encoding bitrate(bps), used by small resolution like 640x480. 75 private static final int ENC_BIT_RATE_LOW = 2000000; 76 // high encoding bitrate(bps), used by large resolution like 1080p. 77 private static final int ENC_BIT_RATE_HIGH = 10000000; 78 private static final Size DEFAULT_SIZE = new Size(640, 480); 79 private static final Size HIGH_RESOLUTION_SIZE = new Size(1920, 1080); 80 81 private int mStatus = STATUS_UNINITIALIZED; 82 83 CameraRecordingStream mRecordingStream; 84 85 private void checkOk() { 86 if (mStatus < STATUS_OK) { 87 throw new IllegalStateException(String.format("Device not OK: %d", mStatus )); 88 } 89 } 90 91 private class OpsHandler extends Handler { 92 @Override 93 public void handleMessage(Message msg) { 94 95 } 96 } 97 98 private CameraOps(Context ctx) throws ApiFailureException { 99 mCameraManager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE); 100 if (mCameraManager == null) { 101 throw new ApiFailureException("Can't connect to camera manager!"); 102 } 103 mBlockingCameraManager = new BlockingCameraManager(mCameraManager); 104 105 mOpsThread = new Thread(new Runnable() { 106 @Override 107 public void run() { 108 Looper.prepare(); 109 mOpsHandler = new OpsHandler(); 110 Looper.loop(); 111 } 112 }, "CameraOpsThread"); 113 mOpsThread.start(); 114 115 mRecordingStream = new CameraRecordingStream(); 116 mStatus = STATUS_OK; 117 } 118 119 static public CameraOps create(Context ctx) throws ApiFailureException { 120 return new CameraOps(ctx); 121 } 122 123 public String[] getDevices() throws ApiFailureException{ 124 checkOk(); 125 try { 126 return mCameraManager.getCameraIdList(); 127 } catch (CameraAccessException e) { 128 throw new ApiFailureException("Can't query device set", e); 129 } 130 } 131 132 public void registerCameraListener(CameraManager.AvailabilityListener listener) 133 throws ApiFailureException { 134 checkOk(); 135 mCameraManager.addAvailabilityListener(listener, mOpsHandler); 136 } 137 138 public CameraCharacteristics getCameraCharacteristics() { 139 checkOk(); 140 if (mCameraCharacteristics == null) { 141 throw new IllegalStateException("CameraCharacteristics is not available"); 142 } 143 return mCameraCharacteristics; 144 } 145 146 public void closeDevice() 147 throws ApiFailureException { 148 checkOk(); 149 mCameraCharacteristics = null; 150 151 if (mCamera == null) return; 152 153 try { 154 mCamera.close(); 155 } catch (Exception e) { 156 throw new ApiFailureException("can't close device!", e); 157 } 158 159 mCamera = null; 160 } 161 162 private void minimalOpenCamera() throws ApiFailureException { 163 if (mCamera == null) { 164 try { 165 String[] devices = mCameraManager.getCameraIdList(); 166 if (devices == null || devices.length == 0) { 167 throw new ApiFailureException("no devices"); 168 } 169 mCamera = mBlockingCameraManager.openCamera(devices[0], 170 /*listener*/null, mOpsHandler); 171 mCameraCharacteristics = mCamera.getProperties(); 172 } catch (CameraAccessException e) { 173 throw new ApiFailureException("open failure", e); 174 } catch (BlockingOpenException e) { 175 throw new ApiFailureException("open async failure", e); 176 } 177 } 178 179 mStatus = STATUS_OK; 180 } 181 182 /** 183 * Set up SurfaceView dimensions for camera preview 184 */ 185 public void minimalPreviewConfig(SurfaceHolder previewHolder) throws ApiFailureException { 186 187 minimalOpenCamera(); 188 try { 189 CameraCharacteristics properties = mCamera.getProperties(); 190 191 Size[] previewSizes = null; 192 Size sz = DEFAULT_SIZE; 193 if (properties != null) { 194 previewSizes = properties.get( 195 CameraCharacteristics.SCALER_AVAILABLE_PROCESSED_SIZES); 196 } 197 198 if (previewSizes != null && previewSizes.length != 0 && 199 Arrays.asList(previewSizes).contains(HIGH_RESOLUTION_SIZE)) { 200 sz = HIGH_RESOLUTION_SIZE; 201 } 202 Log.i(TAG, "Set preview size to " + sz.toString()); 203 previewHolder.setFixedSize(sz.getWidth(), sz.getHeight()); 204 mPreviewSurface = previewHolder.getSurface(); 205 } catch (CameraAccessException e) { 206 throw new ApiFailureException("Error setting up minimal preview", e); 207 } 208 } 209 210 211 /** 212 * Update current preview with manual control inputs. 213 */ 214 public void updatePreview(CameraControls manualCtrl) { 215 updateCaptureRequest(mPreviewRequestBuilder, manualCtrl); 216 217 try { 218 // TODO: add capture result listener 219 mCamera.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null); 220 } catch (CameraAccessException e) { 221 Log.e(TAG, "Update camera preview failed"); 222 } 223 } 224 225 /** 226 * Configure streams and run minimal preview 227 */ 228 public void minimalPreview(SurfaceHolder previewHolder) throws ApiFailureException { 229 230 minimalOpenCamera(); 231 if (mPreviewSurface == null) { 232 throw new ApiFailureException("Preview surface is not created"); 233 } 234 try { 235 mCamera.stopRepeating(); 236 mCamera.waitUntilIdle(); 237 238 List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1); 239 outputSurfaces.add(mPreviewSurface); 240 241 mCamera.configureOutputs(outputSurfaces); 242 243 CaptureRequest.Builder previewBuilder; 244 mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 245 246 mPreviewRequestBuilder.addTarget(mPreviewSurface); 247 248 mCamera.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null); 249 } catch (CameraAccessException e) { 250 throw new ApiFailureException("Error setting up minimal preview", e); 251 } 252 } 253 254 public void minimalJpegCapture(final CaptureListener listener, CaptureResultListener l, 255 Handler h, CameraControls cameraControl) throws ApiFailureException { 256 minimalOpenCamera(); 257 258 try { 259 mCamera.stopRepeating(); 260 mCamera.waitUntilIdle(); 261 262 CameraCharacteristics properties = mCamera.getProperties(); 263 Size[] jpegSizes = null; 264 if (properties != null) { 265 jpegSizes = properties.get( 266 CameraCharacteristics.SCALER_AVAILABLE_JPEG_SIZES); 267 } 268 int width = 640; 269 int height = 480; 270 271 if (jpegSizes != null && jpegSizes.length > 0) { 272 width = jpegSizes[0].getWidth(); 273 height = jpegSizes[0].getHeight(); 274 } 275 276 if (mCaptureReader == null || mCaptureReader.getWidth() != width || 277 mCaptureReader.getHeight() != height) { 278 if (mCaptureReader != null) { 279 mCaptureReader.close(); 280 } 281 mCaptureReader = ImageReader.newInstance(width, height, 282 ImageFormat.JPEG, MAX_CONCURRENT_JPEGS); 283 } 284 285 List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1); 286 outputSurfaces.add(mCaptureReader.getSurface()); 287 288 mCamera.configureOutputs(outputSurfaces); 289 290 CaptureRequest.Builder captureBuilder = 291 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); 292 293 captureBuilder.addTarget(mCaptureReader.getSurface()); 294 295 updateCaptureRequest(captureBuilder, cameraControl); 296 297 ImageReader.OnImageAvailableListener readerListener = 298 new ImageReader.OnImageAvailableListener() { 299 @Override 300 public void onImageAvailable(ImageReader reader) { 301 Image i = null; 302 try { 303 i = reader.acquireNextImage(); 304 listener.onCaptureAvailable(i); 305 } finally { 306 if (i != null) { 307 i.close(); 308 } 309 } 310 } 311 }; 312 mCaptureReader.setOnImageAvailableListener(readerListener, h); 313 314 mCamera.capture(captureBuilder.build(), l, mOpsHandler); 315 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 mCamera.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 mCamera.stopRepeating(); 363 mCamera.waitUntilIdle(); 364 mRecordingStream.stop(); 365 366 mCamera.configureOutputs(mOutputSurfaces); 367 mCamera.setRepeatingRequest(mRecordingRequestBuilder.build(), null, null); 368 } catch (CameraAccessException e) { 369 throw new ApiFailureException("Error stop recording", e); 370 } 371 } 372 373 private Size getRecordingSize() throws ApiFailureException { 374 try { 375 CameraCharacteristics properties = mCamera.getProperties(); 376 377 Size[] recordingSizes = null; 378 if (properties != null) { 379 recordingSizes = properties.get( 380 CameraCharacteristics.SCALER_AVAILABLE_PROCESSED_SIZES); 381 } 382 383 mEncodingBitRate = ENC_BIT_RATE_LOW; 384 if (recordingSizes == null || recordingSizes.length == 0) { 385 Log.w(TAG, "Unable to get recording sizes, default to 640x480"); 386 return DEFAULT_SIZE; 387 } else { 388 /** 389 * TODO: create resolution selection widget on UI, then use the 390 * select size. For now, return HIGH_RESOLUTION_SIZE if it 391 * exists in the processed size list, otherwise return default 392 * size 393 */ 394 if (Arrays.asList(recordingSizes).contains(HIGH_RESOLUTION_SIZE)) { 395 mEncodingBitRate = ENC_BIT_RATE_HIGH; 396 return HIGH_RESOLUTION_SIZE; 397 } else { 398 // Fallback to default size when HD size is not found. 399 Log.w(TAG, 400 "Unable to find the requested size " + HIGH_RESOLUTION_SIZE.toString() 401 + " Fallback to " + DEFAULT_SIZE.toString()); 402 return DEFAULT_SIZE; 403 } 404 } 405 } catch (CameraAccessException e) { 406 throw new ApiFailureException("Error setting up video recording", e); 407 } 408 } 409 410 private void updateCaptureRequest(CaptureRequest.Builder builder, CameraControls cameraControl) { 411 if (cameraControl != null) { 412 // Update the manual control metadata for capture request 413 // Disable 3A routines. 414 if (cameraControl.isManualControlEnabled()) { 415 Log.e(TAG, "update request: " + cameraControl.getSensitivity()); 416 builder.set(CaptureRequest.CONTROL_MODE, 417 CameraMetadata.CONTROL_MODE_OFF); 418 builder.set(CaptureRequest.SENSOR_SENSITIVITY, 419 cameraControl.getSensitivity()); 420 builder.set(CaptureRequest.SENSOR_FRAME_DURATION, 421 cameraControl.getFrameDuration()); 422 builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, 423 cameraControl.getExposure()); 424 } else { 425 builder.set(CaptureRequest.CONTROL_MODE, 426 CameraMetadata.CONTROL_MODE_AUTO); 427 } 428 } 429 } 430 431 public interface CaptureListener { 432 void onCaptureAvailable(Image capture); 433 } 434 435 public static abstract class CaptureResultListener extends CameraDevice.CaptureListener {} 436} 437