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
17package android.hardware.camera2.legacy;
18
19import android.graphics.ImageFormat;
20import android.graphics.SurfaceTexture;
21import android.hardware.Camera;
22import android.hardware.camera2.CameraCharacteristics;
23import android.hardware.camera2.CaptureRequest;
24import android.hardware.camera2.impl.CameraDeviceImpl;
25import android.hardware.camera2.params.StreamConfigurationMap;
26import android.hardware.camera2.utils.LongParcelable;
27import android.hardware.camera2.utils.SizeAreaComparator;
28import android.hardware.camera2.impl.CameraMetadataNative;
29import android.os.ConditionVariable;
30import android.os.Handler;
31import android.os.Message;
32import android.os.SystemClock;
33import android.util.Log;
34import android.util.MutableLong;
35import android.util.Pair;
36import android.util.Size;
37import android.view.Surface;
38
39import java.io.IOException;
40import java.util.ArrayList;
41import java.util.Arrays;
42import java.util.Collection;
43import java.util.Collections;
44import java.util.Iterator;
45import java.util.List;
46import java.util.concurrent.TimeUnit;
47import java.util.concurrent.atomic.AtomicBoolean;
48
49import static com.android.internal.util.Preconditions.*;
50
51/**
52 * This class executes requests to the {@link Camera}.
53 *
54 * <p>
55 * The main components of this class are:
56 * - A message queue of requests to the {@link Camera}.
57 * - A thread that consumes requests to the {@link Camera} and executes them.
58 * - A {@link GLThreadManager} that draws to the configured output {@link Surface}s.
59 * - An {@link CameraDeviceState} state machine that manages the callbacks for various operations.
60 * </p>
61 */
62@SuppressWarnings("deprecation")
63public class RequestThreadManager {
64    private final String TAG;
65    private final int mCameraId;
66    private final RequestHandlerThread mRequestThread;
67
68    private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
69    // For slightly more spammy messages that will get repeated every frame
70    private static final boolean VERBOSE =
71            Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.VERBOSE);
72    private Camera mCamera;
73    private final CameraCharacteristics mCharacteristics;
74
75    private final CameraDeviceState mDeviceState;
76    private final CaptureCollector mCaptureCollector;
77    private final LegacyFocusStateMapper mFocusStateMapper;
78    private final LegacyFaceDetectMapper mFaceDetectMapper;
79
80    private static final int MSG_CONFIGURE_OUTPUTS = 1;
81    private static final int MSG_SUBMIT_CAPTURE_REQUEST = 2;
82    private static final int MSG_CLEANUP = 3;
83
84    private static final int MAX_IN_FLIGHT_REQUESTS = 2;
85
86    private static final int PREVIEW_FRAME_TIMEOUT = 1000; // ms
87    private static final int JPEG_FRAME_TIMEOUT = 4000; // ms (same as CTS for API2)
88    private static final int REQUEST_COMPLETE_TIMEOUT = JPEG_FRAME_TIMEOUT; // ms (same as JPEG timeout)
89
90    private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
91    private boolean mPreviewRunning = false;
92
93    private final List<Surface> mPreviewOutputs = new ArrayList<>();
94    private final List<Surface> mCallbackOutputs = new ArrayList<>();
95    private GLThreadManager mGLThreadManager;
96    private SurfaceTexture mPreviewTexture;
97    private Camera.Parameters mParams;
98
99    private final List<Long> mJpegSurfaceIds = new ArrayList<>();
100
101    private Size mIntermediateBufferSize;
102
103    private final RequestQueue mRequestQueue = new RequestQueue(mJpegSurfaceIds);
104    private LegacyRequest mLastRequest = null;
105    private SurfaceTexture mDummyTexture;
106    private Surface mDummySurface;
107
108    private final Object mIdleLock = new Object();
109    private final FpsCounter mPrevCounter = new FpsCounter("Incoming Preview");
110    private final FpsCounter mRequestCounter = new FpsCounter("Incoming Requests");
111
112    private final AtomicBoolean mQuit = new AtomicBoolean(false);
113
114    // Stuff JPEGs into HAL_PIXEL_FORMAT_RGBA_8888 gralloc buffers to get around SW write
115    // limitations for (b/17379185).
116    private static final boolean USE_BLOB_FORMAT_OVERRIDE = true;
117
118    /**
119     * Container object for Configure messages.
120     */
121    private static class ConfigureHolder {
122        public final ConditionVariable condition;
123        public final Collection<Pair<Surface, Size>> surfaces;
124
125        public ConfigureHolder(ConditionVariable condition, Collection<Pair<Surface,
126                Size>> surfaces) {
127            this.condition = condition;
128            this.surfaces = surfaces;
129        }
130    }
131
132    /**
133     * Counter class used to calculate and log the current FPS of frame production.
134     */
135    public static class FpsCounter {
136        //TODO: Hook this up to SystTrace?
137        private static final String TAG = "FpsCounter";
138        private int mFrameCount = 0;
139        private long mLastTime = 0;
140        private long mLastPrintTime = 0;
141        private double mLastFps = 0;
142        private final String mStreamType;
143        private static final long NANO_PER_SECOND = 1000000000; //ns
144
145        public FpsCounter(String streamType) {
146            mStreamType = streamType;
147        }
148
149        public synchronized void countFrame() {
150            mFrameCount++;
151            long nextTime = SystemClock.elapsedRealtimeNanos();
152            if (mLastTime == 0) {
153                mLastTime = nextTime;
154            }
155            if (nextTime > mLastTime + NANO_PER_SECOND) {
156                long elapsed = nextTime - mLastTime;
157                mLastFps = mFrameCount * (NANO_PER_SECOND / (double) elapsed);
158                mFrameCount = 0;
159                mLastTime = nextTime;
160            }
161        }
162
163        public synchronized double checkFps() {
164            return mLastFps;
165        }
166
167        public synchronized void staggeredLog() {
168            if (mLastTime > mLastPrintTime + 5 * NANO_PER_SECOND) {
169                mLastPrintTime = mLastTime;
170                Log.d(TAG, "FPS for " + mStreamType + " stream: " + mLastFps );
171            }
172        }
173
174        public synchronized void countAndLog() {
175            countFrame();
176            staggeredLog();
177        }
178    }
179    /**
180     * Fake preview for jpeg captures when there is no active preview
181     */
182    private void createDummySurface() {
183        if (mDummyTexture == null || mDummySurface == null) {
184            mDummyTexture = new SurfaceTexture(/*ignored*/0);
185            // TODO: use smallest default sizes
186            mDummyTexture.setDefaultBufferSize(640, 480);
187            mDummySurface = new Surface(mDummyTexture);
188        }
189    }
190
191    private final Camera.ErrorCallback mErrorCallback = new Camera.ErrorCallback() {
192        @Override
193        public void onError(int i, Camera camera) {
194            Log.e(TAG, "Received error " + i + " from the Camera1 ErrorCallback");
195            mDeviceState.setError(CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
196        }
197    };
198
199    private final ConditionVariable mReceivedJpeg = new ConditionVariable(false);
200
201    private final Camera.PictureCallback mJpegCallback = new Camera.PictureCallback() {
202        @Override
203        public void onPictureTaken(byte[] data, Camera camera) {
204            Log.i(TAG, "Received jpeg.");
205            Pair<RequestHolder, Long> captureInfo = mCaptureCollector.jpegProduced();
206            if (captureInfo == null || captureInfo.first == null) {
207                Log.e(TAG, "Dropping jpeg frame.");
208                return;
209            }
210            RequestHolder holder = captureInfo.first;
211            long timestamp = captureInfo.second;
212            for (Surface s : holder.getHolderTargets()) {
213                try {
214                    if (LegacyCameraDevice.containsSurfaceId(s, mJpegSurfaceIds)) {
215                        Log.i(TAG, "Producing jpeg buffer...");
216
217                        int totalSize = data.length + LegacyCameraDevice.nativeGetJpegFooterSize();
218                        totalSize = (totalSize + 3) & ~0x3; // round up to nearest octonibble
219                        LegacyCameraDevice.setNextTimestamp(s, timestamp);
220
221                        if (USE_BLOB_FORMAT_OVERRIDE) {
222                            // Override to RGBA_8888 format.
223                            LegacyCameraDevice.setSurfaceFormat(s,
224                                    LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888);
225
226                            int dimen = (int) Math.ceil(Math.sqrt(totalSize));
227                            dimen = (dimen + 0xf) & ~0xf; // round up to nearest multiple of 16
228                            LegacyCameraDevice.setSurfaceDimens(s, dimen, dimen);
229                            LegacyCameraDevice.produceFrame(s, data, dimen, dimen,
230                                    CameraMetadataNative.NATIVE_JPEG_FORMAT);
231                        } else {
232                            LegacyCameraDevice.setSurfaceDimens(s, totalSize, /*height*/1);
233                            LegacyCameraDevice.produceFrame(s, data, totalSize, /*height*/1,
234                                    CameraMetadataNative.NATIVE_JPEG_FORMAT);
235                        }
236                    }
237                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
238                    Log.w(TAG, "Surface abandoned, dropping frame. ", e);
239                }
240            }
241
242            mReceivedJpeg.open();
243        }
244    };
245
246    private final Camera.ShutterCallback mJpegShutterCallback = new Camera.ShutterCallback() {
247        @Override
248        public void onShutter() {
249            mCaptureCollector.jpegCaptured(SystemClock.elapsedRealtimeNanos());
250        }
251    };
252
253    private final SurfaceTexture.OnFrameAvailableListener mPreviewCallback =
254            new SurfaceTexture.OnFrameAvailableListener() {
255                @Override
256                public void onFrameAvailable(SurfaceTexture surfaceTexture) {
257                    if (DEBUG) {
258                        mPrevCounter.countAndLog();
259                    }
260                    mGLThreadManager.queueNewFrame();
261                }
262            };
263
264    private void stopPreview() {
265        if (VERBOSE) {
266            Log.v(TAG, "stopPreview - preview running? " + mPreviewRunning);
267        }
268        if (mPreviewRunning) {
269            mCamera.stopPreview();
270            mPreviewRunning = false;
271        }
272    }
273
274    private void startPreview() {
275        if (VERBOSE) {
276            Log.v(TAG, "startPreview - preview running? " + mPreviewRunning);
277        }
278        if (!mPreviewRunning) {
279            // XX: CameraClient:;startPreview is not getting called after a stop
280            mCamera.startPreview();
281            mPreviewRunning = true;
282        }
283    }
284
285    private void doJpegCapturePrepare(RequestHolder request) throws IOException {
286        if (DEBUG) Log.d(TAG, "doJpegCapturePrepare - preview running? " + mPreviewRunning);
287
288        if (!mPreviewRunning) {
289            if (DEBUG) Log.d(TAG, "doJpegCapture - create fake surface");
290
291            createDummySurface();
292            mCamera.setPreviewTexture(mDummyTexture);
293            startPreview();
294        }
295    }
296
297    private void doJpegCapture(RequestHolder request) {
298        if (DEBUG) Log.d(TAG, "doJpegCapturePrepare");
299
300        mCamera.takePicture(mJpegShutterCallback, /*raw*/null, mJpegCallback);
301        mPreviewRunning = false;
302    }
303
304    private void doPreviewCapture(RequestHolder request) throws IOException {
305        if (VERBOSE) {
306            Log.v(TAG, "doPreviewCapture - preview running? " + mPreviewRunning);
307        }
308
309        if (mPreviewRunning) {
310            return; // Already running
311        }
312
313        if (mPreviewTexture == null) {
314            throw new IllegalStateException(
315                    "Preview capture called with no preview surfaces configured.");
316        }
317
318        mPreviewTexture.setDefaultBufferSize(mIntermediateBufferSize.getWidth(),
319                mIntermediateBufferSize.getHeight());
320        mCamera.setPreviewTexture(mPreviewTexture);
321
322        startPreview();
323    }
324
325    private void configureOutputs(Collection<Pair<Surface, Size>> outputs) {
326        if (DEBUG) {
327            String outputsStr = outputs == null ? "null" : (outputs.size() + " surfaces");
328            Log.d(TAG, "configureOutputs with " + outputsStr);
329        }
330
331        try {
332            stopPreview();
333        }  catch (RuntimeException e) {
334            Log.e(TAG, "Received device exception in configure call: ", e);
335            mDeviceState.setError(
336                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
337            return;
338        }
339
340        /*
341         * Try to release the previous preview's surface texture earlier if we end up
342         * using a different one; this also reduces the likelihood of getting into a deadlock
343         * when disconnecting from the old previous texture at a later time.
344         */
345        try {
346            mCamera.setPreviewTexture(/*surfaceTexture*/null);
347        } catch (IOException e) {
348            Log.w(TAG, "Failed to clear prior SurfaceTexture, may cause GL deadlock: ", e);
349        } catch (RuntimeException e) {
350            Log.e(TAG, "Received device exception in configure call: ", e);
351            mDeviceState.setError(
352                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
353            return;
354        }
355
356        if (mGLThreadManager != null) {
357            mGLThreadManager.waitUntilStarted();
358            mGLThreadManager.ignoreNewFrames();
359            mGLThreadManager.waitUntilIdle();
360        }
361        resetJpegSurfaceFormats(mCallbackOutputs);
362        mPreviewOutputs.clear();
363        mCallbackOutputs.clear();
364        mJpegSurfaceIds.clear();
365        mPreviewTexture = null;
366
367        List<Size> previewOutputSizes = new ArrayList<>();
368        List<Size> callbackOutputSizes = new ArrayList<>();
369
370        int facing = mCharacteristics.get(CameraCharacteristics.LENS_FACING);
371        int orientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
372        if (outputs != null) {
373            for (Pair<Surface, Size> outPair : outputs) {
374                Surface s = outPair.first;
375                Size outSize = outPair.second;
376                try {
377                    int format = LegacyCameraDevice.detectSurfaceType(s);
378                    LegacyCameraDevice.setSurfaceOrientation(s, facing, orientation);
379                    switch (format) {
380                        case CameraMetadataNative.NATIVE_JPEG_FORMAT:
381                            if (USE_BLOB_FORMAT_OVERRIDE) {
382                                // Override to RGBA_8888 format.
383                                LegacyCameraDevice.setSurfaceFormat(s,
384                                        LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888);
385                            }
386                            mJpegSurfaceIds.add(LegacyCameraDevice.getSurfaceId(s));
387                            mCallbackOutputs.add(s);
388                            callbackOutputSizes.add(outSize);
389                            break;
390                        default:
391                            mPreviewOutputs.add(s);
392                            previewOutputSizes.add(outSize);
393                            break;
394                    }
395                } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
396                    Log.w(TAG, "Surface abandoned, skipping...", e);
397                }
398            }
399        }
400        try {
401            mParams = mCamera.getParameters();
402        } catch (RuntimeException e) {
403            Log.e(TAG, "Received device exception: ", e);
404            mDeviceState.setError(
405                CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
406            return;
407        }
408
409        List<int[]> supportedFpsRanges = mParams.getSupportedPreviewFpsRange();
410        int[] bestRange = getPhotoPreviewFpsRange(supportedFpsRanges);
411        if (DEBUG) {
412            Log.d(TAG, "doPreviewCapture - Selected range [" +
413                    bestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] + "," +
414                    bestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] + "]");
415        }
416        mParams.setPreviewFpsRange(bestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
417                bestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
418
419        if (previewOutputSizes.size() > 0) {
420
421            Size largestOutput = SizeAreaComparator.findLargestByArea(previewOutputSizes);
422
423            // Find largest jpeg dimension - assume to have the same aspect ratio as sensor.
424            Size largestJpegDimen = ParameterUtils.getLargestSupportedJpegSizeByArea(mParams);
425
426            List<Size> supportedPreviewSizes = ParameterUtils.convertSizeList(
427                    mParams.getSupportedPreviewSizes());
428
429            // Use smallest preview dimension with same aspect ratio as sensor that is >= than all
430            // of the configured output dimensions.  If none exists, fall back to using the largest
431            // supported preview size.
432            long largestOutputArea = largestOutput.getHeight() * (long) largestOutput.getWidth();
433            Size bestPreviewDimen = SizeAreaComparator.findLargestByArea(supportedPreviewSizes);
434            for (Size s : supportedPreviewSizes) {
435                long currArea = s.getWidth() * s.getHeight();
436                long bestArea = bestPreviewDimen.getWidth() * bestPreviewDimen.getHeight();
437                if (checkAspectRatiosMatch(largestJpegDimen, s) && (currArea < bestArea &&
438                        currArea >= largestOutputArea)) {
439                    bestPreviewDimen = s;
440                }
441            }
442
443            mIntermediateBufferSize = bestPreviewDimen;
444            mParams.setPreviewSize(mIntermediateBufferSize.getWidth(),
445                    mIntermediateBufferSize.getHeight());
446
447            if (DEBUG) {
448                Log.d(TAG, "Intermediate buffer selected with dimens: " +
449                        bestPreviewDimen.toString());
450            }
451        } else {
452            mIntermediateBufferSize = null;
453            if (DEBUG) {
454                Log.d(TAG, "No Intermediate buffer selected, no preview outputs were configured");
455            }
456        }
457
458        Size smallestSupportedJpegSize = calculatePictureSize(mCallbackOutputs,
459                callbackOutputSizes, mParams);
460        if (smallestSupportedJpegSize != null) {
461            /*
462             * Set takePicture size to the smallest supported JPEG size large enough
463             * to scale/crop out of for the bounding rectangle of the configured JPEG sizes.
464             */
465
466            Log.i(TAG, "configureOutputs - set take picture size to " + smallestSupportedJpegSize);
467            mParams.setPictureSize(
468                    smallestSupportedJpegSize.getWidth(), smallestSupportedJpegSize.getHeight());
469        }
470
471        // TODO: Detect and optimize single-output paths here to skip stream teeing.
472        if (mGLThreadManager == null) {
473            mGLThreadManager = new GLThreadManager(mCameraId, facing, mDeviceState);
474            mGLThreadManager.start();
475        }
476        mGLThreadManager.waitUntilStarted();
477        List<Pair<Surface, Size>> previews = new ArrayList<>();
478        Iterator<Size> previewSizeIter = previewOutputSizes.iterator();
479        for (Surface p : mPreviewOutputs) {
480            previews.add(new Pair<>(p, previewSizeIter.next()));
481        }
482        mGLThreadManager.setConfigurationAndWait(previews, mCaptureCollector);
483        mGLThreadManager.allowNewFrames();
484        mPreviewTexture = mGLThreadManager.getCurrentSurfaceTexture();
485        if (mPreviewTexture != null) {
486            mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback);
487        }
488
489        try {
490            mCamera.setParameters(mParams);
491        } catch (RuntimeException e) {
492                Log.e(TAG, "Received device exception while configuring: ", e);
493                mDeviceState.setError(
494                        CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
495
496        }
497    }
498
499    private void resetJpegSurfaceFormats(Collection<Surface> surfaces) {
500        if (!USE_BLOB_FORMAT_OVERRIDE || surfaces == null) {
501            return;
502        }
503        for(Surface s : surfaces) {
504            try {
505                LegacyCameraDevice.setSurfaceFormat(s, LegacyMetadataMapper.HAL_PIXEL_FORMAT_BLOB);
506            } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
507                Log.w(TAG, "Surface abandoned, skipping...", e);
508            }
509        }
510    }
511
512    /**
513     * Find a JPEG size (that is supported by the legacy camera device) which is equal to or larger
514     * than all of the configured {@code JPEG} outputs (by both width and height).
515     *
516     * <p>If multiple supported JPEG sizes are larger, select the smallest of them which
517     * still satisfies the above constraint.</p>
518     *
519     * <p>As a result, the returned size is guaranteed to be usable without needing
520     * to upscale any of the outputs. If only one {@code JPEG} surface is used,
521     * then no scaling/cropping is necessary between the taken picture and
522     * the {@code JPEG} output surface.</p>
523     *
524     * @param callbackOutputs a non-{@code null} list of {@code Surface}s with any image formats
525     * @param params api1 parameters (used for reading only)
526     *
527     * @return a size large enough to fit all of the configured {@code JPEG} outputs, or
528     *          {@code null} if the {@code callbackOutputs} did not have any {@code JPEG}
529     *          surfaces.
530     */
531    private Size calculatePictureSize( List<Surface> callbackOutputs,
532                                       List<Size> callbackSizes, Camera.Parameters params) {
533        /*
534         * Find the largest JPEG size (if any), from the configured outputs:
535         * - the api1 picture size should be set to the smallest legal size that's at least as large
536         *   as the largest configured JPEG size
537         */
538        if (callbackOutputs.size() != callbackSizes.size()) {
539            throw new IllegalStateException("Input collections must be same length");
540        }
541        List<Size> configuredJpegSizes = new ArrayList<>();
542        Iterator<Size> sizeIterator = callbackSizes.iterator();
543        for (Surface callbackSurface : callbackOutputs) {
544            Size jpegSize = sizeIterator.next();
545                if (!LegacyCameraDevice.containsSurfaceId(callbackSurface, mJpegSurfaceIds)) {
546                    continue; // Ignore non-JPEG callback formats
547                }
548
549                configuredJpegSizes.add(jpegSize);
550        }
551        if (!configuredJpegSizes.isEmpty()) {
552            /*
553             * Find the largest configured JPEG width, and height, independently
554             * of the rest.
555             *
556             * The rest of the JPEG streams can be cropped out of this smallest bounding
557             * rectangle.
558             */
559            int maxConfiguredJpegWidth = -1;
560            int maxConfiguredJpegHeight = -1;
561            for (Size jpegSize : configuredJpegSizes) {
562                maxConfiguredJpegWidth = jpegSize.getWidth() > maxConfiguredJpegWidth ?
563                        jpegSize.getWidth() : maxConfiguredJpegWidth;
564                maxConfiguredJpegHeight = jpegSize.getHeight() > maxConfiguredJpegHeight ?
565                        jpegSize.getHeight() : maxConfiguredJpegHeight;
566            }
567            Size smallestBoundJpegSize = new Size(maxConfiguredJpegWidth, maxConfiguredJpegHeight);
568
569            List<Size> supportedJpegSizes = ParameterUtils.convertSizeList(
570                    params.getSupportedPictureSizes());
571
572            /*
573             * Find the smallest supported JPEG size that can fit the smallest bounding
574             * rectangle for the configured JPEG sizes.
575             */
576            List<Size> candidateSupportedJpegSizes = new ArrayList<>();
577            for (Size supportedJpegSize : supportedJpegSizes) {
578                if (supportedJpegSize.getWidth() >= maxConfiguredJpegWidth &&
579                    supportedJpegSize.getHeight() >= maxConfiguredJpegHeight) {
580                    candidateSupportedJpegSizes.add(supportedJpegSize);
581                }
582            }
583
584            if (candidateSupportedJpegSizes.isEmpty()) {
585                throw new AssertionError(
586                        "Could not find any supported JPEG sizes large enough to fit " +
587                        smallestBoundJpegSize);
588            }
589
590            Size smallestSupportedJpegSize = Collections.min(candidateSupportedJpegSizes,
591                    new SizeAreaComparator());
592
593            if (!smallestSupportedJpegSize.equals(smallestBoundJpegSize)) {
594                Log.w(TAG,
595                        String.format(
596                                "configureOutputs - Will need to crop picture %s into "
597                                + "smallest bound size %s",
598                                smallestSupportedJpegSize, smallestBoundJpegSize));
599            }
600
601            return smallestSupportedJpegSize;
602        }
603
604        return null;
605    }
606
607    private static boolean checkAspectRatiosMatch(Size a, Size b) {
608        float aAspect = a.getWidth() / (float) a.getHeight();
609        float bAspect = b.getWidth() / (float) b.getHeight();
610
611        return Math.abs(aAspect - bAspect) < ASPECT_RATIO_TOLERANCE;
612    }
613
614    // Calculate the highest FPS range supported
615    private int[] getPhotoPreviewFpsRange(List<int[]> frameRates) {
616        if (frameRates.size() == 0) {
617            Log.e(TAG, "No supported frame rates returned!");
618            return null;
619        }
620
621        int bestMin = 0;
622        int bestMax = 0;
623        int bestIndex = 0;
624        int index = 0;
625        for (int[] rate : frameRates) {
626            int minFps = rate[Camera.Parameters.PREVIEW_FPS_MIN_INDEX];
627            int maxFps = rate[Camera.Parameters.PREVIEW_FPS_MAX_INDEX];
628            if (maxFps > bestMax || (maxFps == bestMax && minFps > bestMin)) {
629                bestMin = minFps;
630                bestMax = maxFps;
631                bestIndex = index;
632            }
633            index++;
634        }
635
636        return frameRates.get(bestIndex);
637    }
638
639    private final Handler.Callback mRequestHandlerCb = new Handler.Callback() {
640        private boolean mCleanup = false;
641        private final LegacyResultMapper mMapper = new LegacyResultMapper();
642
643        @Override
644        public boolean handleMessage(Message msg) {
645            if (mCleanup) {
646                return true;
647            }
648
649            if (DEBUG) {
650                Log.d(TAG, "Request thread handling message:" + msg.what);
651            }
652            long startTime = 0;
653            if (DEBUG) {
654                startTime = SystemClock.elapsedRealtimeNanos();
655            }
656            switch (msg.what) {
657                case MSG_CONFIGURE_OUTPUTS:
658                    ConfigureHolder config = (ConfigureHolder) msg.obj;
659                    int sizes = config.surfaces != null ? config.surfaces.size() : 0;
660                    Log.i(TAG, "Configure outputs: " + sizes + " surfaces configured.");
661
662                    try {
663                        boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT,
664                                TimeUnit.MILLISECONDS);
665                        if (!success) {
666                            Log.e(TAG, "Timed out while queueing configure request.");
667                            mCaptureCollector.failAll();
668                        }
669                    } catch (InterruptedException e) {
670                        Log.e(TAG, "Interrupted while waiting for requests to complete.");
671                        mDeviceState.setError(
672                                CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
673                        break;
674                    }
675
676                    configureOutputs(config.surfaces);
677                    config.condition.open();
678                    if (DEBUG) {
679                        long totalTime = SystemClock.elapsedRealtimeNanos() - startTime;
680                        Log.d(TAG, "Configure took " + totalTime + " ns");
681                    }
682                    break;
683                case MSG_SUBMIT_CAPTURE_REQUEST:
684                    Handler handler = RequestThreadManager.this.mRequestThread.getHandler();
685
686                    // Get the next burst from the request queue.
687                    Pair<BurstHolder, Long> nextBurst = mRequestQueue.getNext();
688
689                    if (nextBurst == null) {
690                        // If there are no further requests queued, wait for any currently executing
691                        // requests to complete, then switch to idle state.
692                        try {
693                            boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT,
694                                    TimeUnit.MILLISECONDS);
695                            if (!success) {
696                                Log.e(TAG,
697                                        "Timed out while waiting for prior requests to complete.");
698                                mCaptureCollector.failAll();
699                            }
700                        } catch (InterruptedException e) {
701                            Log.e(TAG, "Interrupted while waiting for requests to complete: ", e);
702                            mDeviceState.setError(
703                                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
704                            break;
705                        }
706
707                        synchronized (mIdleLock) {
708                            // Retry the the request queue.
709                            nextBurst = mRequestQueue.getNext();
710
711                            // If we still have no queued requests, go idle.
712                            if (nextBurst == null) {
713                                mDeviceState.setIdle();
714                                break;
715                            }
716                        }
717                    }
718
719                    if (nextBurst != null) {
720                        // Queue another capture if we did not get the last burst.
721                        handler.sendEmptyMessage(MSG_SUBMIT_CAPTURE_REQUEST);
722                    }
723
724                    // Complete each request in the burst
725                    List<RequestHolder> requests =
726                            nextBurst.first.produceRequestHolders(nextBurst.second);
727                    for (RequestHolder holder : requests) {
728                        CaptureRequest request = holder.getRequest();
729
730                        boolean paramsChanged = false;
731
732                        // Only update parameters if the request has changed
733                        if (mLastRequest == null || mLastRequest.captureRequest != request) {
734
735                            // The intermediate buffer is sometimes null, but we always need
736                            // the Camera1 API configured preview size
737                            Size previewSize = ParameterUtils.convertSize(mParams.getPreviewSize());
738
739                            LegacyRequest legacyRequest = new LegacyRequest(mCharacteristics,
740                                    request, previewSize, mParams); // params are copied
741
742
743                            // Parameters are mutated as a side-effect
744                            LegacyMetadataMapper.convertRequestMetadata(/*inout*/legacyRequest);
745
746                            // If the parameters have changed, set them in the Camera1 API.
747                            if (!mParams.same(legacyRequest.parameters)) {
748                                try {
749                                    mCamera.setParameters(legacyRequest.parameters);
750                                } catch (RuntimeException e) {
751                                    // If setting the parameters failed, report a request error to
752                                    // the camera client, and skip any further work for this request
753                                    Log.e(TAG, "Exception while setting camera parameters: ", e);
754                                    holder.failRequest();
755                                    mDeviceState.setCaptureStart(holder, /*timestamp*/0,
756                                            CameraDeviceImpl.CameraDeviceCallbacks.
757                                                    ERROR_CAMERA_REQUEST);
758                                    continue;
759                                }
760                                paramsChanged = true;
761                                mParams = legacyRequest.parameters;
762                            }
763
764                            mLastRequest = legacyRequest;
765                        }
766
767                        try {
768                            boolean success = mCaptureCollector.queueRequest(holder,
769                                    mLastRequest, JPEG_FRAME_TIMEOUT, TimeUnit.MILLISECONDS);
770
771                            if (!success) {
772                                // Report a request error if we timed out while queuing this.
773                                Log.e(TAG, "Timed out while queueing capture request.");
774                                holder.failRequest();
775                                mDeviceState.setCaptureStart(holder, /*timestamp*/0,
776                                        CameraDeviceImpl.CameraDeviceCallbacks.
777                                                ERROR_CAMERA_REQUEST);
778                                continue;
779                            }
780
781                            // Starting the preview needs to happen before enabling
782                            // face detection or auto focus
783                            if (holder.hasPreviewTargets()) {
784                                doPreviewCapture(holder);
785                            }
786                            if (holder.hasJpegTargets()) {
787                                while(!mCaptureCollector.waitForPreviewsEmpty(PREVIEW_FRAME_TIMEOUT,
788                                        TimeUnit.MILLISECONDS)) {
789                                    // Fail preview requests until the queue is empty.
790                                    Log.e(TAG, "Timed out while waiting for preview requests to " +
791                                            "complete.");
792                                    mCaptureCollector.failNextPreview();
793                                }
794                                mReceivedJpeg.close();
795                                doJpegCapturePrepare(holder);
796                            }
797
798                            /*
799                             * Do all the actions that require a preview to have been started
800                             */
801
802                            // Toggle face detection on/off
803                            // - do this before AF to give AF a chance to use faces
804                            mFaceDetectMapper.processFaceDetectMode(request, /*in*/mParams);
805
806                            // Unconditionally process AF triggers, since they're non-idempotent
807                            // - must be done after setting the most-up-to-date AF mode
808                            mFocusStateMapper.processRequestTriggers(request, mParams);
809
810                            if (holder.hasJpegTargets()) {
811                                doJpegCapture(holder);
812                                if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
813                                    Log.e(TAG, "Hit timeout for jpeg callback!");
814                                    mCaptureCollector.failNextJpeg();
815                                }
816                            }
817
818                        } catch (IOException e) {
819                            Log.e(TAG, "Received device exception during capture call: ", e);
820                            mDeviceState.setError(
821                                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
822                            break;
823                        } catch (InterruptedException e) {
824                            Log.e(TAG, "Interrupted during capture: ", e);
825                            mDeviceState.setError(
826                                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
827                            break;
828                        } catch (RuntimeException e) {
829                            Log.e(TAG, "Received device exception during capture call: ", e);
830                            mDeviceState.setError(
831                                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
832                            break;
833                        }
834
835                        if (paramsChanged) {
836                            if (DEBUG) {
837                                Log.d(TAG, "Params changed -- getting new Parameters from HAL.");
838                            }
839                            try {
840                                mParams = mCamera.getParameters();
841                            } catch (RuntimeException e) {
842                                Log.e(TAG, "Received device exception: ", e);
843                                mDeviceState.setError(
844                                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
845                                break;
846                            }
847
848                            // Update parameters to the latest that we think the camera is using
849                            mLastRequest.setParameters(mParams);
850                        }
851
852                        MutableLong timestampMutable = new MutableLong(/*value*/0L);
853                        try {
854                            boolean success = mCaptureCollector.waitForRequestCompleted(holder,
855                                    REQUEST_COMPLETE_TIMEOUT, TimeUnit.MILLISECONDS,
856                                    /*out*/timestampMutable);
857
858                            if (!success) {
859                                Log.e(TAG, "Timed out while waiting for request to complete.");
860                                mCaptureCollector.failAll();
861                            }
862                        } catch (InterruptedException e) {
863                            Log.e(TAG, "Interrupted waiting for request completion: ", e);
864                            mDeviceState.setError(
865                                    CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
866                            break;
867                        }
868
869                        CameraMetadataNative result = mMapper.cachedConvertResultMetadata(
870                                mLastRequest, timestampMutable.value);
871                        /*
872                         * Order matters: The default result mapper is state-less; the
873                         * other mappers carry state and may override keys set by the default
874                         * mapper with their own values.
875                         */
876
877                        // Update AF state
878                        mFocusStateMapper.mapResultTriggers(result);
879                        // Update face-related results
880                        mFaceDetectMapper.mapResultFaces(result, mLastRequest);
881
882                        if (!holder.requestFailed()) {
883                            mDeviceState.setCaptureResult(holder, result,
884                                    CameraDeviceState.NO_CAPTURE_ERROR);
885                        }
886                    }
887                    if (DEBUG) {
888                        long totalTime = SystemClock.elapsedRealtimeNanos() - startTime;
889                        Log.d(TAG, "Capture request took " + totalTime + " ns");
890                        mRequestCounter.countAndLog();
891                    }
892                    break;
893                case MSG_CLEANUP:
894                    mCleanup = true;
895                    try {
896                        boolean success = mCaptureCollector.waitForEmpty(JPEG_FRAME_TIMEOUT,
897                                TimeUnit.MILLISECONDS);
898                        if (!success) {
899                            Log.e(TAG, "Timed out while queueing cleanup request.");
900                            mCaptureCollector.failAll();
901                        }
902                    } catch (InterruptedException e) {
903                        Log.e(TAG, "Interrupted while waiting for requests to complete: ", e);
904                        mDeviceState.setError(
905                                CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
906                    }
907                    if (mGLThreadManager != null) {
908                        mGLThreadManager.quit();
909                        mGLThreadManager = null;
910                    }
911                    if (mCamera != null) {
912                        mCamera.release();
913                        mCamera = null;
914                    }
915                    resetJpegSurfaceFormats(mCallbackOutputs);
916                    break;
917                case RequestHandlerThread.MSG_POKE_IDLE_HANDLER:
918                    // OK: Ignore message.
919                    break;
920                default:
921                    throw new AssertionError("Unhandled message " + msg.what +
922                            " on RequestThread.");
923            }
924            return true;
925        }
926    };
927
928    /**
929     * Create a new RequestThreadManager.
930     *
931     * @param cameraId the id of the camera to use.
932     * @param camera an open camera object.  The RequestThreadManager takes ownership of this camera
933     *               object, and is responsible for closing it.
934     * @param characteristics the static camera characteristics corresponding to this camera device
935     * @param deviceState a {@link CameraDeviceState} state machine.
936     */
937    public RequestThreadManager(int cameraId, Camera camera, CameraCharacteristics characteristics,
938                                CameraDeviceState deviceState) {
939        mCamera = checkNotNull(camera, "camera must not be null");
940        mCameraId = cameraId;
941        mCharacteristics = checkNotNull(characteristics, "characteristics must not be null");
942        String name = String.format("RequestThread-%d", cameraId);
943        TAG = name;
944        mDeviceState = checkNotNull(deviceState, "deviceState must not be null");
945        mFocusStateMapper = new LegacyFocusStateMapper(mCamera);
946        mFaceDetectMapper = new LegacyFaceDetectMapper(mCamera, mCharacteristics);
947        mCaptureCollector = new CaptureCollector(MAX_IN_FLIGHT_REQUESTS, mDeviceState);
948        mRequestThread = new RequestHandlerThread(name, mRequestHandlerCb);
949        mCamera.setErrorCallback(mErrorCallback);
950    }
951
952    /**
953     * Start the request thread.
954     */
955    public void start() {
956        mRequestThread.start();
957    }
958
959    /**
960     * Flush any pending requests.
961     *
962     * @return the last frame number.
963     */
964    public long flush() {
965        Log.i(TAG, "Flushing all pending requests.");
966        long lastFrame = mRequestQueue.stopRepeating();
967        mCaptureCollector.failAll();
968        return lastFrame;
969    }
970
971    /**
972     * Quit the request thread, and clean up everything.
973     */
974    public void quit() {
975        if (!mQuit.getAndSet(true)) {  // Avoid sending messages on dead thread's handler.
976            Handler handler = mRequestThread.waitAndGetHandler();
977            handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
978            mRequestThread.quitSafely();
979            try {
980                mRequestThread.join();
981            } catch (InterruptedException e) {
982                Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
983                        mRequestThread.getName(), mRequestThread.getId()));
984            }
985        }
986    }
987
988    /**
989     * Submit the given burst of requests to be captured.
990     *
991     * <p>If the burst is repeating, replace the current repeating burst.</p>
992     *
993     * @param requests the burst of requests to add to the queue.
994     * @param repeating true if the burst is repeating.
995     * @param frameNumber an output argument that contains either the frame number of the last frame
996     *                    that will be returned for this request, or the frame number of the last
997     *                    frame that will be returned for the current repeating request if this
998     *                    burst is set to be repeating.
999     * @return the request id.
1000     */
1001    public int submitCaptureRequests(List<CaptureRequest> requests, boolean repeating,
1002            /*out*/LongParcelable frameNumber) {
1003        Handler handler = mRequestThread.waitAndGetHandler();
1004        int ret;
1005        synchronized (mIdleLock) {
1006            ret = mRequestQueue.submit(requests, repeating, frameNumber);
1007            handler.sendEmptyMessage(MSG_SUBMIT_CAPTURE_REQUEST);
1008        }
1009        return ret;
1010    }
1011
1012    /**
1013     * Cancel a repeating request.
1014     *
1015     * @param requestId the id of the repeating request to cancel.
1016     * @return the last frame to be returned from the HAL for the given repeating request, or
1017     *          {@code INVALID_FRAME} if none exists.
1018     */
1019    public long cancelRepeating(int requestId) {
1020        return mRequestQueue.stopRepeating(requestId);
1021    }
1022
1023    /**
1024     * Configure with the current list of output Surfaces.
1025     *
1026     * <p>
1027     * This operation blocks until the configuration is complete.
1028     * </p>
1029     *
1030     * <p>Using a {@code null} or empty {@code outputs} list is the equivalent of unconfiguring.</p>
1031     *
1032     * @param outputs a {@link java.util.Collection} of outputs to configure.
1033     */
1034    public void configure(Collection<Pair<Surface, Size>> outputs) {
1035        Handler handler = mRequestThread.waitAndGetHandler();
1036        final ConditionVariable condition = new ConditionVariable(/*closed*/false);
1037        ConfigureHolder holder = new ConfigureHolder(condition, outputs);
1038        handler.sendMessage(handler.obtainMessage(MSG_CONFIGURE_OUTPUTS, 0, 0, holder));
1039        condition.block();
1040    }
1041}
1042