Android 相机在 startPreview() 处冻结,没有任何错误信息
Posted
技术标签:
【中文标题】Android 相机在 startPreview() 处冻结,没有任何错误信息【英文标题】:Android camera freezes at startPreview() without any error information 【发布时间】:2016-02-12 11:06:30 【问题描述】:我正在写一个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!!!
如果我再次启动我的应用程序,我可以打开相机而不会出现上述错误,但它会在 startPreview() 或 takePicture() 处失败。然后我必须重新启动设备。 由于该设备的 RAM 很小,只有 768MB,我怀疑内存不足是主要问题,尽管我没有直接从我的 APP 获得 OOM 异常。
我已经测试了大约 500 次运行,大约 15 次重启设备,发现:
-
startPreview() 仅在拍照后因 RuntimeException 而失败,并在相机打开后获得 onPictureTaken() 回调。
仅当我在 startPreview() 失败后重新打开相机时才会出现预览冻结问题。当一切正常时,它不会直接从 startPreview() 发生。
我知道如果我的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();
在许多示例代码中,startPreview() 只是在 onPictureTaken() 中调用。 StartPreviewAfterPictureTaken()过程用于处理startPreview失败可能出现的RuntimeException。
【问题讨论】:
我们一开始不知道您是如何设置相机的。显示初始化相机的代码以及如何拍照。 你为什么不使用辅助库....检查 GitHub 至少有 10 个不错的。不要重新发明***。 你会推荐什么@OWADL 在 startPreview() 和 takePicture() 之间require delay 的一些设备。对于某些设备在 takePicture() 和 startPreview() 之间需要延迟,我并不感到惊讶。这些场景对 ODM 来说显然不是很重要。 @Alex Cohn 是的。我发现几个讨论在 onPictureTaken() 之后需要睡眠。但是,我尝试使用循环来 startPreview() -> 捕获异常 -> 休眠一段时间,但它会在没有成功的 startPreview() 的情况下永远循环。这部分的细节我写了in this question。 【参考方案1】:感谢 Alex Cohn 的建议,我找到了解决方案。此方案仅在 HTC Sensation 设备上进行了测试,但在此设备上效果显着。
我只是在 startPreview() 之前放了一条睡眠指令,它将在 onPictureTaken() 中调用:
try
Thread.sleep(20);
catch (Exception e)
e.printStackTrace();
然后我运行 100 次测试,每次运行连续拍摄 3 张照片。 随着睡眠,我在 100 次运行中没有从 startPreview() 得到任何错误。如果没有睡眠,我需要重新打开相机 78 次,并在 100 次运行中重新启动设备 8 次。
【讨论】:
非常有用的信息标记。这只是解决方法,但我认为这对于这种情况是可以的。我会再次测试并告诉你结果!以上是关于Android 相机在 startPreview() 处冻结,没有任何错误信息的主要内容,如果未能解决你的问题,请参考以下文章
我如何从 android 相机 onPreviewFrame 中知道 YUV 格式?