本来思路很简单的,通过opengles3.0的pbo来实现将Camera2的数据读取到内存,可是却遇到了天坑,当设置的分辨率太大的时候调用glReadPixels会导致surface不接受数据。
然后就换ImageReader来实现,可还是遇到分辨率过大的问题,虽然不会导致surface不接收数据,可是会导致渲染线程卡顿
总结下来就是,手机太垃圾了,承受不了高分辨率渲染
最后还是选ImageReader来实现JavaCamera2View,因为是直接改JavaCameraView的代码,就没用opengl的pbo了
JavaCamera2View
public class JavaCamera2View extends CameraBridgeViewBase{ private static final String TAG = "JavaCamera2View"; private Mat[] mFrameChain; private int mChainIdx = 0; private Thread mThread; private boolean mStopThread; protected JavaCameraFrame[] mCameraFrame; private String mCameraId; private CameraManager mCameraManager; private CameraCaptureSession mCameraCaptureSession; private CameraDevice mCameraDevice; private CaptureRequest.Builder builder; private Handler mCameraHandler; private Handler mImageHandler; private android.util.Size[] mSizes; private ImageReader mImageReader; public JavaCamera2View(Context context) { super(context, -1); } public JavaCamera2View(Context context, AttributeSet attrs) { super(context, attrs); } protected void initializeCamera(int width, int height) { synchronized (this) { initCamera2(); //获取摄像头支持的分辨率 android.util.Size mPreviewSize = getPreferredPreviewSize(mSizes, width, height); mFrameWidth = mPreviewSize.getWidth(); mFrameHeight = mPreviewSize.getHeight(); if (width < mFrameWidth || height < mFrameHeight) mScale = Math.min(((float)height)/mFrameHeight, ((float)width)/mFrameWidth); else mScale = 0; if (mFpsMeter != null) { mFpsMeter.setResolution(mFrameWidth, mFrameHeight); } mFrameChain = new Mat[2]; mFrameChain[0] = new Mat(mFrameHeight + (mFrameHeight/2), mFrameWidth, CvType.CV_8UC1); mFrameChain[1] = new Mat(mFrameHeight + (mFrameHeight/2), mFrameWidth, CvType.CV_8UC1); AllocateCache(); mCameraFrame = new JavaCameraFrame[2]; mCameraFrame[0] = new JavaCameraFrame(mFrameChain[0], mFrameWidth, mFrameHeight); mCameraFrame[1] = new JavaCameraFrame(mFrameChain[1], mFrameWidth, mFrameHeight); } } protected void releaseCamera() { synchronized (this) { if (mCameraCaptureSession != null) { mCameraCaptureSession.getDevice().close(); mCameraCaptureSession.close(); mCameraCaptureSession = null; } if (mFrameChain != null) { mFrameChain[0].release(); mFrameChain[1].release(); } if (mCameraFrame != null) { mCameraFrame[0].release(); mCameraFrame[1].release(); } } } private boolean mCameraFrameReady = false; @Override protected boolean connectCamera(int width, int height) { /* 1. We need to instantiate camera * 2. We need to start thread which will be getting frames */ /* First step - initialize camera connection */ initializeCamera(width, height); mCameraFrameReady = false; /* now we can start update thread */ Log.d(TAG, "Starting processing thread"); mStopThread = false; mThread = new Thread(new CameraWorker()); mThread.start(); return true; } @Override protected void disconnectCamera() { /* 1. We need to stop thread which updating the frames * 2. Stop camera and release it */ Log.d(TAG, "Disconnecting from camera"); try { mStopThread = true; Log.d(TAG, "Notify thread"); synchronized (this) { this.notify(); } Log.d(TAG, "Wating for thread"); if (mThread != null) mThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } finally { mThread = null; } /* Now release camera */ releaseCamera(); mCameraFrameReady = false; } private class JavaCameraFrame implements CvCameraViewFrame { @Override public Mat gray() { return mYuvFrameData.submat(0, mHeight, 0, mWidth); } @Override public Mat rgba() { Imgproc.cvtColor(mYuvFrameData, mRgba, Imgproc.COLOR_YUV2RGBA_NV21, 4); return mRgba; } public JavaCameraFrame(Mat Yuv420sp, int width, int height) { super(); mWidth = width; mHeight = height; mYuvFrameData = Yuv420sp; mRgba = new Mat(); } public void release() { mRgba.release(); } private Mat mYuvFrameData; private Mat mRgba; private int mWidth; private int mHeight; } private class CameraWorker implements Runnable { @Override public void run() { openCamera2(); do { boolean hasFrame = false; synchronized (JavaCamera2View.this) { try { while (!mCameraFrameReady && !mStopThread) { JavaCamera2View.this.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } if (mCameraFrameReady) { mChainIdx = 1 - mChainIdx; mCameraFrameReady = false; hasFrame = true; } } if (!mStopThread && hasFrame) { if (!mFrameChain[1 - mChainIdx].empty()) deliverAndDrawFrame(mCameraFrame[1 - mChainIdx]); } } while (!mStopThread); Log.d(TAG, "Finish processing thread"); } } private void initCamera2() { HandlerThread cameraHandlerThread = new HandlerThread("Camera2"); cameraHandlerThread.start(); mCameraHandler = new Handler(cameraHandlerThread.getLooper()); HandlerThread imageHandlerThread = new HandlerThread("image"); imageHandlerThread.start(); mImageHandler = new Handler(imageHandlerThread.getLooper()); mCameraManager = (CameraManager) getContext().getSystemService(Context.CAMERA_SERVICE); try { String[] CameraIdList = mCameraManager.getCameraIdList(); mCameraId = CameraIdList[0]; CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(mCameraId); characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map != null) { mSizes = map.getOutputSizes(SurfaceTexture.class); } } catch (CameraAccessException e) { e.printStackTrace(); } } private void openCamera2() { if (PermissionChecker.checkSelfPermission(getContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { try { mCameraManager.openCamera(mCameraId, stateCallback, mCameraHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } } private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice cameraDevice) { mCameraDevice = cameraDevice; takePreview(); } @Override public void onDisconnected(@NonNull CameraDevice cameraDevice) { if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } } @Override public void onError(@NonNull CameraDevice cameraDevice, int i) { } }; private void takePreview() { try { //如果是用opengl渲染,ImageReader只是拿来给opencv计算用 //宽高不能设置太高,高了会影响opengl渲染线程 //Image的宽高和摄像头支持的宽高有关 //举个例子,设置600x600,Image的宽高却是640x480 //640x480这个宽高是摄像头支持宽高,基本是会小于设定值 mImageReader = ImageReader.newInstance(mFrameWidth, mFrameHeight, ImageFormat.YUV_420_888, 1); mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader imageReader) { Image image = imageReader.acquireNextImage(); if (image == null) { return; } byte[] data = ImageUtils.YUV_420_888toNV21(image); synchronized (JavaCamera2View.this) { mFrameChain[mChainIdx].put(0, 0, data); mCameraFrameReady = true; JavaCamera2View.this.notify(); } image.close(); } }, mImageHandler); builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(mImageReader.getSurface()); mCameraDevice.createCaptureSession(Arrays.asList(mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { if (null == mCameraDevice) return; mCameraCaptureSession = cameraCaptureSession; builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); CaptureRequest previewRequest = builder.build(); try { mCameraCaptureSession.setRepeatingRequest(previewRequest, null, mCameraHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { } }, mCameraHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } //取最低16:9分辨率 private android.util.Size getPreferredPreviewSize(android.util.Size[] sizes, int width, int height) { ListcollectorSizes = new ArrayList<>(); for (android.util.Size option : sizes) { //计算啥,只有最低分辨率的才最流畅... //取16:9分辨率 int w = option.getWidth(); int h = option.getHeight(); int s = w * 100 / h; if (s == 177) { collectorSizes.add(option); } } if (collectorSizes.size() > 0) { return Collections.min(collectorSizes, new Comparator () { @Override public int compare(android.util.Size s1, android.util.Size s2) { return Long.signum(s1.getWidth() * s1.getHeight() - s2.getWidth() * s2.getHeight()); } }); } return sizes[0]; } }
yuv420转n21的代码参考
Android: YUV_420_888编码Image转换为I420和NV21格式byte数组
ImageUtils
public class ImageUtils { public static byte[] imageToByteArray(Image image) { byte[] data = null; if (image.getFormat() == ImageFormat.JPEG) { Image.Plane[] planes = image.getPlanes(); ByteBuffer buffer = planes[0].getBuffer(); data = new byte[buffer.capacity()]; buffer.get(data); return data; } else if (image.getFormat() == ImageFormat.YUV_420_888) { data = NV21toJPEG( YUV_420_888toNV21(image), image.getWidth(), image.getHeight()); } return data; } public static byte[] YUV_420_888toNV21(Image image) { Rect crop = image.getCropRect(); int format = image.getFormat(); int width = crop.width(); int height = crop.height(); Image.Plane[] planes = image.getPlanes(); byte[] data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8]; byte[] rowData = new byte[planes[0].getRowStride()]; int channelOffset = 0; int outputStride = 1; for (int i = 0; i < planes.length; i++) { switch (i) { case 0: channelOffset = 0; outputStride = 1; break; case 1: channelOffset = width * height + 1; outputStride = 2; break; case 2: channelOffset = width * height; outputStride = 2; break; } ByteBuffer buffer = planes[i].getBuffer(); int rowStride = planes[i].getRowStride(); int pixelStride = planes[i].getPixelStride(); int shift = (i == 0) ? 0 : 1; int w = width >> shift; int h = height >> shift; buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift)); for (int row = 0; row < h; row++) { int length; if (pixelStride == 1 && outputStride == 1) { length = w; buffer.get(data, channelOffset, length); channelOffset += length; } else { length = (w - 1) * pixelStride + 1; buffer.get(rowData, 0, length); for (int col = 0; col < w; col++) { data[channelOffset] = rowData[col * pixelStride]; channelOffset += outputStride; } } if (row < h - 1) { buffer.position(buffer.position() + rowStride - length); } } } return data; } public static byte[] NV21toJPEG(byte[] nv21, int width, int height) { ByteArrayOutputStream out = new ByteArrayOutputStream(); YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null); yuv.compressToJpeg(new Rect(0, 0, width, height), 100, out); return out.toByteArray(); } }
转来转去挺麻烦的,也不知道怎么直接将yuv直接转成Bitmap