Search code examples
androidandroid-camerasurfaceviewsurfaceholder

Camera not being released properly on Astro Tab A10 running Marshmallow


I have the some of the following code to use the camera:

@Override
public void surfaceCreated(SurfaceHolder holder) {
    if (mCamera == null) {
        mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
        try {
            mCamera.setPreviewDisplay(mHolder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

private void resetCamera() {
    if (!videoReset) {
        if (videoStarted && !videoStopped) {
            mMediaRecorder.stop();
        }
        MediaScannerConnection.scanFile(TherapistActivity.this, new String[]{videoFile.getAbsolutePath()}, null, null);

        mMediaRecorder.reset();
        mMediaRecorder.release();
        mCamera.release();
        mCamera = null;
        mMediaRecorder = null;

        videoReset = true;
        initSuccess = false;

    }
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    surfaceWidth = width;
    surfaceHeight = height;
    try {
        if (!initSuccess)
            startPreview(mHolder.getSurface());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private void initRecorder(Surface surface) throws IOException {
        Camera.Parameters parameters = mCamera.getParameters();
        Camera.Size previewSize = getOptimalPreviewSize(surfaceWidth, surfaceHeight);

        if (previewSize != null) {
            parameters.setPreviewSize(previewSize.width, previewSize.height);
        }
        parameters.setVideoStabilization(false);
        mCamera.setParameters(parameters);
        mCamera.startPreview();
        mCamera.unlock();

        if (mMediaRecorder == null) mMediaRecorder = new MediaRecorder();
        mMediaRecorder.setCamera(mCamera);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setVideoEncodingBitRate(3072 * 1000);
        mMediaRecorder.setVideoFrameRate(60);
        mMediaRecorder.setVideoSize(1280, 720);
        mMediaRecorder.setOutputFile(videoFile.getAbsolutePath());

        if (!mApp.videoTime) {
            mMediaRecorder.setMaxDuration(30000);
        } else {
            mMediaRecorder.setMaxDuration(Integer.MAX_VALUE);
        }
        mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
            @Override
            public void onInfo(MediaRecorder mr, int what, int extra) {
                if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
                      mCameraWrapper.setBackgroundColor(Color.TRANSPARENT);
                    try {     
                        mCamera.stopPreview();
                        videoRecorded = true;
                    } catch (RuntimeException re) {
                        Log.e("Error", "Could not stop camera!");
                    } finally {
                        videoPreviewStarted = false;
                    }
                    btRecord.setTag("stop");
                    btRecord.setBackgroundResource(R.drawable.stop_nobkgrnd_gray);
                    tvVideoCountdown.setVisibility(View.GONE);
                    initSuccess = false;
                }
            }
        });
        try {
            mMediaRecorder.prepare();
            videoPreviewStarted = true;
        } catch (IllegalStateException e) {
            e.printStackTrace();
        }
        initSuccess = true;
    }
}

private Camera.Size getOptimalPreviewSize(int w, int h) {
    Camera.Size result = null;
    Camera.Parameters p = mCamera.getParameters();
    for (Camera.Size size : p.getSupportedPreviewSizes()) {
        if (size.width <= w && size.height <= h) {
            if (result == null) {
                result = size;
            } else {
                int resultArea = result.width * result.height;
                int newArea = size.width * size.height;

                if (newArea > resultArea) {
                    result = size;
                }
            }
        }
    }
    return result;
}

On the Astro Tab A10 running Marshmallow, apparently the Camera isn't being released properly when being run the second time, since the camera fails to open until the device is rebooted. I am pretty sure this isn't a permissions problem, since I granted the app permissions to the camera at runtime as well as including camera permissions in the manifest.

Does anyone know what might be the problem?

EDIT: Here is the stack trace:

  E/AndroidRuntime: FATAL EXCEPTION: main
       Process: com.mobilityresearch.treadmill3, PID: 4144
       java.lang.RuntimeException: Fail to connect to camera service
           at android.hardware.Camera.<init>(Camera.java:495)
           at android.hardware.Camera.open(Camera.java:341)
           at com.mobilityresearch.treadmill3.TherapistActivity.surfaceCreated(TherapistActivity.java:8260)
           at android.view.SurfaceView.updateWindow(SurfaceView.java:583)
           at android.view.SurfaceView.setVisibility(SurfaceView.java:257)
           at com.mobilityresearch.treadmill3.TherapistActivity$26.onClick(TherapistActivity.java:1701)
           at android.view.View.performClick(View.java:5204)
           at android.view.View$PerformClick.run(View.java:21153)
           at android.os.Handler.handleCallback(Handler.java:739)
           at android.os.Handler.dispatchMessage(Handler.java:95)
           at android.os.Looper.loop(Looper.java:148)
           at android.app.ActivityThread.main(ActivityThread.java:5417)
           at java.lang.reflect.Method.invoke(Native Method)
           at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:742)
           at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:632)

This doesn't happen on any of our other tablets. In fact, it doesn't happen on the older version of the Astro Tab A10 running Android 5.1 Lollipop. I tried calling resetCamera the camera both in surfaceCreated before opening the preview, and surfacedDestroyed after closing the preview, and it doesn't work.

EDIT 2: I just found out that if I actually record video, it works fine, but if I don't record video and only display the preview, it doesn't work.

EDIT 3: Added surfaceChanged and initRecorder. Updated resetCamera.


Solution

  • You are missing call to stopPreview() which stops capturing and drawing preview frames to the surface and setPreviewDisplay(null) which releases the preview display.

    Update your resetCamera() as follows:

    private void resetCamera() {
            ...
            mCamera.stopPreview();
            mCamera.setPreviewDisplay(null);
            mCamera.release();
            ...
    
        }
    }