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