CameraCaptureSessionImpl.java revision 7875a886b8397684c5fac3dd022d19de9874b436
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 */
16package android.hardware.camera2.impl;
17
18import android.hardware.camera2.CameraAccessException;
19import android.hardware.camera2.CameraCaptureSession;
20import android.hardware.camera2.CameraDevice;
21import android.hardware.camera2.CaptureRequest;
22import android.hardware.camera2.dispatch.ArgumentReplacingDispatcher;
23import android.hardware.camera2.dispatch.BroadcastDispatcher;
24import android.hardware.camera2.dispatch.Dispatchable;
25import android.hardware.camera2.dispatch.DuckTypingDispatcher;
26import android.hardware.camera2.dispatch.HandlerDispatcher;
27import android.hardware.camera2.dispatch.InvokeDispatcher;
28import android.hardware.camera2.dispatch.NullDispatcher;
29import android.hardware.camera2.utils.TaskDrainer;
30import android.hardware.camera2.utils.TaskSingleDrainer;
31import android.os.Handler;
32import android.util.Log;
33import android.view.Surface;
34
35import java.util.Arrays;
36import java.util.List;
37
38import static android.hardware.camera2.impl.CameraDeviceImpl.checkHandler;
39import static com.android.internal.util.Preconditions.*;
40
41public class CameraCaptureSessionImpl extends CameraCaptureSession {
42    private static final String TAG = "CameraCaptureSession";
43    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
44
45    /** User-specified set of surfaces used as the configuration outputs */
46    private final List<Surface> mOutputs;
47    /**
48     * User-specified state listener, used for outgoing events; calls to this object will be
49     * automatically {@link Handler#post(Runnable) posted} to {@code mStateHandler}.
50     */
51    private final CameraCaptureSession.StateListener mStateListener;
52    /** User-specified state handler used for outgoing state listener events */
53    private final Handler mStateHandler;
54
55    /** Internal camera device; used to translate calls into existing deprecated API */
56    private final android.hardware.camera2.impl.CameraDeviceImpl mDeviceImpl;
57    /** Internal handler; used for all incoming events to preserve total order */
58    private final Handler mDeviceHandler;
59
60    /** Drain Sequence IDs which have been queued but not yet finished with aborted/completed */
61    private final TaskDrainer<Integer> mSequenceDrainer;
62    /** Drain state transitions from ACTIVE -> IDLE */
63    private final TaskSingleDrainer mIdleDrainer;
64    /** Drain state transitions from BUSY -> IDLE */
65    private final TaskSingleDrainer mAbortDrainer;
66    /** Drain the UNCONFIGURED state transition */
67    private final TaskSingleDrainer mUnconfigureDrainer;
68
69    /** This session is closed; all further calls will throw ISE */
70    private boolean mClosed = false;
71    /** Do not unconfigure if this is set; another session will overwrite configuration */
72    private boolean mSkipUnconfigure = false;
73
74    /** Is the session in the process of aborting? Pay attention to BUSY->IDLE transitions. */
75    private boolean mAborting;
76
77    /**
78     * Create a new CameraCaptureSession.
79     *
80     * <p>The camera device must already be in the {@code IDLE} state when this is invoked.
81     * There must be no pending actions
82     * (e.g. no pending captures, no repeating requests, no flush).</p>
83     */
84    CameraCaptureSessionImpl(List<Surface> outputs,
85            CameraCaptureSession.StateListener listener, Handler stateHandler,
86            android.hardware.camera2.impl.CameraDeviceImpl deviceImpl,
87            Handler deviceStateHandler, boolean configureSuccess) {
88        if (outputs == null || outputs.isEmpty()) {
89            throw new IllegalArgumentException("outputs must be a non-null, non-empty list");
90        } else if (listener == null) {
91            throw new IllegalArgumentException("listener must not be null");
92        }
93
94        // TODO: extra verification of outputs
95        mOutputs = outputs;
96        mStateHandler = checkHandler(stateHandler);
97        mStateListener = createUserStateListenerProxy(mStateHandler, listener);
98
99        mDeviceHandler = checkNotNull(deviceStateHandler, "deviceStateHandler must not be null");
100        mDeviceImpl = checkNotNull(deviceImpl, "deviceImpl must not be null");
101
102        /*
103         * Use the same handler as the device's StateListener for all the internal coming events
104         *
105         * This ensures total ordering between CameraDevice.StateListener and
106         * CameraDevice.CaptureListener events.
107         */
108        mSequenceDrainer = new TaskDrainer<>(mDeviceHandler, new SequenceDrainListener(),
109                /*name*/"seq");
110        mIdleDrainer = new TaskSingleDrainer(mDeviceHandler, new IdleDrainListener(),
111                /*name*/"idle");
112        mAbortDrainer = new TaskSingleDrainer(mDeviceHandler, new AbortDrainListener(),
113                /*name*/"abort");
114        mUnconfigureDrainer = new TaskSingleDrainer(mDeviceHandler, new UnconfigureDrainListener(),
115                /*name*/"unconf");
116
117        // CameraDevice should call configureOutputs and have it finish before constructing us
118
119        if (configureSuccess) {
120            mStateListener.onConfigured(this);
121            if (VERBOSE) Log.v(TAG, "ctor - Created session successfully");
122        } else {
123            mStateListener.onConfigureFailed(this);
124            mClosed = true; // do not fire any other callbacks, do not allow any other work
125            Log.e(TAG, "Failed to create capture session; configuration failed");
126        }
127    }
128
129    @Override
130    public CameraDevice getDevice() {
131        return mDeviceImpl;
132    }
133
134    @Override
135    public synchronized int capture(CaptureRequest request, CaptureListener listener,
136            Handler handler) throws CameraAccessException {
137        if (request == null) {
138            throw new IllegalArgumentException("request must not be null");
139        }
140
141        checkNotClosed();
142        checkLegalToCapture();
143
144        handler = checkHandler(handler, listener);
145
146        if (VERBOSE) {
147            Log.v(TAG, "capture - request " + request + ", listener " + listener + " handler" +
148                    " " + handler);
149        }
150
151        return addPendingSequence(mDeviceImpl.capture(request,
152                createCaptureListenerProxy(handler, listener), mDeviceHandler));
153    }
154
155    @Override
156    public synchronized int captureBurst(List<CaptureRequest> requests, CaptureListener listener,
157            Handler handler) throws CameraAccessException {
158        if (requests == null) {
159            throw new IllegalArgumentException("requests must not be null");
160        } else if (requests.isEmpty()) {
161            throw new IllegalArgumentException("requests must have at least one element");
162        }
163
164        checkNotClosed();
165        checkLegalToCapture();
166
167        handler = checkHandler(handler, listener);
168
169        if (VERBOSE) {
170            CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
171            Log.v(TAG, "captureBurst - requests " + Arrays.toString(requestArray) + ", listener " +
172                    listener + " handler" + "" + handler);
173        }
174
175        return addPendingSequence(mDeviceImpl.captureBurst(requests,
176                createCaptureListenerProxy(handler, listener), mDeviceHandler));
177    }
178
179    @Override
180    public synchronized int setRepeatingRequest(CaptureRequest request, CaptureListener listener,
181            Handler handler) throws CameraAccessException {
182        if (request == null) {
183            throw new IllegalArgumentException("request must not be null");
184        }
185
186        checkNotClosed();
187        checkLegalToCapture();
188
189        handler = checkHandler(handler, listener);
190
191        if (VERBOSE) {
192            Log.v(TAG, "setRepeatingRequest - request " + request + ", listener " + listener +
193                    " handler" + " " + handler);
194        }
195
196        return addPendingSequence(mDeviceImpl.setRepeatingRequest(request,
197                createCaptureListenerProxy(handler, listener), mDeviceHandler));
198    }
199
200    @Override
201    public synchronized int setRepeatingBurst(List<CaptureRequest> requests,
202            CaptureListener listener, Handler handler) throws CameraAccessException {
203        if (requests == null) {
204            throw new IllegalArgumentException("requests must not be null");
205        } else if (requests.isEmpty()) {
206            throw new IllegalArgumentException("requests must have at least one element");
207        }
208
209        checkNotClosed();
210        checkLegalToCapture();
211
212        handler = checkHandler(handler, listener);
213
214        if (VERBOSE) {
215            CaptureRequest[] requestArray = requests.toArray(new CaptureRequest[0]);
216            Log.v(TAG, "setRepeatingBurst - requests " + Arrays.toString(requestArray) +
217                    ", listener " + listener + " handler" + "" + handler);
218        }
219
220        return addPendingSequence(mDeviceImpl.setRepeatingBurst(requests,
221                createCaptureListenerProxy(handler, listener), mDeviceHandler));
222    }
223
224    @Override
225    public synchronized void stopRepeating() throws CameraAccessException {
226        checkNotClosed();
227
228        if (VERBOSE) {
229            Log.v(TAG, "stopRepeating");
230        }
231
232        mDeviceImpl.stopRepeating();
233    }
234
235    @Override
236    public synchronized void abortCaptures() throws CameraAccessException {
237        checkNotClosed();
238
239        if (VERBOSE) {
240            Log.v(TAG, "abortCaptures");
241        }
242
243        if (mAborting) {
244            Log.w(TAG, "abortCaptures - Session is already aborting; doing nothing");
245            return;
246        }
247
248        mAborting = true;
249        mAbortDrainer.taskStarted();
250
251        mDeviceImpl.flush();
252        // The next BUSY -> IDLE set of transitions will mark the end of the abort.
253    }
254
255    /**
256     * Replace this session with another session.
257     *
258     * <p>This is an optimization to avoid unconfiguring and then immediately having to
259     * reconfigure again.</p>
260     *
261     * <p>The semantics are identical to {@link #close}, except that unconfiguring will be skipped.
262     * <p>
263     *
264     * @see CameraCaptureSession#close
265     */
266    synchronized void replaceSessionClose(CameraCaptureSession other) {
267        /*
268         * In order for creating new sessions to be fast, the new session should be created
269         * before the old session is closed.
270         *
271         * Otherwise the old session will always unconfigure if there is no new session to
272         * replace it.
273         *
274         * Unconfiguring could add hundreds of milliseconds of delay. We could race and attempt
275         * to skip unconfigure if a new session is created before the captures are all drained,
276         * but this would introduce nondeterministic behavior.
277         */
278
279        if (VERBOSE) Log.v(TAG, "replaceSessionClose");
280
281        // #close was already called explicitly, keep going the slow route
282        if (mClosed) {
283            if (VERBOSE) Log.v(TAG, "replaceSessionClose - close was already called");
284            return;
285        }
286
287        mSkipUnconfigure = true;
288        close();
289    }
290
291    @Override
292    public synchronized void close() {
293
294        if (mClosed) {
295            if (VERBOSE) Log.v(TAG, "close - reentering");
296            return;
297        }
298
299        if (VERBOSE) Log.v(TAG, "close - first time");
300
301        mClosed = true;
302
303        /*
304         * Flush out any repeating request. Since camera is closed, no new requests
305         * can be queued, and eventually the entire request queue will be drained.
306         *
307         * If the camera device was already closed, short circuit and do nothing; since
308         * no more internal device callbacks will fire anyway.
309         *
310         * Otherwise, once stopRepeating is done, wait for camera to idle, then unconfigure the
311         * camera. Once that's done, fire #onClosed.
312         */
313        try {
314            mDeviceImpl.stopRepeating();
315        } catch (IllegalStateException e) {
316            // OK: Camera device may already be closed, nothing else to do
317            Log.w(TAG, "The camera device was already closed: ", e);
318
319            // TODO: Fire onClosed anytime we get the device onClosed or the ISE?
320            // or just suppress the ISE only and rely onClosed.
321            // Also skip any of the draining work if this is already closed.
322
323            // Short-circuit; queue listener immediately and return
324            mStateListener.onClosed(this);
325            return;
326        } catch (CameraAccessException e) {
327            // OK: close does not throw checked exceptions.
328            Log.e(TAG, "Exception while stopping repeating: ", e);
329
330            // TODO: call onError instead of onClosed if this happens
331        }
332
333        // If no sequences are pending, fire #onClosed immediately
334        mSequenceDrainer.beginDrain();
335    }
336
337    /**
338     * Post calls into a CameraCaptureSession.StateListener to the user-specified {@code handler}.
339     */
340    private StateListener createUserStateListenerProxy(Handler handler, StateListener listener) {
341        InvokeDispatcher<StateListener> userListenerSink = new InvokeDispatcher<>(listener);
342        HandlerDispatcher<StateListener> handlerPassthrough =
343                new HandlerDispatcher<>(userListenerSink, handler);
344
345        return new ListenerProxies.SessionStateListenerProxy(handlerPassthrough);
346    }
347
348    /**
349     * Forward callbacks from
350     * CameraDevice.CaptureListener to the CameraCaptureSession.CaptureListener.
351     *
352     * <p>In particular, all calls are automatically split to go both to our own
353     * internal listener, and to the user-specified listener (by transparently posting
354     * to the user-specified handler).</p>
355     *
356     * <p>When a capture sequence finishes, update the pending checked sequences set.</p>
357     */
358    @SuppressWarnings("deprecation")
359    private CameraDevice.CaptureListener createCaptureListenerProxy(
360            Handler handler, CaptureListener listener) {
361        CameraDevice.CaptureListener localListener = new CameraDevice.CaptureListener() {
362            @Override
363            public void onCaptureSequenceCompleted(CameraDevice camera,
364                    int sequenceId, long frameNumber) {
365                finishPendingSequence(sequenceId);
366            }
367
368            @Override
369            public void onCaptureSequenceAborted(CameraDevice camera,
370                    int sequenceId) {
371                finishPendingSequence(sequenceId);
372            }
373        };
374
375        /*
376         * Split the calls from the device listener into local listener and the following chain:
377         * - replace the first CameraDevice arg with a CameraCaptureSession
378         * - duck type from device listener to session listener
379         * - then forward the call to a handler
380         * - then finally invoke the destination method on the session listener object
381         */
382        if (listener == null) {
383            // OK: API allows the user to not specify a listener, and the handler may
384            // also be null in that case. Collapse whole dispatch chain to only call the local
385            // listener
386            return localListener;
387        }
388
389        InvokeDispatcher<CameraDevice.CaptureListener> localSink =
390                new InvokeDispatcher<>(localListener);
391
392        InvokeDispatcher<CaptureListener> userListenerSink =
393                new InvokeDispatcher<>(listener);
394        HandlerDispatcher<CaptureListener> handlerPassthrough =
395                new HandlerDispatcher<>(userListenerSink, handler);
396        DuckTypingDispatcher<CameraDevice.CaptureListener, CaptureListener> duckToSession
397                = new DuckTypingDispatcher<>(handlerPassthrough, CaptureListener.class);
398        ArgumentReplacingDispatcher<CameraDevice.CaptureListener, CameraCaptureSessionImpl>
399            replaceDeviceWithSession = new ArgumentReplacingDispatcher<>(duckToSession,
400                    /*argumentIndex*/0, this);
401
402        BroadcastDispatcher<CameraDevice.CaptureListener> broadcaster =
403                new BroadcastDispatcher<CameraDevice.CaptureListener>(
404                        replaceDeviceWithSession,
405                        localSink);
406
407        return new ListenerProxies.DeviceCaptureListenerProxy(broadcaster);
408    }
409
410    /**
411     *
412     * Create an internal state listener, to be invoked on the mDeviceHandler
413     *
414     * <p>It has a few behaviors:
415     * <ul>
416     * <li>Convert device state changes into session state changes.
417     * <li>Keep track of async tasks that the session began (idle, abort).
418     * </ul>
419     * </p>
420     * */
421    CameraDevice.StateListener getDeviceStateListener() {
422        final CameraCaptureSession session = this;
423
424        return new CameraDevice.StateListener() {
425            private boolean mBusy = false;
426            private boolean mActive = false;
427
428            @Override
429            public void onOpened(CameraDevice camera) {
430                throw new AssertionError("Camera must already be open before creating a session");
431            }
432
433            @Override
434            public void onDisconnected(CameraDevice camera) {
435                close();
436            }
437
438            @Override
439            public void onError(CameraDevice camera, int error) {
440                // TODO: Handle errors somehow.
441                Log.wtf(TAG, "Got device error " + error);
442            }
443
444            @Override
445            public void onActive(CameraDevice camera) {
446                mIdleDrainer.taskStarted();
447                mActive = true;
448
449                mStateListener.onActive(session);
450            }
451
452            @Override
453            public void onIdle(CameraDevice camera) {
454                boolean isAborting;
455                synchronized (session) {
456                    isAborting = mAborting;
457                }
458
459                /*
460                 * Check which states we transitioned through:
461                 *
462                 * (ACTIVE -> IDLE)
463                 * (BUSY -> IDLE)
464                 *
465                 * Note that this is also legal:
466                 * (ACTIVE -> BUSY -> IDLE)
467                 *
468                 * and mark those tasks as finished
469                 */
470                if (mBusy && isAborting) {
471                    mAbortDrainer.taskFinished();
472
473                    synchronized (session) {
474                        mAborting = false;
475                    }
476                }
477
478                if (mActive) {
479                    mIdleDrainer.taskFinished();
480                }
481
482                mBusy = false;
483                mActive = false;
484
485                mStateListener.onReady(session);
486            }
487
488            @Override
489            public void onBusy(CameraDevice camera) {
490                mBusy = true;
491
492                // TODO: Queue captures during abort instead of failing them
493                // since the app won't be able to distinguish the two actives
494                Log.w(TAG, "Device is now busy; do not submit new captures (TODO: allow this)");
495                mStateListener.onActive(session);
496            }
497
498            @Override
499            public void onUnconfigured(CameraDevice camera) {
500                synchronized (session) {
501                    // Ignore #onUnconfigured before #close is called
502                    if (mClosed) {
503                        mUnconfigureDrainer.taskFinished();
504                    }
505                }
506            }
507        };
508
509    }
510
511    @Override
512    protected void finalize() throws Throwable {
513        try {
514            close();
515        } finally {
516            super.finalize();
517        }
518    }
519
520    private void checkLegalToCapture() {
521        if (mAborting) {
522            throw new IllegalStateException(
523                    "Session is aborting captures; new captures are not permitted");
524        }
525    }
526
527    private void checkNotClosed() {
528        if (mClosed) {
529            throw new IllegalStateException(
530                    "Session has been closed; further changes are illegal.");
531        }
532    }
533
534    /**
535     * Notify the session that a pending capture sequence has just been queued.
536     *
537     * <p>During a shutdown/close, the session waits until all pending sessions are finished
538     * before taking any further steps to shut down itself.</p>
539     *
540     * @see #finishPendingSequence
541     */
542    private int addPendingSequence(int sequenceId) {
543        mSequenceDrainer.taskStarted(sequenceId);
544        return sequenceId;
545    }
546
547    /**
548     * Notify the session that a pending capture sequence is now finished.
549     *
550     * <p>During a shutdown/close, once all pending sequences finish, it is safe to
551     * close the camera further by unconfiguring and then firing {@code onClosed}.</p>
552     */
553    private void finishPendingSequence(int sequenceId) {
554        mSequenceDrainer.taskFinished(sequenceId);
555    }
556
557    private class SequenceDrainListener implements TaskDrainer.DrainListener {
558        @Override
559        public void onDrained() {
560            /*
561             * No repeating request is set; and the capture queue has fully drained.
562             *
563             * If no captures were queued to begin with, and an abort was queued,
564             * it's still possible to get another BUSY before the last IDLE.
565             *
566             * If the camera is already "IDLE" and no aborts are pending,
567             * then the drain immediately finishes.
568             */
569            mAbortDrainer.beginDrain();
570        }
571    }
572
573    private class AbortDrainListener implements TaskDrainer.DrainListener {
574        @Override
575        public void onDrained() {
576            synchronized (CameraCaptureSessionImpl.this) {
577                /*
578                 * Any queued aborts have now completed.
579                 *
580                 * It's now safe to wait to receive the final "IDLE" event, as the camera device
581                 * will no longer again transition to "ACTIVE" by itself.
582                 *
583                 * If the camera is already "IDLE", then the drain immediately finishes.
584                 */
585                mIdleDrainer.beginDrain();
586            }
587        }
588    }
589
590    private class IdleDrainListener implements TaskDrainer.DrainListener {
591        @Override
592        public void onDrained() {
593            synchronized (CameraCaptureSessionImpl.this) {
594                /*
595                 * The device is now IDLE, and has settled. It will not transition to
596                 * ACTIVE or BUSY again by itself.
597                 *
598                 * It's now safe to unconfigure the outputs and after it's done invoke #onClosed.
599                 *
600                 * This operation is idempotent; a session will not be closed twice.
601                 */
602
603                // Fast path: A new capture session has replaced this one; don't unconfigure.
604                if (mSkipUnconfigure) {
605                    mStateListener.onClosed(CameraCaptureSessionImpl.this);
606                    return;
607                }
608
609                // Slow path: #close was called explicitly on this session; unconfigure first
610
611                try {
612                    mUnconfigureDrainer.taskStarted();
613                    mDeviceImpl.configureOutputs(null); // begin transition to unconfigured state
614                } catch (CameraAccessException e) {
615                    // OK: do not throw checked exceptions.
616                    Log.e(TAG, "Exception while configuring outputs: ", e);
617
618                    // TODO: call onError instead of onClosed if this happens
619                }
620
621                mUnconfigureDrainer.beginDrain();
622            }
623        }
624    }
625
626    private class UnconfigureDrainListener implements TaskDrainer.DrainListener {
627        @Override
628        public void onDrained() {
629            synchronized (CameraCaptureSessionImpl.this) {
630                // The device has finished unconfiguring. It's now fully closed.
631                mStateListener.onClosed(CameraCaptureSessionImpl.this);
632            }
633        }
634    }
635}
636