As promised… This blog is moving direction to focus on Flutter Development… First up is how to fix flutter’s dark camera plugin (that can be used in any Android project utilising the Camera2 API)!

When developing my (Xamarin) app Prog I ran into a rather complex bug on Android with the Camera2 API. Upon starting the camera preview, it would appear correctly lit for a split second and promptly become dark. This rendered the camera preview useless, as seen below.

I spent a good month reading over endless Stackoverflow posts, and attempting to translate the Android documentation into Xamarins C# wrapper equivalent. Just as I was about to give up, I gave it one last attempt, and I got it!

It turned out, the camera FPS was too high for the auto exposure to keep up. This resulted in the auto exposure failing miserably and seemingly ‘giving up’. Note, that on high end devices this didn’t seem to be a huge problem. Although, I tested on a “Samsung Galaxy Tab A” which is seemingly low end – but should still be way more than capable of running Prog.

The solution turned out to be pretty simple… You can query the list of available FPS ranges that the auto exposure can handle via the CameraCharacteristics API. A range in this instance has a lower and upper bound – the lower end meaning slower FPS and the upper meaning faster FPS.

The list of ranges returned can come in two forms (x, x) where the lower and upper range is the same (i.e. constant FPS). Or a (x, y) form where x < y but there is variation on the FPS. From personal experience, the (x, y) range appears to use the lower FPS when the exposure is struggling but remain high FPS on high end device. Thus, finding the FPS range with the biggest difference between X and Y components resulted in the ‘sweet spot’ when choosing the FPS range.

Enough Background… This is For Flutter!

Or more specifically… The Flutter’s Camera Plugin. I have already created a Pull Request to fix this – but have been told they’re favouring quality over features before approving the pull request (it’s became a ‘feature’ as choosing a slower FPS in favour of better exposure may not be required by all apps, therefore an option is required to be implemented).

I’m sharing this a work around for those running into this problem and require a useable camera preview in all scenarios.

Within the Android native class of the camera plugin source code (CameraPlugin.java) create a method called “setBestAERange” as shown below. This will get all fpsRanges, check for the range with the biggest difference between lower and upper bound and assign to the member variable “aeFPSRange”.

private void setBestAERange(CameraCharacteristics characteristics) { Range<Integer>[] fpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES); if (fpsRanges.length <= 0) { return; } Integer idx = 0; Integer biggestDiference = 0; for (Integer i = 0; i < fpsRanges.length; i++) { Integer currentDifference = fpsRanges[i].getUpper() - fpsRanges[i].getLower(); if (currentDifference > biggestDiference) { idx = i; biggestDiference = currentDifference; } } aeFPSRange = fpsRanges[idx]; }

Hopefully this should indicate that you need to add a Range<Integer> type member variable to the “Camera” class. Then call this method inside the Camera constructor, just above “computeBestCaptureSize” making sure you pass in the characteristics variable.

Camera(final String cameraName, final String resolutionPreset, @NonNull final Result result) { ... setBestAERange(characteristics); computeBestCaptureSize(streamConfigurationMap); ... }

The final piece of the puzzle is to set this FPS range on the capture request for the camera preview. Add the following code to the “createCaptureSession” method, just before the call to “setRepeatingRequest” on the capture request.

if (Camera.this.aeFPSRange != null) { captureRequestBuilder.set( CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, Camera.this.aeFPSRange); }

Happy usable camera preview!