Search code examples
androidandroid-camerasurfaceviewsurfaceholder

SurfaceView Camera App Switch Camera and Fix Dark Preview


I am building a camera app using surface view. The app loads the camera properly and image is captured. There are two scenarios in image capture:

  1. The image is captured and nothing i.e bytes array is sent to the preview activity. It works fine here.

  2. The image is captured and the byte array from picture callback is sent to the preview activity.

In the second scenario, the app shows a dark screen and becomes unresponsive. I don't believe I know the cause of this as I have checked with many tutorials and seen the same code.

Here's my code so far:

public class ImageActivity extends AppCompatActivity implements SurfaceHolder.Callback {

private static final String TAG = ImageActivity.class.getSimpleName();

private ImageButton closeIV;
private ImageView flipCameraIV;
private FloatingActionButton fab_capture;
private LinearLayout galleryLayout;

private Camera camera;
private Camera.PictureCallback jpegCallback;
private SurfaceView surfaceView;
private SurfaceHolder holder;

private int currCamId = 0;

private boolean isGalleryImg = false;
private boolean isCameraImg = false;

private byte[] camBytes = null;

private static final int GAL_REQ_CODE = 240;
private static final int CAM_PERM_CODE = 242;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    setContentView(R.layout.activity_image);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        getWindow().setStatusBarColor(getResources().getColor(R.color.black));
    }

    init();

    holder = surfaceView.getHolder();
    holder.addCallback(this);
    holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

    currCamId = Camera.CameraInfo.CAMERA_FACING_BACK;

}

private void handleClicks() {

    closeIV.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            finish();
        }
    });

    flipCameraIV.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Log.d(TAG, "Flip Clicked");
            switchCamera();
        }
    });

    galleryLayout.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            isGalleryImg = true;
            Log.d(TAG, "Gallery Button Clicked");
            Intent galleryIntent = new Intent(Intent.ACTION_GET_CONTENT);
            galleryIntent.setType("image/*");
            startActivityForResult(galleryIntent, GAL_REQ_CODE);
        }
    });

    fab_capture.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            captureImage();
        }
    });

    jpegCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] bytes, Camera camera) {
            assert bytes != null;
            Log.d(TAG, "Camera Bytes Array Length:\t" + bytes.length);

            isCameraImg = true;

            ByteArrayOutputStream baos = new ByteArrayOutputStream();


            Intent intent = new Intent(ImageActivity.this, PreviewActivity.class);
            intent.putExtra("camera_bytes", bytes);
            intent.putExtra("fromCamera", isCameraImg);
            startActivity(intent);
            return;

            //refreshCamera();
        }
    };

}


private void captureImage() {
    camera.takePicture(null, null, jpegCallback);
}

public void refreshCamera() {
    if (holder.getSurface() == null) {
        return; // preview surface is empty
    }

    // stop preview, then make changes
    try {
        camera.stopPreview();
    } catch (Exception ex) {
        ex.printStackTrace();
    }

    try {
        camera.setPreviewDisplay(holder);
        camera.startPreview();
    } catch (IOException e) {
        e.printStackTrace();
    }

}

private void init() {
    closeIV = findViewById(R.id.closeIV);
    fab_capture = findViewById(R.id.fab_capture);
    galleryLayout = findViewById(R.id.galleryLayout);
    flipCameraIV = findViewById(R.id.flipCameraIV);
    surfaceView = findViewById(R.id.surfaceView);

    handleClicks();

}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == GAL_REQ_CODE && resultCode == RESULT_OK) {
        Uri imgUri = data.getData();
        Intent intent = new Intent(ImageActivity.this, PreviewActivity.class);
        intent.putExtra("fromGallery", isGalleryImg);
        intent.putExtra("gallery_image", imgUri.toString());
        startActivity(intent);
    }

}

@TargetApi(Build.VERSION_CODES.M)
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

    for (int mResults : grantResults) {
        if (mResults == PackageManager.PERMISSION_DENIED) {
            requestPermissions(new String[]{Manifest.permission.CAMERA,
                            Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    CAM_PERM_CODE);
        } else {
            Toast.makeText(ImageActivity.this, "Permission Already Granted", Toast.LENGTH_SHORT).show();
        }
    }

}

@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
    openCamera();
}

private void openCamera() {
    try {
        camera = Camera.open(currCamId);
    } catch (RuntimeException rex) {
        rex.printStackTrace();
    }

    Camera.Parameters parameters = camera.getParameters();
    parameters.setPreviewFrameRate(20);
    parameters.setPreviewSize(352, 288);
    camera.setDisplayOrientation(90);
    camera.setParameters(parameters);

    try {
        camera.setPreviewDisplay(holder);
    } catch (IOException e) {
        e.printStackTrace();
    }
    camera.startPreview();
}

@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
    refreshCamera();
}

@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
    camera.stopPreview();
    camera.release();
    camera = null;

}

@Override
protected void onPause() {
    super.onPause();
    camera.stopPreview();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    if (camera != null){
        camera.stopPreview();
        camera.release();
        camera = null;
    }
}

}

Also, I have tried switching to the front camera and back again using this snippet but doesn't work.

private void switchCamera() {

    // code I tried to switch camera with but not working

    if (currCamId == Camera.CameraInfo.CAMERA_FACING_BACK){
        currCamId = Camera.CameraInfo.CAMERA_FACING_FRONT;
    } else {
        currCamId = Camera.CameraInfo.CAMERA_FACING_BACK;
    }

    camera = Camera.open(currCamId);
    Camera.CameraInfo info = new Camera.CameraInfo();
    Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_FRONT, info);

    int rotation = this.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 rotate = (info.orientation - degrees + 360) % 360;
    Camera.Parameters parameters = camera.getParameters();
    parameters.setRotation(rotate);

    try {
        camera.setPreviewDisplay(surfaceView.getHolder());
    } catch (IOException e) {
        e.printStackTrace();
    }

    camera.setParameters(parameters);
    camera.setDisplayOrientation(90);
    camera.startPreview();

    //openCamera();

}

I need help in solving this. Thanks.


Solution

  • As i see you're trying to send picture as byte array which is not possible. Because there is a restriction for intent sizes since api23. When sending extras, you must think about the sizes of extras, it really destroys user experience and causes slow load for new activities.

    Anyway, your byte array is sooo big with all that picture data and i think this causing an anr. What must you do? You must save that byte array as a jpeg file first, then just add filepath into your intent. Take a look to this solution

    Oh and also, just a friendly advice based on experience, it's better to use a good camera library instead of android-sdk's classic usage. As i can say, worst part of android development is anything about medias, specially camera api. It's shaming when compared with ios and google is not doing any development about this lack, since 2015.