• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.example.rscamera;
18 
19 import android.content.ContentResolver;
20 import android.graphics.ImageFormat;
21 import android.hardware.camera2.CameraAccessException;
22 import android.hardware.camera2.CameraCaptureSession;
23 import android.hardware.camera2.CameraCharacteristics;
24 import android.hardware.camera2.CameraDevice;
25 import android.hardware.camera2.CameraManager;
26 import android.hardware.camera2.CaptureRequest;
27 import android.hardware.camera2.TotalCaptureResult;
28 import android.hardware.camera2.params.StreamConfigurationMap;
29 import android.media.Image;
30 import android.media.ImageReader;
31 import android.os.ConditionVariable;
32 import android.os.Handler;
33 import android.os.HandlerThread;
34 import android.os.Looper;
35 import android.util.Log;
36 import android.util.Range;
37 import android.util.Size;
38 import android.view.Surface;
39 import android.view.SurfaceHolder;
40 import android.widget.Toast;
41 
42 import com.android.example.rscamera.rscamera.R;
43 
44 import java.io.File;
45 import java.io.FileNotFoundException;
46 import java.io.FileOutputStream;
47 import java.io.IOException;
48 import java.io.OutputStream;
49 import java.nio.ByteBuffer;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Collections;
53 import java.util.Comparator;
54 import 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  */
60 public 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 
resume()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 
hasCapability(int[] capabilities, int capability)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 
ImageSaver(Image image, String fileName, ContentResolver contentResolver)200         public ImageSaver(Image image, String fileName, ContentResolver contentResolver) {
201             mImage = image;
202             mName = fileName;
203             mContentResolver = contentResolver;
204         }
205 
206         @Override
run()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      */
CameraOps(CameraManager manager, ErrorDisplayer errorDisplayer, CameraReadyListener readyListener)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      */
openCamera(final String cameraId)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 
pause()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 
getBestSize()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 }