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() {@Overridepublic 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() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {mPreviewSession = cameraCaptureSession;updatePreview();mActivity.runOnUiThread(new Runnable() {@Overridepublic void run() {//开启录像mMediaRecorder.start();}});}@Overridepublic 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);//设置在录制过程中产生的输出文件的格式mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); if (mNextVideoAbsolutePath == null || mNextVideoAbsolutePath.isEmpty()) {mNextVideoAbsolutePath = getVideoFilePath();}mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);//设置输出文件的路径mMediaRecorder.setVideoEncodingBitRate(10000000);//设置录制的视频编码比特率mMediaRecorder.setVideoFrameRate(25);//设置要捕获的视频帧速率//设置要捕获的视频的宽度和高度mMediaRecorder.setVideoSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);//设置视频编码器,用于录制mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);//设置audio的编码格式int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();Log.d(TAG, "setUpMediaRecorder: " + rotation);switch (mSensorOrientation) {case SENSOR_ORIENTATION_DEFAULT_DEGREES:mMediaRecorder.setOrientationHint(ORIENTATIONS.get(rotation));break;case SENSOR_ORIENTATION_INVERSE_DEGREES:mMediaRecorder.setOrientationHint(ORIENTATIONS.get(2));break;}mMediaRecorder.prepare();
}private String getVideoFilePath() {return getFolderPath() + "/" + System.currentTimeMillis() + ".mp4";
}private void updatePreview() {if (null == mCameraDevice) {return;}try {mPreviewBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);HandlerThread thread = new HandlerThread("CameraPreview");thread.start();mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}
}private void lockFocus() {//创建文件mFile = new File(getFolderPath() + "/" + getNowDate() + ".jpg");try {mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CameraMetadata.CONTROL_AF_TRIGGER_START);mState = STATE_WAITING_LOCK;mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}
}/*** 获取当前时间,用来给文件夹命名*/
private String getNowDate() {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return simpleDateFormat.format(new Date());
}private TextureView.SurfaceTextureListener mSurfaceTextureListener= new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {openCamera(width, height);}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {return false;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {}
};private void startBackgroundThread() {mBackgroundThread = new HandlerThread("CameraBackground");mBackgroundThread.start();mBackgroundHandler = new Handler(mBackgroundThread.getLooper());}/*** 开启相机*/
private void openCamera(int width, int height) {//设置相机输出setUpCameraOutputs(width, height);//配置变换configureTransform(width, height);CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);try {if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {throw new RuntimeException("Time out waiting to lock camera opening.");}if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {return;}//打开相机预览manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();} catch (InterruptedException e) {throw new RuntimeException("Interrupted while trying to lock camera opening.", e);}
}/*** CameraDevice状态更改时被调用。*/
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice cameraDevice) {// 打开相机时调用此方法。 在这里开始相机预览。mCameraOpenCloseLock.release();mCameraDevice = cameraDevice;//创建CameraPreviewSessionstartPreview();}@Overridepublic void onDisconnected(@NonNull CameraDevice cameraDevice) {mCameraOpenCloseLock.release();cameraDevice.close();mCameraDevice = null;}@Overridepublic void onError(@NonNull CameraDevice cameraDevice, int error) {mCameraOpenCloseLock.release();cameraDevice.close();mCameraDevice = null;mActivity.finish();}};/*** 为相机预览创建新的CameraCaptureSession*/
private void startPreview() {try {SurfaceTexture texture = mTextureView.getSurfaceTexture();assert texture != null;// 将默认缓冲区的大小配置为我们想要的相机预览的大小。 设置分辨率texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());// 预览的输出Surface。Surface surface = new Surface(texture);//设置了一个具有输出Surface的CaptureRequest.Builder。mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);mPreviewRequestBuilder.addTarget(surface);//创建一个CameraCaptureSession来进行相机预览。mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {// 相机已经关闭if (null == mCameraDevice) {return;}// 会话准备好后,我们开始显示预览mCaptureSession = cameraCaptureSession;// 自动对焦应mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);// 最终开启相机预览并添加事件mPreviewRequest = mPreviewRequestBuilder.build();try {mCaptureSession.setRepeatingRequest(mPreviewRequest,mCaptureCallback, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {}}, null);} catch (CameraAccessException e) {e.printStackTrace();}
}/*** 处理与JPEG捕获有关的事件*/
private CameraCaptureSession.CaptureCallback mCaptureCallback= new CameraCaptureSession.CaptureCallback() {//处理private void process(CaptureResult result) {switch (mState) {case STATE_PREVIEW: {//预览状态break;}case STATE_WAITING_LOCK: {//等待对焦Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);if (afState == null) {captureStillPicture();} else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {// 某些设备上的控制状态可以为空Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {mState = STATE_PICTURE_TAKEN;captureStillPicture();} else {runPrecaptureSequence();}}break;}case STATE_WAITING_PRECAPTURE: {//某些设备上的控制状态可以为空Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);if (aeState == null ||aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {mState = STATE_WAITING_NON_PRECAPTURE;}break;}case STATE_WAITING_NON_PRECAPTURE: {// CONTROL_AE_STATE can be null on some devicesInteger aeState = result.get(CaptureResult.CONTROL_AE_STATE);if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {mState = STATE_PICTURE_TAKEN;captureStillPicture();}break;}}}@Overridepublic void onCaptureProgressed(@NonNull CameraCaptureSession session,@NonNull CaptureRequest request,@NonNull CaptureResult partialResult) {process(partialResult);}@Overridepublic void onCaptureCompleted(@NonNull CameraCaptureSession session,@NonNull CaptureRequest request,@NonNull TotalCaptureResult result) {process(result);}};/*** 拍摄静态图片。*/
private void captureStillPicture() {try {if (null == mCameraDevice) {return;}// 这是用来拍摄照片的CaptureRequest.Builder。final CaptureRequest.Builder captureBuilder =mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);captureBuilder.addTarget(mImageReader.getSurface());// 使用相同的AE和AF模式作为预览。captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);setAutoFlash(captureBuilder);// 方向int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));CameraCaptureSession.CaptureCallback CaptureCallback= new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureCompleted(@NonNull CameraCaptureSession session,@NonNull CaptureRequest request,@NonNull TotalCaptureResult result) {showToast("图片地址: " + mFile);Log.d(TAG, mFile.toString());unlockFocus();}};//停止连续取景mCaptureSession.stopRepeating();//捕获图片mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);} catch (CameraAccessException e) {e.printStackTrace();}
}private void showToast(final String text) {mActivity.runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(mActivity, text, Toast.LENGTH_SHORT).show();}});
}private int getOrientation(int rotation) {return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
}private void setAutoFlash(CaptureRequest.Builder requestBuilder) {if (mFlashSupported) {requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);}
}/*** 解锁焦点*/
private void unlockFocus() {try {// 重置自动对焦mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);setAutoFlash(mPreviewRequestBuilder);mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,mBackgroundHandler);// 将相机恢复正常的预览状态。mState = STATE_PREVIEW;// 打开连续取景模式mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback,mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}
}/*** 运行捕获静止图像的预捕获序列。 当我们从{@link #()}的{@link #mCaptureCallback}中得到响应时,应该调用此方法。*/
private void runPrecaptureSequence() {try {// 这是如何告诉相机触发的。mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);// 告诉 mCaptureCallback 等待preapture序列被设置.mState = STATE_WAITING_PRECAPTURE;mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}
}private ImageReader.OnImageAvailableListener mOnImageAvailableListener= new ImageReader.OnImageAvailableListener() {@Overridepublic void onImageAvailable(ImageReader reader) {//等图片可以得到的时候获取图片并保存mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));}
};/*** 设置与相机相关的成员变量。** @param width  相机预览的可用尺寸宽度* @param height 相机预览的可用尺寸的高度*/
private void setUpCameraOutputs(int width, int height) {CameraManager manager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);//获取摄像头列表try {for (String cameraId : manager.getCameraIdList()) {CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);//不使用后置摄像Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) {continue;//不结束循环,只跳出本次循环}StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);if (map == null) {continue;}//对于静态图像拍照, 使用最大的可用尺寸Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),new CompareSizesByArea());mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, 2);mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);//获取手机旋转的角度以调整图片的方向int displayRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);boolean swappedDimensions = false;switch (displayRotation) {case Surface.ROTATION_0:case Surface.ROTATION_180://横屏if (mSensorOrientation == 90 || mSensorOrientation == 270) {swappedDimensions = true;}break;case Surface.ROTATION_90:case Surface.ROTATION_270://竖屏if (mSensorOrientation == 0 || mSensorOrientation == 180) {swappedDimensions = true;}break;default:Log.e(TAG, "Display rotation is invalid: " + displayRotation);}Point displaySize = new Point();mActivity.getWindowManager().getDefaultDisplay().getSize(displaySize);int rotatedPreviewWidth = width;int rotatedPreviewHeight = height;int maxPreviewWidth = displaySize.x;int maxPreviewHeight = displaySize.y;if (swappedDimensions) {rotatedPreviewWidth = height;rotatedPreviewHeight = width;maxPreviewWidth = displaySize.y;maxPreviewHeight = displaySize.x;}if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {maxPreviewWidth = MAX_PREVIEW_WIDTH;}if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {maxPreviewHeight = MAX_PREVIEW_HEIGHT;}DisplayMetrics displayMetrics = new DisplayMetrics();mActivity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);int widthPixels = displayMetrics.widthPixels;int heightPixels = displayMetrics.heightPixels;Log.d(TAG, "widthPixels: " + widthPixels + "____heightPixels:" + heightPixels);//尝试使用太大的预览大小可能会超出摄像头的带宽限制mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,maxPreviewHeight, largest);//我们将TextureView的宽高比与我们选择的预览大小相匹配。这样设置不会拉伸,但是不能全屏展示int orientation = mActivity.getResources().getConfiguration().orientation;if (orientation == Configuration.ORIENTATION_LANDSCAPE) {//横屏mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight());Log.d(TAG, "横屏: " + "width:" + mPreviewSize.getWidth() + "____height:" + mPreviewSize.getHeight());} else {// 竖屏mTextureView.setAspectRatio(widthPixels, heightPixels);Log.d(TAG, "竖屏: " + "____height:" + mPreviewSize.getHeight() + "width:" + mPreviewSize.getWidth());}mMediaRecorder = new MediaRecorder();mCameraId = cameraId;return;}} catch (CameraAccessException e) {}}/*** 根据他们的区域比较两个Size*/
static class CompareSizesByArea implements Comparator<Size> {@Overridepublic int compare(Size lhs, Size rhs) {// 我们在这里投放,以确保乘法不会溢出return Long.signum((long) lhs.getWidth() * lhs.getHeight() -(long) rhs.getWidth() * rhs.getHeight());}}private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {// 收集支持的分辨率,这些分辨率至少与预览图面一样大List<Size> bigEnough = new ArrayList<>();// 收集小于预览表面的支持分辨率List<Size> notBigEnough = new ArrayList<>();int w = aspectRatio.getWidth();int h = aspectRatio.getHeight();for (Size option : choices) {if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&option.getHeight() == option.getWidth() * h / w) {if (option.getWidth() >= textureViewWidth &&option.getHeight() >= textureViewHeight) {bigEnough.add(option);} else {notBigEnough.add(option);}}}//挑一个足够大的最小的。如果没有一个足够大的,就挑一个不够大的。if (bigEnough.size() > 0) {return Collections.min(bigEnough, new CompareSizesByArea());} else if (notBigEnough.size() > 0) {return Collections.max(notBigEnough, new CompareSizesByArea());} else {Log.d(TAG, "Couldn't find any suitable preview size");return choices[0];}
}private void configureTransform(int viewWidth, int viewHeight) {if (null == mTextureView || null == mPreviewSize) {return;}int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();Matrix matrix = new Matrix();RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());float centerX = viewRect.centerX();float centerY = viewRect.centerY();if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);float scale = Math.max((float) viewHeight / mPreviewSize.getHeight(),(float) viewWidth / mPreviewSize.getWidth());matrix.postScale(scale, scale, centerX, centerY);matrix.postRotate(90 * (rotation - 2), centerX, centerY);} else if (Surface.ROTATION_180 == rotation) {matrix.postRotate(180, centerX, centerY);}mTextureView.setTransform(matrix);
}/*** 将JPG保存到指定的文件中。*/
private static class ImageSaver implements Runnable {/*** JPEG图像*/private final Image mImage;/*** 保存图像的文件*/private final File mFile;public ImageSaver(Image image, File file) {mImage = image;mFile = file;}@Overridepublic void run() {ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();byte[] bytes = new byte[buffer.remaining()];buffer.get(bytes);FileOutputStream output = null;try {output = new FileOutputStream(mFile);output.write(bytes);} catch (IOException e) {e.printStackTrace();} finally {mImage.close();if (null != output) {try {output.close();} catch (IOException e) {e.printStackTrace();}}}}}

}
(3).自定义view圆环进度条CircleVideoProgressBar,实现类似微信那样的,按住抓拍视频,可以自行设置最多录像时间,写的可能不太好,但是你们也可以自己根据需求重写一个,不多说了,上代码。

public class CircleVideoProgressBar extends View {private int mCurrent;//当前进度private Paint mBgPaint;//背景弧线paintprivate Paint mProgressPaint;//进度Paintprivate float mProgressWidth;//进度条宽度private int mProgressColor = Color.RED;//进度条颜色private int locationStart;//起始位置private float startAngle;//开始角度private ValueAnimator mAnimator;private int mMaxStepNum;//默认最大步数(最大值)public static String GOAL_STEP;
public CircleVideoProgressBar(Context context) {this(context, null);
}public CircleVideoProgressBar(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);
}public CircleVideoProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context, attrs);
}private void init(Context context, AttributeSet attrs) {TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView);locationStart = typedArray.getInt(R.styleable.CircleProgressView_location_start, 1);mProgressWidth = typedArray.getDimension(R.styleable.CircleProgressView_progress_width, WindowDispaly.dip2px(context, 6));mProgressColor = typedArray.getColor(R.styleable.CircleProgressView_progress_color, mProgressColor);typedArray.recycle();//背景圆弧mBgPaint = new Paint();mBgPaint.setAntiAlias(true);mBgPaint.setStrokeWidth(mProgressWidth);mBgPaint.setStyle(Paint.Style.STROKE);mBgPaint.setColor(Color.parseColor("#ebebeb"));mBgPaint.setStrokeCap(Paint.Cap.ROUND);//进度圆弧mProgressPaint = new Paint();mProgressPaint.setAntiAlias(true);mProgressPaint.setStyle(Paint.Style.STROKE);mProgressPaint.setStrokeWidth(mProgressWidth);mProgressColor = context.getResources().getColor(R.color.color_ff3030);mProgressPaint.setColor(mProgressColor);mProgressPaint.setStrokeCap(Paint.Cap.ROUND);//进度条起始角度if (locationStart == 1) {//左startAngle = -180;} else if (locationStart == 2) {//上startAngle = -90;} else if (locationStart == 3) {//右startAngle = 0;} else if (locationStart == 4) {//下startAngle = 90;}
}@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int width = MeasureSpec.getSize(widthMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);int size = width < height ? width : height;setMeasuredDimension(size, size);
}/*** oval  // 绘制范围* startAngle  // 开始角度* sweepAngle  // 扫过角度* useCenter   // 是否使用中心*/
@Override
protected void onDraw(Canvas canvas) {//绘制背景圆弧RectF rectF = new RectF(mProgressWidth / 2, mProgressWidth / 2, getWidth() - mProgressWidth / 2, getHeight() - mProgressWidth / 2);canvas.drawArc(rectF, 0, 360, false, mBgPaint);//绘制当前进度float sweepAngle = 360 * mCurrent / mMaxStepNum;canvas.drawArc(rectF, startAngle, sweepAngle, false, mProgressPaint);
}public int getCurrent() {return mCurrent;
}/*** 设置进度** @param current*/
public void setCurrent(int current) {mCurrent = current;invalidate();
}/*** @param stepNum*/
public void setMaxStepNum(int stepNum) {mMaxStepNum = stepNum;GOAL_STEP = String.valueOf(mMaxStepNum);
}private int tCurrent = -1;/*** 动画效果** @param current  精度条进度:0-100* @param duration 动画时间*/
public void startAnimProgress(int current, int duration) {mAnimator = ValueAnimator.ofInt(0, current);mAnimator.setDuration(duration);mAnimator.setInterpolator(new LinearInterpolator());mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {int current = (int) animation.getAnimatedValue();long time = animation.getCurrentPlayTime();LogUtils.e("运行动画时长:"+time);if (tCurrent != current) {tCurrent = current;setCurrent(current);LogUtils.e("进度:"+current);if (mOnAnimProgressListener != null)mOnAnimProgressListener.valueUpdate(current);}}});mAnimator.start();
}public interface OnAnimProgressListener {void valueUpdate(int progress);
}private OnAnimProgressListener mOnAnimProgressListener;/*** 监听进度条进度** @param onAnimProgressListener*/
public void setOnAnimProgressListener(OnAnimProgressListener onAnimProgressListener) {mOnAnimProgressListener = onAnimProgressListener;
}public void destroy() {if (mAnimator != null) {mAnimator.cancel();}
}

}
(4)调用 Intent intent = new Intent(this, VideoRecordActivity.class);
startActivityForResult(intent, 100);

 public class VideoRecordActivity extends AppCompatActivity {@BindView(R.id.textureview)CircleTextureView textureview;@BindView(R.id.tv_reset)TextView tvReset;@BindView(R.id.cv_pb)CircleVideoProgressBar cvPb;@BindView(R.id.tv_video)TextView tvVideo;@BindView(R.id.tv_ok)TextView tvOk;private CameraController mCameraController;private boolean mIsRecordingVideo; //开始停止录像public static final String VIDEO_BASE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera";private long mCurrentTouchTime;private long mOverTouchTime;private String videoPath;private Handler handler = new Handler() {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);switch (msg.what) {case 10:if (mIsRecordingVideo) {mCameraController.stopRecordingVideo();mIsRecordingVideo = false;}break;}}};@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_video_record);ButterKnife.bind(this);//获取相机管理类的实例mCameraController = CameraController.getmInstance(this);mCameraController.setFolderPath(VIDEO_BASE_PATH);cvPb.setMaxStepNum(100);initOnTouchListener();}
private void initOnTouchListener() {tvVideo.setOnTouchListener((view, event) -> {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:if (!mIsRecordingVideo) {tvOk.setVisibility(View.INVISIBLE);mCurrentTouchTime = System.currentTimeMillis();cvPb.startAnimProgress(100, 6000);mCameraController.startRecordingVideo();tvVideo.setText(" ");mIsRecordingVideo = true;}break;case MotionEvent.ACTION_UP:if (mIsRecordingVideo) {mOverTouchTime = System.currentTimeMillis();long time = mOverTouchTime - mCurrentTouchTime;if (time < 1000) {handler.sendEmptyMessageDelayed(10, 1000);tvVideo.setText("按住认证");cvPb.destroy();cvPb.setCurrent(0);ToastUtils.showLongToast2(this, "请按住按钮录制认证视频");} else if (time < 4000) {cvPb.destroy();cvPb.setCurrent(0);mCameraController.stopRecordingVideo();tvVideo.setText("按住认证");mIsRecordingVideo = false;ToastUtils.showLongToast2(this, "录制认证视频过短");} else {cvPb.destroy();mCameraController.stopRecordingVideo();mIsRecordingVideo = false;tvVideo.setText("录制成功");tvOk.setVisibility(View.VISIBLE);videoPath = mCameraController.getSavePath();Intent intent = new Intent();intent.putExtra("videoPath", videoPath);setResult(RESULT_OK,intent);finish();}}break;}return true;});cvPb.setOnAnimProgressListener(progress -> {if (progress == 100) {if (mIsRecordingVideo) {mCameraController.stopRecordingVideo();mIsRecordingVideo = false;tvVideo.setText("录制成功");tvOk.setVisibility(View.VISIBLE);videoPath = mCameraController.getSavePath();Intent intent = new Intent();intent.putExtra("videoPath", videoPath);setResult(RESULT_OK,intent);finish();}}});
}@OnClick({R.id.tv_ok})
public void onClick(View view) {switch (view.getId()) {case R.id.tv_ok:handler.removeMessages(10);finish();break;}
}@Override
public void onBackPressed() {super.onBackPressed();handler.removeMessages(10);
}@Override
protected void onResume() {super.onResume();mCameraController.InitCamera(textureview);
}@Override
protected void onDestroy() {super.onDestroy();if (cvPb != null) {cvPb.destroy();}
}

}

最后附带工具类、attr、videoRecordActivity xml代码:
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/

  public static int dip2px(Context context, float dpValue) {final float scale = context.getResources().getDisplayMetrics().density;return (int) (dpValue * scale + 0.5f);}
    <!--圆弧进度条--><declare-styleable name="TasksCompletedView"><attr name="radius" format="dimension"/><attr name="strokeWidth" format="dimension"/><attr name="circleColor" format="color"/><attr name="ringColor" format="color"/><attr name="ringBgColor" format="color"/></declare-styleable><!--圆弧进度条2--><declare-styleable name="circleProgressBar"><attr name="circleWidth" format="dimension" /><attr name="betaAngle" format="integer" /><attr name="firstColor" format="color" /><attr name="secondColor" format="color" /></declare-styleable><!--圆弧进度条3--><declare-styleable name="CircleProgressView"><!--画笔宽度--><attr name="progress_width" format="dimension" /><!--画笔颜色--><attr name="progress_color" format="color" /><!--加载进度起始位置--><attr name="location_start" format="enum"><enum name="left" value="1" /><enum name="top" value="2" /><enum name="right" value="3" /><enum name="bottom" value="4" /></attr></declare-styleable><!--圆弧进度条4--><declare-styleable name="CircularProgressView"><attr name="ringWidth" format="dimension" /><attr name="ringColors" format="color" /><attr name="progressTitle" format="string" /></declare-styleable>

activity_video_record.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#000000"><CircleTextureViewandroid:id="@+id/textureview"android:layout_width="300dp"android:layout_height="300dp"android:layout_alignParentTop="true"android:layout_centerHorizontal="true"android:layout_marginTop="100dp" /><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="100dp"android:layout_alignParentBottom="true"android:layout_gravity="center"android:background="@color/white"android:gravity="center"><TextViewandroid:id="@+id/tv_reset"android:layout_width="60dp"android:layout_height="match_parent"android:layout_alignParentLeft="true"android:gravity="center"android:text="撤回"android:textColor="@color/color_3333333"android:visibility="invisible" /><CircleVideoProgressBarandroid:id="@+id/cv_pb"android:layout_width="80dp"android:layout_height="80dp"android:layout_centerInParent="true" /><TextViewandroid:id="@+id/tv_video"android:layout_width="74dp"android:layout_height="74dp"android:layout_centerInParent="true"android:background="@drawable/shape_oval_transparent"android:gravity="center"android:textSize="12sp" /><TextViewandroid:id="@+id/tv_ok"android:layout_width="60dp"android:layout_height="match_parent"android:layout_alignParentRight="true"android:layout_marginRight="30dp"android:gravity="center"android:text="完成"android:textColor="@color/color_ff3030"android:visibility="invisible" /></RelativeLayout>
</RelativeLayout>

android自定义相机取景框模仿微信抓取录像实现活体认证相关推荐

  1. android 实现自动拍照,Android自定义相机实现定时拍照功能

    这篇博客为大家介绍Android自定义相机,并且实现倒计时拍照功能. 首先自定义拍照会用到SurfaceView控件显示照片的预览区域,以下是布局文件: activity_main.xml andro ...

  2. android 自定义相机源码,Android 自定义相机及分析源码

    Android 自定义相机及分析源码 使用Android 系统相机的方法: 要想让应用有相机的action,咱们就必须在清单文件中做一些声明,好让系统知道,如下 action的作用就是声明action ...

  3. android自动对焦第一次对焦,Android自定义相机实现自动对焦和手动对焦

    Android自定义相机实现自动对焦和手动对焦: 不调用系统相机,因为不同的机器打开相机呈现的界面不统一也不能满足需求. 所以为了让程序在不同的机器上呈现出统一的界面,并且可以根据需求进行布局,做了此 ...

  4. Android自定义相机实现定时拍照

    这篇博客为大家介绍Android自定义相机,并且实现倒计时拍照功能. 首先自定义拍照会用到SurfaceView控件显示照片的预览区域,以下是布局文件: activity_main.xml <F ...

  5. Android 自定义相机Demo 入门学习

    Android 自定义相机Demo 本文是参考网上一些自定义相机示例,再结合自己对相机的功能需求写的,基本上包含了很多基本功能,比如相机对焦.闪光灯,以及在手机预览界面上绘制自己想要绘制的图案. 话不 ...

  6. Android自定义相机拍照、图片裁剪的实现

    原文:Android自定义相机拍照.图片裁剪的实现 最近项目里面又要加一个拍照搜题的功能,也就是用户对着不会做的题目拍一张照片,将照片的文字使用ocr识别出来,再调用题库搜索接口搜索出来展示给用户,类 ...

  7. Android自定义相机,切换前后摄像头,照相机拍照

    Android自定义相机,简单实现切换前后摄像头,照相机拍照 Ctrl +C  Ctrl+V 可以直接 run 起来,注释比较详细;源码下载 <?xml version="1.0&qu ...

  8. android 自定义相机,Android自定义相机实现定时拍照功能

    这篇博客为大家介绍Android自定义相机,并且实现倒计时拍照功能. 首先自定义拍照会用到SurfaceView控件显示照片的预览区域,以下是布局文件: activity_main.xml andro ...

  9. Android 在相机加框,android自定义相机带取景框

    [实例简介] 自定义相机,带取景框和照片预览 [实例截图] [核心代码] 自定义相机 └── CustomViewForClock-master ├── app │   ├── app.iml │   ...

  10. Android 自定义相机 身份证拍照 自定义身份证相机

    项目中需要用到拍摄身份证,拍完照片后直接拿到和身份证比例一致的图片,做成功的结果如下:    拍完照后直接拿到裁剪好的图本文的核心技术来自: https://yq.aliyun.com/article ...

最新文章

  1. 计算机科学与技术类高水平国际学术刊物,莘莘学子 | 计算机科学与技术学院本科生薛传雨在国际期刊上发表高水平学术论文...
  2. 用Flutter改造ZS项目小记一:界面显示一张图片
  3. 国内经济学硕士 申国外计算机硕士,一个经济硕士留学美国的视角
  4. FB 宕机,Telegram 用户疯涨,P**hub 流量猛增
  5. OSPF简单多区域及末梢区域配置
  6. qmake生成vs2013工程文件
  7. ZeroC ICE源代码中的那些事 - 嵌套类和局部类
  8. 使用Python scikit-learn 库实现神经网络算法
  9. 计算机专业的口号运动会四字,计算机系运动会口号
  10. 常用的C#正则表达式! [转]
  11. mysql 获取下一条记录数,如何在MySQL中查询当前数据上一条和下一条的记录
  12. vue 初始化请求例子_Vue实例初始化
  13. 【Flask】 结合wtforms的文件上传表单
  14. 计算机专用英语1500词带音标,带音标的计算机英语1500词
  15. 开机后黑屏看不到桌面_开机不显示桌面黑屏怎么办_win10开机黑屏啥也没有的解决办法...
  16. 穆迪将收购GCR Ratings多数股权以拓展非洲业务
  17. MacOS 安装 gstreamer 最新版本(1.20.0)
  18. MybatisPlus代码生成器实现只覆盖指定文件
  19. 软件设计师---UML
  20. 用java实现简单的搜索引擎

热门文章

  1. WebGL切换着色器 绘制不同物体
  2. Android 84、gc、高德、百度、墨卡托地理坐标转换
  3. Word中插入目录时未找到目录项
  4. 十六进制颜色与RGB颜色对照表
  5. python 流程结构练习
  6. 基于Basys3设计的FPGA多功能电子琴
  7. C语言学生宿舍水电费信息管理系统
  8. Tensorflow教程之语音识别
  9. 各种坐标之间的转换方法汇总
  10. kdj指标计算程序代码