【问题标题】:Android: Camera Asynctask with Preview CallbackAndroid:带有预览回调的相机异步任务
【发布时间】:2013-10-07 04:07:04
【问题描述】:

我已经设法使用自定义滤镜(灰度、色调等)进行相机预览。通过操作 RGB 数组,然后将其绘制回画布,然后将其显示在表面视图中,此自定义过滤器与预览回调一起应用。

这样做的缺点是我的 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);
            }
        }
    }
};

非常感谢任何帮助! :) 提前谢谢大家!

【问题讨论】:

  • 您是否运行了任何测试来测量预览回调中花费的时间?我不确定问题是 onPreviewFrame 中的工作量
  • @LiorOhana 不,我没有。我不知道该怎么做,可以建议我测试它的方法吗?谢谢!

标签: android android-asynctask camera android-camera surfaceview


【解决方案1】:

也许我的回答对你来说为时已晚,但我正在研究同一个主题,所以我想无论如何我都会分享我的发现......

首先,如果在 AsyncTask 上调用 Camera 的“open”,然后该线程存在并抓住存在 - 我们真的不能期望回调来自它,可以吗?所以,如果我们想要回调 - 那么我们需要有一个线程,它的生命周期至少与我们想要回调的时间一​​样长。

但是等等,还有更多……Camera.PreviewCallback 的文档不是最清楚的,但其中一个不好的提示是“此回调在 event 线程 open(int) 上调用被叫来的。” “事件”线程是什么意思?好吧,这不是很清楚 - 但查看 Android 代码并进行试验 - 他们需要一个包含 Looper 的线程。可能细节太多了,但是在 Camera 构造函数(从 open 方法调用)中,有代码尝试首先获取当前线程的 Looper,如果它不存在 - 它会尝试获取主线程 Looper - 它继续存在用户界面线程。然后,Camera 使用 Handler 来调度回调,以及通过它初始化的 looper 的其他方法。现在您可能会明白为什么即使您从不同的线程打开相机(您的工作线程没有循环器),您也会在主线程上获得回调 - 所以相机默认使用主线程。

我从我的工作线程中得到了回调,我在这些方法中使用了 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 线程上运行了动画,在我将帧处理从主线程转移之前很生涩,但现在就像黄油一样流畅。

请注意,如果你杀死你的工作线程,那么你的回调当然也会停止,Camera(而不是处理程序)会抱怨你试图使用死线程。

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

【讨论】:

  • 是的,它完全按照你描述的方式工作,我测试过。谢谢!
  • 是否有可能,如果您 @leonman30 可以发布您的整个相机活动 + 支持课程?我正在寻找一个不错的流体非 hacky 相机解决方案.. 但我总是阻止 UI 线程:D
【解决方案2】:

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

【讨论】:

  • 天啊!所以不管onPreviewFrame总是在主线程上运行吗?该死! alrite 现在将改为使用 openGL!谢谢!
  • 我一直在寻找使用openGL显示相机预览的方法,但我找不到任何好的方法,即使我在尝试时找到了,我也无法应用自定义过滤器。你能建议一些方法吗?谢谢大哥!
  • 仅供参考:根据 Android 文档,onPreviewFrame 会在使用 camera.open() 获取相机的任何线程上调用。
  • @DaudArfin,不幸的是你错了,波士顿沃克是对的:来自其他方法的回调被传递到调用 open() 的线程的事件循环。如果此线程没有事件循环,则将回调传递到主应用程序事件循环。如果没有主应用程序事件循环,则不会传递回调。 https://developer.android.com/reference/android/hardware/Camera.PreviewCallback.html#onPreviewFrame(byte[], android.hardware.Camera)
  • @Simon Marquis 是的,我错过了使用事件循环器的尝试。我将编辑我的答案。
【解决方案3】:

我猜你使用AsyncTask 的实现是错误的:

According to the documentation,Camera 的回调在调用open() 的线程上调用。和so is onPreviewFrame 回调。 (因此onPreviewFrame 总是在主线程上执行是不正确的。)

您是opening AsyncTask 的 onPreExecute() 方法中的相机,is invoked on the UI thread 并没有像您预期的那样在后台线程上,因此相机的回调在主线程上执行。

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

【讨论】:

  • 是的,根据文档,您是对的,但据我所知,无论您在哪里调用,回调始终在主线程上运行。如果你有代码来证明我的错误,我会很乐意接受。
猜你喜欢
  • 1970-01-01
  • 2023-03-11
  • 1970-01-01
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多