I'm writing an app for Android using Xamarin which features a custom activity used to capture pictures using the Camera
API. This activity works perfectly on all devices I've tested, however some users were reporting the app crashing entirely when attempting to take a picture. It was quickly evident that all these users were using Samsung phones, and unfortunately I don't have one available to test on.
Thankfully I've been able to capture the exception and stack trace, but I'm at a loss as to what could be causing this issue. The exception, stack trace and the problematic code are below.
This is a fairly simple activity with a full-screen camera preview, flash toggle and capture button. It uses a custom CameraHelper
class to set up and interact with the Camera
API. The camera is configured and the preview is displayed by the OnSurfaceTextureAvailable
method before the user is able to interact and trigger the TakePicture
method.
Exception Stack Trace
java.lang.RuntimeException: takePicture failed
android.hardware.Camera.native_takePicture(Native Method):0
android.hardware.Camera.takePicture(Camera.java:1523):0
android.hardware.Camera.takePicture(Camera.java:1468):0
md5efa7d89b8a471e1a97a183b83296df21.CameraHelper.n_onAutoFocus(Native Method):0
md5efa7d89b8a471e1a97a183b83296df21.CameraHelper.onAutoFocus(CameraHelper.java:39):0
Methods in CameraHelper
// Implements Camera.IPictureCallback and Camera.IAutoFocusCallback
public void OnSurfaceTextureAvailable(object sender, TextureView.SurfaceTextureAvailableEventArgs e)
{
// Get the camera and set its orientation
try
{
_camera = Camera.Open(_cameraInt);
}
catch (Exception ex)
{
_callback.OnInitializationFailed(ex);
return;
}
var orientation = GetDisplayOrientation();
_camera.SetDisplayOrientation(orientation);
// Set the camera parameters
var cameraParameters = _camera.GetParameters();
if (cameraParameters.SupportedFocusModes != null && cameraParameters.SupportedFocusModes.Contains(Camera.Parameters.FocusModeContinuousPicture))
cameraParameters.FocusMode = Camera.Parameters.FocusModeContinuousPicture;
if (cameraParameters.SupportedFlashModes != null && cameraParameters.SupportedFlashModes.Contains(Camera.Parameters.FlashModeAuto))
{
cameraParameters.FlashMode = Camera.Parameters.FlashModeAuto;
HasFlash = true;
}
cameraParameters.JpegQuality = JPEG_QUALITY;
// Set the picture resolution
var pictureSize = GetIdealPictureSize(cameraParameters.SupportedPictureSizes, MAX_MEGAPIXELS);
_imageWidth = pictureSize.Width;
_imageHeight = pictureSize.Height;
cameraParameters.SetPictureSize(pictureSize.Width, pictureSize.Height);
// Set the preview resolution to best match the TextureView
var previewSize = GetIdealPreviewSize(cameraParameters.SupportedPreviewSizes, _previewTexture.Height, _previewTexture.Width);
cameraParameters.SetPreviewSize(previewSize.Width, previewSize.Height);
// Begin outputting camera preview
_camera.SetParameters(cameraParameters);
_camera.SetPreviewTexture(_previewTexture.SurfaceTexture);
_camera.StartPreview();
UpdatePreviewTextureMatrix(); // Ensure the preview is displayed without warping
// Wait for the preview
EventHandler<TextureView.SurfaceTextureUpdatedEventArgs> h = null;
_previewTexture.SurfaceTextureUpdated += h = (s, e2) =>
{
_previewTexture.SurfaceTextureUpdated -= h;
_callback.OnCameraPreviewReady();
_ready = true;
};
}
public void TakePicture()
{
if (!_ready || _busy)
{
var e = new Exception("Camera not ready");
OnTakePictureFailed(e);
return;
}
_busy = true;
_camera.AutoFocus(this);
}
public void OnAutoFocus(bool success, Camera camera)
{
try
{
_camera.TakePicture(null, null, this);
}
catch (Exception e)
{
// On Samsung phones the exception is always thrown here
OnTakePictureFailed(e);
}
}
public void OnPictureTaken(byte[] data, Camera camera)
{
_busy = false;
var rotation = GetPictureRotation();
_callback.OnPictureTaken(data, rotation, _imageWidth, _imageHeight);
}
private void OnTakePictureFailed(Exception e)
{
_busy = false;
_callback.OnTakePictureFailed(e);
}
The camera is available and the preview is showing without issue, and the exception is only thrown on Samsung devices.
The exception was thrown when a Samsung Galaxy phone failed to auto-focus first time - while most devices will only attempt to focus once, Samsung Galaxy phones re-attempt up to three times, and call the OnAutoFocus
callback after each attempt. Because my code called Camera.TakePicture
in the callback, it could be called twice or more in rapid succession, hence it would attempt to take a picture while a picture was already being taken and throw the exception.
The solution was simply to add a boolean which tracked whether the auto-focus callback had already happened, and if so, skip calling Camera.TakePicture
:
public void OnAutoFocus(bool success, Camera camera)
{
if (_hasAttemptedFocus) return;
else _hasAttemptedFocus = true;
_camera.TakePicture(null, null, this);
}
public void OnPictureTaken(byte[] data, Camera camera)
{
_busy = _hasAttemptedFocus = false;
// Do something with the image
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With