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 格式?

android 怎么从camera中获取流

ImageView 在相机表面视图后面移动

使用Camera进行拍照

Camera1 源码解析系列—— Camera1 startPreview() 流程解析

Camera1 源码解析系列—— Camera1 startPreview() 流程解析