我将Android相机的使用封装到一个单独的类(CameraAccess)中,该类使用不可见的SurfaceTexture作为预览并实现Camera.PreviewCallback。在这个回调中,我得到当前帧的字节数组,然后我想在多个视图/片段上使用它。
我的问题是生命周期管理。通常,摄像机在单个View中使用,并在onSurfaceCreated和onSurfaceDestroyed中初始化/释放(参见SurfaceHolder.Callback)。但在我的场景中,我需要在多个View上使用预览。每个视图都将自身添加为CameraAccess类的回调。
我想将CameraAccess作为成员放入Application类。但是当您按下主页按钮时,应用程序仍处于活动状态,但所有视图都被销毁。你会如何处理相机的初始化和释放?
答案 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);
}
}