|
|
@ -14,7 +14,6 @@ import androidx.annotation.Nullable; |
|
|
|
import androidx.annotation.VisibleForTesting; |
|
|
|
import androidx.annotation.VisibleForTesting; |
|
|
|
import androidx.annotation.WorkerThread; |
|
|
|
import androidx.annotation.WorkerThread; |
|
|
|
|
|
|
|
|
|
|
|
import android.util.Log; |
|
|
|
|
|
|
|
import android.view.SurfaceHolder; |
|
|
|
import android.view.SurfaceHolder; |
|
|
|
|
|
|
|
|
|
|
|
import com.google.android.gms.tasks.Task; |
|
|
|
import com.google.android.gms.tasks.Task; |
|
|
@ -23,6 +22,8 @@ import com.otaliastudios.cameraview.CameraException; |
|
|
|
import com.otaliastudios.cameraview.CameraLogger; |
|
|
|
import com.otaliastudios.cameraview.CameraLogger; |
|
|
|
import com.otaliastudios.cameraview.CameraOptions; |
|
|
|
import com.otaliastudios.cameraview.CameraOptions; |
|
|
|
import com.otaliastudios.cameraview.controls.Engine; |
|
|
|
import com.otaliastudios.cameraview.controls.Engine; |
|
|
|
|
|
|
|
import com.otaliastudios.cameraview.engine.offset.Axis; |
|
|
|
|
|
|
|
import com.otaliastudios.cameraview.engine.offset.Reference; |
|
|
|
import com.otaliastudios.cameraview.frame.Frame; |
|
|
|
import com.otaliastudios.cameraview.frame.Frame; |
|
|
|
import com.otaliastudios.cameraview.PictureResult; |
|
|
|
import com.otaliastudios.cameraview.PictureResult; |
|
|
|
import com.otaliastudios.cameraview.VideoResult; |
|
|
|
import com.otaliastudios.cameraview.VideoResult; |
|
|
@ -34,7 +35,6 @@ import com.otaliastudios.cameraview.controls.Hdr; |
|
|
|
import com.otaliastudios.cameraview.controls.Mode; |
|
|
|
import com.otaliastudios.cameraview.controls.Mode; |
|
|
|
import com.otaliastudios.cameraview.controls.WhiteBalance; |
|
|
|
import com.otaliastudios.cameraview.controls.WhiteBalance; |
|
|
|
import com.otaliastudios.cameraview.internal.utils.CropHelper; |
|
|
|
import com.otaliastudios.cameraview.internal.utils.CropHelper; |
|
|
|
import com.otaliastudios.cameraview.internal.utils.Op; |
|
|
|
|
|
|
|
import com.otaliastudios.cameraview.picture.Full1PictureRecorder; |
|
|
|
import com.otaliastudios.cameraview.picture.Full1PictureRecorder; |
|
|
|
import com.otaliastudios.cameraview.picture.Snapshot1PictureRecorder; |
|
|
|
import com.otaliastudios.cameraview.picture.Snapshot1PictureRecorder; |
|
|
|
import com.otaliastudios.cameraview.picture.SnapshotGlPictureRecorder; |
|
|
|
import com.otaliastudios.cameraview.picture.SnapshotGlPictureRecorder; |
|
|
@ -57,52 +57,84 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
|
|
|
|
|
|
|
|
private static final String TAG = Camera1Engine.class.getSimpleName(); |
|
|
|
private static final String TAG = Camera1Engine.class.getSimpleName(); |
|
|
|
private static final CameraLogger LOG = CameraLogger.create(TAG); |
|
|
|
private static final CameraLogger LOG = CameraLogger.create(TAG); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final int PREVIEW_FORMAT = ImageFormat.NV21; |
|
|
|
@VisibleForTesting static final int AUTOFOCUS_END_DELAY_MILLIS = 2500; |
|
|
|
@VisibleForTesting static final int AUTOFOCUS_END_DELAY_MILLIS = 2500; |
|
|
|
|
|
|
|
|
|
|
|
private Camera mCamera; |
|
|
|
private Camera mCamera; |
|
|
|
@VisibleForTesting int mCameraId; |
|
|
|
@VisibleForTesting int mCameraId; |
|
|
|
private int mPreviewStreamFormat; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Runnable mFocusEndRunnable; |
|
|
|
private Runnable mFocusEndRunnable; |
|
|
|
private final Runnable mFocusResetRunnable = new Runnable() { |
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public void run() { |
|
|
|
|
|
|
|
if (!isCameraAvailable()) return; |
|
|
|
|
|
|
|
mCamera.cancelAutoFocus(); |
|
|
|
|
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
|
|
|
|
int maxAF = params.getMaxNumFocusAreas(); |
|
|
|
|
|
|
|
int maxAE = params.getMaxNumMeteringAreas(); |
|
|
|
|
|
|
|
if (maxAF > 0) params.setFocusAreas(null); |
|
|
|
|
|
|
|
if (maxAE > 0) params.setMeteringAreas(null); |
|
|
|
|
|
|
|
applyDefaultFocus(params); // Revert to internal focus.
|
|
|
|
|
|
|
|
mCamera.setParameters(params); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Camera1Engine(@NonNull Callback callback) { |
|
|
|
public Camera1Engine(@NonNull Callback callback) { |
|
|
|
super(callback); |
|
|
|
super(callback); |
|
|
|
mMapper = Mapper.get(Engine.CAMERA1); |
|
|
|
mMapper = Mapper.get(Engine.CAMERA1); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private boolean isCameraAvailable() { |
|
|
|
//region Utilities
|
|
|
|
return getEngineState() == STATE_STARTED; |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public void onError(int error, Camera camera) { |
|
|
|
|
|
|
|
if (error == Camera.CAMERA_ERROR_SERVER_DIED) { |
|
|
|
|
|
|
|
// Looks like this is recoverable.
|
|
|
|
|
|
|
|
LOG.w("Recoverable error inside the onError callback.", "CAMERA_ERROR_SERVER_DIED"); |
|
|
|
|
|
|
|
restart(); |
|
|
|
|
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void schedule(@Nullable final Op<Void> op, final boolean ensureAvailable, final Runnable action) { |
|
|
|
String message = LOG.e("Internal Camera1 error.", error); |
|
|
|
mHandler.run(new Runnable() { |
|
|
|
Exception runtime = new RuntimeException(message); |
|
|
|
|
|
|
|
int reason; |
|
|
|
|
|
|
|
switch (error) { |
|
|
|
|
|
|
|
case Camera.CAMERA_ERROR_EVICTED: reason = CameraException.REASON_DISCONNECTED; break; |
|
|
|
|
|
|
|
case Camera.CAMERA_ERROR_UNKNOWN: reason = CameraException.REASON_UNKNOWN; break; |
|
|
|
|
|
|
|
default: reason = CameraException.REASON_UNKNOWN; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
throw new CameraException(runtime, reason); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//region Protected APIs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@NonNull |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void run() { |
|
|
|
protected List<Size> getPreviewStreamAvailableSizes() { |
|
|
|
if (ensureAvailable && !isCameraAvailable()) { |
|
|
|
List<Camera.Size> sizes = mCamera.getParameters().getSupportedPreviewSizes(); |
|
|
|
if (op != null) op.end(null); |
|
|
|
List<Size> result = new ArrayList<>(sizes.size()); |
|
|
|
} else { |
|
|
|
for (Camera.Size size : sizes) { |
|
|
|
action.run(); |
|
|
|
Size add = new Size(size.width, size.height); |
|
|
|
if (op != null) op.end(null); |
|
|
|
if (!result.contains(add)) result.add(add); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
LOG.i("getPreviewStreamAvailableSizes:", result); |
|
|
|
|
|
|
|
return result; |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
@WorkerThread |
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
protected void onPreviewStreamSizeChanged() { |
|
|
|
|
|
|
|
restartPreview(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
protected boolean collectCameraInfo(@NonNull Facing facing) { |
|
|
|
|
|
|
|
int internalFacing = mMapper.map(facing); |
|
|
|
|
|
|
|
LOG.i("collectCameraInfo", "Facing:", facing, "Internal:", internalFacing, "Cameras:", Camera.getNumberOfCameras()); |
|
|
|
|
|
|
|
Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); |
|
|
|
|
|
|
|
for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) { |
|
|
|
|
|
|
|
Camera.getCameraInfo(i, cameraInfo); |
|
|
|
|
|
|
|
if (cameraInfo.facing == internalFacing) { |
|
|
|
|
|
|
|
setSensorOffset(facing, cameraInfo.orientation); |
|
|
|
|
|
|
|
mCameraId = i; |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//region Start
|
|
|
|
|
|
|
|
|
|
|
|
@NonNull |
|
|
|
@NonNull |
|
|
|
@WorkerThread |
|
|
|
@WorkerThread |
|
|
|
@Override |
|
|
|
@Override |
|
|
@ -118,16 +150,10 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
// Set parameters that might have been set before the camera was opened.
|
|
|
|
// Set parameters that might have been set before the camera was opened.
|
|
|
|
LOG.i("onStartEngine:", "Applying default parameters."); |
|
|
|
LOG.i("onStartEngine:", "Applying default parameters."); |
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
mCameraOptions = new CameraOptions(params, flip(REF_SENSOR, REF_VIEW)); |
|
|
|
mCameraOptions = new CameraOptions(params, getAngles().flip(Reference.SENSOR, Reference.VIEW)); |
|
|
|
applyDefaultFocus(params); |
|
|
|
applyAllParameters(params); |
|
|
|
applyFlash(params, Flash.OFF); |
|
|
|
|
|
|
|
applyLocation(params, null); |
|
|
|
|
|
|
|
applyWhiteBalance(params, WhiteBalance.AUTO); |
|
|
|
|
|
|
|
applyHdr(params, Hdr.OFF); |
|
|
|
|
|
|
|
applyPlaySounds(mPlaySounds); |
|
|
|
|
|
|
|
params.setRecordingHint(mMode == Mode.VIDEO); |
|
|
|
|
|
|
|
mCamera.setParameters(params); |
|
|
|
mCamera.setParameters(params); |
|
|
|
mCamera.setDisplayOrientation(offset(REF_SENSOR, REF_VIEW)); // <- not allowed during preview
|
|
|
|
mCamera.setDisplayOrientation(getAngles().offset(Reference.SENSOR, Reference.VIEW, Axis.ABSOLUTE)); // <- not allowed during preview
|
|
|
|
LOG.i("onStartEngine:", "Ended"); |
|
|
|
LOG.i("onStartEngine:", "Ended"); |
|
|
|
return Tasks.forResult(null); |
|
|
|
return Tasks.forResult(null); |
|
|
|
} |
|
|
|
} |
|
|
@ -161,17 +187,17 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
LOG.i("onStartPreview", "Dispatching onCameraPreviewStreamSizeChanged."); |
|
|
|
LOG.i("onStartPreview", "Dispatching onCameraPreviewStreamSizeChanged."); |
|
|
|
mCallback.onCameraPreviewStreamSizeChanged(); |
|
|
|
mCallback.onCameraPreviewStreamSizeChanged(); |
|
|
|
|
|
|
|
|
|
|
|
Size previewSize = getPreviewStreamSize(REF_VIEW); |
|
|
|
Size previewSize = getPreviewStreamSize(Reference.VIEW); |
|
|
|
if (previewSize == null) { |
|
|
|
if (previewSize == null) { |
|
|
|
throw new IllegalStateException("previewStreamSize should not be null at this point."); |
|
|
|
throw new IllegalStateException("previewStreamSize should not be null at this point."); |
|
|
|
} |
|
|
|
} |
|
|
|
mPreview.setStreamSize(previewSize.getWidth(), previewSize.getHeight()); |
|
|
|
mPreview.setStreamSize(previewSize.getWidth(), previewSize.getHeight()); |
|
|
|
|
|
|
|
|
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
mPreviewStreamFormat = params.getPreviewFormat(); |
|
|
|
params.setPreviewFormat(ImageFormat.NV21); // should be the default, but let's make sure, since YuvImage will only support this & a few others
|
|
|
|
params.setPreviewSize(mPreviewStreamSize.getWidth(), mPreviewStreamSize.getHeight()); // <- not allowed during preview
|
|
|
|
params.setPreviewSize(mPreviewStreamSize.getWidth(), mPreviewStreamSize.getHeight()); // not allowed during preview
|
|
|
|
if (mMode == Mode.PICTURE) { |
|
|
|
if (getMode() == Mode.PICTURE) { |
|
|
|
params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // <- allowed
|
|
|
|
params.setPictureSize(mCaptureSize.getWidth(), mCaptureSize.getHeight()); // allowed during preview
|
|
|
|
} else { |
|
|
|
} else { |
|
|
|
// mCaptureSize in this case is a video size. The available video sizes are not necessarily
|
|
|
|
// mCaptureSize in this case is a video size. The available video sizes are not necessarily
|
|
|
|
// a subset of the picture sizes, so we can't use the mCaptureSize value: it might crash.
|
|
|
|
// a subset of the picture sizes, so we can't use the mCaptureSize value: it might crash.
|
|
|
@ -184,7 +210,7 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
|
|
|
|
|
|
|
|
mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
|
|
|
|
mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
|
|
|
|
mCamera.setPreviewCallbackWithBuffer(this); // Add ourselves
|
|
|
|
mCamera.setPreviewCallbackWithBuffer(this); // Add ourselves
|
|
|
|
getFrameManager().setUp(ImageFormat.getBitsPerPixel(mPreviewStreamFormat), mPreviewStreamSize); |
|
|
|
getFrameManager().setUp(ImageFormat.getBitsPerPixel(PREVIEW_FORMAT), mPreviewStreamSize); |
|
|
|
|
|
|
|
|
|
|
|
LOG.i("onStartPreview", "Starting preview with startPreview()."); |
|
|
|
LOG.i("onStartPreview", "Starting preview with startPreview()."); |
|
|
|
try { |
|
|
|
try { |
|
|
@ -197,6 +223,10 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
return Tasks.forResult(null); |
|
|
|
return Tasks.forResult(null); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//region Stop
|
|
|
|
|
|
|
|
|
|
|
|
@NonNull |
|
|
|
@NonNull |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
protected Task<Void> onStopPreview() { |
|
|
|
protected Task<Void> onStopPreview() { |
|
|
@ -205,7 +235,6 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
mVideoRecorder = null; |
|
|
|
mVideoRecorder = null; |
|
|
|
} |
|
|
|
} |
|
|
|
mPictureRecorder = null; |
|
|
|
mPictureRecorder = null; |
|
|
|
mPreviewStreamFormat = 0; |
|
|
|
|
|
|
|
getFrameManager().release(); |
|
|
|
getFrameManager().release(); |
|
|
|
mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
|
|
|
|
mCamera.setPreviewCallbackWithBuffer(null); // Release anything left
|
|
|
|
try { |
|
|
|
try { |
|
|
@ -216,7 +245,6 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
return Tasks.forResult(null); |
|
|
|
return Tasks.forResult(null); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@NonNull |
|
|
|
@NonNull |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
protected Task<Void> onStopBind() { |
|
|
|
protected Task<Void> onStopBind() { |
|
|
@ -263,77 +291,172 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
return Tasks.forResult(null); |
|
|
|
return Tasks.forResult(null); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//region Pictures
|
|
|
|
|
|
|
|
|
|
|
|
@WorkerThread |
|
|
|
@WorkerThread |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
protected void onPreviewStreamSizeChanged() { |
|
|
|
protected void onTakePicture(@NonNull PictureResult.Stub stub) { |
|
|
|
restartPreview(); |
|
|
|
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); |
|
|
|
|
|
|
|
stub.size = getPictureSize(Reference.OUTPUT); |
|
|
|
|
|
|
|
mPictureRecorder = new Full1PictureRecorder(stub, Camera1Engine.this, mCamera); |
|
|
|
|
|
|
|
mPictureRecorder.take(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@WorkerThread |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
protected boolean collectCameraInfo(@NonNull Facing facing) { |
|
|
|
protected void onTakePictureSnapshot(@NonNull PictureResult.Stub stub, @NonNull AspectRatio viewAspectRatio) { |
|
|
|
int internalFacing = mMapper.map(facing); |
|
|
|
stub.size = getUncroppedSnapshotSize(Reference.OUTPUT); // Not the real size: it will be cropped to match the view ratio
|
|
|
|
LOG.i("collectCameraInfo", "Facing:", facing, "Internal:", internalFacing, "Cameras:", Camera.getNumberOfCameras()); |
|
|
|
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); // Actually it will be rotated and set to 0.
|
|
|
|
Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); |
|
|
|
AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio; |
|
|
|
for (int i = 0, count = Camera.getNumberOfCameras(); i < count; i++) { |
|
|
|
|
|
|
|
Camera.getCameraInfo(i, cameraInfo); |
|
|
|
if (mPreview instanceof GlCameraPreview && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
|
|
|
if (cameraInfo.facing == internalFacing) { |
|
|
|
mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio); |
|
|
|
mSensorOffset = cameraInfo.orientation; |
|
|
|
} else { |
|
|
|
mCameraId = i; |
|
|
|
mPictureRecorder = new Snapshot1PictureRecorder(stub, this, mCamera, outputRatio); |
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
mPictureRecorder.take(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//region Videos
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void setHasFrameProcessors(boolean hasFrameProcessors) { |
|
|
|
protected void onTakeVideo(@NonNull VideoResult.Stub stub) { |
|
|
|
mHasFrameProcessors = hasFrameProcessors; |
|
|
|
stub.rotation = getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR); |
|
|
|
|
|
|
|
stub.size = getAngles().flip(Reference.SENSOR, Reference.OUTPUT) ? mCaptureSize.flip() : mCaptureSize; |
|
|
|
|
|
|
|
// Unlock the camera and start recording.
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
mCamera.unlock(); |
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
|
|
// If this failed, we are unlikely able to record the video.
|
|
|
|
|
|
|
|
// Dispatch an error.
|
|
|
|
|
|
|
|
onVideoResult(null, e); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
mVideoRecorder = new Full1VideoRecorder(Camera1Engine.this, mCamera, mCameraId); |
|
|
|
|
|
|
|
mVideoRecorder.start(stub); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@NonNull |
|
|
|
@SuppressLint("NewApi") |
|
|
|
|
|
|
|
@WorkerThread |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
protected FrameManager instantiateFrameManager() { |
|
|
|
protected void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub, @NonNull AspectRatio viewAspectRatio) { |
|
|
|
return new FrameManager(2, this); |
|
|
|
if (!(mPreview instanceof GlCameraPreview)) { |
|
|
|
|
|
|
|
throw new IllegalStateException("Video snapshots are only supported with GlCameraPreview."); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { |
|
|
|
|
|
|
|
throw new IllegalStateException("Video snapshots are only supported starting from API 18."); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
GlCameraPreview glPreview = (GlCameraPreview) mPreview; |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
// Output size is easy:
|
|
|
|
public void onBufferAvailable(@NonNull byte[] buffer) { |
|
|
|
Size outputSize = getUncroppedSnapshotSize(Reference.OUTPUT); |
|
|
|
// TODO: sync with handler?
|
|
|
|
if (outputSize == null) { |
|
|
|
if (isCameraAvailable()) { |
|
|
|
throw new IllegalStateException("outputSize should not be null."); |
|
|
|
mCamera.addCallbackBuffer(buffer); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
AspectRatio outputRatio = getAngles().flip(Reference.OUTPUT, Reference.VIEW) ? viewAspectRatio.flip() : viewAspectRatio; |
|
|
|
|
|
|
|
Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio); |
|
|
|
|
|
|
|
outputSize = new Size(outputCrop.width(), outputCrop.height()); |
|
|
|
|
|
|
|
stub.size = outputSize; |
|
|
|
|
|
|
|
stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Start.
|
|
|
|
|
|
|
|
mVideoRecorder = new SnapshotVideoRecorder(Camera1Engine.this, glPreview); |
|
|
|
|
|
|
|
mVideoRecorder.start(stub); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void onError(int error, Camera camera) { |
|
|
|
public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) { |
|
|
|
if (error == Camera.CAMERA_ERROR_SERVER_DIED) { |
|
|
|
super.onVideoResult(result, exception); |
|
|
|
// Looks like this is recoverable.
|
|
|
|
if (result == null) { |
|
|
|
LOG.w("Recoverable error inside the onError callback.", "CAMERA_ERROR_SERVER_DIED"); |
|
|
|
// Something went wrong, lock the camera again.
|
|
|
|
restart(); |
|
|
|
mCamera.lock(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//region Parameters
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void applyAllParameters(@NonNull Camera.Parameters params) { |
|
|
|
|
|
|
|
params.setRecordingHint(getMode() == Mode.VIDEO); |
|
|
|
|
|
|
|
applyDefaultFocus(params); |
|
|
|
|
|
|
|
applyFlash(params, Flash.OFF); |
|
|
|
|
|
|
|
applyLocation(params, null); |
|
|
|
|
|
|
|
applyWhiteBalance(params, WhiteBalance.AUTO); |
|
|
|
|
|
|
|
applyHdr(params, Hdr.OFF); |
|
|
|
|
|
|
|
applyZoom(params, 0F); |
|
|
|
|
|
|
|
applyExposureCorrection(params, 0F); |
|
|
|
|
|
|
|
applyPlaySounds(mPlaySounds); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void applyDefaultFocus(@NonNull Camera.Parameters params) { |
|
|
|
|
|
|
|
List<String> modes = params.getSupportedFocusModes(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (getMode() == Mode.VIDEO && |
|
|
|
|
|
|
|
modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { |
|
|
|
|
|
|
|
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
String message = LOG.e("Internal Camera1 error.", error); |
|
|
|
if (modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { |
|
|
|
Exception runtime = new RuntimeException(message); |
|
|
|
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); |
|
|
|
int reason; |
|
|
|
return; |
|
|
|
switch (error) { |
|
|
|
|
|
|
|
case Camera.CAMERA_ERROR_EVICTED: reason = CameraException.REASON_DISCONNECTED; break; |
|
|
|
|
|
|
|
case Camera.CAMERA_ERROR_UNKNOWN: reason = CameraException.REASON_UNKNOWN; break; |
|
|
|
|
|
|
|
default: reason = CameraException.REASON_UNKNOWN; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
throw new CameraException(runtime, reason); |
|
|
|
|
|
|
|
|
|
|
|
if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) { |
|
|
|
|
|
|
|
params.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) { |
|
|
|
|
|
|
|
params.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED); |
|
|
|
|
|
|
|
//noinspection UnnecessaryReturnStatement
|
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public void setFlash(@NonNull Flash flash) { |
|
|
|
|
|
|
|
final Flash old = mFlash; |
|
|
|
|
|
|
|
mFlash = flash; |
|
|
|
|
|
|
|
mHandler.run(new Runnable() { |
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public void run() { |
|
|
|
|
|
|
|
if (getEngineState() == STATE_STARTED) { |
|
|
|
|
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
|
|
|
|
if (applyFlash(params, old)) mCamera.setParameters(params); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
mFlashOp.end(null); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private boolean applyFlash(@NonNull Camera.Parameters params, @NonNull Flash oldFlash) { |
|
|
|
|
|
|
|
if (mCameraOptions.supports(mFlash)) { |
|
|
|
|
|
|
|
params.setFlashMode((String) mMapper.map(mFlash)); |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
mFlash = oldFlash; |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void setLocation(@Nullable Location location) { |
|
|
|
public void setLocation(@Nullable Location location) { |
|
|
|
final Location oldLocation = mLocation; |
|
|
|
final Location oldLocation = mLocation; |
|
|
|
mLocation = location; |
|
|
|
mLocation = location; |
|
|
|
schedule(mLocationOp, true, new Runnable() { |
|
|
|
mHandler.run(new Runnable() { |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void run() { |
|
|
|
public void run() { |
|
|
|
|
|
|
|
if (getEngineState() == STATE_STARTED) { |
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
if (applyLocation(params, oldLocation)) mCamera.setParameters(params); |
|
|
|
if (applyLocation(params, oldLocation)) mCamera.setParameters(params); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
mLocationOp.end(null); |
|
|
|
|
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -353,12 +476,15 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
public void setWhiteBalance(@NonNull WhiteBalance whiteBalance) { |
|
|
|
public void setWhiteBalance(@NonNull WhiteBalance whiteBalance) { |
|
|
|
final WhiteBalance old = mWhiteBalance; |
|
|
|
final WhiteBalance old = mWhiteBalance; |
|
|
|
mWhiteBalance = whiteBalance; |
|
|
|
mWhiteBalance = whiteBalance; |
|
|
|
schedule(mWhiteBalanceOp, true, new Runnable() { |
|
|
|
mHandler.run(new Runnable() { |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void run() { |
|
|
|
public void run() { |
|
|
|
|
|
|
|
if (getEngineState() == STATE_STARTED) { |
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
if (applyWhiteBalance(params, old)) mCamera.setParameters(params); |
|
|
|
if (applyWhiteBalance(params, old)) mCamera.setParameters(params); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
mWhiteBalanceOp.end(null); |
|
|
|
|
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -375,12 +501,15 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
public void setHdr(@NonNull Hdr hdr) { |
|
|
|
public void setHdr(@NonNull Hdr hdr) { |
|
|
|
final Hdr old = mHdr; |
|
|
|
final Hdr old = mHdr; |
|
|
|
mHdr = hdr; |
|
|
|
mHdr = hdr; |
|
|
|
schedule(mHdrOp, true, new Runnable() { |
|
|
|
mHandler.run(new Runnable() { |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void run() { |
|
|
|
public void run() { |
|
|
|
|
|
|
|
if (getEngineState() == STATE_STARTED) { |
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
if (applyHdr(params, old)) mCamera.setParameters(params); |
|
|
|
if (applyHdr(params, old)) mCamera.setParameters(params); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
mHdrOp.end(null); |
|
|
|
|
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -393,268 +522,149 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("UnusedReturnValue") |
|
|
|
|
|
|
|
@TargetApi(17) |
|
|
|
|
|
|
|
private boolean applyPlaySounds(boolean oldPlaySound) { |
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { |
|
|
|
|
|
|
|
Camera.CameraInfo info = new Camera.CameraInfo(); |
|
|
|
|
|
|
|
Camera.getCameraInfo(mCameraId, info); |
|
|
|
|
|
|
|
if (info.canDisableShutterSound) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
// this method is documented to throw on some occasions. #377
|
|
|
|
|
|
|
|
return mCamera.enableShutterSound(mPlaySounds); |
|
|
|
|
|
|
|
} catch (RuntimeException exception) { |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (mPlaySounds) { |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
mPlaySounds = oldPlaySound; |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void setFlash(@NonNull Flash flash) { |
|
|
|
public void setZoom(final float zoom, @Nullable final PointF[] points, final boolean notify) { |
|
|
|
final Flash old = mFlash; |
|
|
|
final float old = mZoomValue; |
|
|
|
mFlash = flash; |
|
|
|
mZoomValue = zoom; |
|
|
|
schedule(mFlashOp, true, new Runnable() { |
|
|
|
mHandler.run(new Runnable() { |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void run() { |
|
|
|
public void run() { |
|
|
|
|
|
|
|
if (getEngineState() == STATE_STARTED) { |
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
if (applyFlash(params, old)) mCamera.setParameters(params); |
|
|
|
if (applyZoom(params, old)) { |
|
|
|
|
|
|
|
mCamera.setParameters(params); |
|
|
|
|
|
|
|
if (notify) { |
|
|
|
|
|
|
|
mCallback.dispatchOnZoomChanged(mZoomValue, points); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
mZoomOp.end(null); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private boolean applyZoom(@NonNull Camera.Parameters params, float oldZoom) { |
|
|
|
private boolean applyFlash(@NonNull Camera.Parameters params, @NonNull Flash oldFlash) { |
|
|
|
if (mCameraOptions.isZoomSupported()) { |
|
|
|
if (mCameraOptions.supports(mFlash)) { |
|
|
|
float max = params.getMaxZoom(); |
|
|
|
params.setFlashMode((String) mMapper.map(mFlash)); |
|
|
|
params.setZoom((int) (mZoomValue * max)); |
|
|
|
|
|
|
|
mCamera.setParameters(params); |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
mFlash = oldFlash; |
|
|
|
mZoomValue = oldZoom; |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
// Choose the best default focus, based on session type.
|
|
|
|
public void setExposureCorrection(final float EVvalue, @NonNull final float[] bounds, |
|
|
|
private void applyDefaultFocus(@NonNull Camera.Parameters params) { |
|
|
|
@Nullable final PointF[] points, final boolean notify) { |
|
|
|
List<String> modes = params.getSupportedFocusModes(); |
|
|
|
final float old = mExposureCorrectionValue; |
|
|
|
|
|
|
|
mExposureCorrectionValue = EVvalue; |
|
|
|
if (mMode == Mode.VIDEO && |
|
|
|
mHandler.run(new Runnable() { |
|
|
|
modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { |
|
|
|
@Override |
|
|
|
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); |
|
|
|
public void run() { |
|
|
|
return; |
|
|
|
if (getEngineState() == STATE_STARTED) { |
|
|
|
} |
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
|
|
|
|
if (applyExposureCorrection(params, old)) { |
|
|
|
if (modes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { |
|
|
|
mCamera.setParameters(params); |
|
|
|
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); |
|
|
|
if (notify) { |
|
|
|
return; |
|
|
|
mCallback.dispatchOnExposureCorrectionChanged(mExposureCorrectionValue, bounds, points); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (modes.contains(Camera.Parameters.FOCUS_MODE_INFINITY)) { |
|
|
|
|
|
|
|
params.setFocusMode(Camera.Parameters.FOCUS_MODE_INFINITY); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (modes.contains(Camera.Parameters.FOCUS_MODE_FIXED)) { |
|
|
|
|
|
|
|
params.setFocusMode(Camera.Parameters.FOCUS_MODE_FIXED); |
|
|
|
|
|
|
|
//noinspection UnnecessaryReturnStatement
|
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
mExposureCorrectionOp.end(null); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
// -----------------
|
|
|
|
|
|
|
|
// Picture recording stuff.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@WorkerThread |
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
protected void onTakePicture(@NonNull PictureResult.Stub stub) { |
|
|
|
|
|
|
|
stub.rotation = offset(REF_SENSOR, REF_OUTPUT); |
|
|
|
|
|
|
|
stub.size = getPictureSize(REF_OUTPUT); |
|
|
|
|
|
|
|
mPictureRecorder = new Full1PictureRecorder(stub, Camera1Engine.this, mCamera); |
|
|
|
|
|
|
|
mPictureRecorder.take(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@WorkerThread |
|
|
|
private boolean applyExposureCorrection(@NonNull Camera.Parameters params, float oldExposureCorrection) { |
|
|
|
@Override |
|
|
|
if (mCameraOptions.isExposureCorrectionSupported()) { |
|
|
|
protected void onTakePictureSnapshot(@NonNull PictureResult.Stub stub, @NonNull AspectRatio viewAspectRatio) { |
|
|
|
// Just make sure we're inside boundaries.
|
|
|
|
stub.size = getUncroppedSnapshotSize(REF_OUTPUT); // Not the real size: it will be cropped to match the view ratio
|
|
|
|
float max = mCameraOptions.getExposureCorrectionMaxValue(); |
|
|
|
stub.rotation = offset(REF_SENSOR, REF_OUTPUT); // Actually it will be rotated and set to 0.
|
|
|
|
float min = mCameraOptions.getExposureCorrectionMinValue(); |
|
|
|
AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio; |
|
|
|
float val = mExposureCorrectionValue; |
|
|
|
|
|
|
|
val = val < min ? min : val > max ? max : val; // cap
|
|
|
|
if (mPreview instanceof GlCameraPreview && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { |
|
|
|
mExposureCorrectionValue = val; |
|
|
|
mPictureRecorder = new SnapshotGlPictureRecorder(stub, this, (GlCameraPreview) mPreview, outputRatio); |
|
|
|
// Apply.
|
|
|
|
} else { |
|
|
|
int indexValue = (int) (mExposureCorrectionValue / params.getExposureCompensationStep()); |
|
|
|
mPictureRecorder = new Snapshot1PictureRecorder(stub, this, mCamera, outputRatio); |
|
|
|
params.setExposureCompensation(indexValue); |
|
|
|
} |
|
|
|
return true; |
|
|
|
mPictureRecorder.take(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
mExposureCorrectionValue = oldExposureCorrection; |
|
|
|
@Override |
|
|
|
return false; |
|
|
|
public void onPreviewFrame(@NonNull byte[] data, Camera camera) { |
|
|
|
|
|
|
|
Frame frame = getFrameManager().getFrame(data, |
|
|
|
|
|
|
|
System.currentTimeMillis(), |
|
|
|
|
|
|
|
offset(REF_SENSOR, REF_OUTPUT), |
|
|
|
|
|
|
|
mPreviewStreamSize, |
|
|
|
|
|
|
|
mPreviewStreamFormat); |
|
|
|
|
|
|
|
mCallback.dispatchFrame(frame); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// -----------------
|
|
|
|
|
|
|
|
// Video recording stuff.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) { |
|
|
|
public void setPlaySounds(boolean playSounds) { |
|
|
|
super.onVideoResult(result, exception); |
|
|
|
final boolean old = mPlaySounds; |
|
|
|
if (result == null) { |
|
|
|
mPlaySounds = playSounds; |
|
|
|
// Something went wrong, lock the camera again.
|
|
|
|
mHandler.run(new Runnable() { |
|
|
|
mCamera.lock(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
protected void onTakeVideo(@NonNull VideoResult.Stub stub) { |
|
|
|
public void run() { |
|
|
|
stub.rotation = offset(REF_SENSOR, REF_OUTPUT); |
|
|
|
if (getEngineState() == STATE_STARTED) { |
|
|
|
stub.size = flip(REF_SENSOR, REF_OUTPUT) ? mCaptureSize.flip() : mCaptureSize; |
|
|
|
applyPlaySounds(old); |
|
|
|
// Unlock the camera and start recording.
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
mCamera.unlock(); |
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
|
|
// If this failed, we are unlikely able to record the video.
|
|
|
|
|
|
|
|
// Dispatch an error.
|
|
|
|
|
|
|
|
onVideoResult(null, e); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
if (!(mVideoRecorder instanceof Full1VideoRecorder)) { |
|
|
|
mPlaySoundsOp.end(null); |
|
|
|
mVideoRecorder = new Full1VideoRecorder(Camera1Engine.this, mCamera, mCameraId); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
mVideoRecorder.start(stub); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@SuppressLint("NewApi") |
|
|
|
@SuppressWarnings("UnusedReturnValue") |
|
|
|
@WorkerThread |
|
|
|
@TargetApi(17) |
|
|
|
@Override |
|
|
|
private boolean applyPlaySounds(boolean oldPlaySound) { |
|
|
|
protected void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub, @NonNull AspectRatio viewAspectRatio) { |
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { |
|
|
|
if (!(mPreview instanceof GlCameraPreview)) { |
|
|
|
Camera.CameraInfo info = new Camera.CameraInfo(); |
|
|
|
throw new IllegalStateException("Video snapshots are only supported with GlCameraPreview."); |
|
|
|
Camera.getCameraInfo(mCameraId, info); |
|
|
|
|
|
|
|
if (info.canDisableShutterSound) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
// this method is documented to throw on some occasions. #377
|
|
|
|
|
|
|
|
return mCamera.enableShutterSound(mPlaySounds); |
|
|
|
|
|
|
|
} catch (RuntimeException exception) { |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { |
|
|
|
|
|
|
|
throw new IllegalStateException("Video snapshots are only supported starting from API 18."); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
GlCameraPreview glPreview = (GlCameraPreview) mPreview; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Size and rotation turned out to be extremely tricky. In case of Snapshot1PictureRecorder
|
|
|
|
|
|
|
|
// we use the preview size in REF_OUTPUT (cropped) and offset(REF_SENSOR, REF_OUTPUT) as rotation.
|
|
|
|
|
|
|
|
// These values mean that we expect input to be in the REF_SENSOR system.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Here everything seems different. We would expect a difference because the two snapshot
|
|
|
|
|
|
|
|
// recorders have different mechanics (the picture one uses a SurfaceTexture with setBufferSize,
|
|
|
|
|
|
|
|
// the video one here uses the MediaCodec input surface which we can't control).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The strangest thing is the fact that the correct angle seems to be the same for FRONT and
|
|
|
|
|
|
|
|
// BACK sensor, which means that our sensor correction actually screws things up. For this reason
|
|
|
|
|
|
|
|
// facing value is temporarily set to BACK.
|
|
|
|
|
|
|
|
Facing realFacing = mFacing; |
|
|
|
|
|
|
|
mFacing = Facing.BACK; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// These are the angles that make it work on a Nexus5X, compared to the offset() results.
|
|
|
|
|
|
|
|
// For instance, SV means offset(REF_SENSOR, REF_VIEW). The rest should be clear.
|
|
|
|
|
|
|
|
// CONFIG | WANTED | SV | VS | VO | OV | SO | OS |
|
|
|
|
|
|
|
|
// ------------|--------|--------|--------|--------|--------|--------|--------|
|
|
|
|
|
|
|
|
// Vertical | 0 | 270 | 90 | 0 | 0 | 270 | 90 |
|
|
|
|
|
|
|
|
// Left | 270 | 270 | 90 | 270 | 90 | 180 | 180 |
|
|
|
|
|
|
|
|
// Right | 90 | 270 | 90 | 90 | 270 | 0 | 0 |
|
|
|
|
|
|
|
|
// Upside down | 180 | 270 | 90 | 180 | 180 | 90 | 270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The VO is the only correct value. Things change when using FRONT camera, in which case,
|
|
|
|
|
|
|
|
// no value is actually correct, and the needed values are the same of BACK!
|
|
|
|
|
|
|
|
// CONFIG | WANTED | SV | VS | VO | OV | SO | OS |
|
|
|
|
|
|
|
|
// ------------|--------|--------|--------|--------|--------|--------|--------|
|
|
|
|
|
|
|
|
// Vertical | 0 | 90 | 270 | 180 | 180 | 270 | 90 |
|
|
|
|
|
|
|
|
// Left | 270 | 90 | 270 | 270 | 90 | 0 | 0 |
|
|
|
|
|
|
|
|
// Right | 90 | 90 | 270 | 90 | 270 | 180 | 180 |
|
|
|
|
|
|
|
|
// Upside down | 180 | 90 | 270 | 0 | 0 | 90 | 270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Based on this we will use VO for everything. See if we get issues about distortion
|
|
|
|
|
|
|
|
// and maybe we can improve. The reason why this happen is beyond my understanding.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Size outputSize = getUncroppedSnapshotSize(REF_OUTPUT); |
|
|
|
|
|
|
|
if (outputSize == null) { |
|
|
|
|
|
|
|
throw new IllegalStateException("outputSize should not be null."); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
AspectRatio outputRatio = flip(REF_OUTPUT, REF_VIEW) ? viewAspectRatio.flip() : viewAspectRatio; |
|
|
|
if (mPlaySounds) { |
|
|
|
Rect outputCrop = CropHelper.computeCrop(outputSize, outputRatio); |
|
|
|
return true; |
|
|
|
outputSize = new Size(outputCrop.width(), outputCrop.height()); |
|
|
|
|
|
|
|
stub.size = outputSize; |
|
|
|
|
|
|
|
stub.rotation = offset(REF_VIEW, REF_OUTPUT); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Reset facing and start.
|
|
|
|
|
|
|
|
mFacing = realFacing; |
|
|
|
|
|
|
|
if (!(mVideoRecorder instanceof SnapshotVideoRecorder)) { |
|
|
|
|
|
|
|
mVideoRecorder = new SnapshotVideoRecorder(Camera1Engine.this, glPreview); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
mVideoRecorder.start(stub); |
|
|
|
mPlaySounds = oldPlaySound; |
|
|
|
|
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// -----------------
|
|
|
|
//endregion
|
|
|
|
// Zoom and simpler stuff.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//region Frame Processing
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@NonNull |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void setZoom(final float zoom, @Nullable final PointF[] points, final boolean notify) { |
|
|
|
protected FrameManager instantiateFrameManager() { |
|
|
|
schedule(mZoomOp, true, new Runnable() { |
|
|
|
return new FrameManager(2, this); |
|
|
|
@Override |
|
|
|
|
|
|
|
public void run() { |
|
|
|
|
|
|
|
if (!mCameraOptions.isZoomSupported()) return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mZoomValue = zoom; |
|
|
|
|
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
|
|
|
|
float max = params.getMaxZoom(); |
|
|
|
|
|
|
|
params.setZoom((int) (zoom * max)); |
|
|
|
|
|
|
|
mCamera.setParameters(params); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (notify) { |
|
|
|
|
|
|
|
mCallback.dispatchOnZoomChanged(zoom, points); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void setExposureCorrection(final float EVvalue, @NonNull final float[] bounds, |
|
|
|
public void onBufferAvailable(@NonNull byte[] buffer) { |
|
|
|
@Nullable final PointF[] points, final boolean notify) { |
|
|
|
if (getEngineState() == STATE_STARTED) { |
|
|
|
schedule(mExposureCorrectionOp, true, new Runnable() { |
|
|
|
mCamera.addCallbackBuffer(buffer); |
|
|
|
@Override |
|
|
|
|
|
|
|
public void run() { |
|
|
|
|
|
|
|
if (!mCameraOptions.isExposureCorrectionSupported()) return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float value = EVvalue; |
|
|
|
|
|
|
|
float max = mCameraOptions.getExposureCorrectionMaxValue(); |
|
|
|
|
|
|
|
float min = mCameraOptions.getExposureCorrectionMinValue(); |
|
|
|
|
|
|
|
value = value < min ? min : value > max ? max : value; // cap
|
|
|
|
|
|
|
|
mExposureCorrectionValue = value; |
|
|
|
|
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
|
|
|
|
int indexValue = (int) (value / params.getExposureCompensationStep()); |
|
|
|
|
|
|
|
params.setExposureCompensation(indexValue); |
|
|
|
|
|
|
|
mCamera.setParameters(params); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (notify) { |
|
|
|
|
|
|
|
mCallback.dispatchOnExposureCorrectionChanged(value, bounds, points); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public void onPreviewFrame(@NonNull byte[] data, Camera camera) { |
|
|
|
|
|
|
|
Frame frame = getFrameManager().getFrame(data, |
|
|
|
|
|
|
|
System.currentTimeMillis(), |
|
|
|
|
|
|
|
getAngles().offset(Reference.SENSOR, Reference.OUTPUT, Axis.RELATIVE_TO_SENSOR), |
|
|
|
|
|
|
|
mPreviewStreamSize, |
|
|
|
|
|
|
|
PREVIEW_FORMAT); |
|
|
|
|
|
|
|
mCallback.dispatchFrame(frame); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// -----------------
|
|
|
|
//endregion
|
|
|
|
// Tap to focus stuff.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//region Auto Focus
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void startAutoFocus(@Nullable final Gesture gesture, @NonNull final PointF point) { |
|
|
|
public void startAutoFocus(@Nullable final Gesture gesture, @NonNull final PointF point) { |
|
|
|
// Must get width and height from the UI thread.
|
|
|
|
// Must get width and height from the UI thread.
|
|
|
|
|
|
|
|
// TODO could take mPreview.surfaceSize like Camera2 does?
|
|
|
|
int viewWidth = 0, viewHeight = 0; |
|
|
|
int viewWidth = 0, viewHeight = 0; |
|
|
|
if (mPreview != null && mPreview.hasSurface()) { |
|
|
|
if (mPreview != null && mPreview.hasSurface()) { |
|
|
|
viewWidth = mPreview.getView().getWidth(); |
|
|
|
viewWidth = mPreview.getView().getWidth(); |
|
|
@ -662,14 +672,15 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
} |
|
|
|
} |
|
|
|
final int viewWidthF = viewWidth; |
|
|
|
final int viewWidthF = viewWidth; |
|
|
|
final int viewHeightF = viewHeight; |
|
|
|
final int viewHeightF = viewHeight; |
|
|
|
// Schedule.
|
|
|
|
mHandler.run(new Runnable() { |
|
|
|
schedule(null, true, new Runnable() { |
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void run() { |
|
|
|
public void run() { |
|
|
|
|
|
|
|
if (getEngineState() < STATE_STARTED) return; |
|
|
|
if (!mCameraOptions.isAutoFocusSupported()) return; |
|
|
|
if (!mCameraOptions.isAutoFocusSupported()) return; |
|
|
|
final PointF p = new PointF(point.x, point.y); // copy.
|
|
|
|
final PointF p = new PointF(point.x, point.y); // copy.
|
|
|
|
|
|
|
|
int offset = getAngles().offset(Reference.SENSOR, Reference.VIEW, Axis.ABSOLUTE); |
|
|
|
List<Camera.Area> meteringAreas2 = computeMeteringAreas(p.x, p.y, |
|
|
|
List<Camera.Area> meteringAreas2 = computeMeteringAreas(p.x, p.y, |
|
|
|
viewWidthF, viewHeightF, offset(REF_SENSOR, REF_VIEW)); |
|
|
|
viewWidthF, viewHeightF, offset); |
|
|
|
List<Camera.Area> meteringAreas1 = meteringAreas2.subList(0, 1); |
|
|
|
List<Camera.Area> meteringAreas1 = meteringAreas2.subList(0, 1); |
|
|
|
|
|
|
|
|
|
|
|
// At this point we are sure that camera supports auto focus... right? Look at CameraView.onTouchEvent().
|
|
|
|
// At this point we are sure that camera supports auto focus... right? Look at CameraView.onTouchEvent().
|
|
|
@ -688,10 +699,8 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
mFocusEndRunnable = new Runnable() { |
|
|
|
mFocusEndRunnable = new Runnable() { |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void run() { |
|
|
|
public void run() { |
|
|
|
if (isCameraAvailable()) { |
|
|
|
|
|
|
|
mCallback.dispatchOnFocusEnd(gesture, false, p); |
|
|
|
mCallback.dispatchOnFocusEnd(gesture, false, p); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
}; |
|
|
|
mHandler.post(AUTOFOCUS_END_DELAY_MILLIS, mFocusEndRunnable); |
|
|
|
mHandler.post(AUTOFOCUS_END_DELAY_MILLIS, mFocusEndRunnable); |
|
|
|
|
|
|
|
|
|
|
@ -717,6 +726,7 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
// Let the mFocusEndRunnable do its job. (could remove it and quickly dispatch
|
|
|
|
// Let the mFocusEndRunnable do its job. (could remove it and quickly dispatch
|
|
|
|
// onFocusEnd here, but let's make it simpler).
|
|
|
|
// onFocusEnd here, but let's make it simpler).
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
@ -763,38 +773,21 @@ public class Camera1Engine extends CameraEngine implements |
|
|
|
return new Rect(left, top, right, bottom); |
|
|
|
return new Rect(left, top, right, bottom); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final Runnable mFocusResetRunnable = new Runnable() { |
|
|
|
// -----------------
|
|
|
|
|
|
|
|
// Size stuff.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public final int getPreviewStreamFormat() { |
|
|
|
|
|
|
|
return mPreviewStreamFormat; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@NonNull |
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
protected List<Size> getPreviewStreamAvailableSizes() { |
|
|
|
|
|
|
|
List<Camera.Size> sizes = mCamera.getParameters().getSupportedPreviewSizes(); |
|
|
|
|
|
|
|
List<Size> result = new ArrayList<>(sizes.size()); |
|
|
|
|
|
|
|
for (Camera.Size size : sizes) { |
|
|
|
|
|
|
|
Size add = new Size(size.width, size.height); |
|
|
|
|
|
|
|
if (!result.contains(add)) result.add(add); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
LOG.i("getPreviewStreamAvailableSizes:", result); |
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public void setPlaySounds(boolean playSounds) { |
|
|
|
|
|
|
|
final boolean old = mPlaySounds; |
|
|
|
|
|
|
|
mPlaySounds = playSounds; |
|
|
|
|
|
|
|
schedule(mPlaySoundsOp, true, new Runnable() { |
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void run() { |
|
|
|
public void run() { |
|
|
|
applyPlaySounds(old); |
|
|
|
if (getEngineState() < STATE_STARTED) return; |
|
|
|
} |
|
|
|
mCamera.cancelAutoFocus(); |
|
|
|
}); |
|
|
|
Camera.Parameters params = mCamera.getParameters(); |
|
|
|
|
|
|
|
int maxAF = params.getMaxNumFocusAreas(); |
|
|
|
|
|
|
|
int maxAE = params.getMaxNumMeteringAreas(); |
|
|
|
|
|
|
|
if (maxAF > 0) params.setFocusAreas(null); |
|
|
|
|
|
|
|
if (maxAE > 0) params.setMeteringAreas(null); |
|
|
|
|
|
|
|
applyDefaultFocus(params); // Revert to internal focus.
|
|
|
|
|
|
|
|
mCamera.setParameters(params); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//endregion
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|