我正在编写一个用Android相机拍照的APP。代码显示为in this question。目前我不需要处理预览帧,因此相机不使用setPreviewCallback()。
在具有768MB RAM的HTC Sensation设备上,相机在拍照过程中有一些奇怪的动作。其中一个是在拍摄照片并在onPictureTaken()回调中获取数据后,startPreview()(拍摄下一张照片)会产生RuntimeException。目前我只能捕获异常并关闭/重新打开相机作为解决方法。
但是,此设备存在更严重的问题。有时我重新打开相机,但startPreview()给了我一个没有异常或错误消息的冻结帧。我的AP不会崩溃。它仍然可以关闭相机并退出,但无法拍摄更多照片(我将获得startPreview()或takePicture()失败的运行时异常)。发生这种情况时,除非重新启动设备,否则内置摄像头APP也会被破坏。
作为稳定性测试,我拍摄照片,获取JPEG数据,但不要将它们写入文件。当我拍摄约100张照片时,我需要重新打开相机10次,最后相机会坏掉。如果我通过BitmapFactory解码内存中的JPEG数据(仍然没有写入文件),则重新打开/冻结率会大大增加。
当startPreview()失败时,我收到这些错误消息:
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
预览冻结时,我不会收到任何错误或相关消息。当时甚至没有关于内存不足异常的消息。如果我关闭我的APP并启动内置摄像头APP,它会立即退出并出现以下错误:
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!!!
如果我再次启动我的APP,我可以在没有上述错误的情况下打开相机,但在startPreview()或takePicture()时会失败。然后我必须重启设备。 由于该设备的RAM大小为768MB,我怀疑内存不足是主要问题,但我没有直接从我的APP获得OOM异常。
我已经测试了大约500次运行,重启装置约15次,并发现:
我知道如果我的APP在没有合适地关闭相机的情况下崩溃,它可能会锁定相机。但我检查了我的APP仍然关闭并在问题发生后释放相机(但相机在重启设备之前不可用)。相机内部似乎有些不对劲。有谁可以帮助我?
编辑: 以下是关于打开相机和开始预览的代码:
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;
}
拍照过程正在尝试拍摄多张(预定义数量)照片,例如连拍模式:
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();
}
}
在许多示例代码中,只在onPictureTaken()中调用startPreview()。 StartPreviewAfterPictureTaken()过程用于处理startPreview可能的RuntimeException失败。
答案 0 :(得分:3)
感谢Alex Cohn的建议,我找到了解决方案。此解决方案仅在HTC Sensation设备上进行测试,但它对此设备有显着影响。
我只是在startPreview()之前放入一个睡眠指令,它将在onPictureTaken()中调用:
try
{
Thread.sleep(20);
}
catch (Exception e)
{
e.printStackTrace();
}
然后我进行了100次测试,每次运行连续拍摄3张照片。 在睡眠中,我在100次运行中没有从startPreview()获得任何错误。如果没有睡眠,我需要重新打开相机78次,并在100次运行中重启设备8次。