最近在搞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.onOpened
manager.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;
}
//打开成功会调用到这里
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
mCameraDevice[cameraNum] = cameraDevice;
startPreview(cameraNum);
mCameraOpenCloseLock[cameraNum].release();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
mCameraOpenCloseLock[cameraNum].release();
cameraDevice.close();
mCameraDevice[cameraNum] = null;
}
@Override
public 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() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mPreviewSession[cameraNum] = session;
updatePreview(cameraNum);
}
@Override
public 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;
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireNextImage();
if (image !=null) {
frameID++;
int width = image.getWidth();//1920
int 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);
}
结束