Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 075f53ce authored by Mark Harman's avatar Mark Harman
Browse files

Support for starting camera preview on background thread without waiting (not yet enabled).

parent 37e5fb48
Loading
Loading
Loading
Loading
+10 −2
Original line number Diff line number Diff line
@@ -3461,6 +3461,11 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen
        if( !update_camera ) {
            // don't try to update camera
        }
        else if( preview.isPreviewStarting() ) {
            // ideally we should avoid getting into this state - we shouldn't allow changing settings if preview is still opening (e.g., we prevent opening
            // the popup menu if preview is not started, and we close camera when going to settings)
            Log.e(TAG, "updateForSettings: preview is still starting");
        }
        else if( need_reopen || preview.getCameraController() == null ) { // if camera couldn't be opened before, might as well try again
            if( allow_dim )
                applicationInterface.getDrawPreview().setDimPreview(true);
@@ -3486,7 +3491,10 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen
            // Even if allow_dim==false, still run as a postDelayed (a) for consistency, (b) to allow UI to run for a bit (to avoid risk of slow frames).
            handler.postDelayed(new Runnable() {
                public void run() {
                    preview.setupCamera(false);
                    // if we ever support calling this with wait_until_started==true, beware of trying to change resolution repeatedly when the popup menu
                    // is open - as currently we'll have problems if trying to calling updateForSettings when preview is already starting on background
                    // thread (see above)
                    preview.setupCamera(false, true, null);
                }
            }, DrawPreview.dim_effect_time_c+16); // +16 to allow time for a frame update to run
        }
@@ -3683,7 +3691,7 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen
                // starting the preview will disable the PausePreviewOnBackPressedCallback, so no need to do it here
                if( MyDebug.LOG )
                    Log.d(TAG, "preview was paused, so unpause it");
                preview.startCameraPreview();
                preview.startCameraPreview(true, null);
            }
            else {
                // shouldn't be here (if preview isn't paused, this callback shouldn't be enabled), but just in case
+2 −2
Original line number Diff line number Diff line
@@ -3833,7 +3833,7 @@ public class MyApplicationInterface extends BasicApplicationInterface {
            }
            if( done ) {
                clearLastImages();
                preview.startCameraPreview();
                preview.startCameraPreview(true, null);
            }
        }
    }
@@ -3907,7 +3907,7 @@ public class MyApplicationInterface extends BasicApplicationInterface {
            }
            clearLastImages();
            drawPreview.clearGhostImage(); // doesn't make sense to show the last image as a ghost, if the user has trashed it!
            preview.startCameraPreview();
            preview.startCameraPreview(true, null);
        }
        // Calling updateGalleryIcon() immediately has problem that it still returns the latest image that we've just deleted!
        // But works okay if we call after a delay. 100ms works fine on Nexus 7 and Galaxy Nexus, but set to 500 just to be safe.
+19 −1
Original line number Diff line number Diff line
@@ -660,7 +660,25 @@ public abstract class CameraController {
    /** Starts the camera preview.
     *  @throws CameraControllerException if the camera preview fails to start.
     */
    public abstract void startPreview() throws CameraControllerException;
    /** Starts the camera preview.
     * @param wait_until_started Whether to wait until the preview is started. Only relevant for
     *                           CameraController2; CameraController1 will always wait.
     * @param runnable           If non-null, a runnable to be called once preview is started. If
     *                           wait_until_started==true, or using CameraController1, this will be
     *                           called on the current thread, before this method exits. Otherwise,
     *                           this will be called on the UI thread, after this method exits (once
     *                           the preview has started).
     * @param on_failed          If non-null, a runnable to be called if the preview fails to start.
     *                           Only relevant for wait_until_started==false and when using
     *                           CameraController2. In such cases, failing to start the camera preview
     *                           may result in either CameraControllerException being thrown, or
     *                           on_failed being called on the UI thread after this method exits
     *                           (depending on when the failure occurs). If either of these happens,
     *                           the "runnable" runnable will not be called.
     * @throws CameraControllerException Failed to start preview. In this case, the runnable will not
     *                                   be called.
     */
    public abstract void startPreview(boolean wait_until_started, Runnable runnable, Runnable on_failed) throws CameraControllerException;
    /** Only relevant for CameraController2: stops the repeating burst for the previous (so effectively
     *  stops the preview), but does not close the capture session for the preview (for that, using
     *  stopPreview() instead of stopRepeating()).
+5 −2
Original line number Diff line number Diff line
@@ -1577,7 +1577,7 @@ public class CameraController1 extends CameraController {
    }

    @Override
    public void startPreview() throws CameraControllerException {
    public void startPreview(boolean wait_until_started, Runnable runnable, Runnable on_failed) throws CameraControllerException {
        if( MyDebug.LOG )
            Log.d(TAG, "startPreview");
        try {
@@ -1589,6 +1589,9 @@ public class CameraController1 extends CameraController {
            e.printStackTrace();
            throw new CameraControllerException();
        }
        if( runnable != null ) {
            runnable.run();
        }
    }

    @Override
@@ -1838,7 +1841,7 @@ public class CameraController1 extends CameraController {
                        // need to start preview again: otherwise fail to take subsequent photos on Nexus 6
                        // and Nexus 7; on Galaxy Nexus we succeed, but exposure compensation has no effect
                        try {
                            startPreview();
                            startPreview(true, null, null);
                        }
                        catch(CameraControllerException e) {
                            if( MyDebug.LOG )
+242 −53
Original line number Diff line number Diff line
@@ -2451,12 +2451,22 @@ public class CameraController2 extends CameraController {
        if( MyDebug.LOG )
            Log.d(TAG, "release: " + this);
        closeCaptureSession();
        CameraDevice camera_to_close = this.camera;
        synchronized( background_camera_lock ) {
            // set all to null straight away, as this can be called on background thread, but also
            // don't want to be in an incomplete state for other threads where camera is non-null but
            // previewBuilder is null
            previewBuilder = null;
            previewIsVideoMode = false;
        if( camera != null ) {
            camera.close();
            camera = null;
        }
        if( camera_to_close != null ) {
            if( MyDebug.LOG )
                Log.d(TAG, "close camera: " + camera_to_close);
            camera_to_close.close();
            if( MyDebug.LOG )
                Log.d(TAG, "close camera complete: " + camera_to_close);
        }
        closePictureImageReader();
        /*if( previewImageReader != null ) {
            previewImageReader.close();
@@ -2476,6 +2486,8 @@ public class CameraController2 extends CameraController {
                e.printStackTrace();
            }
        }
        if( MyDebug.LOG )
            Log.d(TAG, "release exit: " + this);
    }

    /** Enforce a minimum number of points in tonemap curves - needed due to Galaxy S10e having wrong behaviour if fewer
@@ -6210,7 +6222,60 @@ public class CameraController2 extends CameraController {
        return outputs;
    }

    private void createCaptureSession(final MediaRecorder video_recorder, boolean want_photo_video_recording) throws CameraControllerException {
    private abstract static class CreateCaptureSessionFunction {
        public abstract void call() throws CameraAccessException;
    }

    /** Function to support calling a function either on background thread or not depending on wait_until_started.
     */
    private void launchCameraSession(boolean wait_until_started, CreateCaptureSessionFunction function, Runnable on_failed) throws CameraAccessException {
        if( wait_until_started ) {
            /*try {
                Thread.sleep(6000); // test slow to start preview
                //Thread.sleep(25000); // test slow to start preview
            }
            catch(InterruptedException e) {
                throw new RuntimeException(e);
            }*/
            function.call();
        }
        else {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    try {
                        /*try {
                            Thread.sleep(6000); // test slow to start preview
                            //Thread.sleep(25000); // test slow to start preview
                        }
                        catch(InterruptedException e) {
                            throw new RuntimeException(e);
                        }*/
                        function.call();
                    }
                    catch(CameraAccessException e) {
                        Log.e(TAG, "exception create extension session on background thread");
                        e.printStackTrace();
                        //myStateCallback.onConfigureFailed();
                        if( on_failed != null ) {
                            // if waiting, failure will be indicated via CameraControllerException thrown from this method
                            final Activity activity = (Activity)context;
                            activity.runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    if( MyDebug.LOG )
                                        Log.d(TAG, "call on_failed as preview failed to start");
                                    on_failed.run();
                                }
                            });
                        }
                    }
                }
            });
        }
    }

    private void createCaptureSession(boolean wait_until_started, Runnable runnable, Runnable on_failed, final MediaRecorder video_recorder, boolean want_photo_video_recording) throws CameraControllerException {
        if( MyDebug.LOG )
            Log.d(TAG, "create capture session");
        
@@ -6327,18 +6392,46 @@ public class CameraController2 extends CameraController {
            class MyStateCallback extends CameraCaptureSession.StateCallback {
                private boolean callback_done; // must synchronize on this and notifyAll when setting to true

                private void onFailure() {
                    if( on_failed != null && !wait_until_started ) {
                        // if waiting, failure will be indicated on main thread below via CameraControllerException
                        final Activity activity = (Activity)context;
                        activity.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                if( MyDebug.LOG )
                                    Log.d(TAG, "call on_failed as preview failed to start");
                                on_failed.run();
                            }
                        });
                    }
                }

                void onConfigured(@NonNull CameraCaptureSession session, @NonNull CameraExtensionSession eSession) {
                    boolean success = false; // whether we successfully started the preview
                    /*try {
                        Thread.sleep(6000); // test slow to start preview
                        //Thread.sleep(25000); // test slow to start preview
                    }
                    catch(InterruptedException e) {
                        throw new RuntimeException(e);
                    }*/
                    synchronized( background_camera_lock ) {
                        if( camera == null ) {
                            if( MyDebug.LOG ) {
                                Log.d(TAG, "camera is closed");
                            }
                        synchronized( background_camera_lock ) {
                            callback_done = true;
                            background_camera_lock.notifyAll();
                        }
                            // don't call onFailure() - if camera has closed in the meantime, no need to report to user (e.g. this might be going to Settings
                            // whilst preview was starting)
                            return;
                        }
                    synchronized( background_camera_lock ) {

                        if( MyDebug.LOG ) {
                            Log.d(TAG, "camera: " + camera);
                            Log.d(TAG, "previewBuilder: " + previewBuilder);
                        }
                        captureSession = session;
                        extensionSession = eSession;
                        previewBuilder.addTarget(surface_texture);
@@ -6350,6 +6443,7 @@ public class CameraController2 extends CameraController {
                        }
                        try {
                            setRepeatingRequest();
                            success = true;
                        }
                        catch(CameraAccessException e) {
                            if( MyDebug.LOG ) {
@@ -6359,7 +6453,8 @@ public class CameraController2 extends CameraController {
                            }
                            e.printStackTrace();
                            // we indicate that we failed to start the preview by setting captureSession back to null
                            // this will cause a CameraControllerException to be thrown below
                            // this will cause a CameraControllerException to be thrown below (if wait_until_started==true),
                            // or via the on_failed callback (if wait_until_started==false)
                            captureSession = null;
                            extensionSession = null;
                        }
@@ -6368,6 +6463,31 @@ public class CameraController2 extends CameraController {
                        callback_done = true;
                        background_camera_lock.notifyAll();
                    }
                    if( success && runnable != null && !wait_until_started ) {
                        // if not waiting, we run the runnable on UI thread now that preview is started
                        final Activity activity = (Activity)context;
                        activity.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                if( MyDebug.LOG )
                                    Log.d(TAG, "call runnable as preview now started");
                                synchronized( background_camera_lock ) {
                                    if( camera == null ) {
                                        if( MyDebug.LOG ) {
                                            Log.d(TAG, "but camera is closed in the meantime");
                                        }
                                        // don't call onFailure() - if camera has closed in the meantime, no need to report to user (e.g. this might be going to Settings
                                        // whilst preview was starting)
                                        return;
                                    }
                                }
                                runnable.run();
                            }
                        });
                    }
                    else if( !success ) {
                        onFailure();
                    }
                }

                @Override
@@ -6383,7 +6503,9 @@ public class CameraController2 extends CameraController {
                        callback_done = true;
                        background_camera_lock.notifyAll();
                    }
                    // don't throw CameraControllerException here, as won't be caught - instead we throw CameraControllerException below
                    onFailure();
                    // don't throw CameraControllerException here, as won't be caught - instead we throw CameraControllerException below (if wait_until_started==true),
                    // or via the on_failed callback (if wait_until_started==false)
                }

                @Override
@@ -6512,8 +6634,18 @@ public class CameraController2 extends CameraController {
                                }
                            }
                    );
                    launchCameraSession(wait_until_started, new CreateCaptureSessionFunction() {
                        @Override
                        public void call() throws CameraAccessException {
                            if( camera == null ) {
                                // just in case - don't throw exception as we don't want to show error toast, as it may be that another request to start preview is already active
                                Log.e(TAG, "camera is no longer open");
                                return;
                            }
                            camera.createExtensionSession(extensionConfiguration);
                        }
                    }, on_failed);
                }
                is_video_high_speed = false;
            }
            else if( video_recorder != null && want_video_high_speed && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ) {
@@ -6523,13 +6655,33 @@ public class CameraController2 extends CameraController {
                if( ( cameraIdSPhysical != null || want_jpeg_r ) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ) {
                    List<OutputConfiguration> outputs = createOutputConfigurationList(surfaces, preview_surface);
                    SessionConfiguration sessionConfiguration = new SessionConfiguration(SessionConfiguration.SESSION_HIGH_SPEED, outputs, executor, myStateCallback);
                    launchCameraSession(wait_until_started, new CreateCaptureSessionFunction() {
                        @Override
                        public void call() throws CameraAccessException {
                            if( camera == null ) {
                                // just in case - don't throw exception as we don't want to show error toast, as it may be that another request to start preview is already active
                                Log.e(TAG, "camera is no longer open");
                                return;
                            }
                            camera.createCaptureSession(sessionConfiguration);
                        }
                    }, on_failed);
                }
                else {
                    launchCameraSession(wait_until_started, new CreateCaptureSessionFunction() {
                        @Override
                        public void call() throws CameraAccessException {
                            if( camera == null ) {
                                // just in case - don't throw exception as we don't want to show error toast, as it may be that another request to start preview is already active
                                Log.e(TAG, "camera is no longer open");
                                return;
                            }
                            camera.createConstrainedHighSpeedCaptureSession(surfaces,
                                    myStateCallback,
                                    handler);
                        }
                    }, on_failed);
                }
                is_video_high_speed = true;
            }
            else {
@@ -6542,13 +6694,35 @@ public class CameraController2 extends CameraController {
                                myStateCallback,
                                handler);*/
                        SessionConfiguration sessionConfiguration = new SessionConfiguration(SessionConfiguration.SESSION_REGULAR, outputs, executor, myStateCallback);
                        launchCameraSession(wait_until_started, new CreateCaptureSessionFunction() {
                            @Override
                            public void call() throws CameraAccessException {
                                if( camera == null ) {
                                    // just in case - don't throw exception as we don't want to show error toast, as it may be that another request to start preview is already active
                                    Log.e(TAG, "camera is no longer open");
                                    return;
                                }
                                camera.createCaptureSession(sessionConfiguration);
                            }
                        }, on_failed);
                    }
                    else {
                        launchCameraSession(wait_until_started, new CreateCaptureSessionFunction() {
                            @Override
                            public void call() throws CameraAccessException {
                                /*if( true )
                                    throw new CameraAccessException(CameraAccessException.CAMERA_ERROR); // test*/
                                if( camera == null ) {
                                    // just in case - don't throw exception as we don't want to show error toast, as it may be that another request to start preview is already active
                                    Log.e(TAG, "camera is no longer open");
                                    return;
                                }
                                camera.createCaptureSession(surfaces,
                                        myStateCallback,
                                        handler);
                            }
                        }, on_failed);
                    }
                    is_video_high_speed = false;
                }
                catch(NullPointerException e) {
@@ -6563,6 +6737,8 @@ public class CameraController2 extends CameraController {
                    throw new CameraControllerException();
                }
            }

            if( wait_until_started ) {
                if( MyDebug.LOG )
                    Log.d(TAG, "wait until session created...");
                // n.b., we use the background_camera_lock lock instead of a separate lock, so that it's safe to call this
@@ -6593,6 +6769,16 @@ public class CameraController2 extends CameraController {
                        throw new CameraControllerException();
                    }
                }

                if( runnable != null ) {
                    runnable.run();
                }
            }
            else {
                if( MyDebug.LOG )
                    Log.d(TAG, "NOT waiting until session created");
                // runnable is instead run from callback once preview is started
            }
        }
        catch(CameraAccessException e) {
            if( MyDebug.LOG ) {
@@ -6616,7 +6802,7 @@ public class CameraController2 extends CameraController {
    }

    @Override
    public void startPreview() throws CameraControllerException {
    public void startPreview(boolean wait_until_started, Runnable runnable, Runnable on_failed) throws CameraControllerException {
        if( MyDebug.LOG )
            Log.d(TAG, "startPreview");

@@ -6648,10 +6834,13 @@ public class CameraController2 extends CameraController {
                    // do via CameraControllerException instead of preview_error_cb, so caller immediately knows preview has failed
                    throw new CameraControllerException();
                }
                if( runnable != null ) {
                    runnable.run();
                }
                return;
            }
        }
        createCaptureSession(null, false);
        createCaptureSession(wait_until_started, runnable, on_failed, null, false);
    }

    @Override
@@ -8446,7 +8635,7 @@ public class CameraController2 extends CameraController {
            previewIsVideoMode = true;
            previewBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
            camera_settings.setupBuilder(previewBuilder, false);
            createCaptureSession(video_recorder, want_photo_video_recording);
            createCaptureSession(true, null, null, video_recorder, want_photo_video_recording);
        }
        catch(CameraAccessException e) {
            if( MyDebug.LOG ) {
@@ -8466,7 +8655,7 @@ public class CameraController2 extends CameraController {
        // if we change where we play the STOP_VIDEO_RECORDING sound, make sure it can't be heard in resultant video
        playSound(MediaActionSound.STOP_VIDEO_RECORDING);
        createPreviewRequest();
        createCaptureSession(null, false);
        createCaptureSession(true, null, null, null, false);
        /*if( MyDebug.LOG )
            Log.d(TAG, "add preview surface to previewBuilder");
        Surface surface = getPreviewSurface();
Loading