Camera2 full support for hq pictures

pull/493/head
Mattia Iavarone 5 years ago
parent 87796ea2f7
commit bef1044819
  1. 19
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  2. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java
  3. 11
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/WorkerHandler.java
  4. 113
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full2PictureRecorder.java
  5. 3
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java

@ -63,9 +63,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
// TODO full2picture fix rotation
// TODO full2picture add shutter
// TODO full2picture see if we don't launch activity
// TODO zoom // TODO zoom
// TODO exposure correction // TODO exposure correction
// TODO autofocus // TODO autofocus
@ -102,6 +99,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
// Picture capturing // Picture capturing
private ImageReader mPictureReader; private ImageReader mPictureReader;
private final boolean mPictureCaptureStopsPreview = false; // can make configurable at some point
public Camera2Engine(Callback callback) { public Camera2Engine(Callback callback) {
super(callback); super(callback);
@ -212,13 +210,16 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
if (firstFrame.compareAndSet(false, true) && onFirstFrame != null) { if (firstFrame.compareAndSet(false, true) && onFirstFrame != null) {
onFirstFrame.run(); onFirstFrame.run();
} }
if (mPictureRecorder instanceof Full2PictureRecorder) {
((Full2PictureRecorder) mPictureRecorder).onCaptureStarted(request);
}
} }
@Override @Override
public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
super.onCaptureProgressed(session, request, partialResult); super.onCaptureProgressed(session, request, partialResult);
if (mPictureRecorder instanceof Full2PictureRecorder) { if (mPictureRecorder instanceof Full2PictureRecorder) {
((Full2PictureRecorder) mPictureRecorder).process(partialResult); ((Full2PictureRecorder) mPictureRecorder).onCaptureProgressed(partialResult);
} }
} }
@ -226,7 +227,7 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result); super.onCaptureCompleted(session, request, result);
if (mPictureRecorder instanceof Full2PictureRecorder) { if (mPictureRecorder instanceof Full2PictureRecorder) {
((Full2PictureRecorder) mPictureRecorder).process(result); ((Full2PictureRecorder) mPictureRecorder).onCaptureCompleted(result);
} }
} }
@ -614,7 +615,8 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
mRepeatingRequestBuilder, mRepeatingRequestBuilder,
mRepeatingRequestCallback, mRepeatingRequestCallback,
builder, builder,
mPictureReader); mPictureReader,
mPictureCaptureStopsPreview);
mPictureRecorder.take(); mPictureRecorder.take();
} catch (CameraAccessException e) { } catch (CameraAccessException e) {
throw createCameraException(e); throw createCameraException(e);
@ -623,9 +625,14 @@ public class Camera2Engine extends CameraEngine implements ImageReader.OnImageAv
@Override @Override
public void onPictureResult(@Nullable PictureResult.Stub result, @Nullable Exception error) { public void onPictureResult(@Nullable PictureResult.Stub result, @Nullable Exception error) {
boolean fullPicture = mPictureRecorder instanceof Full2PictureRecorder;
super.onPictureResult(result, error); super.onPictureResult(result, error);
//noinspection StatementWithEmptyBody
if (fullPicture && mPictureCaptureStopsPreview) {
// See comments in Full2PictureRecorder.
applyRepeatingRequestBuilder(); applyRepeatingRequestBuilder();
} }
}
//endregion //endregion

@ -1225,7 +1225,8 @@ public abstract class CameraEngine implements
// displayOffset - deviceOrientation // displayOffset - deviceOrientation
// Returns the offset between two reference systems. // Returns the offset between two reference systems.
final int offset(int fromReference, int toReference) { @SuppressWarnings("WeakerAccess")
public final int offset(int fromReference, int toReference) {
if (fromReference == toReference) return 0; if (fromReference == toReference) return 0;
// We only know how to compute offsets with respect to REF_SENSOR. // We only know how to compute offsets with respect to REF_SENSOR.
// That's why we separate the two cases. // That's why we separate the two cases.

@ -52,6 +52,15 @@ public class WorkerHandler {
return handler; return handler;
} }
/**
* Returns a fallback WorkerHandler.
* @return a fallback handler
*/
@NonNull
public static WorkerHandler get() {
return get("FallbackCameraThread");
}
/** /**
* Handy utility to perform an action in a fallback thread. * Handy utility to perform an action in a fallback thread.
* Not to be used for long-running operations since they will block * Not to be used for long-running operations since they will block
@ -60,7 +69,7 @@ public class WorkerHandler {
* @param action the action * @param action the action
*/ */
public static void execute(@NonNull Runnable action) { public static void execute(@NonNull Runnable action) {
get("FallbackCameraThread").post(action); get().post(action);
} }
private HandlerThread mThread; private HandlerThread mThread;

@ -16,6 +16,7 @@ import com.otaliastudios.cameraview.CameraException;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.PictureResult;
import com.otaliastudios.cameraview.internal.utils.ExifHelper; import com.otaliastudios.cameraview.internal.utils.ExifHelper;
import com.otaliastudios.cameraview.internal.utils.WorkerHandler;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
@ -30,7 +31,7 @@ import androidx.exifinterface.media.ExifInterface;
* A {@link PictureResult} that uses standard APIs. * A {@link PictureResult} that uses standard APIs.
*/ */
@RequiresApi(Build.VERSION_CODES.LOLLIPOP) @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class Full2PictureRecorder extends PictureRecorder { public class Full2PictureRecorder extends PictureRecorder implements ImageReader.OnImageAvailableListener {
private static final String TAG = Full2PictureRecorder.class.getSimpleName(); private static final String TAG = Full2PictureRecorder.class.getSimpleName();
private static final CameraLogger LOG = CameraLogger.create(TAG); private static final CameraLogger LOG = CameraLogger.create(TAG);
@ -42,11 +43,14 @@ public class Full2PictureRecorder extends PictureRecorder {
private static final int STATE_WAITING_CAPTURE = 4; private static final int STATE_WAITING_CAPTURE = 4;
private static final int STATE_WAITING_IMAGE = 5; private static final int STATE_WAITING_IMAGE = 5;
private static final int REQUEST_TAG = CameraDevice.TEMPLATE_STILL_CAPTURE;
private CameraCaptureSession mSession; private CameraCaptureSession mSession;
private CaptureRequest.Builder mBuilder; private CaptureRequest.Builder mBuilder;
private CameraCaptureSession.CaptureCallback mCallback; private CameraCaptureSession.CaptureCallback mCallback;
private ImageReader mPictureReader; private ImageReader mPictureReader;
private CaptureRequest.Builder mPictureBuilder; private CaptureRequest.Builder mPictureBuilder;
private boolean mStopPreviewBeforeCapture;
private int mState = STATE_IDLE; private int mState = STATE_IDLE;
public Full2PictureRecorder(@NonNull PictureResult.Stub stub, public Full2PictureRecorder(@NonNull PictureResult.Stub stub,
@ -55,42 +59,16 @@ public class Full2PictureRecorder extends PictureRecorder {
@NonNull CaptureRequest.Builder builder, @NonNull CaptureRequest.Builder builder,
@NonNull CameraCaptureSession.CaptureCallback callback, @NonNull CameraCaptureSession.CaptureCallback callback,
@NonNull CaptureRequest.Builder pictureBuilder, @NonNull CaptureRequest.Builder pictureBuilder,
@NonNull ImageReader pictureReader) { @NonNull ImageReader pictureReader,
boolean stopPreviewBeforeCapture) {
super(stub, listener); super(stub, listener);
mSession = session; mSession = session;
mBuilder = builder; mBuilder = builder;
mCallback = callback; mCallback = callback;
mPictureBuilder = pictureBuilder; mPictureBuilder = pictureBuilder;
mStopPreviewBeforeCapture = stopPreviewBeforeCapture;
mPictureReader = pictureReader; mPictureReader = pictureReader;
mPictureReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { mPictureReader.setOnImageAvailableListener(this, WorkerHandler.get().getHandler());
@Override
public void onImageAvailable(ImageReader reader) {
mState = STATE_IDLE;
// Read the JPEG.
try {
Image image = reader.acquireLatestImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
mResult.data = bytes;
mResult.format = PictureResult.FORMAT_JPEG;
} catch (Exception e) {
mResult = null;
mError = e;
}
// Before leaving, unlock focus.
try {
mBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
mSession.capture(mBuilder.build(), mCallback, null);
} catch (CameraAccessException ignore) { }
// Leave.
dispatchResult();
}
}, null);
} }
@Override @Override
@ -126,11 +104,18 @@ public class Full2PictureRecorder extends PictureRecorder {
private void runCapture() { private void runCapture() {
try { try {
mPictureBuilder.setTag(CameraDevice.TEMPLATE_STILL_CAPTURE); mPictureBuilder.setTag(REQUEST_TAG);
mPictureBuilder.addTarget(mPictureReader.getSurface()); mPictureBuilder.addTarget(mPictureReader.getSurface());
mPictureBuilder.set(CaptureRequest.JPEG_ORIENTATION, mResult.rotation); mPictureBuilder.set(CaptureRequest.JPEG_ORIENTATION, mResult.rotation);
// mCaptureSession.stopRepeating(); if (mStopPreviewBeforeCapture) {
// These two are present in official samples and are probably meant to speed things up?
// But from my tests, they actually make everything slower. So this is disabled by default
// with a boolean coming from the engine. Maybe in the future we can make this configurable
// as some people might want to stop the preview while picture is being taken even if it
// increases the latency.
mSession.stopRepeating();
mSession.abortCaptures(); mSession.abortCaptures();
}
mSession.capture(mPictureBuilder.build(), mCallback, null); mSession.capture(mPictureBuilder.build(), mCallback, null);
} catch (CameraAccessException e) { } catch (CameraAccessException e) {
mResult = null; mResult = null;
@ -139,7 +124,21 @@ public class Full2PictureRecorder extends PictureRecorder {
} }
} }
public void process(@NonNull CaptureResult result) { public void onCaptureStarted(@NonNull CaptureRequest request) {
if (request.getTag() == (Integer) REQUEST_TAG) {
dispatchOnShutter(false);
}
}
public void onCaptureProgressed(@NonNull CaptureResult result) {
process(result);
}
public void onCaptureCompleted(@NonNull CaptureResult result) {
process(result);
}
private void process(@NonNull CaptureResult result) {
switch (mState) { switch (mState) {
case STATE_IDLE: break; case STATE_IDLE: break;
case STATE_WAITING_FOCUS_LOCK: { case STATE_WAITING_FOCUS_LOCK: {
@ -179,7 +178,7 @@ public class Full2PictureRecorder extends PictureRecorder {
} }
case STATE_WAITING_CAPTURE: { case STATE_WAITING_CAPTURE: {
if (result instanceof TotalCaptureResult if (result instanceof TotalCaptureResult
&& result.getRequest().getTag() == (Integer) CameraDevice.TEMPLATE_STILL_CAPTURE) { && result.getRequest().getTag() == (Integer) REQUEST_TAG) {
mState = STATE_WAITING_IMAGE; mState = STATE_WAITING_IMAGE;
} }
break; break;
@ -187,6 +186,50 @@ public class Full2PictureRecorder extends PictureRecorder {
} }
} }
@Override
public void onImageAvailable(ImageReader reader) {
mState = STATE_IDLE;
// Read the JPEG.
Image image = null;
//noinspection TryFinallyCanBeTryWithResources
try {
image = reader.acquireNextImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
mResult.data = bytes;
} catch (Exception e) {
mResult = null;
mError = e;
dispatchResult();
return;
} finally {
if (image != null) image.close();
}
// Just like Camera1, unfortunately, the camera might rotate the image
// and put EXIF=0 instead of respecting our EXIF and leave the image unaltered.
mResult.format = PictureResult.FORMAT_JPEG;
mResult.rotation = 0;
try {
ExifInterface exif = new ExifInterface(new ByteArrayInputStream(mResult.data));
int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
mResult.rotation = ExifHelper.readExifOrientation(exifOrientation);
} catch (IOException ignore) { }
// Before leaving, unlock focus.
try {
mBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
mSession.capture(mBuilder.build(), mCallback, null);
} catch (CameraAccessException ignore) { }
// Leave.
dispatchResult();
}
@Override @Override
protected void dispatchResult() { protected void dispatchResult() {
mState = STATE_IDLE; mState = STATE_IDLE;

@ -119,7 +119,8 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
PicturePreviewActivity.setPictureResult(result); PicturePreviewActivity.setPictureResult(result);
Intent intent = new Intent(CameraActivity.this, PicturePreviewActivity.class); Intent intent = new Intent(CameraActivity.this, PicturePreviewActivity.class);
intent.putExtra("delay", callbackTime - mCaptureTime); intent.putExtra("delay", callbackTime - mCaptureTime);
// TODO temp startActivity(intent); Log.e("CameraActivity", "Picture delay: " + (callbackTime - mCaptureTime));
startActivity(intent);
mCaptureTime = 0; mCaptureTime = 0;
} }

Loading…
Cancel
Save