blob: cb4edd758f6905289c59be923b057d8c42daaa68 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.camera.one.v2.photo.zsl;
import android.hardware.camera2.CameraAccessException;
import androidx.core.util.Pair;
import com.android.camera.async.BufferQueue;
import com.android.camera.async.Updatable;
import com.android.camera.debug.Log;
import com.android.camera.debug.Logger;
import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionClosedException;
import com.android.camera.one.v2.camera2proxy.ImageProxy;
import com.android.camera.one.v2.camera2proxy.TotalCaptureResultProxy;
import com.android.camera.one.v2.core.ResourceAcquisitionFailedException;
import com.android.camera.one.v2.imagesaver.ImageSaver;
import com.android.camera.one.v2.photo.ImageCaptureCommand;
import com.android.camera.one.v2.sharedimagereader.metadatasynchronizer.MetadataPool;
import com.google.common.base.Predicate;
import com.google.common.util.concurrent.Futures;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
/**
* Captures images by first looking to the zsl ring buffer for acceptable (based
* on metadata) images. If no such images are available, a fallback
* ImageCaptureCommand is used instead.
*/
@ParametersAreNonnullByDefault
public class ZslImageCaptureCommand implements ImageCaptureCommand {
private final Logger mLog;
private final BufferQueue<ImageProxy> mZslRingBuffer;
private final MetadataPool mZslMetadataPool;
private final ImageCaptureCommand mFallbackCommand;
private final Predicate<TotalCaptureResultProxy> mMetadataFilter;
private final long mMaxLookBackNanos;
public ZslImageCaptureCommand(Logger.Factory logFactory,
BufferQueue<ImageProxy> zslRingBuffer,
MetadataPool zslMetadataPool,
ImageCaptureCommand fallbackCommand,
Predicate<TotalCaptureResultProxy> metadataFilter,
long maxLookBackNanos) {
mZslRingBuffer = zslRingBuffer;
mLog = logFactory.create(new Log.Tag("ZSLImageCaptureCmd"));
mZslMetadataPool = zslMetadataPool;
mFallbackCommand = fallbackCommand;
mMetadataFilter = metadataFilter;
mMaxLookBackNanos = maxLookBackNanos;
}
/**
* @return All images currently in the ring-buffer, ordered from oldest to
* most recent.
*/
private List<ImageProxy> getAllAvailableImages() throws InterruptedException,
BufferQueue.BufferQueueClosedException {
List<ImageProxy> images = new ArrayList<>();
try {
// Keep grabbing images until there are no more immediately
// available in the ring buffer.
while (true) {
try {
images.add(mZslRingBuffer.getNext(0, TimeUnit.SECONDS));
} catch (TimeoutException e) {
break;
}
}
} catch (Exception e) {
// Close the images to avoid leaking them, since they will not be
// returned to the caller.
for (ImageProxy image : images) {
image.close();
}
throw e;
}
return images;
}
private List<ImageProxy> filterImagesWithinMaxLookBack(List<ImageProxy> images) {
if (images.isEmpty()) {
return Collections.emptyList();
}
List<ImageProxy> filtered = new ArrayList<>();
long mostRecentTimestamp = images.get(images.size() - 1).getTimestamp();
long timestampThreshold = mostRecentTimestamp - mMaxLookBackNanos;
for (ImageProxy image : images) {
if (image.getTimestamp() > timestampThreshold) {
filtered.add(image);
} else {
image.close();
}
}
return filtered;
}
@Nullable
private Pair<ImageProxy, TotalCaptureResultProxy> tryGetZslImage() throws InterruptedException,
BufferQueue.BufferQueueClosedException {
List<ImageProxy> images = filterImagesWithinMaxLookBack(getAllAvailableImages());
ImageProxy imageToSave = null;
TotalCaptureResultProxy metadata = null;
try {
for (ImageProxy image : images) {
Future<TotalCaptureResultProxy> metadataFuture =
mZslMetadataPool.removeMetadataFuture(image.getTimestamp());
try {
if (mMetadataFilter.apply(metadataFuture.get())) {
imageToSave = image;
metadata = metadataFuture.get();
}
} catch (ExecutionException | CancellationException e) {
// If we cannot get metadata for an image, for whatever
// reason, assume it is not acceptable for capture.
}
}
} catch (Exception e) {
if (imageToSave != null) {
imageToSave.close();
}
throw e;
} finally {
for (ImageProxy image : images) {
if (image != imageToSave) {
image.close();
}
}
}
if (imageToSave == null) {
return null;
} else {
return new Pair<>(imageToSave, metadata);
}
}
@Override
public void run(Updatable<Void> imageExposeCallback, ImageSaver imageSaver)
throws InterruptedException, CameraAccessException,
CameraCaptureSessionClosedException, ResourceAcquisitionFailedException {
boolean mustCloseImageSaver = true;
try {
Pair<ImageProxy, TotalCaptureResultProxy> image = tryGetZslImage();
if (image != null) {
mLog.i("ZSL image available");
imageExposeCallback.update(null);
imageSaver.addFullSizeImage(image.first, Futures.immediateFuture(image.second));
} else {
mLog.i("No ZSL image available, using fallback: " + mFallbackCommand);
mustCloseImageSaver = false;
mFallbackCommand.run(imageExposeCallback, imageSaver);
}
} catch (BufferQueue.BufferQueueClosedException e) {
// The zsl ring buffer has been closed, so do nothing since the
// system is shutting down.
} finally {
if (mustCloseImageSaver) {
imageSaver.close();
}
}
}
}