Android相机在startPreview()时冻结,没有任何错误信息

时间:2015-11-11 11:02:09

标签: android camera android-camera

我正在编写一个用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次,并发现:

  1. startPreview()仅在拍摄照片后获取RuntimeException,并且在打开相机后获取onPictureTaken()回调。
  2. 预览冻结问题仅在我在startPreview()失败后重新打开相机时发生。当一切都很好时,它不会直接从startPreview()发生。
  3. 我知道如果我的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失败。

1 个答案:

答案 0 :(得分:3)

感谢Alex Cohn的建议,我找到了解决方案。此解决方案仅在HTC Sensation设备上进行测试,但它对此设备有显着影响。

我只是在startPreview()之前放入一个睡眠指令,它将在onPictureTaken()中调用:

try
{
    Thread.sleep(20);
}
catch (Exception e)
{
    e.printStackTrace();
}

然后我进行了100次测试,每次运行连续拍摄3张照片。 在睡眠中,我在100次运行中没有从startPreview()获得任何错误。如果没有睡眠,我需要重新打开相机78次,并在100次运行中重启设备8次。