1/* 2 * Copyright (C) 2015 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.example.rscamera; 18 19import android.content.ContentResolver; 20import android.graphics.ImageFormat; 21import android.hardware.camera2.CameraAccessException; 22import android.hardware.camera2.CameraCaptureSession; 23import android.hardware.camera2.CameraCharacteristics; 24import android.hardware.camera2.CameraDevice; 25import android.hardware.camera2.CameraManager; 26import android.hardware.camera2.CaptureRequest; 27import android.hardware.camera2.TotalCaptureResult; 28import android.hardware.camera2.params.StreamConfigurationMap; 29import android.media.Image; 30import android.media.ImageReader; 31import android.os.ConditionVariable; 32import android.os.Handler; 33import android.os.HandlerThread; 34import android.os.Looper; 35import android.util.Log; 36import android.util.Range; 37import android.util.Size; 38import android.view.Surface; 39import android.view.SurfaceHolder; 40import android.widget.Toast; 41 42import com.android.example.rscamera.rscamera.R; 43 44import java.io.File; 45import java.io.FileNotFoundException; 46import java.io.FileOutputStream; 47import java.io.IOException; 48import java.io.OutputStream; 49import java.nio.ByteBuffer; 50import java.util.ArrayList; 51import java.util.Arrays; 52import java.util.Collections; 53import java.util.Comparator; 54import java.util.List; 55 56/** 57 * Simple interface for operating the camera, with major camera operations 58 * all performed on a background handler thread. 59 */ 60public class CameraOps { 61 62 private static final String TAG = "CameraOps"; 63 private static final long ONE_SECOND = 1000000000; 64 public static final long CAMERA_CLOSE_TIMEOUT = 2000; // ms 65 66 private final CameraManager mCameraManager; 67 private CameraDevice mCameraDevice; 68 private CameraCaptureSession mCameraSession; 69 private List<Surface> mSurfaces; 70 71 private final ConditionVariable mCloseWaiter = new ConditionVariable(); 72 73 private HandlerThread mCameraThread; 74 private Handler mCameraHandler; 75 76 private final ErrorDisplayer mErrorDisplayer; 77 78 private final CameraReadyListener mReadyListener; 79 private final Handler mReadyHandler; 80 81 private int mISOmax; 82 private int mISOmin; 83 private long mExpMax; 84 private long mExpMin; 85 private float mFocusMin; 86 private float mFocusDist = 0; 87 private int mIso; 88 boolean mAutoExposure = true; 89 boolean mAutoFocus = true; 90 private long mExposure = ONE_SECOND / 33; 91 92 private Object mAutoExposureTag = new Object(); 93 94 private ImageReader mImageReader; 95 private Handler mBackgroundHandler; 96 private CameraCharacteristics mCameraInfo; 97 private HandlerThread mBackgroundThread; 98 CaptureRequest.Builder mHdrBuilder; 99 private Surface mProcessingNormalSurface; 100 CaptureRequest mPreviewRequest; 101 private String mSaveFileName; 102 private ContentResolver mContentResolver; 103 104 public String resume() { 105 String errorMessage = "Unknown error"; 106 boolean foundCamera = false; 107 try { 108 // Find first back-facing camera that has necessary capability 109 String[] cameraIds = mCameraManager.getCameraIdList(); 110 for (String id : cameraIds) { 111 CameraCharacteristics info = mCameraManager.getCameraCharacteristics(id); 112 int facing = info.get(CameraCharacteristics.LENS_FACING); 113 114 int level = info.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); 115 boolean hasFullLevel 116 = (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL); 117 118 int[] capabilities = info.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); 119 int syncLatency = info.get(CameraCharacteristics.SYNC_MAX_LATENCY); 120 boolean hasManualControl = hasCapability(capabilities, 121 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR); 122 boolean hasEnoughCapability = hasManualControl && 123 syncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL; 124 Range<Integer> irange; 125 Range<Long> lrange; 126 127 irange = info.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE); 128 mISOmax = irange.getUpper(); 129 mISOmin = irange.getLower(); 130 lrange = info.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE); 131 mExpMax = lrange.getUpper(); 132 mExpMin = lrange.getLower(); 133 mFocusMin = info.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); 134 135 mFocusDist = mFocusMin; 136 StreamConfigurationMap map = info.get( 137 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 138 Size[] sizes = map.getOutputSizes(ImageFormat.JPEG); 139 Size largest = Collections.max(Arrays.asList(sizes), new Comparator<Size>() { 140 @Override 141 public int compare(Size lhs, Size rhs) { 142 int leftArea = lhs.getHeight() * lhs.getWidth(); 143 int rightArea = lhs.getHeight() * lhs.getWidth(); 144 return Integer.compare(leftArea, rightArea); 145 } 146 }); 147 mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), 148 ImageFormat.JPEG, /*maxImages*/2); 149 mImageReader.setOnImageAvailableListener( 150 mOnImageAvailableListener, mBackgroundHandler); 151 152 if (facing == CameraCharacteristics.LENS_FACING_BACK && 153 (hasFullLevel || hasEnoughCapability)) { 154 // Found suitable camera - get info, open, and set up outputs 155 mCameraInfo = info; 156 openCamera(id); 157 foundCamera = true; 158 break; 159 } 160 } 161 if (!foundCamera) { 162 errorMessage = "no back camera"; 163 } 164 } catch (CameraAccessException e) { 165 errorMessage = e.getMessage(); 166 } 167 // startBackgroundThread 168 mBackgroundThread = new HandlerThread("CameraBackground"); 169 mBackgroundThread.start(); 170 mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); 171 return (foundCamera) ? null : errorMessage; 172 } 173 174 175 private boolean hasCapability(int[] capabilities, int capability) { 176 for (int c : capabilities) { 177 if (c == capability) return true; 178 } 179 return false; 180 } 181 182 private final ImageReader.OnImageAvailableListener mOnImageAvailableListener 183 = new ImageReader.OnImageAvailableListener() { 184 185 @Override 186 public void onImageAvailable(ImageReader reader) { 187 mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mSaveFileName, mContentResolver)); 188 } 189 190 }; 191 192 /** 193 * Saves a JPEG {@link android.media.Image} into the specified {@link java.io.File}. 194 */ 195 private static class ImageSaver implements Runnable { 196 private final Image mImage; 197 private final String mName; 198 ContentResolver mContentResolver; 199 200 public ImageSaver(Image image, String fileName, ContentResolver contentResolver) { 201 mImage = image; 202 mName = fileName; 203 mContentResolver = contentResolver; 204 } 205 206 @Override 207 public void run() { 208 Log.v(TAG, "SAVING..."); 209 MediaStoreSaver.insertImage(mContentResolver, new MediaStoreSaver.StreamWriter() { 210 @Override 211 public void write(OutputStream imageOut) throws IOException { 212 try { 213 ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); 214 byte[] bytes = new byte[buffer.remaining()]; 215 buffer.get(bytes); 216 imageOut.write(bytes); 217 } finally { 218 mImage.close(); 219 } 220 } 221 }, mName, "Saved from Simple Camera Demo"); 222 } 223 } 224 225 /** 226 * Create a new camera ops thread. 227 * 228 * @param errorDisplayer listener for displaying error messages 229 * @param readyListener listener for notifying when camera is ready for requests 230 */ 231 CameraOps(CameraManager manager, ErrorDisplayer errorDisplayer, 232 CameraReadyListener readyListener) { 233 mReadyHandler = new Handler(Looper.getMainLooper()); 234 235 mCameraThread = new HandlerThread("CameraOpsThread"); 236 mCameraThread.start(); 237 238 if (manager == null || errorDisplayer == null || 239 readyListener == null || mReadyHandler == null) { 240 throw new IllegalArgumentException("Need valid displayer, listener, handler"); 241 } 242 243 mCameraManager = manager; 244 mErrorDisplayer = errorDisplayer; 245 mReadyListener = readyListener; 246 247 } 248 249 /** 250 * Open the first backfacing camera listed by the camera manager. 251 * Displays a dialog if it cannot open a camera. 252 */ 253 public void openCamera(final String cameraId) { 254 mCameraHandler = new Handler(mCameraThread.getLooper()); 255 256 mCameraHandler.post(new Runnable() { 257 public void run() { 258 if (mCameraDevice != null) { 259 throw new IllegalStateException("Camera already open"); 260 } 261 try { 262 263 mCameraManager.openCamera(cameraId, mCameraDeviceListener, mCameraHandler); 264 } catch (CameraAccessException e) { 265 String errorMessage = mErrorDisplayer.getErrorString(e); 266 mErrorDisplayer.showErrorDialog(errorMessage); 267 } 268 } 269 }); 270 } 271 272 public void pause() { 273 closeCameraAndWait(); 274 mBackgroundThread.quitSafely(); 275 try { 276 mBackgroundThread.join(); 277 mBackgroundThread = null; 278 mBackgroundHandler = null; 279 } catch (InterruptedException e) { 280 e.printStackTrace(); 281 } 282 } 283 284 public Size getBestSize() { 285 // Find a good size for output - largest 16:9 aspect ratio that's less than 720p 286 final int MAX_WIDTH = 1280; 287 final float TARGET_ASPECT = 16.f / 9.f; 288 final float ASPECT_TOLERANCE = 0.1f; 289 290 291 StreamConfigurationMap configs = 292 mCameraInfo.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 293 294 Size[] outputSizes = configs.getOutputSizes(SurfaceHolder.class); 295 296 Size outputSize = outputSizes[0]; 297 float outputAspect = (float) outputSize.getWidth() / outputSize.getHeight(); 298 for (Size candidateSize : outputSizes) { 299 if (candidateSize.getWidth() > MAX_WIDTH) continue; 300 float candidateAspect = (float) candidateSize.getWidth() / candidateSize.getHeight(); 301 boolean goodCandidateAspect = 302 Math.abs(candidateAspect - TARGET_ASPECT) < ASPECT_TOLERANCE; 303 boolean goodOutputAspect = 304 Math.abs(outputAspect - TARGET_ASPECT) < ASPECT_TOLERANCE; 305 if ((goodCandidateAspect && !goodOutputAspect) || 306 candidateSize.getWidth() > outputSize.getWidth()) { 307 outputSize = candidateSize; 308 outputAspect = candidateAspect; 309 } 310 } 311 return outputSize; 312 } 313 314 /** 315 * Close the camera and wait for the close callback to be called in the camera thread. 316 * Times out after @{value CAMERA_CLOSE_TIMEOUT} ms. 317 */ 318 public void closeCameraAndWait() { 319 mCloseWaiter.close(); 320 mCameraHandler.post(mCloseCameraRunnable); 321 boolean closed = mCloseWaiter.block(CAMERA_CLOSE_TIMEOUT); 322 if (!closed) { 323 Log.e(TAG, "Timeout closing camera"); 324 } 325 } 326 327 private Runnable mCloseCameraRunnable = new Runnable() { 328 public void run() { 329 if (mCameraDevice != null) { 330 mCameraDevice.close(); 331 } 332 mCameraDevice = null; 333 mCameraSession = null; 334 mSurfaces = null; 335 } 336 }; 337 338 /** 339 * Set the output Surfaces, and finish configuration if otherwise ready. 340 */ 341 public void setSurface(Surface surface) { 342 final List<Surface> surfaceList = new ArrayList<Surface>(); 343 surfaceList.add(surface); 344 surfaceList.add(mImageReader.getSurface()); 345 346 mCameraHandler.post(new Runnable() { 347 public void run() { 348 mSurfaces = surfaceList; 349 startCameraSession(); 350 } 351 }); 352 } 353 354 /** 355 * Get a request builder for the current camera. 356 */ 357 public CaptureRequest.Builder createCaptureRequest(int template) throws CameraAccessException { 358 CameraDevice device = mCameraDevice; 359 if (device == null) { 360 throw new IllegalStateException("Can't get requests when no camera is open"); 361 } 362 return device.createCaptureRequest(template); 363 } 364 365 /** 366 * Set a repeating request. 367 */ 368 public void setRepeatingRequest(final CaptureRequest request, 369 final CameraCaptureSession.CaptureCallback listener, 370 final Handler handler) { 371 mCameraHandler.post(new Runnable() { 372 public void run() { 373 try { 374 mCameraSession.setRepeatingRequest(request, listener, handler); 375 } catch (CameraAccessException e) { 376 String errorMessage = mErrorDisplayer.getErrorString(e); 377 mErrorDisplayer.showErrorDialog(errorMessage); 378 } 379 } 380 }); 381 } 382 383 /** 384 * Set a repeating request. 385 */ 386 public void setRepeatingBurst(final List<CaptureRequest> requests, 387 final CameraCaptureSession.CaptureCallback listener, 388 final Handler handler) { 389 mCameraHandler.post(new Runnable() { 390 public void run() { 391 try { 392 mCameraSession.setRepeatingBurst(requests, listener, handler); 393 394 } catch (CameraAccessException e) { 395 String errorMessage = mErrorDisplayer.getErrorString(e); 396 mErrorDisplayer.showErrorDialog(errorMessage); 397 } 398 } 399 }); 400 } 401 402 /** 403 * Configure the camera session. 404 */ 405 private void startCameraSession() { 406 // Wait until both the camera device is open and the SurfaceView is ready 407 if (mCameraDevice == null || mSurfaces == null) return; 408 409 try { 410 411 mCameraDevice.createCaptureSession( 412 mSurfaces, mCameraSessionListener, mCameraHandler); 413 } catch (CameraAccessException e) { 414 String errorMessage = mErrorDisplayer.getErrorString(e); 415 mErrorDisplayer.showErrorDialog(errorMessage); 416 mCameraDevice.close(); 417 mCameraDevice = null; 418 } 419 } 420 421 /** 422 * Main listener for camera session events 423 * Invoked on mCameraThread 424 */ 425 private CameraCaptureSession.StateCallback mCameraSessionListener = 426 new CameraCaptureSession.StateCallback() { 427 428 @Override 429 public void onConfigured(CameraCaptureSession session) { 430 mCameraSession = session; 431 mReadyHandler.post(new Runnable() { 432 public void run() { 433 // This can happen when the screen is turned off and turned back on. 434 if (null == mCameraDevice) { 435 return; 436 } 437 438 mReadyListener.onCameraReady(); 439 } 440 }); 441 442 } 443 444 @Override 445 public void onConfigureFailed(CameraCaptureSession session) { 446 mErrorDisplayer.showErrorDialog("Unable to configure the capture session"); 447 mCameraDevice.close(); 448 mCameraDevice = null; 449 } 450 }; 451 452 /** 453 * Main listener for camera device events. 454 * Invoked on mCameraThread 455 */ 456 private CameraDevice.StateCallback mCameraDeviceListener = new CameraDevice.StateCallback() { 457 458 @Override 459 public void onOpened(CameraDevice camera) { 460 mCameraDevice = camera; 461 startCameraSession(); 462 } 463 464 @Override 465 public void onClosed(CameraDevice camera) { 466 mCloseWaiter.open(); 467 } 468 469 @Override 470 public void onDisconnected(CameraDevice camera) { 471 mErrorDisplayer.showErrorDialog("The camera device has been disconnected."); 472 camera.close(); 473 mCameraDevice = null; 474 } 475 476 @Override 477 public void onError(CameraDevice camera, int error) { 478 mErrorDisplayer.showErrorDialog("The camera encountered an error:" + error); 479 camera.close(); 480 mCameraDevice = null; 481 } 482 483 }; 484 485 public void captureStillPicture(int currentJpegRotation, String name, ContentResolver resolver) { 486 mSaveFileName = name; 487 mContentResolver = resolver; 488 try { 489 // TODO call lock focus if we are in "AF-S(One-Shot AF) mode" 490 // TODO call precapture if we are using flash 491 // This is the CaptureRequest.Builder that we use to take a picture. 492 final CaptureRequest.Builder captureBuilder = 493 createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); 494 Log.v(TAG, " Target " + mImageReader.getWidth() + "," + mImageReader.getHeight()); 495 496 captureBuilder.addTarget(mImageReader.getSurface()); 497 498 captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, currentJpegRotation); 499 500 CameraCaptureSession.CaptureCallback captureCallback 501 = new CameraCaptureSession.CaptureCallback() { 502 503 @Override 504 public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, 505 TotalCaptureResult result) { 506 Log.v(TAG, " onCaptureCompleted"); 507 setParameters(); 508 } 509 }; 510 511 512 setRequest(captureBuilder.build(), captureCallback, null); 513 } catch (CameraAccessException e) { 514 e.printStackTrace(); 515 } 516 } 517 518 /** 519 * Set a repeating request. 520 */ 521 private void setRequest(final CaptureRequest request, 522 final CameraCaptureSession.CaptureCallback listener, 523 final Handler handler) { 524 mCameraHandler.post(new Runnable() { 525 public void run() { 526 try { 527 mCameraSession.stopRepeating(); 528 mCameraSession.capture(request, listener, handler); 529 } catch (CameraAccessException e) { 530 String errorMessage = mErrorDisplayer.getErrorString(e); 531 mErrorDisplayer.showErrorDialog(errorMessage); 532 } 533 } 534 }); 535 } 536 537 public void setUpCamera(Surface processingNormalSurface) { 538 mProcessingNormalSurface = processingNormalSurface; 539 // Ready to send requests in, so set them up 540 try { 541 CaptureRequest.Builder previewBuilder = 542 createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 543 previewBuilder.addTarget(mProcessingNormalSurface); 544 previewBuilder.setTag(mAutoExposureTag); 545 mPreviewRequest = previewBuilder.build(); 546 mHdrBuilder = createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 547 mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE, 548 CaptureRequest.CONTROL_AE_MODE_OFF); 549 mHdrBuilder.addTarget(mProcessingNormalSurface); 550 setParameters(); 551 552 } catch (CameraAccessException e) { 553 String errorMessage = e.getMessage(); 554 // MessageDialogFragment.newInstance(errorMessage).show(getFragmentManager(), FRAGMENT_DIALOG); 555 } 556 } 557 558 /** 559 * Start running an HDR burst on a configured camera session 560 */ 561 public void setParameters() { 562 if (mHdrBuilder == null) { 563 Log.v(TAG," Camera not set up"); 564 return; 565 } 566 if (mAutoExposure) { 567 mHdrBuilder.set(CaptureRequest.SENSOR_FRAME_DURATION, ONE_SECOND / 30); 568 mHdrBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, getExposure()); 569 mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); 570 } else { 571 mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF); 572 mHdrBuilder.set(CaptureRequest.SENSOR_FRAME_DURATION, ONE_SECOND / 30); 573 mHdrBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, getExposure()); 574 mHdrBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, getIso()); 575 } 576 if (mAutoFocus) { 577 mHdrBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); 578 mHdrBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); 579 } else { 580 mHdrBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF); 581 mHdrBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, getFocusDistance()); 582 mHdrBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); 583 } 584 585 setRepeatingRequest(mHdrBuilder.build(), mCaptureCallback, mReadyHandler); 586 } 587 588 private CameraCaptureSession.CaptureCallback mCaptureCallback 589 = new CameraCaptureSession.CaptureCallback() { 590 591 public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, 592 TotalCaptureResult result) { 593 } 594 }; 595 596 /** 597 * Simple listener for main code to know the camera is ready for requests, or failed to 598 * start. 599 */ 600 public interface CameraReadyListener { 601 public void onCameraReady(); 602 } 603 604 /** 605 * Simple listener for displaying error messages 606 */ 607 public interface ErrorDisplayer { 608 public void showErrorDialog(String errorMessage); 609 public String getErrorString(CameraAccessException e); 610 } 611 612 public float getFocusDistance() { 613 return mFocusDist; 614 } 615 616 public void setFocusDistance(float focusDistance) { 617 mFocusDist = focusDistance; 618 } 619 620 public void setIso(int iso) { 621 mIso = iso; 622 } 623 624 public boolean isAutoExposure() { 625 return mAutoExposure; 626 } 627 628 public void setAutoExposure(boolean autoExposure) { 629 mAutoExposure = autoExposure; 630 } 631 632 public boolean isAutoFocus() { 633 return mAutoFocus; 634 } 635 636 public void setAutoFocus(boolean autoFocus) { 637 mAutoFocus = autoFocus; 638 } 639 640 public int getIso() { 641 return mIso; 642 } 643 644 public long getExposure() { 645 return mExposure; 646 } 647 648 public void setExposure(long exposure) { 649 mExposure = exposure; 650 } 651 652 public int getIsoMax() { 653 return mISOmax; 654 } 655 656 public int getIsoMin() { 657 return mISOmin; 658 } 659 660 public long getExpMax() { 661 return mExpMax; 662 } 663 664 public long getExpMin() { 665 return mExpMin; 666 } 667 668 public float getFocusMin() { 669 return mFocusMin; 670 } 671}