RequestThreadManager.java revision 91b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0
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.CaptureRequest;
22import android.hardware.camera2.utils.LongParcelable;
23import android.hardware.camera2.impl.CameraMetadataNative;
24import android.os.ConditionVariable;
25import android.os.Handler;
26import android.os.Message;
27import android.os.SystemClock;
28import android.util.Log;
29import android.util.Pair;
30import android.util.Size;
31import android.view.Surface;
32
33import java.io.IOError;
34import java.io.IOException;
35import java.util.ArrayList;
36import java.util.Collection;
37import java.util.Collections;
38import java.util.Comparator;
39import java.util.List;
40
41/**
42 * This class executes requests to the {@link Camera}.
43 *
44 * <p>
45 * The main components of this class are:
46 * - A message queue of requests to the {@link Camera}.
47 * - A thread that consumes requests to the {@link Camera} and executes them.
48 * - A {@link GLThreadManager} that draws to the configured output {@link Surface}s.
49 * - An {@link CameraDeviceState} state machine that manages the callbacks for various operations.
50 * </p>
51 */
52public class RequestThreadManager {
53    private final String TAG;
54    private final int mCameraId;
55    private final RequestHandlerThread mRequestThread;
56
57    private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
58    private final Camera mCamera;
59
60    private final CameraDeviceState mDeviceState;
61
62    private static final int MSG_CONFIGURE_OUTPUTS = 1;
63    private static final int MSG_SUBMIT_CAPTURE_REQUEST = 2;
64    private static final int MSG_CLEANUP = 3;
65
66    private static final int PREVIEW_FRAME_TIMEOUT = 300; // ms
67    private static final int JPEG_FRAME_TIMEOUT = 1000; // ms
68
69    private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
70    private boolean mPreviewRunning = false;
71
72    private volatile long mLastJpegTimestamp;
73    private volatile long mLastPreviewTimestamp;
74    private volatile RequestHolder mInFlightPreview;
75    private volatile RequestHolder mInFlightJpeg;
76
77    private final List<Surface> mPreviewOutputs = new ArrayList<Surface>();
78    private final List<Surface> mCallbackOutputs = new ArrayList<Surface>();
79    private GLThreadManager mGLThreadManager;
80    private SurfaceTexture mPreviewTexture;
81    private Camera.Parameters mParams;
82
83    private Size mIntermediateBufferSize;
84
85    private final RequestQueue mRequestQueue = new RequestQueue();
86    private CaptureRequest mLastRequest = null;
87    private SurfaceTexture mDummyTexture;
88    private Surface mDummySurface;
89
90    private final FpsCounter mPrevCounter = new FpsCounter("Incoming Preview");
91    private final FpsCounter mRequestCounter = new FpsCounter("Incoming Requests");
92
93    /**
94     * Container object for Configure messages.
95     */
96    private static class ConfigureHolder {
97        public final ConditionVariable condition;
98        public final Collection<Surface> surfaces;
99
100        public ConfigureHolder(ConditionVariable condition, Collection<Surface> surfaces) {
101            this.condition = condition;
102            this.surfaces = surfaces;
103        }
104    }
105
106
107    /**
108     * Comparator for {@link Size} objects.
109     *
110     * <p>This comparator compares by rectangle area.  Tiebreaks on width.</p>
111     */
112    private static class SizeComparator implements Comparator<Size> {
113        @Override
114        public int compare(Size size, Size size2) {
115            if (size == null || size2 == null) {
116                throw new NullPointerException("Null argument passed to compare");
117            }
118            if (size.equals(size2)) return 0;
119            long width = size.getWidth();
120            long width2 = size2.getWidth();
121            long area = width * size.getHeight();
122            long area2 = width2 * size2.getHeight();
123            if (area == area2) {
124                return (width > width2) ? 1 : -1;
125            }
126            return (area > area2) ? 1 : -1;
127
128        }
129    }
130
131    /**
132     * Counter class used to calculate and log the current FPS of frame production.
133     */
134    public static class FpsCounter {
135        //TODO: Hook this up to SystTrace?
136        private static final String TAG = "FpsCounter";
137        private int mFrameCount = 0;
138        private long mLastTime = 0;
139        private long mLastPrintTime = 0;
140        private double mLastFps = 0;
141        private final String mStreamType;
142        private static final long NANO_PER_SECOND = 1000000000; //ns
143
144        public FpsCounter(String streamType) {
145            mStreamType = streamType;
146        }
147
148        public synchronized void countFrame() {
149            mFrameCount++;
150            long nextTime = SystemClock.elapsedRealtimeNanos();
151            if (mLastTime == 0) {
152                mLastTime = nextTime;
153            }
154            if (nextTime > mLastTime + NANO_PER_SECOND) {
155                long elapsed = nextTime - mLastTime;
156                mLastFps = mFrameCount * (NANO_PER_SECOND / (double) elapsed);
157                mFrameCount = 0;
158                mLastTime = nextTime;
159            }
160        }
161
162        public synchronized double checkFps() {
163            return mLastFps;
164        }
165
166        public synchronized void staggeredLog() {
167            if (mLastTime > mLastPrintTime + 5 * NANO_PER_SECOND) {
168                mLastPrintTime = mLastTime;
169                Log.d(TAG, "FPS for " + mStreamType + " stream: " + mLastFps );
170            }
171        }
172
173        public synchronized void countAndLog() {
174            countFrame();
175            staggeredLog();
176        }
177    }
178    /**
179     * Fake preview for jpeg captures when there is no active preview
180     */
181    private void createDummySurface() {
182        if (mDummyTexture == null || mDummySurface == null) {
183            mDummyTexture = new SurfaceTexture(/*ignored*/0);
184            // TODO: use smallest default sizes
185            mDummyTexture.setDefaultBufferSize(640, 480);
186            mDummySurface = new Surface(mDummyTexture);
187        }
188    }
189
190    private final ConditionVariable mReceivedJpeg = new ConditionVariable(false);
191    private final ConditionVariable mReceivedPreview = new ConditionVariable(false);
192
193    private final Camera.PictureCallback mJpegCallback = new Camera.PictureCallback() {
194        @Override
195        public void onPictureTaken(byte[] data, Camera camera) {
196            Log.i(TAG, "Received jpeg.");
197            RequestHolder holder = mInFlightJpeg;
198            if (holder == null) {
199                Log.w(TAG, "Dropping jpeg frame.");
200                mInFlightJpeg = null;
201                return;
202            }
203            for (Surface s : holder.getHolderTargets()) {
204                if (RequestHolder.jpegType(s)) {
205                    Log.i(TAG, "Producing jpeg buffer...");
206                    LegacyCameraDevice.nativeSetSurfaceDimens(s, data.length, /*height*/1);
207                    LegacyCameraDevice.nativeProduceFrame(s, data, data.length, /*height*/1,
208                            CameraMetadataNative.NATIVE_JPEG_FORMAT);
209                }
210            }
211            mReceivedJpeg.open();
212        }
213    };
214
215    private final Camera.ShutterCallback mJpegShutterCallback = new Camera.ShutterCallback() {
216        @Override
217        public void onShutter() {
218            mLastJpegTimestamp = SystemClock.elapsedRealtimeNanos();
219        }
220    };
221
222    private final SurfaceTexture.OnFrameAvailableListener mPreviewCallback =
223            new SurfaceTexture.OnFrameAvailableListener() {
224                @Override
225                public void onFrameAvailable(SurfaceTexture surfaceTexture) {
226                    RequestHolder holder = mInFlightPreview;
227                    if (holder == null) {
228                        mGLThreadManager.queueNewFrame(null);
229                        Log.w(TAG, "Dropping preview frame.");
230                        return;
231                    }
232
233                    if (DEBUG) {
234                        mPrevCounter.countAndLog();
235                    }
236                    mInFlightPreview = null;
237
238                    if (holder.hasPreviewTargets()) {
239                        mGLThreadManager.queueNewFrame(holder.getHolderTargets());
240                    }
241
242                    /**
243                     * TODO: Get timestamp from GL thread after buffer update.
244                     */
245                    mLastPreviewTimestamp = surfaceTexture.getTimestamp();
246                    mReceivedPreview.open();
247                }
248            };
249
250    private void stopPreview() {
251        if (mPreviewRunning) {
252            mCamera.stopPreview();
253            mPreviewRunning = false;
254        }
255    }
256
257    private void startPreview() {
258        if (!mPreviewRunning) {
259            mCamera.startPreview();
260            mPreviewRunning = true;
261        }
262    }
263
264    private void doJpegCapture(RequestHolder request) throws IOException {
265        if (!mPreviewRunning) {
266            createDummySurface();
267            mCamera.setPreviewTexture(mDummyTexture);
268            startPreview();
269        }
270        mInFlightJpeg = request;
271        // TODO: Hook up shutter callback to CameraDeviceStateListener#onCaptureStarted
272        mCamera.takePicture(mJpegShutterCallback, /*raw*/null, mJpegCallback);
273        mPreviewRunning = false;
274    }
275
276    private void doPreviewCapture(RequestHolder request) throws IOException {
277        mInFlightPreview = request;
278        if (mPreviewRunning) {
279            return; // Already running
280        }
281
282        if (mPreviewTexture == null) {
283            throw new IllegalStateException(
284                    "Preview capture called with no preview surfaces configured.");
285        }
286
287        mPreviewTexture.setDefaultBufferSize(mIntermediateBufferSize.getWidth(),
288                mIntermediateBufferSize.getHeight());
289        mCamera.setPreviewTexture(mPreviewTexture);
290        Camera.Parameters params = mCamera.getParameters();
291        List<int[]> supportedFpsRanges = params.getSupportedPreviewFpsRange();
292        int[] bestRange = getPhotoPreviewFpsRange(supportedFpsRanges);
293        if (DEBUG) {
294            Log.d(TAG, "doPreviewCapture - Selected range [" +
295                    bestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] + "," +
296                    bestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] + "]");
297        }
298        params.setPreviewFpsRange(bestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
299                bestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
300        params.setRecordingHint(true);
301        mCamera.setParameters(params);
302
303        startPreview();
304    }
305
306
307    private void configureOutputs(Collection<Surface> outputs) throws IOException {
308        stopPreview();
309        if (mGLThreadManager != null) {
310            mGLThreadManager.waitUntilStarted();
311            mGLThreadManager.ignoreNewFrames();
312            mGLThreadManager.waitUntilIdle();
313        }
314        mPreviewOutputs.clear();
315        mCallbackOutputs.clear();
316        mPreviewTexture = null;
317        mInFlightPreview = null;
318        mInFlightJpeg = null;
319
320        if (outputs != null) {
321            for (Surface s : outputs) {
322                int format = LegacyCameraDevice.nativeDetectSurfaceType(s);
323                switch (format) {
324                    case CameraMetadataNative.NATIVE_JPEG_FORMAT:
325                        mCallbackOutputs.add(s);
326                        break;
327                    default:
328                        mPreviewOutputs.add(s);
329                        break;
330                }
331            }
332        }
333        mParams = mCamera.getParameters();
334        if (mPreviewOutputs.size() > 0) {
335            List<Size> outputSizes = new ArrayList<>(outputs.size());
336            for (Surface s : mPreviewOutputs) {
337                int[] dimens = {0, 0};
338                LegacyCameraDevice.nativeDetectSurfaceDimens(s, dimens);
339                outputSizes.add(new Size(dimens[0], dimens[1]));
340            }
341
342            Size largestOutput = findLargestByArea(outputSizes);
343
344            // Find largest jpeg dimension - assume to have the same aspect ratio as sensor.
345            List<Size> supportedJpegSizes = convertSizeList(mParams.getSupportedPictureSizes());
346            Size largestJpegDimen = findLargestByArea(supportedJpegSizes);
347
348            List<Size> supportedPreviewSizes = convertSizeList(mParams.getSupportedPreviewSizes());
349
350            // Use smallest preview dimension with same aspect ratio as sensor that is >= than all
351            // of the configured output dimensions.  If none exists, fall back to using the largest
352            // supported preview size.
353            long largestOutputArea = largestOutput.getHeight() * (long) largestOutput.getWidth();
354            Size bestPreviewDimen = findLargestByArea(supportedPreviewSizes);
355            for (Size s : supportedPreviewSizes) {
356                long currArea = s.getWidth() * s.getHeight();
357                long bestArea = bestPreviewDimen.getWidth() * bestPreviewDimen.getHeight();
358                if (checkAspectRatiosMatch(largestJpegDimen, s) && (currArea < bestArea &&
359                        currArea >= largestOutputArea)) {
360                    bestPreviewDimen = s;
361                }
362            }
363
364            mIntermediateBufferSize = bestPreviewDimen;
365            if (DEBUG) {
366                Log.d(TAG, "Intermediate buffer selected with dimens: " +
367                        bestPreviewDimen.toString());
368            }
369        } else {
370            mIntermediateBufferSize = null;
371            if (DEBUG) {
372                Log.d(TAG, "No Intermediate buffer selected, no preview outputs were configured");
373            }
374        }
375
376        // TODO: Detect and optimize single-output paths here to skip stream teeing.
377        if (mGLThreadManager == null) {
378            mGLThreadManager = new GLThreadManager(mCameraId);
379            mGLThreadManager.start();
380        }
381        mGLThreadManager.waitUntilStarted();
382        mGLThreadManager.setConfigurationAndWait(mPreviewOutputs);
383        mGLThreadManager.allowNewFrames();
384        mPreviewTexture = mGLThreadManager.getCurrentSurfaceTexture();
385        if (mPreviewTexture != null) {
386            mPreviewTexture.setOnFrameAvailableListener(mPreviewCallback);
387        }
388    }
389
390    private static Size findLargestByArea(List<Size> sizes) {
391        return Collections.max(sizes, new SizeComparator());
392    }
393
394    private static boolean checkAspectRatiosMatch(Size a, Size b) {
395        float aAspect = a.getWidth() / (float) a.getHeight();
396        float bAspect = b.getWidth() / (float) b.getHeight();
397
398        return Math.abs(aAspect - bAspect) < ASPECT_RATIO_TOLERANCE;
399    }
400
401    private static List<Size> convertSizeList(List<Camera.Size> sizeList) {
402        List<Size> sizes = new ArrayList<>(sizeList.size());
403        for (Camera.Size s : sizeList) {
404            sizes.add(new Size(s.width, s.height));
405        }
406        return sizes;
407    }
408
409    // Calculate the highest FPS range supported
410    private int[] getPhotoPreviewFpsRange(List<int[]> frameRates) {
411        if (frameRates.size() == 0) {
412            Log.e(TAG, "No supported frame rates returned!");
413            return null;
414        }
415
416        int bestMin = 0;
417        int bestMax = 0;
418        int bestIndex = 0;
419        int index = 0;
420        for (int[] rate : frameRates) {
421            int minFps = rate[Camera.Parameters.PREVIEW_FPS_MIN_INDEX];
422            int maxFps = rate[Camera.Parameters.PREVIEW_FPS_MAX_INDEX];
423            if (maxFps > bestMax || (maxFps == bestMax && minFps > bestMin)) {
424                bestMin = minFps;
425                bestMax = maxFps;
426                bestIndex = index;
427            }
428            index++;
429        }
430
431        return frameRates.get(bestIndex);
432    }
433
434    private final Handler.Callback mRequestHandlerCb = new Handler.Callback() {
435        private boolean mCleanup = false;
436
437        @SuppressWarnings("unchecked")
438        @Override
439        public boolean handleMessage(Message msg) {
440            if (mCleanup) {
441                return true;
442            }
443
444            if (DEBUG) {
445                Log.d(TAG, "Request thread handling message:" + msg.what);
446            }
447            switch (msg.what) {
448                case MSG_CONFIGURE_OUTPUTS:
449                    ConfigureHolder config = (ConfigureHolder) msg.obj;
450                    int sizes = config.surfaces != null ? config.surfaces.size() : 0;
451                    Log.i(TAG, "Configure outputs: " + sizes +
452                            " surfaces configured.");
453                    try {
454                        configureOutputs(config.surfaces);
455                    } catch (IOException e) {
456                        // TODO: report error to CameraDevice
457                        throw new IOError(e);
458                    }
459                    config.condition.open();
460                    break;
461                case MSG_SUBMIT_CAPTURE_REQUEST:
462                    Handler handler = RequestThreadManager.this.mRequestThread.getHandler();
463
464                    // Get the next burst from the request queue.
465                    Pair<BurstHolder, Long> nextBurst = mRequestQueue.getNext();
466                    if (nextBurst == null) {
467                        mDeviceState.setIdle();
468                        stopPreview();
469                        break;
470                    } else {
471                        // Queue another capture if we did not get the last burst.
472                        handler.sendEmptyMessage(MSG_SUBMIT_CAPTURE_REQUEST);
473                    }
474
475                    // Complete each request in the burst
476                    List<RequestHolder> requests =
477                            nextBurst.first.produceRequestHolders(nextBurst.second);
478                    for (RequestHolder holder : requests) {
479                        CaptureRequest request = holder.getRequest();
480                        if (mLastRequest == null || mLastRequest != request) {
481                            mLastRequest = request;
482                            LegacyMetadataMapper.convertRequestMetadata(mLastRequest,
483                                /*out*/mParams);
484                            mCamera.setParameters(mParams);
485                        }
486                        mDeviceState.setCaptureStart(holder);
487                        long timestamp = 0;
488                        try {
489                            if (holder.hasPreviewTargets()) {
490                                mReceivedPreview.close();
491                                doPreviewCapture(holder);
492                                if (!mReceivedPreview.block(PREVIEW_FRAME_TIMEOUT)) {
493                                    // TODO: report error to CameraDevice
494                                    Log.e(TAG, "Hit timeout for preview callback!");
495                                }
496                                timestamp = mLastPreviewTimestamp;
497                            }
498                            if (holder.hasJpegTargets()) {
499                                mReceivedJpeg.close();
500                                doJpegCapture(holder);
501                                if (!mReceivedJpeg.block(JPEG_FRAME_TIMEOUT)) {
502                                    // TODO: report error to CameraDevice
503                                    Log.e(TAG, "Hit timeout for jpeg callback!");
504                                }
505                                mInFlightJpeg = null;
506                                timestamp = mLastJpegTimestamp;
507                            }
508                        } catch (IOException e) {
509                            // TODO: err handling
510                            throw new IOError(e);
511                        }
512                        if (timestamp == 0) {
513                            timestamp = SystemClock.elapsedRealtimeNanos();
514                        }
515                        CameraMetadataNative result = LegacyMetadataMapper.convertResultMetadata(mParams,
516                                request, timestamp);
517                        mDeviceState.setCaptureResult(holder, result);
518                    }
519                    if (DEBUG) {
520                        mRequestCounter.countAndLog();
521                    }
522                    break;
523                case MSG_CLEANUP:
524                    mCleanup = true;
525                    if (mGLThreadManager != null) {
526                        mGLThreadManager.quit();
527                    }
528                    if (mCamera != null) {
529                        mCamera.release();
530                    }
531                    break;
532                default:
533                    throw new AssertionError("Unhandled message " + msg.what +
534                            " on RequestThread.");
535            }
536            return true;
537        }
538    };
539
540    /**
541     * Create a new RequestThreadManager.
542     *
543     * @param cameraId the id of the camera to use.
544     * @param camera an open camera object.  The RequestThreadManager takes ownership of this camera
545     *               object, and is responsible for closing it.
546     * @param deviceState a {@link CameraDeviceState} state machine.
547     */
548    public RequestThreadManager(int cameraId, Camera camera,
549                                CameraDeviceState deviceState) {
550        mCamera = camera;
551        mCameraId = cameraId;
552        String name = String.format("RequestThread-%d", cameraId);
553        TAG = name;
554        mDeviceState = deviceState;
555        mRequestThread = new RequestHandlerThread(name, mRequestHandlerCb);
556    }
557
558    /**
559     * Start the request thread.
560     */
561    public void start() {
562        mRequestThread.start();
563    }
564
565    /**
566     * Flush the pending requests.
567     */
568    public void flush() {
569        // TODO: Implement flush.
570        Log.e(TAG, "flush not yet implemented.");
571    }
572
573    /**
574     * Quit the request thread, and clean up everything.
575     */
576    public void quit() {
577        Handler handler = mRequestThread.waitAndGetHandler();
578        handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
579        mRequestThread.quitSafely();
580        try {
581            mRequestThread.join();
582        } catch (InterruptedException e) {
583            Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
584                    mRequestThread.getName(), mRequestThread.getId()));
585        }
586    }
587
588    /**
589     * Submit the given burst of requests to be captured.
590     *
591     * <p>If the burst is repeating, replace the current repeating burst.</p>
592     *
593     * @param requests the burst of requests to add to the queue.
594     * @param repeating true if the burst is repeating.
595     * @param frameNumber an output argument that contains either the frame number of the last frame
596     *                    that will be returned for this request, or the frame number of the last
597     *                    frame that will be returned for the current repeating request if this
598     *                    burst is set to be repeating.
599     * @return the request id.
600     */
601    public int submitCaptureRequests(List<CaptureRequest> requests, boolean repeating,
602            /*out*/LongParcelable frameNumber) {
603        Handler handler = mRequestThread.waitAndGetHandler();
604        int ret = mRequestQueue.submit(requests, repeating, frameNumber);
605        handler.sendEmptyMessage(MSG_SUBMIT_CAPTURE_REQUEST);
606        return ret;
607    }
608
609    /**
610     * Cancel a repeating request.
611     *
612     * @param requestId the id of the repeating request to cancel.
613     * @return the last frame to be returned from the HAL for the given repeating request, or
614     *          {@code INVALID_FRAME} if none exists.
615     */
616    public long cancelRepeating(int requestId) {
617        return mRequestQueue.stopRepeating(requestId);
618    }
619
620
621    /**
622     * Configure with the current list of output Surfaces.
623     *
624     * <p>
625     * This operation blocks until the configuration is complete.
626     * </p>
627     *
628     * <p>Using a {@code null} or empty {@code outputs} list is the equivalent of unconfiguring.</p>
629     *
630     * @param outputs a {@link java.util.Collection} of outputs to configure.
631     */
632    public void configure(Collection<Surface> outputs) {
633        Handler handler = mRequestThread.waitAndGetHandler();
634        final ConditionVariable condition = new ConditionVariable(/*closed*/false);
635        ConfigureHolder holder = new ConfigureHolder(condition, outputs);
636        handler.sendMessage(handler.obtainMessage(MSG_CONFIGURE_OUTPUTS, 0, 0, holder));
637        condition.block();
638    }
639}
640