I'm writing an APP to take picture by Android camera. The codes are shown in this question. Currently I don't need to handle the preview frame, so setPreviewCallback() is not used for the camera.
On an HTC Sensation device with 768MB RAM, the camera has some weird actions in the take picture process. One of it is that after taking a picture and get the data in onPictureTaken() callback, the startPreview() (to take next picture) gives a RuntimeException. Currently I can only catch the exception and close/reopen the camera as a workaround.
However, there is a more serious problem on this device. Sometimes I reopen the camera, but startPreview() gives me a frozen frame without exception or error message. My AP does not crash. It can still close the camera and exit, but cannot take more pictures (I will get a runtime exception of startPreview() or takePicture() fail). When this happen, the built-in camera APP will also be broken unless I restart the device.
As a stability test, I take the photos, get the JPEG data, but do not write them to files. When I take about 100 photos, I need to reopen the camera about 10 times, and finally the camera will broken. If I decode the JPEG data in memory by BitmapFactory (still not write to files), the reopen/freeze rates are greatly increased.
I get these error message when startPreview() fails:
E/SurfaceTexture(112): [SurfaceView] setBufferCount: client owns some buffers
E/SurfaceTextureClient(115): ISurfaceTexture::setBufferCount(7) returned Invalid argument
E/SurfaceTexture(112): [SurfaceView] dequeueBuffer: MIN_UNDEQUEUED_BUFFERS=2 exceeded (dequeued=5)
E/QualcommCameraHardwareZSL(115): getBuffersAndStartPreview: dequeueBuffer failed for preview buffer. Error = -16
When the preview freezes, I don't get any error or related messages. There is even no message about out-of-memory exception at that time. If I close my APP and launch the built-in camera APP, it exits immediately with these errors:
E/MemoryHeapBase(115): mmap(fd=142, size=9011200) failed (Out of memory)
E/QualcommCameraHardwareZSL(115): Failed to get camera memory for RawZSLAdsppool heap cnt(20)
E/MemoryHeapBase(115): mmap(fd=142, size=9011200) failed (Out of memory)
E/QualcommCameraHardwareZSL(115): Failed to get camera memory for RawZSLAdsppool heap cnt(18)
...
E/QualcommCameraHardwareZSL(115): initZslBuffer X failed cnt(0)
E/QualcommCameraHardwareZSL(115): initRaw X: error initializing mRawZSLAdspMapped
E/QualcommCameraHardwareZSL(115): Init ZSL buffers X failed
E/QualcommCameraHardwareZSL(115): Failed to allocate ZSL buffers
E/QualcommCameraHardwareZSL(115): Starting ZSL CAMERA_OPS_STREAMING_ZSL failed!!!
If I launch my APP again, I can open the camera without the errors above, but it will fail at startPreview() or takePicture(). Then I must restart the device. Since the device has a small RAM size of 768MB, I suspect that out-of-memory is the main issues, though I don't get OOM exception directly from my APP.
I have tested about 500 runs with ~15 times of restarting device, and found that:
- startPreview() only fails with RuntimeException after taking a picture and getting onPictureTaken() callback since the camera is opened.
- The preview frozen issue only happens when I reopen the camera after a failed startPreview(). It does not happen directly from startPreview() when everything is fine.
I know that if my APP crashes without closing the camera appropriately, it may lock the camera. But I have checked that my APP still close and release the camera after the issue happens (but the camera is unavailable until restarting device). It seems that something wrong inside the camera. Could anyone help me?
Edit: Here is the codes about opening camera and starting preview:
public Camera m_camera;
int m_camera_index;
int m_camera_rotation;
String m_camera_focus_mode;
int m_preview_width;
int m_preview_height;
int m_picture_width;
int m_picture_height;
SurfaceHolder m_surface_holder;
boolean m_is_during_preview;
public CameraView(Context context)
{
super(context);
m_surface_holder = getHolder();
m_surface_holder.addCallback(this);
m_surface_holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
m_is_during_preview = false;
}
public void OpenCamera()
{
CloseCamera();
// Determine m_camera_index on this device
...
m_camera = Camera.open(m_camera_index);
if (m_camera == null)
{
LogError("Failed to open any camera.");
return;
}
try
{
m_camera.setPreviewDisplay(m_surface_holder);
}
catch (IOException e)
{
e.printStackTrace();
m_camera = null;
return;
}
// Determine m_display_orientation
...
m_camera.setDisplayOrientation(m_display_orientation);
Camera.Parameters parameters = m_camera.getParameters();
// Determine m_preview_width x m_preview_height
// and m_picture_width x m_picture_height from the supported ones
...
parameters.setPictureSize(m_picture_width, m_picture_height);
parameters.setPreviewSize(m_preview_width, m_preview_height);
// Set m_camera_rotation so we get the picture data with correct orientation
m_camera_rotation = m_display_orientation;
if (m_activity.m_is_frontal_camera)
{
m_camera_rotation = 360 - m_display_orientation;
if (m_camera_rotation == 360)
m_camera_rotation = 0;
}
parameters.setRotation(m_camera_rotation);
parameters.setPreviewFormat(ImageFormat.NV21);
// Determine m_camera_focus_mode from the supported ones
...
parameters.setFocusMode(m_camera_focus_mode);
m_camera.setParameters(parameters);
m_is_during_preview = false;
}
public void CloseCamera()
{
if (m_camera != null)
{
StopPreview();
m_camera.release();
m_camera = null;
}
}
public void RestartCamera()
{
// Only use to restart the camera when startPreview() fails after taking a picture
CloseCamera();
m_camera = Camera.open(m_camera_index);
if (m_camera == null)
{
LogError("Failed to reopen camera.");
return;
}
try
{
m_camera.setPreviewDisplay(m_surface_holder);
}
catch (IOException e)
{
e.printStackTrace();
m_camera = null;
return;
}
m_camera.setDisplayOrientation(m_display_orientation);
Camera.Parameters parameters = m_camera.getParameters();
parameters.setPictureSize(m_picture_width, m_picture_height);
parameters.setPreviewSize(m_preview_width, m_preview_height);
parameters.setRotation(m_camera_rotation);
parameters.setPreviewFormat(ImageFormat.NV21);
parameters.setFocusMode(m_camera_focus_mode);
m_camera.setParameters(parameters);
StartPreview();
}
public void StartPreview()
{
if (m_camera == null)
return;
if (m_is_during_preview == true)
return;
m_camera.startPreview();
m_is_during_preview = true;
}
public void StopPreview()
{
if (m_is_during_preview == false)
return;
if (m_camera != null)
{
m_camera.stopPreview();
}
m_is_during_preview = false;
}
The take picture process is trying to take multiple (a predefined number) pictures, like the burst mode:
final int multishot_count = 3;
...
public void TakePicture()
{
// Invoke multishot from UI when the camera preview is already started.
// startup of multishot task
...
m_take_picture_count = 0;
TakeOnePicture();
}
private void TakeOnePicture()
{
m_camera.takePicture(null, null, new Camera.PictureCallback()
{
@Override
public void onPictureTaken(byte[] data, final Camera camera)
{
m_take_picture_count++;
// feed the JPEG data to a queue and handle it in another thread
synchronized(m_jpeg_data_lock)
{
int data_size = data.length;
ByteBuffer buffer = ByteBuffer.allocateDirect(data_size);
buffer.put(data, 0, data_size);
m_jpeg_data_queue.offer(buffer);
if (m_take_picture_count == multishot_count)
m_is_all_pictures_taken = true;
}
if (m_take_picture_count < multishot_count)
{
StartPreviewAfterPictureTaken();
TakeOnePicture();
}
else
{
// Finalize the multishot task and process the image data
EndTakePictureTask end_take_picture_task = new EndTakePictureTask();
end_take_picture_task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
});
}
private void StartPreviewAfterPictureTaken()
{
// Start preview in onPictureTaken() callback,
// which may get RuntimeException in some devices.
try
{
m_camera.startPreview();
}
catch (RuntimeException e)
{
LogError("Fail to start preview. Workaround: reopen camera.");
RestartCamera();
}
}
In many sample codes startPreview() is just called in onPictureTaken(). The StartPreviewAfterPictureTaken() process is used to handle the possible RuntimeException of startPreview fails.