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.v1; 18 19 import android.content.Context; 20 import android.graphics.ImageFormat; 21 import android.hardware.camera2.CameraAccessException; 22 import android.hardware.camera2.CameraCaptureSession; 23 import android.hardware.camera2.CameraDevice; 24 import android.hardware.camera2.CameraManager; 25 import android.hardware.camera2.CameraMetadata; 26 import android.hardware.camera2.CameraCharacteristics; 27 import android.hardware.camera2.CaptureRequest; 28 import android.hardware.camera2.CaptureRequest.Builder; 29 import android.hardware.camera2.params.OutputConfiguration; 30 import android.util.Size; 31 import android.media.Image; 32 import android.media.ImageReader; 33 import android.media.MediaCodec; 34 import android.os.ConditionVariable; 35 import android.os.Handler; 36 import android.os.HandlerThread; 37 import android.util.Log; 38 import android.util.Size; 39 import android.view.Surface; 40 import android.view.SurfaceHolder; 41 42 import com.android.ex.camera2.blocking.BlockingCameraManager; 43 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException; 44 import com.android.ex.camera2.blocking.BlockingStateCallback; 45 import com.android.ex.camera2.blocking.BlockingSessionCallback; 46 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.List; 50 import java.util.Set; 51 52 /** 53 * A camera controller class that runs in its own thread, to 54 * move camera ops off the UI. Generally thread-safe. 55 */ 56 public class CameraOps { 57 58 public static interface Listener { onCameraOpened(String cameraId, CameraCharacteristics characteristics)59 void onCameraOpened(String cameraId, CameraCharacteristics characteristics); 60 } 61 62 private static final String TAG = "CameraOps"; 63 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 64 65 private final HandlerThread mOpsThread; 66 private final Handler mOpsHandler; 67 68 private final CameraManager mCameraManager; 69 private final BlockingCameraManager mBlockingCameraManager; 70 private final BlockingStateCallback mDeviceListener = 71 new BlockingStateCallback(); 72 73 private String mCameraId; 74 private CameraDevice mCamera; 75 private CameraCaptureSession mSession; 76 77 private ImageReader mCaptureReader; 78 private CameraCharacteristics mCameraCharacteristics; 79 80 private int mEncodingBitRate; 81 private int mDeviceOrientation; 82 83 private CaptureRequest.Builder mPreviewRequestBuilder; 84 private CaptureRequest.Builder mRecordingRequestBuilder; 85 List<Surface> mOutputSurfaces = new ArrayList<Surface>(2); 86 private Surface mPreviewSurface; 87 private Surface mPreviewSurface2; 88 // How many still capture buffers do we want to hold on to at once 89 private static final int MAX_CONCURRENT_STILL_CAPTURES = 2; 90 91 private static final int STATUS_ERROR = 0; 92 private static final int STATUS_UNINITIALIZED = 1; 93 private static final int STATUS_OK = 2; 94 // low encoding bitrate(bps), used by small resolution like 640x480. 95 private static final int ENC_BIT_RATE_LOW = 2000000; 96 // high encoding bitrate(bps), used by large resolution like 1080p. 97 private static final int ENC_BIT_RATE_HIGH = 10000000; 98 private static final Size DEFAULT_SIZE = new Size(640, 480); 99 private static final Size HIGH_RESOLUTION_SIZE = new Size(1920, 1080); 100 101 private static final long IDLE_WAIT_MS = 2000; 102 // General short wait timeout for most state transitions 103 private static final long STATE_WAIT_MS = 500; 104 105 private int mStatus = STATUS_UNINITIALIZED; 106 107 CameraRecordingStream mRecordingStream; 108 private final Listener mListener; 109 private final Handler mListenerHandler; 110 111 // Physical camera id of the current logical multi-camera. "" if this is not a logical 112 // multi-camera. 113 private String mPhysicalCameraId1; 114 private String mPhysicalCameraId2; 115 checkOk()116 private void checkOk() { 117 if (mStatus < STATUS_OK) { 118 throw new IllegalStateException(String.format("Device not OK: %d", mStatus )); 119 } 120 } 121 CameraOps(Context ctx, Listener listener, Handler handler)122 private CameraOps(Context ctx, Listener listener, Handler handler) throws ApiFailureException { 123 mCameraManager = (CameraManager) ctx.getSystemService(Context.CAMERA_SERVICE); 124 if (mCameraManager == null) { 125 throw new ApiFailureException("Can't connect to camera manager!"); 126 } 127 mBlockingCameraManager = new BlockingCameraManager(mCameraManager); 128 129 mOpsThread = new HandlerThread("CameraOpsThread"); 130 mOpsThread.start(); 131 mOpsHandler = new Handler(mOpsThread.getLooper()); 132 133 mRecordingStream = new CameraRecordingStream(); 134 mStatus = STATUS_OK; 135 136 mListener = listener; 137 mListenerHandler = handler; 138 } 139 create(Context ctx, Listener listener, Handler handler)140 static public CameraOps create(Context ctx, Listener listener, Handler handler) 141 throws ApiFailureException { 142 return new CameraOps(ctx, listener, handler); 143 } 144 getDevices()145 public String[] getDevices() throws ApiFailureException{ 146 checkOk(); 147 try { 148 return mCameraManager.getCameraIdList(); 149 } catch (CameraAccessException e) { 150 throw new ApiFailureException("Can't query device set", e); 151 } 152 } 153 registerCameraListener(CameraManager.AvailabilityCallback listener)154 public void registerCameraListener(CameraManager.AvailabilityCallback listener) 155 throws ApiFailureException { 156 checkOk(); 157 mCameraManager.registerAvailabilityCallback(listener, mOpsHandler); 158 } 159 getCameraCharacteristics()160 public CameraCharacteristics getCameraCharacteristics() { 161 checkOk(); 162 if (mCameraCharacteristics == null) { 163 throw new IllegalStateException("CameraCharacteristics is not available"); 164 } 165 return mCameraCharacteristics; 166 } 167 closeDevice()168 public void closeDevice() 169 throws ApiFailureException { 170 checkOk(); 171 mCameraCharacteristics = null; 172 173 if (mCamera == null) return; 174 175 try { 176 mCamera.close(); 177 } catch (Exception e) { 178 throw new ApiFailureException("can't close device!", e); 179 } 180 181 mCamera = null; 182 mSession = null; 183 } 184 minimalOpenCamera()185 private void minimalOpenCamera() throws ApiFailureException { 186 // Open camera if not yet opened, or the currently opened camera is not the right one. 187 if (mCamera == null || !mCameraId.equals(mCamera.getId())) { 188 closeDevice(); 189 190 mPhysicalCameraId1 = ""; 191 mPhysicalCameraId2 = ""; 192 final CameraCharacteristics characteristics; 193 try { 194 CameraCharacteristics c = mCameraManager.getCameraCharacteristics(mCameraId); 195 int[] caps = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); 196 for (int cap : caps) { 197 if (CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA 198 != cap) { 199 continue; 200 } 201 202 Set<String> physicalIds = c.getPhysicalCameraIds(); 203 if (physicalIds.size() != 2) { 204 throw new ApiFailureException( 205 "3 or more physical cameras are not yet supported"); 206 } 207 String[] physicalIdsArray = physicalIds.toArray(new String[2]); 208 mPhysicalCameraId1 = physicalIdsArray[0]; 209 mPhysicalCameraId2 = physicalIdsArray[1]; 210 break; 211 } 212 Log.i(TAG, "Opening " + mCameraId); 213 mCamera = mBlockingCameraManager.openCamera(mCameraId, 214 mDeviceListener, mOpsHandler); 215 mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId); 216 characteristics = mCameraCharacteristics; 217 } catch (CameraAccessException e) { 218 throw new ApiFailureException("open failure", e); 219 } catch (BlockingOpenException e) { 220 throw new ApiFailureException("open async failure", e); 221 } 222 223 // Dispatch listener event 224 if (mListener != null && mListenerHandler != null) { 225 mListenerHandler.post(new Runnable() { 226 @Override 227 public void run() { 228 mListener.onCameraOpened(mCameraId, characteristics); 229 } 230 }); 231 } 232 } 233 234 mStatus = STATUS_OK; 235 } 236 configureOutputs(List<Surface> outputs)237 private void configureOutputs(List<Surface> outputs) throws CameraAccessException { 238 BlockingSessionCallback sessionListener = new BlockingSessionCallback(); 239 mCamera.createCaptureSession(outputs, sessionListener, mOpsHandler); 240 mSession = sessionListener.waitAndGetSession(IDLE_WAIT_MS); 241 } 242 configureOutputsByConfigs(List<OutputConfiguration> outputConfigs)243 private void configureOutputsByConfigs(List<OutputConfiguration> outputConfigs) 244 throws CameraAccessException { 245 BlockingSessionCallback sessionListener = new BlockingSessionCallback(); 246 mCamera.createCaptureSessionByOutputConfigurations(outputConfigs, sessionListener, mOpsHandler); 247 mSession = sessionListener.waitAndGetSession(IDLE_WAIT_MS); 248 } 249 250 /** 251 * Set up SurfaceView dimensions for camera preview 252 */ minimalPreviewConfig(String cameraId, SurfaceHolder previewHolder, SurfaceHolder previewHolder2)253 public void minimalPreviewConfig(String cameraId, SurfaceHolder previewHolder, 254 SurfaceHolder previewHolder2) throws ApiFailureException { 255 256 mCameraId = cameraId; 257 minimalOpenCamera(); 258 try { 259 CameraCharacteristics properties = 260 mCameraManager.getCameraCharacteristics(mCamera.getId()); 261 262 Size[] previewSizes = null; 263 Size sz = DEFAULT_SIZE; 264 if (properties != null) { 265 previewSizes = 266 properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP). 267 getOutputSizes(previewHolder.getClass()); 268 } 269 270 if (previewSizes != null && previewSizes.length != 0 && 271 Arrays.asList(previewSizes).contains(HIGH_RESOLUTION_SIZE)) { 272 sz = HIGH_RESOLUTION_SIZE; 273 } 274 Log.i(TAG, "Set preview size to " + sz.toString()); 275 previewHolder.setFixedSize(sz.getWidth(), sz.getHeight()); 276 previewHolder2.setFixedSize(sz.getWidth(), sz.getHeight()); 277 mPreviewSurface = previewHolder.getSurface(); 278 mPreviewSurface2 = previewHolder2.getSurface(); 279 } catch (CameraAccessException e) { 280 throw new ApiFailureException("Error setting up minimal preview", e); 281 } 282 } 283 284 285 /** 286 * Update current preview with user-specified control inputs. 287 */ updatePreview(CameraControls controls)288 public void updatePreview(CameraControls controls) { 289 if (VERBOSE) { 290 Log.v(TAG, "updatePreview - begin"); 291 } 292 293 updateCaptureRequest(mPreviewRequestBuilder, controls); 294 295 try { 296 // Insert a one-time request if any triggers were set into the request 297 if (hasTriggers(mPreviewRequestBuilder)) { 298 mSession.capture(mPreviewRequestBuilder.build(), /*listener*/null, /*handler*/null); 299 removeTriggers(mPreviewRequestBuilder); 300 301 if (VERBOSE) { 302 Log.v(TAG, "updatePreview - submitted extra one-shot capture with triggers"); 303 } 304 } else { 305 if (VERBOSE) { 306 Log.v(TAG, "updatePreview - no triggers, regular repeating request"); 307 } 308 } 309 310 // TODO: add capture result listener 311 mSession.setRepeatingRequest(mPreviewRequestBuilder.build(), 312 /*listener*/null, /*handler*/null); 313 } catch (CameraAccessException e) { 314 Log.e(TAG, "Update camera preview failed"); 315 } 316 317 if (VERBOSE) { 318 Log.v(TAG, "updatePreview - end"); 319 } 320 } 321 hasTriggers(Builder requestBuilder)322 private static boolean hasTriggers(Builder requestBuilder) { 323 if (requestBuilder == null) { 324 return false; 325 } 326 327 Integer afTrigger = requestBuilder.get(CaptureRequest.CONTROL_AF_TRIGGER); 328 Integer aePrecaptureTrigger = requestBuilder.get( 329 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER); 330 331 if (VERBOSE) { 332 Log.v(TAG, String.format("hasTriggers - afTrigger = %s, aePreCaptureTrigger = %s", 333 afTrigger, aePrecaptureTrigger)); 334 } 335 336 337 if (afTrigger != null && afTrigger != CaptureRequest.CONTROL_AF_TRIGGER_IDLE) { 338 return true; 339 } 340 341 342 if (aePrecaptureTrigger != null 343 && aePrecaptureTrigger != CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE) { 344 return true; 345 } 346 347 return false; 348 } 349 removeTriggers(Builder requestBuilder)350 private static void removeTriggers(Builder requestBuilder) { 351 if (requestBuilder == null) { 352 return; 353 } 354 355 requestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 356 CaptureRequest.CONTROL_AF_TRIGGER_IDLE); 357 requestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, 358 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE); 359 } 360 361 /** 362 * Update current device orientation (0~360 degrees) 363 */ updateOrientation(int orientation)364 public void updateOrientation(int orientation) { 365 mDeviceOrientation = orientation; 366 } 367 368 /** 369 * Configure streams and run minimal preview 370 */ minimalPreview(SurfaceHolder previewHolder, SurfaceHolder previewHolder2, CameraControls camCtl)371 public void minimalPreview(SurfaceHolder previewHolder, SurfaceHolder previewHolder2, 372 CameraControls camCtl) throws ApiFailureException { 373 374 minimalOpenCamera(); 375 376 if (mPreviewSurface == null || mPreviewSurface2 == null) { 377 throw new ApiFailureException("Preview surface is not created"); 378 } 379 try { 380 List<OutputConfiguration> outputConfigs = 381 new ArrayList<OutputConfiguration>(/*capacity*/2); 382 boolean isLogicalCamera = 383 !mPhysicalCameraId1.equals("") && !mPhysicalCameraId2.equals(""); 384 if (isLogicalCamera) { 385 OutputConfiguration config1 = new OutputConfiguration(previewHolder.getSurface()); 386 config1.setPhysicalCameraId(mPhysicalCameraId1); 387 outputConfigs.add(config1); 388 389 OutputConfiguration config2 = new OutputConfiguration(previewHolder2.getSurface()); 390 config2.setPhysicalCameraId(mPhysicalCameraId2); 391 outputConfigs.add(config2); 392 } else { 393 OutputConfiguration config = new OutputConfiguration(previewHolder.getSurface()); 394 outputConfigs.add(config); 395 } 396 configureOutputsByConfigs(outputConfigs); 397 398 mPreviewRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 399 updateCaptureRequest(mPreviewRequestBuilder, camCtl); 400 401 mPreviewRequestBuilder.addTarget(mPreviewSurface); 402 if (isLogicalCamera) { 403 mPreviewRequestBuilder.addTarget(mPreviewSurface2); 404 } 405 406 mSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null); 407 } catch (CameraAccessException e) { 408 throw new ApiFailureException("Error setting up minimal preview", e); 409 } 410 } 411 412 private static class SimpleImageListener implements ImageReader.OnImageAvailableListener { 413 private final ConditionVariable imageAvailable = new ConditionVariable(); 414 private final CaptureCallback mListener; 415 SimpleImageListener(final CaptureCallback listener)416 SimpleImageListener(final CaptureCallback listener) { 417 mListener = listener; 418 } 419 420 @Override onImageAvailable(ImageReader reader)421 public void onImageAvailable(ImageReader reader) { 422 Image i = null; 423 try { 424 i = reader.acquireNextImage(); 425 mListener.onCaptureAvailable(i); 426 } finally { 427 if (i != null) { 428 i.close(); 429 } 430 imageAvailable.open(); 431 } 432 } 433 waitForImageAvailable(long timeout)434 public void waitForImageAvailable(long timeout) { 435 if (imageAvailable.block(timeout)) { 436 imageAvailable.close(); 437 } else { 438 Log.e(TAG, "wait for image available timed out after " + timeout + "ms"); 439 } 440 } 441 } 442 minimalStillCapture(final CaptureCallback listener, CaptureResultListener l, Handler h, CameraControls cameraControl, int format)443 public void minimalStillCapture(final CaptureCallback listener, CaptureResultListener l, 444 Handler h, CameraControls cameraControl, int format) throws ApiFailureException { 445 minimalOpenCamera(); 446 447 try { 448 CameraCharacteristics properties = 449 mCameraManager.getCameraCharacteristics(mCamera.getId()); 450 Size[] stillSizes = null; 451 if (properties != null) { 452 stillSizes = properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP). 453 getOutputSizes(format); 454 } 455 int width = 640; 456 int height = 480; 457 458 if (stillSizes != null && stillSizes.length > 0) { 459 width = stillSizes[0].getWidth(); 460 height = stillSizes[0].getHeight(); 461 } 462 463 if (mCaptureReader == null || mCaptureReader.getWidth() != width || 464 mCaptureReader.getHeight() != height || 465 mCaptureReader.getImageFormat() != format) { 466 if (mCaptureReader != null) { 467 mCaptureReader.close(); 468 } 469 mCaptureReader = ImageReader.newInstance(width, height, 470 format, MAX_CONCURRENT_STILL_CAPTURES); 471 } 472 473 List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1); 474 outputSurfaces.add(mCaptureReader.getSurface()); 475 476 configureOutputs(outputSurfaces); 477 478 CaptureRequest.Builder captureBuilder = 479 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); 480 captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientationHint()); 481 482 captureBuilder.addTarget(mCaptureReader.getSurface()); 483 484 updateCaptureRequest(captureBuilder, cameraControl); 485 486 SimpleImageListener readerListener = new SimpleImageListener(listener); 487 488 mCaptureReader.setOnImageAvailableListener(readerListener, h); 489 490 mSession.capture(captureBuilder.build(), l, mOpsHandler); 491 492 readerListener.waitForImageAvailable(1000L/*timeout*/); 493 } catch (CameraAccessException e) { 494 throw new ApiFailureException("Error in minimal still capture", e); 495 } 496 } 497 startRecording(Context applicationContext, boolean useMediaCodec, int outputFormat)498 public void startRecording(Context applicationContext, boolean useMediaCodec, int outputFormat) 499 throws ApiFailureException { 500 minimalOpenCamera(); 501 Size recordingSize = getRecordingSize(); 502 int orientationHint = getOrientationHint(); 503 try { 504 if (mRecordingRequestBuilder == null) { 505 mRecordingRequestBuilder = 506 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 507 } 508 // Setup output stream first 509 mRecordingStream.configure( 510 applicationContext, recordingSize, useMediaCodec, mEncodingBitRate, 511 orientationHint, outputFormat); 512 mRecordingStream.onConfiguringOutputs(mOutputSurfaces, /* detach */false); 513 mRecordingStream.onConfiguringRequest(mRecordingRequestBuilder, /* detach */false); 514 515 // TODO: For preview, create preview stream class, and do the same thing like recording. 516 mOutputSurfaces.add(mPreviewSurface); 517 mRecordingRequestBuilder.addTarget(mPreviewSurface); 518 519 // Start camera streaming and recording. 520 configureOutputs(mOutputSurfaces); 521 mSession.setRepeatingRequest(mRecordingRequestBuilder.build(), null, null); 522 mRecordingStream.start(); 523 } catch (CameraAccessException e) { 524 throw new ApiFailureException("Error start recording", e); 525 } 526 } 527 stopRecording(Context ctx)528 public void stopRecording(Context ctx) throws ApiFailureException { 529 try { 530 /** 531 * <p> 532 * Only stop camera recording stream. 533 * </p> 534 * <p> 535 * FIXME: There is a race condition to be fixed in CameraDevice. 536 * Basically, when stream closes, encoder and its surface is 537 * released, while it still takes some time for camera to finish the 538 * output to that surface. Then it cause camera in bad state. 539 * </p> 540 */ 541 mRecordingStream.onConfiguringRequest(mRecordingRequestBuilder, /* detach */true); 542 mRecordingStream.onConfiguringOutputs(mOutputSurfaces, /* detach */true); 543 544 // Remove recording surface before calling RecordingStream.stop, 545 // since that invalidates the surface. 546 configureOutputs(mOutputSurfaces); 547 548 mRecordingStream.stop(ctx); 549 550 mSession.setRepeatingRequest(mRecordingRequestBuilder.build(), null, null); 551 } catch (CameraAccessException e) { 552 throw new ApiFailureException("Error stop recording", e); 553 } 554 } 555 556 /** 557 * Flush all current requests and in-progress work 558 */ flush()559 public void flush() throws ApiFailureException { 560 minimalOpenCamera(); 561 try { 562 mSession.abortCaptures(); 563 } catch (CameraAccessException e) { 564 throw new ApiFailureException("Error flushing", e); 565 } 566 } 567 getOrientationHint()568 private int getOrientationHint() { 569 // snap to {0, 90, 180, 270} 570 int orientation = ((int)Math.round(mDeviceOrientation/90.0)*90) % 360; 571 572 CameraCharacteristics properties = getCameraCharacteristics(); 573 int sensorOrientation = properties.get(CameraCharacteristics.SENSOR_ORIENTATION); 574 575 // TODO: below calculation is for back-facing camera only 576 // front-facing camera should use: 577 // return (sensorOrientation - orientation +360) % 360; 578 return (sensorOrientation + orientation) % 360; 579 } 580 getRecordingSize()581 private Size getRecordingSize() throws ApiFailureException { 582 try { 583 CameraCharacteristics properties = 584 mCameraManager.getCameraCharacteristics(mCamera.getId()); 585 586 Size[] recordingSizes = null; 587 if (properties != null) { 588 recordingSizes = 589 properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP). 590 getOutputSizes(MediaCodec.class); 591 } 592 593 mEncodingBitRate = ENC_BIT_RATE_LOW; 594 if (recordingSizes == null || recordingSizes.length == 0) { 595 Log.w(TAG, "Unable to get recording sizes, default to 640x480"); 596 return DEFAULT_SIZE; 597 } else { 598 /** 599 * TODO: create resolution selection widget on UI, then use the 600 * select size. For now, return HIGH_RESOLUTION_SIZE if it 601 * exists in the processed size list, otherwise return default 602 * size 603 */ 604 if (Arrays.asList(recordingSizes).contains(HIGH_RESOLUTION_SIZE)) { 605 mEncodingBitRate = ENC_BIT_RATE_HIGH; 606 return HIGH_RESOLUTION_SIZE; 607 } else { 608 // Fallback to default size when HD size is not found. 609 Log.w(TAG, 610 "Unable to find the requested size " + HIGH_RESOLUTION_SIZE.toString() 611 + " Fallback to " + DEFAULT_SIZE.toString()); 612 return DEFAULT_SIZE; 613 } 614 } 615 } catch (CameraAccessException e) { 616 throw new ApiFailureException("Error setting up video recording", e); 617 } 618 } 619 updateCaptureRequest(CaptureRequest.Builder builder, CameraControls cameraControl)620 private void updateCaptureRequest(CaptureRequest.Builder builder, 621 CameraControls cameraControl) { 622 if (cameraControl != null) { 623 // Update the manual control metadata for capture request 624 // may disable 3A routines. 625 updateCaptureRequest(builder, cameraControl.getManualControls()); 626 // Update the AF control metadata for capture request (if manual is not used) 627 updateCaptureRequest(builder, cameraControl.getAfControls()); 628 } 629 } 630 updateCaptureRequest(CaptureRequest.Builder builder, CameraManualControls manualControls)631 private void updateCaptureRequest(CaptureRequest.Builder builder, 632 CameraManualControls manualControls) { 633 if (manualControls == null) { 634 return; 635 } 636 637 if (manualControls.isManualControlEnabled()) { 638 Log.e(TAG, "update request: " + manualControls.getSensitivity()); 639 builder.set(CaptureRequest.CONTROL_MODE, 640 CameraMetadata.CONTROL_MODE_OFF); 641 builder.set(CaptureRequest.SENSOR_SENSITIVITY, 642 manualControls.getSensitivity()); 643 builder.set(CaptureRequest.SENSOR_FRAME_DURATION, 644 manualControls.getFrameDuration()); 645 builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, 646 manualControls.getExposure()); 647 648 if (VERBOSE) { 649 Log.v(TAG, "updateCaptureRequest - manual - control.mode = OFF"); 650 } 651 } else { 652 builder.set(CaptureRequest.CONTROL_MODE, 653 CameraMetadata.CONTROL_MODE_AUTO); 654 655 if (VERBOSE) { 656 Log.v(TAG, "updateCaptureRequest - manual - control.mode = AUTO"); 657 } 658 } 659 } 660 updateCaptureRequest(CaptureRequest.Builder builder, CameraAutoFocusControls cameraAfControl)661 private void updateCaptureRequest(CaptureRequest.Builder builder, 662 CameraAutoFocusControls cameraAfControl) { 663 if (cameraAfControl == null) { 664 return; 665 } 666 667 if (cameraAfControl.isAfControlEnabled()) { 668 builder.set(CaptureRequest.CONTROL_AF_MODE, cameraAfControl.getAfMode()); 669 670 Integer afTrigger = cameraAfControl.consumePendingTrigger(); 671 672 if (afTrigger != null) { 673 builder.set(CaptureRequest.CONTROL_AF_TRIGGER, afTrigger); 674 } 675 676 if (VERBOSE) { 677 Log.v(TAG, "updateCaptureRequest - AF - set trigger to " + afTrigger); 678 } 679 } 680 } 681 682 public interface CaptureCallback { onCaptureAvailable(Image capture)683 void onCaptureAvailable(Image capture); 684 } 685 686 public static abstract class CaptureResultListener 687 extends CameraCaptureSession.CaptureCallback {} 688 } 689