[go: nahoru, domu]

blob: 0de93ff763a93662fb3ef8d13c30dc57a43b4fa5 [file] [log] [blame]
/*
* Copyright 2019 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 androidx.camera.extensions.impl;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.SessionConfiguration;
import android.media.Image;
import android.media.ImageWriter;
import android.os.Build;
import android.util.Log;
import android.util.Pair;
import android.util.Range;
import android.util.Size;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
/**
* Implementation for night image capture use case.
*
* <p>This implementation enable the Extensions-Interface v1.4 features such as postview,
* onCaptureProcessProgressed callback and realtime capture latency.
*
* <p>This class should be implemented by OEM and deployed to the target devices. 3P developers
* don't need to implement this, unless this is used for related testing usage.
*
* @since 1.0
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class NightImageCaptureExtenderImpl implements ImageCaptureExtenderImpl {
private static final String TAG = "NightICExtender";
private static final int DEFAULT_STAGE_ID = 0;
private static final int SESSION_STAGE_ID = 101;
private static final int EV_INDEX = 10;
private static final int CAPTURE_STAGET_COUNT = 10;
public NightImageCaptureExtenderImpl() {
}
@Override
public void init(@NonNull String cameraId,
@NonNull CameraCharacteristics cameraCharacteristics) {
}
@Override
public boolean isExtensionAvailable(@NonNull String cameraId,
@NonNull CameraCharacteristics cameraCharacteristics) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { // ImageWriter needs API 23.
return false;
}
Range<Integer> compensationRange =
cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
return compensationRange != null && compensationRange.contains(EV_INDEX);
}
@NonNull
@Override
public List<CaptureStageImpl> getCaptureStages() {
// Placeholder set of CaptureRequest.Key values
List<CaptureStageImpl> captureStages = new ArrayList<>();
for (int i = 0; i < CAPTURE_STAGET_COUNT; i++) {
SettableCaptureStage captureStage = new SettableCaptureStage(DEFAULT_STAGE_ID + i);
captureStage.addCaptureRequestParameters(
CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, EV_INDEX);
captureStages.add(captureStage);
}
return captureStages;
}
private NightCaptureProcessorImpl mCaptureProcessor = null;
@Nullable
@Override
public CaptureProcessorImpl getCaptureProcessor() {
if (mCaptureProcessor == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Needs ImageWriter
mCaptureProcessor = new NightCaptureProcessorImpl();
}
}
return mCaptureProcessor;
}
@Override
public void onInit(@NonNull String cameraId,
@NonNull CameraCharacteristics cameraCharacteristics,
@NonNull Context context) {
}
@Override
public void onDeInit() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && mCaptureProcessor != null) {
mCaptureProcessor.release();
}
}
@Nullable
@Override
public CaptureStageImpl onPresetSession() {
// Set the necessary CaptureRequest parameters via CaptureStage, here we use some
// placeholder set of CaptureRequest.Key values
SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
return captureStage;
}
@Nullable
@Override
public CaptureStageImpl onEnableSession() {
// Set the necessary CaptureRequest parameters via CaptureStage, here we use some
// placeholder set of CaptureRequest.Key values
SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
return captureStage;
}
@Nullable
@Override
public CaptureStageImpl onDisableSession() {
// Set the necessary CaptureRequest parameters via CaptureStage, here we use some
// placeholder set of CaptureRequest.Key values
SettableCaptureStage captureStage = new SettableCaptureStage(SESSION_STAGE_ID);
return captureStage;
}
@Override
public int getMaxCaptureStage() {
return CAPTURE_STAGET_COUNT + 1;
}
@Nullable
@Override
public List<Pair<Integer, Size[]>> getSupportedResolutions() {
return null;
}
@Nullable
@Override
public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(@Nullable Size captureSize) {
Pair<Integer, Size[]> pair = new Pair<>(ImageFormat.YUV_420_888, new Size[]{captureSize});
return Arrays.asList(pair);
}
@Nullable
@Override
public Range<Long> getEstimatedCaptureLatencyRange(@Nullable Size captureOutputSize) {
return new Range<>(300L, 1000L);
}
@NonNull
@Override
public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
List<CaptureRequest.Key> keys = new ArrayList<>(Arrays.asList(
CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_REGIONS,
CaptureRequest.CONTROL_AE_REGIONS));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
keys.add(CaptureRequest.CONTROL_ZOOM_RATIO);
} else {
keys.add(CaptureRequest.SCALER_CROP_REGION);
}
return Collections.unmodifiableList(keys);
}
@NonNull
@Override
public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
List<CaptureResult.Key> keys = new ArrayList<>(Arrays.asList(
CaptureResult.CONTROL_AF_MODE,
CaptureResult.CONTROL_AE_REGIONS,
CaptureResult.CONTROL_AF_REGIONS,
CaptureResult.CONTROL_AE_STATE,
CaptureResult.CONTROL_AF_STATE));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
keys.add(CaptureResult.CONTROL_ZOOM_RATIO);
} else {
keys.add(CaptureResult.SCALER_CROP_REGION);
}
return Collections.unmodifiableList(keys);
}
@Override
public int onSessionType() {
return SessionConfiguration.SESSION_REGULAR;
}
@Override
public boolean isCaptureProcessProgressAvailable() {
return true;
}
@Nullable
@Override
public Pair<Long, Long> getRealtimeCaptureLatency() {
return new Pair<>(500L, 3000L);
}
@Override
public boolean isPostviewAvailable() {
return true;
}
@RequiresApi(23)
final class NightCaptureProcessorImpl implements CaptureProcessorImpl {
private ImageWriter mImageWriter;
private ImageWriter mImageWriterPostview;
@Override
public void onOutputSurface(@NonNull Surface surface, int imageFormat) {
mImageWriter = ImageWriter.newInstance(surface, 2);
}
@Override
public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results) {
processInternal(results, null, null, false);
}
@Override
public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
@NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
processInternal(results, resultCallback, executor, false);
}
@SuppressWarnings("BanThreadSleep")
public void processInternal(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
@Nullable ProcessResultImpl resultCallback, @Nullable Executor executor,
boolean hasPostview) {
Executor executorForCallback = executor != null ? executor : (cmd) -> cmd.run();
// Check for availability of all requested images
for (int i = 0; i < getMaxCaptureStage() - 1; i++) {
if (!results.containsKey(DEFAULT_STAGE_ID + i)) {
Log.w(TAG,
"Unable to process since images does not contain all images.");
return;
}
}
// Do processing of images, our placeholder logic just copies the first
// Image into the output buffer.
List<Pair<Image, TotalCaptureResult>> imageDataPairs = new ArrayList<>(
results.values());
Image outputImage = mImageWriter.dequeueInputImage();
// Do processing here
// The sample here simply returns the normal image result
int stageId = DEFAULT_STAGE_ID;
Image normalImage = imageDataPairs.get(stageId).first;
TotalCaptureResult captureResult = imageDataPairs.get(stageId).second;
if (resultCallback != null) {
executorForCallback.execute(() -> {
resultCallback.onCaptureProcessProgressed(10);
});
}
try {
ByteBuffer yByteBuffer = outputImage.getPlanes()[0].getBuffer();
ByteBuffer uByteBuffer = outputImage.getPlanes()[2].getBuffer();
ByteBuffer vByteBuffer = outputImage.getPlanes()[1].getBuffer();
// Sample here just simply copy/paste the capture image result
yByteBuffer.put(normalImage.getPlanes()[0].getBuffer());
if (resultCallback != null) {
executorForCallback.execute(
() -> resultCallback.onCaptureProcessProgressed(50));
}
uByteBuffer.put(normalImage.getPlanes()[2].getBuffer());
vByteBuffer.put(normalImage.getPlanes()[1].getBuffer());
} catch (IllegalStateException e) {
Log.e(TAG, "Error accessing the Image: " + e);
// Since something went wrong, don't try to queue up the image.
// Instead let the Image writing get dropped.
return;
}
if (resultCallback != null) {
executorForCallback.execute(
() -> resultCallback.onCaptureProcessProgressed(75));
}
if (hasPostview) {
mImageWriterPostview.queueInputImage(normalImage);
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
mImageWriter.queueInputImage(outputImage);
if (resultCallback != null) {
executorForCallback.execute(
() -> resultCallback.onCaptureProcessProgressed(100));
}
if (resultCallback != null) {
executorForCallback.execute(
() -> resultCallback.onCaptureCompleted(
captureResult.get(CaptureResult.SENSOR_TIMESTAMP),
getFilteredResults(captureResult)));
}
}
@SuppressWarnings("unchecked")
private List<Pair<CaptureResult.Key, Object>> getFilteredResults(
TotalCaptureResult captureResult) {
List<Pair<CaptureResult.Key, Object>> list = new ArrayList<>();
for (CaptureResult.Key key : captureResult.getKeys()) {
list.add(new Pair<>(key, captureResult.get(key)));
}
return list;
}
@Override
public void onResolutionUpdate(@NonNull Size size) {
}
@Override
public void onImageFormatUpdate(int imageFormat) {
}
@Override
public void onPostviewOutputSurface(@NonNull Surface surface) {
mImageWriterPostview = ImageWriter.newInstance(surface, ImageFormat.YUV_420_888);
}
@Override
public void onResolutionUpdate(@NonNull Size size, @NonNull Size postviewSize) {
onResolutionUpdate(size);
}
@Override
public void processWithPostview(
@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
@NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
processInternal(results, resultCallback, executor, true);
}
void release() {
if (mImageWriter != null) {
mImageWriter.close();
}
if (mImageWriterPostview != null) {
mImageWriterPostview.close();
}
}
}
}