2015-11-11 69 views
1

我正在寫一個應用程序,以Android相機拍照。代碼顯示爲in this question。目前我不需要處理預覽框,所以setPreviewCallback()不用於相機。Android的相機在startPreview()凍結,沒有任何錯誤信息

在帶有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 

當預覽凍結,我沒有得到任何錯誤或相關的消息。當時甚至沒有關於內存不足異常的消息。如果我收我的應用程序並啓動內置攝像頭應用程序,它會立即與這些錯誤退出:

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()。然後我必須重新啓動設備。 由於該設備有一個768MB的小內存大小,我懷疑內存不足是主要問題,儘管我沒有直接從我的APP獲得OOM異常。

我已經測試了大約500奔跑〜重啓設備的15倍,並發現:

  1. startPreview()只失敗的RuntimeException拍照並獲得onPictureTaken()回調,因爲相機被打開後, 。
  2. 預覽凍結問題只發生在失敗的startPreview()後重新打開相機時。當一切正常時,它不會直接從startPreview()發生。

我知道如果我的應用程序崩潰而沒有正確關閉相機,它可能會鎖定相機。但是我已經檢查過我的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失敗。

+0

我們不知道如何在第一個地方設置相機。顯示初始化相機的代碼以及如何拍攝照片。 –

+0

爲什麼你不使用助手庫....檢查GitHub至少有10個好東西。不要重新發明輪子。 – OWADVL

+0

你會推薦@OWADL –

回答

2

感謝Alex Cohn的建議,我找到了解決方案。此解決方案僅在HTC Sensation設備上進行測試,但對此設備有顯着影響。

我只是把startPreview之前休眠指令(),這將在onPictureTaken()被調用:

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

然後我運行一個測試100次,其中每次運行拍攝3張照片連續。 隨着睡眠,我在100次運行中沒有從startPreview()中得到任何錯誤。如果沒有睡眠,我需要重新打開相機78次,並在100次運行中重新啓動設備8次。

+0

非常有用的信息標記。這只是解決方法,但我認爲這種情況是可以的。我會再次測試並讓你知道結果! – Phuong