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