实现效果
相机预览
切换相机
一、Android相机API
目前Android中有三种API,供开发者对相机进行操作
Camera1
Camera2
CameraX
本文重点在于说明Camera1
的使用
二、CameraManager
可以对相机的使用进行封装,只需要定义相机的相关操作,具体实现是Camera1
,或Camera2
,亦或是CameraX
,都无需外部进行修改,只需内部进行定义,首先,先定义相机的相关操作接口ICamera
相机接口
public interface ICamera {
/**
* 打开相机
*/
void openCamera(Activity activity, SurfaceTexture surfaceTexture);
/**
* 打开相机
*/
void openCamera(int facing, Activity activity, SurfaceTexture surfaceTexture);
/**
* 关闭相机
*/
void closeCamera();
/**
* 切换相机
*/
void switchCamera();
/**
* 切换相机
*/
void switchCamera(int facing);
/**
* 设置Facing
*/
void setCameraFacing(int facing);
/**
* 获取Facing
*/
int getCameraFacing();
/**
* 设置预览尺寸
*/
void setPreviewSize(Size cameraSize);
/**
* 获取预览尺寸
*/
Size getPreviewSize();
/**
* 设置显示旋转角度
*/
void setDisplayOrientation(int displayOrientation);
/**
* 获取显示旋转角度
*/
int getDisplayOrientation();
/**
* 释放相机
*/
void releaseCamera();
/**
* 添加相机回调
*/
void addOnCameraListener(OnCameraListener onCameraListener);
/**
* 移除相机回调
*/
void removeOnCameraListener(OnCameraListener onCameraListener);
/**
* 移除所有回调
*/
void removeAllOnCameraListener();
}
复制代码
注意到,里面有个相机回调OnCameraListener
,这是自定义的一个相机回调
public interface OnCameraListener {
/**
* 相机打开
*/
void onCameraOpened(Size cameraSize, int facing);
/**
* 相机关闭
*/
void onCameraClosed();
/**
* 相机异常
*/
void onCameraError(Exception e);
}
复制代码
接下来,我们新建CameraManager
,实现ICamera
接口
public class CameraManager implements ICamera {
/**
* Camera实现
*/
private final ICamera camera = new Camera1();
/**
* 后台线程
*/
private Handler handler;
private HandlerThread thread;
@Override
public void openCamera(Activity activity, SurfaceTexture surfaceTexture) {
postTask(new Runnable() {
@Override
public void run() {
camera.openCamera(activity, surfaceTexture);
}
});
}
@Override
public void openCamera(int facing, Activity activity, SurfaceTexture surfaceTexture) {
postTask(new Runnable() {
@Override
public void run() {
camera.openCamera(facing, activity, surfaceTexture);
}
});
}
@Override
public void closeCamera() {
postTask(new Runnable() {
@Override
public void run() {
camera.closeCamera();
}
});
}
@Override
public void switchCamera() {
postTask(new Runnable() {
@Override
public void run() {
camera.switchCamera();
}
});
}
@Override
public void switchCamera(int facing) {
postTask(new Runnable() {
@Override
public void run() {
camera.switchCamera(facing);
}
});
}
@Override
public void setCameraFacing(int facing) {
postTask(new Runnable() {
@Override
public void run() {
camera.setCameraFacing(facing);
}
});
}
@Override
public int getCameraFacing() {
return camera.getCameraFacing();
}
@Override
public void setPreviewSize(Size cameraSize) {
postTask(new Runnable() {
@Override
public void run() {
camera.setPreviewSize(cameraSize);
}
});
}
@Override
public Size getPreviewSize() {
return camera.getPreviewSize();
}
@Override
public void setDisplayOrientation(int displayOrientation) {
postTask(new Runnable() {
@Override
public void run() {
camera.setDisplayOrientation(displayOrientation);
}
});
}
@Override
public int getDisplayOrientation() {
return camera.getDisplayOrientation();
}
@Override
public void releaseCamera() {
if (handler == null) {
return;
}
postTask(new Runnable() {
@Override
public void run() {
camera.releaseCamera();
stopBackground();
}
});
}
@Override
public void addOnCameraListener(OnCameraListener onCameraListener) {
postTask(new Runnable() {
@Override
public void run() {
camera.addOnCameraListener(onCameraListener);
}
});
}
@Override
public void removeOnCameraListener(OnCameraListener onCameraListener) {
postTask(new Runnable() {
@Override
public void run() {
camera.removeOnCameraListener(onCameraListener);
}
});
}
@Override
public void removeAllOnCameraListener() {
postTask(new Runnable() {
@Override
public void run() {
camera.removeAllOnCameraListener();
}
});
}
/**
* 获取Handler
*/
private Handler getHandler() {
if (thread == null || handler == null) {
startBackground();
}
return handler;
}
/**
* 开启线程
*/
private void startBackground() {
stopBackground();
thread = new HandlerThread("camera_manager");
thread.start();
handler = new Handler(thread.getLooper());
}
/**
* 关闭线程
*/
private void stopBackground() {
if (thread != null) {
thread.quitSafely();
thread = null;
}
if (handler != null) {
handler = null;
}
}
/**
* 执行任务
*/
private void postTask(Runnable runnable) {
getHandler().post(runnable);
}
}
复制代码
注意到,CameraManager
内部开启了Handler
,目的是让相机的相关操作都在子线程进行
接下来可以具体实现Camera1
同样,我们新建Camera1
,并实现ICamera
接口
三、Camera1
全局变量
/**
* Camera
*/
private Camera camera;
/**
* 预览尺寸
*/
private Size cameraSize = new Size(-1, -1);
/**
* 默认摄像头方向
*/
private int facing = CameraConstants.facing.BACK;
/**
* 旋转角度
*/
private int displayOrientation = -1;
复制代码
打开相机
public void openCamera(int facing, Activity activity, SurfaceTexture surfaceTexture) {
// 先关闭相机
closeCamera();
// 判断是否存在摄像头
int cameraNum = Camera.getNumberOfCameras();
if (cameraNum <= 0) {
onCameraError(new IllegalStateException("camera num <= 0"));
return;
}
// 检查传入的facing
int cameraIndex = -1;
if (facing == CameraConstants.facing.BACK) {
cameraIndex = Camera.CameraInfo.CAMERA_FACING_BACK;
} else if (facing == CameraConstants.facing.FRONT) {
cameraIndex = Camera.CameraInfo.CAMERA_FACING_FRONT;
}
if (cameraIndex == -1) {
onCameraError(new IllegalStateException("camera facing exception"));
return;
}
// 判断摄像头个数,以决定使用哪个打开方式
if (cameraNum >= 2) {
camera = Camera.open(cameraIndex);
} else {
camera = Camera.open();
}
// 判断Camera是否初始化成功
if (camera == null) {
onCameraError(new IllegalStateException("camera is null"));
return;
}
this.facing = facing;
try {
// 获取摄像头参数
Camera.Parameters parameters = camera.getParameters();
List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();
if (cameraSize.getWidth() <= 0 || cameraSize.getHeight() <= 0) {
Camera.Size size = findTheBestSize(previewSizeList,
activity.getResources().getDisplayMetrics().widthPixels,
activity.getResources().getDisplayMetrics().heightPixels);
cameraSize.setWidth(size.width);
cameraSize.setHeight(size.height);
}
// 设置预览尺寸
parameters.setPreviewSize(cameraSize.getWidth(), cameraSize.getHeight());
// 这里设置使用的对焦模式
if (this.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
// 设置摄像头参数
camera.setParameters(parameters);
camera.setPreviewTexture(surfaceTexture);
if (displayOrientation < 0) {
displayOrientation = CameraUtils.getDisplayOrientation(activity, cameraIndex);
}
camera.setDisplayOrientation(displayOrientation);
camera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
onCameraOpened(cameraSize, Camera1.this.facing);
}
});
camera.startPreview();
} catch (IOException e) {
onCameraError(e);
}
}
复制代码
注意到,在对相机参数设置的时候,使用findTheBestSize
方法进行选择最佳的尺寸,该方法不是Android
原生提供的,是根据相机所支持的所有预览尺寸和当前屏幕的尺寸所计算得到的
private Camera.Size findTheBestSize(List<Camera.Size> sizeList, int screenW, int screenH) {
if (sizeList == null || sizeList.isEmpty()) {
throw new IllegalArgumentException();
}
Log.d(TAG, "findTheBestSize: screenW:" + screenW + " screenH:" + screenH);
Camera.Size bestSize = sizeList.get(0);
for (Camera.Size size : sizeList) {
int width = size.height;
int height = size.width;
Log.d(TAG, "findTheBestSize: width:" + width + " height:" + height);
float ratioW = (float) width / screenW;
float ratioH = (float) height / screenH;
Log.d(TAG, "findTheBestSize: ratioW:" + ratioW + " ratioH:" + ratioH);
if (ratioW == ratioH) {
bestSize = size;
break;
}
}
return bestSize;
}
复制代码
还注意到,设置相机旋转角度时,有用到CameraUtils.getDisplayOrientation
方法,该方法大概长这样
public static int getDisplayOrientation(Activity activity, int facing) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(facing, info);
int rotation = activity.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
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;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
return result;
}
复制代码
关闭相机
public void closeCamera() {
if (camera == null) {
return;
}
camera.stopPreview();
camera.release();
camera = null;
onCameraClosed();
}
复制代码
切换相机
public void switchCamera() {
if (facing == CameraConstants.facing.BACK) {
facing = CameraConstants.facing.FRONT;
} else {
facing = CameraConstants.facing.BACK;
}
}
复制代码
注意到,切换相机方法并没有真正的去切换相机,而是改变全局变量facing
的值,需要配合使用openCamera
方法后,进行切换相机的操作
接下来就是真正实现相机预览
四、TextureView
可以配合TextureView使用,进行相机相关操作
首先在xml中定义TextureView
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextureView
android:id="@+id/textureView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
复制代码
Activity中定义CameraManager
private final CameraManager cameraManager = new CameraManager();
复制代码
打开相机
private void openCamera() {
if (textureView.isAvailable()) {
cameraManager.openCamera(TextureViewCameraActivity.this, textureView.getSurfaceTexture());
} else {
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
cameraManager.openCamera(TextureViewCameraActivity.this, surface);
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
return true;
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
}
});
}
}
复制代码
关闭相机
private void closeCamera() {
cameraManager.closeCamera();
}
复制代码
openCamera
,可以选择在Activity
的onResume
回调中调用,也可以在某个按钮点击后进行调用
closeCamera
,可以选择在Activity
的onPause
回调中调用,也可以在某个按钮点击后进行调用
切换相机
private void switchCamera() {
cameraManager.switchCamera();
openCamera();
}
复制代码
切换相机,可以先调用cameraManager.switchCamera()
,接着调用openCamera
即可
五、SurfaceView
当然,我们也可以选择SurfaceView作为相机的预览的容器,我们只需要修改Camera1中的setPreviewTexture(surfaceTexure)
为setPreviewDisplay(surfaceHolder)
即可
六、GitHub
以上相关代码都可以在下面找到