多个视图上的摄像头预览 - 初始化/释放处理

时间:2013-12-27 12:21:30

标签: android camera

我将Android相机的使用封装到一个单独的类(CameraAccess)中,该类使用不可见的SurfaceTexture作为预览并实现Camera.PreviewCallback。在这个回调中,我得到当前帧的字节数组,然后我想在多个视图/片段上使用它。

我的问题是生命周期管理。通常,摄像机在单个View中使用,并在onSurfaceCreated和onSurfaceDestroyed中初始化/释放(参见SurfaceHolder.Callback)。但在我的场景中,我需要在多个View上使用预览。每个视图都将自身添加为CameraAccess类的回调。

我想将CameraAccess作为成员放入Application类。但是当您按下主页按钮时,应用程序仍处于活动状态,但所有视图都被销毁。你会如何处理相机的初始化和释放?

1 个答案:

答案 0 :(得分:1)

以下是我如何解决它。

我将所有逻辑放入一个名为CameraAccess的单例类中。所有想要访问相机/预览的视图都将实现CameraFrameCallback并在onSurfaceCreated / onSurfaceDestroyed中添加/删除它们自己。每当接收到帧时,它将由封装的帧类处理,该类负责颜色空间转换和转换为位图。它还负责为Bitmap分配内存,然后当它们将内容绘制到画布上时,所有视图都会使用它。正如您将看到的,我使用OpenCV进行简单的色彩空间转换。 OpenCV Mat(Matrix类)也适用于以后的图像处理。

public class CameraAccess implements Camera.PreviewCallback,
        LoaderCallbackInterface {

    // see http://developer.android.com/guide/topics/media/camera.html for more
    // details

    final static String TAG = "CameraAccess";
    Context context;
    int cameraIndex; // example: CameraInfo.CAMERA_FACING_FRONT or
    // CameraInfo.CAMERA_FACING_BACK
    Camera mCamera;
    int mFrameWidth;
    int mFrameHeight;
    Mat mFrame;
    CameraAccessFrame mCameraFrame;
    List<CameraFrameCallback> mCallbacks = new ArrayList<CameraFrameCallback>();
    boolean mOpenCVloaded;
    byte mBuffer[]; // needed to avoid OpenCV error:
                    // "queueBuffer: BufferQueue has been abandoned!"

    private static CameraAccess mInstance;

    public static CameraAccess getInstance(Context context, int cameraIndex) {
        if (mInstance != null)
            return mInstance;

        mInstance = new CameraAccess(context, cameraIndex);
        return mInstance;
    }

    private CameraAccess(Context context, int cameraIndex) {
        this.context = context;
        this.cameraIndex = cameraIndex;

        if (!OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_7, context,
                this)) {
            Log.e(TAG, "Cannot connect to OpenCVManager");
        } else
            Log.d(TAG, "OpenCVManager successfully connected");
    }

    private boolean checkCameraHardware() {
        if (context.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_CAMERA)) {
            // this device has a camera
            return true;
        } else {
            // no camera on this device
            return false;
        }
    }

    public static Camera getCameraInstance(int cameraIndex) {
        Camera c = null;
        try {
            c = Camera.open(cameraIndex); // attempt to get a
                                            // Camera
                                            // instance

            Log.d(TAG, "Camera opened. index: " + cameraIndex);
        } catch (Exception e) {
            // Camera is not available (in use or does not exist)
        }
        return c; // returns null if camera is unavailable
    }

    public void addCallback(CameraFrameCallback callback) {
        // we don't care if the callback is already in the list
        this.mCallbacks.add(callback);

        if (mCamera != null)
            callback.onCameraInitialized(mFrameWidth, mFrameHeight);
        else if (mOpenCVloaded)
            connectCamera();
    }

    public void removeCallback(CameraFrameCallback callback) {
        boolean removed = false;
        do {
            // someone might have added the callback multiple times
            removed = this.mCallbacks.remove(callback);

            if (removed)
                callback.onCameraReleased();

        } while (removed == true);

        if (mCallbacks.size() == 0)
            releaseCamera();
    }

    @Override
    public void onPreviewFrame(byte[] frame, Camera arg1) {
        mFrame.put(0, 0, frame);
        mCameraFrame.invalidate();

        for (CameraFrameCallback callback : mCallbacks)
            callback.onFrameReceived(mCameraFrame);

        if (mCamera != null)
            mCamera.addCallbackBuffer(mBuffer);
    }

    private void connectCamera() {
        synchronized (this) {
            if (true) {// checkCameraHardware()) {
                mCamera = getCameraInstance(cameraIndex);

                Parameters params = mCamera.getParameters();
                List<Camera.Size> sizes = params.getSupportedPreviewSizes();

                // Camera.Size previewSize = sizes.get(0);
                Collections.sort(sizes, new PreviewSizeComparer());
                Camera.Size previewSize = null;
                for (Camera.Size s : sizes) {
                    if (s == null)
                        break;

                    previewSize = s;
                }

                // List<Integer> formats = params.getSupportedPictureFormats();
                // params.setPreviewFormat(ImageFormat.NV21);

                params.setPreviewSize(previewSize.width, previewSize.height);
                mCamera.setParameters(params);

                params = mCamera.getParameters();

                mFrameWidth = params.getPreviewSize().width;
                mFrameHeight = params.getPreviewSize().height;

                int size = mFrameWidth * mFrameHeight;
                size = size
                        * ImageFormat
                                .getBitsPerPixel(params.getPreviewFormat()) / 8;
                mBuffer = new byte[size];

                mFrame = new Mat(mFrameHeight + (mFrameHeight / 2),
                        mFrameWidth, CvType.CV_8UC1);
                mCameraFrame = new CameraAccessFrame(mFrame, mFrameWidth,
                        mFrameHeight);

                SurfaceTexture texture = new SurfaceTexture(0);

                try {
                    mCamera.setPreviewTexture(texture);
                    mCamera.addCallbackBuffer(mBuffer);
                    mCamera.setPreviewCallbackWithBuffer(this);
                    mCamera.startPreview();

                    Log.d(TAG, "Camera preview started");
                } catch (Exception e) {
                    Log.d(TAG,
                            "Error starting camera preview: " + e.getMessage());
                }

                for (CameraFrameCallback callback : mCallbacks)
                    callback.onCameraInitialized(mFrameWidth, mFrameHeight);
            }
        }
    }

    private void releaseCamera() {
        synchronized (this) {
            if (mCamera != null) {
                mCamera.stopPreview();
                mCamera.setPreviewCallback(null);

                mCamera.release();

                Log.d(TAG, "Preview stopped and camera released");
            }
            mCamera = null;

            if (mFrame != null) {
                mFrame.release();
            }

            if (mCameraFrame != null) {
                mCameraFrame.release();
            }

            for (CameraFrameCallback callback : mCallbacks)
                callback.onCameraReleased();
        }
    }

    private class CameraAccessFrame implements CameraFrame {
        private Mat mYuvFrameData;
        private Mat mRgba;
        private int mWidth;
        private int mHeight;
        private Bitmap mCachedBitmap;
        private boolean mRgbaConverted;
        private boolean mBitmapConverted;

        @Override
        public Mat gray() {
            return mYuvFrameData.submat(0, mHeight, 0, mWidth);
        }

        @Override
        public Mat rgba() {
            if (!mRgbaConverted) {
                Imgproc.cvtColor(mYuvFrameData, mRgba,
                        Imgproc.COLOR_YUV2BGR_NV12, 4);
                mRgbaConverted = true;
            }
            return mRgba;
        }

        @Override
        public Bitmap toBitmap() {
            if (mBitmapConverted)
                return mCachedBitmap;

            Mat rgba = this.rgba();
            Utils.matToBitmap(rgba, mCachedBitmap);
            mBitmapConverted = true;
            return mCachedBitmap;
        }

        public CameraAccessFrame(Mat Yuv420sp, int width, int height) {
            super();
            mWidth = width;
            mHeight = height;
            mYuvFrameData = Yuv420sp;
            mRgba = new Mat();

            this.mCachedBitmap = Bitmap.createBitmap(width, height,
                    Bitmap.Config.ARGB_8888);
        }

        public void release() {
            mRgba.release();
            mCachedBitmap.recycle();
        }

        public void invalidate() {
            mRgbaConverted = false;
            mBitmapConverted = false;
        }
    };

    public interface CameraFrameCallback {
        void onCameraInitialized(int frameWidth, int frameHeight);

        void onFrameReceived(CameraFrame frame);

        void onCameraReleased();
    }

    @Override
    public void onManagerConnected(int status) {
        mOpenCVloaded = true;

        if (mCallbacks.size() > 0)
            connectCamera();
    }

    @Override
    public void onPackageInstall(int operation,
            InstallCallbackInterface callback) {
    }

    private class PreviewSizeComparer implements Comparator<Camera.Size> {
        @Override
        public int compare(Size arg0, Size arg1) {
            if (arg0 != null && arg1 == null)
                return -1;
            if (arg0 == null && arg1 != null)
                return 1;

            if (arg0.width < arg1.width)
                return -1;
            else if (arg0.width > arg1.width)
                return 1;
            else
                return 0;
        }

    }
}

public class CameraCanvasView extends SurfaceView implements CameraFrameCallback, SurfaceHolder.Callback {

    Context context;
    CameraAccess mCamera;
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Rect mBackgroundSrc = new Rect();

    public CameraCanvasView(Context context) {
        super(context);

        this.context = context;

        SurfaceHolder sh = this.getHolder();
        sh.addCallback(this);

        setFocusable(true);

        this.mCamera = CameraAccess.getInstance(context,
                CameraInfo.CAMERA_FACING_BACK);
    }

    @Override
    public void onCameraInitialized(int frameWidth, int frameHeight) {
    }

    @Override
    public void onFrameReceived(CameraFrame frame) {
        this.setBackgroundImage(frame.toBitmap());
    }

    @Override
    public void onCameraReleased() {

        setBackgroundImage(null);
    }

    @Override
    public void surfaceCreated(SurfaceHolder arg0) {
        this.setWillNotDraw(false);
        this.mCamera.addCallback(this);
    }

    @Override
    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder arg0) {
        this.mCamera.removeCallback(this);
    }

    public void setBackgroundImage(Bitmap image) {
        this.mBackground = image;

        if (image != null)
            this.mBackgroundSrc.set(0, 0, image.getWidth(), image.getHeight());
        else
            this.mBackgroundSrc.setEmpty();

        invalidate();
    }

    @Override
    public void onDraw(Canvas canvas) {
        canvas.drawColor(Color.BLACK);

        if (mBackground != null && !mBackground.isRecycled())
            canvas.drawBitmap(mBackground, mBackgroundSrc, boundingBox, paint);
    }
}