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 com.android.camera.one.v2;
18
19import android.annotation.TargetApi;
20import android.hardware.camera2.CameraCaptureSession;
21import android.hardware.camera2.CaptureRequest;
22import android.hardware.camera2.CaptureResult;
23import android.hardware.camera2.CaptureResult.Key;
24import android.hardware.camera2.TotalCaptureResult;
25import android.media.Image;
26import android.media.ImageReader;
27import android.os.Build;
28import android.os.Handler;
29import android.os.SystemClock;
30import android.util.Pair;
31
32import com.android.camera.debug.Log;
33import com.android.camera.debug.Log.Tag;
34import com.android.camera.util.ConcurrentSharedRingBuffer;
35import com.android.camera.util.ConcurrentSharedRingBuffer.PinStateListener;
36import com.android.camera.util.ConcurrentSharedRingBuffer.Selector;
37import com.android.camera.util.ConcurrentSharedRingBuffer.SwapTask;
38import com.android.camera.util.Task;
39
40import java.util.Collections;
41import java.util.List;
42import java.util.Map;
43import java.util.Set;
44import java.util.concurrent.ConcurrentHashMap;
45import java.util.concurrent.Executor;
46import java.util.concurrent.RejectedExecutionException;
47import java.util.concurrent.atomic.AtomicInteger;
48
49/**
50 * Implements {@link android.media.ImageReader.OnImageAvailableListener} and
51 * {@link android.hardware.camera2.CameraCaptureSession.CaptureCallback} to
52 * store the results of capture requests (both {@link Image}s and
53 * {@link TotalCaptureResult}s in a ring-buffer from which they may be saved.
54 * <br>
55 * This also manages the lifecycle of {@link Image}s within the application as
56 * they are passed in from the lower-level camera2 API.
57 */
58@TargetApi(Build.VERSION_CODES.LOLLIPOP)
59public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback implements
60        ImageReader.OnImageAvailableListener {
61    /**
62     * Callback to listen for changes to the ability to capture an existing
63     * image from the internal ring-buffer.
64     */
65    public interface CaptureReadyListener {
66        /**
67         * Called whenever the ability to capture an existing image from the
68         * ring-buffer changes. Calls to {@link #tryCaptureExistingImage} are
69         * more likely to succeed or fail depending on the value passed in to
70         * this function.
71         *
72         * @param capturePossible true if capture is more-likely to be possible,
73         *            false if capture is less-likely to be possible.
74         */
75        public void onReadyStateChange(boolean capturePossible);
76    }
77
78    /**
79     * Callback for listening to changes to individual metadata values.
80     */
81    public static interface MetadataChangeListener {
82        /**
83         * This will be called whenever a metadata value changes.
84         * Implementations should not take too much time to execute since this
85         * will be called faster than the camera's frame rate.
86         *
87         * @param key the {@link CaptureResult} key this listener listens for.
88         * @param second the previous value, or null if no such value existed.
89         *            The type will be that associated with the
90         *            {@link android.hardware.camera2.CaptureResult.Key} this
91         *            listener is bound to.
92         * @param newValue the new value. The type will be that associated with
93         *            the {@link android.hardware.camera2.CaptureResult.Key}
94         *            this listener is bound to.
95         * @param result the CaptureResult containing the new value
96         */
97        public void onImageMetadataChange(Key<?> key, Object second, Object newValue,
98                CaptureResult result);
99    }
100
101    /**
102     * Callback for saving an image.
103     */
104    public interface ImageCaptureListener {
105         /**
106         * Called with the {@link Image} and associated
107         * {@link TotalCaptureResult}. A typical implementation would save this
108         * to disk.
109         * <p>
110         * Note: Implementations must be thread-safe and must not close the
111         * image.
112         * </p>
113         */
114        public void onImageCaptured(Image image, TotalCaptureResult captureResult);
115    }
116
117    /**
118     * Callback for placing constraints on which images to capture. See
119     * {@link #tryCaptureExistingImage} and {@link #captureNextImage}.
120     */
121    public static interface CapturedImageConstraint {
122        /**
123         * Implementations should return true if the provided
124         * TotalCaptureResults satisfies constraints necessary for the intended
125         * image capture. For example, a constraint may return false if
126         * {@captureResult} indicates that the lens was moving during image
127         * capture.
128         *
129         * @param captureResult The metadata associated with the image.
130         * @return true if this image satisfies the constraint and can be
131         *         captured, false otherwise.
132         */
133        boolean satisfiesConstraint(TotalCaptureResult captureResult);
134    }
135
136    /**
137     * Holds an {@link Image} and {@link TotalCaptureResult} pair which may be
138     * added asynchronously.
139     */
140    private class CapturedImage {
141        /**
142         * The Image and TotalCaptureResult may be received at different times
143         * (via the onImageAvailableListener and onCaptureProgressed callbacks,
144         * respectively).
145         */
146        private Image mImage = null;
147        private TotalCaptureResult mMetadata = null;
148
149        /**
150         * Resets the object, closing and removing any existing image and
151         * metadata.
152         */
153        public void reset() {
154            if (mImage != null) {
155                mImage.close();
156                int numOpenImages = mNumOpenImages.decrementAndGet();
157                if (DEBUG_PRINT_OPEN_IMAGE_COUNT) {
158                    Log.v(TAG, "Closed an image. Number of open images = " + numOpenImages);
159                }
160            }
161
162            mImage = null;
163
164            mMetadata = null;
165        }
166
167        /**
168         * @return true if both the image and metadata are present, false
169         *         otherwise.
170         */
171        public boolean isComplete() {
172            return mImage != null && mMetadata != null;
173        }
174
175        /**
176         * Adds the image. Note that this can only be called once before a
177         * {@link #reset()} is necessary.
178         *
179         * @param image the {@Link Image} to add.
180         */
181        public void addImage(Image image) {
182            if (mImage != null) {
183                throw new IllegalArgumentException(
184                        "Unable to add an Image when one already exists.");
185            }
186            mImage = image;
187        }
188
189        /**
190         * Retrieves the {@link Image} if it has been added, returns null if it
191         * is not available yet.
192         */
193        public Image tryGetImage() {
194            return mImage;
195        }
196
197        /**
198         * Adds the metadata. Note that this can only be called once before a
199         * {@link #reset()} is necessary.
200         *
201         * @param metadata the {@Link TotalCaptureResult} to add.
202         */
203        public void addMetadata(TotalCaptureResult metadata) {
204            if (mMetadata != null) {
205                throw new IllegalArgumentException(
206                        "Unable to add a TotalCaptureResult when one already exists.");
207            }
208            mMetadata = metadata;
209        }
210
211        /**
212         * Retrieves the {@link TotalCaptureResult} if it has been added,
213         * returns null if it is not available yet.
214         */
215        public TotalCaptureResult tryGetMetadata() {
216            return mMetadata;
217        }
218    }
219
220    private static final Tag TAG = new Tag("ZSLImageListener");
221
222    /**
223     * If true, the number of open images will be printed to LogCat every time
224     * an image is opened or closed.
225     */
226    private static final boolean DEBUG_PRINT_OPEN_IMAGE_COUNT = false;
227
228    /**
229     * The maximum duration for an onImageAvailable() callback before debugging
230     * output is printed. This is a little under 1/30th of a second to enable
231     * detecting jank in the preview stream caused by {@link #onImageAvailable}
232     * taking too long to return.
233     */
234    private static final long DEBUG_MAX_IMAGE_CALLBACK_DUR = 25;
235
236    /**
237     * If spacing between onCaptureCompleted() callbacks is lower than this
238     * value, camera operations at the Java level have stalled, and are now
239     * catching up. In milliseconds.
240     */
241    private static final long DEBUG_INTERFRAME_STALL_WARNING = 5;
242
243    /**
244     * Last called to onCaptureCompleted() in SystemClock.uptimeMillis().
245     */
246    private long mDebugLastOnCaptureCompletedMillis = 0;
247
248    /**
249     * Number of frames in a row exceeding DEBUG_INTERFRAME_STALL_WARNING.
250     */
251    private long mDebugStalledFrameCount = 0;
252
253    /**
254     * Stores the ring-buffer of captured images.<br>
255     * Note that this takes care of thread-safe reference counting of images to
256     * ensure that they are never leaked by the app.
257     */
258    private final ConcurrentSharedRingBuffer<CapturedImage> mCapturedImageBuffer;
259
260    /** Track the number of open images for debugging purposes. */
261    private final AtomicInteger mNumOpenImages = new AtomicInteger(0);
262
263    /**
264     * The handler used to invoke light-weight listeners:
265     * {@link CaptureReadyListener} and {@link MetadataChangeListener}.
266     */
267    private final Handler mListenerHandler;
268
269    /**
270     * The executor used to invoke {@link ImageCaptureListener}. Note that this
271     * is different from mListenerHandler because a typical ImageCaptureListener
272     * will compress the image to jpeg, and we may wish to execute these tasks
273     * on multiple threads.
274     */
275    private final Executor mImageCaptureListenerExecutor;
276
277    /**
278     * The set of constraints which must be satisfied for a newly acquired image
279     * to be captured and sent to {@link #mPendingImageCaptureListener}. null if
280     * there is no pending capture request.
281     */
282    private List<ImageCaptureManager.CapturedImageConstraint> mPendingImageCaptureConstraints;
283
284    /**
285     * The callback to be invoked upon successfully capturing a newly-acquired
286     * image which satisfies {@link #mPendingImageCaptureConstraints}. null if
287     * there is no pending capture request.
288     */
289    private ImageCaptureManager.ImageCaptureListener mPendingImageCaptureListener;
290
291    /**
292     * Map from CaptureResult key to the frame number of the capture result
293     * containing the most recent value for this key and the most recent value
294     * of the key.
295     */
296    private final Map<Key<?>, Pair<Long, Object>>
297            mMetadata = new ConcurrentHashMap<CaptureResult.Key<?>, Pair<Long, Object>>();
298
299    /**
300     * The set of callbacks to be invoked when an entry in {@link #mMetadata} is
301     * changed.
302     */
303    private final Map<Key<?>, Set<MetadataChangeListener>>
304            mMetadataChangeListeners = new ConcurrentHashMap<Key<?>, Set<MetadataChangeListener>>();
305
306    /**
307     * @param maxImages the maximum number of images provided by the
308     *            {@link ImageReader}. This must be greater than 2.
309     * @param listenerHandler the handler on which to invoke listeners. Note
310     *            that this should probably be on a different thread than the
311     *            one used for camera operations, such as capture requests and
312     *            OnImageAvailable listeners, to avoid stalling the preview.
313     * @param imageCaptureCallbackExecutor the executor on which to invoke image
314     *            capture listeners, {@link ImageCaptureListener}.
315     */
316    ImageCaptureManager(int maxImages, Handler listenerHandler,
317            Executor imageCaptureCallbackExecutor) {
318        // Ensure that there are always 2 images available for the framework to
319        // continue processing frames.
320        // TODO Could we make this tighter?
321        mCapturedImageBuffer = new ConcurrentSharedRingBuffer<ImageCaptureManager.CapturedImage>(
322                maxImages - 2);
323
324        mListenerHandler = listenerHandler;
325        mImageCaptureListenerExecutor = imageCaptureCallbackExecutor;
326    }
327
328    /**
329     * See {@link CaptureReadyListener}.
330     */
331    public void setCaptureReadyListener(final CaptureReadyListener listener) {
332        mCapturedImageBuffer.setListener(mListenerHandler,
333                new PinStateListener() {
334                @Override
335                    public void onPinStateChange(boolean pinsAvailable) {
336                        listener.onReadyStateChange(pinsAvailable);
337                    }
338                });
339    }
340
341    /**
342     * Adds a metadata stream listener associated with the given key.
343     *
344     * @param key the key of the metadata to track.
345     * @param listener the listener to be invoked when the value associated with
346     *            key changes.
347     */
348    public <T> void addMetadataChangeListener(Key<T> key, MetadataChangeListener listener) {
349        if (!mMetadataChangeListeners.containsKey(key)) {
350            // Listeners may be added to this set from a different thread than
351            // that which must iterate over this set to invoke the listeners.
352            // Therefore, we need a thread save hash set.
353            mMetadataChangeListeners.put(key,
354                    Collections.newSetFromMap(new ConcurrentHashMap<
355                            ImageCaptureManager.MetadataChangeListener, Boolean>()));
356        }
357        mMetadataChangeListeners.get(key).add(listener);
358    }
359
360    /**
361     * Removes the metadata stream listener associated with the given key.
362     *
363     * @param key the key associated with the metadata to track.
364     * @param listener the listener to be invoked when the value associated with
365     *            key changes.
366     * @return true if the listener was removed, false if no such listener had
367     *         been added.
368     */
369    public <T> boolean removeMetadataChangeListener(Key<T> key, MetadataChangeListener listener) {
370        if (!mMetadataChangeListeners.containsKey(key)) {
371            return false;
372        } else {
373            return mMetadataChangeListeners.get(key).remove(listener);
374        }
375    }
376
377    @Override
378    public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
379            final CaptureResult partialResult) {
380        long frameNumber = partialResult.getFrameNumber();
381
382        // Update mMetadata for whichever keys are present, if this frame is
383        // supplying newer values.
384        for (final Key<?> key : partialResult.getKeys()) {
385            Pair<Long, Object> oldEntry = mMetadata.get(key);
386            final Object oldValue = (oldEntry != null) ? oldEntry.second : null;
387
388            boolean newerValueAlreadyExists = oldEntry != null
389                    && frameNumber < oldEntry.first;
390            if (newerValueAlreadyExists) {
391                continue;
392            }
393
394            final Object newValue = partialResult.get(key);
395            mMetadata.put(key, new Pair<Long, Object>(frameNumber, newValue));
396
397            // If the value has changed, call the appropriate listeners, if
398            // any exist.
399            if (oldValue == newValue || !mMetadataChangeListeners.containsKey(key)) {
400                continue;
401            }
402
403            for (final MetadataChangeListener listener :
404                    mMetadataChangeListeners.get(key)) {
405                Log.v(TAG, "Dispatching to metadata change listener for key: "
406                        + key.toString());
407                mListenerHandler.post(new Runnable() {
408                        @Override
409                    public void run() {
410                        listener.onImageMetadataChange(key, oldValue, newValue,
411                                partialResult);
412                    }
413                });
414            }
415        }
416    }
417
418    @Override
419    public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
420            final TotalCaptureResult result) {
421        final long timestamp = result.get(TotalCaptureResult.SENSOR_TIMESTAMP);
422
423        // Detect camera thread stall.
424        long now = SystemClock.uptimeMillis();
425        if (now - mDebugLastOnCaptureCompletedMillis < DEBUG_INTERFRAME_STALL_WARNING) {
426            Log.e(TAG, "Camera thread has stalled for " + ++mDebugStalledFrameCount +
427                    " frames at # " + result.getFrameNumber() + ".");
428        } else {
429            mDebugStalledFrameCount = 0;
430        }
431        mDebugLastOnCaptureCompletedMillis = now;
432
433        // Find the CapturedImage in the ring-buffer and attach the
434        // TotalCaptureResult to it.
435        // See documentation for swapLeast() for details.
436        boolean swapSuccess = mCapturedImageBuffer.swapLeast(timestamp,
437                new SwapTask<CapturedImage>() {
438                @Override
439                    public CapturedImage create() {
440                        CapturedImage image = new CapturedImage();
441                        image.addMetadata(result);
442                        return image;
443                    }
444
445                @Override
446                    public CapturedImage swap(CapturedImage oldElement) {
447                        oldElement.reset();
448                        oldElement.addMetadata(result);
449                        return oldElement;
450                    }
451
452                @Override
453                    public void update(CapturedImage existingElement) {
454                        existingElement.addMetadata(result);
455                    }
456                });
457
458        if (!swapSuccess) {
459            // Do nothing on failure to swap in.
460            Log.v(TAG, "Unable to add new image metadata to ring-buffer.");
461        }
462
463        tryExecutePendingCaptureRequest(timestamp);
464    }
465
466    @Override
467    public void onImageAvailable(ImageReader reader) {
468        long startTime = SystemClock.currentThreadTimeMillis();
469
470        final Image img = reader.acquireLatestImage();
471
472        if (img != null) {
473            int numOpenImages = mNumOpenImages.incrementAndGet();
474            if (DEBUG_PRINT_OPEN_IMAGE_COUNT) {
475                Log.v(TAG, "Acquired an image. Number of open images = " + numOpenImages);
476            }
477
478            // Try to place the newly-acquired image into the ring buffer.
479            boolean swapSuccess = mCapturedImageBuffer.swapLeast(
480                    img.getTimestamp(), new SwapTask<CapturedImage>() {
481                            @Override
482                        public CapturedImage create() {
483                            CapturedImage image = new CapturedImage();
484                            image.addImage(img);
485                            return image;
486                        }
487
488                            @Override
489                        public CapturedImage swap(CapturedImage oldElement) {
490                            oldElement.reset();
491                            oldElement.addImage(img);
492                            return oldElement;
493                        }
494
495                            @Override
496                        public void update(CapturedImage existingElement) {
497                            existingElement.addImage(img);
498                        }
499                    });
500
501            if (!swapSuccess) {
502                // If we were unable to save the image to the ring buffer, we
503                // must close it now.
504                // We should only get here if the ring buffer is closed.
505                img.close();
506                numOpenImages = mNumOpenImages.decrementAndGet();
507                if (DEBUG_PRINT_OPEN_IMAGE_COUNT) {
508                    Log.v(TAG, "Closed an image. Number of open images = " + numOpenImages);
509                }
510            }
511
512            tryExecutePendingCaptureRequest(img.getTimestamp());
513
514            long endTime = SystemClock.currentThreadTimeMillis();
515            long totTime = endTime - startTime;
516            if (totTime > DEBUG_MAX_IMAGE_CALLBACK_DUR) {
517                // If it takes too long to swap elements, we will start skipping
518                // preview frames, resulting in visible jank.
519                Log.v(TAG, "onImageAvailable() took " + totTime + "ms");
520            }
521        }
522    }
523
524    /**
525     * Closes the listener, eventually freeing all currently-held {@link Image}
526     * s.
527     */
528    public void close() {
529        try {
530            mCapturedImageBuffer.close(new Task<CapturedImage>() {
531                    @Override
532                public void run(CapturedImage e) {
533                    e.reset();
534                }
535            });
536        } catch (InterruptedException e) {
537            e.printStackTrace();
538        }
539    }
540
541    /**
542     * Sets the pending image capture request, overriding any previous calls to
543     * {@link #captureNextImage} which have not yet been resolved. When the next
544     * available image which satisfies the given constraints can be captured,
545     * onImageCaptured will be invoked.
546     *
547     * @param onImageCaptured the callback which will be invoked with the
548     *            captured image.
549     * @param constraints the set of constraints which must be satisfied in
550     *            order for the image to be captured.
551     */
552    public void captureNextImage(final ImageCaptureListener onImageCaptured,
553            final List<CapturedImageConstraint> constraints) {
554        mPendingImageCaptureListener = onImageCaptured;
555        mPendingImageCaptureConstraints = constraints;
556    }
557
558    /**
559     * Tries to resolve any pending image capture requests.
560     *
561     * @param newImageTimestamp the timestamp of a newly-acquired image which
562     *            should be captured if appropriate and possible.
563     */
564    private void tryExecutePendingCaptureRequest(long newImageTimestamp) {
565        if (mPendingImageCaptureListener != null) {
566            final Pair<Long, CapturedImage> pinnedImage = mCapturedImageBuffer.tryPin(
567                    newImageTimestamp);
568            if (pinnedImage != null) {
569                CapturedImage image = pinnedImage.second;
570
571                if (!image.isComplete()) {
572                    mCapturedImageBuffer.release(pinnedImage.first);
573                    return;
574                }
575
576                // Check to see if the image satisfies all constraints.
577                TotalCaptureResult captureResult = image.tryGetMetadata();
578
579                if (mPendingImageCaptureConstraints != null) {
580                    for (CapturedImageConstraint constraint : mPendingImageCaptureConstraints) {
581                        if (!constraint.satisfiesConstraint(captureResult)) {
582                            mCapturedImageBuffer.release(pinnedImage.first);
583                            return;
584                        }
585                    }
586                }
587
588                // If we get here, the image satisfies all the necessary
589                // constraints.
590
591                if (tryExecuteCaptureOrRelease(pinnedImage, mPendingImageCaptureListener)) {
592                    // If we successfully handed the image off to the callback,
593                    // remove the pending
594                    // capture request.
595                    mPendingImageCaptureListener = null;
596                    mPendingImageCaptureConstraints = null;
597                }
598            }
599        }
600    }
601
602    /**
603     * Tries to capture an existing image from the ring-buffer, if one exists
604     * that satisfies the given constraint and can be pinned.
605     *
606     * @return true if the image could be captured, false otherwise.
607     */
608    public boolean tryCaptureExistingImage(final ImageCaptureListener onImageCaptured,
609            final List<CapturedImageConstraint> constraints) {
610        // The selector to use in choosing the image to capture.
611        Selector<ImageCaptureManager.CapturedImage> selector;
612
613        if (constraints == null || constraints.isEmpty()) {
614            // If there are no constraints, use a trivial Selector.
615            selector = new Selector<ImageCaptureManager.CapturedImage>() {
616                    @Override
617                public boolean select(CapturedImage image) {
618                    return true;
619                }
620            };
621        } else {
622            // If there are constraints, create a Selector which will return
623            // true if all constraints
624            // are satisfied.
625            selector = new Selector<ImageCaptureManager.CapturedImage>() {
626                    @Override
627                public boolean select(CapturedImage e) {
628                    // If this image already has metadata associated with it,
629                    // then use it.
630                    // Otherwise, we can't block until it's available, so assume
631                    // it doesn't
632                    // satisfy the required constraints.
633                    TotalCaptureResult captureResult = e.tryGetMetadata();
634
635                    if (captureResult == null || e.tryGetImage() == null) {
636                        return false;
637                    }
638
639                    for (CapturedImageConstraint constraint : constraints) {
640                        if (!constraint.satisfiesConstraint(captureResult)) {
641                            return false;
642                        }
643                    }
644                    return true;
645                }
646            };
647        }
648
649        // Acquire a lock (pin) on the most recent (greatest-timestamp) image in
650        // the ring buffer which satisfies our constraints.
651        // Note that this must be released as soon as we are done with it.
652        final Pair<Long, CapturedImage> toCapture = mCapturedImageBuffer.tryPinGreatestSelected(
653                selector);
654
655        return tryExecuteCaptureOrRelease(toCapture, onImageCaptured);
656    }
657
658    /**
659     * Tries to execute the image capture callback with the pinned CapturedImage
660     * provided.
661     *
662     * @param toCapture The pinned CapturedImage to pass to the callback, or
663     *            release on failure.
664     * @param callback The callback to execute.
665     * @return true upon success, false upon failure and the release of the
666     *         pinned image.
667     */
668    private boolean tryExecuteCaptureOrRelease(final Pair<Long, CapturedImage> toCapture,
669            final ImageCaptureListener callback) {
670        if (toCapture == null) {
671            return false;
672        } else {
673            try {
674                mImageCaptureListenerExecutor.execute(new Runnable() {
675                        @Override
676                    public void run() {
677                        try {
678                            CapturedImage img = toCapture.second;
679                            callback.onImageCaptured(img.tryGetImage(),
680                                    img.tryGetMetadata());
681                        } finally {
682                            mCapturedImageBuffer.release(toCapture.first);
683                        }
684                    }
685                });
686            } catch (RejectedExecutionException e) {
687                // We may get here if the thread pool has been closed.
688                mCapturedImageBuffer.release(toCapture.first);
689                return false;
690            }
691
692            return true;
693        }
694    }
695}
696