您现在的位置是:网站首页> 编程资料编程资料
JetPack开发中使用CameraX完成拍照和拍视频功能_其它综合_
2023-05-27
428人已围观
简介 JetPack开发中使用CameraX完成拍照和拍视频功能_其它综合_
前段时间CameraX的Beta版发布了,这几天有时间也来尝试一下。Beta版本是对外测试版本,意味着它已经走出实验室走向生产,API的调用基本稳定不会大改了,bug也会更少可以用于生成环境。
之前使用Camera1和Camera2开发相机功能的时候需要调用非常复杂的API,而且由于Android手机的碎片化严重,不同手机对相机功能的支持度也不一样,因此很多做相机相关应用的公司都会封装自己的相机库来简化相机的使用步骤和处理兼容性问题。
CameraX其实就是Google开发的一个用来简化相机开发时候API的调用和处理各种兼容性问题的库。最多兼容到Android 5.0,底层调用的也是Camera2,不过比Camera2用起来更简单,而且可以绑定生命周期,从而可以自动的处理相机的开启释放等工作。
下面开始来尝试吧
添加依赖
dependencies { // CameraX 核心库使用 camera2 实现 implementation "androidx.camera:camera-camera2:1.0.0-beta03" // 可以使用CameraView implementation "androidx.camera:camera-view:1.0.0-alpha10" // 可以使用供应商扩展 implementation "androidx.camera:camera-extensions:1.0.0-alpha10" //camerax的生命周期库 implementation "androidx.camera:camera-lifecycle:1.0.0-beta03" }
如果想要使用CameraX拍照非常简单,只需要配置不同的使用状态,然后绑定到生命周期中即可。比如预览需要设置预览相关的状态,拍照需要设置拍照相关的状态,录制视频需要设置录制相关的状态。
配置状态
预览配置:Preview用于相机预览的时候显示预览画面。
Preview preview = new Preview.Builder() //设置宽高比 .setTargetAspectRatio(screenAspectRatio) //设置当前屏幕的旋转 .setTargetRotation(rotation) .build();
照相配置:ImageCapture 用于拍照,并将图片保存
ImageCapture imageCapture = new ImageCapture.Builder() //优化捕获速度,可能降低图片质量 .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) //设置宽高比 .setTargetAspectRatio(screenAspectRatio) //设置初始的旋转角度 .setTargetRotation(rotation) .build();
录制视频设置:VideoCapture 用来录制视频和保存视频,宽高比和分辨率设置一个就可以了,不要同时设置否则报错。根据实际的需求来设置,如果对宽高比要求高就设置宽高比,反之就设置分辨率
VideoCapture videoCapture = new VideoCaptureConfig.Builder() //设置当前旋转 .setTargetRotation(rotation) //设置宽高比 .setTargetAspectRatio(screenAspectRatio) //分辨率 //.setTargetResolution(resolution) //视频帧率 越高视频体积越大 .setVideoFrameRate(25) //bit率 越大视频体积越大 .setBitRate(3 * 1024 * 1024) .build();
绑定到生命周期:ProcessCameraProvider 是一个单例类,可以把相机的生命周期绑定到任何LifecycleOwner类中。AppCompatActivity和Fragment都是LifecycleOwner
//Future表示一个异步的任务,ListenableFuture可以监听这个任务,当任务完成的时候执行回调 ListenableFuturecameraProviderFuture = ProcessCameraProvider.getInstance(this); ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); //重新绑定之前必须先取消绑定 cameraProvider.unbindAll(); Camera camera = cameraProvider.bindToLifecycle(CameraActivity.this, cameraSelector,preview,imageCapture,videoCapture);
OK预览,照相,录视频的配置和绑定到生命周期的工作就完成了
预览的时候需要显示到一个View控件上吧,CameraX中提供了一个PreviewView用来显示预览画面。其内部封装了TextureView和SurfaceView,可以根据不同的模式来选择其内部使用TextureView还是SurfaceView来显示。
xml中添加PreviewView,并在代码中将其附加到前面创建出来的Preview这个实例上
preview.setSurfaceProvider(mPreviewView.createSurfaceProvider(camera .getCameraInfo()));
这样当我们进入该页面的时候就可以看到相机的预览效果呢,接下来就是执行拍照和录制的功能了
执行拍照录像
拍照:
//创建图片保存的文件地址 File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath(), System.currentTimeMillis() + ".jpeg"); ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build(); mImageCapture.takePicture(outputFileOptions,mExecutorService , new ImageCapture.OnImageSavedCallback() { @Override public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { Uri savedUri = outputFileResults.getSavedUri(); if(savedUri == null){ savedUri = Uri.fromFile(file); } outputFilePath = file.getAbsolutePath(); onFileSaved(savedUri); } @Override public void onError(@NonNull ImageCaptureException exception) { Log.e(TAG, "Photo capture failed: "+exception.getMessage(), exception); } }); //将前面保存的文件添加到媒体中 private void onFileSaved(Uri savedUri) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { sendBroadcast(new Intent(android.hardware.Camera.ACTION_NEW_PICTURE, savedUri)); } String mimeTypeFromExtension = MimeTypeMap.getSingleton().getMimeTypeFromExtension(MimeTypeMap .getFileExtensionFromUrl(savedUri.getPath())); MediaScannerConnection.scanFile(getApplicationContext(), new String[]{new File(savedUri.getPath()).getAbsolutePath()}, new String[]{mimeTypeFromExtension}, new MediaScannerConnection.OnScanCompletedListener() { @Override public void onScanCompleted(String path, Uri uri) { Log.d(TAG, "Image capture scanned into media store: $uri"+uri); } }); PreviewActivity.start(this, outputFilePath, !takingPicture); }
- 调用ImageCapture的takePicture方法来拍照
- 传入一个文件地址用来保存拍好的照片
- onImageSaved方法是照片已经拍好并存好之后的回调
- onFileSaved方法中将前面保存的文件添加到媒体中,最后跳转到预览界面。
录视频:
//创建视频保存的文件地址 File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath(), System.currentTimeMillis() + ".mp4"); mVideoCapture.startRecording(file, Executors.newSingleThreadExecutor(), new VideoCapture.OnVideoSavedCallback() { @Override public void onVideoSaved(@NonNull File file) { outputFilePath = file.getAbsolutePath(); onFileSaved(Uri.fromFile(file)); } @Override public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) { Log.i(TAG,message); } });
videoCapture.stopRecording();
- 使用VideoCapture的startRecording方法来录视频
- 传入一个File文件用来保存视频,
- 录制完成之后回调onVideoSaved方法,并返回该文件的实例。
- 调用onFileSaved方法将前面保存的文件添加到媒体中,最后跳转到预览界面。
- 到达录制时间的时候,需要调用videoCapture.stopRecording();方法来停止录像。
到这里使用CameraX拍照和录制视频的功能都能完成了,是不是非常简单。下面来点题外的,自定义一个View,实现点击拍照,长按录像的效果。效果如下:
代码:
public class RecordView extends View implements View.OnLongClickListener, View.OnClickListener { private static final int PROGRESS_INTERVAL = 100; private int mBgColor; private int mStrokeColor; private int mStrokeWidth; private int mDuration; private int mWidth; private int mHeight; private int mRadius; private int mProgressValue; private boolean isRecording; private RectF mArcRectF; private Paint mBgPaint, mProgressPaint; private OnRecordListener mOnRecordListener; private long mStartRecordTime; public void setOnRecordListener(OnRecordListener onRecordListener) { mOnRecordListener = onRecordListener; } public RecordView(Context context) { this(context, null); } public RecordView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public RecordView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RecordView); mBgColor = typedArray.getColor(R.styleable.RecordView_bg_color, Color.WHITE); mStrokeColor = typedArray.getColor(R.styleable.RecordView_stroke_color, Color.RED); mStrokeWidth = typedArray.getDimensionPixelOffset(R.styleable.RecordView_stroke_width, SizeUtils.dp2px(5)); mDuration = typedArray.getInteger(R.styleable.RecordView_duration, 10); mRadius = typedArray.getDimensionPixelOffset(R.styleable.RecordView_radius, SizeUtils.dp2px(40)); typedArray.recycle(); mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBgPaint.setStyle(Paint.Style.FILL); mBgPaint.setColor(mBgColor); mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mProgressPaint.setStyle(Paint.Style.STROKE); mProgressPaint.setColor(mStrokeColor); mProgressPaint.setStrokeWidth(mStrokeWidth); setEvent(); } private void setEvent() { Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); mProgressValue++; postInvalidate(); if (mProgressValue < mDuration*10) { sendEmptyMessageDelayed(0, PROGRESS_INTERVAL); } else { finishRecord(); } } }; setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if(event.getAction() == MotionEvent.ACTION_DOWN){ mStartRecordTime = System.currentTimeMillis(); handler.sendEmptyMessage(0); }else if(event.getAction() == MotionEvent.ACTION_UP){ long duration = System.currentTimeMillis() - mStartRecordTime; //是否大于系统设定的最小长按时间 if(duration > ViewConfiguration.getLongPressTimeout()){ finishRecord(); } handler.removeCallbacksAndMessages(null); isRecording = false; mStartRecordTime = 0; mProgressValue = 0; postInvalidate(); } return false; } }); setOnClickListener(this); setOnLongClickListener(this); } private void finishRecord() { if(mOnRecordListener!=null){ mOnRecordListener.onFinish(); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = w; mArcRectF = new RectF(mStrokeWidth / 2f, mStrokeWidth / 2f, mWidth - mStrokeWidth / 2f, mHeight - mStrokeWidth / 2f); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(mWidth / 2f, mHeight / 2f, mRadius, mBgPaint); if (isRecording) { canvas.drawCircle(mWidth / 2f, mHeight / 2f, mRadius/10f*11, mBgPaint); float sweepAngle = 360f * mProgressValue / (mDuration*10); Log.i("sweepAngle",sweepAngle+""); canvas.drawArc(mArcRectF, -90, sweepAngle, false, mProgressPaint); } } @Override public boolean onLongClick(View v) { isRecording = true; if(mOnRecordListener!=null){ mOnRecordListener.onRecordVideo(); } return true; } @Override public void onClick(View v) { if(mOnRecordListener!=null){ mOnRecordListener.onTackPicture(); } } public interface OnRecordListener { void onTackPicture(); void onRecordVideo(); void onFinish(); } }
实现起来也非常简单,首先绘制一个圆,监听该View的点击和长按事件,长按的时候在根据总录制时长和当前录制时间算出需要绘制的角度,就可以在圆上面
相关内容
- 在Windows系统上安装Cygwin搭建Swoole测试环境的图文教程_其它综合_
- 最新IDEA永久激活教程(支持最新2019.2版本)_其它综合_
- SpringBoot + Vue + Electron 开发 QQ 版聊天工具的详细教程_其它综合_
- 解决Chrome在新版MacOS上报错 NET::ERR_CERT_WEAK_KEY 的问题_其它综合_
- Loongnix安装PyCharm Community 2020.2.3的教程详解_其它综合_
- WebStorm安装配置教程_其它综合_
- 最新IntelliJ IDEA 2020.2永久激活码(亲测有效)_其它综合_
- 支付宝小程序向用户发红包的实现方法_其它综合_
- 解决使用IDE Run运行出错package pack/test is not in GOROOT (/usr/local/go/src/pack/test)_其它综合_
- 一看就懂的IDEA编辑器 .http教程详解_其它综合_