Add ImageReader version of picture recorder

pull/524/head
Mattia Iavarone 6 years ago
parent ff6b63b0bb
commit 5f2a5d65d3
  1. 58
      cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java

@ -4,9 +4,13 @@ import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.media.Image;
import android.media.ImageReader;
import android.opengl.EGL14;
import android.opengl.EGLContext;
import android.opengl.Matrix;
@ -36,7 +40,10 @@ import androidx.annotation.Nullable;
import android.view.Surface;
import java.nio.ByteBuffer;
/**
* API 19.
* Records a picture snapshots from the {@link GlCameraPreview}. It works as follows:
*
* - We register a one time {@link RendererFrameCallback} on the preview
@ -65,9 +72,10 @@ import android.view.Surface;
* This is technically correct, we can just re-draw on this offscreen surface the input texture.
* However, despite being more correct, this method is significantly slower than the previous.
*
* A third and better method could be using {@link android.media.ImageReader} to construct
* a surface and use that as the output for a new EGL window surface. This would make sense
* and would avoid us using glReadPixels. However, it is API 19.
* A third method is using {@link ImageReader} to construct a surface and use that
* as the output for a new EGL window surface. This makes sense and would avoid us using glReadPixels.
* However, it's not working. It looks like ImageReader is not capable of converting the input data
* into JPEG: "RGBA override BLOB format buffer should have height == width"
*/
public class SnapshotGlPictureRecorder extends PictureRecorder {
@ -76,6 +84,7 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
private static final int METHOD_WINDOW_SURFACE = 0;
private static final int METHOD_PBUFFER_SURFACE = 1;
private static final int METHOD_IMAGEREADER_SURFACE = 2;
// METHOD_PBUFFER_SURFACE: (409 + 420 + 394 + 424 + 437 + 482 + 420 + 463 + 451 + 378 + 416 + 429 + 459 + 361 + 466 + 517 + 492 + 435 + 585 + 427) / 20
// METHOD_WINDOW_SURFACE: (329 + 462 + 343 + 312 + 311 + 322 + 495 + 342 + 310 + 329 + 373 + 380 + 667 + 315 + 354 + 351 + 315 + 333 + 277 + 274) / 20
@ -152,16 +161,22 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
@Override
public void run() {
// 0. Create the output surface.
EglBaseSurface surface;
ImageReader reader = null;
EglBaseSurface eglSurface;
if (METHOD == METHOD_WINDOW_SURFACE) {
surface = new EglWindowSurface(core, mSurfaceTexture);
eglSurface = new EglWindowSurface(core, mSurfaceTexture);
eglSurface.makeCurrent();
} else if (METHOD == METHOD_PBUFFER_SURFACE) {
surface = new EglBaseSurface(core);
surface.createOffscreenSurface(mResult.size.getWidth(), mResult.size.getHeight());
eglSurface = new EglBaseSurface(core);
eglSurface.createOffscreenSurface(mResult.size.getWidth(), mResult.size.getHeight());
eglSurface.makeCurrent();
} else if (METHOD == METHOD_IMAGEREADER_SURFACE) {
reader = ImageReader.newInstance(mResult.size.getWidth(), mResult.size.getHeight(), ImageFormat.JPEG, 1);
eglSurface = new EglWindowSurface(core, reader.getSurface(), true);
eglSurface.makeCurrent();
} else {
throw new RuntimeException("Unknown method.");
}
surface.makeCurrent();
// 1. Get latest texture
mSurfaceTexture.updateTexImage();
@ -220,15 +235,38 @@ public class SnapshotGlPictureRecorder extends PictureRecorder {
// 8. Draw and save
mViewport.drawFrame(mTextureId, mTransform);
if (mHasOverlay) mViewport.drawFrame(mOverlayTextureId, mOverlayTransform);
mResult.data = surface.saveFrameTo(Bitmap.CompressFormat.JPEG);
mResult.format = PictureResult.FORMAT_JPEG;
if (METHOD == METHOD_WINDOW_SURFACE) {
// We do not call swapBuffers so we do not really publish to the texture.
// We read pixels at the gl level.
mResult.data = eglSurface.saveFrameTo(Bitmap.CompressFormat.JPEG);
} else if (METHOD == METHOD_PBUFFER_SURFACE) {
// For pbuffer surfaces, swapBuffers is not needed. They already hold
// the data, so we can call glReadPixels safely.
mResult.data = eglSurface.saveFrameTo(Bitmap.CompressFormat.JPEG);
} else if (METHOD == METHOD_IMAGEREADER_SURFACE) {
// Publish into the output Surface.
eglSurface.swapBuffers();
Image image;
//noinspection ConstantConditions,StatementWithEmptyBody
while ((image = reader.acquireNextImage()) == null) {}
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
mResult.data = bytes;
image.close();
reader.close();
} else {
throw new RuntimeException("Unknown method.");
}
// 9. Cleanup
mSurfaceTexture.releaseTexImage();
surface.releaseEglSurface();
eglSurface.releaseEglSurface();
mViewport.release();
mSurfaceTexture.release();
if (mHasOverlay) {
mOverlaySurfaceTexture.releaseTexImage();
mOverlaySurface.release();
mOverlaySurfaceTexture.release();
}

Loading…
Cancel
Save