Frame Processing maxWidth, maxHeight and format (#704)
	
		
	
				
					
				
			* Create CameraEngine and CameraBaseEngine * Promote filters to stable - no experimental flag * Fix setSnapshotMaxWidth / Height bugs * Add setFrameProcessingMaxWidth and setFrameProcessingMaxHeight * Add setFrameProcessingMaxWidth and setFrameProcessingMaxHeight (docs) * Prepare Frame for Images, abstract FrameManager, create ByteBufferFrameManager * Fix tests * Fix unit tests * Send Images for Camera2 * Tests * Add CameraView.setFrameProcessingFormat(int), tests, docs * Add CameraOptions.getSupportedFrameProcessingFormats(), tests * Add CameraEngine support, integration tests * Fix demo app, add getFrameProcessingPoolSize * Fix tests * Fix testspull/708/head
							parent
							
								
									4a6b9be905
								
							
						
					
					
						commit
						e1721bb77d
					
				@ -0,0 +1,105 @@ | 
				
			|||||||
 | 
					package com.otaliastudios.cameraview.frame; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.graphics.ImageFormat; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.test.ext.junit.runners.AndroidJUnit4; | 
				
			||||||
 | 
					import androidx.test.filters.SmallTest; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.BaseTest; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.size.Size; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.After; | 
				
			||||||
 | 
					import org.junit.Before; | 
				
			||||||
 | 
					import org.junit.Test; | 
				
			||||||
 | 
					import org.junit.runner.RunWith; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.mockito.Matchers.any; | 
				
			||||||
 | 
					import static org.mockito.Mockito.mock; | 
				
			||||||
 | 
					import static org.mockito.Mockito.never; | 
				
			||||||
 | 
					import static org.mockito.Mockito.reset; | 
				
			||||||
 | 
					import static org.mockito.Mockito.times; | 
				
			||||||
 | 
					import static org.mockito.Mockito.verify; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@RunWith(AndroidJUnit4.class) | 
				
			||||||
 | 
					@SmallTest | 
				
			||||||
 | 
					public class ByteBufferFrameManagerTest extends BaseTest { | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private ByteBufferFrameManager.BufferCallback callback; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Before | 
				
			||||||
 | 
					    public void setUp() { | 
				
			||||||
 | 
					        callback = mock(ByteBufferFrameManager.BufferCallback.class); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @After | 
				
			||||||
 | 
					    public void tearDown() { | 
				
			||||||
 | 
					        callback = null; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test | 
				
			||||||
 | 
					    public void testAllocate() { | 
				
			||||||
 | 
					        ByteBufferFrameManager manager = new ByteBufferFrameManager(1, callback); | 
				
			||||||
 | 
					        manager.setUp(ImageFormat.NV21, new Size(50, 50)); | 
				
			||||||
 | 
					        verify(callback, times(1)).onBufferAvailable(any(byte[].class)); | 
				
			||||||
 | 
					        reset(callback); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        manager = new ByteBufferFrameManager(5, callback); | 
				
			||||||
 | 
					        manager.setUp(ImageFormat.NV21, new Size(50, 50)); | 
				
			||||||
 | 
					        verify(callback, times(5)).onBufferAvailable(any(byte[].class)); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test | 
				
			||||||
 | 
					    public void testOnFrameReleased_alreadyFull() { | 
				
			||||||
 | 
					        ByteBufferFrameManager manager = new ByteBufferFrameManager(1, callback); | 
				
			||||||
 | 
					        manager.setUp(ImageFormat.NV21, new Size(50, 50)); | 
				
			||||||
 | 
					        int length = manager.getFrameBytes(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Frame frame1 = manager.getFrame(new byte[length], 0, 0); | 
				
			||||||
 | 
					        // Since frame1 is already taken and poolSize = 1, a new Frame is created.
 | 
				
			||||||
 | 
					        Frame frame2 = manager.getFrame(new byte[length], 0, 0); | 
				
			||||||
 | 
					        // Release the first frame so it goes back into the pool.
 | 
				
			||||||
 | 
					        manager.onFrameReleased(frame1, (byte[]) frame1.getData()); | 
				
			||||||
 | 
					        reset(callback); | 
				
			||||||
 | 
					        // Release the second. The pool is already full, so onBufferAvailable should not be called
 | 
				
			||||||
 | 
					        // since this Frame instance will NOT be reused.
 | 
				
			||||||
 | 
					        manager.onFrameReleased(frame2, (byte[]) frame2.getData()); | 
				
			||||||
 | 
					        verify(callback, never()).onBufferAvailable((byte[]) frame2.getData()); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test | 
				
			||||||
 | 
					    public void testOnFrameReleased_sameLength() { | 
				
			||||||
 | 
					        ByteBufferFrameManager manager = new ByteBufferFrameManager(1, callback); | 
				
			||||||
 | 
					        manager.setUp(ImageFormat.NV21, new Size(50, 50)); | 
				
			||||||
 | 
					        int length = manager.getFrameBytes(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // A camera preview frame comes. Request a frame.
 | 
				
			||||||
 | 
					        byte[] picture = new byte[length]; | 
				
			||||||
 | 
					        Frame frame = manager.getFrame(picture, 0, 0); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Release the frame and ensure that onBufferAvailable is called.
 | 
				
			||||||
 | 
					        reset(callback); | 
				
			||||||
 | 
					        manager.onFrameReleased(frame, (byte[]) frame.getData()); | 
				
			||||||
 | 
					        verify(callback, times(1)).onBufferAvailable(picture); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test | 
				
			||||||
 | 
					    public void testOnFrameReleased_differentLength() { | 
				
			||||||
 | 
					        ByteBufferFrameManager manager = new ByteBufferFrameManager(1, callback); | 
				
			||||||
 | 
					        manager.setUp(ImageFormat.NV21, new Size(50, 50)); | 
				
			||||||
 | 
					        int length = manager.getFrameBytes(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // A camera preview frame comes. Request a frame.
 | 
				
			||||||
 | 
					        byte[] picture = new byte[length]; | 
				
			||||||
 | 
					        Frame frame = manager.getFrame(picture, 0, 0); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Don't release the frame. Change the allocation size.
 | 
				
			||||||
 | 
					        manager.setUp(ImageFormat.NV16, new Size(15, 15)); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Now release the old frame and ensure that onBufferAvailable is NOT called,
 | 
				
			||||||
 | 
					        // because the released data has wrong length.
 | 
				
			||||||
 | 
					        manager.onFrameReleased(frame, (byte[]) frame.getData()); | 
				
			||||||
 | 
					        reset(callback); | 
				
			||||||
 | 
					        verify(callback, never()).onBufferAvailable(picture); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,100 +0,0 @@ | 
				
			|||||||
package com.otaliastudios.cameraview.internal.utils; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import android.graphics.Bitmap; | 
					 | 
				
			||||||
import android.graphics.BitmapFactory; | 
					 | 
				
			||||||
import android.graphics.Canvas; | 
					 | 
				
			||||||
import android.graphics.Color; | 
					 | 
				
			||||||
import android.graphics.ImageFormat; | 
					 | 
				
			||||||
import android.graphics.Paint; | 
					 | 
				
			||||||
import android.graphics.PorterDuff; | 
					 | 
				
			||||||
import android.graphics.Rect; | 
					 | 
				
			||||||
import android.graphics.YuvImage; | 
					 | 
				
			||||||
import android.media.Image; | 
					 | 
				
			||||||
import android.media.ImageReader; | 
					 | 
				
			||||||
import android.os.Handler; | 
					 | 
				
			||||||
import android.os.Looper; | 
					 | 
				
			||||||
import android.view.Surface; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import androidx.annotation.NonNull; | 
					 | 
				
			||||||
import androidx.test.ext.junit.runners.AndroidJUnit4; | 
					 | 
				
			||||||
import androidx.test.filters.SmallTest; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.otaliastudios.cameraview.BaseTest; | 
					 | 
				
			||||||
import com.otaliastudios.cameraview.tools.Op; | 
					 | 
				
			||||||
import com.otaliastudios.cameraview.tools.SdkExclude; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.junit.Test; | 
					 | 
				
			||||||
import org.junit.runner.RunWith; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.ByteArrayOutputStream; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static org.junit.Assert.assertNotNull; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** | 
					 | 
				
			||||||
 * Starting from API 29, surface.lockCanvas() sets the surface format to RGBA_8888: | 
					 | 
				
			||||||
 * https://github.com/aosp-mirror/platform_frameworks_base/blob/android10-release/core/jni/android_view_Surface.cpp#L215-L217 .
 | 
					 | 
				
			||||||
 * For this reason, acquireLatestImage crashes because we requested a different format. | 
					 | 
				
			||||||
 */ | 
					 | 
				
			||||||
@SdkExclude(minSdkVersion = 29) | 
					 | 
				
			||||||
@RunWith(AndroidJUnit4.class) | 
					 | 
				
			||||||
@SmallTest | 
					 | 
				
			||||||
public class ImageHelperTest extends BaseTest { | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @NonNull | 
					 | 
				
			||||||
    private Image getImage() { | 
					 | 
				
			||||||
        ImageReader reader = ImageReader.newInstance(100, 100, ImageFormat.YUV_420_888, 1); | 
					 | 
				
			||||||
        Surface readerSurface = reader.getSurface(); | 
					 | 
				
			||||||
        final Op<Image> imageOp = new Op<>(); | 
					 | 
				
			||||||
        reader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { | 
					 | 
				
			||||||
            @Override | 
					 | 
				
			||||||
            public void onImageAvailable(ImageReader reader) { | 
					 | 
				
			||||||
                Image image = reader.acquireLatestImage(); | 
					 | 
				
			||||||
                if (image != null) imageOp.controller().end(image); | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
        }, new Handler(Looper.getMainLooper())); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Write on reader surface.
 | 
					 | 
				
			||||||
        Canvas readerCanvas = readerSurface.lockCanvas(null); | 
					 | 
				
			||||||
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); | 
					 | 
				
			||||||
        paint.setColor(Color.RED); | 
					 | 
				
			||||||
        readerCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.MULTIPLY); | 
					 | 
				
			||||||
        readerCanvas.drawCircle(50, 50, 50, paint); | 
					 | 
				
			||||||
        readerSurface.unlockCanvasAndPost(readerCanvas); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Wait
 | 
					 | 
				
			||||||
        Image image = imageOp.await(5000); | 
					 | 
				
			||||||
        assertNotNull(image); | 
					 | 
				
			||||||
        return image; | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Test | 
					 | 
				
			||||||
    public void testImage() { | 
					 | 
				
			||||||
        Image image = getImage(); | 
					 | 
				
			||||||
        int width = image.getWidth(); | 
					 | 
				
			||||||
        int height = image.getHeight(); | 
					 | 
				
			||||||
        int bitsPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.NV21); | 
					 | 
				
			||||||
        int sizeBits = width * height * bitsPerPixel; | 
					 | 
				
			||||||
        int sizeBytes = (int) Math.ceil(sizeBits / 8.0d); | 
					 | 
				
			||||||
        byte[] bytes = new byte[sizeBytes]; | 
					 | 
				
			||||||
        ImageHelper.convertToNV21(image, bytes); | 
					 | 
				
			||||||
        image.close(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Read the image
 | 
					 | 
				
			||||||
        YuvImage yuvImage = new YuvImage(bytes, ImageFormat.NV21, width, height, null); | 
					 | 
				
			||||||
        ByteArrayOutputStream jpegStream = new ByteArrayOutputStream(); | 
					 | 
				
			||||||
        yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, jpegStream); | 
					 | 
				
			||||||
        byte[] jpegByteArray = jpegStream.toByteArray(); | 
					 | 
				
			||||||
        Bitmap bitmap = BitmapFactory.decodeByteArray(jpegByteArray, 0, jpegByteArray.length); | 
					 | 
				
			||||||
        assertNotNull(bitmap); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Wanted to do assertions on the color here but it doesn't work. There must be an issue
 | 
					 | 
				
			||||||
        // with how we are drawing the image in this test, since in real camera, the algorithm works well.
 | 
					 | 
				
			||||||
        // So for now let's just test that nothing crashes during this process.
 | 
					 | 
				
			||||||
        // int color = bitmap.getPixel(bitmap.getWidth() - 1, bitmap.getHeight() - 1);
 | 
					 | 
				
			||||||
        // assertEquals(Color.red(color), 255, 5);
 | 
					 | 
				
			||||||
        // assertEquals(Color.green(color), 0, 5);
 | 
					 | 
				
			||||||
        // assertEquals(Color.blue(color), 0, 5);
 | 
					 | 
				
			||||||
        // assertEquals(Color.alpha(color), 0, 5);
 | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -0,0 +1,927 @@ | 
				
			|||||||
 | 
					package com.otaliastudios.cameraview.engine; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.location.Location; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.annotation.CallSuper; | 
				
			||||||
 | 
					import androidx.annotation.NonNull; | 
				
			||||||
 | 
					import androidx.annotation.Nullable; | 
				
			||||||
 | 
					import androidx.annotation.VisibleForTesting; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.android.gms.tasks.Task; | 
				
			||||||
 | 
					import com.google.android.gms.tasks.Tasks; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.CameraException; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.CameraOptions; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.PictureResult; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.VideoResult; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.controls.Audio; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.controls.Facing; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.controls.Flash; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.controls.Hdr; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.controls.Mode; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.controls.PictureFormat; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.controls.VideoCodec; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.controls.WhiteBalance; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.engine.offset.Angles; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.engine.offset.Reference; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.engine.orchestrator.CameraState; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.frame.FrameManager; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.overlay.Overlay; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.picture.PictureRecorder; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.preview.CameraPreview; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.size.AspectRatio; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.size.Size; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.size.SizeSelector; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.size.SizeSelectors; | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.video.VideoRecorder; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.File; | 
				
			||||||
 | 
					import java.util.ArrayList; | 
				
			||||||
 | 
					import java.util.Collection; | 
				
			||||||
 | 
					import java.util.List; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** | 
				
			||||||
 | 
					 * Abstract implementation of {@link CameraEngine} that helps in common tasks. | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					public abstract class CameraBaseEngine extends CameraEngine { | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected CameraPreview mPreview; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected CameraOptions mCameraOptions; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected PictureRecorder mPictureRecorder; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected VideoRecorder mVideoRecorder; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected Size mCaptureSize; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected Size mPreviewStreamSize; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected Size mFrameProcessingSize; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected int mFrameProcessingFormat; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected boolean mHasFrameProcessors; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected Flash mFlash; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected WhiteBalance mWhiteBalance; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected VideoCodec mVideoCodec; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected Hdr mHdr; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected PictureFormat mPictureFormat; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected Location mLocation; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected float mZoomValue; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected float mExposureCorrectionValue; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected boolean mPlaySounds; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected boolean mPictureMetering; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected boolean mPictureSnapshotMetering; | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") protected float mPreviewFrameRate; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private final FrameManager mFrameManager; | 
				
			||||||
 | 
					    private final Angles mAngles; | 
				
			||||||
 | 
					    @Nullable private SizeSelector mPreviewStreamSizeSelector; | 
				
			||||||
 | 
					    private SizeSelector mPictureSizeSelector; | 
				
			||||||
 | 
					    private SizeSelector mVideoSizeSelector; | 
				
			||||||
 | 
					    private Facing mFacing; | 
				
			||||||
 | 
					    private Mode mMode; | 
				
			||||||
 | 
					    private Audio mAudio; | 
				
			||||||
 | 
					    private long mVideoMaxSize; | 
				
			||||||
 | 
					    private int mVideoMaxDuration; | 
				
			||||||
 | 
					    private int mVideoBitRate; | 
				
			||||||
 | 
					    private int mAudioBitRate; | 
				
			||||||
 | 
					    private long mAutoFocusResetDelayMillis; | 
				
			||||||
 | 
					    private int mSnapshotMaxWidth; // in REF_VIEW like SizeSelectors
 | 
				
			||||||
 | 
					    private int mSnapshotMaxHeight; // in REF_VIEW like SizeSelectors
 | 
				
			||||||
 | 
					    private int mFrameProcessingMaxWidth; // in REF_VIEW like SizeSelectors
 | 
				
			||||||
 | 
					    private int mFrameProcessingMaxHeight; // in REF_VIEW like SizeSelectors
 | 
				
			||||||
 | 
					    private Overlay mOverlay; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Ops used for testing.
 | 
				
			||||||
 | 
					    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mZoomTask | 
				
			||||||
 | 
					            = Tasks.forResult(null); | 
				
			||||||
 | 
					    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mExposureCorrectionTask | 
				
			||||||
 | 
					            = Tasks.forResult(null); | 
				
			||||||
 | 
					    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mFlashTask | 
				
			||||||
 | 
					            = Tasks.forResult(null); | 
				
			||||||
 | 
					    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mWhiteBalanceTask | 
				
			||||||
 | 
					            = Tasks.forResult(null); | 
				
			||||||
 | 
					    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mHdrTask | 
				
			||||||
 | 
					            = Tasks.forResult(null); | 
				
			||||||
 | 
					    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mLocationTask | 
				
			||||||
 | 
					            = Tasks.forResult(null); | 
				
			||||||
 | 
					    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mPlaySoundsTask | 
				
			||||||
 | 
					            = Tasks.forResult(null); | 
				
			||||||
 | 
					    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) Task<Void> mPreviewFrameRateTask | 
				
			||||||
 | 
					            = Tasks.forResult(null); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") | 
				
			||||||
 | 
					    protected CameraBaseEngine(@NonNull Callback callback) { | 
				
			||||||
 | 
					        super(callback); | 
				
			||||||
 | 
					        mFrameManager = instantiateFrameManager(); | 
				
			||||||
 | 
					        mAngles = new Angles(); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Called at construction time to get a frame manager that can later be | 
				
			||||||
 | 
					     * accessed through {@link #getFrameManager()}. | 
				
			||||||
 | 
					     * @return a frame manager | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    protected abstract FrameManager instantiateFrameManager(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final Angles getAngles() { | 
				
			||||||
 | 
					        return mAngles; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public FrameManager getFrameManager() { | 
				
			||||||
 | 
					        return mFrameManager; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Nullable | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final CameraOptions getCameraOptions() { | 
				
			||||||
 | 
					        return mCameraOptions; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setPreview(@NonNull CameraPreview cameraPreview) { | 
				
			||||||
 | 
					        if (mPreview != null) mPreview.setSurfaceCallback(null); | 
				
			||||||
 | 
					        mPreview = cameraPreview; | 
				
			||||||
 | 
					        mPreview.setSurfaceCallback(this); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final CameraPreview getPreview() { | 
				
			||||||
 | 
					        return mPreview; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setOverlay(@Nullable Overlay overlay) { | 
				
			||||||
 | 
					        mOverlay = overlay; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Nullable | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final Overlay getOverlay() { | 
				
			||||||
 | 
					        return mOverlay; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setPreviewStreamSizeSelector(@Nullable SizeSelector selector) { | 
				
			||||||
 | 
					        mPreviewStreamSizeSelector = selector; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Nullable | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final SizeSelector getPreviewStreamSizeSelector() { | 
				
			||||||
 | 
					        return mPreviewStreamSizeSelector; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setPictureSizeSelector(@NonNull SizeSelector selector) { | 
				
			||||||
 | 
					        mPictureSizeSelector = selector; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final SizeSelector getPictureSizeSelector() { | 
				
			||||||
 | 
					        return mPictureSizeSelector; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setVideoSizeSelector(@NonNull SizeSelector selector) { | 
				
			||||||
 | 
					        mVideoSizeSelector = selector; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final SizeSelector getVideoSizeSelector() { | 
				
			||||||
 | 
					        return mVideoSizeSelector; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setVideoMaxSize(long videoMaxSizeBytes) { | 
				
			||||||
 | 
					        mVideoMaxSize = videoMaxSizeBytes; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final long getVideoMaxSize() { | 
				
			||||||
 | 
					        return mVideoMaxSize; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setVideoMaxDuration(int videoMaxDurationMillis) { | 
				
			||||||
 | 
					        mVideoMaxDuration = videoMaxDurationMillis; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final int getVideoMaxDuration() { | 
				
			||||||
 | 
					        return mVideoMaxDuration; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setVideoCodec(@NonNull VideoCodec codec) { | 
				
			||||||
 | 
					        mVideoCodec = codec; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final VideoCodec getVideoCodec() { | 
				
			||||||
 | 
					        return mVideoCodec; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setVideoBitRate(int videoBitRate) { | 
				
			||||||
 | 
					        mVideoBitRate = videoBitRate; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final int getVideoBitRate() { | 
				
			||||||
 | 
					        return mVideoBitRate; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setAudioBitRate(int audioBitRate) { | 
				
			||||||
 | 
					        mAudioBitRate = audioBitRate; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final int getAudioBitRate() { | 
				
			||||||
 | 
					        return mAudioBitRate; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setSnapshotMaxWidth(int maxWidth) { | 
				
			||||||
 | 
					        mSnapshotMaxWidth = maxWidth; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final int getSnapshotMaxWidth() { | 
				
			||||||
 | 
					        return mSnapshotMaxWidth; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setSnapshotMaxHeight(int maxHeight) { | 
				
			||||||
 | 
					        mSnapshotMaxHeight = maxHeight; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final int getSnapshotMaxHeight() { | 
				
			||||||
 | 
					        return mSnapshotMaxHeight; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setFrameProcessingMaxWidth(int maxWidth) { | 
				
			||||||
 | 
					        mFrameProcessingMaxWidth = maxWidth; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final int getFrameProcessingMaxWidth() { | 
				
			||||||
 | 
					        return mFrameProcessingMaxWidth; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setFrameProcessingMaxHeight(int maxHeight) { | 
				
			||||||
 | 
					        mFrameProcessingMaxHeight = maxHeight; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final int getFrameProcessingMaxHeight() { | 
				
			||||||
 | 
					        return mFrameProcessingMaxHeight; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final int getFrameProcessingFormat() { | 
				
			||||||
 | 
					        return mFrameProcessingFormat; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setAutoFocusResetDelay(long delayMillis) { | 
				
			||||||
 | 
					        mAutoFocusResetDelayMillis = delayMillis; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final long getAutoFocusResetDelay() { | 
				
			||||||
 | 
					        return mAutoFocusResetDelayMillis; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Helper function for subclasses. | 
				
			||||||
 | 
					     * @return true if AF should be reset | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") | 
				
			||||||
 | 
					    protected final boolean shouldResetAutoFocus() { | 
				
			||||||
 | 
					        return mAutoFocusResetDelayMillis > 0 && mAutoFocusResetDelayMillis != Long.MAX_VALUE; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Sets a new facing value. This will restart the engine session (if there's any) | 
				
			||||||
 | 
					     * so that we can open the new facing camera. | 
				
			||||||
 | 
					     * @param facing facing | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setFacing(final @NonNull Facing facing) { | 
				
			||||||
 | 
					        final Facing old = mFacing; | 
				
			||||||
 | 
					        if (facing != old) { | 
				
			||||||
 | 
					            mFacing = facing; | 
				
			||||||
 | 
					            getOrchestrator().scheduleStateful("facing", CameraState.ENGINE, | 
				
			||||||
 | 
					                    new Runnable() { | 
				
			||||||
 | 
					                @Override | 
				
			||||||
 | 
					                public void run() { | 
				
			||||||
 | 
					                    if (collectCameraInfo(facing)) { | 
				
			||||||
 | 
					                        restart(); | 
				
			||||||
 | 
					                    } else { | 
				
			||||||
 | 
					                        mFacing = old; | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            }); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final Facing getFacing() { | 
				
			||||||
 | 
					        return mFacing; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Sets a new audio value that will be used for video recordings. | 
				
			||||||
 | 
					     * @param audio desired audio | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setAudio(@NonNull Audio audio) { | 
				
			||||||
 | 
					        if (mAudio != audio) { | 
				
			||||||
 | 
					            if (isTakingVideo()) { | 
				
			||||||
 | 
					                LOG.w("Audio setting was changed while recording. " + | 
				
			||||||
 | 
					                        "Changes will take place starting from next video"); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            mAudio = audio; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final Audio getAudio() { | 
				
			||||||
 | 
					        return mAudio; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Sets the desired mode (either picture or video). | 
				
			||||||
 | 
					     * @param mode desired mode. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setMode(@NonNull Mode mode) { | 
				
			||||||
 | 
					        if (mode != mMode) { | 
				
			||||||
 | 
					            mMode = mode; | 
				
			||||||
 | 
					            getOrchestrator().scheduleStateful("mode", CameraState.ENGINE, | 
				
			||||||
 | 
					                    new Runnable() { | 
				
			||||||
 | 
					                @Override | 
				
			||||||
 | 
					                public void run() { | 
				
			||||||
 | 
					                    restart(); | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            }); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final Mode getMode() { | 
				
			||||||
 | 
					        return mMode; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final float getZoomValue() { | 
				
			||||||
 | 
					        return mZoomValue; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final float getExposureCorrectionValue() { | 
				
			||||||
 | 
					        return mExposureCorrectionValue; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final Flash getFlash() { | 
				
			||||||
 | 
					        return mFlash; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final WhiteBalance getWhiteBalance() { | 
				
			||||||
 | 
					        return mWhiteBalance; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final Hdr getHdr() { | 
				
			||||||
 | 
					        return mHdr; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Nullable | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final Location getLocation() { | 
				
			||||||
 | 
					        return mLocation; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final PictureFormat getPictureFormat() { | 
				
			||||||
 | 
					        return mPictureFormat; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final float getPreviewFrameRate() { | 
				
			||||||
 | 
					        return mPreviewFrameRate; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final boolean hasFrameProcessors() { | 
				
			||||||
 | 
					        return mHasFrameProcessors; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setPictureMetering(boolean enable) { | 
				
			||||||
 | 
					        mPictureMetering = enable; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final boolean getPictureMetering() { | 
				
			||||||
 | 
					        return mPictureMetering; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void setPictureSnapshotMetering(boolean enable) { | 
				
			||||||
 | 
					        mPictureSnapshotMetering = enable; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final boolean getPictureSnapshotMetering() { | 
				
			||||||
 | 
					        return mPictureSnapshotMetering; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //region Picture and video control
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final boolean isTakingPicture() { | 
				
			||||||
 | 
					        return mPictureRecorder != null; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public /* final */ void takePicture(final @NonNull PictureResult.Stub stub) { | 
				
			||||||
 | 
					        // Save boolean before scheduling! See how Camera2Engine calls this with a temp value.
 | 
				
			||||||
 | 
					        final boolean metering = mPictureMetering; | 
				
			||||||
 | 
					        getOrchestrator().scheduleStateful("take picture", CameraState.BIND, | 
				
			||||||
 | 
					                new Runnable() { | 
				
			||||||
 | 
					            @Override | 
				
			||||||
 | 
					            public void run() { | 
				
			||||||
 | 
					                LOG.i("takePicture:", "running. isTakingPicture:", isTakingPicture()); | 
				
			||||||
 | 
					                if (isTakingPicture()) return; | 
				
			||||||
 | 
					                if (mMode == Mode.VIDEO) { | 
				
			||||||
 | 
					                    throw new IllegalStateException("Can't take hq pictures while in VIDEO mode"); | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                stub.isSnapshot = false; | 
				
			||||||
 | 
					                stub.location = mLocation; | 
				
			||||||
 | 
					                stub.facing = mFacing; | 
				
			||||||
 | 
					                stub.format = mPictureFormat; | 
				
			||||||
 | 
					                onTakePicture(stub, metering); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        }); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * The snapshot size is the {@link #getPreviewStreamSize(Reference)}, but cropped based on the | 
				
			||||||
 | 
					     * view/surface aspect ratio. | 
				
			||||||
 | 
					     * @param stub a picture stub | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public /* final */ void takePictureSnapshot(final @NonNull PictureResult.Stub stub) { | 
				
			||||||
 | 
					        // Save boolean before scheduling! See how Camera2Engine calls this with a temp value.
 | 
				
			||||||
 | 
					        final boolean metering = mPictureSnapshotMetering; | 
				
			||||||
 | 
					        getOrchestrator().scheduleStateful("take picture snapshot", CameraState.BIND, | 
				
			||||||
 | 
					                new Runnable() { | 
				
			||||||
 | 
					            @Override | 
				
			||||||
 | 
					            public void run() { | 
				
			||||||
 | 
					                LOG.i("takePictureSnapshot:", "running. isTakingPicture:", isTakingPicture()); | 
				
			||||||
 | 
					                if (isTakingPicture()) return; | 
				
			||||||
 | 
					                stub.location = mLocation; | 
				
			||||||
 | 
					                stub.isSnapshot = true; | 
				
			||||||
 | 
					                stub.facing = mFacing; | 
				
			||||||
 | 
					                stub.format = PictureFormat.JPEG; | 
				
			||||||
 | 
					                // Leave the other parameters to subclasses.
 | 
				
			||||||
 | 
					                //noinspection ConstantConditions
 | 
				
			||||||
 | 
					                AspectRatio ratio = AspectRatio.of(getPreviewSurfaceSize(Reference.OUTPUT)); | 
				
			||||||
 | 
					                onTakePictureSnapshot(stub, ratio, metering); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        }); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public void onPictureShutter(boolean didPlaySound) { | 
				
			||||||
 | 
					        getCallback().onShutter(!didPlaySound); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public void onPictureResult(@Nullable PictureResult.Stub result, @Nullable Exception error) { | 
				
			||||||
 | 
					        mPictureRecorder = null; | 
				
			||||||
 | 
					        if (result != null) { | 
				
			||||||
 | 
					            getCallback().dispatchOnPictureTaken(result); | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            LOG.e("onPictureResult", "result is null: something went wrong.", error); | 
				
			||||||
 | 
					            getCallback().dispatchError(new CameraException(error, | 
				
			||||||
 | 
					                    CameraException.REASON_PICTURE_FAILED)); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final boolean isTakingVideo() { | 
				
			||||||
 | 
					        return mVideoRecorder != null && mVideoRecorder.isRecording(); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void takeVideo(final @NonNull VideoResult.Stub stub, final @NonNull File file) { | 
				
			||||||
 | 
					        getOrchestrator().scheduleStateful("take video", CameraState.BIND, new Runnable() { | 
				
			||||||
 | 
					            @Override | 
				
			||||||
 | 
					            public void run() { | 
				
			||||||
 | 
					                LOG.i("takeVideo:", "running. isTakingVideo:", isTakingVideo()); | 
				
			||||||
 | 
					                if (isTakingVideo()) return; | 
				
			||||||
 | 
					                if (mMode == Mode.PICTURE) { | 
				
			||||||
 | 
					                    throw new IllegalStateException("Can't record video while in PICTURE mode"); | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                stub.file = file; | 
				
			||||||
 | 
					                stub.isSnapshot = false; | 
				
			||||||
 | 
					                stub.videoCodec = mVideoCodec; | 
				
			||||||
 | 
					                stub.location = mLocation; | 
				
			||||||
 | 
					                stub.facing = mFacing; | 
				
			||||||
 | 
					                stub.audio = mAudio; | 
				
			||||||
 | 
					                stub.maxSize = mVideoMaxSize; | 
				
			||||||
 | 
					                stub.maxDuration = mVideoMaxDuration; | 
				
			||||||
 | 
					                stub.videoBitRate = mVideoBitRate; | 
				
			||||||
 | 
					                stub.audioBitRate = mAudioBitRate; | 
				
			||||||
 | 
					                onTakeVideo(stub); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        }); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * @param stub a video stub | 
				
			||||||
 | 
					     * @param file the output file | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void takeVideoSnapshot(@NonNull final VideoResult.Stub stub, | 
				
			||||||
 | 
					                                        @NonNull final File file) { | 
				
			||||||
 | 
					        getOrchestrator().scheduleStateful("take video snapshot", CameraState.BIND, | 
				
			||||||
 | 
					                new Runnable() { | 
				
			||||||
 | 
					            @Override | 
				
			||||||
 | 
					            public void run() { | 
				
			||||||
 | 
					                LOG.i("takeVideoSnapshot:", "running. isTakingVideo:", isTakingVideo()); | 
				
			||||||
 | 
					                stub.file = file; | 
				
			||||||
 | 
					                stub.isSnapshot = true; | 
				
			||||||
 | 
					                stub.videoCodec = mVideoCodec; | 
				
			||||||
 | 
					                stub.location = mLocation; | 
				
			||||||
 | 
					                stub.facing = mFacing; | 
				
			||||||
 | 
					                stub.videoBitRate = mVideoBitRate; | 
				
			||||||
 | 
					                stub.audioBitRate = mAudioBitRate; | 
				
			||||||
 | 
					                stub.audio = mAudio; | 
				
			||||||
 | 
					                stub.maxSize = mVideoMaxSize; | 
				
			||||||
 | 
					                stub.maxDuration = mVideoMaxDuration; | 
				
			||||||
 | 
					                //noinspection ConstantConditions
 | 
				
			||||||
 | 
					                AspectRatio ratio = AspectRatio.of(getPreviewSurfaceSize(Reference.OUTPUT)); | 
				
			||||||
 | 
					                onTakeVideoSnapshot(stub, ratio); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        }); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void stopVideo() { | 
				
			||||||
 | 
					        getOrchestrator().schedule("stop video", true, new Runnable() { | 
				
			||||||
 | 
					            @Override | 
				
			||||||
 | 
					            public void run() { | 
				
			||||||
 | 
					                LOG.i("stopVideo", "running. isTakingVideo?", isTakingVideo()); | 
				
			||||||
 | 
					                onStopVideo(); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        }); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @EngineThread | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") | 
				
			||||||
 | 
					    protected void onStopVideo() { | 
				
			||||||
 | 
					        if (mVideoRecorder != null) { | 
				
			||||||
 | 
					            mVideoRecorder.stop(false); | 
				
			||||||
 | 
					            // Do not null this, so we respond correctly to isTakingVideo(),
 | 
				
			||||||
 | 
					            // which checks for recorder presence and recorder.isRecording().
 | 
				
			||||||
 | 
					            // It will be nulled in onVideoResult.
 | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @CallSuper | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) { | 
				
			||||||
 | 
					        mVideoRecorder = null; | 
				
			||||||
 | 
					        if (result != null) { | 
				
			||||||
 | 
					            getCallback().dispatchOnVideoTaken(result); | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            LOG.e("onVideoResult", "result is null: something went wrong.", exception); | 
				
			||||||
 | 
					            getCallback().dispatchError(new CameraException(exception, | 
				
			||||||
 | 
					                    CameraException.REASON_VIDEO_FAILED)); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public void onVideoRecordingStart() { | 
				
			||||||
 | 
					        getCallback().dispatchOnVideoRecordingStart(); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public void onVideoRecordingEnd() { | 
				
			||||||
 | 
					        getCallback().dispatchOnVideoRecordingEnd(); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @EngineThread | 
				
			||||||
 | 
					    protected abstract void onTakePicture(@NonNull PictureResult.Stub stub, boolean doMetering); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @EngineThread | 
				
			||||||
 | 
					    protected abstract void onTakePictureSnapshot(@NonNull PictureResult.Stub stub, | 
				
			||||||
 | 
					                                                  @NonNull AspectRatio outputRatio, | 
				
			||||||
 | 
					                                                  boolean doMetering); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @EngineThread | 
				
			||||||
 | 
					    protected abstract void onTakeVideoSnapshot(@NonNull VideoResult.Stub stub, | 
				
			||||||
 | 
					                                                @NonNull AspectRatio outputRatio); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @EngineThread | 
				
			||||||
 | 
					    protected abstract void onTakeVideo(@NonNull VideoResult.Stub stub); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //region Size / Surface
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final void onSurfaceChanged() { | 
				
			||||||
 | 
					        LOG.i("onSurfaceChanged:", "Size is", getPreviewSurfaceSize(Reference.VIEW)); | 
				
			||||||
 | 
					        getOrchestrator().scheduleStateful("surface changed", CameraState.BIND, | 
				
			||||||
 | 
					                new Runnable() { | 
				
			||||||
 | 
					            @Override | 
				
			||||||
 | 
					            public void run() { | 
				
			||||||
 | 
					                // Compute a new camera preview size and apply.
 | 
				
			||||||
 | 
					                Size newSize = computePreviewStreamSize(); | 
				
			||||||
 | 
					                if (newSize.equals(mPreviewStreamSize)) { | 
				
			||||||
 | 
					                    LOG.i("onSurfaceChanged:", | 
				
			||||||
 | 
					                            "The computed preview size is identical. No op."); | 
				
			||||||
 | 
					                } else { | 
				
			||||||
 | 
					                    LOG.i("onSurfaceChanged:", | 
				
			||||||
 | 
					                            "Computed a new preview size. Calling onPreviewStreamSizeChanged()."); | 
				
			||||||
 | 
					                    mPreviewStreamSize = newSize; | 
				
			||||||
 | 
					                    onPreviewStreamSizeChanged(); | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        }); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * The preview stream size has changed. At this point, some engine might want to | 
				
			||||||
 | 
					     * simply call {@link #restartPreview()}, others to {@link #restartBind()}. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * It basically depends on the step at which the preview stream size is actually used. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @EngineThread | 
				
			||||||
 | 
					    protected abstract void onPreviewStreamSizeChanged(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Nullable | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final Size getPictureSize(@SuppressWarnings("SameParameterValue") @NonNull Reference reference) { | 
				
			||||||
 | 
					        Size size = mCaptureSize; | 
				
			||||||
 | 
					        if (size == null || mMode == Mode.VIDEO) return null; | 
				
			||||||
 | 
					        return getAngles().flip(Reference.SENSOR, reference) ? size.flip() : size; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Nullable | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final Size getVideoSize(@SuppressWarnings("SameParameterValue") @NonNull Reference reference) { | 
				
			||||||
 | 
					        Size size = mCaptureSize; | 
				
			||||||
 | 
					        if (size == null || mMode == Mode.PICTURE) return null; | 
				
			||||||
 | 
					        return getAngles().flip(Reference.SENSOR, reference) ? size.flip() : size; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Nullable | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final Size getPreviewStreamSize(@NonNull Reference reference) { | 
				
			||||||
 | 
					        Size size = mPreviewStreamSize; | 
				
			||||||
 | 
					        if (size == null) return null; | 
				
			||||||
 | 
					        return getAngles().flip(Reference.SENSOR, reference) ? size.flip() : size; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @SuppressWarnings("SameParameterValue") | 
				
			||||||
 | 
					    @Nullable | 
				
			||||||
 | 
					    private Size getPreviewSurfaceSize(@NonNull Reference reference) { | 
				
			||||||
 | 
					        CameraPreview preview = mPreview; | 
				
			||||||
 | 
					        if (preview == null) return null; | 
				
			||||||
 | 
					        return getAngles().flip(Reference.VIEW, reference) ? preview.getSurfaceSize().flip() | 
				
			||||||
 | 
					                : preview.getSurfaceSize(); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Returns the snapshot size, but not cropped with the view dimensions, which | 
				
			||||||
 | 
					     * is what we will do before creating the snapshot. However, cropping is done at various | 
				
			||||||
 | 
					     * levels so we don't want to perform the op here. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * The base snapshot size is based on PreviewStreamSize (later cropped with view ratio). Why? | 
				
			||||||
 | 
					     * One might be tempted to say that it's the SurfaceSize (which already matches the view ratio). | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * The camera sensor will capture preview frames with PreviewStreamSize and that's it. Then they | 
				
			||||||
 | 
					     * are hardware-scaled by the preview surface, but this does not affect the snapshot, as the | 
				
			||||||
 | 
					     * snapshot recorder simply creates another surface. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * Done tests to ensure that this is true, by using | 
				
			||||||
 | 
					     * 1. small SurfaceSize and biggest() PreviewStreamSize: output is not low quality | 
				
			||||||
 | 
					     * 2. big SurfaceSize and smallest() PreviewStreamSize: output is low quality | 
				
			||||||
 | 
					     * In both cases the result.size here was set to the biggest of the two. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * I could not find the same evidence for videos, but I would say that the same things should | 
				
			||||||
 | 
					     * apply, despite the capturing mechanism being different. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @param reference the reference system | 
				
			||||||
 | 
					     * @return the uncropped snapshot size | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @Nullable | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public final Size getUncroppedSnapshotSize(@NonNull Reference reference) { | 
				
			||||||
 | 
					        Size baseSize = getPreviewStreamSize(reference); | 
				
			||||||
 | 
					        if (baseSize == null) return null; | 
				
			||||||
 | 
					        boolean flip = getAngles().flip(reference, Reference.VIEW); | 
				
			||||||
 | 
					        int maxWidth = flip ? mSnapshotMaxHeight : mSnapshotMaxWidth; | 
				
			||||||
 | 
					        int maxHeight = flip ? mSnapshotMaxWidth : mSnapshotMaxHeight; | 
				
			||||||
 | 
					        if (maxWidth <= 0) maxWidth = Integer.MAX_VALUE; | 
				
			||||||
 | 
					        if (maxHeight <= 0) maxHeight = Integer.MAX_VALUE; | 
				
			||||||
 | 
					        float baseRatio = AspectRatio.of(baseSize).toFloat(); | 
				
			||||||
 | 
					        float maxValuesRatio = AspectRatio.of(maxWidth, maxHeight).toFloat(); | 
				
			||||||
 | 
					        if (maxValuesRatio >= baseRatio) { | 
				
			||||||
 | 
					            // Height is the real constraint.
 | 
				
			||||||
 | 
					            int outHeight = Math.min(baseSize.getHeight(), maxHeight); | 
				
			||||||
 | 
					            int outWidth = (int) Math.floor((float) outHeight * baseRatio); | 
				
			||||||
 | 
					            return new Size(outWidth, outHeight); | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            // Width is the real constraint.
 | 
				
			||||||
 | 
					            int outWidth = Math.min(baseSize.getWidth(), maxWidth); | 
				
			||||||
 | 
					            int outHeight = (int) Math.floor((float) outWidth / baseRatio); | 
				
			||||||
 | 
					            return new Size(outWidth, outHeight); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * This is called either on cameraView.start(), or when the underlying surface changes. | 
				
			||||||
 | 
					     * It is possible that in the first call the preview surface has not already computed its | 
				
			||||||
 | 
					     * dimensions. | 
				
			||||||
 | 
					     * But when it does, the {@link CameraPreview.SurfaceCallback} should be called, | 
				
			||||||
 | 
					     * and this should be refreshed. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @return the capture size | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") | 
				
			||||||
 | 
					    protected final Size computeCaptureSize() { | 
				
			||||||
 | 
					        return computeCaptureSize(mMode); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") | 
				
			||||||
 | 
					    protected final Size computeCaptureSize(@NonNull Mode mode) { | 
				
			||||||
 | 
					        // We want to pass stuff into the REF_VIEW reference, not the sensor one.
 | 
				
			||||||
 | 
					        // This is already managed by CameraOptions, so we just flip again at the end.
 | 
				
			||||||
 | 
					        boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW); | 
				
			||||||
 | 
					        SizeSelector selector; | 
				
			||||||
 | 
					        Collection<Size> sizes; | 
				
			||||||
 | 
					        if (mode == Mode.PICTURE) { | 
				
			||||||
 | 
					            selector = mPictureSizeSelector; | 
				
			||||||
 | 
					            sizes = mCameraOptions.getSupportedPictureSizes(); | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            selector = mVideoSizeSelector; | 
				
			||||||
 | 
					            sizes = mCameraOptions.getSupportedVideoSizes(); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        selector = SizeSelectors.or(selector, SizeSelectors.biggest()); | 
				
			||||||
 | 
					        List<Size> list = new ArrayList<>(sizes); | 
				
			||||||
 | 
					        Size result = selector.select(list).get(0); | 
				
			||||||
 | 
					        if (!list.contains(result)) { | 
				
			||||||
 | 
					            throw new RuntimeException("SizeSelectors must not return Sizes other than " + | 
				
			||||||
 | 
					                    "those in the input list."); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        LOG.i("computeCaptureSize:", "result:", result, "flip:", flip, "mode:", mode); | 
				
			||||||
 | 
					        if (flip) result = result.flip(); // Go back to REF_SENSOR
 | 
				
			||||||
 | 
					        return result; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * This is called anytime {@link #computePreviewStreamSize()} is called. | 
				
			||||||
 | 
					     * This means that it should be called during the binding process, when | 
				
			||||||
 | 
					     * we can be sure that the camera is available (engineState == STARTED). | 
				
			||||||
 | 
					     * @return a list of available sizes for preview | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @EngineThread | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    protected abstract List<Size> getPreviewStreamAvailableSizes(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @EngineThread | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") | 
				
			||||||
 | 
					    protected final Size computePreviewStreamSize() { | 
				
			||||||
 | 
					        @NonNull List<Size> previewSizes = getPreviewStreamAvailableSizes(); | 
				
			||||||
 | 
					        // These sizes come in REF_SENSOR. Since there is an external selector involved,
 | 
				
			||||||
 | 
					        // we must convert all of them to REF_VIEW, then flip back when returning.
 | 
				
			||||||
 | 
					        boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW); | 
				
			||||||
 | 
					        List<Size> sizes = new ArrayList<>(previewSizes.size()); | 
				
			||||||
 | 
					        for (Size size : previewSizes) { | 
				
			||||||
 | 
					            sizes.add(flip ? size.flip() : size); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Create our own default selector, which will be used if the external
 | 
				
			||||||
 | 
					        // mPreviewStreamSizeSelector is null, or if it fails in finding a size.
 | 
				
			||||||
 | 
					        Size targetMinSize = getPreviewSurfaceSize(Reference.VIEW); | 
				
			||||||
 | 
					        if (targetMinSize == null) { | 
				
			||||||
 | 
					            throw new IllegalStateException("targetMinSize should not be null here."); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        AspectRatio targetRatio = AspectRatio.of(mCaptureSize.getWidth(), mCaptureSize.getHeight()); | 
				
			||||||
 | 
					        if (flip) targetRatio = targetRatio.flip(); | 
				
			||||||
 | 
					        LOG.i("computePreviewStreamSize:", | 
				
			||||||
 | 
					                "targetRatio:", targetRatio, | 
				
			||||||
 | 
					                "targetMinSize:", targetMinSize); | 
				
			||||||
 | 
					        SizeSelector matchRatio = SizeSelectors.and( // Match this aspect ratio and sort by biggest
 | 
				
			||||||
 | 
					                SizeSelectors.aspectRatio(targetRatio, 0), | 
				
			||||||
 | 
					                SizeSelectors.biggest()); | 
				
			||||||
 | 
					        SizeSelector matchSize = SizeSelectors.and( // Bigger than this size, and sort by smallest
 | 
				
			||||||
 | 
					                SizeSelectors.minHeight(targetMinSize.getHeight()), | 
				
			||||||
 | 
					                SizeSelectors.minWidth(targetMinSize.getWidth()), | 
				
			||||||
 | 
					                SizeSelectors.smallest()); | 
				
			||||||
 | 
					        SizeSelector matchAll = SizeSelectors.or( | 
				
			||||||
 | 
					                SizeSelectors.and(matchRatio, matchSize), // Try to respect both constraints.
 | 
				
			||||||
 | 
					                matchSize, // If couldn't match aspect ratio, at least respect the size
 | 
				
			||||||
 | 
					                matchRatio, // If couldn't respect size, at least match aspect ratio
 | 
				
			||||||
 | 
					                SizeSelectors.biggest() // If couldn't match any, take the biggest.
 | 
				
			||||||
 | 
					        ); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Apply the external selector with this as a fallback,
 | 
				
			||||||
 | 
					        // and return a size in REF_SENSOR reference.
 | 
				
			||||||
 | 
					        SizeSelector selector; | 
				
			||||||
 | 
					        if (mPreviewStreamSizeSelector != null) { | 
				
			||||||
 | 
					            selector = SizeSelectors.or(mPreviewStreamSizeSelector, matchAll); | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            selector = matchAll; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        Size result = selector.select(sizes).get(0); | 
				
			||||||
 | 
					        if (!sizes.contains(result)) { | 
				
			||||||
 | 
					            throw new RuntimeException("SizeSelectors must not return Sizes other than " + | 
				
			||||||
 | 
					                    "those in the input list."); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        if (flip) result = result.flip(); | 
				
			||||||
 | 
					        LOG.i("computePreviewStreamSize:", "result:", result, "flip:", flip); | 
				
			||||||
 | 
					        return result; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * This is called anytime {@link #computeFrameProcessingSize()} is called. | 
				
			||||||
 | 
					     * Implementors can return null if frame processor size is not selectable | 
				
			||||||
 | 
					     * @return a list of available sizes for frame processing | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @EngineThread | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    protected abstract List<Size> getFrameProcessingAvailableSizes(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @EngineThread | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @SuppressWarnings("WeakerAccess") | 
				
			||||||
 | 
					    protected final Size computeFrameProcessingSize() { | 
				
			||||||
 | 
					        @NonNull List<Size> frameSizes = getFrameProcessingAvailableSizes(); | 
				
			||||||
 | 
					        // These sizes come in REF_SENSOR. Since there is an external selector involved,
 | 
				
			||||||
 | 
					        // we must convert all of them to REF_VIEW, then flip back when returning.
 | 
				
			||||||
 | 
					        boolean flip = getAngles().flip(Reference.SENSOR, Reference.VIEW); | 
				
			||||||
 | 
					        List<Size> sizes = new ArrayList<>(frameSizes.size()); | 
				
			||||||
 | 
					        for (Size size : frameSizes) { | 
				
			||||||
 | 
					            sizes.add(flip ? size.flip() : size); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        AspectRatio targetRatio = AspectRatio.of( | 
				
			||||||
 | 
					                mPreviewStreamSize.getWidth(), | 
				
			||||||
 | 
					                mPreviewStreamSize.getHeight()); | 
				
			||||||
 | 
					        if (flip) targetRatio = targetRatio.flip(); | 
				
			||||||
 | 
					        int maxWidth = mFrameProcessingMaxWidth; | 
				
			||||||
 | 
					        int maxHeight = mFrameProcessingMaxHeight; | 
				
			||||||
 | 
					        if (maxWidth <= 0 || maxWidth == Integer.MAX_VALUE) maxWidth = 640; | 
				
			||||||
 | 
					        if (maxHeight <= 0 || maxHeight == Integer.MAX_VALUE) maxHeight = 640; | 
				
			||||||
 | 
					        Size targetMaxSize = new Size(maxWidth, maxHeight); | 
				
			||||||
 | 
					        LOG.i("computeFrameProcessingSize:", | 
				
			||||||
 | 
					                "targetRatio:", targetRatio, | 
				
			||||||
 | 
					                "targetMaxSize:", targetMaxSize); | 
				
			||||||
 | 
					        SizeSelector matchRatio = SizeSelectors.aspectRatio(targetRatio, 0); | 
				
			||||||
 | 
					        SizeSelector matchSize = SizeSelectors.and( | 
				
			||||||
 | 
					                SizeSelectors.maxHeight(targetMaxSize.getHeight()), | 
				
			||||||
 | 
					                SizeSelectors.maxWidth(targetMaxSize.getWidth()), | 
				
			||||||
 | 
					                SizeSelectors.biggest()); | 
				
			||||||
 | 
					        SizeSelector matchAll = SizeSelectors.or( | 
				
			||||||
 | 
					                SizeSelectors.and(matchRatio, matchSize), // Try to respect both constraints.
 | 
				
			||||||
 | 
					                matchSize, // If couldn't match aspect ratio, at least respect the size
 | 
				
			||||||
 | 
					                SizeSelectors.smallest() // If couldn't match any, take the smallest.
 | 
				
			||||||
 | 
					        ); | 
				
			||||||
 | 
					        Size result = matchAll.select(sizes).get(0); | 
				
			||||||
 | 
					        if (!sizes.contains(result)) { | 
				
			||||||
 | 
					            throw new RuntimeException("SizeSelectors must not return Sizes other than " + | 
				
			||||||
 | 
					                    "those in the input list."); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        if (flip) result = result.flip(); | 
				
			||||||
 | 
					        LOG.i("computeFrameProcessingSize:", "result:", result, "flip:", flip); | 
				
			||||||
 | 
					        return result; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //endregion
 | 
				
			||||||
 | 
					} | 
				
			||||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						@ -0,0 +1,162 @@ | 
				
			|||||||
 | 
					package com.otaliastudios.cameraview.frame; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.annotation.NonNull; | 
				
			||||||
 | 
					import androidx.annotation.Nullable; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.otaliastudios.cameraview.size.Size; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.concurrent.LinkedBlockingQueue; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** | 
				
			||||||
 | 
					 * This class manages the allocation of byte buffers and {@link Frame} objects. | 
				
			||||||
 | 
					 * We are interested in recycling both of them, especially byte[] buffers which can create a lot | 
				
			||||||
 | 
					 * of overhead. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * The pool size applies to both the {@link Frame} pool and the byte[] pool - it makes sense to use | 
				
			||||||
 | 
					 * the same number since they are consumed at the same time. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * We can work in two modes, depending on whether a | 
				
			||||||
 | 
					 * {@link BufferCallback} is passed to the constructor. The modes changes the buffer behavior. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * 1. {@link #BUFFER_MODE_DISPATCH}: in this mode, as soon as we have a buffer, it is dispatched to | 
				
			||||||
 | 
					 *    the {@link BufferCallback}. The callback should then fill the buffer, and finally call | 
				
			||||||
 | 
					 *    {@link FrameManager#getFrame(Object, long, int)} to receive a frame. | 
				
			||||||
 | 
					 *    This is used for Camera1. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * 2. {@link #BUFFER_MODE_ENQUEUE}: in this mode, the manager internally keeps a queue of byte | 
				
			||||||
 | 
					 *    buffers, instead of handing them to the callback. The users can ask for buffers through | 
				
			||||||
 | 
					 *    {@link #getBuffer()}. | 
				
			||||||
 | 
					 *    This buffer can be filled with data and used to get a frame | 
				
			||||||
 | 
					 *    {@link FrameManager#getFrame(Object, long, int)}, or, in case it was not filled, returned to | 
				
			||||||
 | 
					 *    the queue using {@link #onBufferUnused(byte[])}. | 
				
			||||||
 | 
					 *    This is used for Camera2. | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					public class ByteBufferFrameManager extends FrameManager<byte[]> { | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Receives callbacks on buffer availability | 
				
			||||||
 | 
					     * (when a Frame is released, we reuse its buffer). | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public interface BufferCallback { | 
				
			||||||
 | 
					        void onBufferAvailable(@NonNull byte[] buffer); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * In this mode, we have a {@link #mBufferCallback} and dispatch | 
				
			||||||
 | 
					     * new buffers to the callback. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private final static int BUFFER_MODE_DISPATCH = 0; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * In this mode, we have a {@link #mBufferQueue} where we store | 
				
			||||||
 | 
					     * buffers and only dispatch when requested. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    private final static int BUFFER_MODE_ENQUEUE = 1; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private LinkedBlockingQueue<byte[]> mBufferQueue; | 
				
			||||||
 | 
					    private BufferCallback mBufferCallback; | 
				
			||||||
 | 
					    private final int mBufferMode; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Construct a new frame manager. | 
				
			||||||
 | 
					     * The construction must be followed by an {@link #setUp(int, Size)} call | 
				
			||||||
 | 
					     * as soon as the parameters are known. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @param poolSize the size of the backing pool. | 
				
			||||||
 | 
					     * @param callback a callback | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public ByteBufferFrameManager(int poolSize, @Nullable BufferCallback callback) { | 
				
			||||||
 | 
					        super(poolSize, byte[].class); | 
				
			||||||
 | 
					        if (callback != null) { | 
				
			||||||
 | 
					            mBufferCallback = callback; | 
				
			||||||
 | 
					            mBufferMode = BUFFER_MODE_DISPATCH; | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            mBufferQueue = new LinkedBlockingQueue<>(poolSize); | 
				
			||||||
 | 
					            mBufferMode = BUFFER_MODE_ENQUEUE; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public void setUp(int format, @NonNull Size size) { | 
				
			||||||
 | 
					        super.setUp(format, size); | 
				
			||||||
 | 
					        int bytes = getFrameBytes(); | 
				
			||||||
 | 
					        for (int i = 0; i < getPoolSize(); i++) { | 
				
			||||||
 | 
					            if (mBufferMode == BUFFER_MODE_DISPATCH) { | 
				
			||||||
 | 
					                mBufferCallback.onBufferAvailable(new byte[bytes]); | 
				
			||||||
 | 
					            } else { | 
				
			||||||
 | 
					                mBufferQueue.offer(new byte[bytes]); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Returns a new byte buffer than can be filled. | 
				
			||||||
 | 
					     * This can only be called in {@link #BUFFER_MODE_ENQUEUE} mode! Where the frame | 
				
			||||||
 | 
					     * manager also holds a queue of the byte buffers. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * If not null, the buffer returned by this method can be filled and used to get | 
				
			||||||
 | 
					     * a new frame through {@link FrameManager#getFrame(Object, long, int)}. | 
				
			||||||
 | 
					     * | 
				
			||||||
 | 
					     * @return a buffer, or null | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @Nullable | 
				
			||||||
 | 
					    public byte[] getBuffer() { | 
				
			||||||
 | 
					        if (mBufferMode != BUFFER_MODE_ENQUEUE) { | 
				
			||||||
 | 
					            throw new IllegalStateException("Can't call getBuffer() " + | 
				
			||||||
 | 
					                    "when not in BUFFER_MODE_ENQUEUE."); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        return mBufferQueue.poll(); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Can be called if the buffer obtained by {@link #getBuffer()} | 
				
			||||||
 | 
					     * was not used to construct a frame, so it can be put back into the queue. | 
				
			||||||
 | 
					     * @param buffer a buffer | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    public void onBufferUnused(@NonNull byte[] buffer) { | 
				
			||||||
 | 
					        if (mBufferMode != BUFFER_MODE_ENQUEUE) { | 
				
			||||||
 | 
					            throw new IllegalStateException("Can't call onBufferUnused() " + | 
				
			||||||
 | 
					                    "when not in BUFFER_MODE_ENQUEUE."); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isSetUp()) { | 
				
			||||||
 | 
					            mBufferQueue.offer(buffer); | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            LOG.w("onBufferUnused: buffer was returned but we're not set up anymore."); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    protected void onFrameDataReleased(@NonNull byte[] data, boolean recycled) { | 
				
			||||||
 | 
					        if (recycled && data.length == getFrameBytes()) { | 
				
			||||||
 | 
					            if (mBufferMode == BUFFER_MODE_DISPATCH) { | 
				
			||||||
 | 
					                mBufferCallback.onBufferAvailable(data); | 
				
			||||||
 | 
					            } else { | 
				
			||||||
 | 
					                mBufferQueue.offer(data); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    protected byte[] onCloneFrameData(@NonNull byte[] data) { | 
				
			||||||
 | 
					        byte[] clone = new byte[data.length]; | 
				
			||||||
 | 
					        System.arraycopy(data, 0, clone, 0, data.length); | 
				
			||||||
 | 
					        return clone; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** | 
				
			||||||
 | 
					     * Releases all frames controlled by this manager and | 
				
			||||||
 | 
					     * clears the pool. | 
				
			||||||
 | 
					     * In BUFFER_MODE_ENQUEUE, releases also all the buffers. | 
				
			||||||
 | 
					     */ | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    public void release() { | 
				
			||||||
 | 
					        super.release(); | 
				
			||||||
 | 
					        if (mBufferMode == BUFFER_MODE_ENQUEUE) { | 
				
			||||||
 | 
					            mBufferQueue.clear(); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,30 @@ | 
				
			|||||||
 | 
					package com.otaliastudios.cameraview.frame; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import android.media.Image; | 
				
			||||||
 | 
					import android.os.Build; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.annotation.NonNull; | 
				
			||||||
 | 
					import androidx.annotation.RequiresApi; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@RequiresApi(Build.VERSION_CODES.KITKAT) | 
				
			||||||
 | 
					public class ImageFrameManager extends FrameManager<Image> { | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public ImageFrameManager(int poolSize) { | 
				
			||||||
 | 
					        super(poolSize, Image.class); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    protected void onFrameDataReleased(@NonNull Image data, boolean recycled) { | 
				
			||||||
 | 
					        try { | 
				
			||||||
 | 
					            data.close(); | 
				
			||||||
 | 
					        } catch (Exception ignore) {} | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NonNull | 
				
			||||||
 | 
					    @Override | 
				
			||||||
 | 
					    protected Image onCloneFrameData(@NonNull Image data) { | 
				
			||||||
 | 
					        throw new RuntimeException("Cannot freeze() an Image Frame. " + | 
				
			||||||
 | 
					                "Please consider using the frame synchronously in your process() method, " + | 
				
			||||||
 | 
					                "which also gives better performance."); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,100 +0,0 @@ | 
				
			|||||||
package com.otaliastudios.cameraview.internal.utils; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import android.graphics.ImageFormat; | 
					 | 
				
			||||||
import android.media.Image; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.nio.ByteBuffer; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import androidx.annotation.NonNull; | 
					 | 
				
			||||||
import androidx.annotation.RequiresApi; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** | 
					 | 
				
			||||||
 * Conversions for {@link android.media.Image}s into byte arrays. | 
					 | 
				
			||||||
 */ | 
					 | 
				
			||||||
@RequiresApi(19) | 
					 | 
				
			||||||
public class ImageHelper { | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** | 
					 | 
				
			||||||
     * From https://stackoverflow.com/a/52740776/4288782 .
 | 
					 | 
				
			||||||
     * The result array should have a size that is at least 3/2 * w * h. | 
					 | 
				
			||||||
     * This is correctly computed by {@link com.otaliastudios.cameraview.frame.FrameManager}. | 
					 | 
				
			||||||
     * | 
					 | 
				
			||||||
     * @param image input image | 
					 | 
				
			||||||
     * @param result output array | 
					 | 
				
			||||||
     */ | 
					 | 
				
			||||||
    public static void convertToNV21(@NonNull Image image, @NonNull byte[] result) { | 
					 | 
				
			||||||
        if (image.getFormat() != ImageFormat.YUV_420_888) { | 
					 | 
				
			||||||
            throw new IllegalStateException("CAn only convert from YUV_420_888."); | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
        int width = image.getWidth(); | 
					 | 
				
			||||||
        int height = image.getHeight(); | 
					 | 
				
			||||||
        int ySize = width * height; | 
					 | 
				
			||||||
        int uvSize = width * height / 4; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ByteBuffer yBuffer = image.getPlanes()[0].getBuffer(); // Y
 | 
					 | 
				
			||||||
        ByteBuffer uBuffer = image.getPlanes()[1].getBuffer(); // U
 | 
					 | 
				
			||||||
        ByteBuffer vBuffer = image.getPlanes()[2].getBuffer(); // V
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        int rowStride = image.getPlanes()[0].getRowStride(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (image.getPlanes()[0].getPixelStride() != 1) { | 
					 | 
				
			||||||
            throw new AssertionError("Something wrong in convertToNV21"); | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        int pos = 0; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (rowStride == width) { // likely
 | 
					 | 
				
			||||||
            yBuffer.get(result, 0, ySize); | 
					 | 
				
			||||||
            pos += ySize; | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
        else { | 
					 | 
				
			||||||
            int yBufferPos = width - rowStride; // not an actual position
 | 
					 | 
				
			||||||
            for (; pos<ySize; pos+=width) { | 
					 | 
				
			||||||
                yBufferPos += rowStride - width; | 
					 | 
				
			||||||
                yBuffer.position(yBufferPos); | 
					 | 
				
			||||||
                yBuffer.get(result, pos, width); | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        rowStride = image.getPlanes()[2].getRowStride(); | 
					 | 
				
			||||||
        int pixelStride = image.getPlanes()[2].getPixelStride(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (rowStride != image.getPlanes()[1].getRowStride()) { | 
					 | 
				
			||||||
            throw new AssertionError("Something wrong in convertToNV21"); | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
        if (pixelStride != image.getPlanes()[1].getPixelStride()) { | 
					 | 
				
			||||||
            throw new AssertionError("Something wrong in convertToNV21"); | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (pixelStride == 2 && rowStride == width && uBuffer.get(0) == vBuffer.get(1)) { | 
					 | 
				
			||||||
            // maybe V an U planes overlap as per NV21, which means vBuffer[1]
 | 
					 | 
				
			||||||
            // is alias of uBuffer[0]
 | 
					 | 
				
			||||||
            byte savePixel = vBuffer.get(1); | 
					 | 
				
			||||||
            vBuffer.put(1, (byte)0); | 
					 | 
				
			||||||
            if (uBuffer.get(0) == 0) { | 
					 | 
				
			||||||
                vBuffer.put(1, (byte)255); | 
					 | 
				
			||||||
                //noinspection ConstantConditions
 | 
					 | 
				
			||||||
                if (uBuffer.get(0) == 255) { | 
					 | 
				
			||||||
                    vBuffer.put(1, savePixel); | 
					 | 
				
			||||||
                    vBuffer.get(result, ySize, uvSize); | 
					 | 
				
			||||||
                    return; // shortcut
 | 
					 | 
				
			||||||
                } | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // unfortunately, the check failed. We must save U and V pixel by pixel
 | 
					 | 
				
			||||||
            vBuffer.put(1, savePixel); | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // other optimizations could check if (pixelStride == 1) or (pixelStride == 2),
 | 
					 | 
				
			||||||
        // but performance gain would be less significant
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (int row=0; row<height/2; row++) { | 
					 | 
				
			||||||
            for (int col=0; col<width/2; col++) { | 
					 | 
				
			||||||
                int vuPos = col*pixelStride + row*rowStride; | 
					 | 
				
			||||||
                result[pos++] = vBuffer.get(vuPos); | 
					 | 
				
			||||||
                result[pos++] = uBuffer.get(vuPos); | 
					 | 
				
			||||||
            } | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue