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