blob: de119c98ddd235cb2c6ed42ffaf7d94c22c74c2e [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.example.cannylive;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;
import android.util.Range;
import android.util.Size;
import android.view.Surface;
import android.view.SurfaceHolder;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Simple interface for operating the camera, with major camera operations
* all performed on a background handler thread.
*/
public class CameraOps {
private static final String TAG = "CameraOps";
private static final long ONE_SECOND = 1000000000;
public static final long CAMERA_CLOSE_TIMEOUT = 2000; // ms
private final CameraManager mCameraManager;
private CameraDevice mCameraDevice;
private CameraCaptureSession mCameraSession;
private List<Surface> mSurfaces;
private final ConditionVariable mCloseWaiter = new ConditionVariable();
private HandlerThread mCameraThread;
private Handler mCameraHandler;
private final ErrorDisplayer mErrorDisplayer;
private final CameraReadyListener mReadyListener;
private final Handler mReadyHandler;
private int mISOmax;
private int mISOmin;
private long mExpMax;
private long mExpMin;
private float mFocusMin;
private float mFocusDist = 0;
private int mIso;
boolean mAutoExposure = true;
boolean mAutoFocus = true;
private long mExposure = ONE_SECOND / 33;
private Object mAutoExposureTag = new Object();
private ImageReader mImageReader;
private Handler mBackgroundHandler;
private CameraCharacteristics mCameraInfo;
private HandlerThread mBackgroundThread;
CaptureRequest.Builder mHdrBuilder;
private Surface mProcessingNormalSurface;
CaptureRequest mPreviewRequest;
private String mSaveFileName;
private Context mContext;
private int mCaptureMode;
public String resume() {
String errorMessage = "Unknown error";
boolean foundCamera = false;
try {
// Find first back-facing camera that has necessary capability
String[] cameraIds = mCameraManager.getCameraIdList();
for (String id : cameraIds) {
CameraCharacteristics info = mCameraManager.getCameraCharacteristics(id);
int facing = info.get(CameraCharacteristics.LENS_FACING);
int level = info.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
boolean hasFullLevel
= (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
int[] capabilities = info.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
int syncLatency = info.get(CameraCharacteristics.SYNC_MAX_LATENCY);
boolean hasManualControl = hasCapability(capabilities,
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR);
boolean hasEnoughCapability = hasManualControl &&
syncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL;
Range<Integer> irange;
Range<Long> lrange;
irange = info.get(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE);
if (irange != null) {
mISOmax = irange.getUpper();
mISOmin = irange.getLower();
lrange = info.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE);
mExpMax = lrange.getUpper();
mExpMin = lrange.getLower();
mFocusMin = info.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);
} else {
mISOmax = 200;
mISOmin = 100;
mExpMax = 1000;
}
mFocusDist = mFocusMin;
StreamConfigurationMap map = info.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] sizes = map.getOutputSizes(ImageFormat.JPEG);
List<Size> sizeList = Arrays.asList(sizes);
Collections.sort(sizeList, new Comparator<Size>() {
@Override
public int compare(Size lhs, Size rhs) {
int leftArea = lhs.getHeight() * lhs.getWidth();
int rightArea = lhs.getHeight() * lhs.getWidth();
return Integer.compare(leftArea, rightArea);
}
});
Size max = sizeList.get(0);
int check = 1;
Size big = sizeList.get(check);
float aspect = 16/9f;
Log.v(TAG,"max big "+max.getWidth()+" x "+max.getHeight());
for (int i = 0; i < sizeList.size(); i++) {
Size s = sizeList.get(i);
if (s.getHeight() == 720) {
big = s;
break;
}
}
Log.v(TAG,"BIG wil be "+big.getWidth()+" x "+big.getHeight());
mImageReader = ImageReader.newInstance(big.getWidth(), big.getHeight(),
ImageFormat.JPEG, /*maxImages*/2);
mImageReader.setOnImageAvailableListener(
mOnImageAvailableListener, mBackgroundHandler);
if (facing == CameraCharacteristics.LENS_FACING_BACK &&
(hasFullLevel || hasEnoughCapability)) {
// Found suitable camera - get info, open, and set up outputs
mCameraInfo = info;
openCamera(id);
foundCamera = true;
break;
}
}
if (!foundCamera) {
errorMessage = "no back camera";
}
} catch (CameraAccessException e) {
errorMessage = e.getMessage();
}
// startBackgroundThread
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
return (foundCamera) ? null : errorMessage;
}
private boolean hasCapability(int[] capabilities, int capability) {
for (int c : capabilities) {
if (c == capability) return true;
}
return false;
}
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(),
mSaveFileName, mContext,mCaptureMode));
}
};
/**
* Saves a JPEG {@link android.media.Image} into the specified {@link java.io.File}.
*/
private static class ImageSaver implements Runnable {
private final Image mImage;
private final String mName;
Context mContext;
private int mMode;
public ImageSaver(Image image, String fileName, Context context,int mode) {
mImage = image;
mName = fileName;
mContext = context;
mMode = mode;
}
@Override
public void run() {
Log.v(TAG, "S>> SAVING...");
String url = MediaStoreSaver.insertImage(mContext.getContentResolver(),
new MediaStoreSaver.StreamWriter() {
@Override
public void write(OutputStream imageOut) throws IOException {
try {
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
Log.v(TAG, "S>> size=" + mImage.getWidth() +
"," + mImage.getHeight());
Log.v(TAG, "S>> bytes " + bytes.length +
" (" + bytes.length / (1024 * 1024) + "MB");
Log.v(TAG, "S>> bytes out " + bytes.length / mImage.getWidth());
buffer.get(bytes);
imageOut.write(bytes);
} finally {
mImage.close();
}
}
}, mName, "Saved from Simple Camera Demo");
ViewfinderProcessor.reProcessImage(mContext, url, mMode);
}
}
/**
* Create a new camera ops thread.
*
* @param errorDisplayer listener for displaying error messages
* @param readyListener listener for notifying when camera is ready for requests
*/
CameraOps(CameraManager manager, ErrorDisplayer errorDisplayer,
CameraReadyListener readyListener) {
mReadyHandler = new Handler(Looper.getMainLooper());
mCameraThread = new HandlerThread("CameraOpsThread");
mCameraThread.start();
if (manager == null || errorDisplayer == null ||
readyListener == null || mReadyHandler == null) {
throw new IllegalArgumentException("Need valid displayer, listener, handler");
}
mCameraManager = manager;
mErrorDisplayer = errorDisplayer;
mReadyListener = readyListener;
}
/**
* Open the first backfacing camera listed by the camera manager.
* Displays a dialog if it cannot open a camera.
*/
public void openCamera(final String cameraId) {
mCameraHandler = new Handler(mCameraThread.getLooper());
mCameraHandler.post(new Runnable() {
public void run() {
if (mCameraDevice != null) {
throw new IllegalStateException("Camera already open");
}
try {
mCameraManager.openCamera(cameraId, mCameraDeviceListener, mCameraHandler);
} catch (CameraAccessException e) {
String errorMessage = mErrorDisplayer.getErrorString(e);
mErrorDisplayer.showErrorDialog(errorMessage);
}
}
});
}
public void pause() {
closeCameraAndWait();
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public Size getBestSize() {
// Find a good size for output - largest 16:9 aspect ratio that's less than 720p
final int MAX_WIDTH = 640;
final float TARGET_ASPECT = 16.f / 9.f;
StreamConfigurationMap configs =
mCameraInfo.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] outputSizes = configs.getOutputSizes(SurfaceHolder.class);
Size outputSize = null;
ArrayList<Size> smallEnough = new ArrayList<Size>();
for (Size candidateSize : outputSizes) {
if (candidateSize.getWidth() <= MAX_WIDTH) {
Log.v(TAG, "consider " + candidateSize);
smallEnough.add(candidateSize);
}
}
if (smallEnough.size() == 0) {
return outputSizes[outputSizes.length - 1]; //pick the smallest
}
Size maxSize = smallEnough.get(0);
double aspectDelta = Math.abs(maxSize.getWidth() / maxSize.getHeight() - TARGET_ASPECT);
for (Size candidateSize : smallEnough) {
if (maxSize.getWidth() < candidateSize.getWidth()) {
maxSize = candidateSize;
aspectDelta = Math.abs(maxSize.getWidth() / maxSize.getHeight() - TARGET_ASPECT);
}
if (maxSize.getWidth() == candidateSize.getWidth()) {
if (aspectDelta > Math.abs(candidateSize.getWidth() / candidateSize.getHeight() - TARGET_ASPECT)) {
maxSize = candidateSize;
aspectDelta = Math.abs(maxSize.getWidth() / maxSize.getHeight() - TARGET_ASPECT);
}
}
}
return maxSize;
}
/**
* Close the camera and wait for the close callback to be called in the camera thread.
* Times out after @{value CAMERA_CLOSE_TIMEOUT} ms.
*/
public void closeCameraAndWait() {
mCloseWaiter.close();
mCameraHandler.post(mCloseCameraRunnable);
boolean closed = mCloseWaiter.block(CAMERA_CLOSE_TIMEOUT);
if (!closed) {
Log.e(TAG, "Timeout closing camera");
}
}
private Runnable mCloseCameraRunnable = new Runnable() {
public void run() {
if (mCameraDevice != null) {
mCameraDevice.close();
}
mCameraDevice = null;
mCameraSession = null;
mSurfaces = null;
}
};
/**
* Set the output Surfaces, and finish configuration if otherwise ready.
*/
public void setSurface(Surface surface) {
final List<Surface> surfaceList = new ArrayList<Surface>();
surfaceList.add(surface);
surfaceList.add(mImageReader.getSurface());
mCameraHandler.post(new Runnable() {
public void run() {
mSurfaces = surfaceList;
startCameraSession();
}
});
}
/**
* Get a request builder for the current camera.
*/
public CaptureRequest.Builder createCaptureRequest(int template) throws CameraAccessException {
CameraDevice device = mCameraDevice;
if (device == null) {
throw new IllegalStateException("Can't get requests when no camera is open");
}
return device.createCaptureRequest(template);
}
/**
* Set a repeating request.
*/
public void setRepeatingRequest(final CaptureRequest request,
final CameraCaptureSession.CaptureCallback listener,
final Handler handler) {
mCameraHandler.post(new Runnable() {
public void run() {
try {
mCameraSession.setRepeatingRequest(request, listener, handler);
} catch (CameraAccessException e) {
String errorMessage = mErrorDisplayer.getErrorString(e);
mErrorDisplayer.showErrorDialog(errorMessage);
}
}
});
}
/**
* Set a repeating request.
*/
public void setRepeatingBurst(final List<CaptureRequest> requests,
final CameraCaptureSession.CaptureCallback listener,
final Handler handler) {
mCameraHandler.post(new Runnable() {
public void run() {
try {
mCameraSession.setRepeatingBurst(requests, listener, handler);
} catch (CameraAccessException e) {
String errorMessage = mErrorDisplayer.getErrorString(e);
mErrorDisplayer.showErrorDialog(errorMessage);
}
}
});
}
/**
* Configure the camera session.
*/
private void startCameraSession() {
// Wait until both the camera device is open and the SurfaceView is ready
if (mCameraDevice == null || mSurfaces == null) return;
try {
mCameraDevice.createCaptureSession(
mSurfaces, mCameraSessionListener, mCameraHandler);
} catch (CameraAccessException e) {
String errorMessage = mErrorDisplayer.getErrorString(e);
mErrorDisplayer.showErrorDialog(errorMessage);
mCameraDevice.close();
mCameraDevice = null;
}
}
/**
* Main listener for camera session events
* Invoked on mCameraThread
*/
private CameraCaptureSession.StateCallback mCameraSessionListener =
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
mCameraSession = session;
mReadyHandler.post(new Runnable() {
public void run() {
// This can happen when the screen is turned off and turned back on.
if (null == mCameraDevice) {
return;
}
mReadyListener.onCameraReady();
}
});
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
mErrorDisplayer.showErrorDialog("Unable to configure the capture session");
mCameraDevice.close();
mCameraDevice = null;
}
};
/**
* Main listener for camera device events.
* Invoked on mCameraThread
*/
private CameraDevice.StateCallback mCameraDeviceListener = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
mCameraDevice = camera;
startCameraSession();
}
@Override
public void onClosed(CameraDevice camera) {
mCloseWaiter.open();
}
@Override
public void onDisconnected(CameraDevice camera) {
mErrorDisplayer.showErrorDialog("The camera device has been disconnected.");
camera.close();
mCameraDevice = null;
}
@Override
public void onError(CameraDevice camera, int error) {
mErrorDisplayer.showErrorDialog("The camera encountered an error:" + error);
camera.close();
mCameraDevice = null;
}
};
public void captureStillPicture(int currentJpegRotation, String name, Context context, int mode) {
mSaveFileName = name;
mContext = context;
mCaptureMode = mode;
try {
// TODO call lock focus if we are in "AF-S(One-Shot AF) mode"
// TODO call precapture if we are using flash
// This is the CaptureRequest.Builder that we use to take a picture.
final CaptureRequest.Builder captureBuilder =
createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
Log.v(TAG, "S>> Target " + mImageReader.getWidth() + "," + mImageReader.getHeight());
captureBuilder.addTarget(mImageReader.getSurface());
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, currentJpegRotation);
CameraCaptureSession.CaptureCallback captureCallback
= new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
TotalCaptureResult result) {
Log.v(TAG, "S>> onCaptureCompleted");
setParameters();
}
};
setRequest(captureBuilder.build(), captureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* Set a repeating request.
*/
private void setRequest(final CaptureRequest request,
final CameraCaptureSession.CaptureCallback listener,
final Handler handler) {
mCameraHandler.post(new Runnable() {
public void run() {
try {
mCameraSession.stopRepeating();
mCameraSession.capture(request, listener, handler);
} catch (CameraAccessException e) {
String errorMessage = mErrorDisplayer.getErrorString(e);
mErrorDisplayer.showErrorDialog(errorMessage);
}
}
});
}
public void setUpCamera(Surface processingNormalSurface) {
mProcessingNormalSurface = processingNormalSurface;
// Ready to send requests in, so set them up
try {
CaptureRequest.Builder previewBuilder =
createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
previewBuilder.addTarget(mProcessingNormalSurface);
previewBuilder.setTag(mAutoExposureTag);
mPreviewRequest = previewBuilder.build();
mHdrBuilder = createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_OFF);
mHdrBuilder.addTarget(mProcessingNormalSurface);
setParameters();
} catch (CameraAccessException e) {
String errorMessage = e.getMessage();
// MessageDialogFragment.newInstance(errorMessage).show(getFragmentManager(), FRAGMENT_DIALOG);
}
}
/**
* Start running an HDR burst on a configured camera session
*/
public void setParameters() {
if (mHdrBuilder == null) {
Log.v(TAG, " Camera not set up");
return;
}
if (mAutoExposure) {
mHdrBuilder.set(CaptureRequest.SENSOR_FRAME_DURATION, ONE_SECOND / 30);
mHdrBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, getExposure());
mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
} else {
mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
mHdrBuilder.set(CaptureRequest.SENSOR_FRAME_DURATION, ONE_SECOND / 30);
mHdrBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, getExposure());
mHdrBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, getIso());
}
if (mAutoFocus) {
mHdrBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO);
mHdrBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
} else {
mHdrBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
mHdrBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, getFocusDistance());
mHdrBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START);
}
setRepeatingRequest(mHdrBuilder.build(), mCaptureCallback, mReadyHandler);
}
private CameraCaptureSession.CaptureCallback mCaptureCallback
= new CameraCaptureSession.CaptureCallback() {
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
TotalCaptureResult result) {
}
};
/**
* Simple listener for main code to know the camera is ready for requests, or failed to
* start.
*/
public interface CameraReadyListener {
public void onCameraReady();
}
/**
* Simple listener for displaying error messages
*/
public interface ErrorDisplayer {
public void showErrorDialog(String errorMessage);
public String getErrorString(CameraAccessException e);
}
public float getFocusDistance() {
return mFocusDist;
}
public void setFocusDistance(float focusDistance) {
mFocusDist = focusDistance;
}
public void setIso(int iso) {
mIso = iso;
}
public boolean isAutoExposure() {
return mAutoExposure;
}
public void setAutoExposure(boolean autoExposure) {
mAutoExposure = autoExposure;
}
public boolean isAutoFocus() {
return mAutoFocus;
}
public void setAutoFocus(boolean autoFocus) {
mAutoFocus = autoFocus;
}
public int getIso() {
return mIso;
}
public long getExposure() {
return mExposure;
}
public void setExposure(long exposure) {
mExposure = exposure;
}
public int getIsoMax() {
return mISOmax;
}
public int getIsoMin() {
return mISOmin;
}
public long getExpMax() {
return mExpMax;
}
public long getExpMin() {
return mExpMin;
}
public float getFocusMin() {
return mFocusMin;
}
}