Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Camera live preview freezes on camera switch

Tags:

android

camera

I am creating a custom camera app. The problem I am facing is that the camera preview freezes when I switch between the front and the back camera. When starting the activity with either the front or back camera by calling

boolean opened = safeCameraOpenInView(view, Camera.CameraInfo.CAMERA_FACING_BACK)

in the OnCreateView method of the fragment, both cameras will display on startup as expected. As soon as I call the same method in the on click listener of my switch button, the camera freezes immediately.

This is a new implementation which I tried where all the code is within the same fragment instead of a custom class based on the question here: Custom class camera live preview freezes on camera switch yet the result is exactly the same. I am pretty sure I need to do something with the surface view to bind it to the new camera but I am at a loss of how to do this. Anybody have some pointers?

My activity:

public class Camera2ActivityFragment extends Fragment {

    // Native camera.
    private Camera mCamera;


    // View to display the camera output.
    private CameraPreview mPreview;

    // Reference to the containing view.
    private View mCameraView;

    /**
     * Default empty constructor.
     */
    public Camera2ActivityFragment(){
        super();
    }

    /**
     * Static factory method
     * @param sectionNumber
     * @return
     */
    public static Camera2ActivityFragment newInstance(int sectionNumber) {
        Camera2ActivityFragment fragment = new Camera2ActivityFragment();
        //Bundle args = new Bundle();
        //args.putInt(ARG_SECTION_NUMBER, sectionNumber);
        //fragment.setArguments(args);
        return fragment;
    }

    /**
     * OnCreateView fragment override
     * @param inflater
     * @param container
     * @param savedInstanceState
     * @return
     */
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_camera2, container, false);
        boolean opened = safeCameraOpenInView(view, Camera.CameraInfo.CAMERA_FACING_BACK);

        if(opened == false){
            Log.d("CameraGuide","Error, Camera failed to open");
            return view;
        }

        // Trap the capture button.
        Button captureButton = (Button) view.findViewById(R.id.btnCameraStart);
        captureButton.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        // get an image from the camera
                        mCamera.takePicture(null, null, mPicture);
                    }
                }
        );

        Button switchCameraButton = (Button) view.findViewById(R.id.btnSwitchCamera);
        switchCameraButton.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        safeCameraOpenInView(getView(), Camera.CameraInfo.CAMERA_FACING_FRONT); //ISSUE OCCURS HERE!
                    }
                }
        );

        return view;
    }

    /**
     * Recommended "safe" way to open the camera.
     * @param view
     * @return
     */
    private boolean safeCameraOpenInView(View view, int camID) {
        boolean qOpened = false;
        releaseCameraAndPreview();
        //mCamera = getCameraInstance(Camera.CameraInfo.CAMERA_FACING_BACK);
        mCamera = getCameraInstance(camID);
        mCameraView = view;
        qOpened = (mCamera != null);

        if(qOpened == true){
            mPreview = new CameraPreview(getActivity().getBaseContext(), mCamera,view);
            FrameLayout preview = (FrameLayout) view.findViewById(R.id.camera_view);
            preview.addView(mPreview);
            mPreview.startCameraPreview();
        }
        return qOpened;
    }

    /**
     * Safe method for getting a camera instance.
     * @return
     */
    public static Camera getCameraInstance(int camID){
        Camera c = null;
        try {
            c = Camera.open(camID); // attempt to get a Camera instance
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return c; // returns null if camera is unavailable
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        releaseCameraAndPreview();
    }

    /**
     * Clear any existing preview / camera.
     */
    private void releaseCameraAndPreview() {

        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
        if(mPreview != null){
            mPreview.destroyDrawingCache();
            mPreview.mCamera = null;
        }
    }

    /**
     * Surface on which the camera projects it's capture results. This is derived both from Google's docs and the
     * excellent StackOverflow answer provided below.
     *
     * Reference / Credit: https://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
     */
    class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

        // SurfaceHolder
        private SurfaceHolder mHolder;

        // Our Camera.
        private Camera mCamera;

        // Parent Context.
        private Context mContext;

        // Camera Sizing (For rotation, orientation changes)
        private Camera.Size mPreviewSize;

        // List of supported preview sizes
        private List<Camera.Size> mSupportedPreviewSizes;

        // Flash modes supported by this camera
        private List<String> mSupportedFlashModes;

        // View holding this camera.
        private View mCameraView;

        public CameraPreview(Context context, Camera camera, View cameraView) {
            super(context);

            // Capture the context
            mCameraView = cameraView;
            mContext = context;
            setCamera(camera);

            // Install a SurfaceHolder.Callback so we get notified when the
            // underlying surface is created and destroyed.
            mHolder = getHolder();
            mHolder.addCallback(this);
            mHolder.setKeepScreenOn(true);
            // deprecated setting, but required on Android versions prior to 3.0
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }

        /**
         * Begin the preview of the camera input.
         */
        public void startCameraPreview()
        {
            try{
                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();
            }
            catch(Exception e){
                e.printStackTrace();
            }
        }

        /**
         * Extract supported preview and flash modes from the camera.
         * @param camera
         */
        private void setCamera(Camera camera)
        {
            // Source: https://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
            mCamera = camera;
            mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
            mSupportedFlashModes = mCamera.getParameters().getSupportedFlashModes();

            // Set the camera to Auto Flash mode.
            if (mSupportedFlashModes != null && mSupportedFlashModes.contains(Camera.Parameters.FLASH_MODE_AUTO)){
                Camera.Parameters parameters = mCamera.getParameters();
                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
                parameters.setRotation(90);
                //parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
                mCamera.setParameters(parameters);
                mCamera.setDisplayOrientation(90);
            }

            requestLayout();
        }

        /**
         * The Surface has been created, now tell the camera where to draw the preview.
         * @param holder
         */
        public void surfaceCreated(SurfaceHolder holder) {
            try {
                mCamera.setPreviewDisplay(holder);

                //mCam = Camera.open();
                //mCam.setPreviewDisplay(holder);

            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /**
         * Dispose of the camera preview.
         * @param holder
         */
        public void surfaceDestroyed(SurfaceHolder holder) {
            if (mCamera != null){
                mCamera.stopPreview();
            }
        }

        /**
         * React to surface changed events
         * @param holder
         * @param format
         * @param w
         * @param h
         */
        public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

            //Log.e(TAG, "surfaceChanged => w=" + w + ", h=" + h);
            // If your preview can change or rotate, take care of those events here.
            // Make sure to stop the preview before resizing or reformatting it.
            if (mHolder.getSurface() == null){
                // preview surface does not exist
                return;
            }

            // stop preview before making changes
            try {
                mCamera.stopPreview();
                //mCamera.release();
            } catch (Exception e){
                // ignore: tried to stop a non-existent preview
            }

            // set preview size and make any resize, rotate or reformatting changes here
            // start preview with new settings
            try {
                Camera.Parameters parameters = mCamera.getParameters();
                parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
                parameters.setRotation(90);
                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
                mCamera.setParameters(parameters);
                mCamera.setDisplayOrientation(90);
                mCamera.setPreviewDisplay(mHolder);
                mCamera.enableShutterSound(true);
                mCamera.startPreview();

            } catch (Exception e){
                //Log.d(TAG, "Error starting camera preview: " + e.getMessage());
            }
        }

        /**
         * Calculate the measurements of the layout
         * @param widthMeasureSpec
         * @param heightMeasureSpec
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
        {
            /*
            // Source: https://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
            final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
            final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);
            setMeasuredDimension(width, height);

            if (mSupportedPreviewSizes != null){
                mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
            }
            */
            final int width = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec);
            final int height = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec);

            if (mSupportedPreviewSizes != null) {
                mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
            }

            float ratio;
            if(mPreviewSize.height >= mPreviewSize.width)
                ratio = (float) mPreviewSize.height / (float) mPreviewSize.width;
            else
                ratio = (float) mPreviewSize.width / (float) mPreviewSize.height;

            // One of these methods should be used, second method squishes preview slightly
            setMeasuredDimension(width, (int) (width * ratio));
//        setMeasuredDimension((int) (width * ratio), height);
        }

        /**
         * Update the layout based on rotation and orientation changes.
         * @param changed
         * @param left
         * @param top
         * @param right
         * @param bottom
         */
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom)
        {
            // Source: https://stackoverflow.com/questions/7942378/android-camera-will-not-work-startpreview-fails
            if (changed) {
                final int width = right - left;
                final int height = bottom - top;

                int previewWidth = width;
                int previewHeight = height;

                if (mPreviewSize != null){
                    Display display = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();

                    switch (display.getRotation())
                    {
                        case Surface.ROTATION_0:
                            previewWidth = mPreviewSize.height;
                            previewHeight = mPreviewSize.width;
                            mCamera.setDisplayOrientation(90);
                            break;
                        case Surface.ROTATION_90:
                            previewWidth = mPreviewSize.width;
                            previewHeight = mPreviewSize.height;
                            break;
                        case Surface.ROTATION_180:
                            previewWidth = mPreviewSize.height;
                            previewHeight = mPreviewSize.width;
                            break;
                        case Surface.ROTATION_270:
                            previewWidth = mPreviewSize.width;
                            previewHeight = mPreviewSize.height;
                            mCamera.setDisplayOrientation(180);
                            break;
                    }
                }

                final int scaledChildHeight = previewHeight * width / previewWidth;
                mCameraView.layout(0, height - scaledChildHeight, width, height);
            }
        }

        /**
         *
         * @param sizes
         * @param width
         * @param height
         * @return
         */
        private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int width, int height)
        {
            final double ASPECT_TOLERANCE = 0.1;
            double targetRatio = (double) height / width;

            if (sizes == null)
                return null;

            Camera.Size optimalSize = null;
            double minDiff = Double.MAX_VALUE;

            int targetHeight = height;

            for (Camera.Size size : sizes) {
                double ratio = (double) size.height / size.width;
                if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
                    continue;

                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }

            if (optimalSize == null) {
                minDiff = Double.MAX_VALUE;
                for (Camera.Size size : sizes) {
                    if (Math.abs(size.height - targetHeight) < minDiff) {
                        optimalSize = size;
                        minDiff = Math.abs(size.height - targetHeight);
                    }
                }
            }

            return optimalSize;
        }
    }

    /**
     * Picture Callback for handling a picture capture and saving it out to a file.
     */
    private Camera.PictureCallback mPicture = new Camera.PictureCallback() {

        @Override
        public void onPictureTaken(byte[] data, Camera camera) {

            File pictureFile = getOutputMediaFile();
            if (pictureFile == null){
                Toast.makeText(getActivity(), "Image retrieval failed.", Toast.LENGTH_SHORT)
                        .show();
                return;
            }

            try {
                FileOutputStream fos = new FileOutputStream(pictureFile);
                fos.write(data);
                fos.close();

            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };

    /**
     * Used to return the camera File output.
     * @return
     */
    private File getOutputMediaFile(){

        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), "Pocket Booth");

        if (! mediaStorageDir.exists()){
            if (! mediaStorageDir.mkdirs()){
                Log.d("Camera Guide", "Required media storage does not exist");
                return null;
            }
        }

        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        File mediaFile;
        mediaFile = new File(mediaStorageDir.getPath() + File.separator +
                "IMG_"+ timeStamp + ".jpg");

        //DialogHelper.showDialog( "Success!","Your picture has been saved!",getActivity());

        return mediaFile;
    }
}
like image 453
Joachim Prinsloo Avatar asked Jun 15 '16 06:06

Joachim Prinsloo


2 Answers

Well, Here I'm going to write a tutorial for you about capturing an image using Camera by enabling some common features of camera.

Step 1 : Create a preview class

/**
 * A basic Camera preview class
 */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private static final String TAG = "CameraPreview";
    private SurfaceHolder mHolder;
    private Camera mCamera;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (mHolder.getSurface() == null) {
            // preview surface does not exist
            return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e) {
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        startPreview();
    }

    public void resetCamera(Camera camera) {
        mCamera = camera;
    }

    public void startPreview() {
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();
        } catch (Exception e) {
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}

Step 2 : Use FrameLayout to hold the preview.

 <FrameLayout
        android:id="@+id/cameraPreview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

Step 3 : Get the camera and send to the preview class. you may need to set the parameters that you need prior passing the camera.

   /**
     * Create our Preview view and set it as the content of UI.
     */
    private void initCameraPreview(final int cameraId, final boolean createPreview) {
        mCamera = getCameraInstance(cameraId);
        setupCameraParameters(cameraId);

        if (createPreview) {
            mPreview = new CameraPreview(this, mCamera);
            mPreviewHolder.addView(mPreview);
        }
        mReadyToCapture = true;
    }



   /**
     * A safe way to get an instance of the Camera object.
     */
    private Camera getCameraInstance(int cameraId) {
        Camera c = null;
        try {
            c = Camera.open(cameraId); // attempt to get a Camera instance
        } catch (Exception e) {
            e.printStackTrace();
            // Camera is not available (in use or does not exist)
        }
        return c; // returns null if camera is unavailable
    }


   /**
     * Measure and Setup the camera parameters.
     */
    private void setupCameraParameters(int cameraId) {
        boolean hasFlash;

        Camera.Parameters parameters = mCamera.getParameters();

        mPreviewSize = determineBestPreviewSize(parameters);
        parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);

        Camera.Size bestPictureSize = determineBestPictureSize(parameters);
        parameters.setPictureSize(bestPictureSize.width, bestPictureSize.height);

        hasFlash = Util.hasSystemFeature(this, PackageManager.FEATURE_CAMERA_FLASH);
        if (mCurrentCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            hasFlash = Util.hasFrontCameraFlash(parameters);
        } else {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        }
        if (hasFlash)
            parameters.setFlashMode(mFlashMode);

        int[] orientations = Util.getCameraDisplayOrientation(this, cameraId);

        mDisplayOrientation = orientations[0];
        mLayoutOrientation = orientations[1];

        mCamera.setDisplayOrientation(mDisplayOrientation);

        mCamera.setParameters(parameters);
    }

    private Camera.Size determineBestPreviewSize(Camera.Parameters parameters) {
        List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
        return determineBestSize(sizes);
    }

    private Camera.Size determineBestPictureSize(Camera.Parameters parameters) {
        List<Camera.Size> sizes = parameters.getSupportedPictureSizes();
        return determineBestSize(sizes);
    }

    private Camera.Size determineBestSize(List<Camera.Size> sizes) {
        Camera.Size bestSize = null;

        for (Camera.Size currentSize : sizes) {
            boolean isDesiredRatio = (currentSize.width / 4) == (currentSize.height / 3);
            boolean isBetterSize = (bestSize == null || currentSize.width > bestSize.width);
            boolean isInBounds = currentSize.width <= PICTURE_SIZE_MAX_WIDTH;
            if (isDesiredRatio && isInBounds && isBetterSize) {
                bestSize = currentSize;
            }
        }

        if (bestSize == null) {
            return sizes.get(0);
        }

        return bestSize;
    }

Step 4 : Writing method for swapping camera

   /**
     * Swapping between system cameras
     */
    private void swapCamera() {

        if (!(Camera.getNumberOfCameras() > 1)) {
         /* No front facing camera to switch.*/
            return;
        }

        mReadyToCapture = false;

        mCamera.stopPreview();
        releaseCamera(false);

        if (mCurrentCameraId == Camera.CameraInfo.CAMERA_FACING_BACK)
            mCurrentCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
        else
            mCurrentCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;

        initCameraPreview(mCurrentCameraId, false);
        mPreview.resetCamera(mCamera);
        mPreview.startPreview();
    }

Step 5 : Method for toggling flash

   /**
     * Toggling camera flash to ON/OFF
     */
    private void toggleFlash() {

        if (Util.hasSystemFeature(this, PackageManager.FEATURE_CAMERA_FLASH)) {

            Camera.Parameters parameters = mCamera.getParameters();


            if (mCurrentCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                if (!Util.hasFrontCameraFlash(parameters)) {

                 /* Front facing camera doesn\'t supported flash. */

                    return;
                }
            }

            mReadyToCapture = false;

            if (Camera.Parameters.FLASH_MODE_ON.equals(parameters.getFlashMode())) {
                mFlashMode = Camera.Parameters.FLASH_MODE_OFF;

            } else {
                mFlashMode = Camera.Parameters.FLASH_MODE_ON;

            }
            mCameraHandler.post(new Runnable() {
                @Override
                public void run() {

                    mCamera.stopPreview();
                    releaseCamera(false);

                    initCameraPreview(mCurrentCameraId, false);

                    mPreview.resetCamera(mCamera);
                    mPreview.startPreview();
                }
            });

        } else {
            /* warning_no_flash */
        }

    }

Step 6: Methods for handling camera during the states changes of a screen

   /**
     * Release the camera for other applications
     */
    private void releaseCamera(boolean remove) {
        if (mCamera != null) {
            if (remove)
                mPreview.getHolder().removeCallback(mPreview);
            mCamera.release();
            mCamera = null;
        }
    }

Step 7: Utility classes.

   /**
     * Check whether the given feature available in s/m
     *
     * @return Returns true if the devices supports the feature, else
     * false.
     */
    public static boolean hasSystemFeature(Context context, String feature) {
        return context.getPackageManager().hasSystemFeature(feature);
    }

   /**
     * Check whether front camera flash feature available in s/m
     */
    public static boolean hasFrontCameraFlash(Camera.Parameters cameraParameters) {
        boolean result = true;
        if (cameraParameters.getFlashMode() == null) {
            result = false;
        }
        List<String> supportedFlashModes = cameraParameters.getSupportedFlashModes();
        if (supportedFlashModes == null || supportedFlashModes.isEmpty()
                || supportedFlashModes.size() == 1 &&
                supportedFlashModes.get(0).equals(Camera.Parameters.FLASH_MODE_OFF)) {
            result = false;
        }
        return result;
    }

   /**
     * Showing camera in the same orientation as the display
     */
    public static int[] getCameraDisplayOrientation(Activity activity,
                                                    int cameraId) {
        Camera.CameraInfo info =
                new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        int rotation = activity.getWindowManager().getDefaultDisplay()
                .getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        } else {  // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        return new int[]{result, degrees};
    }

Step 8: Capturing

  // Get an image from the camera
 if (null != mCamera && mReadyToCapture) {
     mCameraOrientationListener.rememberOrientation();
     mCamera.takePicture(mShutter, null, mPicture)
    }

   /**
     * Camera shutter sound callback,
     * used to enable sound while capture
     */
    private Camera.ShutterCallback mShutter = new Camera.ShutterCallback() {
        @Override
        public void onShutter() {

        }
    };

    /**
     * Camera picture callback
     */
    private Camera.PictureCallback mPicture = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            mReadyToCapture = false;


            Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);

            int rotation = ((mCurrentCameraId == Camera.CameraInfo.CAMERA_FACING_BACK ? mDisplayOrientation :
                    ((360 - mDisplayOrientation) % 360)) + mCameraOrientationListener.getRememberedOrientation()
                    + mLayoutOrientation) % 360;

            if (rotation != 0) {
                Matrix matrix = new Matrix();
                matrix.postRotate(rotation);
                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
            }


        }
    };

Step 9: Camera orientation listener for handling image rotation

/**
 * Orientation listener to remember the device's orientation when the user presses
 * the shutter button.
 * <p/>
 * The orientation will be normalized to return it in steps of 90 degrees
 * (0, 90, 180, 270).
 */
public class CameraOrientationListener extends OrientationEventListener {
    private int currentNormalizedOrientation;
    private int rememberedNormalizedOrientation;

    public CameraOrientationListener(Context context) {
        super(context, SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    public void onOrientationChanged(int orientation) {
        if (orientation != ORIENTATION_UNKNOWN) {
            currentNormalizedOrientation = normalize(orientation);
        }
    }

    private int normalize(int degrees) {
        if (degrees > 315 || degrees <= 45) {
            return 0;
        }

        if (degrees > 45 && degrees <= 135) {
            return 90;
        }

        if (degrees > 135 && degrees <= 225) {
            return 180;
        }

        if (degrees > 225 && degrees <= 315) {
            return 270;
        }

        throw new RuntimeException("The physics as we know them are no more. Watch out for anomalies.");
    }

    public void rememberOrientation() {
        rememberedNormalizedOrientation = currentNormalizedOrientation;
    }

    public int getRememberedOrientation() {
        return rememberedNormalizedOrientation;
    }
}

Step 10: States handling

   @Override
    public void onPause() {
        super.onPause();
        mReadyToCapture = false;
        releaseCamera(true);
    }


    @Override
    public void onResume() {
        super.onResume();
        removePreview();
        mReadyToCapture = false;
        smoothCameraLoading();
    }

   private void removePreview() {
        mPreviewHolder.removeAllViews();
    }

   private void smoothCameraLoading() {
        mCameraHandler.post(new Runnable() {
            @Override
            public void run() {
                initCameraPreview(mCurrentCameraId, true);
            }
        });
    }

Step 11: Instance variable used

    private String mFlashMode = Camera.Parameters.FLASH_MODE_OFF;
    private int mCurrentCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
    private int mDisplayOrientation;
    private int mLayoutOrientation;
    private boolean mReadyToCapture = false;
    private Camera.Size mPreviewSize;
    private FrameLayout mPreviewHolder;
    private Camera mCamera;
    private CameraPreview mPreview;
    private Handler mCameraHandler;
    private CameraOrientationListener mCameraOrientationListener;
    private FrameLayout mRootView;
like image 64
Anoop M Maddasseri Avatar answered Sep 20 '22 01:09

Anoop M Maddasseri


In my camera app, it works with somethings different from yours: 1. Open camera when surfaceCreated. 2. StartPreview and no stopPreview in surfaceChanged. 3. Release camera in surfaceDestoryed.

To change the camera, simply set the surfaceView to INVISIBLE, then set your camera parameters, then set the surfaceView to VISIBLE again. In my app, I only do it like this:

        mbtCamera.setOnClickListener(new OnClickListener(){
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            if(cameraMax > 1){
                mSurfaceView.setVisibility(View.INVISIBLE);
                if(mCameraParam.isZoomSupported()) cameraZoomRatios.clear();
                if(cameraUsing == 0) cameraUsing = 1; else cameraUsing = 0;
                mCameraOverlayView.setScaleFactor(1.0f);
                mSurfaceView.setVisibility(View.VISIBLE);
            }
        }
    });

Hope this will help!

The complete callback:

    class CameraSurfaceHolderCallback implements SurfaceHolder.Callback {
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mCamera = Camera.open(cameraUsing);
        mCameraParam = mCamera.getParameters();
        supportFlashMode = mCameraParam.getSupportedFlashModes();
        if(supportFlashMode == null) mbtFlash.setVisibility(View.INVISIBLE);
        android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
        android.hardware.Camera.getCameraInfo(cameraUsing, info);
        int rotation = 0;
        if (info.facing == CameraInfo.CAMERA_FACING_FRONT){
            rotation = (info.orientation - rt + 360) % 360;
            if(mCameraParam.getSupportedFlashModes() == null)   mbtFlash.setVisibility(View.INVISIBLE); 
        }
        else{ // back-facing camera
            rotation = (info.orientation + rt) % 360;
            mbtFlash.setVisibility(View.VISIBLE);
            if(flashMode.equals(Camera.Parameters.FLASH_MODE_AUTO)) icons[0] = BitmapFactory.decodeResource(getResources(), R.drawable.ic_action_flash_automatic);
            else if(flashMode.equals(Camera.Parameters.FLASH_MODE_OFF)) icons[0] = BitmapFactory.decodeResource(getResources(), R.drawable.ic_action_flash_off);
            else if(flashMode.equals(Camera.Parameters.FLASH_MODE_ON)) icons[0] = BitmapFactory.decodeResource(getResources(), R.drawable.ic_action_flash_on);
            Matrix rot = new Matrix();
            rot.setRotate(360-rt, icons[0].getWidth()/2, icons[0].getHeight()/2);
            icons[0] = Bitmap.createBitmap(icons[0], 0, 0, icons[0].getWidth(), icons[0].getHeight(), rot, true);
            mbtFlash.setImageBitmap(icons[0]);
            mCameraParam.setFlashMode(flashMode);
        }
        if(mCameraParam.isZoomSupported()){
            cameraZoomRatios = mCameraParam.getZoomRatios();
            mCameraOverlayView.setMaxScaleFactor(cameraZoomRatios.get(mCameraParam.getMaxZoom())/100f);
        }else mCameraOverlayView.setMaxScaleFactor(1.0f);
        List<Size> ss = mCameraParam.getSupportedPictureSizes();
        Size maxResolution = ss.get(0);
        long pixel1, pixel2;
        pixel1 = maxResolution.width * maxResolution.height;
        for(int i=0; i<ss.size(); i++){
            pixel2 = ss.get(i).width * ss.get(i).height;
            if(pixel2 > pixel1){
                maxResolution = ss.get(i);
                pixel1 = pixel2;
            }
        }
        mCameraParam.setPictureSize(maxResolution.width, maxResolution.height);
        mCameraParam.setJpegQuality(100);

        LayoutParams rlParams = (LayoutParams) mSurfaceView.getLayoutParams();
        WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
        Display dp = wm.getDefaultDisplay();
        DisplayMetrics dm = new DisplayMetrics();
        dp.getMetrics(dm);
        float dpiScale = dm.density;
        float wScaleFactor = (dm.widthPixels-10*dpiScale)/maxResolution.height;
        float hScaleFactor = (dm.heightPixels-mllTopButtons.getHeight()-mllBotButtons.getHeight()-10*dpiScale)/maxResolution.width;
        if(wScaleFactor < hScaleFactor){
            rlParams.width = (int) (dm.widthPixels - 10*dpiScale);
            rlParams.height = (int) (maxResolution.width * wScaleFactor);
        }else{
            rlParams.width = (int) (maxResolution.height * hScaleFactor);
            rlParams.height = (int) (dm.heightPixels-mllTopButtons.getHeight()-mllBotButtons.getHeight()-10*dpiScale);
        }
        mSurfaceView.setLayoutParams(rlParams);
        mCameraOverlayView.setLayoutParams(rlParams);

        ss = mCameraParam.getSupportedJpegThumbnailSizes();
        float photoAspectRatio, thumbAspectRatio;
        photoAspectRatio = (float)maxResolution.width / maxResolution.height;
        thumbAspectRatio = 0;
        pixel1 = 0;
        for(int i=0; i<ss.size(); i++){
            if(ss.get(i).height != 0) thumbAspectRatio = (float)ss.get(i).width / ss.get(i).height;
            if(thumbAspectRatio == photoAspectRatio){
                if(pixel1 == 0)
                {
                    maxResolution = ss.get(i);
                    pixel1 = ss.get(i).width * ss.get(i).height;
                }else{
                    pixel2 = ss.get(i).width * ss.get(i).height;
                    if((pixel2 < pixel1)&&(pixel2 != 0)){
                        maxResolution = ss.get(i);
                        pixel1 = pixel2;
                    }
                }
            }
        }
        if(pixel1 != 0){
            mCameraParam.setJpegThumbnailSize(maxResolution.width, maxResolution.height);
            mCameraParam.setJpegThumbnailQuality(100);
        }

        List<String> focusModes = mCameraParam.getSupportedFocusModes();
        if(focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)){
            mCameraParam.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
            focusingColor = Color.YELLOW;
        }

        int minEv = mCameraParam.getMinExposureCompensation();
        int maxEv = mCameraParam.getMaxExposureCompensation();
        if((minEv == 0)&&(maxEv == 0)) mCameraOverlayView.setEVCompensate(false);
        else mCameraOverlayView.setEVCompensate(minEv, maxEv);
        mCameraParam.setRotation(rotation);
        mCamera.setParameters(mCameraParam);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        try {
            mCamera.setDisplayOrientation(90);
            mCamera.setPreviewDisplay(mSurfaceView.getHolder());

            List<Size> ss = mCameraParam.getSupportedPreviewSizes();
            Size maxResolution = ss.get(0);
            long pixel1, pixel2;
            float photoAspectRatio, previewAspectRatio;
            photoAspectRatio = (float)mCameraParam.getPictureSize().width / mCameraParam.getPictureSize().height;
            previewAspectRatio = 0;
            pixel1 = 0;
            for(int i=0; i<ss.size(); i++){
                if(ss.get(i).height != 0) previewAspectRatio = (float)ss.get(i).width / ss.get(i).height;
                if(previewAspectRatio == photoAspectRatio){
                    if(pixel1 == 0)
                    {
                        maxResolution = ss.get(i);
                        pixel1 = ss.get(i).width * ss.get(i).height;
                    }else{
                        pixel2 = ss.get(i).width * ss.get(i).height;
                        if(pixel2 > pixel1){
                            maxResolution = ss.get(i);
                            pixel1 = pixel2;
                        }
                    }
                }
            }
            if(pixel1 != 0) mCameraParam.setPreviewSize(maxResolution.width, maxResolution.height);
            mCamera.setParameters(mCameraParam);
            mCamera.startPreview();
        }
        catch(Exception e){}
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mCamera.stopPreview();
        mCamera.release();
    }
}

I used it in activity and no fragment. I am not sure this will make something different.

like image 23
i_A_mok Avatar answered Sep 20 '22 01:09

i_A_mok