最近在搞CameraAPP需要将Camera2弄成一个后台服务,发现跟预览的Activity没多大变动只是加了Service,和一些简单的修改。之前的公司也用到Camera2,发现用到的时候还是蛮多的所以记录一下,代码在文章末尾
camera2的结构如下,主要是通过相机管理器(CameraManager)获得相机设备(CameraDevice),然后再开启一个控制相机的会话,最后发送 拍照、预览、录像等请求。
Camera流程大概如下
1.获取Camera2服务管理器,遍历摄像头,打开每一个摄像头
public void onCreate() {super.onCreate();mActivity = this;//获取Camera管理器CameraManager manager = (CameraManager) this.getSystemService("camera");try {String[] ids = manager.getCameraIdList();mCameraNum = ids.length ;mCameraIds = ids;} catch (CameraAccessException e) {e.printStackTrace();}mBackgroundThread = new HandlerThread[mCameraNum];mBackgroundHandler = new Handler[mCameraNum];mCameraOpenCloseLock = new Semaphore[mCameraNum];mPreviewBuilder = new CaptureRequest.Builder[mCameraNum];mPreviewSession =new CameraCaptureSession[mCameraNum];mCameraDevice = new CameraDevice[mCameraNum];mStateCallback =new StateCallback[mCameraNum];mVideoSize = new Size[mCameraNum];mPreviewSize = new Size[mCameraNum];mImageReader = new RefCountedAutoCloseable[mCameraNum];mFrameListener = new FrameListener[mCameraNum];for (int i = 0; i < mCameraNum; i++) {mCameraOpenCloseLock[i]= new Semaphore(1);mStateCallback[i] = new StateCallback(i);}int width =1920;int height = 1080;mOpenCameraList.clear();//遍历摄像头,分别打开for (int i = 0; i < mCameraNum; i++) {int CameraId = Integer.valueOf(mCameraIds[i]);mFrameListener[i] = new FrameListener(CameraId,this);if(CameraId < 100 ){Log.e(TAG,"只打开USB摄像头 skip:"+mCameraIds[i]);continue;}mOpenCameraList.add(CameraId);startBackgroundThread(i);打开摄像头openCamera(i,width, height);}//设置前台服务bindNotification("Launcher 进程");}
1.获取摄像头参数,设置图像回调,打开摄像头
private void openCamera(int cameraNum, int width, int height) {if (!hasPermissionsGranted(VIDEO_PERMISSIONS)) {requestVideoPermissions();return;}CameraManager manager = (CameraManager) this.getSystemService(this.CAMERA_SERVICE);try {if (!mCameraOpenCloseLock[cameraNum].tryAcquire(2500, TimeUnit.MILLISECONDS)) {throw new RuntimeException("Time out waiting to lock camera opening.");}Log.e(TAG, String.valueOf(manager.getCameraIdList().length));String cameraId = manager.getCameraIdList()[cameraNum];mCameraIds[cameraNum] = cameraId;// Choose the sizes for camera preview and video recording获取摄像头的参数CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);if (map == null) {throw new RuntimeException("Cannot get available preview/video sizes");}mVideoSize[cameraNum] = new Size(4096,2160);mPreviewSize[cameraNum] = new Size(4096,2160);int orientation = getResources().getConfiguration().orientation;configureTransform(cameraNum,width, height);if (mImageReader[cameraNum] == null || mImageReader[cameraNum].getAndRetain() == null) {//设置摄像头的图像回调mImageReader[cameraNum] = new RefCountedAutoCloseable<>(ImageReader.newInstance(mPreviewSize[cameraNum].getWidth(),mPreviewSize[cameraNum].getHeight(), ImageFormat.YUV_420_888, /*maxImages*/5));}if (mImageReader[cameraNum] !=null){mImageReader[cameraNum].get().setOnImageAvailableListener(mFrameListener[cameraNum], mBackgroundHandler[cameraNum]);}Log.d(TAG,"openCamera:"+cameraId);//打开摄像头,打开成功会调用到 mStateCallback.onOpenedmanager.openCamera(cameraId, mStateCallback[cameraNum], null);} catch (CameraAccessException e) {} catch (NullPointerException e) {} catch (InterruptedException e) {throw new RuntimeException("Interrupted while trying to lock camera opening.");}
}
class StateCallback extends CameraDevice.StateCallback {int cameraNum;public StateCallback(int cameraNum) {super();this.cameraNum = cameraNum;}//打开成功会调用到这里@Overridepublic void onOpened(@NonNull CameraDevice cameraDevice) {mCameraDevice[cameraNum] = cameraDevice;startPreview(cameraNum);mCameraOpenCloseLock[cameraNum].release();}@Overridepublic void onDisconnected(@NonNull CameraDevice cameraDevice) {mCameraOpenCloseLock[cameraNum].release();cameraDevice.close();mCameraDevice[cameraNum] = null;}@Overridepublic void onError(@NonNull CameraDevice cameraDevice, int error) {mCameraOpenCloseLock[cameraNum].release();cameraDevice.close();mCameraDevice[cameraNum] = null;}};
1.打开成功开始重定向输出对象到ImageReader
private void startPreview(final int cameraNum) {if (null == mCameraDevice[cameraNum] || null == mPreviewSize[cameraNum]) {return;}try {closePreviewSession(cameraNum);//设置Camera为预览输出mPreviewBuilder[cameraNum] = mCameraDevice[cameraNum].createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);List<Surface> surfaces;//获取mImageReader的SureFace ,就能通过ImageReader的图像回调获取数据mPreviewBuilder[cameraNum].addTarget(mImageReader[cameraNum].get().getSurface());surfaces = Arrays.asList(mImageReader[cameraNum].get().getSurface());mCameraDevice[cameraNum].createCaptureSession(surfaces,new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mPreviewSession[cameraNum] = session;updatePreview(cameraNum);}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {}}, mBackgroundHandler[cameraNum]);} catch (CameraAccessException e) {e.printStackTrace();}}
图像数据回调
class FrameListener implements ImageReader.OnImageAvailableListener{int cameraNum;Context context;public FrameListener(int cameraNum, Context context) {this.cameraNum = cameraNum;this.context = context;}long frameID = 0;@Overridepublic void onImageAvailable(ImageReader reader) {Image image = reader.acquireNextImage();if (image !=null) {frameID++;int width = image.getWidth();//1920int height = image.getHeight();//1080//摄像头1920*1080 y长度2073600,uv1036800//获取y数据地址ByteBuffer ybuffer = image.getPlanes()[0].getBuffer();//u数据地址,一般uv数据都是交替存放,所以这里包含有uv的数据ByteBuffer ubuffer = image.getPlanes()[1].getBuffer();//ByteBuffer vbuffer = image.getPlanes()[2].getBuffer();int yLen = ybuffer.remaining();int uLen = ubuffer.remaining();int vLen = vbuffer.remaining();byte[] yBytes = new byte[yLen];byte[] uBytes = new byte[uLen];//byte[] vBytes = new byte[vLen];byte[] yuvBytes = new byte[3110400];ybuffer.get(yBytes);ubuffer.get(uBytes);//vbuffer.get(vBytes);System.arraycopy(yBytes,0,yuvBytes,0,2073600);System.arraycopy(uBytes,0,yuvBytes,2073600,1036800);nativeReadImageBuf(width,height,image.getFormat(),yuvBytes, 3110400, mOpenCameraList.size(), mOpenCameraList.indexOf(cameraNum));image.close();}}}
当服务起来后会直接打开摄像头,获取回调数据
运行一段时间后服务自动停止,原因是没有和APP活动在同一个生命周期
使用
/*** 设置为前台服务* @param title*/
protected void bindNotification(String title){String CHANNEL_ONE_ID = "com.example.android.camera2videopushnew";String CHANNEL_ONE_NAME = "com.example.android.camera2videopushnew.name";Notification notification = null;NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ONE_ID,CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_LOW);notificationChannel.enableLights(false);notificationChannel.setLightColor(Color.RED);notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);manager.createNotificationChannel(notificationChannel);notification = new Notification.Builder(this,CHANNEL_ONE_ID).setContentTitle(title).setContentText(title).build();notification.flags |= Notification.FLAG_NO_CLEAR;startForeground(1, notification);}
结束
APP代码链接:【免费】AndroidCamera2后台服务APP资源-CSDN文库