0

So I have a very basic camera implementation. My goal is to automatically switch between the front and back cameras every 10 second, until stopped by click of button.

Here is my MainActivity:

public class MainActivity extends Activity {
    private Camera mCamera = null;
    private CameraView mCameraView = null;
    private CountDownTimer countDownTimer;
    private int mCamId = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startCam();

        if(mCamera != null) {
            mCameraView = new CameraView(this, mCamera);//create a SurfaceView to show camera data
            FrameLayout camera_view = (FrameLayout)findViewById(R.id.camera_view);
            camera_view.addView(mCameraView);//add the SurfaceView to the layout
        }

        //btn to close the application
        ImageButton imgClose = (ImageButton)findViewById(R.id.imgClose);
        imgClose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                System.exit(0);
            }
        });
    }

    private void startCam() {
        try{
            //you can use open(int) to use different cameras
            mCamId = mCamId == 0 ? 1 : 0;
            mCamera = Camera.open(mCamId);
            switchCam();

        } catch (Exception e){
            Log.d("ERROR", "Failed to get camera: " + e.getMessage());
        }
    }

    private void switchCam() {
        //10 seconds
        countDownTimer = new CountDownTimer(10000, 1000) {

            @Override
            public void onTick(long l) {
                Log.d(TAG, l + " left");
            }

            @Override
            public void onFinish() {
                cleanup();
                startCam();
            }
        }.start();
    }

    public void cleanup() {
        Log.i(TAG, "Switching Camera");
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;

        }
    }
}

And here is my CameraView class:

public class CameraView extends SurfaceView implements SurfaceHolder.Callback{

    private SurfaceHolder mHolder;
    private Camera mCamera;

    public CameraView(Context context, Camera camera){
        super(context);

        mCamera = camera;
        mCamera.setDisplayOrientation(90);
        //get the holder and set this class as the callback, so we can get camera data here
        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        try{
            //when the surface is created, we can set the camera to draw images in this surfaceholder
            mCamera.setPreviewDisplay(surfaceHolder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d("ERROR", "Camera error on surfaceCreated " + e.getMessage());
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i2, int i3) {
        //before changing the application orientation, you need to stop the preview, rotate and then start it again
        if(mHolder.getSurface() == null)//check if the surface is ready to receive camera data
            return;

        try{
            mCamera.stopPreview();
        } catch (Exception e){
            //this will happen when you are trying the camera if it's not running
        }

        //now, recreate the camera preview
        try{
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d("ERROR", "Camera error on surfaceChanged " + e.getMessage());
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        //our app has only one screen, so we'll destroy the camera in the surface
        //if you are unsing with more screens, please move this code your activity
        mCamera.stopPreview();
        mCamera.release();
    }

The first camera starts with no problem. However, the interface freezes into a static image at the switching of second camera, that is after 10 seconds. I am unable to fix it. Where am I mistaken?

Minimal, Complete, Verifiable and Compilable Code: LINK

Jishan
  • 1,654
  • 4
  • 28
  • 62
  • 1
    Does the whole application freeze? Is there anything useful in the logcat output? The link to the provided sourcecode is dead btw! – Al0x Nov 24 '17 at 14:30
  • @Al0x I just updated the link immediately! Nope, just the Camera freezes! – Jishan Nov 24 '17 at 14:31

2 Answers2

0

Your CameraView is not synchronized with the MainActivity. Both hold reference to mCamera, but when the Activity swaps the camera, the View is not informed.

The minimal change to your code would be:

  1. Move the code that creates mCameraView to startCam().
  2. In cleanup(), instead of mCamera.stopPreview() etc, remove mCameraView from the FrameLayout.

Now when the timer event happens, the framework will call CameraView.SurfaceDestroyed() to release mCamera, and after that you will create a new CameraView for the front-facing camera.

Few additional notes:

  1. You can keep the same CameraView if you swap its mCamera.
  2. It is a bad practice to run Camera.open() on the UI thread, this call may be slow on some devices, and even cause ANR.
  3. The preferred way is to use a background HandlerThread so that the Camera callbacks also happen on the background.
  4. Google fixed this in the new camera2 API (available on Lollipop and higher). Your minSdkVersion being 21, you have all reasons to use this new API.
  5. Use FLAG_KEEP_SCREEN_ON while camera preview is live.
Alex Cohn
  • 56,089
  • 9
  • 113
  • 307
-1

What you do at the moment:

In the onCreate method you're updating the FrameLayout after startCam().

        if(mCamera != null) {
        mCameraView = new CameraView(this, mCamera);//create a SurfaceView to show camera data
        FrameLayout camera_view = (FrameLayout)findViewById(R.id.camera_view);
        camera_view.addView(mCameraView);//add the SurfaceView to the layout
    }

When the camera switches you release the old camera and open the new one, but you forget to also update the FrameLayout. So the problem is not that anything "freezes" but the switch to the new Camera isn't updated in your GUI.

How to fix your problem:

In the method switchCam() you also need to update the FrameLayout, just as you did in the onCreate method.

A working example of the switchCam() method would be as followed:

    private void switchCam() {
        //10 seconds
        countDownTimer = new CountDownTimer(10000, 1000) {

            @Override
            public void onTick(long l) {
                Log.d(TAG, l + " left");
            }

            @Override
            public void onFinish() {
                cleanup();
                startCam();
                if(mCamera != null) {
                    mCameraView = new CameraView(getApplicationContext(), mCamera);//create a SurfaceView to show camera data
                    FrameLayout camera_view =
 (FrameLayout)findViewById(R.id.camera_view);
    if(( camera_view).getChildCount() > 0)
                    {
                        camera_view.removeAllViews();
                    }
                    camera_view.addView(mCameraView);//add the SurfaceView to the layout
                }
            }
        }.start();
    }

If this alone doesn't solve your problem, you could also try the following:

Change the uses-feature in your AndroidManifest hardware.camera to hardware.camera2 as hardware.camera is deprecated.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="in.harjot.andorid.cameraswap">
...
    <uses-feature android:name="android.hardware.camera2" />
...
</manifest>

On my Nexus5X the app also seemed to have a problem with permissions so I added the following to the onCreate method BEFORE startCam() is called (without this I actually couldn't see anything at all):

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if (ContextCompat.checkSelfPermission(this,
            Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {

            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.CAMERA},
                    1);
    }

    startCam();
    ...
}
Al0x
  • 917
  • 2
  • 13
  • 30
  • 1
    Can you make the change in the full code so that this works? I am confused as to where I must put the snippet! Thanks btw! – Jishan Nov 24 '17 at 15:13
  • My answer now includes a working example of the switchCam() method. – Al0x Nov 24 '17 at 15:23
  • My answer now includes every single bit that I changed in your example code to get it working. I have tried it on a Nexus5X with Android 8.0.0. Given the fact that the downvotes are pretty ridiculous, as I am just trying to help, I'm out. – Al0x Nov 24 '17 at 15:56