1 /*
2  * Copyright (C) 2014 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.camera.one.v2;
18 
19 import android.annotation.TargetApi;
20 import android.app.Activity;
21 import android.graphics.ImageFormat;
22 import android.graphics.Rect;
23 import android.graphics.SurfaceTexture;
24 import android.hardware.camera2.CameraAccessException;
25 import android.hardware.camera2.CameraCaptureSession;
26 import android.hardware.camera2.CameraCharacteristics;
27 import android.hardware.camera2.CameraDevice;
28 import android.hardware.camera2.CameraMetadata;
29 import android.hardware.camera2.CaptureRequest;
30 import android.hardware.camera2.CaptureResult;
31 import android.hardware.camera2.DngCreator;
32 import android.hardware.camera2.TotalCaptureResult;
33 import android.hardware.camera2.params.MeteringRectangle;
34 import android.hardware.camera2.params.StreamConfigurationMap;
35 import android.location.Location;
36 import android.media.Image;
37 import android.media.ImageReader;
38 import android.net.Uri;
39 import android.os.Build;
40 import android.os.Handler;
41 import android.os.HandlerThread;
42 import android.os.SystemClock;
43 import android.view.Surface;
44 
45 import com.android.camera.CaptureModuleUtil;
46 import com.android.camera.Exif;
47 import com.android.camera.Storage;
48 import com.android.camera.debug.DebugPropertyHelper;
49 import com.android.camera.debug.Log;
50 import com.android.camera.debug.Log.Tag;
51 import com.android.camera.exif.ExifInterface;
52 import com.android.camera.exif.ExifTag;
53 import com.android.camera.exif.Rational;
54 import com.android.camera.one.AbstractOneCamera;
55 import com.android.camera.one.CameraDirectionProvider;
56 import com.android.camera.one.OneCamera;
57 import com.android.camera.one.Settings3A;
58 import com.android.camera.one.v2.camera2proxy.AndroidCaptureResultProxy;
59 import com.android.camera.one.v2.camera2proxy.AndroidImageProxy;
60 import com.android.camera.one.v2.camera2proxy.CaptureResultProxy;
61 import com.android.camera.processing.imagebackend.TaskImageContainer;
62 import com.android.camera.session.CaptureSession;
63 import com.android.camera.ui.focus.LensRangeCalculator;
64 import com.android.camera.ui.motion.LinearScale;
65 import com.android.camera.util.CameraUtil;
66 import com.android.camera.util.CaptureDataSerializer;
67 import com.android.camera.util.ExifUtil;
68 import com.android.camera.util.JpegUtilNative;
69 import com.android.camera.util.Size;
70 import com.google.common.base.Optional;
71 import com.google.common.util.concurrent.FutureCallback;
72 import com.google.common.util.concurrent.Futures;
73 import com.google.common.util.concurrent.ListenableFuture;
74 import com.google.common.util.concurrent.MoreExecutors;
75 
76 import java.io.File;
77 import java.io.FileOutputStream;
78 import java.io.IOException;
79 import java.nio.ByteBuffer;
80 import java.util.ArrayList;
81 import java.util.LinkedList;
82 import java.util.List;
83 
84 /**
85  * {@link OneCamera} implementation directly on top of the Camera2 API for
86  * cameras without API 2 FULL support (limited or legacy).
87  */
88 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
89 public class OneCameraImpl extends AbstractOneCamera {
90     /** Captures that are requested but haven't completed yet. */
91     private static class InFlightCapture {
92         final PhotoCaptureParameters parameters;
93         final CaptureSession session;
94         Image image;
95         TotalCaptureResult totalCaptureResult;
96 
InFlightCapture(PhotoCaptureParameters parameters, CaptureSession session)97         public InFlightCapture(PhotoCaptureParameters parameters,
98                 CaptureSession session) {
99             this.parameters = parameters;
100             this.session = session;
101         }
102 
103         /** Set the image once it's been received. */
setImage(Image capturedImage)104         public InFlightCapture setImage(Image capturedImage) {
105             image = capturedImage;
106             return this;
107         }
108 
109         /** Set the total capture result once it's been received. */
setCaptureResult(TotalCaptureResult result)110         public InFlightCapture setCaptureResult(TotalCaptureResult result) {
111             totalCaptureResult = result;
112             return this;
113         }
114 
115         /**
116          * Returns whether the capture is complete (which is the case once the
117          * image and capture result are both present.
118          */
isCaptureComplete()119         boolean isCaptureComplete() {
120             return image != null && totalCaptureResult != null;
121         }
122     }
123 
124     private static final Tag TAG = new Tag("OneCameraImpl2");
125 
126     /** If true, will write data about each capture request to disk. */
127     private static final boolean DEBUG_WRITE_CAPTURE_DATA = DebugPropertyHelper.writeCaptureData();
128     /** If true, will log per-frame AF info. */
129     private static final boolean DEBUG_FOCUS_LOG = DebugPropertyHelper.showFrameDebugLog();
130 
131     /** Default JPEG encoding quality. */
132     private static final Byte JPEG_QUALITY = 90;
133 
134     /**
135      * Set to ImageFormat.JPEG, to use the hardware encoder, or
136      * ImageFormat.YUV_420_888 to use the software encoder. You can also try
137      * RAW_SENSOR experimentally.
138      */
139     private static final int sCaptureImageFormat = DebugPropertyHelper.isCaptureDngEnabled() ?
140             ImageFormat.RAW_SENSOR : ImageFormat.JPEG;
141 
142     /** Duration to hold after manual focus tap. */
143     private static final int FOCUS_HOLD_MILLIS = Settings3A.getFocusHoldMillis();
144     /** Zero weight 3A region, to reset regions per API. */
145     private static final MeteringRectangle[] ZERO_WEIGHT_3A_REGION = AutoFocusHelper
146             .getZeroWeightRegion();
147 
148     /**
149      * CaptureRequest tags.
150      * <ul>
151      * <li>{@link #PRESHOT_TRIGGERED_AF}</li>
152      * <li>{@link #CAPTURE}</li>
153      * </ul>
154      */
155     public static enum RequestTag {
156         /** Request that is part of a pre shot trigger. */
157         PRESHOT_TRIGGERED_AF,
158         /** Capture request (purely for logging). */
159         CAPTURE,
160         /** Tap to focus (purely for logging). */
161         TAP_TO_FOCUS
162     }
163 
164     /** Directory to store raw DNG files in. */
165     private final File mRawDirectory;
166 
167     /** Current CONTROL_AF_MODE request to Camera2 API. */
168     private int mControlAFMode = CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
169     /** Last OneCamera.AutoFocusState reported. */
170     private AutoFocusState mLastResultAFState = AutoFocusState.INACTIVE;
171     /** Flag to take a picture when the lens is stopped. */
172     private boolean mTakePictureWhenLensIsStopped = false;
173     /** Takes a (delayed) picture with appropriate parameters. */
174     private Runnable mTakePictureRunnable;
175     /** Keep PictureCallback for last requested capture. */
176     private PictureCallback mLastPictureCallback = null;
177     /** Last time takePicture() was called in uptimeMillis. */
178     private long mTakePictureStartMillis;
179     /** Runnable that returns to CONTROL_AF_MODE = AF_CONTINUOUS_PICTURE. */
180     private final Runnable mReturnToContinuousAFRunnable = new Runnable() {
181         @Override
182         public void run() {
183             mAFRegions = ZERO_WEIGHT_3A_REGION;
184             mAERegions = ZERO_WEIGHT_3A_REGION;
185             mControlAFMode = CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
186             repeatingPreview(null);
187         }
188     };
189 
190     /** Current zoom value. 1.0 is no zoom. */
191     private float mZoomValue = 1f;
192     /** Current crop region: set from mZoomValue. */
193     private Rect mCropRegion;
194     /** Current AF and AE regions */
195     private MeteringRectangle[] mAFRegions = ZERO_WEIGHT_3A_REGION;
196     private MeteringRectangle[] mAERegions = ZERO_WEIGHT_3A_REGION;
197     /** Last frame for which CONTROL_AF_STATE was received. */
198     private long mLastControlAfStateFrameNumber = 0;
199 
200     /**
201      * Common listener for preview frame metadata.
202      */
203     private final CameraCaptureSession.CaptureCallback mCaptureCallback =
204             new CameraCaptureSession.CaptureCallback() {
205                 @Override
206                 public void onCaptureStarted(CameraCaptureSession session,
207                         CaptureRequest request, long timestamp,
208                         long frameNumber) {
209                     if (request.getTag() == RequestTag.CAPTURE
210                             && mLastPictureCallback != null) {
211                         mLastPictureCallback.onQuickExpose();
212                     }
213                 }
214 
215                 // AF state information is sometimes available 1 frame before
216                 // onCaptureCompleted(), so we take advantage of that.
217                 @Override
218                 public void onCaptureProgressed(CameraCaptureSession session,
219                         CaptureRequest request, CaptureResult partialResult) {
220                     autofocusStateChangeDispatcher(partialResult);
221                     super.onCaptureProgressed(session, request, partialResult);
222                 }
223 
224                 @Override
225                 public void onCaptureCompleted(CameraCaptureSession session,
226                         CaptureRequest request, TotalCaptureResult result) {
227                     autofocusStateChangeDispatcher(result);
228                     // This checks for a HAL implementation error where
229                     // TotalCaptureResult
230                     // is missing CONTROL_AF_STATE. This should not happen.
231                     if (result.get(CaptureResult.CONTROL_AF_STATE) == null) {
232                         AutoFocusHelper.checkControlAfState(result);
233                     }
234                     if (DEBUG_FOCUS_LOG) {
235                         AutoFocusHelper.logExtraFocusInfo(result);
236                     }
237 
238                     Float diopter = result.get(CaptureResult.LENS_FOCUS_DISTANCE);
239                     if (diopter != null && mFocusDistanceListener != null) {
240                         mFocusDistanceListener.onFocusDistance(diopter, mLensRange);
241                     }
242 
243                     if (request.getTag() == RequestTag.CAPTURE) {
244                         // Add the capture result to the latest in-flight
245                         // capture. If all the data for that capture is
246                         // complete, store the image on disk.
247                         InFlightCapture capture = null;
248                         synchronized (mCaptureQueue) {
249                             if (mCaptureQueue.getFirst().setCaptureResult(result)
250                                     .isCaptureComplete()) {
251                                 capture = mCaptureQueue.removeFirst();
252                             }
253                         }
254                         if (capture != null) {
255                             OneCameraImpl.this.onCaptureCompleted(capture);
256                         }
257                     }
258                     super.onCaptureCompleted(session, request, result);
259                 }
260             };
261     /** Thread on which the camera operations are running. */
262     private final HandlerThread mCameraThread;
263     /** Handler of the {@link #mCameraThread}. */
264     private final Handler mCameraHandler;
265     /** The characteristics of this camera. */
266     private final CameraCharacteristics mCharacteristics;
267     private final LinearScale mLensRange;
268     /** The underlying Camera2 API camera device. */
269     private final CameraDevice mDevice;
270     private final CameraDirectionProvider mDirectionProvider;
271 
272     /**
273      * The aspect ratio (width/height) of the full resolution for this camera.
274      * Usually the native aspect ratio of this camera.
275      */
276     private final float mFullSizeAspectRatio;
277     /** The Camera2 API capture session currently active. */
278     private CameraCaptureSession mCaptureSession;
279     /** The surface onto which to render the preview. */
280     private Surface mPreviewSurface;
281     /**
282      * A queue of capture requests that have been requested but are not done
283      * yet.
284      */
285     private final LinkedList<InFlightCapture> mCaptureQueue =
286             new LinkedList<InFlightCapture>();
287     /** Whether closing of this device has been requested. */
288     private volatile boolean mIsClosed = false;
289 
290     /** Receives the normal captured images. */
291     private final ImageReader mCaptureImageReader;
292     ImageReader.OnImageAvailableListener mCaptureImageListener =
293             new ImageReader.OnImageAvailableListener() {
294                 @Override
295                 public void onImageAvailable(ImageReader reader) {
296                     // Add the image data to the latest in-flight capture.
297                     // If all the data for that capture is complete, store the
298                     // image data.
299                     InFlightCapture capture = null;
300                     synchronized (mCaptureQueue) {
301                         if (mCaptureQueue.getFirst().setImage(reader.acquireLatestImage())
302                                 .isCaptureComplete()) {
303                             capture = mCaptureQueue.removeFirst();
304                         }
305                     }
306                     if (capture != null) {
307                         onCaptureCompleted(capture);
308                     }
309                 }
310             };
311 
312     /**
313      * Instantiates a new camera based on Camera 2 API.
314      *
315      * @param device The underlying Camera 2 device.
316      * @param characteristics The device's characteristics.
317      * @param pictureSize the size of the final image to be taken.
318      */
OneCameraImpl(CameraDevice device, CameraCharacteristics characteristics, Size pictureSize)319     OneCameraImpl(CameraDevice device, CameraCharacteristics characteristics, Size pictureSize) {
320         mDevice = device;
321         mCharacteristics = characteristics;
322         mLensRange = LensRangeCalculator.getDiopterToRatioCalculator(characteristics);
323         mDirectionProvider = new CameraDirectionProvider(characteristics);
324         mFullSizeAspectRatio = calculateFullSizeAspectRatio(characteristics);
325         mRawDirectory = new File(Storage.instance().DIRECTORY, "DNG");
326 
327         // Override pictureSize for RAW (our picture size settings don't include
328         // RAW, which typically only supports one size (sensor size). This also
329         // typically differs from the larges JPEG or YUV size.
330         // TODO: If we ever want to support RAW properly, it should be one entry
331         // in the picture quality list, which should then lead to the right
332         // pictureSize being passes into here.
333         if (sCaptureImageFormat == ImageFormat.RAW_SENSOR) {
334             pictureSize = getDefaultPictureSize();
335         }
336 
337         mCameraThread = new HandlerThread("OneCamera2");
338         mCameraThread.start();
339         mCameraHandler = new Handler(mCameraThread.getLooper());
340 
341         mCaptureImageReader = ImageReader.newInstance(pictureSize.getWidth(),
342                 pictureSize.getHeight(),
343                 sCaptureImageFormat, 2);
344         mCaptureImageReader.setOnImageAvailableListener(mCaptureImageListener, mCameraHandler);
345         Log.d(TAG, "New Camera2 based OneCameraImpl created.");
346     }
347 
348     /**
349      * Take picture, initiating an auto focus scan if needed.
350      */
351     @Override
takePicture(final PhotoCaptureParameters params, final CaptureSession session)352     public void takePicture(final PhotoCaptureParameters params, final CaptureSession session) {
353         // Do not do anything when a picture is already requested.
354         if (mTakePictureWhenLensIsStopped) {
355             return;
356         }
357 
358         // Not ready until the picture comes back.
359         broadcastReadyState(false);
360 
361         mTakePictureRunnable = new Runnable() {
362             @Override
363             public void run() {
364                 takePictureNow(params, session);
365             }
366         };
367         mLastPictureCallback = params.callback;
368         mTakePictureStartMillis = SystemClock.uptimeMillis();
369 
370         // This class implements a very simple version of AF, which
371         // only delays capture if the lens is scanning.
372         if (mLastResultAFState == AutoFocusState.ACTIVE_SCAN) {
373             Log.v(TAG, "Waiting until scan is done before taking shot.");
374             mTakePictureWhenLensIsStopped = true;
375         } else {
376             // We could do CONTROL_AF_TRIGGER_START and wait until lens locks,
377             // but this would slow down the capture.
378             takePictureNow(params, session);
379         }
380     }
381 
382     /**
383      * Take picture immediately. Parameters passed through from takePicture().
384      */
takePictureNow(PhotoCaptureParameters params, CaptureSession session)385     public void takePictureNow(PhotoCaptureParameters params, CaptureSession session) {
386         long dt = SystemClock.uptimeMillis() - mTakePictureStartMillis;
387         Log.v(TAG, "Taking shot with extra AF delay of " + dt + " ms.");
388         try {
389             // JPEG capture.
390             CaptureRequest.Builder builder = mDevice
391                     .createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
392             builder.setTag(RequestTag.CAPTURE);
393             addBaselineCaptureKeysToRequest(builder);
394 
395             // Enable lens-shading correction for even better DNGs.
396             if (sCaptureImageFormat == ImageFormat.RAW_SENSOR) {
397                 builder.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
398                         CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE_ON);
399             } else if (sCaptureImageFormat == ImageFormat.JPEG) {
400                 builder.set(CaptureRequest.JPEG_QUALITY, JPEG_QUALITY);
401                 builder.set(CaptureRequest.JPEG_ORIENTATION,
402                         CameraUtil.getJpegRotation(params.orientation, mCharacteristics));
403             }
404 
405             builder.addTarget(mPreviewSurface);
406             builder.addTarget(mCaptureImageReader.getSurface());
407             CaptureRequest request = builder.build();
408 
409             if (DEBUG_WRITE_CAPTURE_DATA) {
410                 final String debugDataDir = makeDebugDir(params.debugDataFolder,
411                         "normal_capture_debug");
412                 Log.i(TAG, "Writing capture data to: " + debugDataDir);
413                 CaptureDataSerializer.toFile("Normal Capture", request, new File(debugDataDir,
414                         "capture.txt"));
415             }
416 
417             mCaptureSession.capture(request, mCaptureCallback, mCameraHandler);
418         } catch (CameraAccessException e) {
419             Log.e(TAG, "Could not access camera for still image capture.");
420             broadcastReadyState(true);
421             params.callback.onPictureTakingFailed();
422             return;
423         }
424         synchronized (mCaptureQueue) {
425             mCaptureQueue.add(new InFlightCapture(params, session));
426         }
427     }
428 
429     @Override
startPreview(Surface previewSurface, CaptureReadyCallback listener)430     public void startPreview(Surface previewSurface, CaptureReadyCallback listener) {
431         mPreviewSurface = previewSurface;
432         setupAsync(mPreviewSurface, listener);
433     }
434 
435     @Override
close()436     public void close() {
437         if (mIsClosed) {
438             Log.w(TAG, "Camera is already closed.");
439             return;
440         }
441         try {
442             if (mCaptureSession != null) {
443                 mCaptureSession.abortCaptures();
444             }
445         } catch (CameraAccessException e) {
446             Log.e(TAG, "Could not abort captures in progress.");
447         }
448         mIsClosed = true;
449         mCameraThread.quitSafely();
450         mDevice.close();
451     }
452 
getSupportedPreviewSizes()453     public Size[] getSupportedPreviewSizes() {
454         StreamConfigurationMap config = mCharacteristics
455                 .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
456         return Size.convert(config.getOutputSizes(SurfaceTexture.class));
457     }
458 
getFullSizeAspectRatio()459     public float getFullSizeAspectRatio() {
460         return mFullSizeAspectRatio;
461     }
462 
463     @Override
getDirection()464     public Facing getDirection() {
465         return mDirectionProvider.getDirection();
466     }
467 
saveJpegPicture(byte[] jpegData, final PhotoCaptureParameters captureParams, CaptureSession session, CaptureResult result)468     private void saveJpegPicture(byte[] jpegData, final PhotoCaptureParameters captureParams,
469             CaptureSession session, CaptureResult result) {
470         int heading = captureParams.heading;
471         int width = 0;
472         int height = 0;
473         int rotation = 0;
474         ExifInterface exif = null;
475         try {
476             exif = new ExifInterface();
477             exif.readExif(jpegData);
478 
479             Integer w = exif.getTagIntValue(ExifInterface.TAG_PIXEL_X_DIMENSION);
480             width = (w == null) ? width : w;
481             Integer h = exif.getTagIntValue(ExifInterface.TAG_PIXEL_Y_DIMENSION);
482             height = (h == null) ? height : h;
483 
484             // Get image rotation from EXIF.
485             rotation = Exif.getOrientation(exif);
486 
487             // Set GPS heading direction based on sensor, if location is on.
488             if (heading >= 0) {
489                 ExifTag directionRefTag = exif.buildTag(
490                         ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
491                         ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
492                 ExifTag directionTag = exif.buildTag(
493                         ExifInterface.TAG_GPS_IMG_DIRECTION,
494                         new Rational(heading, 1));
495                 exif.setTag(directionRefTag);
496                 exif.setTag(directionTag);
497             }
498             new ExifUtil(exif).populateExif(Optional.<TaskImageContainer.TaskImage> absent(),
499                     Optional.of((CaptureResultProxy) new AndroidCaptureResultProxy(result)),
500                     Optional.<Location> absent());
501         } catch (IOException e) {
502             Log.w(TAG, "Could not read exif from gcam jpeg", e);
503             exif = null;
504         }
505         ListenableFuture<Optional<Uri>> futureUri = session.saveAndFinish(jpegData, width, height,
506                 rotation, exif);
507         Futures.addCallback(futureUri, new FutureCallback<Optional<Uri>>() {
508             @Override
509             public void onSuccess(Optional<Uri> uriOptional) {
510                 captureParams.callback.onPictureSaved(uriOptional.orNull());
511             }
512 
513             @Override
514             public void onFailure(Throwable throwable) {
515                 captureParams.callback.onPictureSaved(null);
516             }
517         }, MoreExecutors.directExecutor());
518     }
519 
520     /**
521      * Asynchronously sets up the capture session.
522      *
523      * @param previewSurface the surface onto which the preview should be
524      *            rendered.
525      * @param listener called when setup is completed.
526      */
setupAsync(final Surface previewSurface, final CaptureReadyCallback listener)527     private void setupAsync(final Surface previewSurface, final CaptureReadyCallback listener) {
528         mCameraHandler.post(new Runnable() {
529             @Override
530             public void run() {
531                 setup(previewSurface, listener);
532             }
533         });
534     }
535 
536     /**
537      * Configures and attempts to create a capture session.
538      *
539      * @param previewSurface the surface onto which the preview should be
540      *            rendered.
541      * @param listener called when the setup is completed.
542      */
setup(Surface previewSurface, final CaptureReadyCallback listener)543     private void setup(Surface previewSurface, final CaptureReadyCallback listener) {
544         try {
545             if (mCaptureSession != null) {
546                 mCaptureSession.abortCaptures();
547                 mCaptureSession = null;
548             }
549             List<Surface> outputSurfaces = new ArrayList<Surface>(2);
550             outputSurfaces.add(previewSurface);
551             outputSurfaces.add(mCaptureImageReader.getSurface());
552 
553             mDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
554 
555                 @Override
556                 public void onConfigureFailed(CameraCaptureSession session) {
557                     listener.onSetupFailed();
558                 }
559 
560                 @Override
561                 public void onConfigured(CameraCaptureSession session) {
562                     mCaptureSession = session;
563                     mAFRegions = ZERO_WEIGHT_3A_REGION;
564                     mAERegions = ZERO_WEIGHT_3A_REGION;
565                     mZoomValue = 1f;
566                     mCropRegion = cropRegionForZoom(mZoomValue);
567                     boolean success = repeatingPreview(null);
568                     if (success) {
569                         listener.onReadyForCapture();
570                     } else {
571                         listener.onSetupFailed();
572                     }
573                 }
574 
575                 @Override
576                 public void onClosed(CameraCaptureSession session) {
577                     super.onClosed(session);
578                 }
579             }, mCameraHandler);
580         } catch (CameraAccessException ex) {
581             Log.e(TAG, "Could not set up capture session", ex);
582             listener.onSetupFailed();
583         }
584     }
585 
586     /**
587      * Adds current regions to CaptureRequest and base AF mode +
588      * AF_TRIGGER_IDLE.
589      *
590      * @param builder Build for the CaptureRequest
591      */
addBaselineCaptureKeysToRequest(CaptureRequest.Builder builder)592     private void addBaselineCaptureKeysToRequest(CaptureRequest.Builder builder) {
593         builder.set(CaptureRequest.CONTROL_AF_REGIONS, mAFRegions);
594         builder.set(CaptureRequest.CONTROL_AE_REGIONS, mAERegions);
595         builder.set(CaptureRequest.SCALER_CROP_REGION, mCropRegion);
596         builder.set(CaptureRequest.CONTROL_AF_MODE, mControlAFMode);
597         builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
598         // Enable face detection
599         builder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE,
600                 CaptureRequest.STATISTICS_FACE_DETECT_MODE_FULL);
601         builder.set(CaptureRequest.CONTROL_SCENE_MODE,
602                 CaptureRequest.CONTROL_SCENE_MODE_FACE_PRIORITY);
603     }
604 
605     /**
606      * Request preview capture stream with AF_MODE_CONTINUOUS_PICTURE.
607      *
608      * @return true if request was build and sent successfully.
609      * @param tag
610      */
repeatingPreview(Object tag)611     private boolean repeatingPreview(Object tag) {
612         try {
613             CaptureRequest.Builder builder = mDevice.
614                     createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
615             builder.addTarget(mPreviewSurface);
616             builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
617             addBaselineCaptureKeysToRequest(builder);
618             mCaptureSession.setRepeatingRequest(builder.build(), mCaptureCallback,
619                     mCameraHandler);
620             Log.v(TAG, String.format("Sent repeating Preview request, zoom = %.2f", mZoomValue));
621             return true;
622         } catch (CameraAccessException ex) {
623             Log.e(TAG, "Could not access camera setting up preview.", ex);
624             return false;
625         }
626     }
627 
628     /**
629      * Request preview capture stream with auto focus trigger cycle.
630      */
sendAutoFocusTriggerCaptureRequest(Object tag)631     private void sendAutoFocusTriggerCaptureRequest(Object tag) {
632         try {
633             // Step 1: Request single frame CONTROL_AF_TRIGGER_START.
634             CaptureRequest.Builder builder;
635             builder = mDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
636             builder.addTarget(mPreviewSurface);
637             builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
638             mControlAFMode = CameraMetadata.CONTROL_AF_MODE_AUTO;
639             addBaselineCaptureKeysToRequest(builder);
640             builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
641             builder.setTag(tag);
642             mCaptureSession.capture(builder.build(), mCaptureCallback, mCameraHandler);
643 
644             // Step 2: Call repeatingPreview to update mControlAFMode.
645             repeatingPreview(tag);
646             resumeContinuousAFAfterDelay(FOCUS_HOLD_MILLIS);
647         } catch (CameraAccessException ex) {
648             Log.e(TAG, "Could not execute preview request.", ex);
649         }
650     }
651 
652     /**
653      * Resume AF_MODE_CONTINUOUS_PICTURE after FOCUS_HOLD_MILLIS.
654      */
resumeContinuousAFAfterDelay(int millis)655     private void resumeContinuousAFAfterDelay(int millis) {
656         mCameraHandler.removeCallbacks(mReturnToContinuousAFRunnable);
657         mCameraHandler.postDelayed(mReturnToContinuousAFRunnable, millis);
658     }
659 
660     /**
661      * This method takes appropriate action if camera2 AF state changes.
662      * <ol>
663      * <li>Reports changes in camera2 AF state to OneCamera.FocusStateListener.</li>
664      * <li>Take picture after AF scan if mTakePictureWhenLensIsStopped true.</li>
665      * </ol>
666      */
autofocusStateChangeDispatcher(CaptureResult result)667     private void autofocusStateChangeDispatcher(CaptureResult result) {
668         if (result.getFrameNumber() < mLastControlAfStateFrameNumber ||
669                 result.get(CaptureResult.CONTROL_AF_STATE) == null) {
670             return;
671         }
672         mLastControlAfStateFrameNumber = result.getFrameNumber();
673 
674         // Convert to OneCamera mode and state.
675         AutoFocusState resultAFState = AutoFocusHelper.
676                 stateFromCamera2State(result.get(CaptureResult.CONTROL_AF_STATE));
677 
678         // TODO: Consider using LENS_STATE.
679         boolean lensIsStopped = resultAFState == AutoFocusState.ACTIVE_FOCUSED ||
680                 resultAFState == AutoFocusState.ACTIVE_UNFOCUSED ||
681                 resultAFState == AutoFocusState.PASSIVE_FOCUSED ||
682                 resultAFState == AutoFocusState.PASSIVE_UNFOCUSED;
683 
684         if (mTakePictureWhenLensIsStopped && lensIsStopped) {
685             // Take the shot.
686             mCameraHandler.post(mTakePictureRunnable);
687             mTakePictureWhenLensIsStopped = false;
688         }
689 
690         // Report state change when AF state has changed.
691         if (resultAFState != mLastResultAFState && mFocusStateListener != null) {
692             mFocusStateListener.onFocusStatusUpdate(resultAFState, result.getFrameNumber());
693         }
694         mLastResultAFState = resultAFState;
695     }
696 
697     @Override
triggerFocusAndMeterAtPoint(float nx, float ny)698     public void triggerFocusAndMeterAtPoint(float nx, float ny) {
699         int sensorOrientation = mCharacteristics.get(
700                 CameraCharacteristics.SENSOR_ORIENTATION);
701         mAERegions = AutoFocusHelper.aeRegionsForNormalizedCoord(nx, ny, mCropRegion,
702                 sensorOrientation);
703         mAFRegions = AutoFocusHelper.afRegionsForNormalizedCoord(nx, ny, mCropRegion,
704                 sensorOrientation);
705 
706         sendAutoFocusTriggerCaptureRequest(RequestTag.TAP_TO_FOCUS);
707     }
708 
709     @Override
getMaxZoom()710     public float getMaxZoom() {
711         return mCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
712     }
713 
714     @Override
setZoom(float zoom)715     public void setZoom(float zoom) {
716         mZoomValue = zoom;
717         mCropRegion = cropRegionForZoom(zoom);
718         repeatingPreview(null);
719     }
720 
721     @Override
pickPreviewSize(Size pictureSize, Activity context)722     public Size pickPreviewSize(Size pictureSize, Activity context) {
723         if (pictureSize == null) {
724             // TODO The default should be selected by the caller, and
725             // pictureSize should never be null.
726             pictureSize = getDefaultPictureSize();
727         }
728         float pictureAspectRatio = pictureSize.getWidth() / (float) pictureSize.getHeight();
729         Size[] supportedSizes = getSupportedPreviewSizes();
730 
731         // Since devices only have one raw resolution we need to be more
732         // flexible for selecting a matching preview resolution.
733         Double aspectRatioTolerance = sCaptureImageFormat == ImageFormat.RAW_SENSOR ? 10d : null;
734         Size size = CaptureModuleUtil.getOptimalPreviewSize(supportedSizes,
735                 pictureAspectRatio, aspectRatioTolerance, context);
736         Log.d(TAG, "Selected preview size: " + size);
737         return size;
738     }
739 
cropRegionForZoom(float zoom)740     private Rect cropRegionForZoom(float zoom) {
741         return AutoFocusHelper.cropRegionForZoom(mCharacteristics, zoom);
742     }
743 
744     /**
745      * Calculate the aspect ratio of the full size capture on this device.
746      *
747      * @param characteristics the characteristics of the camera device.
748      * @return The aspect ration, in terms of width/height of the full capture
749      *         size.
750      */
calculateFullSizeAspectRatio(CameraCharacteristics characteristics)751     private static float calculateFullSizeAspectRatio(CameraCharacteristics characteristics) {
752         Rect activeArraySize =
753                 characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
754         return ((float) (activeArraySize.width())) / activeArraySize.height();
755     }
756 
757     /*
758      * Called when a capture that is in flight is completed.
759      * @param capture the in-flight capture which needs to contain the received
760      * image and capture data
761      */
onCaptureCompleted(InFlightCapture capture)762     private void onCaptureCompleted(InFlightCapture capture) {
763 
764         // Experimental support for writing RAW. We do not have a usable JPEG
765         // here, so we don't use the usual capture session mechanism and instead
766         // just store the RAW file in its own directory.
767         // TODO: If we make this a real feature we should probably put the DNGs
768         // into the Camera directly.
769         if (sCaptureImageFormat == ImageFormat.RAW_SENSOR) {
770             if (!mRawDirectory.exists()) {
771                 if (!mRawDirectory.mkdirs()) {
772                     throw new RuntimeException("Could not create RAW directory.");
773                 }
774             }
775             File dngFile = new File(mRawDirectory, capture.session.getTitle() + ".dng");
776             writeDngBytesAndClose(capture.image, capture.totalCaptureResult,
777                     mCharacteristics, dngFile);
778         } else {
779             // Since this is not an HDR+ session, we will just save the
780             // result.
781             byte[] imageBytes = acquireJpegBytesAndClose(capture.image);
782             saveJpegPicture(imageBytes, capture.parameters, capture.session,
783                     capture.totalCaptureResult);
784         }
785         broadcastReadyState(true);
786         capture.parameters.callback.onPictureTaken(capture.session);
787     }
788 
789     /**
790      * Take the given RAW image and capture result, convert it to a DNG and
791      * write it to disk.
792      *
793      * @param image the image containing the 16-bit RAW data (RAW_SENSOR)
794      * @param captureResult the capture result for the image
795      * @param characteristics the camera characteristics of the camera that took
796      *            the RAW image
797      * @param dngFile the destination to where the resulting DNG data is written
798      *            to
799      */
writeDngBytesAndClose(Image image, TotalCaptureResult captureResult, CameraCharacteristics characteristics, File dngFile)800     private static void writeDngBytesAndClose(Image image, TotalCaptureResult captureResult,
801             CameraCharacteristics characteristics, File dngFile) {
802         try (DngCreator dngCreator = new DngCreator(characteristics, captureResult);
803                 FileOutputStream outputStream = new FileOutputStream(dngFile)) {
804             // TODO: Add DngCreator#setThumbnail and add the DNG to the normal
805             // filmstrip.
806             dngCreator.writeImage(outputStream, image);
807             outputStream.close();
808             image.close();
809         } catch (IOException e) {
810             Log.e(TAG, "Could not store DNG file", e);
811             return;
812         }
813         Log.i(TAG, "Successfully stored DNG file: " + dngFile.getAbsolutePath());
814     }
815 
816     /**
817      * Given an image reader, this extracts the final image. If the image in the
818      * reader is JPEG, we extract and return it as is. If the image is YUV, we
819      * convert it to JPEG and return the result.
820      *
821      * @param image the image we got from the image reader.
822      * @return A valid JPEG image.
823      */
acquireJpegBytesAndClose(Image image)824     private static byte[] acquireJpegBytesAndClose(Image image) {
825         ByteBuffer buffer;
826         if (image.getFormat() == ImageFormat.JPEG) {
827             Image.Plane plane0 = image.getPlanes()[0];
828             buffer = plane0.getBuffer();
829         } else if (image.getFormat() == ImageFormat.YUV_420_888) {
830             buffer = ByteBuffer.allocateDirect(image.getWidth() * image.getHeight() * 3);
831 
832             Log.v(TAG, "Compressing JPEG with software encoder.");
833             int numBytes = JpegUtilNative.compressJpegFromYUV420Image(
834                     new AndroidImageProxy(image), buffer, JPEG_QUALITY);
835 
836             if (numBytes < 0) {
837                 throw new RuntimeException("Error compressing jpeg.");
838             }
839             buffer.limit(numBytes);
840         } else {
841             throw new RuntimeException("Unsupported image format.");
842         }
843 
844         byte[] imageBytes = new byte[buffer.remaining()];
845         buffer.get(imageBytes);
846         buffer.rewind();
847         image.close();
848         return imageBytes;
849     }
850 
851     /**
852      * @return The largest supported picture size.
853      */
getDefaultPictureSize()854     public Size getDefaultPictureSize() {
855         StreamConfigurationMap configs =
856                 mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
857         android.util.Size[] supportedSizes = configs.getOutputSizes(sCaptureImageFormat);
858 
859         // Find the largest supported size.
860         android.util.Size largestSupportedSize = supportedSizes[0];
861         long largestSupportedSizePixels =
862                 largestSupportedSize.getWidth() * largestSupportedSize.getHeight();
863         for (int i = 1; i < supportedSizes.length; i++) {
864             long numPixels = supportedSizes[i].getWidth() * supportedSizes[i].getHeight();
865             if (numPixels > largestSupportedSizePixels) {
866                 largestSupportedSize = supportedSizes[i];
867                 largestSupportedSizePixels = numPixels;
868             }
869         }
870         return new Size(largestSupportedSize.getWidth(), largestSupportedSize.getHeight());
871     }
872 }
873