2013-10-07 137 views
3

我设法使用自定义过滤器(灰度,色相等)工作来获取相机预览。通过操作RGB数组,然后将其绘制回画布,然后将其显示在表面视图中,此自定义过滤器将应用预览回调。Android:带预览回调的相机Asynctask

从这个缺点是我得到一个非常低的FPS。有了这个低FPS,如果我不使用Asynctask在后台线程中执行此操作,它在UI线程中做了太多工作。所以我尝试使用Asynctask进行相机操作(我的主要目的是即使从相机预览回调中获得大量工作,仍然可以使UI保持完美的工作状态)。

但即使在使用Asynctask之后,它并没有多大帮助。所以我想知道是我的实现是错误的,还是因为即使使用asynctask UI线程仍然会受到影响?我的代码

片断如下:

CameraActivity.java

public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    Log.d("ACTIVITY_LIFECYCLE","CameraActivity: onCreate"); 
    setContentView(R.layout.camera_layout); 
} 

@TargetApi(Build.VERSION_CODES.HONEYCOMB) 
@Override 
protected void onResume() { 
    Log.d("ACTIVITY_LIFECYCLE","CameraActivity: onResume"); 
    if(preview == null){ 
     preview = new CameraPreviewAsync(this,camera); 
     preview.execute(); 
    } 
    super.onResume(); 
} 

@Override 
protected void onPause() { 
    Log.d("ACTIVITY_LIFECYCLE","CameraActivity: onPause"); 
    if(preview!=null){ 
     preview.cancel(true); 
     camera = preview.getCamera(); 
     if(camera!=null){ 
      camera.stopPreview(); 
      camera.setPreviewCallback(null); 
      camera.release(); 
      camera = null; 
      preview.setCamera(camera); 
     } 
     preview = null; 
    } 
    super.onPause(); 
} 

@Override 
public void onDestroy(){ 
    Log.d("ACTIVITY_LIFECYCLE","CameraActivity: onDestroy"); 
    super.onDestroy(); 
} 

CameraPreviewAsync.java:

private final String TAG = "CameraPreviewAsync"; 

private CameraActivity camAct; 
private Camera mCamera; 
private int cameraId; 
private SurfaceView mSurfaceView; 
private SurfaceHolder mHolder; 

private boolean isPreviewRunning = false; 
private int[] rgbints; 
private int width; 
private int height; 
private Bitmap mBitmap; 

public CameraPreviewAsync(CameraActivity act, Camera cam){ 
    this.camAct = act; 
    this.mCamera = cam; 
    this.mSurfaceView = (SurfaceView) act.findViewById(R.id.surfaceView); 
} 

public void resetSurface(){ 
    if(mCamera!=null){ 
     mCamera.stopPreview(); 
     mCamera.setPreviewCallback(null); 
     mCamera.release(); 
     mCamera = null; 
    } 
    int tempId = R.id.surfaceView; 
    RelativeLayout buttonBar = (RelativeLayout) camAct.findViewById(R.id.buttonBar); 
    ((RelativeLayout) camAct.findViewById(R.id.preview)).removeAllViews(); 

    SurfaceView newSurface = new SurfaceView(camAct); 
    newSurface.setId(tempId); 
    RelativeLayout.LayoutParams layParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 
    layParams.alignWithParent = true; 
    newSurface.setLayoutParams(layParams); 
    ((RelativeLayout) camAct.findViewById(R.id.preview)).addView(newSurface); 
    ((RelativeLayout) camAct.findViewById(R.id.preview)).addView(buttonBar); 
} 

@Override 
protected void onPreExecute() { 
    //Things to do before doInBackground executed 
    Log.d(TAG,"onPreExecute"); 

    RelativeLayout.LayoutParams layParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 
    layParams.alignWithParent = true; 
    mSurfaceView.setLayoutParams(layParams); 

    //Check number of camera in the device, if less than 2 then remove swap button 
    if (Camera.getNumberOfCameras() < 2) { 
     ((RelativeLayout) camAct.findViewById(R.id.buttonBar)).removeViewAt(R.id.cameraSwap); 
    } 

    //Opening the camera 
    cameraId = findBackFacingCamera(); 
    if (cameraId < 0) { 
     cameraId = findFrontFacingCamera(); 
     if (cameraId < 0) 
      Toast.makeText(camAct, "No camera found.", Toast.LENGTH_LONG).show(); 
     else 
      mCamera = Camera.open(cameraId); 
    } else { 
     mCamera = Camera.open(cameraId); 
    } 

    //invalidate the menu bar and show menu appropriately 
    camAct.invalidateOptionsMenu(); 

    // get Camera parameters and set it to Auto Focus 
    if(mCamera!=null){ 
     Camera.Parameters params = mCamera.getParameters(); 
     List<String> focusModes = params.getSupportedFocusModes(); 
     if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { 
      // set the focus mode 
      params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); 
      // set Camera parameters 
      mCamera.setParameters(params); 
     } 
    } 

    super.onPreExecute(); 
} 

@Override 
protected Void doInBackground(Void... params) { 
    //Things to do in the background thread 
    Log.d(TAG,"doInBackground"); 

    mHolder = mSurfaceView.getHolder(); 
    mHolder.addCallback(surfaceCallback); 

    return null; 
}  

@Override 
protected void onPostExecute(Void values) { 
    //Things to do after doInBackground 
    Log.d(TAG,"onPostExecute"); 

} 

@Override 
protected void onCancelled(){ 
    super.onCancelled(); 
} 

/* 
* ************************************************************************************ 
* SURFACEHOLDER CALLBACK 
* ************************************************************************************ 
*/ 
SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() { 

    @Override 
    public void surfaceCreated(SurfaceHolder holder) { 
     Log.d(TAG,"surfaceCreated!!"); 
     if(CameraActivity.filterMode == CameraActivity.NORMAL_FILTER){ 
      try { 
       if (mCamera != null) { 
        mCamera.startPreview(); 
        mCamera.setPreviewDisplay(holder); 
       }else{ 
        Log.d(TAG,"CAMERA IS NULL in surfaceCreated!!"); 
       } 
      } catch (IOException exception) { 
       Log.e(TAG, "IOException caused by setPreviewDisplay()", exception); 
      } 
     }else{ 
      synchronized(mSurfaceView){ 
       if(isPreviewRunning){ 
        return; 
       }else{      

        mSurfaceView.setWillNotDraw(false); 
        if(mCamera!=null){ 
         isPreviewRunning = true; 
         Camera.Parameters p = mCamera.getParameters(); 
         List<Size> sizes = p.getSupportedPreviewSizes(); 

         Size size = p.getPreviewSize(); 
         width = size.width; 
         height = size.height; 

         p.setPreviewFormat(ImageFormat.NV21); 
         showSupportedCameraFormats(p); 
         mCamera.setParameters(p); 

         rgbints = new int[width * height]; 

         mCamera.startPreview(); 
         mCamera.setPreviewCallback(previewCallback); 
        } 
       } 
      } 
     } 
    } 

    @Override 
    public void surfaceDestroyed(SurfaceHolder holder) { 
     Log.d(TAG,"surfaceDestroyed!"); 

     if(CameraActivity.filterMode == CameraActivity.NORMAL_FILTER){ 
      if (mCamera != null) { 
       mCamera.stopPreview(); 
       isPreviewRunning = false; 
      } 
     }else{ 
      synchronized(mSurfaceView){ 
       if(mCamera!=null){ 
        mCamera.setPreviewCallback(null); 
        mCamera.stopPreview(); 
        isPreviewRunning = false; 
       } 
      } 
     } 
    } 

    @Override 
    public void surfaceChanged(SurfaceHolder holder, int format, int width, 
      int height) { 
     Log.d(TAG,"surfaceChanged!"); 
    } 
}; 


/* 
* ************************************************************************************ 
* CAMERA PREVIEW CALLBACK 
* ************************************************************************************ 
*/ 

Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() { 

    @Override 
    public void onPreviewFrame(byte[] data, Camera camera) { 
     if (!isPreviewRunning) 
      return; 
     Canvas resCanvas = null; 

     if (mHolder == null) { 
      return; 
     } 

     try { 
      synchronized (mHolder) { 
       resCanvas = mHolder.lockCanvas(null); 
       int resCanvasW = resCanvas.getWidth(); 
       int resCanvasH = resCanvas.getHeight(); 

       if(mBitmap == null){ 
        mBitmap = Bitmap.createBitmap (width, height, Bitmap.Config.ARGB_8888); 
       } 

       decodeYUV(rgbints, data, width, height); 

       Canvas canvas = new Canvas(mBitmap); 

       //Setting the filter 
       if(camAct.getCustomFilter().equalsIgnoreCase("NORMAL")) ;//don't change the rgb value 
       if(camAct.getCustomFilter().equalsIgnoreCase("GRAYSCALE")) rgbints = grayscale(rgbints); 
       if(camAct.getCustomFilter().equalsIgnoreCase("INVERT")) rgbints = invert(rgbints); 
       if(camAct.getCustomFilter().equalsIgnoreCase("BOOSTRED")) rgbints = boostColor(rgbints,1); 
       if(camAct.getCustomFilter().equalsIgnoreCase("BOOSTGREEN")) rgbints = boostColor(rgbints,2); 
       if(camAct.getCustomFilter().equalsIgnoreCase("BOOSTBLUE")) rgbints = boostColor(rgbints,3); 
       if(camAct.getCustomFilter().equalsIgnoreCase("NOISE")) rgbints = noise(rgbints); 
       if(camAct.getCustomFilter().equalsIgnoreCase("HUE")) rgbints = hue(rgbints); 
       if(camAct.getCustomFilter().equalsIgnoreCase("SATURATION")) rgbints = saturation(rgbints); 
       if(camAct.getCustomFilter().equalsIgnoreCase("ENGRAVE")) rgbints = engrave(rgbints); 
       if(camAct.getCustomFilter().equalsIgnoreCase("EMBOSS")) rgbints = emboss(rgbints); 

       // draw the decoded image, centered on canvas 
       canvas.drawBitmap(rgbints, 0, width, 0,0, width, height, false, null); 

       resCanvas.drawBitmap (mBitmap, resCanvasW-((width+resCanvasW)>>1), resCanvasH-((height+resCanvasH)>>1),null); 
      } 
     } catch (Exception e){ 
      e.printStackTrace(); 
     } finally { 
      // do this in a finally so that if an exception is thrown 
      // during the above, we don't leave the Surface in an 
      // inconsistent state 
      if (resCanvas != null) { 
       mHolder.unlockCanvasAndPost(resCanvas); 
      } 
     } 
    } 
}; 

任何帮助,非常感谢! :)先谢谢你们!

+1

您是否运行过任何测试来衡量在预览回调中花费的时间?我不确定问题是在预览框 –

+0

@LiorOhana nope的工作量我没有。我不知道该怎么做,能否告诉我一种测试方法?谢谢! OMG! – CodingBird

回答

4

其他方法的回调被传递到调用open()的线程的事件循环。如果此线程没有事件循环,则回调将传递到主应用程序事件循环。如果没有主应用程序事件循环,则不传递回调。 Source

+0

OMG!那么无论onPreviewFrame会一直在主线程上运行?该死的! alrite现在会在openGL上看起来!谢谢! – CodingBird

+0

我一直在寻找使用openGL显示相机预览的方法,但是我找不到任何好的,即使我尝试时找到一个,但我无法应用自定义过滤器。你能提出一些方法吗?谢啦! – CodingBird

+1

仅供参考:从Android文档中,在任何线程使用camera.open()获取相机时调用onPreviewFrame。 –

4

我想用AsyncTask是错误的实现:

According to the documentation,相机的回调调用了哪些称为open()的线程上。和so isonPreviewFrame回调。 (因此,它是不正确的,onPreviewFrame总是在主线程上执行。)

您是openingAsyncTask的onPreExecute()方法的相机,这is invoked on the UI thread,而不是在后台线程,你可能预期,因此相机的回调会在主线程上执行。

我想你应该在AsyncTask的doInBackground()方法中打开相机。

+0

根据文件你是正确的,但迄今为止,我已经看到你在哪里打电话,回调总是在主线程上运行。如果你有代码证明我从字面上证明我错了,我会很乐意接受它。 –

21

也许我的回答是为时已晚,你,但我是研究同一主题的这样想我会反正大家分享我的发现......

首先,如果相机的“开放”的呼吁的AsyncTask然后该线程就存在并被占用而存在 - 我们无法真正期望回调来自它,我们可以。所以,如果我们想要回调 - 那么我们需要有一个线程,至少在我们需要回调的时候能够继续。

但是等等,还有更多... Camera.PreviewCallback的文档并不是最清晰的,但其中一个不好的提示是“这个回调在事件上调用了线程open(int)被调用。 “ “事件”线程是什么意思?嗯,它不是很清楚 - 但看着Android代码和试验 - 他们需要的是一个包含Looper的线程。可能太多的细节,但在Camera构造函数(从open方法调用)中,有代码尝试首先获取当前线程的Looper,如果它不存在 - 它会尝试获取主线程looper - UI线程。相机然后使用处理程序来分派回调,以及通过它初始化的活套的其他方法。现在你可能会明白为什么你在主线程中得到回调,即使你从另一个线程打开了相机 - 你的工作线程没有活套 - 所以相机默认使用主线程。

我的回调从我的工作线程,我用HandlerThread的方法沿着这些线路的工作:

private void startCamera() { 
    if (mCameraThread == null) { 
     mCameraThread = new HandlerThread(CAMERA_THREAD_NAME); 
     mCameraThread.start(); 
     mCameraHandler = new Handler(mCameraThread.getLooper()); 
    } 
    mCameraHandler.post(new Runnable() { 
     @Override 
     public void run() { 
      try { 
       mCamera = Camera.open(); 

...

我使用的调试,以确认我的onPreviewFrame根本上运行工作者线程。我还在UI线程上运行了动画,在我将帧处理从主线程转换开来之前,它是生涩的,但现在像黄油一样流畅。

请注意,如果你杀死你的工作线程,那么当然你的回调也将停止,相机(而非Handler)会抱怨你尝试使用死线程。

顺便说一句,作为一种替代解决方案,当然可以在主线程上调用回调函数,但是可以将帧数据的处理委托给单独的线程。

+0

是的,它的工作方式与您所描述的完全相同,我对它进行了测试。谢谢! – CanC

+2

是否有可能,如果你@ leonman30可以发布你的整个相机活动+支持类?我正在寻找一个很好的流体非hacky相机解决方案..但我总是阻止UI线程:D – Wicked161089