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