之前对于 camera 的了解只限于 camera1 的简单使用上,知道打开相机后要对相机参数进行设置,如最佳预览大小,对焦等。最近的工作涉及到了 camera 的使用上。所以对 android camera1 和 2 都做了一定的了解。
首先需要知道的是在 Android 手机上,相机对于手机竖屏来说都是倾斜 90° 的,所以需要对相机的预览都是要旋转 90°(对于竖屏来说,如果手机横屏那就需要对旋转的角度再判断)。这里就要开始吐槽了,也不知道这么多年为什么一直不改。
对于 camera1 的使用,大概步骤就是:
- 打开一个摄像头
- 对打开的摄像头进行参数的设置,这里主要设置的参数有界面预览大小,拍照的照片大小,对焦模式,输出数据的模式,预览界面的旋转角度,预览画面设置
- 设置预览的回调
- 真正开始预览
/**
* Point point = new Point(surfaceView.getWidth(), surfaceView.getHeight());
* WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
* int a = windowManager.getDefaultDisplay().getOrientation();
* Log.d("TAG", "onCreate is " + a);
* Log.d("TAG", "onCreate is " + windowManager.getDefaultDisplay().getRotation());
* Camera1Helper camera1Helper = new Camera1Helper.Builder()
* .previewOn(surfaceView)
* .previewViewSize(point)
* .rotation(a)
* .cameraListener(new CameraListener() {
* @Override
* public void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror) {
* Log.d("TAG", "onCameraOpened");
* }
*
* @Override
* public void onPreview(byte[] data, Camera camera) {
* Log.d("TAG", "onPreview");
* }
*
* @Override
* public void onCameraClosed() {
* Log.d("TAG", "onCameraClosed");
* }
*
* @Override
* public void onCameraError(Exception e) {
* Log.d("TAG", "onCameraError");
* }
*
* @Override
* public void onCameraConfigurationChanged(int cameraID, int displayOrientation) {
* Log.d("TAG", "onCameraConfigurationChanged");
* }
* }).build();
* camera1Helper.start();
*/
public class Camera1Helper implements Camera.PreviewCallback {
private static final String TAG = "Camera1Helper";
private Camera mCamera;
private int mCameraId;
private Point previewViewSize;
private View previewDisplayView;
private Camera.Size previewSize;
private Point specificPreviewSize;
private int displayOrientation = 0;
private int rotation;
private int additionalRotation;
private boolean isMirror = false;
private Integer specificCameraId = null;
private CameraListener cameraListener;
private boolean isDisplay;
private final SurfaceTexture surfaceTexture = new SurfaceTexture(0);
private Camera1Helper(Camera1Helper.Builder builder) {
specificCameraId = builder.specificCameraId;
cameraListener = builder.cameraListener;
if (builder.previewDisplayView == null) {
isDisplay = false;
} else {
isDisplay = true;
previewDisplayView = builder.previewDisplayView;
rotation = builder.rotation;
additionalRotation = builder.additionalRotation;
previewViewSize = builder.previewViewSize;
specificPreviewSize = builder.previewSize;
if (builder.previewDisplayView instanceof TextureView) {
isMirror = builder.isMirror;
} else if (isMirror) {
throw new RuntimeException("mirror is effective only when the preview is on a textureView");
}
}
init();
}
public void init() {
if (isDisplay) {
if (previewDisplayView instanceof TextureView) {
((TextureView) this.previewDisplayView).setSurfaceTextureListener(textureListener);
} else if (previewDisplayView instanceof SurfaceView) {
((SurfaceView) previewDisplayView).getHolder().addCallback(surfaceCallback);
}
if (isMirror) {
previewDisplayView.setScaleX(-1);
}
}
}
public void start() {
synchronized (this) {
//相机数量为2则打开1,1则打开0,相机ID 1为前置,0为后置
mCameraId = Camera.getNumberOfCameras() - 1;
//若指定了相机ID且该相机存在,则打开指定的相机
if (specificCameraId != null && specificCameraId <= mCameraId) {
mCameraId = specificCameraId;
}
//没有相机
if (mCameraId == -1) {
if (cameraListener != null) {
cameraListener.onCameraError(new Exception("camera not found"));
}
return;
}
if (mCamera == null) {
mCamera = Camera.open(mCameraId);
}
if (isDisplay) {
displayOrientation = getCameraOri(rotation);
mCamera.setDisplayOrientation(displayOrientation);
}
try {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewFormat(ImageFormat.NV21);
if (isDisplay) {
//预览大小设置
previewSize = parameters.getPreviewSize();
List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
if (supportedPreviewSizes != null && supportedPreviewSizes.size() > 0) {
previewSize = getBestSupportedSize(supportedPreviewSizes, previewViewSize);
}
parameters.setPreviewSize(previewSize.width, previewSize.height);
}
//对焦模式设置
List<String> supportedFocusModes = parameters.getSupportedFocusModes();
if (supportedFocusModes != null && supportedFocusModes.size() > 0) {
if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
} else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
} else if (supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
}
mCamera.setParameters(parameters);
if (isDisplay) {
if (previewDisplayView instanceof TextureView) {
mCamera.setPreviewTexture(((TextureView) previewDisplayView).getSurfaceTexture());
} else {
mCamera.setPreviewDisplay(((SurfaceView) previewDisplayView).getHolder());
}
}else {
mCamera.setPreviewTexture(surfaceTexture);
}
mCamera.setPreviewCallback(this);
mCamera.startPreview();
if (cameraListener != null) {
cameraListener.onCameraOpened(mCamera, mCameraId, displayOrientation, isMirror);
}
} catch (Exception e) {
if (cameraListener != null) {
cameraListener.onCameraError(e);
}
}
}
}
private int getCameraOri(int rotation) {
int degrees = rotation * 90;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
default:
break;
}
additionalRotation /= 90;
additionalRotation *= 90;
degrees += additionalRotation;
int result;
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(mCameraId, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360;
} else {
result = (info.orientation - degrees + 360) % 360;
}
return result;
}
private void stop() {
synchronized (this) {
if (mCamera == null) {
return;
}
try {
mCamera.setPreviewCallback(null);
mCamera.setPreviewDisplay(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
if (cameraListener != null) {
cameraListener.onCameraClosed();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public boolean isStopped() {
synchronized (this) {
return mCamera == null;
}
}
public void release() {
stop();
previewDisplayView = null;
specificCameraId = null;
cameraListener = null;
previewViewSize = null;
specificPreviewSize = null;
previewSize = null;
}
private Camera.Size getBestSupportedSize(List<Camera.Size> sizes, Point previewViewSize) {
if (sizes == null || sizes.size() == 0 || previewViewSize == null) {
return mCamera.getParameters().getPreviewSize();
}
Camera.Size bestSize = sizes.get(0);
float previewViewRatio = (float) previewViewSize.x / (float) previewViewSize.y;
if (previewViewRatio > 1) {
previewViewRatio = 1 / previewViewRatio;
}
boolean isNormalRotate = (additionalRotation % 180 == 0);
for (Camera.Size s : sizes) {
if (specificPreviewSize != null && specificPreviewSize.x == s.width && specificPreviewSize.y == s.height) {
return s;
}
if (isNormalRotate) {
if (Math.abs((s.height / (float) s.width) - previewViewRatio) < Math.abs(bestSize.height / (float) bestSize.width - previewViewRatio)) {
bestSize = s;
}
} else {
if (Math.abs((s.width / (float) s.height) - previewViewRatio) < Math.abs(bestSize.width / (float) bestSize.height - previewViewRatio)) {
bestSize = s;
}
}
}
return bestSize;
}
public List<Camera.Size> getSupportedPreviewSizes() {
if (mCamera == null) {
return null;
}
return mCamera.getParameters().getSupportedPreviewSizes();
}
public List<Camera.Size> getSupportedPictureSizes() {
if (mCamera == null) {
return null;
}
return mCamera.getParameters().getSupportedPictureSizes();
}
@Override
public void onPreviewFrame(byte[] nv21, Camera camera) {
if (cameraListener != null) {
cameraListener.onPreview(nv21, camera);
}
}
private TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
start();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
Log.i(TAG, "onSurfaceTextureSizeChanged: " + width + " " + height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
stop();
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
};
private SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
stop();
}
};
public void changeDisplayOrientation(int rotation) {
if (mCamera != null) {
this.rotation = rotation;
displayOrientation = getCameraOri(rotation);
mCamera.setDisplayOrientation(displayOrientation);
if (cameraListener != null) {
cameraListener.onCameraConfigurationChanged(mCameraId, displayOrientation);
}
}
}
public interface CameraListener {
/**
* 当打开时执行
* @param camera 相机实例
* @param cameraId 相机ID
* @param displayOrientation 相机预览旋转角度
* @param isMirror 是否镜像显示
*/
void onCameraOpened(Camera camera, int cameraId, int displayOrientation, boolean isMirror);
/**
* 预览数据回调
* @param data 预览数据
* @param camera 相机实例
*/
void onPreview(byte[] data, Camera camera);
/**
* 当相机关闭时执行
*/
void onCameraClosed();
/**
* 当出现异常时执行
* @param e 相机相关异常
*/
void onCameraError(Exception e);
/**
* 属性变化时调用
* @param cameraID 相机ID
* @param displayOrientation 相机旋转方向
*/
void onCameraConfigurationChanged(int cameraID, int displayOrientation);
}
public static final class Builder {
/**
* 预览显示的view,目前仅支持surfaceView和textureView
*/
private View previewDisplayView;
/**
* 是否镜像显示,只支持textureView
*/
private boolean isMirror;
/**
* 指定的相机ID
*/
private Integer specificCameraId;
/**
* 事件回调
*/
private CameraListener cameraListener;
/**
* 屏幕的长宽,在选择最佳相机比例时用到
*/
private Point previewViewSize;
/**
* 传入getWindowManager().getDefaultDisplay().getRotation()的值即可
*/
private int rotation;
/**
* 指定的预览宽高,若系统支持则会以这个预览宽高进行预览
*/
private Point previewSize;
/**
* 额外的旋转角度(用于适配一些定制设备)
*/
private int additionalRotation;
public Builder() {
}
public Builder previewOn(View val) {
if (val instanceof SurfaceView || val instanceof TextureView) {
previewDisplayView = val;
return this;
} else {
throw new RuntimeException("you must preview on a textureView or a surfaceView");
}
}
public Builder isMirror(boolean val) {
isMirror = val;
return this;
}
public Builder previewSize(Point val) {
previewSize = val;
return this;
}
public Builder previewViewSize(Point val) {
previewViewSize = val;
return this;
}
public Builder rotation(int val) {
rotation = val;
return this;
}
public Builder additionalRotation(int val) {
additionalRotation = val;
return this;
}
public Builder specificCameraId(Integer val) {
specificCameraId = val;
return this;
}
public Builder cameraListener(CameraListener val) {
cameraListener = val;
return this;
}
public Camera1Helper build() {
if (previewViewSize == null) {
Log.e(TAG, "previewViewSize is null, now use default previewSize");
}
if (cameraListener == null) {
Log.e(TAG, "cameraListener is null, callback will not be called");
}
if (previewDisplayView == null) {
Log.e(TAG, "previewDisplayView is null ,you can preview on a textureView or a surfaceView");
}
return new Camera1Helper(this);
}
}
}
因为我要使用到的是无界面拿到预览数据,和平常的还有点不一样
相比较于 camera1 ,camera2 的使用和 1 没有半毛钱关系,而且巨多回调,一眼看去几乎都是回调
- 打开相机服务
- 设置摄像头的配置信息,预览大小设置等,设置相机设备的可用状态发生变化回调,设置相机设备的闪光灯的 Torch 模式可用状态发生变化回调等等
- 打开摄像头
- 摄像头打开回调成功后创建预览的请求,预览设置可以设置预览显示、图像格式、图像分辨率、传感器控制、闪光灯控制、3A(自动对焦-AF、自动曝光-AE和自动白平衡-AWB)控制等
- 预览的请求成功后设置重复请求就基本上差不多了
/**
* Point point = new Point(surfaceView.getWidth(), surfaceView.getHeight());
* Camera2Helper camera2Helper = new Camera2Helper.Builder()
* .previewOn(surfaceView)
* .previewViewSize(point)
* .cameraListener(new Camera2Helper.CameraListener() {
* @Override
* public void onPreview(byte[] y, byte[] u, byte[] v, Size previewSize, int stride) {
* Log.d("TAG", "onPreview");
* }
*
* @Override
* public void onCameraClosed() {
* Log.d("TAG", "onCameraClosed");
* }
*
* @Override
* public void onCameraError(Exception e) {
* Log.d("TAG", "onCameraError");
* }
*
* @Override
* public void onCameraOpen() {
* Log.d("TAG", "onCameraOpen");
* }
* }).build();
* camera2Helper.start(MainActivity.this);
*/
public class Camera2Helper {
private static final String TAG = "Camera2Helper";
private int mCameraId;
private Point previewViewSize;
private View previewDisplayView;
private Size previewSize;
private Point specificPreviewSize;
private ImageReader mImageReader;
private Handler mBackgroundHandler;
private HandlerThread mBackgroundThread;
private CameraDevice mCameraDevice;
private CaptureRequest.Builder mPreviewRequestBuilder;
private CameraCaptureSession mCaptureSession;
private Integer specificCameraId = null;
private CameraListener cameraListener;
private CameraManager mCameraManager;
private boolean isDisplay;
//当一个相机设备的可用状态发生变化时回调
private CameraManager.AvailabilityCallback mAvailabilityCallback = new CameraManager.AvailabilityCallback() {
@Override
public void onCameraAvailable(@NonNull String cameraId) {
super.onCameraAvailable(cameraId);
}
@Override
public void onCameraUnavailable(@NonNull String cameraId) {
super.onCameraUnavailable(cameraId);
}
};
//当一个相机设备的闪光灯的 Torch 模式可用状态发生变化时回调
//通过 setTorchMode(String cameraId, boolean enabled) 方法设置 Torch 模式。
private CameraManager.TorchCallback mTorchCallback = new CameraManager.TorchCallback() {
@Override
public void onTorchModeUnavailable(@NonNull String cameraId) {
super.onTorchModeUnavailable(cameraId);
}
@Override
public void onTorchModeChanged(@NonNull String cameraId, boolean enabled) {
super.onTorchModeChanged(cameraId, enabled);
}
};
private Camera2Helper(Camera2Helper.Builder builder) {
specificCameraId = builder.specificCameraId;
cameraListener = builder.cameraListener;
if (builder.previewDisplayView == null) {
isDisplay = false;
} else {
isDisplay = true;
previewDisplayView = builder.previewDisplayView;
previewViewSize = builder.previewViewSize;
specificPreviewSize = builder.previewSize;
}
}
public void start(Context context) {
synchronized (this) {
mCameraManager = (CameraManager)
context.getSystemService(Context.CAMERA_SERVICE);
// mCameraManager.registerAvailabilityCallback(mAvailabilityCallback, mHandler);
// mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
try {
mCameraId = mCameraManager.getCameraIdList().length - 1;
//若指定了相机ID且该相机存在,则打开指定的相机
if (specificCameraId != null && specificCameraId <= mCameraId) {
mCameraId = specificCameraId;
}
//没有相机
if (mCameraId == -1) {
if (cameraListener != null) {
cameraListener.onCameraError(new Exception("camera not found"));
}
return;
}
// 这个摄像头的配置信息
CameraCharacteristics characteristics =
mCameraManager.getCameraCharacteristics(String.valueOf(mCameraId));
StreamConfigurationMap map = characteristics.get
(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
previewSize = getBestSupportedSize(previewViewSize, new ArrayList<Size>(Arrays.asList(map.getOutputSizes(SurfaceTexture.class))));
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
if (!isDisplay) {
mImageReader = ImageReader.newInstance(previewSize.getWidth(),
previewSize.getHeight(),
ImageFormat.YUV_420_888, 2
);
mImageReader.setOnImageAvailableListener(new OnImageAvailableListenerImpl(), mBackgroundHandler);
}
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
return;
}
mCameraManager.openCamera(String.valueOf(mCameraId), mDeviceStateCallback, mBackgroundHandler);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void stop() {
try {
if (null != mCaptureSession) {
mCaptureSession.stopRepeating();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void release() {
cameraListener.onCameraClosed();
stop();
try {
if (null != mCaptureSession) {
mCaptureSession.close();
mCaptureSession = null;
}
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != mImageReader) {
mImageReader.close();
mImageReader = null;
}
if (null != cameraListener) {
cameraListener = null;
}
//当回调不再需要时一定要注销,否则将带来内存泄漏的问题。
// mCameraManager.unregisterAvailabilityCallback(mAvailabilityCallback);
// mCameraManager.unregisterTorchCallback(mTorchCallback);
} catch (Exception e) {
e.printStackTrace();
}
}
private void selectCeameraId() {
try {
//获取可用相机
String[] cameraIdList = mCameraManager.getCameraIdList();
for (String cameraId : cameraIdList) {
//获取摄像头参数
CameraCharacteristics mCameraCharacteristics = mCameraManager.getCameraCharacteristics(cameraId);
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private CameraDevice.StateCallback mDeviceStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
cameraListener.onCameraOpen();
mCameraDevice = cameraDevice;
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
cameraListener.onCameraError(new Exception("CameraDevice.StateCallback error is " + error));
mCameraDevice = null;
}
};
private void createCameraPreviewSession() {
try {
/**
* TEMPLATE_PREVIEW:预览模式
* TEMPLATE_STILL_CAPTURE:拍照模式
* TEMPLATE_RECORD:视频录制模式
* TEMPLATE_VIDEO_SNAPSHOT:视频截图模式
* TEMPLATE_MANUAL:手动配置参数模式
*
*/
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
//可以设置图像格式、图像分辨率、传感器控制、闪光灯控制、3A(自动对焦-AF、自动曝光-AE和自动白平衡-AWB)控制等
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
if (isDisplay) {
Surface surface;
if (previewDisplayView instanceof SurfaceView) {
surface = ((SurfaceView) previewDisplayView).getHolder().getSurface();
mPreviewRequestBuilder.addTarget(surface);
} else {
SurfaceTexture texture = ((TextureView) previewDisplayView).getSurfaceTexture();
texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
surface = new Surface(texture);
mPreviewRequestBuilder.addTarget(surface);
}
mCameraDevice.createCaptureSession(Arrays.asList(surface), mCaptureStateCallback, mBackgroundHandler);
} else {
mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
mCameraDevice.createCaptureSession(Arrays.asList(mImageReader.getSurface()), mCaptureStateCallback, mBackgroundHandler);
}
} catch (CameraAccessException e) {
e.printStackTrace();
cameraListener.onCameraError(e);
}
}
private CameraCaptureSession.StateCallback mCaptureStateCallback = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
if (null == mCameraDevice) {
return;
}
mCaptureSession = session;
try {
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
}
};
private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
}
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
}
};
private Size getBestSupportedSize(Point previewViewSize, ArrayList<Size> sizes) {
Size bestSize = sizes.get(0);
if (previewViewSize == null) {
return bestSize;
}
float previewViewRatio = (float) previewViewSize.x / (float) previewViewSize.y;
if (previewViewRatio > 1) {
previewViewRatio = 1 / previewViewRatio;
}
for (Size s : sizes) {
if (specificPreviewSize != null && specificPreviewSize.x == s.getWidth() && specificPreviewSize.y == s.getHeight()) {
return s;
}
if (Math.abs((s.getHeight() / (float) s.getWidth()) - previewViewRatio) < Math.abs(bestSize.getHeight() / (float) bestSize.getWidth() - previewViewRatio)) {
bestSize = s;
}
}
return bestSize;
}
private class OnImageAvailableListenerImpl implements ImageReader.OnImageAvailableListener {
private byte[] y;
private byte[] u;
private byte[] v;
@Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireNextImage();
Image.Plane[] planes = image.getPlanes();
if (y == null) {
y = new byte[planes[0].getBuffer().limit() - planes[0].getBuffer().position()];
u = new byte[planes[1].getBuffer().limit() - planes[1].getBuffer().position()];
v = new byte[planes[2].getBuffer().limit() - planes[2].getBuffer().position()];
}
if (image.getPlanes()[0].getBuffer().remaining() == y.length) {
// 分别填到 yuv
planes[0].getBuffer().get(y);
planes[1].getBuffer().get(u);
planes[2].getBuffer().get(v);
}
if (cameraListener != null) {
cameraListener.onPreview(y, u, v, previewSize, planes[0].getRowStride());
}
image.close();
}
}
private void yuvToNv21(byte[] y, byte[] u, byte[] v, byte[] nv21, int stride, int height) {
System.arraycopy(y, 0, nv21, 0, y.length);
// 注意,若length值为 y.length * 3 / 2 会有数组越界的风险,需使用真实数据长度计算
int length = y.length + u.length / 2 + v.length / 2;
int uIndex = 0, vIndex = 0;
for (int i = stride * height; i < length; i += 2) {
nv21[i] = v[vIndex];
nv21[i + 1] = u[uIndex];
vIndex += 2;
uIndex += 2;
}
}
public interface CameraListener {
/**
* 预览数据回调
*
* @param y 预览数据,Y分量
* @param u 预览数据,U分量
* @param v 预览数据,V分量
* @param previewSize 预览尺寸
* @param stride 步长
*/
void onPreview(byte[] y, byte[] u, byte[] v, Size previewSize, int stride);
/**
* 当相机关闭时执行
*/
void onCameraClosed();
/**
* 当出现异常时执行
*
* @param e 相机相关异常
*/
void onCameraError(Exception e);
void onCameraOpen();
}
public static final class Builder {
/**
* 预览显示的view,目前仅支持surfaceView和textureView
*/
private View previewDisplayView;
/**
* 指定的相机ID
*/
private Integer specificCameraId;
/**
* 事件回调
*/
private CameraListener cameraListener;
/**
* 屏幕的长宽,在选择最佳相机比例时用到
*/
private Point previewViewSize;
/**
* 指定的预览宽高,若系统支持则会以这个预览宽高进行预览
*/
private Point previewSize;
public Builder() {
}
public Camera2Helper.Builder previewOn(View val) {
if (val instanceof SurfaceView || val instanceof TextureView) {
previewDisplayView = val;
return this;
} else {
throw new RuntimeException("you must preview on a textureView or a surfaceView");
}
}
public Camera2Helper.Builder previewSize(Point val) {
previewSize = val;
return this;
}
public Camera2Helper.Builder previewViewSize(Point val) {
previewViewSize = val;
return this;
}
public Camera2Helper.Builder specificCameraId(Integer val) {
specificCameraId = val;
return this;
}
public Camera2Helper.Builder cameraListener(CameraListener val) {
cameraListener = val;
return this;
}
public Camera2Helper build() {
if (previewViewSize == null) {
Log.e(TAG, "previewViewSize is null, now use default previewSize");
}
if (cameraListener == null) {
Log.e(TAG, "cameraListener is null, callback will not be called");
}
if (previewDisplayView == null) {
Log.e(TAG, "previewDisplayView is null ,you can preview on a textureView or a surfaceView");
}
return new Camera2Helper(this);
}
}
}
可能是太难用了,google 自己都看不下去了,现在又搞了一个 camerax 来简化调用相机的难度,但是 camerax 没有具体了解过,这里不做介绍。但是从代码量上来说 100 行不到就可以实现预览了,对于 camera2 来说,简直不要太友好。