android自定义相机取景框模仿微信抓取录像实现活体认证
出于费用成本的考虑,以及百度的图片识别度相对于华为准确度不那么高(驾驶证行驶证识别不太给力,反光时候,识别出差率比较高)。最近公司决定将身份认证模块由原来的调用第三方百度AI改为华为AI,由于百度android sdk的全部封装好了,只需要调用接口即可完成所有功能,而华为没有活体认证android相关sdk,需要开发者自己上传检测活体视频,为了便捷抓取头部部位,那么就需要开发者自定义相机取景框,上传录像小视频,调用华为认证接口去实现身份认证。
1、实现方式
camera2+MediaRecorder+TextureView+CircleVideoProgressBar+(上传抓取的录像文件+调取接口代码就不上了)
2、过程
1.一开始想用别人的PictureSelector(不了解的可以自行搜索)去实现,发现好像不能默认打开前置摄像头,且需求需要对取景框做调整,又不想把代码下载下来改源码(放弃)
2.直接通过intent 直接调用
/拍摄视频
int durationLimit = getVideoCaptureDurationLimit();
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
intent.putExtra(MediaStore.EXTRA_SIZE_LIMIT, sizeLimit);
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, durationLimit);
startActivityForResult(intent, REQUEST_CODE_TAKE_VIDEO);
这种也不行,EXTRA_VIDEO_QUALITY值设为0清晰度过低,模糊,设为1清晰度有了,但是5秒的视频占用空间高的有40M,直接上传的话体验不太好,在手机上压缩也需要20秒+,显然不符合要求(放弃)
3.网络寻觅了一会,好多都是互相抄的,以前老一套的东西为多,用Camera这个类做的,这个类已经过时许久了,且发现它并不兼容现在的多摄像头安卓高版本手机(弃用)
3.说说思路****
(1)自定义CircleTextureView,活体抓取录像需要的圆型相机取景框;
public class CircleTextureView extends TextureView {
private int mRatioWidth = 0;
private int mRatioHeight = 0;
public CircleTextureView(Context context) {
this(context, null);
}
public CircleTextureView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
float pxValue = WindowDispaly.dip2px(context, 150);
Rect rect = new Rect(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
outline.setRoundRect(rect, pxValue);
}
});
setClipToOutline(true);
}
/**
* @param width Relative horizontal size
* @param height Relative vertical size
*/
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}
(2)写一个相机管理类CameraController:
public class CameraController {
private static final String TAG = "CameraController";
private static Activity mActivity;
private String mFolderPath; //保存视频,图片的文件夹路径
private ImageReader mImageReader;
private HandlerThread mBackgroundThread;
private Handler mBackgroundHandler;
private CircleTextureView mTextureView;
//一个信号量以防止应用程序在 关闭相机之前退出。
private Semaphore mCameraOpenCloseLock = new Semaphore(1);
private String mCameraId;//当前相机的ID。
private CameraDevice mCameraDevice;
private Size mPreviewSize;
private CaptureRequest.Builder mPreviewRequestBuilder;
private CameraCaptureSession mCaptureSession;
private CaptureRequest mPreviewRequest;
private File mFile;//拍照储存文件
private Integer mSensorOrientation;
private CameraCaptureSession mPreviewSession;
private CaptureRequest.Builder mPreviewBuilder;
private static final int MAX_PREVIEW_WIDTH = 1920;//Camera2 API保证的最大预览宽度
private static final int MAX_PREVIEW_HEIGHT = 1080;//Camera2 API保证的最大预览高度
private boolean mFlashSupported;
private int mState = STATE_PREVIEW;
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90;
private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270;
private static final int STATE_PREVIEW = 0;//相机状态:显示相机预览。
private static final int STATE_WAITING_LOCK = 1;//相机状态:等待焦点被锁定。
private static final int STATE_WAITING_PRECAPTURE = 2;//等待曝光被Precapture状态。
//相机状态:等待曝光的状态是不是Precapture。
private static final int STATE_WAITING_NON_PRECAPTURE = 3;
private static final int STATE_PICTURE_TAKEN = 4;//相机状态:拍照。
private MediaRecorder mMediaRecorder;
private String mNextVideoAbsolutePath;
private String saveVideoTempPath;
private CameraController() {
}
private static class ClassHolder {
static CameraController mInstance = new CameraController();
}
public static CameraController getmInstance(Activity activity) {
mActivity = activity;
return ClassHolder.mInstance;
}
public void InitCamera(CircleTextureView textureView) {
this.mTextureView = textureView;
startBackgroundThread();
if (mTextureView.isAvailable()) {
openCamera(mTextureView.getWidth(), mTextureView.getHeight());
} else {
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
/**
* 设置需要保存文件的文件夹路径
* @param path
*/
public void setFolderPath(String path) {
this.mFolderPath = path;
File mFolder = new File(path);
if (!mFolder.exists()) {
mFolder.mkdirs();
Log.e(TAG, "文件夹不存在去创建");
} else {
Log.e(TAG, "文件夹已创建");
}
}
public String getFolderPath() {
return mFolderPath;
}
/**
* 拍照
*/
public void takePicture() {
lockFocus();
}
/**
* 开始录像
*/
public void startRecordingVideo() {
if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
return;
}
try {
closePreviewSession();
setUpMediaRecorder();
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
List<Surface> surfaces = new ArrayList<>();
//为相机预览设置曲面
Surface previewSurface = new Surface(texture);
surfaces.add(previewSurface);
mPreviewBuilder.addTarget(previewSurface);
//设置MediaRecorder的表面
Surface recorderSurface = mMediaRecorder.getSurface();
surfaces.add(recorderSurface);
mPreviewBuilder.addTarget(recorderSurface);
// 启动捕获会话
// 一旦会话开始,我们就可以更新UI并开始录制
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
mPreviewSession = cameraCaptureSession;
updatePreview();
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
//开启录像
mMediaRecorder.start();
}
});
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
}
}, mBackgroundHandler);
} catch (CameraAccessException | IOException e) {
e.printStackTrace();
}
}
/**
* 停止录像
*/
public void stopRecordingVideo() {
saveVideoTempPath = mNextVideoAbsolutePath;
Uri uri = Uri.fromFile(new File(mNextVideoAbsolutePath));
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);//通知更新图库
intent.setData(uri);
mActivity.sendBroadcast(intent);
mMediaRecorder.stop();
mMediaRecorder.reset();
mNextVideoAbsolutePath = null;
startPreview();
}
public String getSavePath() {
return saveVideoTempPath;
}
private void closePreviewSession() {
if (mPreviewSession != null) {
mPreviewSession.close();
mPreviewSession = null;
}
}
private void setUpMediaRecorder() throws IOException {
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); //设置用于录制的音源
//开始捕捉和编码数据到 setOutputFile(指定的文件)
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);