Android CameraView – Lessons Learned

For a long now I have implemented the android SurfaceView and TextureView for custom applications which require custom camera inside one of the screens. Let me share with you part of my experience.

Long story short

As you know the Android OS has now 2 camera API’s with which you have to work with. Camera1 and Camera2. There are major differences between them so it is hard to understand both of them. You should be using Camera2 for Android 5.0 and up and Camera1 for devices below 5.0.

Search for a CameraPreview library

This is the first thought that hit me. Well, if I have to implement two different APIs to support different devices, there must be a library that supports both of them. And I was right. There is the CameraView library which is not connected with Google in any way, but it is made in their repository. But, as you can see, the lib has 49 issues at the moment and is not very well supported. But let’s use it or we will have to implement to whole API by ourselves.

Autofocus problem

Rear camera autofocus does not work as expected

On some Samsung devices, the camera just doesn’t autofocus correctly. I made a solution so when you tap the SurfaceView, the autofocus is reinitialized. That means the camera does not focus on a particularly visible area, just enable and disable the autofocus. So we would have a ClickListener in the CameraView class to enable and disable the AutoFocus

setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View view) {
    // Check if the autofocus is enabled
    if (getAutoFocus()) {
      setAutoFocus(false);
      setAutoFocus(true);
    }
  }
  });

Why this works? Well, behind the scenes this calls the current implementation (Camera1 or 2) AutoFocus method, which internally set the camera new parameters. So if you just say to the camera object .setFocus(true), it will not update the preview and not start the autofocus. You have to set the needed parameters to the camera object so it can start the autofocus. That is why it works. You can check the setAutoFocus source for Camera1 and Camera2.

Front camera autofocus is not supported on most of the devices

Forget to use the autofocus feature for the front camera. There isn’t one. Or if there is, it is a rare case. Just put autoFocus=”false” if you are going to use the front camera. Or you will get an exception, especially when using the Camera2 API.

Camera2 API is not fully supported even on devices with Android 5 or 6

This was a big shock for me. Hey, we have implemented Android 5 OS, but you can’t use the new Camera API. And if you try, guess what happens? CRASH. This happened again on Samsung S5 mini and on a Lenovo A1000. So, what we do then? The new version of the library checks the supported mode of the camera and determines if it is LEGACY or not. I still can’t believe there is a method with which you can check that and it is built into the API. Here it is how it looks like:

    private boolean supportsCamera2Api() {
        CameraManager manager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE);

        try {
            for (String cameraId : manager.getCameraIdList()) {
                CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
                Integer cameraSupportInt = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
                if (cameraSupportInt == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
                        || cameraSupportInt == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3) {
                    return true;
                }
            }
        } catch (CameraAccessException | NullPointerException e) {
            e.printStackTrace();
            return false;
        }

        return false;
    }

Basically, internally what the lib does is fallback if it has problems with starting the Camera2 API on devices with Android 5.0 and up. Here is the code.

Taking picture with continuous autofocus makes older devices take multiple shots of it

Fuck, this is one of the strange problems where you are wondering why the fuck you had to implement a lib, but then realize that you would swear far more if you hadn’t. On devices with Android 4.4 taking a picture with the FOCUS_MODE_CONTINUOUS_PICTURE makes the device take multiple shots of the same picture. You have to enter the Camera1 API class and make it look like the following:

 void takePictureInternal() {
        mCamera.takePicture(null, null, null, new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                mCallback.onPictureTaken(data);
                // Cancel the autofocus after picture is taken
                // so it won't be taken many times
                camera.cancelAutoFocus();
                camera.startPreview();
            }
        });
    }

Font camera image is rotated

This is an “ordinary” thing that I found recently. If you take an image with the front camera, it is rotated based on how the front camera sensor is placed. You can check THIS LINK for more info.

There are 2 ways to rotate the image:
* Use the MediaStore cursor to return you the orientation
* Read the EXIF data of the image

MediaStore example

String selectedImage = data.getData();
String[] filePathColumn = {MediaStore.Images.Media.DATA};

Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
filePath = cursor.getString(columnIndex);
cursor.close();

int rotateImage = getCameraPhotoOrientation(MyActivity.this, selectedImage, filePath);

Reading the EXIF data

You can use the following class to read the image EXIF data and get it’s correct orientation. The isOldCameraApi is a flag if the current Camera API is 1, because it has some orientation problems when taking an image.

   // isOldCameraApi is a flag I use if the Preview is Camera1 or Camera2 because on older devices
   // you just have to rotate the image on certain types
    public static Bitmap correctImageOrientation(File file, boolean isOldCameraApi) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        Bitmap bitmap = null;
        try {
            bitmap = BitmapFactory.decodeStream(new FileInputStream(file), null, options);
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
            bitmap = BitmapFactory.decodeStream(new FileInputStream(file), null, options);
            ExifInterface ei = new ExifInterface(file.getAbsolutePath());
            int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_UNDEFINED);

            return getRotatedBitmap(bitmap, orientation, isOldCameraApi);

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

    private static Bitmap getRotatedBitmap(Bitmap bitmap, int orientation, boolean isOldCameraApi) {
        Timber.d("Orientation: " + orientation + " OldCameraAPI: " + isOldCameraApi);
        switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                bitmap = rotateImage(bitmap, 90);
                break;
            case ExifInterface.ORIENTATION_ROTATE_180:
                bitmap = rotateImage(bitmap, 180);
                break;
            case ExifInterface.ORIENTATION_ROTATE_270:
                bitmap = rotateImage(bitmap, 270);
                break;
            case ExifInterface.ORIENTATION_NORMAL:
            case ExifInterface.ORIENTATION_UNDEFINED:
                if (isOldCameraApi) {
                    bitmap = rotateImage(bitmap, 180);
                }
                break;
            default:
                break;
        }
        return bitmap;
    }

    public static Bitmap rotateImage(Bitmap source, float angle) {
        Matrix matrix = new Matrix();
        matrix.postRotate(angle);
        Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix,
                true);

        return bitmap;
    }

Final

These are just part of the many problems the Android CameraView has. I will post some more issues if I can remember all of them. Be careful when using the Camera API and prefer to revert back to Camera1 whenever you find a problem. Camera2 is still not well implemented on most devices and you will hit awful edgecases which will eat your time.
 

You may also like...